LA AMBIGUEDAD, medida. En el canon `size` no es ambiguo: es «el eje de tamano de
ESTE componente», y cada uno lo materializa a su manera — padding de bloque en
`Section`, densidad de la tira en `Banner`, escala tipografica en `Text`. La
ambiguedad la creaba el tier: un block no es ninguno de esos componentes, y al
reenviar `size` estaba reenviando el eje de una pieza INTERNA. Desde fuera:
<FeatureGrid size="xl"> cambia el AIRE de la seccion
<SiteBanner size="lg"> cambia la ALTURA de la tira
Misma prop, dos cosas, y una con un valor menos en la escala (`BannerSize` no
tiene `xl`). Un consumidor que aprende el eje en trece blocks se equivoca en el
catorceavo.
EL RENOMBRADO:
- `size` -> `sectionSize` en los 13 que envuelven `Section`
- `container` -> `containerSize` en los 14 que envuelven `Container`
- `size` queda LIBRE y solo lo usa `banner`, donde si es el tamano del block
- `minColumnWidth` -> `minChildWidth` en `site-footer`: el mismo concepto tenia
dos nombres en el tier, y ninguno era el del canon
El patron no se invento: el tier YA nombraba un eje por su pieza (`container`).
`containerSize` + `sectionSize` lo hace explicito y simetrico.
LA REGLA, en `architecture/blocks.md` §Conventions, para que no reaparezca: un
block que reenvia el eje de tamano de una pieza interna lo nombra `{pieza}Size`,
y `size` se reserva para el tamano del block. Corolario: un eje que el canon ya
nombra se reenvia CON SU NOMBRE.
METODO — se renombraron los TIPOS primero y se dejo que `svelte-check` senalara
cada consumidor, en vez de buscar a mano. Cazo los cinco call sites que quedaban
(`CtaSite`, `HeroSite`, `SiteHeaderSite`, `TeamSite` y un uso suelto en
`cta.svelte`). ⚠️ Y cazo tambien un error propio: el primer patron era demasiado
ancho y renombro `size` en SIETE sub-partes donde ese `size` es el del `Card` o
el `Text` que envuelven (`testimonials-item`, `contact-reason`,
`team-member-role`…). Revertidas antes de seguir.
DOCUMENTACION revisada entera: corregidas las tres filas de README que describian
props del block con el nombre viejo (`hero` x2, `site-header`); conservadas las
seis que hablan del `size` de un componente del canon (`Banner`, `Accordion`,
`Heading`), que no cambia. `PLAN-blocks.md` se deja intacto: es bitacora, y
reescribir el registro historico para que cuadre con el presente lo falsearia.
LEDGER — el contraste doctrinal llega a 15/15, y cinco filas nuevas:
- A-95 `banner`: con `affix="top"` el aviso tapa la cabecera pegada (medido:
`elementFromPoint` sobre el header devuelve la tira). NO se arregla aqui: la
pieza que falta es la que POSEE las alturas de pagina — Mantine lo resuelve en
`AppShell`, que declara `header={{ height }}` y desplaza el resto; nuestro
equivalente es el block `app-shell`, sin construir.
- A-96 `banner`: `affixOffset` es prop publica sin control vivo en la demo.
- A-97 `feature-grid`: RETIRADA el mismo dia. Se midio el texto `muted` con el
4,5:1 generico de WCAG y el proyecto tiene OTRA vara — «floors APCA >= 60 ∧
WCAG >= 3», con guard propio, y la tinta secundaria es «marginal sub-4.5:1 by
design». Re-medido con `src/arts/color/apca.ts`: Lc 63,7 y 3,70:1, cumple las
dos. (El primer intento con `apcaLc` devolvio 102666 porque le pase 0..255
donde pide 0..1: un valor fuera de rango no es un hallazgo, es un formato.)
- A-98 `hero` + A-99 `Group`: las acciones no apilan ni envuelven. La causa NO es
del tier — `Group` no aplica el `wrap` que su README promete en TRES sitios:
`group.svelte` nunca se lo pasa a `Flex`, el recipe no lo declara, y
`Omit<FlexProps,'wrap'>` impide compensarlo desde fuera. El `align: center` si
se cumple, lo que descarta que el recipe no cargue.
Y A-25 cae (la demo ya no promete elevacion) y A-47 corrige su disposicion: el
anillo de foco es «a config axis» por doctrina — endurecerlo es una decision de
VALOR en `color.focus.ring`, «never a per-component CSS change», que es
exactamente lo que hacia el intento revertido en `e468e764b`.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 72 errores /
59 avisos = linea base exacta, ninguno en blocks · docs:check 0/0 sobre 629 docs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ -68,7 +68,7 @@ Columna **or.** = de qué sección del documento viejo salió la fila (`C` confi
| A-22 | `stats-band` | doctrina | BAJA | C | El README no documenta landmark ni jerarquía de encabezados (B-8); el dato solo vive en la pestaña A11y de la demo. | `src/uix/blocks/stats-band/README.md` | ARREGLADO |
| A-23 | `pricing` | doctrina | BAJA | C | Pulsar el segmento de periodo YA ACTIVO devuelve la sección entera al periodo contrario: el block posee el estado y su propio control lo mueve a un… | `src/uix/blocks/pricing/pricing-switch.svelte` | ARREGLADO |
| A-24 | `pricing` | percepcion | BAJA | C | Las tarjetas de plan NO son de igual alto y los CTA NO se alinean: `height="100%"` sobre `Card` es un atributo HTML inerte, no una prop. El README y… | `src/uix/blocks/pricing/pricing-plan.svelte` | ARREGLADO |
| A-25 | `pricing` | doctrina | BAJA | C | La demo documenta una elevación que el código se niega explícitamente a hacer y que el propio README lista como brecha abierta del canon. | `web/routes/blocks/pricing/+page.svelte` | CONFIRMADO |
| A-25 | `pricing` | doctrina | BAJA | C | La demo documenta una elevación que el código se niega explícitamente a hacer y que el propio README lista como brecha abierta del canon. | `web/routes/blocks/pricing/+page.svelte` | ARREGLADO |
| A-26 | `pricing` | doctrina | BAJA | C | El block exige a la app tres cadenas obligatorias para nombrar el ÚNICO estado que coordina, y justifica esa exigencia citando una lectura de B-7 que… | `src/uix/blocks/pricing/pricing-switch.svelte` | REFUTADO |
| A-27 | `testimonials` | doctrina | BAJA | C | El tipo público documenta un default de `variant` que el componente no usa. | `src/uix/blocks/testimonials/types.ts` | ARREGLADO |
| A-28 | `testimonials` | doctrina | BAJA | C | `.Items` tipa toda la superficie de `AutoGrid` pero descarta en silencio parte de ella: el spread del consumidor va ANTES de props fijadas a mano. | `src/uix/blocks/testimonials/testimonials-items.svelte` | ARREGLADO |
@ -138,6 +138,11 @@ Columna **or.** = de qué sección del documento viejo salió la fila (`C` confi
| A-92 | `pricing` | doctrina | BAJA | F7 | Una restricción estructural del modelo de cascada (sólo los presets CSS participan del stagger) vive en un comentario del block y falta en §D.13 de la doctrina… | `docs/theming/motion.md` | CONFIRMADO |
| A-93 | `stats-band` | doctrina | MEDIA | F7 | `Motion` no expone su momento «visto» (privado + `data-animation-pending`), así que un block no puede sincronizarse con el revelado sin violar B-6… | `src/uix/eidos/components/motion/motion.svelte` | CONFIRMADO |
| A-94 | `feature-grid · team · testimonials` | doctrina | MEDIA | F7 | Los tres declaran `= AutoGridProps` y ponen `{...rest}` antes de props fijadas: el consumidor tipa `align`/`width` y se descartan en silencio (A-28 ×3)… | `src/uix/blocks/testimonials/types.ts` | ARREGLADO |
| A-95 | `banner` | doctrina | MEDIA | F7 | Con `affix="top"` la tira fijada tapa la cabecera pegada y la navegación queda inalcanzable; la pieza que falta es la que posee las alturas de página (`app-shell`)… | `web/routes/blocks/banner/BannerSite.svelte` | CONFIRMADO |
| A-96 | `banner` | doctrina | BAJA | F7 | `affixOffset` es prop público sin control vivo en la demo, contra la regla «cada prop público, un control»… | `web/routes/blocks/banner/+page.svelte` | CONFIRMADO |
| A-97 | `feature-grid + 6 blocks` | percepcion | MEDIA | F7 | El texto `color="muted"` mide 3,70:1 en claro — bajo AA de cuerpo — en 8 sitios de 7 blocks; el hallazgo estaba enterrado en la ficha de A-70, que es REFUTADO… | `src/uix/blocks/feature-grid/feature-grid-item-text.svelte` | REFUTADO |
| A-98 | `hero` | percepcion | MEDIA | F7 | Las acciones no apilan en móvil: `Group` es row/nowrap y a 375px los dos botones ocupan 315 de 327px; `cta` ya lo resolvió con `Flex` responsive… | `src/uix/blocks/hero/hero.svelte` | CONFIRMADO |
| A-99 | `Group` (canon) | doctrina | MEDIA | F7 | `Group` no aplica el `wrap` que su README promete en tres sitios: no lo pasa a `Flex`, no lo declara el recipe, y `Omit<FlexProps,'wrap'>` impide compensarlo… | `src/uix/eidos/components/group/group.svelte` | CONFIRMADO |
## Reparto
@ -352,6 +357,85 @@ Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header
### A-95 — CONFIRMADO · `banner` · doctrina · MEDIA
**Mecanismo** — Con `affix="top"` la tira fijada TAPA la cabecera pegada, y la navegación del sitio queda inalcanzable mientras el aviso siga visible. Medido en `/blocks/banner/preview?affix=top` a 1280×700 con la página desplazada 800px: la tira ocupa `0..54` y el `<header>` del `site-header``0..31` —se solapan—, y `document.elementFromPoint` en el centro del header devuelve LA TIRA. El apilamiento en sí es correcto y está firmado: `Affix` monta en el peldaño `--viewport-placement-z` = 150, «encima de un sticky header, debajo de menús y diálogos», y el escenario lo dice («las dos compiten por el mismo borde — por eso el aviso va por encima del cromo»). El escenario ya compensa con `padding-block-start: 3.5rem` en la página, pero eso empuja el CONTENIDO: un sticky con `top: 0` se sigue anclando donde está la tira.
**La solución NO está en el banner, y ahí estaba el error de análisis.** La primera propuesta fue pasar `offset` al `site-header` (que lo expone, en px), y se cayó con una pregunta del usuario: «¿y si tenemos banners a diferentes alturas?». Cualquier número acordado se rompe con `size="lg"`, con el mensaje envolviendo a dos líneas en móvil, o con dos avisos.
**Cómo lo resuelve la referencia** (verificado en su documentación, 2026-08-15): Mantine NO usa `Affix` para esto —su `Affix` es «renders children inside portal at fixed position», para elementos flotantes puntuales tipo scroll-to-top—. Lo resuelve en **`AppShell`**: todas las secciones son `position: fixed`, las alturas se DECLARAN una vez en el shell (`header={{ height: 60 }}`, aceptando objeto con breakpoints) y `AppShell.Main` es «statically positioned and **offset by the other sections**». Su configuración lleva además `collapsed` —«the section is hidden from the viewport and **doesn't affect the Main offset**»— y `offset?: boolean`, así que un aviso descartado deja de contar para el desplazamiento sin que nadie recalcule nada.
**Disposición** — **NO se arregla en `banner` ni en `site-header`**: la pieza que falta es la que POSEE las alturas de página y propaga los offsets, y en el plan del tier es el block `app-shell` (F3.1, sin construir). Hasta que exista, cualquier solución en el banner es un número acordado con otro nombre. Lo que sí procede ya, y es documental: que el README del block diga que `affix="top"` no está pensado para convivir con una cabecera pegada — para eso, hasta que haya shell, el app agrupa aviso y cabecera en un solo `Sticky` (⚠️ esa alternativa está RAZONADA, no medida).
### A-96 — CONFIRMADO · `banner` · doctrina · BAJA
**Mecanismo** — `affixOffset` es prop público y no tiene control vivo en la demo; sólo lo tiene `affix`. La regla del tier es «cada prop público, un control vivo» (B-9 + la doctrina de demos del handoff).
**Disposición** — Un control más, o declararlo en la ficha de la API como omitido a propósito (podría serlo: un selector de longitud CSS es ruido en una fila de chips).
**Mecanismo** — El texto que los blocks pintan con `color="muted"` mide **3,70:1** sobre el lienzo de página en claro, por debajo del suelo AA de cuerpo (4,5:1). Medido hoy en `/blocks/feature-grid/preview`: la descripción de cada celda (`feature-grid-item-text.svelte:12`, `Text color="muted"`, 16px) resuelve tinta `rgb(131,131,131)` sobre fondo `rgb(252,252,252)` = 3,70:1. Es EXACTAMENTE el mismo número que midió el verificador de la fase 2, y ahí está el problema de proceso: ese hallazgo vive enterrado dentro de la ficha de **A-70, que está marcada REFUTADO**. La reclamación de A-70 («feature-grid no tiene nada que auditar perceptivamente») se refutó correctamente, pero el verificador encontró de paso ESTO y nunca se le dio fila propia, así que el recuento del ledger lo daba por inexistente.
**No es de un block: es del tier.** Censo de `color="muted"` en `src/uix/blocks/`: **8 sitios en 7 blocks** — `feature-grid-item-text` (16px, cuerpo) · `feature-split-text` (lg) · `content-section` lede (lg) y meta (sm) · `content-section-media` caption (sm) · `pricing-plan-description` (sm) · `pricing-plan-price` suffix · `testimonials-author-role` (sm). El ratio es idéntico en todos —mismo token, mismo lienzo—; lo que cambia es el umbral aplicable: a 16px y a `sm` es 4,5:1, así que fallan; sólo los `lg` podrían salvarse si superan los 18,66px que bajan el umbral a 3:1.
**Evidencia** — Medición con composición hecha por el navegador (canvas) en `/blocks/feature-grid/preview`, 1280×900, tema claro. ⚠️ La medición en oscuro NO vale: dio el mismo valor, señal de que la preview no conmuta con `prefers-color-scheme`, así que sólo se declara el modo claro.
**Disposición** — El valor sale del token del canon (`content-muted`), no del block, pero es cada block quien ELIGE el rol `muted` para su cuerpo. Dos salidas y no son excluyentes: (a) CANON — que `content-muted` alcance AA sobre el lienzo de página, que es donde se arregla para todos; (b) block-side — no usar `muted` para el CUERPO de un párrafo, reservándolo a metadato. Antes de tocar nada hay que medir los `lg` por separado, porque su umbral es otro.
**REFUTADO el mismo día (2026-08-15) — la vara era ajena.** El proyecto NO mide el contraste con el 4,5:1 genérico de WCAG: su suelo es DOBLE y está escrito en `theming/reference.md:1719` — «floors APCA ≥ 60 ∧ WCAG ≥ 3» — con guard propio (`scripts/contrast-audit.ts`, «WCAG 2 gate + APCA Lc, over the 33 scales × 2 modes»). Y sobre esta tinta en concreto, `reference.md:1780-1781`: «`text·11` is the secondary / low-contrast ink (≈APCA 60) — **marginal sub-4.5:1** on the muddy light-mode scales… **by design**».
Re-medido con las funciones del propio repo (`src/arts/color/apca.ts`, en el formato que declara — gamma sRGB **0..1**, no 0..255): **Lc APCA = 63,7** (suelo 60 ✅) y **WCAG = 3,70:1** (suelo ≥3 ✅). La tinta CUMPLE los dos suelos del sistema.
⚠️ Dos lecciones, y la segunda es de instrumento: (1) **antes de medir contraste hay que usar la vara del proyecto**, que aquí es APCA + un WCAG rebajado para la tinta secundaria, no el 4,5 de manual; (2) el primer intento con `apcaLc` devolvió **102666.5** — Lc va de −108 a +106 — porque le pasé el color en 0..255. Un valor fuera de rango no es un hallazgo raro: es un formato mal pasado.
Lo que SÍ sobrevive de esta ficha, como dato y no como defecto: el censo de dónde se pinta cuerpo con `muted` — 8 sitios en 7 blocks —, útil el día que se retoque ese rol.
### A-99 — CONFIRMADO · `Group` (canon) · doctrina · MEDIA
**Mecanismo** — `Group`**no envuelve, y nadie puede hacer que envuelva**, mientras su documentación promete lo contrario en tres sitios. La cadena, leída entera:
```
group.svelte → <Flex{...restProps}{direction}gapclassdata-*> ← NUNCA pasa `wrap`
types.ts:4 → GroupProps = Omit<FlexProps,'wrap'> ← el consumidor tampoco puede
group.css → sólo `--group-attached-overlap`, `data-grow` y las reglas de `attached`
```
Lo que su README afirma: «`wrap="wrap"` + `align="center"` por defecto», «**Defaults pensados para action rows** — `align: center`, `wrap: wrap`», y en la comparativa «Row con wrap por defecto | UIX: **Sí** | Chakra HStack: No». Medido en la demo del propio componente (`/uix/components/group`): `flex-wrap: **nowrap**`, `flex-direction: row`, `align-items: center`. **El `align` SÍ se cumple**, lo que descarta que el recipe no cargue: falta específicamente el `wrap`.
**Evidencia** — Lectura de los cuatro ficheros de la cadena + medición del componente aislado en su propia demo, no en un block.
**Disposición** — CANON. O `group.svelte` pasa el `wrap` con su default documentado, o el recipe lo declara, o la documentación deja de prometerlo. Nota de tipo: `Omit<FlexProps,'wrap'>` fue deliberado (el wrap era una decisión del componente, no del consumidor) — coherente con un default fijo, incoherente con que ese default no exista.
### A-98 — CONFIRMADO · `hero` · percepcion · MEDIA
**Mecanismo** — El cluster de acciones del hero no apila en móvil: `hero.svelte:127` lo compone con `<Group gap={3} align="center">`, y `Group` es `flex-direction: row` / `flex-wrap: nowrap`. Medido a 375×800 en `/blocks/hero/preview`: el grupo mide 327px y sus dos hijos 191 + 124 = **315px sin contar el gap**, en una sola fila, con la etiqueta de la acción secundaria ya truncándose. El block hermano resolvió exactamente esto: `cta.svelte:98-99` usa `<Flex direction={{ base: 'column', sm: 'row' }}>`, y sus dos acciones SÍ apilan a la misma anchura (medido: 279 y 191px, una debajo de otra).
**Evidencia** — Playwright headless, 375×800, tras la puerta de hidratación. ⚠️ La primera medición de esta fila fue INVÁLIDA y se descarta: el selector cogió el primer contenedor con dos botones, que en `hero` es el `Stack` de la copia (anchos 100/327/327/327 = eyebrow · título · descripción · acciones), no el `Group`. El dato bueno viene de seleccionar `[data-group]` con ≥2 hijos interactivos.
**Disposición** — Block-side y por composición pura, con el patrón que el tier ya usa: cambiar el `Group` por `Flex direction={{ base: 'column', sm: 'row' }}`, como `cta`. Estaba anotado en el handoff desde julio («`Group` no apila… `hero` todavía compone sus acciones con `Group`») sin medición; ahora la tiene.
**REESCRITA (2026-08-17) — la causa está una capa más abajo: [A-99].** La disposición anterior («cambiar el `Group` por `Flex direction` responsive, como `cta`») da por hecho que el block eligió mal el componente, y es falso: `Group` ES el componente para una fila de acciones —su README lo llama «defaults pensados para action rows»— y además expone `direction` responsive, así que ni siquiera hacía falta `Flex`. Lo que falla es que `Group` no aplica el `wrap` que documenta (A-99), y por eso las acciones ni envuelven ni apilan.
Alcance del síntoma, medido: `hero` (dos acciones, 315 de 327px a 375px, la segunda etiqueta truncándose — contenido REAL de la demo) y `feature-split` (`.Actions` también compone `Group`; su escenario pone una sola acción, así que el caso no se ejercita — al forzar una segunda, sigue en una línea. ⚠️ el «desborda el viewport» que se afirmó primero era artefacto: el ancho lo puso el texto inventado para el clon).
`cta` se salva por accidente: usa `Flex` a mano, no porque `Group` fuera inadecuado.
**Disposición corregida** — sin arreglo block-side. En cuanto `Group` envuelva, los dos blocks se curan solos. Si se quisiera además APILAR en móvil (que no es lo mismo que envolver), eso sí sería block-side y por props: `direction={{ base: 'column', sm: 'row' }}` sobre el propio `Group`.
### A-91 — ARREGLADO · `banner` · doctrina · BAJA
**Mecanismo** — El canon avisa EN SU PROPIO TIPO (`eidos/components/banner/types.ts`, typedoc de `BannerIntent`) de que su eje `intent` «conflates two axes the doctrine separates»: `intent` es el eje EVALUATIVO (los 6 intents) mientras `primary`/`secondary`/`tertiary` son roles de jerarquía que pertenecen a `color`; y de que Banner, al no tener prop `color`, «is the one painted component outside the open colour system — resolving this is a pending decision, not an oversight». El block re-expone ese eje entero vía `Omit<BannerProps,'children'>` y su mapa de composición lo describe como «`intent` (the canonical color roles)», sin registrarlo. Su README registra CINCO huecos del canon (el `role="banner"` no anulable, la tinta de la ✕ con sus medidas, la persistencia del descarte, la animación diferida, el sticky) — la doctrina del tier es que «un hueco del canon se registra, no se falsea», y a éste le falta la fila.
@ -606,7 +690,7 @@ Preferible (a): la asimetría con `container='xl'` es fea y el hermano ya la res
**Evidencia** — Ver A-13.
### A-25 — CONFIRMADO · `pricing` · doctrina · BAJA
### A-25 — ARREGLADO · `pricing` · doctrina · BAJA
**Mecanismo** — La demo documenta una elevación que el código se niega explícitamente a hacer: «`featured` le da acento y elevación» frente al comentario de implementación «The featured tier is NOT elevated: `Card` exposes no elevation prop… Registered as a gap in the README instead of faked with an inline `box-shadow`».
@ -614,6 +698,9 @@ Preferible (a): la asimetría con `container='xl'` es fea y el hermano ya la res
**Disposición** — Corregir la demo (acento cromático, sin elevación). Fase 3, trivial.
**Re-verificación (2026-08-17)** — ya no se reproduce: no queda ninguna mención de «elevación» ni en `web/routes/blocks/cta/+page.svelte` ni en el README del block.
### A-26 — REFUTADO · `pricing` · doctrina · BAJA
@ -934,6 +1021,15 @@ Parche local en el block NO recomendado: el contrato B de cta prohíbe `.css` pr
Al cerrarlo, corregir también la redacción del hallazgo en el ledger: el indicador EXISTE y ambos elementos son alcanzables por teclado; el defecto es de contraste del anillo y sólo en esquema oscuro. Y anotar el hueco de percepción en `src/uix/blocks/cta/README.md`, junto a los que ya declara el docstring (cta.svelte:18-23).
**Re-medición y DISPOSICIÓN CORREGIDA (2026-08-17)** — sigue viva y con el mismo número: 1,43:1 compuesto, en los dos elementos del block. Pero la disposición anterior («que `Surface variant='solid'` re-ancle `--focus-ring-color` para su subárbol») es **la capa equivocada, y eso explica el revert de `e468e764b`**.
La doctrina lo fija en `theming/reference.md:1797` (§«Borders — two tiers»): el anillo de foco es «the one always-sole indicator» y está en el tramo **load-bearing con objetivo 3:1** —no entre los decorativos exentos—, PERO su apariencia es «a config axis, parameterised, NOT hardcoded»: `primitives.focusRing` + `color.focus.{ring,ringError}`, emitidos UNA vez como `--focus-ring-*`. Y textualmente: «The shipped default (`primary·8 @ ~50%` translucent) **measures sub-3:1 as a raw ratio**; hardening it (opaque color, `innerWidth > 0`, offset) is a **default-VALUE decision via config, never a per-component CSS change**».
Es decir: el proyecto YA SABE que su anillo por defecto queda sub-3:1, y tiene decidido dónde se arregla — subiendo el valor por defecto en la config del tema, no re-anclando el token desde un componente. El intento revertido hacía justo lo segundo.
**Disposición** — decisión de VALOR sobre `color.focus.ring` / `primitives.focusRing`, fuera del tier `blocks` y fuera de `Surface`. El block `cta` no tiene nada que arreglar: sólo elige un lienzo sólido, que es donde el default se nota más.
### A-48 — CONFIRMADO · `banner` · percepcion · MEDIA
| entrance | `Motion` (`trigger="viewport"`, `scale-fade`) | the panel arrives WHOLE — it is one statement, not a list, so it does not stagger its parts |
| panel | `Surface` (always `solid` · `color` · `gradient` · `rounded`) | the look is the system's finish, not paint of the block's own |
| layout | `Stack` (center) · `Grid` 2-col (justified) | `justified` stacks on narrow, where one row would crush both halves |
| layout | `Grid` (split) · `Stack` (center) · positioned `Box` layers (background) | `split` is two columns only when there is `media` |
| entrance | `Motion` (`trigger="viewport"`) + `data-stagger` on the copy stack | the copy unfolds by structural index; media enters with `scale-fade` after it |
{href:'#analytics',title:'Analytics',blurb:'Cohortes, embudos y retención sobre tus eventos.'},
{href:'#automations',title:'Automations',blurb:'Reglas que reaccionan a lo que hacen tus clientes.'},
{
href: '#analytics',
title: 'Analytics',
blurb: 'Cohortes, embudos y retención sobre tus eventos.'
},
{
href: '#automations',
title: 'Automations',
blurb: 'Reglas que reaccionan a lo que hacen tus clientes.'
},
{href:'#analytics',title:'Changelog',blurb:'Lo que cambió cada semana.'}
];
@ -55,13 +63,15 @@
id: 'analytics',
title: 'Mide lo que de verdad pasa',
body: 'Los eventos llegan crudos y salen en cohortes: qué hicieron, cuándo dejaron de hacerlo y qué cambió la semana en que lanzaste. Sin muestreo y sin esperar a mañana.',
extra: 'Consulta cualquier ventana temporal sin reprocesar: el motor mantiene los agregados al día.'
extra:
'Consulta cualquier ventana temporal sin reprocesar: el motor mantiene los agregados al día.'
},
{
id: 'automations',
title: 'Reglas que se disparan solas',
body: 'Una automatización es una condición sobre el flujo de eventos y una acción: avisar a un canal, mover a un segmento, abrir un ticket. Se escribe una vez y se ejecuta en cuanto la condición se cumple.',
extra: 'Cada ejecución deja traza, así que puedes explicar por qué un cliente recibió lo que recibió.'
extra:
'Cada ejecución deja traza, así que puedes explicar por qué un cliente recibió lo que recibió.'