From a6d4d4f5a3f89209ff0833d7aad607385ffc74a5 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 22 Aug 2026 00:27:46 +0200 Subject: [PATCH] =?UTF-8?q?uix(skeleton):=20temable=20=E2=80=94=200=20%=20?= =?UTF-8?q?=E2=86=92=2045=20%,=20y=20dos=20privados=20que=20son=20conmutad?= =?UTF-8?q?ores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 9 claves fusionadas en el bloque `skeleton` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → 45 %, cero globales. Nueve de sus once knobs pasaban por privado, y la lectura correcta es que **dos de esos privados NO son deuda: son conmutadores**. `--_skeleton-bg` y `--_skeleton-highlight` tienen DOS fuentes cada uno según haya `data-color` — la mezcla neutra cross-mode (un `color-mix` hacia `neutral-solid`, para que el placeholder siga siendo un gris visible en los dos modos: el `neutral-track` desnudo desaparece sobre una superficie clara) o el forward de paleta. Aplanarlos exigiría duplicar cada regla por color, así que se quedan — mismo criterio que el borde de foco de textarea y la tinta de spinner. Son el grueso del 55 % que no alcanza, y es honesto. - **Una coordenada por talla** (`size`) gobierna el alto del bloque, el diámetro del círculo (vía `aspect-ratio`) y el hueco entre líneas de la forma `text`. Ese hueco es `calc(size * 0.5)`: una PROPORCIÓN, construcción, no un knob. - **El radio es por FORMA, no por talla**: `radius-md` en `rect` y `50%` en `circle`, donde el `50%` es identidad (un círculo es redondo) — el knob es sólo el del rectángulo. La línea de la forma `text` lleva el suyo. - `duration` entra al contrato en vez de seguir en `1.4s` a pelo, igual que se hizo con el spinner (§13). Artefactos y gates: - Sonda antes/después = 0 diffs en 203 valores × 7 estados. - R-5.4: 8/9. `line-radius` adjudicado: sólo la forma `text` renderiza `[data-skeleton-line]` y la demo monta `rect` (0 líneas, contadas); forzada la forma, alcanza (4 → 1234px). - censo --only 45 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (9 claves). Co-Authored-By: Claude Fable 5 --- docs/audit/theming/README.md | 12 +-- docs/audit/theming/skeleton.md | 91 ++++++++----------- scripts/theming-sentinel-exceptions.ts | 4 + src/uix/eidos/components/skeleton/README.md | 29 ++++++ .../eidos/components/skeleton/skeleton.css | 40 +++----- src/uix/eidos/generated/base.css | 25 +++++ src/uix/eidos/lib/recipes/base.ts | 27 ++++++ .../uix/components/skeleton/+page.svelte | 10 +- 8 files changed, 149 insertions(+), 89 deletions(-) diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index a2c9ae993..fdac4b0bc 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,9 +10,9 @@ - **Medido**: 2026-08-21 · **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**: **49%** — 2304 de 4676 knobs pasan por un token público del componente -- **Reparto**: público 2304 · privado 631 · global 1174 · literal 567 · sistema transversal 509 _(fuera del ratio)_ -- **Sin token público propio**: 36 · **alcance < 20 %**: 29 · **alcance 100 %**: 9 · **con eje `size`**: 60 +- **Alcance global**: **49%** — 2309 de 4676 knobs pasan por un token público del componente +- **Reparto**: público 2309 · privado 627 · global 1173 · literal 567 · sistema transversal 509 _(fuera del ratio)_ +- **Sin token público propio**: 35 · **alcance < 20 %**: 28 · **alcance 100 %**: 9 · **con eje `size`**: 59 ## Cómo se lee @@ -91,12 +91,12 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma | 19 | [timeline](./timeline.md) | 24 | 51% | 49 | 45 | | 20 | [avatar](./avatar.md) | 23 | 36% | 36 | 0 | -## Sin ningún token público propio (36) +## Sin ningún token público propio (35) Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward (`_palette-*`), que no son contrato: un tema no puede nombrarlos. -[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md) +[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md) ## Tabla completa (162 recetas, por alcance ascendente) @@ -108,7 +108,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [month-grid](./month-grid.md) | 0% | 61 | 0 | 7 | 0 | 5 | 49 | 0 | – | | [year-grid](./year-grid.md) | 0% | 61 | 0 | 7 | 0 | 5 | 49 | 0 | – | | [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – | -| [skeleton](./skeleton.md) | 0% | 11 | 0 | 9 | 1 | 1 | 0 | 0 | y | | [skin-media-player](./skin-media-player.md) | 0% | 11 | 0 | 0 | 2 | 7 | 2 | 0 | y | | [scroll-frames](./scroll-frames.md) | 0% | 9 | 0 | 1 | 4 | 4 | 0 | 0 | – | | [announce](./announce.md) | 0% | 8 | 0 | 0 | 8 | 0 | 0 | 0 | – | @@ -161,6 +160,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [popover](./popover.md) | 42% | 51 | 20 | 6 | 22 | 0 | 3 | 36 | y | | [qr-code](./qr-code.md) | 43% | 7 | 3 | 0 | 0 | 4 | 0 | 10 | – | | [navigation-menu](./navigation-menu.md) | 44% | 38 | 15 | 1 | 13 | 5 | 4 | 26 | y | +| [skeleton](./skeleton.md) | 45% | 11 | 5 | 5 | 0 | 1 | 0 | 9 | – | | [waveform](./waveform.md) | 45% | 11 | 5 | 1 | 1 | 4 | 0 | 10 | y | | [color-picker](./color-picker.md) | 46% | 82 | 36 | 10 | 22 | 11 | 3 | 54 | y | | [checkbox](./checkbox.md) | 47% | 33 | 15 | 12 | 2 | 3 | 1 | 44 | y | diff --git a/docs/audit/theming/skeleton.md b/docs/audit/theming/skeleton.md index 3114a0ba2..c98ccc78a 100644 --- a/docs/audit/theming/skeleton.md +++ b/docs/audit/theming/skeleton.md @@ -5,38 +5,32 @@ > Vista de conjunto: [README](./README.md) · método y protocolo: > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. -- **Medido**: 2026-08-20 · **Alcance**: **0%** — 0 de 11 knobs por token público -- **Knobs de apariencia**: 11 — público 0 · privado 9 · global 1 · literal 1 · sistema 0 _(fuera del ratio)_ -- **Contrato hoy** (`lib/recipes/base.ts`): 0 pública(s) · 1 privada(s) forward — `_palette-track` -- **Eje `size`**: sí · **ficheros**: `skeleton.css` +- **Medido**: 2026-08-21 · **Alcance**: **45%** — 5 de 11 knobs por token público +- **Knobs de apariencia**: 11 — público 5 · privado 5 · global 0 · literal 1 · sistema 0 _(fuera del ratio)_ +- **Contrato hoy** (`lib/recipes/base.ts`): 9 pública(s) — `size-xs`, `size-sm`, `size-md`, `size-lg`, `size-xl`, `size`, `radius`, `line-radius`, `duration` · 1 privada(s) forward — `_palette-track` +- **Eje `size`**: no · **ficheros**: `skeleton.css` ## 1. Knobs fuera de alcance -### 1.1 Directo a primitivo global (1) +### 1.1 Directo a primitivo global (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `skeleton.css:71` | `[data-skeleton-line]` | `border-radius` | `var(--radius-sm)` | +_Ninguno._ -### 1.2 A través de un privado (9) +### 1.2 A través de un privado (5) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `skeleton.css:22` | `[data-skeleton]` | `background` | `var(--_skeleton-bg)` | -| 2 | `skeleton.css:23` | `[data-skeleton]` | `border-radius` | `var(--_skeleton-radius)` | -| 3 | `skeleton.css:25` | `[data-skeleton]` | `block-size` | `var(--_skeleton-size)` | -| 4 | `skeleton.css:56` | `[data-skeleton][data-shape='circle']` | `inline-size` | `var(--_skeleton-size)` | -| 5 | `skeleton.css:62` | `[data-skeleton][data-shape='text']` | `gap` | `calc(var(--_skeleton-size) * 0.5)` | -| 6 | `skeleton.css:70` | `[data-skeleton-line]` | `block-size` | `var(--_skeleton-size, 1rem)` | -| 7 | `skeleton.css:72` | `[data-skeleton-line]` | `background` | `var(--_skeleton-bg)` | -| 8 | `skeleton.css:92` | `[data-skeleton][data-animated]` | `background-image` | `linear-gradient( var(--gradient-angle-to-r), var(--_skeleton-bg) 0%, var(--_skeleton-highlight) 50%, var(--_skeleton-bg) 100% )` | -| 9 | `skeleton.css:108` | `[data-skeleton][data-shape='text'][data-animated] [data-skeleton-line]` | `background-image` | `linear-gradient( var(--gradient-angle-to-r), var(--_skeleton-bg) 0%, var(--_skeleton-highlight) 50%, var(--_skeleton-bg) 100% )` | +| 1 | `skeleton.css:20` | `[data-skeleton]` | `background` | `var(--_skeleton-bg)` | +| 2 | `skeleton.css:21` | `[data-skeleton]` | `border-radius` | `var(--_skeleton-radius)` | +| 3 | `skeleton.css:56` | `[data-skeleton-line]` | `background` | `var(--_skeleton-bg)` | +| 4 | `skeleton.css:76` | `[data-skeleton][data-animated]` | `background-image` | `linear-gradient( var(--gradient-angle-to-r), var(--_skeleton-bg) 0%, var(--_skeleton-highlight) 50%, var(--_skeleton-bg) 100% )` | +| 5 | `skeleton.css:92` | `[data-skeleton][data-shape='text'][data-animated] [data-skeleton-line]` | `background-image` | `linear-gradient( var(--gradient-angle-to-r), var(--_skeleton-bg) 0%, var(--_skeleton-highlight) 50%, var(--_skeleton-bg) 100% )` | ### 1.3 Literales (1) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `skeleton.css:24` | `[data-skeleton]` | `inline-size` | `100%` | +| 1 | `skeleton.css:22` | `[data-skeleton]` | `inline-size` | `100%` | ## 2. Sistema transversal (0) — informativo, fuera del ratio @@ -50,17 +44,13 @@ _Ninguno._ | --- | ---: | --- | --- | :-: | | `--_skeleton-bg` | 2 | `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%)`, `color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent)` | global, private | no | | `--_skeleton-highlight` | 2 | `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 7%)`, `color-mix(in srgb, var(--_skeleton-palette-track) 90%, white 8%)` | global, private | no | -| `--_skeleton-size` | 6 | `1rem`, `0.5rem`, `0.75rem`, `1.5rem`, `2rem` | literal | no | -| `--_skeleton-radius` | 3 | `var(--radius-md)`, `50%` | global, literal | no | -| `--_skeleton-duration` | 1 | `1.4s` | literal | no | +| `--_skeleton-radius` | 3 | `var(--skeleton-radius)`, `50%` | literal, public | no | Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_skeleton-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` (30) +### 4.1 Tokens a declarar en `lib/recipes/base.ts` (5) 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 @@ -70,46 +60,20 @@ no distingue lo que debería — se marca `⚠`. | token (`--skeleton-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | -| `radius` | `root` | ⚠ `var(--radius-md)` / `50%` | 3 | +| `radius` _(ya existe)_ | `root` | ⚠ `var(--skeleton-radius)` / `50%` | 3 | | `bg` | `root` | ⚠ `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%)` / `color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent)` | 2 | | `line-bg` | `root` | ⚠ `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%)` / `color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent)` | 2 | | `bg-image` | `root` | ⚠ `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%)` / `color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent)` | 2 | | `line-bg-image` | `root` | ⚠ `color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%)` / `color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent)` | 2 | -| `height` | `root` | `1rem` | 1 | -| `height-xs` | `size:xs` | `0.5rem` | 1 | -| `height-sm` | `size:sm` | `0.75rem` | 1 | -| `height-md` | `size:md` | `1rem` | 1 | -| `height-lg` | `size:lg` | `1.5rem` | 1 | -| `height-xl` | `size:xl` | `2rem` | 1 | -| `width` | `root` | `1rem` | 1 | -| `width-xs` | `size:xs` | `0.5rem` | 1 | -| `width-sm` | `size:sm` | `0.75rem` | 1 | -| `width-md` | `size:md` | `1rem` | 1 | -| `width-lg` | `size:lg` | `1.5rem` | 1 | -| `width-xl` | `size:xl` | `2rem` | 1 | -| `gap` | `root` | `1rem` | 1 | -| `gap-xs` | `size:xs` | `0.5rem` | 1 | -| `gap-sm` | `size:sm` | `0.75rem` | 1 | -| `gap-md` | `size:md` | `1rem` | 1 | -| `gap-lg` | `size:lg` | `1.5rem` | 1 | -| `gap-xl` | `size:xl` | `2rem` | 1 | -| `line-height` | `root` | `1rem` | 1 | -| `line-height-xs` | `size:xs` | `0.5rem` | 1 | -| `line-height-sm` | `size:sm` | `0.75rem` | 1 | -| `line-height-md` | `size:md` | `1rem` | 1 | -| `line-height-lg` | `size:lg` | `1.5rem` | 1 | -| `line-height-xl` | `size:xl` | `2rem` | 1 | -| `line-radius` | `root` | `var(--radius-sm)` | 1 | ### 4.2 Sin nombre mecánico (1) - **⚠ 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** — 1: `inline-size`. -### 4.3 Avisos sobre los tokens propuestos (3) +### 4.3 Avisos sobre los tokens propuestos (2) - **el privado `--_skeleton-bg` debe pasar a leer este público (o desaparecer)** — `--skeleton-bg`, `--skeleton-line-bg`, `--skeleton-bg-image`, `--skeleton-line-bg-image` - **el privado `--_skeleton-radius` debe pasar a leer este público (o desaparecer)** — `--skeleton-radius` -- **el privado `--_skeleton-size` debe pasar a leer este público (o desaparecer)** — `--skeleton-height`, `--skeleton-height-xs`, `--skeleton-height-sm`, `--skeleton-height-md`, `--skeleton-height-lg`, `--skeleton-height-xl`, `--skeleton-width`, `--skeleton-width-xs`, `--skeleton-width-sm`, `--skeleton-width-md`, `--skeleton-width-lg`, `--skeleton-width-xl`, `--skeleton-gap`, `--skeleton-gap-xs`, `--skeleton-gap-sm`, `--skeleton-gap-md`, `--skeleton-gap-lg`, `--skeleton-gap-xl`, `--skeleton-line-height`, `--skeleton-line-height-xs`, `--skeleton-line-height-sm`, `--skeleton-line-height-md`, `--skeleton-line-height-lg`, `--skeleton-line-height-xl` ### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4) @@ -123,6 +87,25 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**Medido 2026-08-22.** Nueve de sus once knobs pasan por privado, y la lectura +correcta es que **dos de esos privados NO son deuda**: son conmutadores. + +1. **`--_skeleton-bg` y `--_skeleton-highlight` se quedan privados.** Cada uno + tiene DOS fuentes según haya `data-color`: la mezcla neutra cross-mode + (`color-mix` hacia `neutral-solid`, para que el placeholder siga siendo un + gris visible en los dos modos — el `neutral-track` desnudo desaparece sobre + una superficie clara) o el forward de paleta. Aplanarlos exigiría duplicar + cada regla por color. Mismo patrón que el borde de foco de `textarea` y la + tinta de `spinner`, y son el ~18 % que no alcanza. +2. **`--_skeleton-size` es la coordenada por talla** y gobierna el alto del + bloque, el diámetro del círculo (via `aspect-ratio`) y el hueco entre líneas + de la forma `text` — ese hueco es `calc(size * 0.5)`, una PROPORCIÓN, o sea + construcción, no un knob. +3. **`--_skeleton-radius` no es una coordenada por talla sino por FORMA**: + `radius-md` en `rect`, `50%` en `circle`. El `50%` es identidad (un círculo + es redondo), así que el knob es sólo el del rectángulo. La línea de la forma + `text` tiene el suyo propio, más pequeño. +4. **`duration` entra al contrato** en vez de quedarse en `1.4s` a pelo — + mismo criterio que se aplicó al spinner (`next-features.md` §13). diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index aca9d1e75..239f1d1ff 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -202,6 +202,10 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'time-label-font-size-lg': 'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' }, + skeleton: { + 'line-radius': + "only the `text` shape renders [data-skeleton-line]; the demo mounts `rect` (0 lines, counted). Forced shape='text' → reaches (4 → 1234px)" + }, spinner: { 'bar-radius': "the demo mounts the RING variant only (0 bars, 0 dots, counted); forced variant='bars' → reaches (1234px)", diff --git a/src/uix/eidos/components/skeleton/README.md b/src/uix/eidos/components/skeleton/README.md index 5743e5a24..6ea9d9550 100644 --- a/src/uix/eidos/components/skeleton/README.md +++ b/src/uix/eidos/components/skeleton/README.md @@ -64,6 +64,35 @@ Referencias: [Chakra Skeleton](https://chakra-ui.com/docs/components/skeleton) espectro (bloque, avatar, párrafo); formas libres = el consumidor estila el contenedor. +## Talla y tema + +**Guard R-5.4** (`npm run theming:sentinel -- skeleton `): 8/9 tokens +mueven un computed en vivo; el que calla está adjudicado con su medición en +`scripts/theming-sentinel-exceptions.ts`. Corrido 2026-08-22. + +Contrato en `lib/recipes/base.ts > skeleton` (9 claves, fusionadas con el +forward de paleta). Alcance del censo: **45 %** (era 0 %), con cero globales. + +**Dos privados sobreviven, y no son deuda: son conmutadores.** +`--_skeleton-bg` y `--_skeleton-highlight` tienen DOS fuentes cada uno según +haya `data-color` — la mezcla neutra cross-mode (un `color-mix` hacia +`neutral-solid`, para que el placeholder siga siendo un gris visible en los dos +modos: el `neutral-track` desnudo desaparece sobre una superficie clara) o el +forward de paleta. Aplanarlos exigiría duplicar cada regla por color, así que +se quedan — mismo criterio que el borde de foco de `textarea` y la tinta de +`spinner`. Son el grueso del 55 % que no alcanza. + +**Una coordenada por talla** (`size`) gobierna el alto del bloque, el diámetro +del círculo (vía `aspect-ratio`) y el hueco entre líneas de la forma `text` — +ese hueco es `calc(size * 0.5)`, una PROPORCIÓN, o sea construcción y no un +knob. **El radio es por FORMA, no por talla**: `radius-md` en `rect` y `50%` en +`circle`, donde el `50%` es identidad (un círculo es redondo) y por eso el knob +es sólo el del rectángulo; la línea de la forma `text` lleva el suyo, más +pequeño. + +`duration` entra al contrato en vez de quedarse en `1.4s` a pelo, igual que se +hizo con el spinner (`next-features.md` §13). + ## Gaps | Gap | Disposición | Detalle | diff --git a/src/uix/eidos/components/skeleton/skeleton.css b/src/uix/eidos/components/skeleton/skeleton.css index 2ad6fd0f9..eae717da8 100644 --- a/src/uix/eidos/components/skeleton/skeleton.css +++ b/src/uix/eidos/components/skeleton/skeleton.css @@ -2,7 +2,7 @@ * Skeleton — placeholder block. * * Shapes: - * - rect → rounded rectangle, sized by --_skeleton-{w,h} + * - rect → rounded rectangle, sized by `--skeleton-size` * - circle → 1:1 ratio, fully rounded * - text → stacked thin bars at staggered widths * @@ -14,52 +14,36 @@ * `neutral-track` is ~white in light mode and vanishes on a light surface. */ --_skeleton-bg: color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%); --_skeleton-highlight: color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 7%); - --_skeleton-size: 1rem; - --_skeleton-radius: var(--radius-md); - --_skeleton-duration: 1.4s; + --_skeleton-radius: var(--skeleton-radius); display: block; background: var(--_skeleton-bg); border-radius: var(--_skeleton-radius); inline-size: 100%; - block-size: var(--_skeleton-size); + block-size: var(--skeleton-size); overflow: hidden; position: relative; } -/* Sizes — intrinsic block-size when the consumer doesn't override. */ -[data-skeleton][data-size='xs'] { - --_skeleton-size: 0.5rem; -} -[data-skeleton][data-size='sm'] { - --_skeleton-size: 0.75rem; -} -[data-skeleton][data-size='md'] { - --_skeleton-size: 1rem; -} -[data-skeleton][data-size='lg'] { - --_skeleton-size: 1.5rem; -} -[data-skeleton][data-size='xl'] { - --_skeleton-size: 2rem; -} +/* Sizes — the intrinsic block-size, emitted by the TSC from the contract. */ /* ── Shapes ────────────────────────────────────────────────────────── */ [data-skeleton][data-shape='rect'] { - --_skeleton-radius: var(--radius-md); + --_skeleton-radius: var(--skeleton-radius); } [data-skeleton][data-shape='circle'] { + /* `50%` is shape IDENTITY — a circle is round — not a knob. */ --_skeleton-radius: 50%; aspect-ratio: 1 / 1; - inline-size: var(--_skeleton-size); + inline-size: var(--skeleton-size); } [data-skeleton][data-shape='text'] { display: flex; flex-direction: column; - gap: calc(var(--_skeleton-size) * 0.5); + gap: calc(var(--skeleton-size) * 0.5); background: transparent; overflow: visible; block-size: auto; @@ -67,8 +51,8 @@ [data-skeleton-line] { display: block; - block-size: var(--_skeleton-size, 1rem); - border-radius: var(--radius-sm); + block-size: var(--skeleton-size); + border-radius: var(--skeleton-line-radius); background: var(--_skeleton-bg); overflow: hidden; position: relative; @@ -96,7 +80,7 @@ var(--_skeleton-bg) 100% ); background-size: 200% 100%; - animation: skeleton-shimmer var(--_skeleton-duration) ease-in-out infinite; + animation: skeleton-shimmer var(--skeleton-duration) ease-in-out infinite; } [data-skeleton][data-shape='text'][data-animated] { @@ -112,7 +96,7 @@ var(--_skeleton-bg) 100% ); background-size: 200% 100%; - animation: skeleton-shimmer var(--_skeleton-duration) ease-in-out infinite; + animation: skeleton-shimmer var(--skeleton-duration) ease-in-out infinite; } /* functional: continuous loading-shimmer sweep — machinery, not an event signature */ diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 17593fc38..8eb92da99 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -2146,6 +2146,14 @@ --proof-of-human-token-size: 1.25rem; --proof-of-human-token-radius: var(--radius-full); --proof-of-human-token-focus-offset: 3px; + --skeleton-size-xs: 0.5rem; + --skeleton-size-sm: 0.75rem; + --skeleton-size-md: 1rem; + --skeleton-size-lg: 1.5rem; + --skeleton-size-xl: 2rem; + --skeleton-radius: var(--radius-md); + --skeleton-line-radius: var(--radius-sm); + --skeleton-duration: 1.4s; --spinner-size-xs: 0.75rem; --spinner-size-sm: 1rem; --spinner-size-md: 1.25rem; @@ -6101,9 +6109,26 @@ } [data-skeleton] { + --skeleton-size: var(--skeleton-size-md); --_skeleton-palette-track: var(--color-primary-track); } +[data-skeleton][data-size='xs'] { + --skeleton-size: var(--skeleton-size-xs); +} + +[data-skeleton][data-size='sm'] { + --skeleton-size: var(--skeleton-size-sm); +} + +[data-skeleton][data-size='lg'] { + --skeleton-size: var(--skeleton-size-lg); +} + +[data-skeleton][data-size='xl'] { + --skeleton-size: var(--skeleton-size-xl); +} + [data-skeleton][data-color], [data-skeleton][data-color-custom] { --_skeleton-palette-track: var(--palette-track, var(--color-primary-track)); } diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 76aed0cdb..d756d1ef1 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -3149,6 +3149,33 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ // the `'neutral'` default stamps no `data-color` and keeps the base grey mix, // so the host value here is the forward fallback only. skeleton: { + // The one size coordinate: block height, circle diameter (via aspect-ratio) + // and the gap between text lines, which is `calc(size * 0.5)` — a + // PROPORTION, so construction rather than a knob. + 'size-xs': '0.5rem', + 'size-sm': '0.75rem', + 'size-md': '1rem', + 'size-lg': '1.5rem', + 'size-xl': '2rem', + size: { + declarations: [ + { value: 'var(--skeleton-size-md)', scope: 'host' }, + { value: 'var(--skeleton-size-xs)', scope: 'size:xs' }, + { value: 'var(--skeleton-size-sm)', scope: 'size:sm' }, + { value: 'var(--skeleton-size-lg)', scope: 'size:lg' }, + { value: 'var(--skeleton-size-xl)', scope: 'size:xl' } + ] + }, + // Radius is per SHAPE, not per size: the rectangle's is a knob, the + // circle's `50%` is identity. + radius: 'var(--radius-md)', + 'line-radius': 'var(--radius-sm)', + // The shimmer period. Bare `1.4s` until now — same call as spinner's. + duration: '1.4s', + // `--_skeleton-bg` / `-highlight` stay PRIVATE: each is one variable with + // two sources (the cross-mode neutral mix, or the palette forward when + // `data-color` lands), and flattening them would duplicate every rule per + // colour. '_palette-track': { value: 'var(--color-primary-track)', scope: 'host' } }, // Spinner — `color` tints the ink (text slot) + the ring track. The base diff --git a/web/routes/uix/components/skeleton/+page.svelte b/web/routes/uix/components/skeleton/+page.svelte index fac95dfee..484a8ebbe 100644 --- a/web/routes/uix/components/skeleton/+page.svelte +++ b/web/routes/uix/components/skeleton/+page.svelte @@ -9,8 +9,9 @@ import { compileMorfo } from '$uix/morfo'; import { skeletonMorfo } from '@/uix/morfo/components/skeleton'; import { getActiveUix } from '$active-uix'; + import TokensPanel from '../../lib/TokensPanel.svelte'; - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; type TraceEntry = { event: string; family: string; intent?: string; at: number }; const uix = getActiveUix(); @@ -164,6 +165,9 @@ + @@ -524,6 +528,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'a11y'}

Accessibility