blocks(background): seis fondos, y el que parecía mejor era el que no se podía leer

F5 de PLAN-background.md: los seis blocks de D-BG.10 (2) reciben `Background.*`.
El hero cayó en F2 y la página compuesta ya existía — no había que construirla,
había que verificarla con la decoración puesta.

La forma es uniforme y tiene un solo desvío: cada block gana un prop `decor` que
monta `<Background.Pattern>` como HIJA de su `Section`, la misma composición que
el hero ya probó. El desvío es `cta`, donde la decoración va dentro del PANEL,
que es donde está el ojo.

**Dos hallazgos que sólo la medición podía dar.**

Dentro del panel sólido del `cta`, las dos tramas TINTADAS hunden el titular
bajo AA: `glow` lleva la copia blanca de 5,18:1 a **3,06:1** y `mesh` a
**2,27:1**. Pintan un lavado claro anclado en `50% 0%` — exactamente donde se
asienta el titular. Las de línea toman la tinta de regla, oscurecen, y la copia
sube a **14,35:1** mientras la textura sigue leyéndose a 3,19:1 contra el panel.
Por eso el default es `rings`, que además irradia del mismo anclaje superior que
usaba el glow. Las tintadas se ofrecen igual: el app decide, pero ya con el
número delante, en el tipo y en el README.

Y una trama tintada dentro del panel era invisible por construcción:
`--_background-tint` cae en `--color-primary-solid`, que es EL MISMO valor que
pinta el panel — un halo del color del panel sobre el color del panel. El block
pasa ahora `color="var(--color-content-on-solid)"`, el token que ya usa para su
propia copia; medido después, el tinte resuelve a `#ffffff`.

**Los defaults, y por qué dos están apagados.** `cta` `rings` · `stats-band`
`grid`, porque una banda de cifras se lee como medida y la rejilla es la trama
que lo dice sin decorar por decorar · `testimonials` y `feature-split` `glow`.
`site-footer` y `banner` reciben la capacidad APAGADA: la columna B del plan de
calidad no pide acabado en ninguno de los dos, y encenderlo por nuestra cuenta
sería inventar un aspecto que nadie pidió.

**Un desvío de alcance, declarado.** La columna B pedía «`Backdrop` alterno» POR
FILA en `feature-split`; se resuelve en la SECCIÓN. Una banda por fila obligaría
a cada `Row` a poseer el estado de alternancia, y eso es coordinación — un block
que coordina deja de ser un block que no posee nada.

Medido en Chrome sobre la página compuesta real: cinco pilas, los cinco
anfitriones adoptados (`position: relative` + `isolation: isolate`) sin que
ningún block los posicione; la copia mide 15,88:1 sobre las secciones decoradas
y 5,18:1 sobre el panel del cta — AA en todas. Sobre lienzo oscuro, medido
aparte: 17,06 → 13,23:1 con `glow` y 12,37:1 con `grid`/`dots`.

Ledger: seis filas nuevas, A-100…A-105, cada una con su mecanismo y su
evidencia.

Gates: `blocks:check` 0/18 · `rtl:check` 0/180 · `docs:check` 0/0 en 634 docs ·
434/435 (el fallo es el `skin-media-player` de siempre) · `check` 0 errores
propios · prettier limpio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 740274a847
commit 331912cf23

@ -143,6 +143,12 @@ Columna **or.** = de qué sección del documento viejo salió la fila (`C` confi
| 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. Síntoma de [A-99]; cae sin tocar el block… | `src/uix/blocks/hero/hero.svelte` | ARREGLADO |
| A-99 | `Group` (canon) | doctrina | MEDIA | F7 | `Group` no aplica NINGUNO de los dos defaults de action row que su README promete (`wrap: wrap` y `align: center`): nunca se los pasa a `Flex`, y el recipe no puede suplirlos… | `src/uix/eidos/components/group/group.svelte` | ARREGLADO |
| A-100 | `cta` | percepcion | ALTA | F5-bg | Dentro del panel sólido, las dos tramas TINTADAS hunden el titular blanco bajo AA: `glow` 5,18 → 3,06:1 y `mesh` → 2,27:1. Las de línea oscurecen y suben la copia a 14,35:1 | `src/uix/blocks/cta/cta.svelte` | ARREGLADO |
| A-101 | `cta` | doctrina | MEDIA | F5-bg | Un `glow` tintado con el acento del propio panel resuelve EXACTAMENTE al fondo del panel: el halo es invisible por construcción. El acento debe viajar como tinta on-solid | `src/uix/blocks/cta/cta.svelte` | ARREGLADO |
| A-102 | `stats-band` | percepcion | BAJA | F5-bg | Sin decoración de sección (columna B: «reveal + Backdrop»); el reveal ya estaba, el fondo no | `src/uix/blocks/stats-band/stats-band.svelte` | ARREGLADO |
| A-103 | `testimonials` | percepcion | BAJA | F5-bg | Sin decoración de sección; el halo detrás de las citas es el acabado que las referencias pintan a mano | `src/uix/blocks/testimonials/testimonials.svelte` | ARREGLADO |
| A-104 | `feature-split` | percepcion | BAJA | F5-bg | Columna B pedía «`Backdrop` alterno» por fila; se resuelve en la SECCIÓN — una banda por fila obligaría a cada `Row` a poseer estado de alternancia | `src/uix/blocks/feature-split/feature-split.svelte` | ARREGLADO |
| A-105 | `site-footer + banner` | doctrina | BAJA | F5-bg | Ambos ganan la capacidad de decoración APAGADA por defecto: la columna B no pide acabado en ninguno, y encenderlo sería inventar un aspecto que nadie pidió | `src/uix/blocks/site-footer/site-footer.svelte` | ARREGLADO |
## Reparto
@ -1881,3 +1887,60 @@ LÍNEA BASE sin biografía: todas las tarjetas 195px, desviación 0px — es dec
**Mecanismo** — `ContactVerification` es ahora `ProofOfHumanStatus` importado del canon, no una copia de las mismas cuatro palabras.
**Evidencia** — `contact/state.ts`.
### A-100 — ARREGLADO · `cta` · percepcion · ALTA
**Reclamación** — la decoración del panel, encendida sin medir, deja el titular
bajo AA.
**Mecanismo** — el panel es `Surface variant='solid'` con la copia en tinta
on-solid (blanca). `glow` y `mesh` son las dos tramas que pintan con el TINTE, y
lo hacen sobre un área grande anclada en `50% 0%` — exactamente donde se asienta
el titular. El lavado claro sube la luminancia del fondo bajo la copia.
**Evidencia** — medido en Chrome sobre panel `primary` (`142,78,198`) con tinta
`#ffffff`: llano **5,18:1** · con `glow` **3,06:1** · con `mesh` **2,27:1** ·
con cualquiera de línea (`grid`/`dots`/`lines`/`rings`) **14,35:1** · con
`vignette` **9,89:1**. La textura de línea sigue siendo visible: la regla mide
3,19:1 contra el panel, que es el registro de una decoración.
**Arreglo** — el default pasa a `rings`, que irradia del mismo anclaje superior
que usaba el glow. Las tintadas se siguen ofreciendo, con el número escrito en
el tipo y en el README: el app decide, pero informado.
### A-101 — ARREGLADO · `cta` · doctrina · MEDIA
**Reclamación** — la trama tintada dentro del panel es invisible.
**Mecanismo** — `--_background-tint` cae por defecto en `--color-primary-solid`,
que es el MISMO valor que pinta el panel cuando `color='primary'`: un halo del
color del panel sobre el color del panel.
**Evidencia** — medido: `--_background-tint` resolvía a
`oklch(0.5556 0.1829 305.86)`, idéntico al `background-color` del panel.
**Arreglo** — el block pasa `color="var(--color-content-on-solid)"`, el mismo
token que ya usa para la tinta de su copia; medido después, el tinte resuelve a
`#ffffff`.
### A-102 · A-103 · A-104 · A-105 — ARREGLADO · decoración de sección · F5
**Reclamación** — la columna B del plan de calidad pedía acabado de fondo en
`stats-band` (con reveal, que ya estaba), `testimonials` y `feature-split`; y
`site-footer` / `banner` no tenían la capacidad siquiera.
**Mecanismo** — los cuatro primeros montan `<Background><Background.Pattern>` como
HIJA de su `Section`: la sección se vuelve anfitriona por la regla de foundation
(`:where(:has(> [data-background]))`), así que la banda sangra a todo el ancho
detrás de la medida y ningún block gana una caja. `site-footer` y `banner` la
reciben APAGADA: la columna B no pide acabado en ninguno de los dos.
**Evidencia** — medido en Chrome, copia sobre el lienzo de página (`10,10,11`,
tinta `238,238,238`): llano **17,06:1**; con `glow` **13,23:1**; con `grid` /
`dots` **12,37:1**. Los cinco anfitriones computan `position: relative` +
`isolation: isolate` sin que ningún block los posicione.
**Nota de alcance** — `feature-split` resuelve la alternancia en la sección y no
por fila a propósito: una banda por fila obligaría a cada `Row` a poseer el
estado de alternancia, que es coordinación, y un block que coordina deja de ser
un block que no posee nada.

@ -742,6 +742,57 @@ componente pisaba y un `aria-pressed` inerte.
Gates: audit **PASS 0** · eidos-lint invalid 0 · rtl 0/180 · docs 0/0 en 634 ·
blocks 0/18 · smoke PASS · 441/442 · `check` 0 propios · prettier limpio.
### 7.octies F5 ejecutada (2026-08-18) — rollout al tier
Los seis blocks de D-BG.10 (2) reciben `Background.*`. El (1) —el hero— aterrizó
en F2, y el (3) —la página compuesta— **ya existía** (`/blocks/landing`): no
había que construirla, había que verificarla con la decoración puesta, y eso es
lo que se hizo.
**La forma, uniforme y con un solo desvío**: cada block gana un prop `decor` que
monta `<Background.Pattern>` como HIJA de su `Section` — la misma que el hero ya
probó. El desvío es `cta`, donde la decoración va dentro del PANEL (`Surface`),
que es donde está el ojo.
**Dos hallazgos que sólo la medición podía dar, y que habría shippeado a ciegas:**
1. **Dentro del panel sólido del `cta`, las dos tramas TINTADAS hunden el titular
bajo AA**: `glow` lleva la copia blanca de 5,18:1 a **3,06:1** y `mesh` a
**2,27:1**, porque pintan un lavado claro anclado en `50% 0%` — exactamente
donde se asienta el titular. Las de línea toman la tinta de regla, oscurecen,
y la copia sube a **14,35:1** mientras la textura sigue leyéndose a 3,19:1
contra el panel. El default es `rings`, que irradia del mismo anclaje que
usaba el glow. Las tintadas se ofrecen igual, con el número en el tipo.
2. **Una trama tintada dentro del panel es invisible por construcción**:
`--_background-tint` cae en `--color-primary-solid`, que es EL MISMO valor que
pinta el panel — un halo del color del panel sobre el color del panel. El
block pasa `color="var(--color-content-on-solid)"`, el token que ya usa para
su copia; medido después, el tinte resuelve a `#ffffff`.
**Los defaults, y por qué dos están apagados.** `cta` `rings` · `stats-band`
`grid` (una banda de cifras se lee como medida) · `testimonials` y
`feature-split` `glow`. `site-footer` y `banner` reciben la capacidad **APAGADA**:
la columna B del plan de calidad no pide acabado en ninguno de los dos, y
encenderlo por nuestra cuenta sería inventar un aspecto que nadie pidió.
**Un desvío de alcance, declarado**: la columna B pedía «`Backdrop` alterno» POR
FILA en `feature-split`; se resuelve en la SECCIÓN. Una banda por fila obligaría
a cada `Row` a poseer el estado de alternancia, que es coordinación — y un block
que coordina deja de ser un block que no posee nada.
**Medido en Chrome sobre la página compuesta real** (`/blocks/landing`): cinco
pilas, los cinco anfitriones adoptados (`position: relative` + `isolation:
isolate`) sin que ningún block los posicione; la copia en modo claro mide
15,88:1 sobre las secciones decoradas y 5,18:1 sobre el panel del cta — AA en
todas. Sobre lienzo oscuro, medido aparte: 17,06 → 13,23:1 con `glow` y
12,37:1 con `grid`/`dots`.
Ledger: seis filas nuevas, **A-100…A-105**, con su mecanismo y su evidencia.
Gates: `blocks:check` 0/18 · `rtl:check` 0/180 · `docs:check` 0/0 en 634 ·
434/435 (el fallo es el `skin-media-player` de siempre) · `check` 0 errores
propios · prettier limpio.
### F1.5 — correcciones de supervisión (para el agente, ANTES del commit de F1)
| # | Qué | Cómo (exacto) | Gate |

@ -57,6 +57,11 @@ here can be blocked, so there is no state to derive and no state to explain.
## Decisions
- **La decoración existe pero está APAGADA por defecto** (2026-08-18): un aviso de
página mide dos líneas y su trabajo es leerse; la columna B no pide acabado aquí.
La capacidad se ofrece porque una tira de marketing a veces la quiere — el block
no lo decide por el app.
**2026-07-30 — reference floor** (dossier: Tailwind Plus «Banners» 13 · Untitled
UI 16 · Flowbite 5 — the most-shipped category of the whole dossier):

@ -27,9 +27,11 @@
import { Banner } from '$uix/eidos/components/banner';
import { Container } from '$uix/eidos/components/container';
import { Wrap } from '$uix/eidos/components/wrap';
import { Background } from '$uix/eidos/components/background';
import type { SiteBannerProps } from './types';
let {
decor = 'none',
containerSize = 'xl',
affix,
affixOffset = '0px',
@ -55,6 +57,13 @@
{#snippet strip()}
<Banner {...rest}>
{#if decor !== 'none'}
<!-- A child of the strip, which becomes its host through the foundation's
own rule. Off by default: a notice two lines tall is there to be read. -->
<Background>
<Background.Pattern pattern={decor} fade="to-b" />
</Background>
{/if}
<!--
`width="100%"` so the container takes the strip's free space and its own
max-width does the capping (as a flex item it would otherwise shrink to its

@ -1,6 +1,7 @@
import type { Snippet } from 'svelte';
import type { BannerProps } from '$uix/eidos/components/banner';
import type { ContainerSize } from '$uix/eidos/components/container';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
/**
* Props for the `site-banner` block.
@ -12,6 +13,15 @@ import type { ContainerSize } from '$uix/eidos/components/container';
* measure, the dismiss wiring and the slots.
*/
export type SiteBannerProps = Omit<BannerProps, 'children'> & {
/**
* Strip decoration — `none` by default, and that default is deliberate: a
* page-level notice is two lines tall and its job is to be read, so column B
* of the quality plan asks for no decoration here. The capability is offered
* because a marketing strip sometimes wants one; the block does not decide
* that for the app.
* @default 'none'
*/
decor?: BackgroundPatternKind | 'none';
/**
* Pin the strip to a viewport edge instead of letting it sit in the page
* flow. Composes the canon `Affix`, so the strip stays put through any

@ -46,6 +46,17 @@ one a machine would be the opposite mistake.
## Decisions
- **La decoración va DENTRO del panel, y estructural por medición** (2026-08-18):
sobre el panel sólido las dos tramas TINTADAS pintan un lavado claro justo donde
se asienta el titular y hunden la copia blanca bajo AA — `glow` 5,18 → **3,06:1**
y `mesh` **2,27:1**. Las de línea toman la tinta de regla, oscurecen, y la copia
sube a **14,35:1** mientras la textura sigue leyéndose a 3,19:1 contra el panel.
Por eso el default es `rings`, que además irradia del mismo anclaje superior que
usaba el glow. Se ofrecen igual: el app decide, pero ya con el número delante.
Y su acento viaja como tinta ON-SOLID, porque un glow tintado con el acento del
propio panel resuelve EXACTAMENTE al fondo del panel — un halo del color del
panel sobre el color del panel, que es nada.
**2026-07-29 — reference floor** (dossier §P1: Tailwind Plus «CTA 11» ·
PrimeBlocks «CTA 12» · Untitled UI):

@ -33,10 +33,12 @@
import { Heading } from '$uix/eidos/components/heading';
import { Text } from '$uix/eidos/components/text';
import { Motion } from '$uix/eidos/components/motion';
import { Background } from '$uix/eidos/components/background';
import type { CtaProps } from './types';
let {
layout = 'center',
decor = 'rings',
color = 'primary',
gradient = true,
level = 2,
@ -134,6 +136,32 @@
padding={{ base: 6, md: 10 }}
width="100%"
>
<!-- The decoration is a CHILD of the panel, not a wrapper: the Surface
becomes its host through the foundation's own `:has` rule, so the
panel gains a texture and nothing about its layout moves.
A STRUCTURAL pattern by default, and the reason is measured, not
aesthetic. Over the solid panel the two TINTED treatments paint a
light wash exactly where the title sits and drop white copy under
AA: `glow` 5.18 → 3.06:1, `mesh` → 2.27:1. The line patterns take
the dark rule token instead, so they DARKEN the panel and the copy
improves (14.35:1) while the texture still reads at 3.19:1 against
the canvas. `rings` radiates from the same top-centre anchor the
glow used, so the motif survives the swap.
The accent still travels as the ON-SOLID ink, for the treatments
that use it: a glow tinted with the panel's own accent resolves to
EXACTLY the panel's background — the halo would be the panel colour
over the panel colour, which is nothing. -->
{#if decor !== 'none'}
<Background>
<Background.Pattern
pattern={decor}
color="var(--color-content-on-solid)"
fade="radial"
/>
</Background>
{/if}
{#if centered}
<Stack gap={6} align="center">
{@render copy()}

@ -5,6 +5,7 @@ import type { SectionSize } from '$uix/eidos/components/section';
import type { HeadingLevel } from '$uix/eidos/components/heading';
import type { SurfaceProps } from '$uix/eidos/components/surface';
import type { ComponentColorProp } from '$uix/eidos/lib/types';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
/** How the panel arranges its copy and its actions. */
export type CtaLayout = 'center' | 'justified' | 'split';
@ -26,6 +27,24 @@ export type CtaProps = Omit<HTMLAttributes<HTMLElement>, 'children' | 'title'> &
* @default 'center'
*/
layout?: CtaLayout;
/**
* The decoration INSIDE the panel — `rings` (default) · every other
* `Background.Pattern` treatment · `none`.
*
* **The two tinted treatments cost legibility here, measured.** Over the
* solid panel `glow` and `mesh` paint a light wash exactly where the title
* sits: white copy falls from 5.18:1 to **3.06:1** and **2.27:1**, under AA.
* The line patterns (`grid` · `dots` · `lines` · `rings`) take the dark rule
* token, so they darken the panel — the copy rises to 14.35:1 and the texture
* still reads at 3.19:1. They are offered anyway; the app owns the call, and
* now it owns the number too.
*
* Their accent is the ON-SOLID ink, not the panel's colour: a glow tinted
* with the accent resolves to exactly the panel's background — a halo of the
* panel colour over the panel colour, which is nothing.
* @default 'rings'
*/
decor?: BackgroundPatternKind | 'none';
/**
* Panel accent — the full colour system (role · intent · any of the 33 donor
* scales · a raw CSS value). The panel is always the SOLID canvas: measured

@ -51,6 +51,12 @@ explain.
## Decisions
- **La decoración es `glow` por defecto** (2026-08-18), HIJA de la `Section`.
Columna B pedía «`Backdrop` alterno» por fila; se resuelve en la SECCIÓN y no
por fila a propósito: una banda por fila obligaría a cada `Row` a ser anfitriona
y a alternar su propio fondo, que es la clase de estado que un block no debe
poseer. Medido: la copia pasa de 17,06:1 a 13,23:1 en el peor punto.
**2026-07-23 — reference floor** (dossier §P1: Tailwind Plus «Features 15» ·
Untitled UI «Features 42» · Flowbite; the #1 gap):

@ -12,9 +12,11 @@
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { Stack } from '$uix/eidos/components/stack';
import { Background } from '$uix/eidos/components/background';
import type { FeatureSplitProps } from './types';
let {
decor = 'glow',
containerSize = 'lg',
sectionSize = 'xl',
gap = 12,
@ -25,6 +27,16 @@
<section {...rest}>
<Section size={sectionSize}>
{#if decor !== 'none'}
<!-- The decoration is a CHILD of the Section, not a wrapper around it: the
stack pins itself behind the section's own content, so the band bleeds
the full width behind the measure and the block gains no extra box.
The section becomes the host through the foundation's own rule —
nothing here positions it. -->
<Background>
<Background.Pattern pattern={decor} fade="radial" />
</Background>
{/if}
<Container size={containerSize}>
<Stack {gap}>
{@render children?.()}

@ -7,8 +7,19 @@ import type { HeadingProps } from '$uix/eidos/components/heading';
import type { TextProps } from '$uix/eidos/components/text';
import type { GroupProps } from '$uix/eidos/components/group';
import type { ComponentColorProp } from '$uix/eidos/lib/types';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
export type FeatureSplitProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
/**
* Section decoration — `glow` (default) · every other `Background.Pattern`
* treatment · `none`. A CHILD of the `Section`, not a wrapper around it: the
* section becomes the host through the foundation's own rule, so the band
* bleeds full-width behind the measure and the block gains no extra box.
* Reduced motion, forced colours and `prefers-contrast` are the component's
* responsibility, not the block's.
* @default 'glow'
*/
decor?: BackgroundPatternKind | 'none';
/** Content measure. Passed straight to `Container`. @default 'lg' */
containerSize?: ContainerSize;
/** Block-axis padding. Passed straight to `Section`. @default 'xl' */

@ -46,6 +46,11 @@ No state, no shape, no words.
## Decisions
- **La decoración existe pero está APAGADA por defecto** (2026-08-18): la columna
B del plan de calidad no pide acabado para el pie, y encenderlo por nuestra
cuenta sería inventar un aspecto que nadie pidió. La capacidad está para el app
que la quiera. Medido con `dots`: la copia pasa de 17,06:1 a 12,37:1.
**2026-07-30 — reference floor** (dossier: Tailwind Plus «Footers» 7 · Untitled UI
40 · Flowbite):

@ -25,9 +25,11 @@
import { AutoGrid } from '$uix/eidos/components/auto-grid';
import { Separator } from '$uix/eidos/components/separator';
import { Motion } from '$uix/eidos/components/motion';
import { Background } from '$uix/eidos/components/background';
import type { SiteFooterProps } from './types';
let {
decor = 'none',
// `lg`, the same measure `hero` / `cta` / `newsletter` default to. A footer
// wider than every other section of the page reads as misaligned — verified
// by composing it under a `lg` section.
@ -46,6 +48,16 @@
<footer {...rest}>
<Section size={sectionSize}>
{#if decor !== 'none'}
<!-- The decoration is a CHILD of the Section, not a wrapper around it: the
stack pins itself behind the section's own content, so the band bleeds
the full width behind the measure and the block gains no extra box.
The section becomes the host through the foundation's own rule —
nothing here positions it. -->
<Background>
<Background.Pattern pattern={decor} fade="radial" />
</Background>
{/if}
<Container size={containerSize}>
<!--
One entrance for the whole footer. No stagger: nobody reads a footer

@ -5,12 +5,23 @@ import type { SectionSize } from '$uix/eidos/components/section';
import type { StackProps } from '$uix/eidos/components/stack';
import type { HeadingProps } from '$uix/eidos/components/heading';
import type { AutoGridProps } from '$uix/eidos/components/auto-grid';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
// The root renders a native `<footer>`, so it carries the raw HTML attributes.
// Every OTHER part wraps a canon component and extends THAT component's props
// (which already refine `style`/`class`), never `HTMLAttributes` — spreading the
// raw HTML attribute surface into a canon component clashes on `style`.
export type SiteFooterProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
/**
* Section decoration — `none` (default) · every other `Background.Pattern`
* treatment · `none`. A CHILD of the `Section`, not a wrapper around it: the
* section becomes the host through the foundation's own rule, so the band
* bleeds full-width behind the measure and the block gains no extra box.
* Reduced motion, forced colours and `prefers-contrast` are the component's
* responsibility, not the block's.
* @default 'none'
*/
decor?: BackgroundPatternKind | 'none';
/**
* Content measure. Passed straight to `Container`. `lg` matches `hero` / `cta` /
* `newsletter`: a footer wider than the rest of the page reads as misaligned.

@ -53,6 +53,13 @@ say.
## Decisions
- **La decoración es `grid` por defecto** (2026-08-18): una banda de cifras se lee
como medida, y la rejilla es la trama que dice eso sin decorar por decorar. Va
como HIJA de la `Section` —la sección se vuelve anfitriona por la regla de
foundation—, así que la banda sangra a todo el ancho detrás de la medida y el
block no gana ninguna caja. Medido sobre el lienzo de página: la copia pasa de
17,06:1 a 12,37:1 en el peor punto, muy por encima de AA.
**2026-07-29 — reference floor** (dossier §P1: Tailwind Plus «Stats 8» ·
Untitled UI «Metrics 16» · Flowbite):

@ -10,9 +10,11 @@
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { AutoGrid } from '$uix/eidos/components/auto-grid';
import { Background } from '$uix/eidos/components/background';
import type { StatsBandProps } from './types';
let {
decor = 'grid',
containerSize = 'lg',
sectionSize = 'lg',
columns,
@ -25,6 +27,16 @@
<section {...rest}>
<Section size={sectionSize}>
{#if decor !== 'none'}
<!-- The decoration is a CHILD of the Section, not a wrapper around it: the
stack pins itself behind the section's own content, so the band bleeds
the full width behind the measure and the block gains no extra box.
The section becomes the host through the foundation's own rule —
nothing here positions it. -->
<Background>
<Background.Pattern pattern={decor} fade="radial" />
</Background>
{/if}
<Container size={containerSize}>
<!--
`data-stagger` is the foundation's structural rhythm: it writes each

@ -8,11 +8,22 @@ import type {
MetricsValueProps
} from '$uix/eidos/components/metrics';
import type { CountUpProps } from '$uix/eidos/components/count-up';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
// Sub-parts extend the props of the canon component they wrap, never
// `HTMLAttributes`: the raw attribute surface clashes with the canon's refined
// `style`/`class` when spread through (the feature-grid lesson).
export type StatsBandProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
/**
* Section decoration — `grid` (default) · every other `Background.Pattern`
* treatment · `none`. A CHILD of the `Section`, not a wrapper around it: the
* section becomes the host through the foundation's own rule, so the band
* bleeds full-width behind the measure and the block gains no extra box.
* Reduced motion, forced colours and `prefers-contrast` are the component's
* responsibility, not the block's.
* @default 'grid'
*/
decor?: BackgroundPatternKind | 'none';
/** Content measure. Passed straight to `Container`. @default 'lg' */
containerSize?: ContainerSize;
/** Block-axis padding. Passed straight to `Section`. @default 'lg' */

@ -42,6 +42,13 @@ state, no words. The face is the app's, in the `avatar` slot.
## Decisions
- **La decoración es `glow` por defecto** (2026-08-18): un halo cálido detrás de
las citas es el acabado que todas las referencias pintan a mano. HIJA de la
`Section`. Medido sobre el lienzo de página: la copia pasa de 17,06:1 a
**13,23:1** en el punto más brillante del halo — el glow AQUÍ es seguro porque
el lienzo es oscuro y su tinte lo aclara poco; dentro de un panel sólido claro
sería lo contrario, y eso está medido en el README de `cta`.
**2026-07-24 — reference floor** (dossier §P1: Tailwind Plus «Testimonials 8» ·
Untitled UI «Testimonials 26» · Flowbite):

@ -11,10 +11,12 @@
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { Stack } from '$uix/eidos/components/stack';
import { Background } from '$uix/eidos/components/background';
import { setTestimonialsContext } from './context';
import type { TestimonialsProps } from './types';
let {
decor = 'glow',
layout = 'grid',
containerSize = 'lg',
sectionSize = 'lg',
@ -34,6 +36,16 @@
<section {...rest}>
<Section size={sectionSize}>
{#if decor !== 'none'}
<!-- The decoration is a CHILD of the Section, not a wrapper around it: the
stack pins itself behind the section's own content, so the band bleeds
the full width behind the measure and the block gains no extra box.
The section becomes the host through the foundation's own rule —
nothing here positions it. -->
<Background>
<Background.Pattern pattern={decor} fade="radial" />
</Background>
{/if}
<!-- The spotlight takes a NARROWER measure: one quote set at the section's
full width is a line nobody finishes reading. The cap rides the canon's
own container scale instead of a hand-written number. -->

@ -7,6 +7,7 @@ import type { BoxProps } from '$uix/eidos/components/box';
import type { CardProps } from '$uix/eidos/components/card';
import type { TextProps } from '$uix/eidos/components/text';
import type { GroupProps } from '$uix/eidos/components/group';
import type { BackgroundPatternKind } from '$uix/eidos/components/background';
/**
* How the section arranges its quotes.
@ -25,6 +26,16 @@ import type { GroupProps } from '$uix/eidos/components/group';
export type TestimonialsLayout = 'grid' | 'spotlight';
export type TestimonialsProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
/**
* Section decoration — `glow` (default) · every other `Background.Pattern`
* treatment · `none`. A CHILD of the `Section`, not a wrapper around it: the
* section becomes the host through the foundation's own rule, so the band
* bleeds full-width behind the measure and the block gains no extra box.
* Reduced motion, forced colours and `prefers-contrast` are the component's
* responsibility, not the block's.
* @default 'glow'
*/
decor?: BackgroundPatternKind | 'none';
/**
* The arrangement. Travels to the parts through context, so it is set ONCE
* here — `Items` stops being a grid, `Item` drops its card, the quote steps up

Loading…
Cancel
Save

Powered by TurnKey Linux.