From befd1987feacfd88e2601d492892310913d68270 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 22 Aug 2026 21:30:40 +0200 Subject: [PATCH] =?UTF-8?q?uix(radio-cards):=2061=20%=20es=20su=20techo=20?= =?UTF-8?q?=E2=80=94=2021=20de=20sus=2023=20knobs=20no=20son=20suyos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Una sola clave nueva (`invalid-border`, 39 → 40) y su veredicto §5. RadioCards es un **skin sobre RadioGroup**: renderiza `` 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 dejaría que un tema rompiera «un radio es un radio» sólo dentro de las tarjetas. Lo acuñado es lo que sí es suyo: el borde de la TARJETA en validación fallida. Los dos `--radius-full` del indicador son identidad (un radio es redondo por definición, misma clase que el `50%` de un círculo) y los cuatro literales son geometría del glifo (el `46%` del punto interior, el `100%` del SVG). LA LECCIÓN DE LA MEDICIÓN: la primera lectura forzada de `invalid-border` dio «sin efecto». No era el token: la tarjeta transiciona `border-color`, y leer justo después de escribir devuelve el valor de PARTIDA — exactamente el motivo por el que el guard congela las transiciones. Con la congelación puesta, `oklch(0.9555 0.0207 13.86)` → `rgb(1,2,3)`. **Una medición forzada a mano tiene que congelar también.** Queda escrito en el veredicto y en el ledger. Las otras cuatro adjudicaciones, con su medición: `horizontal-basis` sólo bajo `[data-orientation=horizontal]` (192px → 1234px), `disabled-opacity` sólo bajo `[data-disabled]` (0.4 → 0.123) y las dos de transición, que el guard congela (0,12s → 11,5s · cubic-bezier → steps(4)). Gates: sonda antes/después **0 diffs en 3.540 valores** (19 nodos × 7 estados) · centinela 35/40 con las cinco adjudicadas · censo --only 61 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · docs:check 0. Co-Authored-By: Claude Opus 5 --- docs/audit/theming/README.md | 8 +- docs/audit/theming/radio-cards.md | 80 ++++++++++++------- docs/process/PLAN-theming.md | 18 +++++ scripts/theming-sentinel-exceptions.ts | 16 ++++ .../components/radio-cards/radio-cards.css | 2 +- src/uix/eidos/generated/base.css | 1 + src/uix/eidos/lib/recipes/base.ts | 3 + 7 files changed, 96 insertions(+), 32 deletions(-) diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index 1315eaeec..cbc80b175 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,8 +10,8 @@ - **Medido**: 2026-08-22 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/` - **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**? -- **Alcance global**: **60%** — 2770 de 4628 knobs pasan por un token público del componente -- **Reparto**: público 2770 · privado 338 · global 1021 · literal 499 · sistema transversal 509 · excepción firmada 54 _(los dos últimos, fuera del ratio)_ +- **Alcance global**: **60%** — 2771 de 4628 knobs pasan por un token público del componente +- **Reparto**: público 2771 · privado 338 · global 1020 · literal 499 · sistema transversal 509 · excepción firmada 54 _(los dos últimos, fuera del ratio)_ - **Sin token público propio**: 24 · **alcance < 20 %**: 16 · **alcance 100 %**: 17 · **con eje `size`**: 58 ## Cómo se lee @@ -81,7 +81,7 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma | 6 | [color-picker](./color-picker.md) | 39 | 51% | 82 | 54 | | 7 | [field-langs](./field-langs.md) | 37 | 0% | 39 | 0 | | 8 | [date-range-picker](./date-range-picker.md) | 30 | 40% | 53 | 22 | -| 9 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 | +| 9 | [radio-cards](./radio-cards.md) | 27 | 61% | 72 | 40 | | 10 | [drawer](./drawer.md) | 26 | 57% | 62 | 44 | | 11 | [popover](./popover.md) | 26 | 46% | 51 | 36 | | 12 | [natural-time-picker](./natural-time-picker.md) | 25 | 67% | 77 | 62 | @@ -164,11 +164,11 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [badge](./badge.md) | 59% | 22 | 13 | 6 | 1 | 2 | 0 | 65 | y | | [float-panel](./float-panel.md) | 59% | 53 | 29 | 3 | 12 | 5 | 4 | 28 | – | | [checkbox](./checkbox.md) | 59% | 33 | 19 | 8 | 2 | 3 | 1 | 44 | y | -| [radio-cards](./radio-cards.md) | 60% | 72 | 42 | 0 | 24 | 4 | 2 | 39 | y | | [image](./image.md) | 60% | 35 | 21 | 1 | 1 | 12 | 0 | 22 | y | | [cropper](./cropper.md) | 60% | 27 | 15 | 0 | 3 | 7 | 2 | 15 | – | | [mark](./mark.md) | 60% | 5 | 3 | 2 | 0 | 0 | 0 | 3 | – | | [emoji-picker](./emoji-picker.md) | 61% | 32 | 17 | 0 | 8 | 3 | 4 | 17 | – | +| [radio-cards](./radio-cards.md) | 61% | 72 | 43 | 0 | 23 | 4 | 2 | 40 | y | | [clipboard](./clipboard.md) | 63% | 8 | 5 | 2 | 1 | 0 | 0 | 6 | – | | [spin-field](./spin-field.md) | 64% | 27 | 16 | 0 | 8 | 1 | 2 | 20 | – | | [meter](./meter.md) | 64% | 14 | 9 | 1 | 2 | 2 | 0 | 28 | y | diff --git a/docs/audit/theming/radio-cards.md b/docs/audit/theming/radio-cards.md index fbefdff1e..938f6d57d 100644 --- a/docs/audit/theming/radio-cards.md +++ b/docs/audit/theming/radio-cards.md @@ -5,14 +5,14 @@ > Vista de conjunto: [README](./README.md) · método y protocolo: > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. -- **Medido**: 2026-08-22 · **Alcance**: **60%** — 42 de 70 knobs por token público -- **Knobs de apariencia**: 72 — público 42 · privado 0 · global 24 · literal 4 · sistema 2 · excepción 0 _(los dos últimos, fuera del ratio)_ -- **Contrato hoy** (`lib/recipes/base.ts`): 39 pública(s) — `gap`, `horizontal-basis`, `padding-xs`, `padding-sm`, `padding-md`, `padding-lg`, `padding-xl`, `radius`, `border-width`, `border`, `hover-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` +- **Medido**: 2026-08-22 · **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 (24) +### 1.1 Directo a primitivo global (23) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | @@ -20,26 +20,25 @@ | 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:131` | `[data-radio-cards][data-invalid] [data-radio-cards-item]` | `border-color` | `var(--color-threat-element)` | -| 6 | `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` | -| 7 | `radio-cards.css:234` | `[data-radio-cards-indicator]` | `inline-size` | `var(--radio-group-item-size-md)` ⤴ prestado de `radio-group` | -| 8 | `radio-cards.css:235` | `[data-radio-cards-indicator]` | `block-size` | `var(--radio-group-item-size-md)` ⤴ prestado de `radio-group` | -| 9 | `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` | -| 10 | `radio-cards.css:237` | `[data-radio-cards-indicator]` | `border-radius` | `var(--radius-full)` | -| 11 | `radio-cards.css:238` | `[data-radio-cards-indicator]` | `background` | `var(--radio-group-indicator-bg)` ⤴ prestado de `radio-group` | -| 12 | `radio-cards.css:249` | `[data-radio-cards-indicator]::after` | `border-radius` | `var(--radius-full)` | -| 13 | `radio-cards.css:250` | `[data-radio-cards-indicator]::after` | `background` | `var(--_radio-group-palette-solid)` ⤴ prestado de `radio-group` | -| 14 | `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` | -| 15 | `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` | -| 16 | `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` | -| 17 | `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` | -| 18 | `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` | -| 19 | `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` | -| 20 | `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` | -| 21 | `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` | -| 22 | `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` | -| 23 | `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` | -| 24 | `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` | +| 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) @@ -82,7 +81,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl - **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` (20) +### 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 @@ -100,7 +99,6 @@ no distingue lo que debería — se marca `⚠`. | `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 | -| `invalid-item-border` | `root` | `var(--color-threat-element)` | 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 | @@ -130,6 +128,34 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**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 `` +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). diff --git a/docs/process/PLAN-theming.md b/docs/process/PLAN-theming.md index f045a5e6e..4a44803cb 100644 --- a/docs/process/PLAN-theming.md +++ b/docs/process/PLAN-theming.md @@ -455,6 +455,24 @@ de commit. Sin artefacto, el paso no se ha hecho. ## 8. Registro +- 2026-08-22 — **`radio-cards` 60 % → 61 %: una sola clave, y ése es su techo.** + 21 de sus 23 knobs fuera de alcance **no son suyos**: es un SKIN sobre + RadioGroup —renderiza `` y su indicador ES un radio—, así + que lee `--radio-group-*` y el puente de paleta del anfitrión. Mandato de + composición (`theming-audit` §5.3-3): se teman en la ficha del dueño. Lo suyo + es la TARJETA: `invalid-border`. Los dos `--radius-full` del indicador son + identidad (un radio es redondo por definición). + + **La lección de la medición**: la primera lectura de `invalid-border` dio «sin + efecto» porque la tarjeta transiciona `border-color` y leer justo después de + escribir devuelve el valor de PARTIDA. Con las transiciones congeladas —lo que + el guard hace por defecto y mi sonda a mano no— alcanza. **Una medición + forzada tiene que congelar también.** + + Gates: sonda 0 diffs en 3.540 valores · centinela 35/40 con las cinco + adjudicadas y medidas · censo 61 % · `--names` sin desviadas · audit PASS · + lint 0. + - 2026-08-22 — **`prose` 1 % → 83 %** (35 claves de costura, 2 → 37). Global **59 % → 60 %**; `global` del catálogo 1.064 → 1.021. diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index bd136174c..c1041666e 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -314,6 +314,22 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'disabled-row-fg': 'no disabled row in the demo (0 mounted, counted); forced data-disabled → reaches (oklch(0.61 0 0) → rgb(1,2,3))' }, + 'radio-cards': { + // All five forced by hand 2026-08-22, WITH transitions frozen: the card + // transitions `border-color`, and reading right after the write returns the + // starting value (the same trap the guard's own freeze exists for — the + // first measurement of invalid-border read "no effect" for that reason). + 'invalid-border': + 'only paints under [data-invalid] and the demo boots valid; forced -> reaches (oklch(0.9555 0.0207 13.86) -> rgb(1,2,3))', + 'horizontal-basis': + 'only under [data-orientation=horizontal]; forced -> reaches (192px -> 1234px of flex-basis)', + 'disabled-opacity': + 'only under [data-disabled] on the item; forced -> reaches (0.4 -> 0.123)', + 'transition-duration': + 'the guard freezes transitions on purpose; measured without the freeze -> reaches (0.12s -> 11.5s)', + 'transition-ease': + 'same freeze; measured without it -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(4))' + }, prose: { // Prose styles bare HTML at ZERO specificity on purpose (`:where([data-prose] el)`, // so an embedded component keeps its own look). That is exactly what the docs diff --git a/src/uix/eidos/components/radio-cards/radio-cards.css b/src/uix/eidos/components/radio-cards/radio-cards.css index 38579ee38..5b8479d81 100644 --- a/src/uix/eidos/components/radio-cards/radio-cards.css +++ b/src/uix/eidos/components/radio-cards/radio-cards.css @@ -128,7 +128,7 @@ /* ── Invalid (form validation) — threat border across the card ─────────── */ [data-radio-cards][data-invalid] [data-radio-cards-item] { - border-color: var(--color-threat-element); + border-color: var(--radio-cards-invalid-border); } /* ── Leading icon / media ──────────────────────────────────────────────── */ diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index aa7492304..13be938b6 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -2535,6 +2535,7 @@ --radio-cards-border-width: var(--border-width); --radio-cards-border: var(--color-border-default); --radio-cards-hover-border: var(--color-border-strong); + --radio-cards-invalid-border: var(--color-threat-element); --radio-cards-bg: var(--color-surface-default); --radio-cards-content-gap: var(--space-3); --radio-cards-text-gap: var(--space-0-5); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index e58749c53..314a0a247 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -3842,6 +3842,9 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ 'border-width': 'var(--border-width)', border: 'var(--color-border-default)', 'hover-border': 'var(--color-border-strong)', + // El borde de la tarjeta en validación fallida: propio de este skin (la + // tarjeta ES suya), a diferencia del indicador, que es un radio. + 'invalid-border': 'var(--color-threat-element)', bg: 'var(--color-surface-default)', 'content-gap': 'var(--space-3)', 'text-gap': 'var(--space-0-5)',