uix(skeleton): temable — 0 % → 45 %, y dos privados que son conmutadores

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 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent e3f39ce282
commit a6d4d4f5a3

@ -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 |

@ -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 `⚠`.
<!-- veredicto:start -->
_(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).
<!-- veredicto:end -->

@ -202,6 +202,10 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'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)",

@ -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 <url>`): 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 |

@ -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 */

@ -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));
}

@ -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

@ -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 @@
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>9</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -524,6 +528,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="skeleton" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.