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/radio-cards.md

162 lines
13 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.

# radio-cards — 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**: **61%** — 43 de 70 knobs por token público
- **Knobs de apariencia**: 72 — público 43 · privado 0 · global 23 · literal 4 · sistema 2 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 40 pública(s) — `gap`, `horizontal-basis`, `padding-xs`, `padding-sm`, `padding-md`, `padding-lg`, `padding-xl`, `radius`, `border-width`, `border`, `hover-border`, `invalid-border`, `bg`, `content-gap`, `text-gap`, `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-fg`, `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`, `icon-size-xs`, `icon-size-sm`, `icon-size-md`, `icon-size-lg`, `icon-size-xl`, `icon-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`
- **Eje `size`**: sí · **ficheros**: `radio-cards.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (23)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-cards.css:97` | `[data-radio-cards] [data-radio-cards-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly])` | `border-color` | `var(--_radio-group-palette-solid-hover)` ⤴ prestado de `radio-group` |
| 2 | `radio-cards.css:98` | `[data-radio-cards] [data-radio-cards-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly])` | `background` | `color-mix(in srgb, var(--_radio-group-palette-solid) 12%, var(--color-surface-default))` ⤴ prestado de `radio-group` |
| 3 | `radio-cards.css:116` | `[data-radio-cards] [data-radio-cards-item][data-state='checked']` | `border-color` | `var(--_radio-group-palette-solid)` ⤴ prestado de `radio-group` |
| 4 | `radio-cards.css:117` | `[data-radio-cards] [data-radio-cards-item][data-state='checked']` | `background` | `color-mix(in srgb, var(--_radio-group-palette-solid) 7%, var(--color-surface-default))` ⤴ prestado de `radio-group` |
| 5 | `radio-cards.css:169` | `[data-radio-cards] [data-radio-cards-item][data-state='checked'] [data-radio-cards-icon]` | `color` | `var(--_radio-group-palette-solid)` ⤴ prestado de `radio-group` |
| 6 | `radio-cards.css:234` | `[data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-md)` ⤴ prestado de `radio-group` |
| 7 | `radio-cards.css:235` | `[data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-md)` ⤴ prestado de `radio-group` |
| 8 | `radio-cards.css:236` | `[data-radio-cards-indicator]` | `border` | `var(--radio-group-indicator-border-width) solid var(--radio-group-indicator-border)` ⤴ prestado de `radio-group` |
| 9 | `radio-cards.css:237` | `[data-radio-cards-indicator]` | `border-radius` | `var(--radius-full)` |
| 10 | `radio-cards.css:238` | `[data-radio-cards-indicator]` | `background` | `var(--radio-group-indicator-bg)` ⤴ prestado de `radio-group` |
| 11 | `radio-cards.css:249` | `[data-radio-cards-indicator]::after` | `border-radius` | `var(--radius-full)` |
| 12 | `radio-cards.css:250` | `[data-radio-cards-indicator]::after` | `background` | `var(--_radio-group-palette-solid)` ⤴ prestado de `radio-group` |
| 13 | `radio-cards.css:256` | `[data-radio-cards][data-size='xs'] [data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-xs)` ⤴ prestado de `radio-group` |
| 14 | `radio-cards.css:257` | `[data-radio-cards][data-size='xs'] [data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-xs)` ⤴ prestado de `radio-group` |
| 15 | `radio-cards.css:260` | `[data-radio-cards][data-size='sm'] [data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-sm)` ⤴ prestado de `radio-group` |
| 16 | `radio-cards.css:261` | `[data-radio-cards][data-size='sm'] [data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-sm)` ⤴ prestado de `radio-group` |
| 17 | `radio-cards.css:264` | `[data-radio-cards][data-size='lg'] [data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-lg)` ⤴ prestado de `radio-group` |
| 18 | `radio-cards.css:265` | `[data-radio-cards][data-size='lg'] [data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-lg)` ⤴ prestado de `radio-group` |
| 19 | `radio-cards.css:268` | `[data-radio-cards][data-size='xl'] [data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-xl)` ⤴ prestado de `radio-group` |
| 20 | `radio-cards.css:269` | `[data-radio-cards][data-size='xl'] [data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-xl)` ⤴ prestado de `radio-group` |
| 21 | `radio-cards.css:274` | `[data-radio-cards] [data-radio-cards-item][data-state='checked'] [data-radio-cards-indicator]` | `border-color` | `var(--_radio-group-palette-solid)` ⤴ prestado de `radio-group` |
| 22 | `radio-cards.css:281` | `[data-radio-cards] [data-radio-cards-item][data-disabled] [data-radio-cards-indicator]` | `border-color` | `var(--radio-group-disabled-indicator-border)` ⤴ prestado de `radio-group` |
| 23 | `radio-cards.css:282` | `[data-radio-cards] [data-radio-cards-item][data-disabled] [data-radio-cards-indicator]` | `background` | `var(--radio-group-disabled-indicator-bg)` ⤴ prestado de `radio-group` |
### 1.2 A través de un privado (0)
_Ninguno._
### 1.3 Literales (4)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-cards.css:147` | `[data-radio-cards-icon] svg` | `inline-size` | `100%` |
| 2 | `radio-cards.css:148` | `[data-radio-cards-icon] svg` | `block-size` | `100%` |
| 3 | `radio-cards.css:247` | `[data-radio-cards-indicator]::after` | `inline-size` | `46%` |
| 4 | `radio-cards.css:248` | `[data-radio-cards-indicator]::after` | `block-size` | `46%` |
### 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 (2) — 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 | `radio-cards.css:89` | `[data-radio-cards] [data-radio-cards-item]:hover:not([data-disabled]):not([data-readonly]):not([data-state='checked'])` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
| 2 | `radio-cards.css:102` | `[data-radio-cards] [data-radio-cards-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
_La receta no declara privados propios en su CSS._
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_radio-cards-columns`.
## 4. Propuesta de corrección
- **Consume tokens públicos de `radio-group`.** Un token prestado importa la semántica de su dueño: la corrección no es duplicarlo con prefijo propio, sino la decisión de familia que la auditoría de fase 1 dejó registrada (`theming-audit.md` §B, familia calendar).
- **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` (19)
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 (`--radio-cards-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `on-item-border` | `root` | `var(--_radio-group-palette-solid)` | 2 |
| `indicator-width` | `root` | ⚠ `var(--radio-group-item-size-md)` / `46%` | 2 |
| `indicator-height` | `root` | ⚠ `var(--radio-group-item-size-md)` / `46%` | 2 |
| `indicator-radius` | `root` | `var(--radius-full)` | 2 |
| `indicator-bg` | `root` | ⚠ `var(--radio-group-indicator-bg)` / `var(--_radio-group-palette-solid)` | 2 |
| `hover-on-item-border` | `root` | `var(--_radio-group-palette-solid-hover)` | 1 |
| `hover-on-item-bg` | `root` | `color-mix(in srgb, var(--_radio-group-palette-solid) 12%, var(--color-surface-default))` | 1 |
| `on-item-bg` | `root` | `color-mix(in srgb, var(--_radio-group-palette-solid) 7%, var(--color-surface-default))` | 1 |
| `on-item-fg` | `root` | `var(--_radio-group-palette-solid)` | 1 |
| `indicator-width-xs` | `root` | `var(--radio-group-item-size-xs)` | 1 |
| `indicator-height-xs` | `root` | `var(--radio-group-item-size-xs)` | 1 |
| `indicator-width-sm` | `root` | `var(--radio-group-item-size-sm)` | 1 |
| `indicator-height-sm` | `root` | `var(--radio-group-item-size-sm)` | 1 |
| `indicator-width-lg` | `root` | `var(--radio-group-item-size-lg)` | 1 |
| `indicator-height-lg` | `root` | `var(--radio-group-item-size-lg)` | 1 |
| `indicator-width-xl` | `root` | `var(--radio-group-item-size-xl)` | 1 |
| `indicator-height-xl` | `root` | `var(--radio-group-item-size-xl)` | 1 |
| `disabled-item-border` | `root` | `var(--radio-group-disabled-indicator-border)` | 1 |
| `disabled-item-bg` | `root` | `var(--radio-group-disabled-indicator-bg)` | 1 |
### 4.2 Sin nombre mecánico (3)
- **⚠ 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`.
- **⚠ decisión: `border` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border`.
### 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 -->
**ADJUDICADO 2026-08-22 — 60 % → 61 %, y ése es su techo.** Una sola clave
nueva, porque **21 de sus 23 knobs fuera de alcance NO son suyos**.
`RadioCards` es un **skin sobre RadioGroup**: renderiza `<RadioGroup.Provider>`
y su indicador ES un radio. Por eso lee `--radio-group-item-size-*`,
`--radio-group-indicator-*` y el puente de paleta del anfitrión
(`--_radio-group-palette-*`, declarado en el host `[data-radio-group]` donde
esta tarjeta vive). El censo los marca «⤴ prestado» y el mandato de composición
—`theming-audit` §5.3-3— dice que **se teman en la ficha del dueño**: coserlos
aquí sería vocabulario paralelo, y además dejaría que un tema rompiera «un radio
es un radio» sólo dentro de las tarjetas.
**Lo acuñado, que sí es suyo**: `invalid-border` — el borde de la TARJETA en
validación fallida. La tarjeta es de este skin; el indicador no.
**Los dos `--radius-full` del indicador son identidad**, no knobs: un radio es
redondo por definición (misma clase que el `100%` y el `50%` de un círculo). Y
sus cuatro literales son geometría del glifo (el `46%` del punto interior, el
`100%` del SVG del icono).
**Cinco adjudicaciones, todas forzadas a mano y con las transiciones
CONGELADAS**: la tarjeta transiciona `border-color`, y leer justo después de
escribir devuelve el valor de partida — la primera medición de `invalid-border`
dio «sin efecto» por eso, que es exactamente el motivo por el que el guard
congela. Con la congelación: invalid `oklch(0.9555 0.0207 13.86) → rgb(1,2,3)`,
`horizontal-basis` 192px → 1234px, `disabled-opacity` 0.4 → 0.123, y las dos
de transición medidas SIN congelar (0.12s → 11.5s).
Sonda antes/después: **0 diffs en 3.540 valores** (19 nodos × 7 estados).
<!-- veredicto:end -->

Powered by TurnKey Linux.