feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
<script lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* The hero mini-page: the block with a page worth of product content, so the
|
|
|
|
|
* demo can render it FULL-BLEED (the hero at the real top of a real page) and
|
|
|
|
|
* the `preview` route can serve the same thing as a standalone document for
|
|
|
|
|
* the device-width frame.
|
|
|
|
|
*
|
|
|
|
|
* The media is a framed screenshot placeholder composed from canon components
|
|
|
|
|
* — no external asset, no raw CSS — so it reads as a product shot in every
|
|
|
|
|
* theme.
|
|
|
|
|
*/
|
|
|
|
|
import { Hero } from '$blocks/hero';
|
|
|
|
|
import { Section } from '$uix/eidos/components/section';
|
|
|
|
|
import { Container } from '$uix/eidos/components/container';
|
|
|
|
|
import { Stack } from '$uix/eidos/components/stack';
|
|
|
|
|
import { Group } from '$uix/eidos/components/group';
|
|
|
|
|
import { Grid } from '$uix/eidos/components/grid';
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
import { Box } from '$uix/eidos/components/box';
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
import { Wrap } from '$uix/eidos/components/wrap';
|
|
|
|
|
import { Surface } from '$uix/eidos/components/surface';
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
import { Mockup } from '$uix/eidos/components/mockup';
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
import { MediaPlayer } from '$uix/eidos/components/media-player';
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
import { Heading } from '$uix/eidos/components/heading';
|
|
|
|
|
import { Text } from '$uix/eidos/components/text';
|
|
|
|
|
import { Badge } from '$uix/eidos/components/badge';
|
|
|
|
|
import { Button } from '$uix/eidos/components/button';
|
|
|
|
|
import { Link } from '$uix/eidos/components/link';
|
|
|
|
|
import * as Icon from '$uix/eidos/components/icon';
|
|
|
|
|
|
|
|
|
|
type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
|
|
|
type SectionSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
|
|
|
type Layout = 'center' | 'split' | 'background';
|
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2
componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card).
Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un
Display, ni un TextGradient, ni un CountUp.
La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga
`.css`, así que **un block solo puede ser tan expresivo como los componentes del
canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14
blocks uno a uno, es construir lo que falta en el canon y componerlo.
- **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No
posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'`
cuando dispara el observador, así que reduced-motion degrada DENTRO del preset.
Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting:
enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de
una animación de scroll debe ser «sin animación», nunca «página en blanco».
- **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en
cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan
por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por
defecto, así que una lista se escalona poniendo `data-stagger` en SU
contenedor: cero delays a mano, cero números en el consumidor.
- **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open`
(disclosure); ahora puede esperar también a ser vista.
- **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` ·
`grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward
de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco.
**El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía
de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales),
entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade`
y `decor` (glow por defecto) expuesto como control vivo en la demo.
Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con
índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con
su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 ·
`svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
type Decor = 'glow' | 'mesh' | 'grid' | 'dots' | 'none';
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
type MediaKind = 'dashboard' | 'video';
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
layout = 'split' as Layout,
|
|
|
|
|
level = 1,
|
|
|
|
|
container = 'lg' as ContainerSize,
|
|
|
|
|
size = 'xl' as SectionSize,
|
|
|
|
|
withMedia = true,
|
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2
componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card).
Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un
Display, ni un TextGradient, ni un CountUp.
La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga
`.css`, así que **un block solo puede ser tan expresivo como los componentes del
canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14
blocks uno a uno, es construir lo que falta en el canon y componerlo.
- **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No
posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'`
cuando dispara el observador, así que reduced-motion degrada DENTRO del preset.
Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting:
enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de
una animación de scroll debe ser «sin animación», nunca «página en blanco».
- **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en
cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan
por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por
defecto, así que una lista se escalona poniendo `data-stagger` en SU
contenedor: cero delays a mano, cero números en el consumidor.
- **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open`
(disclosure); ahora puede esperar también a ser vista.
- **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` ·
`grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward
de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco.
**El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía
de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales),
entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade`
y `decor` (glow por defecto) expuesto como control vivo en la demo.
Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con
índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con
su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 ·
`svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
withEyebrow = true,
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
mediaKind = 'dashboard' as MediaKind,
|
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2
componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card).
Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un
Display, ni un TextGradient, ni un CountUp.
La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga
`.css`, así que **un block solo puede ser tan expresivo como los componentes del
canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14
blocks uno a uno, es construir lo que falta en el canon y componerlo.
- **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No
posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'`
cuando dispara el observador, así que reduced-motion degrada DENTRO del preset.
Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting:
enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de
una animación de scroll debe ser «sin animación», nunca «página en blanco».
- **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en
cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan
por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por
defecto, así que una lista se escalona poniendo `data-stagger` en SU
contenedor: cero delays a mano, cero números en el consumidor.
- **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open`
(disclosure); ahora puede esperar también a ser vista.
- **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` ·
`grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward
de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco.
**El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía
de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales),
entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade`
y `decor` (glow por defecto) expuesto como control vivo en la demo.
Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con
índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con
su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 ·
`svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
decor = 'glow' as Decor
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
}: {
|
|
|
|
|
layout?: Layout;
|
|
|
|
|
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
|
|
|
container?: ContainerSize;
|
|
|
|
|
size?: SectionSize;
|
|
|
|
|
withMedia?: boolean;
|
|
|
|
|
withEyebrow?: boolean;
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
mediaKind?: MediaKind;
|
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2
componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card).
Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un
Display, ni un TextGradient, ni un CountUp.
La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga
`.css`, así que **un block solo puede ser tan expresivo como los componentes del
canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14
blocks uno a uno, es construir lo que falta en el canon y componerlo.
- **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No
posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'`
cuando dispara el observador, así que reduced-motion degrada DENTRO del preset.
Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting:
enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de
una animación de scroll debe ser «sin animación», nunca «página en blanco».
- **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en
cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan
por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por
defecto, así que una lista se escalona poniendo `data-stagger` en SU
contenedor: cero delays a mano, cero números en el consumidor.
- **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open`
(disclosure); ahora puede esperar también a ser vista.
- **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` ·
`grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward
de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco.
**El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía
de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales),
entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade`
y `decor` (glow por defecto) expuesto como control vivo en la demo.
Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con
índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con
su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 ·
`svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
decor?: Decor;
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
} = $props();
|
|
|
|
|
|
|
|
|
|
// Faux chart bars for the screenshot placeholder — fixed heights in rem so
|
|
|
|
|
// the card is exactly as tall as its content, no empty frame.
|
|
|
|
|
const bars = ['3rem', '5rem', '3.6rem', '6.4rem', '4.4rem', '7.4rem', '5.6rem'];
|
|
|
|
|
const centered = $derived(layout !== 'split');
|
|
|
|
|
</script>
|
|
|
|
|
|
refactor(blocks)!: el eje de tamano dice de QUIEN es — size deja de significar dos cosas
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>
2 months ago
|
|
|
<Hero {layout} {level} containerSize={container} sectionSize={size} {decor}>
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
{#snippet backdrop()}
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
<!-- The backdrop is a real Surface with the aurora gradient finish: the
|
|
|
|
|
identity's `primary` solid painted by `--gradient-aurora` (derived from
|
|
|
|
|
the theme's own colour roles). No hand-picked colour — swap the palette
|
|
|
|
|
and the backdrop follows. -->
|
|
|
|
|
<Surface variant="solid" color="primary" gradient width="100%" height="100%" />
|
|
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
{#snippet eyebrow()}
|
|
|
|
|
{#if withEyebrow}
|
|
|
|
|
<Badge size="sm" variant="soft" color="primary">Beta pública</Badge>
|
|
|
|
|
{/if}
|
|
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
{#snippet title()}
|
|
|
|
|
Analítica de producto sin sorpresas en la factura
|
|
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
{#snippet description()}
|
|
|
|
|
Eventos crudos, cohortes al día y automatizaciones que reaccionan solas. Sin muestreo, sin
|
|
|
|
|
reprocesar y sin un contrato que crece con cada pico de tráfico.
|
|
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
{#snippet actions()}
|
fix(blocks): lo que la auditoría encontró DENTRO del tier
Una auditoría multi-agente (7 dimensiones doctrinales + refutación adversarial)
confirmó 32 hallazgos sobre la pasada de calidad. Esto corrige los que caen
dentro de `blocks/`; la deuda de fuera queda REGISTRADA, no tocada
(`PLAN-blocks-quality.md` §6).
- **B-7 roto en `pricing`**: el block traía tres strings castellanas propias como
defaults del toggle — dos visibles y un nombre accesible. Hardcodeaba un idioma
DENTRO del framework y esquivaba `langs`, en el mismo block cuyo README predica
«cero strings propias». Ahora los tres son props REQUERIDAS y las pone la app.
- **El block pintaba**: `pricing.Plan` metía `box-shadow` por `style=` porque
`Card` no expone elevación. Retirado; el hueco queda registrado como candidato
a canon en vez de falseado.
- **El escalonado estaba roto y el comentario mentía**: puse `spring-pop` (driver
JS) en los planes, y un driver JS no lee el `animation-delay` donde vive el
stagger — entraban TODOS a la vez mientras el comentario afirmaba lo contrario.
Vuelto al preset CSS: verificado, índices 0,1,2 a 70ms.
- **Comentario falso en la demo del hero**: atribuía al `intent` visual una
consecuencia sonora/háptica que ningún camino produce (el morfo del Button dice
explícitamente que `contact-activate` NO lleva intent). Reescrito.
- **Composition maps mentían**: los 6 blocks que ahora componen `Motion` no lo
decían; el hero seguía diciendo `Heading` (es `Display`), sin `Backdrop` ni
`decor`, y declaraba DIFERIDO un preset de cromo de media que ya está shipeado
(`Mockup`). Al día, sin filas duplicadas ni datos rancios (el chip de
feature-grid es `solid`, no `soft`).
- **Handoff y plan**: el plan seguía prescribiendo un `<Reveal>` que esta misma
sesión creó y retiró; el handoff daba `feature-split` por «sin decidir» estando
hecho, y los gates con números falsos.
Verificado: `blocks:check` verde (7) · `svelte-check` sin errores propios · en el
navegador, pricing escalona de nuevo, las etiquetas vienen de la app y ninguna
tarjeta pinta sombra.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
<!-- `intent="fulfill"`: the page's completion, carried as ANTICIPATORY
|
|
|
|
|
WEIGHT (colour and form). It does NOT load the Button's
|
|
|
|
|
`contact-activate` with intent — the morfo says so explicitly — so it
|
|
|
|
|
produces no sound or haptic by itself; the evaluative consequence is
|
|
|
|
|
the `commit` the app emits when the outcome lands (see PricingSite). -->
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
<Button variant="solid" size="lg" intent="fulfill">
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
{#snippet endIcon()}<Icon.ArrowRight size="xs" />{/snippet}
|
|
|
|
|
{#snippet child({ props, content })}
|
|
|
|
|
<a href="#pricing" {...props}>{@render content()}</a>
|
|
|
|
|
{/snippet}
|
|
|
|
|
Empezar gratis
|
|
|
|
|
</Button>
|
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un
join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo
al re-verificar — el journal devuelve los resultados en otro orden que la
entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union
SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito,
3 dato. La tasa real de refutacion es del 13%, no del 28%.
De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks
componen bien; lo que estaba roto era el arco perceptivo.
CANON
- Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la
paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el
hover de variante. Medido: el enlace del footer pasa de la tinta del padre a
la suya.
- Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo
inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper
compartido (buildSizeStyle). Tarjetas al fin de igual alto.
- Form.Submit / Form.Reset: componen el Button del canon, asi que la accion
principal de un formulario recupera el contact-activate en el gesto. Y el
aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo
consumidor), verificado con el AX tree de Chrome.
- Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia
dos commits con intents contradictorios y dos earcons.
- NavigationMenu: commit-select se mueve al Link (navegar es el acto
evaluable), el despliegue habla como emerge-open/close en vez de fingir una
seleccion por hover, y el pack casa por fin con la parte que recibe el
estampado — antes sonaba a la ganancia base, 10x lo disenado.
- Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser
la del control, asi que md significa lo mismo en todo el sistema.
- Fundacion: [data-on] arrastra la propiedad color, no solo las variables.
BLOCKS
- hero: la CTA secundaria pasa de 1.61:1 a 6.61:1.
- contact: separa incomplete de invalid — el camino de error por campo era
inalcanzable por construccion — y su frase llega a la AT.
- newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras
propias, Submit y Reason como partes que leen el contexto.
- site-header ya no congela la pagina al cruzar el breakpoint; site-footer
emite su commit; pricing no se vacia; cta llega a sangre de verdad.
DOCTRINA
- La frontera dura 1 nombra $libs/forms como puerta sancionada.
- El contrato B admite un segundo servicio: el anunciador. Un block que posee
las palabras de sus estados tiene que poder decirlas.
Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks ·
svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
<!-- No ink override here: the block sets it for the whole copy cluster under
|
|
|
|
|
`background`. This used to pass `--color-content-on-solid` by hand, which
|
|
|
|
|
was inert anyway (`subtle` resolves to `color: inherit`). -->
|
|
|
|
|
<Link href="#docs" variant="subtle" size="lg">Ver la documentación</Link>
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
{#snippet media()}
|
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya
con movimiento, la media admite vídeo de verdad y los CTA declaran su intención
semántica en vez de ser neutros.
- **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y
sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item,
pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las
tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano.
Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y
el acordeón del FAQ entran también.
- **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup`
—nada a medida, el slot `media` toma lo que la app componga— y la demo lo
expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un
vídeo?» ya tiene respuesta demostrada, no teórica.
- **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero
es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el
plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y
los 7 bloques lo eran.
Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene
morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin
usar: el `intent` de los componentes que compone, y los eventos que esos
componentes ya emiten (Accordion emerge, ToggleGroup, Drawer).
Verificado con Playwright en los 6: reveals con índices estructurales
(feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando
470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página.
`blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin
errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
|
|
|
{#if withMedia && mediaKind === 'video'}
|
|
|
|
|
<!-- A hero with VIDEO: the canon `MediaPlayer` inside the same `Mockup`
|
|
|
|
|
chrome. Nothing bespoke — the block's `media` slot takes whatever the
|
|
|
|
|
app composes. -->
|
|
|
|
|
<Mockup chrome="browser" url="acme.app/demo">
|
|
|
|
|
<MediaPlayer media="video">
|
|
|
|
|
<MediaPlayer.Media
|
|
|
|
|
src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4"
|
|
|
|
|
loop
|
|
|
|
|
muted
|
|
|
|
|
autoplay
|
|
|
|
|
playsinline
|
|
|
|
|
/>
|
|
|
|
|
<MediaPlayer.DefaultControls />
|
|
|
|
|
</MediaPlayer>
|
|
|
|
|
</Mockup>
|
|
|
|
|
{:else if withMedia}
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
<!-- Dogfoods the canon `Mockup`: the browser chrome (dots + URL) is the
|
|
|
|
|
primitive's, so the faux dashboard inside is just the content. -->
|
|
|
|
|
<Mockup chrome="browser" url="acme.app/analytics">
|
|
|
|
|
<Box padding={4}>
|
|
|
|
|
<Stack gap={5}>
|
|
|
|
|
<Group gap={2} align="center" justify="end">
|
|
|
|
|
<Badge size="xs" variant="soft" color="primary">en vivo</Badge>
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
</Group>
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
<!-- Grid gives each bar an equal 1fr track — no reliance on flex-grow. -->
|
|
|
|
|
<Grid columns={7} gap={3} align="end">
|
|
|
|
|
{#each bars as h, i (i)}
|
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un
join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo
al re-verificar — el journal devuelve los resultados en otro orden que la
entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union
SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito,
3 dato. La tasa real de refutacion es del 13%, no del 28%.
De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks
componen bien; lo que estaba roto era el arco perceptivo.
CANON
- Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la
paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el
hover de variante. Medido: el enlace del footer pasa de la tinta del padre a
la suya.
- Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo
inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper
compartido (buildSizeStyle). Tarjetas al fin de igual alto.
- Form.Submit / Form.Reset: componen el Button del canon, asi que la accion
principal de un formulario recupera el contact-activate en el gesto. Y el
aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo
consumidor), verificado con el AX tree de Chrome.
- Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia
dos commits con intents contradictorios y dos earcons.
- NavigationMenu: commit-select se mueve al Link (navegar es el acto
evaluable), el despliegue habla como emerge-open/close en vez de fingir una
seleccion por hover, y el pack casa por fin con la parte que recibe el
estampado — antes sonaba a la ganancia base, 10x lo disenado.
- Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser
la del control, asi que md significa lo mismo en todo el sistema.
- Fundacion: [data-on] arrastra la propiedad color, no solo las variables.
BLOCKS
- hero: la CTA secundaria pasa de 1.61:1 a 6.61:1.
- contact: separa incomplete de invalid — el camino de error por campo era
inalcanzable por construccion — y su frase llega a la AT.
- newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras
propias, Submit y Reason como partes que leen el contexto.
- site-header ya no congela la pagina al cruzar el breakpoint; site-footer
emite su commit; pricing no se vacia; cta llega a sangre de verdad.
DOCTRINA
- La frontera dura 1 nombra $libs/forms como puerta sancionada.
- El contrato B admite un segundo servicio: el anunciador. Un block que posee
las palabras de sus estados tiene que poder decirlas.
Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks ·
svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
<Surface
|
|
|
|
|
variant="solid"
|
|
|
|
|
color={i === 5 ? 'primary' : 'neutral'}
|
|
|
|
|
rounded
|
|
|
|
|
height={h}
|
|
|
|
|
/>
|
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es
el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un
screenshot enmarcado por demo, se cierra de raíz con un componente del canon
que los blocks componen.
`<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier
media (imagen, vídeo, AspectRatio) en cromo de dispositivo:
- `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla.
- `phone`: bisel oscuro redondeado con notch.
- `plain` (def.): pantalla limpia enmarcada, con sombra.
Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual
que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su
hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`,
`--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano.
eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio).
Dogfood + bug arreglado de paso en `hero`:
- La demo del hero deja de componer a mano el marco del screenshot y usa
`<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro.
- Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro
de svelte-check con backslashes mal escapados): los snippets `title` y
`background` colisionaban con los atributos HTML homónimos y se volvían
`string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y
renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo
nombre sea un atributo HTML necesita Omit o un nombre distinto.
Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch)
en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de
verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 ·
`blocks:check` verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
{/each}
|
|
|
|
|
</Grid>
|
|
|
|
|
<Grid templateColumns="1fr 20%" gap={3} align="center">
|
|
|
|
|
<Surface variant="solid" color="neutral" rounded height="0.5rem" />
|
|
|
|
|
<Surface variant="solid" color="neutral" rounded height="0.5rem" />
|
|
|
|
|
</Grid>
|
|
|
|
|
</Stack>
|
|
|
|
|
</Box>
|
|
|
|
|
</Mockup>
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
{/if}
|
|
|
|
|
{/snippet}
|
|
|
|
|
</Hero>
|
|
|
|
|
|
|
|
|
|
<!-- A floor so the page reads as a page, not a lone band. -->
|
|
|
|
|
<Section size="lg">
|
|
|
|
|
<Container size={container}>
|
|
|
|
|
<Stack gap={6}>
|
|
|
|
|
<Text size="sm" weight="medium" color="muted" align={centered ? 'center' : 'start'}>
|
|
|
|
|
Con la confianza de equipos de producto en
|
|
|
|
|
</Text>
|
|
|
|
|
<Wrap gap={8} justify={centered ? 'center' : 'start'}>
|
|
|
|
|
{#each ['Northwind', 'Umbra', 'Kessel', 'Volta', 'Marisol'] as brand (brand)}
|
|
|
|
|
<Text size="lg" weight="semibold" color="muted">{brand}</Text>
|
|
|
|
|
{/each}
|
|
|
|
|
</Wrap>
|
|
|
|
|
<Heading level={level === 1 ? 2 : 3}>
|
|
|
|
|
Del evento crudo a la decisión, sin escala intermedia
|
|
|
|
|
</Heading>
|
|
|
|
|
<Text color="muted" measure="wide">
|
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un
join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo
al re-verificar — el journal devuelve los resultados en otro orden que la
entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union
SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito,
3 dato. La tasa real de refutacion es del 13%, no del 28%.
De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks
componen bien; lo que estaba roto era el arco perceptivo.
CANON
- Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la
paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el
hover de variante. Medido: el enlace del footer pasa de la tinta del padre a
la suya.
- Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo
inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper
compartido (buildSizeStyle). Tarjetas al fin de igual alto.
- Form.Submit / Form.Reset: componen el Button del canon, asi que la accion
principal de un formulario recupera el contact-activate en el gesto. Y el
aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo
consumidor), verificado con el AX tree de Chrome.
- Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia
dos commits con intents contradictorios y dos earcons.
- NavigationMenu: commit-select se mueve al Link (navegar es el acto
evaluable), el despliegue habla como emerge-open/close en vez de fingir una
seleccion por hover, y el pack casa por fin con la parte que recibe el
estampado — antes sonaba a la ganancia base, 10x lo disenado.
- Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser
la del control, asi que md significa lo mismo en todo el sistema.
- Fundacion: [data-on] arrastra la propiedad color, no solo las variables.
BLOCKS
- hero: la CTA secundaria pasa de 1.61:1 a 6.61:1.
- contact: separa incomplete de invalid — el camino de error por campo era
inalcanzable por construccion — y su frase llega a la AT.
- newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras
propias, Submit y Reason como partes que leen el contexto.
- site-header ya no congela la pagina al cruzar el breakpoint; site-footer
emite su commit; pricing no se vacia; cta llega a sangre de verdad.
DOCTRINA
- La frontera dura 1 nombra $libs/forms como puerta sancionada.
- El contrato B admite un segundo servicio: el anunciador. Un block que posee
las palabras de sus estados tiene que poder decirlas.
Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks ·
svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
Los eventos llegan tal cual y salen en cohortes: quién hizo qué, cuándo dejó de hacerlo y
|
|
|
|
|
qué cambió la semana en que lanzaste. Consulta cualquier ventana sin reprocesar.
|
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
</Text>
|
|
|
|
|
</Stack>
|
|
|
|
|
</Container>
|
|
|
|
|
</Section>
|