12 KiB
background — alcance de tema: análisis y propuesta
Generado por
node --import tsx/esm scripts/theming-census.ts --report. Lo medido y la propuesta se regeneran; el Veredicto (§5) se conserva. Vista de conjunto: README · método y protocolo:PLAN-theming.md§1, §2, §7.
- Medido: 2026-08-25 · Alcance: 100% — 19 de 19 knobs por token público
- Knobs de apariencia: 23 — público 19 · privado 0 · global 0 · literal 0 · sistema 3 · excepción 3 · estructural 0 · puente 0 · canal 1 (los cinco últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 37 pública(s) —pattern-rule,pattern-cell,pattern-dot-size,pattern-glow-size,pattern-glow-at,pattern-glow-strength,pattern-mesh-image,pattern-mesh-opacity,pattern-noise-opacity,pattern-vignette-strength,pattern-lines-width,pattern-lines-gap,pattern-lines-angle,pattern-rings-width,pattern-rings-gap,fade-size,fade-at,scrim-fg,scrim-fg-over-dark,scrim-fg-over-light,scrim-blur-sm,scrim-blur-md,scrim-blur-lg,scrim-blur-xl,scrim-blur-xxl,scrim-strength-xs,scrim-strength-sm,scrim-strength-md,scrim-strength-lg,scrim-strength-xl,gradient-drift-duration,pause-offset,pause-z,parallax-travel,spotlight-size,spotlight-strength,spotlight-fg - Eje
size: no · ficheros:background.css
1. Knobs fuera de alcance
1.1 Directo a primitivo global (0)
Ninguno.
1.2 A través de un privado (0)
Sólo el residuo: el puente de paleta (§2-ter) y el canal de valor (§2-quater) salen aparte, porque no son deuda ni tienen nombre que acuñar.
Ninguno.
1.3 Literales (0)
Ninguno.
1.4 Excepciones firmadas (3) — fuera del ratio
Literales que llevan su anotación /* literal: <razón> */ en la propia
declaración: la válvula de recipe-contract §3, la misma que honra
component-audit. Una desviación firmada no es deuda — se listan para que la
razón se lea, no para acuñarlas.
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | background.css:76 |
[data-background-layer] :where(img, video) |
inline-size |
100% |
| 2 | background.css:77 |
[data-background-layer] :where(img, video) |
block-size |
100% |
| 3 | background.css:240 |
[data-background-layer][data-pattern='noise'] |
background-image |
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E") |
2. Sistema transversal (3) — informativo, fuera del ratio
Un tema los alcanza a nivel de sistema, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | background.css:100 |
[data-background-layer][data-opacity='subtle'] |
opacity |
var(--opacity-subtle) |
| 2 | background.css:103 |
[data-background-layer][data-opacity='muted'] |
opacity |
var(--opacity-muted) |
| 3 | background.css:106 |
[data-background-layer][data-opacity='ghost'] |
opacity |
var(--opacity-ghost) |
2-quater. Canal de valor (1) — fuera del ratio
La receta lee un privado que nadie declara — ni su CSS ni el generador: lo
escribe soma o el envoltorio por instancia (un %, un rect medido, la talla
que pide una prop). Un tema no debe alcanzarlo: fijarlo rompe el
comportamiento, y por eso tabs rechazó por escrito esa misma propuesta.
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | background.css:265 |
[data-background-layer][data-kind='gradient'] |
background |
var(--_background-gradient-image, transparent) |
3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
|---|---|---|---|---|
--_background-tint |
2 | var(--color-primary-solid), var(--palette-solid, var(--color-primary-solid)) |
global | no |
--_background-rule |
1 | var(--background-pattern-rule) |
public | sí |
--_background-cell |
1 | var(--background-pattern-cell) |
public | sí |
--_background-scrim-ink |
4 | var(--background-scrim-fg), var(--background-scrim-fg-over-dark), var(--background-scrim-fg-over-light), var(--palette-solid, var(--background-scrim-fg)) |
public | sí |
--_background-scrim-weight |
5 | var(--background-scrim-strength-md), var(--background-scrim-strength-xs), var(--background-scrim-strength-sm), var(--background-scrim-strength-lg), var(--background-scrim-strength-xl) |
public | sí |
--_background-parallax-offset |
3 | calc(var(--_background-parallax-travel) * -1), var(--_background-parallax-travel), calc( (var(--background-progress, 0.5) - 0.5) * 2 * var(--_background-parallax-travel) ) |
private, public | no |
--_background-parallax-travel |
1 | calc( var(--background-parallax-travel) * var(--_background-speed, 0) ) |
public | sí |
--_background-bleed-size |
1 | var( --_background-bleed, max(var(--_background-parallax-travel), var(--_background-parallax-travel) * -1) ) |
private | no |
--_background-spotlight-ink |
2 | var(--background-spotlight-fg), var(--palette-solid, var(--background-spotlight-fg)) |
public | sí |
Consumidos y no declarados en el CSS (vienen de base.ts o de un estilo inline del wrapper): --_background-bleed, --_background-depth, --_background-gradient-image, --_background-speed.
4. Propuesta de corrección
4.1 Tokens a declarar en lib/recipes/base.ts (1)
Valor verbatim del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca ⚠.
token (--background-…) |
scope TSC | valor propuesto | usos |
|---|---|---|---|
layer-bg-image |
root |
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E") |
1 |
4.2 Sin nombre mecánico (2)
- ⚠ decisión:
100%es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar — 2:inline-size,block-size.
4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- Privado que no deriva de un público — §3 lo marca; el privado debe leer el público o desaparecer.
- Velo o acento en el nodo equivocado (
archetype: 'item'en un envoltorio, unbackgrounden shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba. - Doble animación al mover un sello a una superficie con animación propia — registro de
animationstart/animationend. - Diff de computed = 0 en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- Centinela por token nuevo: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
5. Veredicto
EJECUTADO 2026-08-23 — 57 % → 95 %. Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y 19 tokens preexistentes medidos uno a uno: el componente no había pasado por R-5.4.
Las seis claves son la MISMA costura, dos veces. El patrón mesh leía
var(--gradient-aurora) y los cinco pasos de escarcha leían var(--blur-*),
los dos a pelo: el VALOR es del sistema, el KNOB es del componente. Con
scrim-blur-* hay además una razón dura — blur es familia MÉTRICA del eje
scaling (theming §23), así que un literal ahí sería ciego al zoom global.
Tres literales firmados. El 100% de img / video es identidad (una capa
de medio ES la caja de su capa). Y el grano del ruido no se acuña: es un
feTurbulence en un data-URI, la textura ES la técnica —el único patrón que el
UA no elimina bajo forced-colors— y re-escaparlo para meterlo en el contrato
cambiaría el valor serializado sin cambiar la imagen. Su mando sigue siendo
pattern-noise-opacity.
El 5 % que queda es el privado, y es canal de valor.
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop colors. Un público encima no lo alcanzaría —el inline gana a
toda regla normal; sólo un !important de autor lo pisa, y al pisarlo el
degradado deja de seguir a la prop— y
mentiría: es el caso de --gp-current-gradient del gradient-picker y del
preview-z de drag-drop que la revisión de F2-B retiró.
Una sospecha mía, refutada midiendo. La línea del mesh hacía exactamente lo
que un apunte de referencia da por roto: un gradiente MESH consumido por el
longhand background-image (su color base final no es una capa de imagen
válida, así que computa none). Medido aquí: --gradient-aurora pinta —su
serialización empieza por un radial-gradient y el navegador la acepta—, así
que no hay defecto. La nota vale para un mesh serializado con color base
final, no para este token.
El guard midió 3 de 37 y ahora mide 18, con un campo nuevo. Las partes de
este componente son CAPAS OPT-IN independientes (velo, foco, pausa…), cada una
tras su control, y sólo un patrón se renderiza a la vez: sobre el escenario por
defecto casi todo el contrato no tiene nodo que pintar. prepareWith (nuevo:
enciende TODOS los controles que se le den, al contrario que openWith, que
para en el primero) más el barrido de data-pattern lo llevan a 18.
Y una lección del propio guard: montar MÁS puede medir MENOS. Encender
además el foco puntual, la velocidad de paralaje y la profundidad hizo que la
corrida bajase de 17 a 9: los tres repintan el background-image y el
translate de la misma capa donde se medían los patrones. prepareWith se dejó
en lo que no tapa.
Las 19 adjudicaciones, todas medidas forzando el atributo sobre la capa REAL
(nunca sobre un nodo fabricado): las dos del mask (fade-*, que el guard no
puede ver porque no fotografía mask-image — hueco de instrumento, §13), las
dos del contexto de tinta (on='dark' / on='light'), los ocho pasos de las
dos escalas (blur 4→33, 8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4
· 0.7 → 0.9), el gradient-drift-duration (24s → 42s, en una capa gradient
que EXCLUYE a la de patrón), los tres del foco puntual (40% → 42px…, y
spotlight-fg sólo cuando la capa NO lleva data-color: con él manda
--palette-solid, por diseño), el parallax-travel (sólo entra por las
keyframes ligadas al scroll, que el escenario medido no tiene) y los dos de la
pausa (la demo no monta ese control: cero nodos en la página).
Un aviso para quien mida aquí: los chips de esta demo se aplican con RETRASO.
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada igual en las dos
corridas. La prueba buena fue la de EQUIVALENCIA, determinista: cada token nuevo
resuelve al mismo valor que el primitivo que sustituyó
(pattern-mesh-image = --gradient-aurora; scrim-blur-{k} = --blur-{k} =
4/8/12/16/24 px), y lo pintado al forzar el atributo es idéntico (el mesh pinta
el aurora, el ruido pinta su data-URI intacto).
Verificación: sonda estándar antes/después 0 diffs (384 valores · 7
estados) · equivalencia token↔primitivo exacta en las seis claves · R-5.4 18/37
con las 19 restantes adjudicadas y medidas, cero STALE · component:audit PASS
· censo 95 % · eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid, 0
class-hooks · docs:check 0.