uix(color-field): temable — 56 % → 93 %, y sus TRECE tokens nunca se habían medido

Cuatro claves nuevas (22 → 26), un literal firmado, un préstamo adjudicado y
trece tokens PREEXISTENTES medidos uno a uno: el componente nunca había pasado
por R-5.4. Diff de computed 0 sobre 2.336 valores en 8 estados.

LAS CUATRO CLAVES

color-field IS-A Field y no realiasa nada suyo, así que lo que faltaba era
pequeño y concreto:

- separator-bg + separator-thickness — los separadores de canal («,», « / ») se
  pintan como líneas verticales de un pelo, y ni su tinta (--color-border-default)
  ni su grosor (1px) eran alcanzables. El 1px ES --border-width: es un filete, y
  dropdown-menu.separator-thickness lee la misma familia. El MISMO nodo
  renderizado como glifo («#», «%») ya tenía su literal-fg — una parte, dos
  tratamientos, ahora los dos con mando.
- segment-min-width (3ch) + hex-segment-min-width (6ch) — el ancho fijo por
  número de caracteres, el que impide el reflujo al teclear (99 → 100). Los
  gemelos date-field y time-field cargan el mismo hecho DENTRO de un
  calc(2ch + 2 * var(--…-segment-padding-inline)), así que el suyo ya viajaba con
  un token; aquí iba a pelo. Precedentes de `ch` en el catálogo:
  editable.area-min-inline-size (12ch) y tags-input.input-min-inline-size (8ch).

EL 7 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA

El chip lee var(--field-control-radius) porque IGUALA el radio del control —el
comentario del CSS lo dice: «so the chip tracks the control»—. Qué DECIDE ese
token: el radio del control de campo. Un swatch-radius propio daría un segundo
mando para una decisión ajena y permitiría desincronizar el chip del campo que
lo contiene. Préstamo con dueño: se tema en la ficha de field — misma lectura
que el --slider-disabled-opacity de waveform, hoy mismo.

TRECE ADJUDICACIONES, TODAS PROBADAS SOBRE NODOS REALES

La demo arranca con valor, en md, editable y con tres formatos: cada estado y
cada paso de talla que no monta no tiene nodo que pintar. Seis de estado
(placeholder, invalid, los tres de readonly, disabled), cuatro de la escala del
chip (14→77, 16→77, 20→77, 32→77 px), la etiqueta del formato BLOQUEADO —que
sólo existe con un único formato permitido, medida desmarcando rgb y hsl CON LOS
CHIPS DE LA PROPIA DEMO, no fabricando el nodo— y las dos de transición.

LAS DOS DE TRANSICIÓN, DOS DIAGNÓSTICOS FALSOS Y EL SEGUNDO ES NUEVO

El primero es la ley ya escrita: el guard congela `transition` para hacer
medible todo lo demás, así que los tokens que SON la transición no se mueven
bajo él. El segundo fue mío: midiéndolas a mano leí 0s y sospeché del
@media (prefers-reduced-motion: reduce) del final del fichero — lo comprobé
corriendo con reducedMotion: 'no-preference' y SEGUÍA dando 0s, porque el nodo
que había cogido (querySelector('[data-color-field-segment]')) es el PRIMER
segmento, que es un `literal` y declara transition: none de su propia cosecha.
Sobre un segmento editable: 0.12s → 11.5s y cubic-bezier(0.4,0,0.2,1) →
steps(7). Mide el nodo que PINTA, no el primero que casa el selector.

VERIFICACIÓN

Sonda antes/después 0 diffs (2.336 valores · 8 estados · 10 nodos) · R-5.4 13/26
con las trece restantes adjudicadas y medidas, cero STALE · component:audit PASS
· censo 93 % · eidos-lint 14 morfo-backed / 14 eidos-only / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es
skin-media-player, el conocido) · Chrome: los dos glifos con tinta y sin fondo,
el divisor a 1px con el fondo del borde, hex 64.4px (6ch), chip 18px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 11b92ded6f
commit 3c6742b9c5

@ -10,8 +10,8 @@
- **Medido**: 2026-08-23 · **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**: **65%** — 3014 de 4602 knobs pasan por un token público del componente
- **Reparto**: público 3014 · privado 318 · global 845 · literal 425 · sistema transversal 509 · excepción firmada 110 _(los dos últimos, fuera del ratio)_
- **Alcance global**: **66%** — 3019 de 4601 knobs pasan por un token público del componente
- **Reparto**: público 3019 · privado 318 · global 844 · literal 420 · sistema transversal 509 · excepción firmada 111 _(los dos últimos, fuera del ratio)_
- **Sin token público propio**: 24 · **alcance < 20 %**: 12 · **alcance 100 %**: 26 · **con eje `size`**: 57
## Cómo se lee
@ -134,7 +134,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [toggle-group](./toggle-group.md) | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | – |
| [sidebar](./sidebar.md) | 55% | 45 | 23 | 0 | 14 | 5 | 3 | 50 | – |
| [audio-player](./audio-player.md) | 56% | 18 | 10 | 0 | 8 | 0 | 0 | 8 | – |
| [color-field](./color-field.md) | 56% | 16 | 9 | 0 | 2 | 5 | 0 | 22 | y |
| [background](./background.md) | 57% | 26 | 13 | 1 | 6 | 3 | 3 | 31 | – |
| [s-text-virtual-list](./s-text-virtual-list.md) | 58% | 12 | 7 | 0 | 3 | 2 | 0 | 9 | – |
| [badge](./badge.md) | 59% | 22 | 13 | 6 | 1 | 2 | 0 | 65 | y |
@ -216,6 +215,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [button](./button.md) | 91% | 23 | 20 | 2 | 0 | 0 | 1 | 106 | y |
| [tree-view](./tree-view.md) | 92% | 28 | 24 | 1 | 0 | 1 | 2 | 46 | – |
| [gradient-builder](./gradient-builder.md) | 93% | 60 | 52 | 0 | 1 | 3 | 4 | 72 | – |
| [color-field](./color-field.md) | 93% | 15 | 14 | 0 | 1 | 0 | 0 | 26 | y |
| [scroll-area](./scroll-area.md) | 94% | 17 | 15 | 0 | 0 | 1 | 1 | 13 | – |
| [tree-grid](./tree-grid.md) | 95% | 42 | 37 | 1 | 0 | 1 | 3 | 47 | – |
| [picker-shell](./picker-shell.md) | 95% | 21 | 20 | 0 | 1 | 0 | 0 | 31 | y |

@ -5,42 +5,37 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-23 · **Alcance**: **56%** — 9 de 16 knobs por token público
- **Knobs de apariencia**: 16 — público 9 · privado 0 · global 2 · literal 5 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 22 pública(s) — `segment-padding-inline-xs`, `segment-padding-inline-sm`, `segment-padding-inline-md`, `segment-padding-inline-lg`, `segment-padding-inline-xl`, `placeholder-fg`, `literal-fg`, `segment-radius`, `segment-fg`, `segment-invalid-fg`, `segment-readonly-bg`, `segment-readonly-fg`, `segment-readonly-decoration`, `swatch-size-xs`, `swatch-size-sm`, `swatch-size-md`, `swatch-size-lg`, `swatch-size-xl`, `format-select-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`
- **Medido**: 2026-08-23 · **Alcance**: **93%** — 14 de 15 knobs por token público
- **Knobs de apariencia**: 15 — público 14 · privado 0 · global 1 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 26 pública(s) — `segment-padding-inline-xs`, `segment-padding-inline-sm`, `segment-padding-inline-md`, `segment-padding-inline-lg`, `segment-padding-inline-xl`, `placeholder-fg`, `literal-fg`, `segment-radius`, `segment-fg`, `segment-invalid-fg`, `segment-readonly-bg`, `segment-readonly-fg`, `segment-readonly-decoration`, `swatch-size-xs`, `swatch-size-sm`, `swatch-size-md`, `swatch-size-lg`, `swatch-size-xl`, `format-select-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `separator-bg`, `separator-thickness`, `segment-min-width`, `hex-segment-min-width`
- **Eje `size`**: sí · **ficheros**: `color-field.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 | `color-field.css:82` | `[data-color-field-segment][data-segment='literal']` | `background` | `var(--color-border-default)` |
| 2 | `color-field.css:122` | `[data-color-field-swatch][data-color-swatch]` | `border-radius` | `var(--field-control-radius)` ⤴ prestado de `field` |
| 1 | `color-field.css:122` | `[data-color-field-swatch][data-color-swatch]` | `border-radius` | `var(--field-control-radius)` ⤴ prestado de `field` |
### 1.2 A través de un privado (0)
_Ninguno._
### 1.3 Literales (5)
### 1.3 Literales (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `color-field.css:59` | `[data-color-field-segment]` | `min-inline-size` | `3ch` |
| 2 | `color-field.css:76` | `[data-color-field-segment][data-segment='literal']` | `inline-size` | `1px` |
| 3 | `color-field.css:77` | `[data-color-field-segment][data-segment='literal']` | `min-inline-size` | `1px` |
| 4 | `color-field.css:111` | `[data-color-field-segment][data-segment='hex']` | `min-inline-size` | `6ch` |
| 5 | `color-field.css:158` | `[data-color-field-format-select][data-select]` | `inline-size` | `fit-content` |
_Ninguno._
### 1.4 Excepciones firmadas (0) — fuera del ratio
### 1.4 Excepciones firmadas (1) — fuera del ratio
Literales que llevan su anotación `/* literal: <razón> */` en la propia
declaración: la válvula de recipe-contract §3, la misma que honra
`component-audit`. **Una desviación firmada no es deuda** — se listan para que la
razón se lea, no para acuñarlas.
_Ninguno._
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `color-field.css:158` | `[data-color-field-format-select][data-select]` | `inline-size` | `fit-content` |
## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
@ -61,7 +56,7 @@ _Ninguno._
- **Consume tokens públicos de `field`.** 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` (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
@ -71,8 +66,6 @@ no distingue lo que debería — se marca `⚠`.
| token (`--color-field-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `segment-width` | `root` | ⚠ `3ch` / `1px` / `6ch` | 4 |
| `segment-bg` | `root` | `var(--color-border-default)` | 1 |
| `swatch-radius` | `root` | `var(--field-control-radius)` | 1 |
| `format-select-width` | `root` | `fit-content` | 1 |
@ -88,6 +81,63 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**EJECUTADO 2026-08-23 — 56 % → 93 %.** Cuatro claves nuevas (22 → 26), un
literal firmado, un préstamo adjudicado **y TRECE tokens preexistentes medidos
uno a uno**: el componente nunca había pasado por R-5.4. Diff de computed **0
sobre 2.336 valores en 8 estados**.
**Las cuatro claves.** El componente es un Field y no realiasa nada suyo, así
que lo que faltaba era pequeño y muy concreto:
- **`separator-bg` + `separator-thickness`** — los separadores de canal («,»,
« / ») se pintan como líneas verticales de un pelo, y ni su tinta
(`--color-border-default`) ni su grosor (`1px`) eran alcanzables. El `1px` ES
`--border-width`: es un filete, y `dropdown-menu.separator-thickness` lee la
misma familia. El mismo nodo renderizado como GLIFO («#», «%») ya tenía su
`literal-fg` — una parte, dos tratamientos, y ahora los dos con mando.
- **`segment-min-width` (3ch) + `hex-segment-min-width` (6ch)** — el ancho fijo
por número de caracteres, que impide el reflujo al teclear (99 → 100). Los
gemelos `date-field` y `time-field` cargan el mismo hecho DENTRO de un
`calc(2ch + 2 * var(--…-segment-padding-inline))`, así que el suyo ya viajaba
con un token y el censo lo daba por alcanzable; aquí iba a pelo. Precedentes
de unidad `ch` en el catálogo: `editable.area-min-inline-size` (12ch) y
`tags-input.input-min-inline-size` (8ch).
**El 7 % que queda es una fila, y es doctrina.** El chip de color lee
`var(--field-control-radius)` porque IGUALA el radio del control — el comentario
del CSS lo dice: «so the chip tracks the control». Qué DECIDE ese token: el
radio del control de campo. Un `swatch-radius` propio daría un segundo mando
para una decisión ajena y permitiría desincronizar el chip del campo que lo
contiene. Préstamo con dueño, se tema en la ficha de `field` — la misma lectura
que el `--slider-disabled-opacity` de `waveform`, el mismo día.
**Y el `fit-content` del conmutador es identidad**: el trigger se mide POR su
contenido, no contra una longitud. Firmado con su anotación, fuera del ratio.
**Las trece adjudicaciones, todas PROBADAS forzando sobre nodos reales.** La
demo arranca con valor, en `md`, editable y con tres formatos: cada estado y
cada paso de talla que no monta no tiene nodo que pintar. Seis de estado
(`placeholder`, `invalid`, los tres de `readonly`, `disabled`), cuatro de la
escala del chip (14 → 77, 16 → 77, 20 → 77, 32 → 77 px), la etiqueta del formato
BLOQUEADO —que sólo existe con un único formato permitido, así que se midió
desmarcando `rgb` y `hsl` **con los chips de la propia demo**, no fabricando el
nodo— y las dos de transición.
**Las dos de transición costaron dos diagnósticos falsos, y el segundo es nuevo.**
El primero es la ley ya escrita: el guard congela `transition` para hacer
medible todo lo demás, así que los tokens que SON la transición no se pueden
mover bajo él. El segundo fue mío: midiéndolas a mano leí `0s` y sospeché del
`@media (prefers-reduced-motion: reduce)` del final del fichero — lo comprobé
corriendo con `reducedMotion: 'no-preference'` y **seguía dando 0s**, porque el
nodo que había cogido (`querySelector('[data-color-field-segment]')`) es el
PRIMER segmento, que es un `literal` y declara `transition: none` de su propia
cosecha. Sobre un segmento editable: `0.12s → 11.5s` y
`cubic-bezier(0.4,0,0.2,1) → steps(7)`. **Mide el nodo que pinta, no el primero
que casa el selector.**
**Verificación**: sonda antes/después **0 diffs** (2.336 valores · 8 estados · 10
nodos) · R-5.4 13/26 con las trece restantes adjudicadas y medidas, cero STALE ·
`component:audit` PASS · censo 93 % · `eidos-lint` 14 morfo-backed / 14
eidos-only / **0 invalid, 0 class-hooks**.
<!-- veredicto:end -->

@ -18,6 +18,43 @@
*/
export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'color-field': {
// 2026-08-23. Thirteen tokens, none of them a lie: the demo boots with a
// value, in `md`, editable, with three formats allowed — so every state
// and every size step but the mounted one has no node to paint. All
// forced on the REAL nodes (never a fabricated one), transitions frozen.
'placeholder-fg':
'no empty field in the demo; forced data-placeholder → reaches (oklch(0.61 0 0) → rgb(1,2,3))',
'segment-invalid-fg':
'invalid is off in the demo; forced data-invalid → reaches (oklch(0.5855 0.1743 42.74) → rgb(1,2,3))',
'segment-readonly-bg':
'readonly is off in the demo; forced data-readonly → reaches (oklch(0.931 0 0) → rgb(1,2,3))',
'segment-readonly-fg':
'readonly is off in the demo; forced data-readonly → reaches (oklch(0.5032 0 0) → rgb(1,2,3))',
'segment-readonly-decoration':
'readonly is off in the demo; forced data-readonly → reaches (underline dotted → overline wavy)',
'disabled-opacity':
'disabled is off in the demo; forced data-disabled → reaches (0.4 → 0.123)',
// The swatch scale: only the step in force paints. Forced each size on the
// real root and measured the swatch — every step reaches.
'swatch-size-xs': 'only the size in force paints; forced data-size=xs → reaches (14px → 77px)',
'swatch-size-sm': 'only the size in force paints; forced data-size=sm → reaches (16px → 77px)',
'swatch-size-lg': 'only the size in force paints; forced data-size=lg → reaches (20px → 77px)',
'swatch-size-xl': 'only the size in force paints; forced data-size=xl → reaches (32px → 77px)',
// The locked-format LABEL only renders when a single format is allowed
// (`isLocked`). Driven through the demo's own chips — not a hand-made node.
'format-select-fg':
'the static label only renders with ONE allowed format; unchecked rgb+hsl in the demo → reaches (oklch(0.5032 0 0) → rgb(1,2,3))',
// The known law: the guard freezes `transition` to make everything else
// measurable, so the tokens that ARE the transition can never move under
// it. Measured in a separate unfrozen pass, on an EDITABLE segment — the
// first segment is a `literal`, which declares `transition: none` of its
// own and reads 0s whatever the token says.
'transition-duration':
'frozen by the guard by design; unfrozen pass on the hex segment → reaches (0.12s → 11.5s)',
'transition-ease':
'frozen by the guard by design; unfrozen pass on the hex segment → reaches (cubic-bezier(0.4,0,0.2,1) → steps(7))'
},
'navigation-menu': {
// 2026-08-23. The rule is `[…-trigger][data-disabled], […-link][data-disabled]`
// and the demo mounts neither — measured with transitions FROZEN, because

@ -33,6 +33,37 @@ Color Picker, Bits UI Color Picker.
Partes públicas: `Label`, `Input`, `Segment`, `FormatSelect`, `HiddenInput`.
## Talla y tema
**color-field IS-A Field**: la caja, la altura, el relleno, la tipografía y el
anillo de foco los posee `field.css`, y esta receta NO los realiasa. Su contrato
cubre sólo lo suyo — el segmento, el divisor, el chip y el conmutador de formato
— con 26 claves en `lib/recipes/base.ts`.
| Token (`--color-field-…`) | Qué fija |
| ----------------------------------------------- | ------------------------------------------- |
| `segment-padding-inline-{xs…xl}` | relleno por talla del segmento |
| `segment-min-width` · `hex-segment-min-width` | el ancho FIJO por nº de caracteres (3ch / 6ch) |
| `segment-radius` · `segment-fg` | forma y tinta del segmento |
| `separator-bg` · `separator-thickness` | la línea divisoria entre canales |
| `placeholder-fg` · `literal-fg` | hueco y glifos de formato («#», «%») |
| `segment-invalid-fg` | segmento inválido |
| `segment-readonly-{bg,fg,decoration}` | segmento de sólo lectura |
| `swatch-size-{xs…xl}` | el chip de color por talla |
| `format-select-fg` | la etiqueta estática del formato bloqueado |
| `transition-{duration,ease}` · `disabled-opacity` | movimiento y apagado |
**Lo que NO acuña, y por qué** (2026-08-23): el chip lee
`var(--field-control-radius)` porque IGUALA el radio del control —«so the chip
tracks the control»—, y esa decisión es de Field: un `swatch-radius` propio
permitiría desincronizarlos. Préstamo con dueño, se tema en la ficha de `field`.
Y el `fit-content` del conmutador es identidad (el trigger se mide POR su
contenido, no contra una longitud), firmado con su anotación.
**El divisor**: los separadores de canal («,», « / ») se pintan como líneas
verticales de un pelo y los glifos de formato («#», «%») como texto — la misma
parte, dos tratamientos. Por eso hay `separator-*` y `literal-fg`.
## Comparativa
| Capacidad | Soma/Eidos | Ark UI | Chakra UI | Bits UI |

@ -56,7 +56,7 @@
/* Channels (RGB 0-255 / HSL 0-360 / 0-100 / alpha) are at most 3 digits →
fixed 3ch so they don't reflow 99 → 100. Hex (6 chars) overridden below.
Font-relative + tabular figures (shared field-segment-state layer). */
min-inline-size: 3ch;
min-inline-size: var(--color-field-segment-min-width);
padding-inline: 0;
border-radius: var(--color-field-segment-radius);
color: var(--color-field-segment-fg);
@ -73,13 +73,13 @@
VERTICAL DIVIDER LINES between the editable segments. The format's PREFIX /
SUFFIX glyphs ("#", "%") keep their text — see the override below. */
[data-color-field-segment][data-segment='literal'] {
inline-size: 1px;
min-inline-size: 1px;
inline-size: var(--color-field-separator-thickness);
min-inline-size: var(--color-field-separator-thickness);
align-self: stretch;
padding-inline: 0;
margin-inline: var(--space-1);
color: transparent;
background: var(--color-border-default);
background: var(--color-field-separator-bg);
border-radius: 0;
pointer-events: none;
user-select: none;
@ -108,7 +108,7 @@
/* Hex is always 6 chars (RRGGBB) — wider than the 3ch channel default. */
[data-color-field-segment][data-segment='hex'] {
min-inline-size: 6ch;
min-inline-size: var(--color-field-hex-segment-min-width);
}
/* Leading value swatch (opt-in via <ColorField.Input swatch>) — composes the
@ -155,7 +155,7 @@
pin it to the inline end — a standalone <Select> stretches to 100%, which here
would eat the rest of the field. The `[data-select]` bump overrides that. */
[data-color-field-format-select][data-select] {
inline-size: fit-content;
inline-size: fit-content; /* literal: identity — the trigger is sized BY its content, not to a length */
margin-inline-start: auto;
}

@ -1551,6 +1551,10 @@
--color-field-transition-duration: var(--duration-fast);
--color-field-transition-ease: var(--ease-default);
--color-field-disabled-opacity: var(--opacity-disabled);
--color-field-separator-bg: var(--color-border-default);
--color-field-separator-thickness: var(--border-width);
--color-field-segment-min-width: 3ch;
--color-field-hex-segment-min-width: 6ch;
--color-picker-gap: var(--space-1-5);
--color-picker-control-gap: var(--space-2);
--color-picker-label-fg: var(--color-content-primary);

@ -1684,7 +1684,23 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'format-select-fg': 'var(--color-content-secondary)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
'disabled-opacity': 'var(--opacity-disabled)',
// The channel SEPARATORS (",", " / ") render as thin vertical divider
// lines between the editable segments — a rule of the recipe's own, so
// its ink and its thickness are knobs. `1px` IS `--border-width`: the
// divider is a hairline, and `dropdown-menu.separator-thickness` reads
// the same family. (The format's prefix / suffix GLYPHS, "#" and "%",
// are the same part rendered as real text and take `literal-fg`.)
'separator-bg': 'var(--color-border-default)',
'separator-thickness': 'var(--border-width)',
// A segment holds a FIXED number of characters so the field never
// reflows while typing: 3 for a channel (RGB 0-255 · HSL 0-360/0-100 ·
// alpha) and 6 for hex (RRGGBB). Font-relative on purpose — the width
// has to follow the face the theme picks. The twins (`date-field` /
// `time-field`) carry the same fact inside a `calc(2ch + padding)`, so
// theirs already rode a token; this one was a bare literal.
'segment-min-width': '3ch',
'hex-segment-min-width': '6ch'
},
'color-picker': {
// ── La costura (2026-08-22) ──────────────────────────────────────────

@ -9,6 +9,7 @@
import { compileMorfo } from '$uix/morfo';
import { colorFieldMorfo } from '@/uix/morfo/components/color-field';
import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
import {
colorValueFromRgb,
type ColorValue,
@ -17,7 +18,7 @@
} from '$libs/color';
import { colorValue, PALETTE_SCALES } from '../../lib/harness.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 };
type Direction = 'ltr' | 'rtl';
@ -338,6 +339,9 @@
<span data-uix-tab-count>{events.length}</span>
</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>26</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -906,6 +910,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="color-field" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.