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

169 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-23 · **Alcance**: **78%** — 28 de 36 knobs por token público
- **Knobs de apariencia**: 37 — público 28 · privado 8 · global 0 · literal 0 · sistema 1 · excepción 0 _(los dos ú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 (8)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `card.css:60` | `[data-card]` | `background` | `var(--_card-bg)` |
| 2 | `card.css:61` | `[data-card]` | `color` | `var(--_card-fg)` |
| 3 | `card.css:198` | `[data-card][data-lift][data-variant='ghost']:hover:not([data-disabled])` | `background` | `var(--_card-palette-track)` |
| 4 | `card.css:202` | `[data-card][data-lift][data-variant='outline']:hover:not([data-disabled])` | `background` | `var(--_card-palette-track)` |
| 5 | `card.css:293` | `[data-card-title]` | `color` | `var(--_card-fg)` |
| 6 | `card.css:309` | `[data-card][data-variant='solid'] [data-card-description]` | `color` | `color-mix(in srgb, var(--_card-palette-contrast) 80%, transparent)` |
| 7 | `card.css:317` | `[data-card-body]` | `color` | `var(--_card-fg)` |
| 8 | `card.css:333` | `[data-card][data-variant='solid'] [data-card-footer]` | `border-block-start-color` | `color-mix(in srgb, var(--_card-palette-contrast) 18%, transparent)` |
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (0) — 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.
_Ninguno._
## 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:226` | `[data-card][data-interactive]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 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-palette-track)` / `var(--_card-palette-solid)` / `var(--card-outline-bg)` / `transparent` | 5 |
| `fg` | `root` | ⚠ `var(--_card-palette-text)` / `var(--_card-palette-contrast)` | 5 |
| `title-fg` | `root` | ⚠ `var(--_card-palette-text)` / `var(--_card-palette-contrast)` | 5 |
| `body-fg` | `root` | ⚠ `var(--_card-palette-text)` / `var(--_card-palette-contrast)` | 5 |
### 4.2 Sin nombre mecánico (4)
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 3: `background`, `color`.
- **⚠ decisión: la propiedad no tiene slot canónico en el vocabulario** — 1: `border-block-start-color`.
### 4.3 Avisos sobre los tokens propuestos (2)
- **el privado `--_card-bg` debe pasar a leer este público (o desaparecer)** — `--card-bg`
- **el privado `--_card-fg` debe pasar a leer este público (o desaparecer)** — `--card-fg`, `--card-title-fg`, `--card-body-fg`
### 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.