uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA

10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.

Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.

Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:

- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
  tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
  escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
  declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
  estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
  vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
  enseñando su vidrio») y el radio del área de pulsación de las teclas.

Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.

EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.

Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.

Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent ab64efee77
commit 5e8b4dba15

@ -10,9 +10,9 @@
- **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**: **56%** — 2628 de 4678 knobs pasan por un token público del componente
- **Reparto**: público 2628 · privado 338 · global 1148 · literal 564 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 28 · **alcance < 20 %**: 20 · **alcance 100 %**: 14 · **con eje `size`**: 58
- **Alcance global**: **56%** — 2630 de 4678 knobs pasan por un token público del componente
- **Reparto**: público 2630 · privado 338 · global 1147 · literal 563 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 27 · **alcance < 20 %**: 19 · **alcance 100 %**: 14 · **con eje `size`**: 58
## Cómo se lee
@ -91,12 +91,12 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 19 | [float-panel](./float-panel.md) | 20 | 59% | 53 | 28 |
| 20 | [field](./field.md) | 19 | 72% | 73 | 60 |
## Sin ningún token público propio (28)
## Sin ningún token público propio (27)
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) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.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) · [`mask-field`](./mask-field.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) · [`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) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.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) · [`mask-field`](./mask-field.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) · [`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 | – |
| [skin-media-player](./skin-media-player.md) | 0% | 11 | 0 | 0 | 2 | 7 | 2 | 0 | y |
| [aspect-ratio](./aspect-ratio.md) | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – |
| [date-picker](./date-picker.md) | 0% | 2 | 0 | 0 | 0 | 2 | 0 | 0 | – |
| [sticky](./sticky.md) | 0% | 2 | 0 | 1 | 0 | 1 | 0 | 1 | – |
@ -127,6 +126,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [anchor-nav](./anchor-nav.md) | 20% | 11 | 2 | 0 | 8 | 0 | 1 | 2 | – |
| [time-range-picker](./time-range-picker.md) | 20% | 88 | 17 | 19 | 33 | 15 | 4 | 22 | y |
| [palabras](./palabras.md) | 22% | 471 | 103 | 1 | 272 | 90 | 5 | 0 | – |
| [skin-media-player](./skin-media-player.md) | 22% | 11 | 2 | 0 | 1 | 6 | 2 | 10 | y |
| [date-range-field](./date-range-field.md) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| [time-range-field](./time-range-field.md) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| [surface](./surface.md) | 25% | 4 | 1 | 3 | 0 | 0 | 0 | 25 | – |

@ -5,35 +5,33 @@
> 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 9 knobs por token público
- **Knobs de apariencia**: 11 — público 0 · privado 0 · global 2 · literal 7 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`**
- **Medido**: 2026-08-22 · **Alcance**: **22%** — 2 de 9 knobs por token público
- **Knobs de apariencia**: 11 — público 2 · privado 0 · global 1 · literal 6 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 10 pública(s) — `base-width`, `record-base-width`, `cassette-base-width`, `deck-base-width`, `scale-sm`, `scale-md`, `scale-lg`, `scale-xl`, `lamp-opacity`, `key-radius`
- **Eje `size`**: sí · **ficheros**: `skin-media-player.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (2)
### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `skin-media-player.css:53` | `[data-skin-media-player]` | `inline-size` | `min(100%, var(--_smp-size))` |
| 2 | `skin-media-player.css:263` | `[data-skin-media-player-key]` | `border-radius` | `var(--radius-sm)` |
### 1.2 A través de un privado (0)
_Ninguno._
### 1.3 Literales (7)
### 1.3 Literales (6)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `skin-media-player.css:86` | `[data-skin-media-player] > svg` | `inline-size` | `100%` |
| 2 | `skin-media-player.css:87` | `[data-skin-media-player] > svg` | `block-size` | `100%` |
| 3 | `skin-media-player.css:123` | `[data-skin-media-player-cassette]` | `filter` | `drop-shadow(0 20px 34px rgba(58, 46, 36, 0.45))` |
| 4 | `skin-media-player.css:195` | `.smp-lamp` | `opacity` | `0.18` |
| 5 | `skin-media-player.css:199` | `[data-media-player]:not([data-paused]) .smp-lamp` | `opacity` | `1` |
| 6 | `skin-media-player.css:202` | `[data-skin-media-player-deck][data-spinning] .smp-lamp` | `opacity` | `1` |
| 7 | `skin-media-player.css:306` | `[data-skin-media-player-fader] [data-slider]` | `inline-size` | `100%` |
| 4 | `skin-media-player.css:199` | `[data-media-player]:not([data-paused]) .smp-lamp` | `opacity` | `1` |
| 5 | `skin-media-player.css:202` | `[data-skin-media-player-deck][data-spinning] .smp-lamp` | `opacity` | `1` |
| 6 | `skin-media-player.css:306` | `[data-skin-media-player-fader] [data-slider]` | `inline-size` | `100%` |
## 2. Sistema transversal (2) — informativo, fuera del ratio
@ -52,7 +50,7 @@ _La receta no declara privados propios en su CSS._
- **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)
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (2)
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
@ -64,8 +62,6 @@ no distingue lo que debería — se marca `⚠`.
| --- | --- | --- | ---: |
| `width` | `root` | `min(100%, var(--_smp-size))` | 1 |
| `cassette-filter` | `root` | `drop-shadow(0 20px 34px rgba(58, 46, 36, 0.45))` | 1 |
| `opacity` | `root` | `0.18` | 1 |
| `key-radius` | `root` | `var(--radius-sm)` | 1 |
### 4.2 Sin nombre mecánico (5)
@ -88,6 +84,33 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido 2026-08-22.** Su cabecera fija la doctrina antes de que la mire nadie:
**componente de TONO FIJO**. Los cuatro objetos son cosas físicas —caoba,
aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje
dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se
vuelve azul porque el tema lo haga». La pintura vive en el SVG, bajo la
excepción que `recipe-css-contract.test.ts` ya reconoce para
`natural-time-picker`, `color-picker` y `proof-of-human`. Eso acota lo que
puede entrar al contrato antes de nombrar nada.
1. **Lo tokenizable es el TAMAÑO**, y hoy vive en privados: `--_smp-base`
(480px, el ancho `md` que suministra `controls.ts` por dispositivo) y
`--_smp-scale`, que sigue el eje `size` del reproductor (0.7 / 1 / 1.35 /
1.7). El comentario ya los llama «design values: retune here, in one place»
— el contrato es ese sitio. El eje lo estampa el PLAYER en su propia raíz y
el skin lo lee de ahí; no hay prop ni vocabulario propio, y los selectores
son `[data-media-player][data-size='k'] [data-skin-media-player]`.
2. **`aspect-ratio: 1000 / 880` es identidad de la depicción**: la proporción
del objeto dibujado. Cambiarla no retoca un tema, deforma el aparato.
3. **La opacidad de la lámpara apagada (0.18) sí es un knob** — su comentario
dice por qué existe: «una bombilla apagada sigue enseñando su vidrio». Los
dos `1` de encendida son identidad.
4. **El `drop-shadow` del casete se queda literal**: su color
(`rgba(58, 46, 36, …)`) es marrón de madera, parte de la depicción, no una
sombra del sistema.
5. Los cuatro `100%` (el `<svg>`, el fader) son identidad de layout.
Con eso, el techo de este componente es bajo POR NATURALEZA, y la mayor parte
de lo que queda fuera son literales de depicción con excepción escrita.
<!-- veredicto:end -->

@ -709,6 +709,16 @@ Lo que `textarea` añadió (2026-08-21):
mirando la cifra a mano. Con el censo ya endurecido (ver arriba) es el
momento de escribirlo.
- **⚠ El valor centinela GRANDE no prueba nada contra una propiedad capada.**
`skin-media-player` calcula su ancho como `min(100%, base × scale)`: el
`1234px` del guard se capa al contenedor y los siete tokens de tamaño leen
«no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133px y escalas
0.11/0.22/0.33) alcanzan exactamente. Es primo del caso ya registrado «un
valor centinela igual al real lee como no efecto» (`9999px` contra un
`--radius-full` que ya era 9999px): **el centinela tiene que ser imposible
EN LA DIRECCIÓN en que la propiedad puede moverse**. Un guard que probara
ambos extremos —uno muy grande y uno muy pequeño— cazaría las dos familias.
Lo que la REVISIÓN ADVERSARIAL de F2-B destapó (2026-08-22):
- **`drag-drop.preview-z` era un token que MENTÍA, y lo escondió una

@ -132,7 +132,7 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'preview-font-size':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (14 → 99px)',
'preview-shadow':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (0 0 0 7px rgb(7,8,9))',
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (0 0 0 7px rgb(7,8,9))'
},
link: {
'external-icon-size':
@ -246,6 +246,22 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'line-radius':
"only the `text` shape renders [data-skeleton-line]; the demo mounts `rect` (0 lines, counted). Forced shape='text' → reaches (4 → 1234px)"
},
'skin-media-player': {
'record-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with data-skin=record forced (the demo mounts turntable) -> reaches (111px exactly)',
'cassette-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=cassette forced -> reaches (122px exactly)',
'deck-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=deck forced -> reaches (133px exactly)',
'scale-sm':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with the data-size=sm of the PLAYER forced -> reaches (133 x 0.11 = 14.625px)',
'scale-lg':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=lg forced -> reaches (133 x 0.22 = 29.25px)',
'scale-xl':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=xl forced -> reaches (133 x 0.33 = 43.875px)',
'lamp-opacity':
'the lamp only dims while the player is paused and the deck still; forced -> reaches (0.18 -> 0.123)'
},
spinner: {
'bar-radius':
"the demo mounts the RING variant only (0 bars, 0 dots, counted); forced variant='bars' → reaches (1234px)",
@ -260,10 +276,8 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'the STATIC heading form is not mounted in the demo (0 [data-static] nodes vs 1 [data-button], counted); forced → reaches (rgb(1,2,3))',
'title-font-family':
'static heading not mounted in the demo; forced → reaches (Instrument Sans → Zapfino)',
'title-font-size':
'static heading not mounted in the demo; forced → reaches (16 → 1234px)',
'title-line-height':
'static heading not mounted in the demo; forced → reaches (20px → 3.77em)',
'title-font-size': 'static heading not mounted in the demo; forced → reaches (16 → 1234px)',
'title-line-height': 'static heading not mounted in the demo; forced → reaches (20px → 3.77em)',
'title-padding-block-end':
'static heading not mounted in the demo; forced → reaches (8 → 1234px)'
},
@ -286,12 +300,11 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'the DEMO HARNESS wins: `[data-uix-docs] code` (uix.css, 0-1-1) beats the recipe base rule `[data-code]` (0-1-0) and sets border-radius/padding/font on the inline code — confirmed with CDP matched-rules. The variant rules (0,2,0) DO win, which is why only this base-rule token reads dead. Same harness rule that hides the <pre> of code-block (next-features §13)',
'soft-bg':
"the demo mounts the `plain` variant only (no chrome); forced variant='soft' → reaches (rgb(1,2,3))",
'border-width':
"the demo mounts `plain` only; forced variant='outline' → reaches (1 → 9px)",
'border-width': "the demo mounts `plain` only; forced variant='outline' → reaches (1 → 9px)",
border: "the demo mounts `plain` only; forced variant='outline' → reaches (rgb(4,5,6))",
'padding-inline':
"only the chromed variants pad; forced → reaches (4.12 → 77px), and the variant rule (0,2,0) beats the harness",
'padding-block': "only the chromed variants pad; forced → reaches (1.29 → 66px)"
'only the chromed variants pad; forced → reaches (4.12 → 77px), and the variant rule (0,2,0) beats the harness',
'padding-block': 'only the chromed variants pad; forced → reaches (1.29 → 66px)'
},
'code-block': {
'pre-padding':

@ -149,6 +149,44 @@ cuatro variantes de `AudioPlayer` sobre este mismo morfo.
`eidos.dom.measure` anclado a un nodo, que es lo que la casa exige para
cualquier lectura de layout.
## Talla y tema
**Guard R-5.4** (`npm run theming:sentinel -- skin-media-player <url>`): 3/10
en vivo; los siete que callan están adjudicados con su medición en
`scripts/theming-sentinel-exceptions.ts`. Corrido 2026-08-22.
Contrato en `lib/recipes/base.ts > skin-media-player` (10 claves, entrada
NUEVA). Alcance del censo: **22 %** (era 0 %), y ese techo bajo es **la
naturaleza del componente**, no deuda.
**Es un componente de TONO FIJO.** Sus cuatro objetos son cosas físicas —caoba,
aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje
dorado— y esos tonos son la DEPICCIÓN, no roles de tema: un tocadiscos no se
vuelve azul porque el tema lo haga. La pintura vive en el SVG, bajo la
excepción que `recipe-css-contract.test.ts` ya reconoce para
`natural-time-picker`, `color-picker` y `proof-of-human`.
**Lo que sí entra al contrato es el TAMAÑO**, que la propia receta llamaba
«design values: retune here, in one place»: el ancho base **por skin** (un
casete no mide lo que un tocadiscos: 300 / 340 / 480 px) y la escala que sigue
el eje `size` del reproductor (0.7 / 1 / 1.35 / 1.7). El eje lo estampa el
PLAYER en su propia raíz y el skin lo lee de ahí — sin prop propia ni segundo
vocabulario. Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada
sigue enseñando su vidrio») y el radio del área de pulsación de las teclas.
**Fuera a propósito**: el `aspect-ratio` de cada skin (la proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera, parte de la depicción) y los cuatro `100%` de layout.
### ⚠ Su ancho no se puede medir con un centinela GRANDE
`inline-size: min(100%, base × scale)`: cualquier valor mayor que el contenedor
se capa, así que el `1234px` del guard deja los siete tokens de tamaño leyendo
«no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas
0.11/0.22/0.33) alcanzan exactamente. Registrado en `next-features.md` §13: el
centinela tiene que ser imposible **en la dirección en que la propiedad puede
moverse**.
## Gaps
| Gap | Disposición | Detalle |

@ -24,10 +24,10 @@
block. Left to fill its container the machines are nearly square, eat the
page and push the player's own control bar below the fold.
The base is the `md` width, supplied per device by `controls.ts`. */
--_smp-base: 480px;
--_smp-base: var(--skin-media-player-base-width);
/* And it follows the player's `size`, like every other piece of chrome
inside it. Both slots are design values: retune here, in one place. */
--_smp-scale: 1;
--_smp-scale: var(--skin-media-player-scale-md);
--_smp-size: calc(var(--_smp-base) * var(--_smp-scale));
/* Timing slots — the recipe's public surface for movement. */
@ -59,26 +59,26 @@
descendant, so it reads it from there — no prop of its own, no second
vocabulary. */
[data-media-player][data-size='sm'] [data-skin-media-player] {
--_smp-scale: 0.7;
--_smp-scale: var(--skin-media-player-scale-sm);
}
[data-media-player][data-size='lg'] [data-skin-media-player] {
--_smp-scale: 1.35;
--_smp-scale: var(--skin-media-player-scale-lg);
}
[data-media-player][data-size='xl'] [data-skin-media-player] {
--_smp-scale: 1.7;
--_smp-scale: var(--skin-media-player-scale-xl);
}
[data-skin-media-player][data-skin='record'] {
--_smp-aspect: 525 / 570;
--_smp-base: 340px;
--_smp-base: var(--skin-media-player-record-base-width);
}
[data-skin-media-player][data-skin='cassette'] {
--_smp-aspect: 1000 / 629;
--_smp-base: 300px;
--_smp-base: var(--skin-media-player-cassette-base-width);
}
[data-skin-media-player][data-skin='deck'] {
--_smp-aspect: 1000 / 780;
--_smp-base: 480px;
--_smp-base: var(--skin-media-player-deck-base-width);
}
[data-skin-media-player] > svg {
@ -192,7 +192,7 @@
}
.smp-lamp {
opacity: 0.18; /* literal: an unlit bulb still shows its glass */
opacity: var(--skin-media-player-lamp-opacity);
transition: opacity var(--_smp-lamp-duration);
}
[data-media-player]:not([data-paused]) .smp-lamp {
@ -260,7 +260,7 @@
background: none;
color: inherit;
cursor: pointer;
border-radius: var(--radius-sm);
border-radius: var(--skin-media-player-key-radius);
/* The paint lives in the SVG underneath; this is only the hit area. */
appearance: none;
}

@ -4084,6 +4084,16 @@
--menu-dial-gap: var(--space-3);
--menu-dial-stagger-step: 28ms;
--menu-dial-backdrop-bg: color-mix(in oklab, var(--color-neutral-text-strong) 40%, transparent);
--skin-media-player-base-width: 480px;
--skin-media-player-record-base-width: 340px;
--skin-media-player-cassette-base-width: 300px;
--skin-media-player-deck-base-width: 480px;
--skin-media-player-scale-sm: 0.7;
--skin-media-player-scale-md: 1;
--skin-media-player-scale-lg: 1.35;
--skin-media-player-scale-xl: 1.7;
--skin-media-player-lamp-opacity: 0.18;
--skin-media-player-key-radius: var(--radius-sm);
--knob-gap-sm: var(--size-sm-gap);
--knob-gap-md: var(--size-md-gap);
--knob-gap-lg: var(--size-lg-gap);

@ -6611,6 +6611,32 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
// a private on purpose: the recipe used to read `var(--knob-gap,
// var(--_knob-gap))`, where the public goes FIRST — so declaring the public
// in the contract would have killed the per-size scale outright.
// SkinMediaPlayer is a FIXED-TONE component: its hues are the depiction of
// physical objects (mahogany, brushed aluminium, chrome, ivory), not theme
// roles, and they live as literals in the SVG paint under the exception
// `recipe-css-contract.test.ts` already carves. What CAN be themed is its
// size — the recipe itself calls these "design values: retune here, in one
// place" — plus the unlit lamp and the key's hit area.
//
// The size axis is the PLAYER's: it stamps `data-size` on its own root and
// the skin reads it from there, so the resolved name targets the skin part
// while the scope comes from the ancestor.
'skin-media-player': {
// The base width is PER SKIN, not one number: each depicted object has its
// own natural width (a cassette is not a turntable). The root default is
// the deck's, which `controls.ts` supplies per device.
'base-width': '480px',
'record-base-width': '340px',
'cassette-base-width': '300px',
'deck-base-width': '480px',
'scale-sm': '0.7',
'scale-md': '1',
'scale-lg': '1.35',
'scale-xl': '1.7',
// An unlit bulb still shows its glass.
'lamp-opacity': '0.18',
'key-radius': 'var(--radius-sm)'
},
knob: {
'gap-sm': 'var(--size-sm-gap)',
'gap-md': 'var(--size-md-gap)',

Loading…
Cancel
Save

Powered by TurnKey Linux.