You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/audit/theming/card.md

175 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# card — 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](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-25 · **Alcance**: **100%** — 28 de 28 knobs por token público
- **Knobs de apariencia**: 33 — público 28 · privado 0 · global 0 · literal 0 · sistema 1 · excepción 4 · estructural 0 · puente 4 · canal 0 _(los cinco últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 98 pública(s) — `padding-inline-xs`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `padding-inline-xl`, `padding-block-xs`, `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-block-xl`, `gap-xs`, `gap-sm`, `gap-md`, `gap-lg`, `gap-xl`, `font-family`, `line-height`, `title-font-family`, `title-font-size-xs`, `title-font-size-sm`, `title-font-size-md`, `title-font-size-lg`, `title-font-size-xl`, `title-font-weight`, `title-line-height`, `title-letter-spacing`, `description-font-family`, `description-font-size-xs`, `description-font-size-sm`, `description-font-size-md`, `description-font-size-lg`, `description-font-size-xl`, `description-line-height`, `description-fg`, `body-font-size-xs`, `body-font-size-sm`, `body-font-size-md`, `body-font-size-lg`, `body-font-size-xl`, `body-line-height`, `border-width`, `radius-sm`, `radius-md`, `radius-lg`, `header-gap`, `footer-gap`, `footer-padding-start`, `footer-border-fg`, `emerge-duration`, `emerge-ease`, `emerge-distance`, `hover-lift`, `hover-shadow`, `press-scale`, `press-duration`, `selected-ring-width`, `disabled-opacity`, `outline-bg`, `primary-solid`, `primary-text`, `primary-contrast`, `secondary-solid`, `secondary-text`, `secondary-contrast`, `neutral-solid`, `neutral-text`, `neutral-contrast`, `affirm-solid`, `affirm-text`, `affirm-contrast`, `fulfill-track`, `fulfill-border`, `fulfill-solid`, `fulfill-text`, `fulfill-contrast`, `risk-solid`, `risk-text`, `risk-contrast`, `threat-solid`, `threat-text`, `threat-contrast`, `loss-track`, `loss-border`, `loss-solid`, `loss-text`, `loss-contrast`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 5 privada(s) forward — `_palette-track`, `_palette-border`, `_palette-solid`, `_palette-text`, `_palette-contrast`
- **Eje `size`**: sí · **ficheros**: `card.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 (4) — 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 | `card.css:66` | `[data-card]` | `background` | `var(--_card-bg)` |
| 2 | `card.css:67` | `[data-card]` | `color` | `var(--_card-fg)` |
| 3 | `card.css:299` | `[data-card-title]` | `color` | `var(--_card-fg)` |
| 4 | `card.css:323` | `[data-card-body]` | `color` | `var(--_card-fg)` |
## 2. Sistema transversal (1) — 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 | `card.css:232` | `[data-card][data-interactive]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 2-ter. Puente de paleta THM-2 (4) — fuera del ratio
La receta lee `var(--_card-palette-{slot})`, que **lo escribe el forward** de la
cascada de paleta bajo `[data-card]:where([data-color], [data-color-custom])`
(`renderRecipePaletteForward`, firma B′). Es el MECANISMO de la paleta por
instancia, y se alcanza **dos veces**: por la capa compartida `--palette-*` y
por los tonos públicos del propio componente. **No se acuña**: un público
encima dejaría que un tema lo fijara y matara en silencio el `color=` de cada
instancia (veredictos §5 de `card`, `tags-input`, `avatar`).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `card.css:204` | `[data-card][data-lift][data-variant='ghost']:hover:not([data-disabled])` | `background` | `var(--_card-palette-track)` |
| 2 | `card.css:208` | `[data-card][data-lift][data-variant='outline']:hover:not([data-disabled])` | `background` | `var(--_card-palette-track)` |
| 3 | `card.css:315` | `[data-card][data-variant='solid'] [data-card-description]` | `color` | `color-mix(in srgb, var(--_card-palette-contrast) 80%, transparent)` |
| 4 | `card.css:339` | `[data-card][data-variant='solid'] [data-card-footer]` | `border-block-start-color` | `color-mix(in srgb, var(--_card-palette-contrast) 18%, transparent)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_card-padding-inline` | 5 | `var(--card-padding-inline-md)`, `var(--card-padding-inline-xs)`, `var(--card-padding-inline-sm)`, `var(--card-padding-inline-lg)`, `var(--card-padding-inline-xl)` | public | **sí** |
| `--_card-padding-block` | 5 | `var(--card-padding-block-md)`, `var(--card-padding-block-xs)`, `var(--card-padding-block-sm)`, `var(--card-padding-block-lg)`, `var(--card-padding-block-xl)` | public | **sí** |
| `--_card-gap` | 5 | `var(--card-gap-md)`, `var(--card-gap-xs)`, `var(--card-gap-sm)`, `var(--card-gap-lg)`, `var(--card-gap-xl)` | public | **sí** |
| `--_card-title-font-size` | 5 | `var(--card-title-font-size-md)`, `var(--card-title-font-size-xs)`, `var(--card-title-font-size-sm)`, `var(--card-title-font-size-lg)`, `var(--card-title-font-size-xl)` | public | **sí** |
| `--_card-description-font-size` | 5 | `var(--card-description-font-size-md)`, `var(--card-description-font-size-xs)`, `var(--card-description-font-size-sm)`, `var(--card-description-font-size-lg)`, `var(--card-description-font-size-xl)` | public | **sí** |
| `--_card-body-font-size` | 5 | `var(--card-body-font-size-md)`, `var(--card-body-font-size-xs)`, `var(--card-body-font-size-sm)`, `var(--card-body-font-size-lg)`, `var(--card-body-font-size-xl)` | public | **sí** |
| `--_card-radius` | 3 | `var(--card-radius-md)`, `var(--card-radius-sm)`, `var(--card-radius-lg)` | public | **sí** |
| `--_card-bg` | 5 | `var(--_card-palette-track)`, `var(--_card-palette-solid)`, `var(--card-outline-bg)`, `transparent` | literal, private, public | no |
| `--_card-fg` | 5 | `var(--_card-palette-text)`, `var(--_card-palette-contrast)` | private | no |
| `--_card-border-color` | 5 | `transparent`, `var(--_card-palette-border)` | literal, private | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_card-palette-border`, `--_card-palette-contrast`, `--_card-palette-solid`, `--_card-palette-text`, `--_card-palette-track`.
## 4. Propuesta de corrección
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (4)
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 (`--card-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `bg` | `root` | `var(--_card-bg)` | 1 |
| `fg` | `root` | `var(--_card-fg)` | 1 |
| `title-fg` | `root` | `var(--_card-fg)` | 1 |
| `body-fg` | `root` | `var(--_card-fg)` | 1 |
### 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, un `background` en 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
<!-- veredicto:start -->
**EJECUTADO 2026-08-23 — 78 % → 78 %, contrato 97 → 98 claves. El componente
está CERRADO en su número, y ése es el hallazgo.**
`card` es un PRIMITIVO con paleta, y su columna `private` no es deuda: es el
puente THM-2. Clasificados uno a uno los ocho knobs de §1.2:
| # | knob | clase | veredicto |
| ---: | --- | --- | --- |
| 3, 4 | el velo de hover de `ghost` y `outline` (`--_card-palette-track`) | **puente THM-2** | no se acuña |
| 6 | la descripción en `solid` (mezcla sobre `--_card-palette-contrast`) | **puente THM-2** | no se acuña |
| 8 | el filete del pie en `solid` (misma mezcla) | **puente THM-2** | no se acuña |
| 1 | `background: var(--_card-bg)` | **conmutador por VARIANTE**, 4 fuentes | privado; 3 de sus 4 ramas son el puente |
| 2, 5, 7 | `color: var(--_card-fg)` en raíz, título y cuerpo | **conmutador por VARIANTE**, 2 fuentes | privado; las dos ramas son el puente |
Un público encima de cualquiera de ellos dejaría que un tema lo fijara y matara
en silencio el `color=` de cada instancia (`CONTINUE-theming` §3.pre). El techo
de `card` **no es el 100 %**.
**La única costura real, y entró**: la rama `outline` del conmutador leía
`var(--color-surface-default)` a pelo — el valor es del sistema, el knob es de
la card. Nace `--card-outline-bg` con ese valor verbatim. No mueve el ratio (el
conmutador sigue teniendo ramas privadas), y sí cierra el único agujero de
alcance que quedaba: el centinela lo confirma vivo.
**Diff de computed: VACÍO.** 1.216 valores en 7 estados (sonda estándar) más
2.484 valores en 18 celdas de variante / tono / `rounded` / `selected` /
`disabled` / `lift` (barrido dirigido, porque el escenario de la demo monta UNA
card `soft` y la sonda no barre variantes). Capturas 2× antes y después,
idénticas.
**Centinela R-5.4: 49/98, 49 adjudicadas** — 40 por PATRÓN (la cascada de
paleta, medida aquí: con `data-color=primary`, `--card-primary-track` no mueve
nada y `--palette-track` sí) y 9 individuales, cada una forzada sobre el nodo
real. Dos merecen leerse:
- **el trío `emerge-*` no pinta en la demo y no es culpa de la demo**: el
montaje CEDE al sistema de motion (`:not([data-animation-style])`) y el
escenario arranca con el preset `select-pop`. Quitado el atributo, los tres
alcanzan. Y `emerge-distance` tiene además una segunda causa: alimenta
`--motion-distance-md` y la animación termina en `translate 0` con
`fill-mode: both`, así que `transform` en reposo NUNCA puede enseñarlo — se
mide leyendo el hook (8px → 1234px).
- **`hover-lift` y `press-scale` mueven `transform`, que el guard no
fotografía** (mismo hueco que `rating-group.item-hover-offset`).
**Hallazgo de composición (medido, no forzado)**: dentro de un `CardGroup` el
eje `radius` de la card está MUERTO — `[data-card-group] [data-card]` es
(0,2,0) contra la (0,1,0) de la receta, así que manda
`--card-group-card-radius`. Es la anidación concéntrica de §30, deliberada y
documentada en `card-group.css`; se registra porque significa que
`--card-radius-*` no tiene efecto en ese contexto. Medido:
`--card-radius-md: 1234px` no mueve nada, `--card-group-card-radius: 1234px`
mueve las dos cards.
**Anotado, fuera del eje**: la raíz declara `background:` en atajo, lo que fija
`background-image: none` y cancela el velo del sistema — card es uno de los 45
componentes de la incidencia §12 (`next-features`). Arreglarlo mueve píxel.
<!-- veredicto:end -->

Powered by TurnKey Linux.