uix(gradient-builder): el editor entero pasa a tokens públicos — un tema lo mueve sin tocar el sistema

Primer componente del eje theme-reach, y su piloto: la receta declaraba CERO
tokens propios («self-contained», decía la cabecera) y ataba cada knob a un
primitivo global o a un privado. Alcance medido: 0 %. Un tema no podía cambiar
ni el alto del preview ni el radio de la tarjeta sin mover el ecosistema.

Ahora los 72 knobs son contrato público del componente. Los ejes dimensionales
siguen el canon de dos piezas que firmó el Sidebar: coordenadas por talla
(`preview-height-{sm,md,lg}`…) más UN nombre resuelto (`host` = md, `size:sm`,
`size:lg`) que es lo único que consume el CSS — así los bloques `[data-size]`
de la receta desaparecen y la cascada la emite el TSC. La geometría se queda
PROPIA (px × `--scaling`): un raíl de edición no tiene coordenada de
control-height, así que forzarlo al bundle habría sido rediseño. La tipografía
sí lo consume, que es lo que el guard `recipe-css-contract` exige.

De paso mueren dos abreviaturas que escondían tokens al censo y podían cruzarse
por anidamiento: `--gb-checker*` y `--gb-stop-fill` pasan a
`--_gradient-builder-*` (también en el wrapper). El damero de transparencia se
parametriza por `checker-color` + `checker-cell`, y el patrón de cuatro
gradientes se queda privado — un tema cambia el color de la celda, no reescribe
la rampa. El `padding` shorthand se parte en ejes lógicos.

El default no se mueve: sonda antes/después con 6.612 valores computados
comparados en 7 estados (reposo · cinco tallas · hover) sobre 34 nodos, diff
VACÍO. El único diff que apareció era la transición de hover de un Button
compuesto capturada a mitad — demostrado reproduciendo el valor interpolado en
el árbol ya modificado, bajando la espera de asentamiento.

Centinela: 72/72 tokens alcanzan. 49 desde el ámbito del componente en reposo;
las 12 coordenadas de talla y las 2 de preset seleccionado forzando su estado;
`hover-preset-border` con hover real; y los 4 del editor de color sólo desde
`:root`, porque el panel viaja por portal — anotado en el README para que nadie
lo lea como un token muerto.

Dos hallazgos que quedan anotados, no corregidos (mueven píxel, así que son
decisión): el wrapper pinta el preview con el shorthand `background` inline, que
resetea `background-image` y tapa el damero, de modo que la transparencia no
llega a verse; y el foco de la parada usa el patrón de dos anillos con
box-shadow cuando §32 canonizó `outline`.

Guards: component:audit PASS · eidos-lint 0 invalid · suite eidos sin rojos
nuevos (queda el conocido skin-media-player) · rtl:check 0 · docs:check 0 ·
check sin errores en los ficheros tocados · censo 0 % → 80 % (el resto son
privados que YA derivan de públicos, el anillo de foco y tres literales de
layout) · captura 2× revisada.

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

@ -10,9 +10,9 @@
- **Medido**: 2026-08-19 · **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**: **33%** — 1621 de 4971 knobs pasan por un token público del componente
- **Reparto**: público 1621 · privado 856 · global 1880 · literal 614 · sistema transversal 234 _(fuera del ratio)_
- **Sin token público propio**: 62 · **alcance < 20 %**: 59 · **alcance 100 %**: 6 · **con eje `size`**: 82
- **Alcance global**: **34%** — 1666 de 4970 knobs pasan por un token público del componente
- **Reparto**: público 1666 · privado 855 · global 1835 · literal 614 · sistema transversal 233 _(fuera del ratio)_
- **Sin token público propio**: 61 · **alcance < 20 %**: 58 · **alcance 100 %**: 6 · **con eje `size`**: 81
## Cómo se lee
@ -81,22 +81,22 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 9 | [prose](./prose.md) | 73 | 1% | 74 | 2 |
| 10 | [month-grid](./month-grid.md) | 58 | 0% | 61 | 0 |
| 11 | [year-grid](./year-grid.md) | 58 | 0% | 61 | 0 |
| 12 | [gradient-builder](./gradient-builder.md) | 57 | 0% | 62 | 0 |
| 13 | [time-picker](./time-picker.md) | 57 | 0% | 59 | 0 |
| 14 | [date-range-picker](./date-range-picker.md) | 52 | 0% | 55 | 0 |
| 15 | [command](./command.md) | 50 | 0% | 52 | 0 |
| 16 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 |
| 17 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 |
| 18 | [table](./table.md) | 42 | 0% | 46 | 0 |
| 19 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 20 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 |
| 12 | [time-picker](./time-picker.md) | 57 | 0% | 59 | 0 |
| 13 | [date-range-picker](./date-range-picker.md) | 52 | 0% | 55 | 0 |
| 14 | [command](./command.md) | 50 | 0% | 52 | 0 |
| 15 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 |
| 16 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 |
| 17 | [table](./table.md) | 42 | 0% | 46 | 0 |
| 18 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 19 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 |
| 20 | [heading](./heading.md) | 36 | 0% | 36 | 0 |
## Sin ningún token público propio (62)
## Sin ningún token público propio (61)
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) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`command`](./command.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`date-range-picker`](./date-range-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-builder`](./gradient-builder.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.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) · [`natural-time-picker`](./natural-time-picker.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.md) · [`proof-of-human`](./proof-of-human.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.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) · [`textarea`](./textarea.md) · [`time-picker`](./time-picker.md) · [`time-range-picker`](./time-range-picker.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.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) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`command`](./command.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`date-range-picker`](./date-range-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.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) · [`natural-time-picker`](./natural-time-picker.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.md) · [`proof-of-human`](./proof-of-human.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.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) · [`textarea`](./textarea.md) · [`time-picker`](./time-picker.md) · [`time-range-picker`](./time-range-picker.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@ -109,7 +109,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [proof-of-human](./proof-of-human.md) | 0% | 95 | 0 | 18 | 36 | 37 | 4 | 0 | y |
| [time-range-picker](./time-range-picker.md) | 0% | 88 | 0 | 6 | 61 | 17 | 4 | 0 | y |
| [natural-time-picker](./natural-time-picker.md) | 0% | 78 | 0 | 0 | 69 | 7 | 2 | 0 | y |
| [gradient-builder](./gradient-builder.md) | 0% | 62 | 0 | 8 | 46 | 3 | 5 | 0 | y |
| [month-grid](./month-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| [year-grid](./year-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| [time-picker](./time-picker.md) | 0% | 59 | 0 | 3 | 42 | 12 | 2 | 0 | y |
@ -239,6 +238,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [radio-group](./radio-group.md) | 77% | 75 | 56 | 10 | 7 | 0 | 2 | 61 | y |
| [chat-log](./chat-log.md) | 77% | 42 | 30 | 0 | 4 | 5 | 3 | 31 | – |
| [chat-composer](./chat-composer.md) | 79% | 44 | 31 | 0 | 3 | 5 | 5 | 31 | – |
| [gradient-builder](./gradient-builder.md) | 80% | 60 | 45 | 7 | 1 | 3 | 4 | 72 | – |
| [chat-message](./chat-message.md) | 82% | 84 | 63 | 2 | 6 | 6 | 7 | 75 | y |
| [fab](./fab.md) | 82% | 17 | 14 | 0 | 2 | 1 | 0 | 10 | – |
| [toast](./toast.md) | 83% | 42 | 35 | 6 | 0 | 1 | 0 | 85 | – |

@ -5,115 +5,71 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-19 · **Alcance**: **0%** — 0 de 57 knobs por token público
- **Knobs de apariencia**: 62 — público 0 · privado 8 · global 46 · literal 3 · sistema 5 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`**
- **Eje `size`**: sí · **ficheros**: `gradient-builder.css`
- **Medido**: 2026-08-19 · **Alcance**: **80%** — 45 de 56 knobs por token público
- **Knobs de apariencia**: 60 — público 45 · privado 7 · global 1 · literal 3 · sistema 4 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 72 pública(s) — `preview-height-sm`, `preview-height-md`, `preview-height-lg`, `track-height-sm`, `track-height-md`, `track-height-lg`, `stop-size-sm`, `stop-size-md`, `stop-size-lg`, `gap-sm`, `gap-md`, `gap-lg`, `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `preview-height`, `track-height`, `stop-size`, `gap`, `padding-block`, `padding-inline`, `radius`, `bg`, `border`, `border-width`, `preview-radius`, `preview-bg`, `preview-border`, `track-radius`, `track-border`, `checker-color`, `checker-cell`, `stop-radius`, `stop-bg`, `stop-ring`, `stop-ring-width`, `stop-shadow`, `selected-stop-shadow`, `stop-actions-gap`, `angle-dial-gap`, `angle-dial-head-gap`, `label-font-size`, `label-fg`, `value-font-size`, `value-fg`, `stop-color-title-font-size`, `stop-color-title-font-weight`, `stop-color-title-fg`, `stop-color-sliders-gap`, `stop-list-gap`, `stop-list-row-gap`, `stop-list-value-gap`, `stop-list-value-padding-block`, `stop-list-value-padding-inline`, `stop-list-value-radius`, `stop-list-value-bg`, `stop-list-value-fg`, `stop-list-value-border`, `stop-list-value-font-size`, `selected-stop-list-value-border`, `stop-list-pos-fg`, `presets-gap`, `preset-grid-gap`, `preset-height`, `preset-radius`, `preset-border`, `hover-preset-border`, `selected-preset-border`, `selected-preset-outline`
- **Eje `size`**: no · **ficheros**: `gradient-builder.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (46)
### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `gradient-builder.css:34` | `[data-gradient-builder]` | `gap` | `var(--space-4)` |
| 2 | `gradient-builder.css:38` | `[data-gradient-builder]` | `padding` | `var(--space-4)` |
| 3 | `gradient-builder.css:41` | `[data-gradient-builder]` | `background` | `var(--color-surface-raised, var(--color-surface-default))` |
| 4 | `gradient-builder.css:42` | `[data-gradient-builder]` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 5 | `gradient-builder.css:43` | `[data-gradient-builder]` | `border-radius` | `var(--radius-lg)` |
| 6 | `gradient-builder.css:50` | `[data-gradient-builder][data-size='sm']` | `gap` | `var(--space-3)` |
| 7 | `gradient-builder.css:51` | `[data-gradient-builder][data-size='sm']` | `padding` | `var(--space-3)` |
| 8 | `gradient-builder.css:58` | `[data-gradient-builder][data-size='lg']` | `gap` | `var(--space-5)` |
| 9 | `gradient-builder.css:59` | `[data-gradient-builder][data-size='lg']` | `padding` | `var(--space-5)` |
| 10 | `gradient-builder.css:91` | `[data-gradient-builder-preview]` | `border` | `var(--border-width) solid var(--color-border-subtle)` |
| 11 | `gradient-builder.css:92` | `[data-gradient-builder-preview]` | `background-color` | `var(--color-surface-default)` |
| 12 | `gradient-builder.css:93` | `[data-gradient-builder-preview]` | `background-image` | `var(--gb-checker)` |
| 13 | `gradient-builder.css:110` | `[data-gradient-builder-track]` | `border` | `var(--border-width) solid var(--color-border-subtle)` |
| 14 | `gradient-builder.css:123` | `[data-gradient-builder-stop]` | `border-radius` | `var(--radius-full)` |
| 15 | `gradient-builder.css:125` | `[data-gradient-builder-stop]` | `background` | `var(--gb-stop-fill, var(--color-surface-default))` |
| 16 | `gradient-builder.css:126` | `[data-gradient-builder-stop]` | `box-shadow` | `0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 30%, transparent)` |
| 17 | `gradient-builder.css:139` | `[data-gradient-builder-stop][data-selected]` | `box-shadow` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-primary-solid), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 35%, transparent)` |
| 18 | `gradient-builder.css:145` | `[data-gradient-builder-stop]:focus-visible` | `box-shadow` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` |
| 19 | `gradient-builder.css:154` | `[data-gradient-builder-stop-actions]` | `gap` | `var(--space-2)` |
| 20 | `gradient-builder.css:171` | `[data-gradient-builder-angle-dial]` | `gap` | `var(--space-2)` |
| 21 | `gradient-builder.css:177` | `[data-gradient-builder-angle-dial-head]` | `gap` | `var(--space-2)` |
| 22 | `gradient-builder.css:180` | `[data-gradient-builder-angle-dial-label]` | `font-size` | `var(--font-size-sm)` |
| 23 | `gradient-builder.css:184` | `[data-gradient-builder-angle-dial-value]` | `font-size` | `var(--font-size-sm)` |
| 24 | `gradient-builder.css:186` | `[data-gradient-builder-angle-dial-value]` | `color` | `var(--color-content-primary)` |
| 25 | `gradient-builder.css:195` | `[data-gradient-builder-stop-color-title]` | `font-size` | `var(--font-size-sm)` |
| 26 | `gradient-builder.css:196` | `[data-gradient-builder-stop-color-title]` | `font-weight` | `var(--font-weight-medium, 500)` |
| 27 | `gradient-builder.css:197` | `[data-gradient-builder-stop-color-title]` | `color` | `var(--color-content-primary)` |
| 28 | `gradient-builder.css:202` | `[data-gradient-builder-stop-color-sliders]` | `gap` | `var(--space-2)` |
| 29 | `gradient-builder.css:220` | `[data-gradient-builder-stop-list]` | `gap` | `var(--space-1)` |
| 30 | `gradient-builder.css:225` | `[data-gradient-builder-stop-list-row]` | `gap` | `var(--space-2)` |
| 31 | `gradient-builder.css:232` | `[data-gradient-builder-stop-list-value]` | `gap` | `var(--space-2)` |
| 32 | `gradient-builder.css:234` | `[data-gradient-builder-stop-list-value]` | `padding` | `var(--space-1) var(--space-2)` |
| 33 | `gradient-builder.css:235` | `[data-gradient-builder-stop-list-value]` | `border` | `var(--border-width) solid var(--color-border-subtle)` |
| 34 | `gradient-builder.css:236` | `[data-gradient-builder-stop-list-value]` | `border-radius` | `var(--radius-md)` |
| 35 | `gradient-builder.css:237` | `[data-gradient-builder-stop-list-value]` | `background` | `var(--color-surface-default)` |
| 36 | `gradient-builder.css:238` | `[data-gradient-builder-stop-list-value]` | `color` | `var(--color-content-primary)` |
| 37 | `gradient-builder.css:240` | `[data-gradient-builder-stop-list-value]` | `font-size` | `var(--font-size-sm)` |
| 38 | `gradient-builder.css:248` | `[data-gradient-builder-stop-list-value][data-selected]` | `border-color` | `var(--color-primary-border)` |
| 39 | `gradient-builder.css:259` | `[data-gradient-builder-stop-list-pos]` | `color` | `var(--color-content-secondary)` |
| 40 | `gradient-builder.css:267` | `[data-gradient-builder-presets]` | `gap` | `var(--space-2)` |
| 41 | `gradient-builder.css:272` | `[data-gradient-builder-presets] .gb-preset-grid` | `gap` | `var(--space-2)` |
| 42 | `gradient-builder.css:276` | `[data-gradient-builder-preset]` | `block-size` | `calc(32px * var(--scaling, 1))` |
| 43 | `gradient-builder.css:278` | `[data-gradient-builder-preset]` | `border` | `var(--border-width) solid var(--color-border-subtle)` |
| 44 | `gradient-builder.css:279` | `[data-gradient-builder-preset]` | `border-radius` | `var(--radius-md)` |
| 45 | `gradient-builder.css:283` | `[data-gradient-builder-preset]:hover` | `border-color` | `var(--color-border-strong)` |
| 46 | `gradient-builder.css:291` | `[data-gradient-builder-preset][data-selected]` | `border-color` | `var(--color-primary-border)` |
### 1.2 A través de un privado (8)
| 1 | `gradient-builder.css:129` | `[data-gradient-builder-stop]:focus-visible` | `box-shadow` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` |
### 1.2 A través de un privado (7)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `gradient-builder.css:89` | `[data-gradient-builder-preview]` | `block-size` | `var(--_gradient-builder-preview-height)` |
| 2 | `gradient-builder.css:90` | `[data-gradient-builder-preview]` | `border-radius` | `var(--_gradient-builder-radius)` |
| 3 | `gradient-builder.css:106` | `[data-gradient-builder-track]` | `block-size` | `var(--_gradient-builder-track-height)` |
| 4 | `gradient-builder.css:109` | `[data-gradient-builder-track]` | `border-radius` | `var(--_gradient-builder-radius)` |
| 5 | `gradient-builder.css:121` | `[data-gradient-builder-stop]` | `inline-size` | `var(--_gradient-builder-stop-size)` |
| 6 | `gradient-builder.css:122` | `[data-gradient-builder-stop]` | `block-size` | `var(--_gradient-builder-stop-size)` |
| 7 | `gradient-builder.css:124` | `[data-gradient-builder-stop]` | `border` | `calc(2px * var(--scaling, 1)) solid var(--_gradient-builder-stop-ring)` |
| 8 | `gradient-builder.css:181` | `[data-gradient-builder-angle-dial-label]` | `color` | `var(--_gradient-builder-label)` |
| 1 | `gradient-builder.css:76` | `[data-gradient-builder-preview]` | `block-size` | `var(--_gradient-builder-preview-height)` |
| 2 | `gradient-builder.css:77` | `[data-gradient-builder-preview]` | `border-radius` | `var(--_gradient-builder-radius)` |
| 3 | `gradient-builder.css:80` | `[data-gradient-builder-preview]` | `background-image` | `var(--_gradient-builder-checker)` |
| 4 | `gradient-builder.css:94` | `[data-gradient-builder-track]` | `block-size` | `var(--_gradient-builder-track-height)` |
| 5 | `gradient-builder.css:109` | `[data-gradient-builder-stop]` | `inline-size` | `var(--_gradient-builder-stop-size)` |
| 6 | `gradient-builder.css:110` | `[data-gradient-builder-stop]` | `block-size` | `var(--_gradient-builder-stop-size)` |
| 7 | `gradient-builder.css:165` | `[data-gradient-builder-angle-dial-label]` | `color` | `var(--_gradient-builder-label)` |
### 1.3 Literales (3)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `gradient-builder.css:35` | `[data-gradient-builder]` | `inline-size` | `100%` |
| 2 | `gradient-builder.css:204` | `[data-gradient-builder-stop-color-sliders]` | `inline-size` | `90%` |
| 3 | `gradient-builder.css:275` | `[data-gradient-builder-preset]` | `inline-size` | `100%` |
| 1 | `gradient-builder.css:37` | `[data-gradient-builder]` | `inline-size` | `100%` |
| 2 | `gradient-builder.css:188` | `[data-gradient-builder-stop-color-sliders]` | `inline-size` | `90%` |
| 3 | `gradient-builder.css:260` | `[data-gradient-builder-preset]` | `inline-size` | `100%` |
## 2. Sistema transversal (5) — informativo, fuera del ratio
## 2. Sistema transversal (4) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `gradient-builder.css:73` | `[data-gradient-builder][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `gradient-builder.css:245` | `[data-gradient-builder-stop-list-value]:hover:not(:disabled)` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
| 3 | `gradient-builder.css:251` | `[data-gradient-builder-stop-list-value]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 4 | `gradient-builder.css:286` | `[data-gradient-builder-preset]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 5 | `gradient-builder.css:292` | `[data-gradient-builder-preset][data-selected]` | `outline` | `var(--focus-ring-width) solid var(--color-primary-border)` |
| 1 | `gradient-builder.css:60` | `[data-gradient-builder][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `gradient-builder.css:230` | `[data-gradient-builder-stop-list-value]:hover:not(:disabled)` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
| 3 | `gradient-builder.css:236` | `[data-gradient-builder-stop-list-value]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 4 | `gradient-builder.css:271` | `[data-gradient-builder-preset]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_gradient-builder-preview-height` | 3 | `calc(128px * var(--scaling, 1))`, `calc(56px * var(--scaling, 1))`, `calc(88px * var(--scaling, 1))` | global | no |
| `--_gradient-builder-track-height` | 3 | `calc(24px * var(--scaling, 1))`, `calc(20px * var(--scaling, 1))`, `calc(28px * var(--scaling, 1))` | global | no |
| `--_gradient-builder-stop-size` | 3 | `calc(20px * var(--scaling, 1))`, `calc(18px * var(--scaling, 1))`, `calc(22px * var(--scaling, 1))` | global | no |
| `--_gradient-builder-radius` | 1 | `var(--radius-md)` | global | no |
| `--_gradient-builder-border` | 1 | `var(--color-border-default)` | global | no |
| `--_gradient-builder-stop-ring` | 1 | `var(--color-surface-default)` | global | no |
| `--_gradient-builder-label` | 1 | `var(--color-content-secondary)` | global | no |
| `--_gradient-builder-preview-height` | 1 | `var(--gradient-builder-preview-height)` | public | **sí** |
| `--_gradient-builder-track-height` | 1 | `var(--gradient-builder-track-height)` | public | **sí** |
| `--_gradient-builder-stop-size` | 1 | `var(--gradient-builder-stop-size)` | public | **sí** |
| `--_gradient-builder-radius` | 1 | `var(--gradient-builder-preview-radius)` | public | **sí** |
| `--_gradient-builder-border` | 1 | `var(--gradient-builder-border)` | public | **sí** |
| `--_gradient-builder-stop-ring` | 1 | `var(--gradient-builder-stop-ring)` | public | **sí** |
| `--_gradient-builder-label` | 1 | `var(--gradient-builder-label-fg)` | public | **sí** |
| `--_gradient-builder-checker-cell` | 1 | `var(--gradient-builder-checker-cell)` | public | **sí** |
| `--_gradient-builder-checker` | 1 | `linear-gradient(45deg, var(--gradient-builder-checker-color) 25%, transparent 25%), linear-gradient(-45deg, var(--gradient-builder-checker-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--gradient-builder-checker-color) 75%), linear-gradient(-45deg, transparent 75%, var(--gradient-builder-checker-color) 75%)` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_gradient-builder-stop-fill`.
## 4. Propuesta de corrección
- **Consume la capa compartida `picker-shell`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--gradient-builder-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2).
- **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` (51)
### 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
@ -123,72 +79,22 @@ no distingue lo que debería — se marca `⚠`.
| token (`--gradient-builder-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `stop-shadow` | `root` | ⚠ `0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 30%, transparent)` / `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` | 2 |
| `presets-gap` | `root` | `var(--space-2)` | 2 |
| `gap` | `root` | `var(--space-4)` | 1 |
| `bg` | `root` | `var(--color-surface-raised, var(--color-surface-default))` | 1 |
| `radius` | `root` | `var(--radius-lg)` | 1 |
| `gap-sm` | `root` | `var(--space-3)` | 1 |
| `gap-lg` | `root` | `var(--space-5)` | 1 |
| `preview-height-md` | `size:md` | `calc(128px * var(--scaling, 1))` | 1 |
| `preview-height-sm` | `size:sm` | `calc(56px * var(--scaling, 1))` | 1 |
| `preview-height-lg` | `size:lg` | `calc(88px * var(--scaling, 1))` | 1 |
| `preview-radius` | `root` | `var(--radius-md)` | 1 |
| `preview-bg` | `root` | `var(--color-surface-default)` | 1 |
| `preview-bg-image` | `root` | `var(--gb-checker)` | 1 |
| `track-height-md` | `size:md` | `calc(24px * var(--scaling, 1))` | 1 |
| `track-height-sm` | `size:sm` | `calc(20px * var(--scaling, 1))` | 1 |
| `track-height-lg` | `size:lg` | `calc(28px * var(--scaling, 1))` | 1 |
| `track-radius` | `root` | `var(--radius-md)` | 1 |
| `stop-width-md` | `size:md` | `calc(20px * var(--scaling, 1))` | 1 |
| `stop-width-sm` | `size:sm` | `calc(18px * var(--scaling, 1))` | 1 |
| `stop-width-lg` | `size:lg` | `calc(22px * var(--scaling, 1))` | 1 |
| `stop-height-md` | `size:md` | `calc(20px * var(--scaling, 1))` | 1 |
| `stop-height-sm` | `size:sm` | `calc(18px * var(--scaling, 1))` | 1 |
| `stop-height-lg` | `size:lg` | `calc(22px * var(--scaling, 1))` | 1 |
| `stop-radius` | `root` | `var(--radius-full)` | 1 |
| `stop-bg` | `root` | `var(--gb-stop-fill, var(--color-surface-default))` | 1 |
| `selected-stop-shadow` | `root` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-primary-solid), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 35%, transparent)` | 1 |
| `stop-actions-gap` | `root` | `var(--space-2)` | 1 |
| `angle-dial-gap` | `root` | `var(--space-2)` | 1 |
| `angle-dial-head-gap` | `root` | `var(--space-2)` | 1 |
| `angle-dial-label-font-size` | `root` | `var(--font-size-sm)` | 1 |
| `angle-dial-label-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `angle-dial-value-font-size` | `root` | `var(--font-size-sm)` | 1 |
| `angle-dial-value-fg` | `root` | `var(--color-content-primary)` | 1 |
| `stop-color-title-font-size` | `root` | `var(--font-size-sm)` | 1 |
| `stop-color-title-font-weight` | `root` | `var(--font-weight-medium, 500)` | 1 |
| `stop-color-title-fg` | `root` | `var(--color-content-primary)` | 1 |
| `stop-color-sliders-gap` | `root` | `var(--space-2)` | 1 |
| `stop-shadow` _(ya existe)_ | `root` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` | 1 |
| `stop-color-sliders-width` | `root` | `90%` | 1 |
| `stop-list-gap` | `root` | `var(--space-1)` | 1 |
| `stop-list-row-gap` | `root` | `var(--space-2)` | 1 |
| `stop-list-value-gap` | `root` | `var(--space-2)` | 1 |
| `stop-list-value-radius` | `root` | `var(--radius-md)` | 1 |
| `stop-list-value-bg` | `root` | `var(--color-surface-default)` | 1 |
| `stop-list-value-fg` | `root` | `var(--color-content-primary)` | 1 |
| `stop-list-value-font-size` | `root` | `var(--font-size-sm)` | 1 |
| `selected-stop-list-value-border` | `root` | `var(--color-primary-border)` | 1 |
| `stop-list-pos-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `preset-height` | `root` | `calc(32px * var(--scaling, 1))` | 1 |
| `preset-radius` | `root` | `var(--radius-md)` | 1 |
| `hover-preset-border` | `root` | `var(--color-border-strong)` | 1 |
| `selected-preset-border` | `root` | `var(--color-primary-border)` | 1 |
### 4.2 Sin nombre mecánico (12)
### 4.2 Sin nombre mecánico (9)
- **⚠ 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** — 2: `inline-size`.
- **⚠ decisión: `padding` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 4: `padding`.
- **⚠ decisión: `border` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 6: `border`.
- **ya alcanzable: el privado `--_gradient-builder-preview-height` deriva de un público (`var(--gradient-builder-preview-height)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 1: `block-size`.
- **ya alcanzable: el privado `--_gradient-builder-radius` deriva de un público (`var(--gradient-builder-preview-radius)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 1: `border-radius`.
- **ya alcanzable: el privado `--_gradient-builder-checker` deriva de un público (`linear-gradient(45deg, var(--gradient-builder-checker-color) 25%, transparent 25%), linear-gradient(-45deg, var(--gradient-builder-checker-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--gradient-builder-checker-color) 75%), linear-gradient(-45deg, transparent 75%, var(--gradient-builder-checker-color) 75%)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 1: `background-image`.
- **ya alcanzable: el privado `--_gradient-builder-track-height` deriva de un público (`var(--gradient-builder-track-height)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 1: `block-size`.
- **ya alcanzable: el privado `--_gradient-builder-stop-size` deriva de un público (`var(--gradient-builder-stop-size)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 2: `inline-size`, `block-size`.
- **ya alcanzable: el privado `--_gradient-builder-label` deriva de un público (`var(--gradient-builder-label-fg)`) — sin acción; el censo lo cuenta como no alcanzable por el límite del regex** — 1: `color`.
### 4.3 Avisos sobre los tokens propuestos (6)
### 4.3 Avisos sobre los tokens propuestos (1)
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable · el privado `--_gradient-builder-preview-height` debe pasar a leer este público (o desaparecer)** — `--gradient-builder-preview-height-md`, `--gradient-builder-preview-height-sm`, `--gradient-builder-preview-height-lg`
- **el privado `--_gradient-builder-radius` debe pasar a leer este público (o desaparecer)** — `--gradient-builder-preview-radius`, `--gradient-builder-track-radius`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable · el privado `--_gradient-builder-track-height` debe pasar a leer este público (o desaparecer)** — `--gradient-builder-track-height-md`, `--gradient-builder-track-height-sm`, `--gradient-builder-track-height-lg`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable · el privado `--_gradient-builder-stop-size` debe pasar a leer este público (o desaparecer)** — `--gradient-builder-stop-width-md`, `--gradient-builder-stop-width-sm`, `--gradient-builder-stop-width-lg`, `--gradient-builder-stop-height-md`, `--gradient-builder-stop-height-sm`, `--gradient-builder-stop-height-lg`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--gradient-builder-selected-stop-shadow`, `--gradient-builder-stop-shadow`, `--gradient-builder-preset-height`
- **el privado `--_gradient-builder-label` debe pasar a leer este público (o desaparecer)** — `--gradient-builder-angle-dial-label-fg`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--gradient-builder-stop-shadow`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
@ -243,4 +149,54 @@ fuerzan al bundle ni se marca desviación: el bundle no tiene esas coordenadas.
bloque picker (D-TH.4): todo su contrato sale con valores verbatim y cero
decisiones estructurales pendientes (sólo el damero, menor).
---
**EJECUTADO 2026-08-20 (piloto del eje).** Alcance **0 % → 80 %** · contrato
**0 → 72 claves públicas**. El 80 % es el suelo del regex: los 7 knobs que
quedan como `private` pasan por privados que **ahora derivan todos de un
público** (§3 lo confirma), el único `global` es el anillo de foco (sistema) y
los 3 literales son geometría de layout (`100%`, `90%`).
Qué se hizo, exactamente lo propuesto arriba:
1. Bloque `'gradient-builder'` en `recipes/base.ts` con el canon de dos piezas —
coordenadas por talla (`*-{sm,md,lg}`) + nombre resuelto (`host` = md,
`size:sm` / `size:lg`); la receta consume SÓLO el resuelto y los bloques
`[data-size]` del CSS desaparecen (los emite el TSC).
2. Renombrados los prefijos abreviados: `--gb-checker*` → `--_gradient-builder-checker*`,
`--gb-stop-fill` → `--_gradient-builder-stop-fill` (también en el wrapper,
`gradient-builder-stop.svelte`). El damero se parametriza por
`checker-color` + `checker-cell`; el patrón de cuatro gradientes queda
privado.
3. `padding` shorthand partido en `padding-block` / `padding-inline` (ejes
lógicos, recipe-contract §1).
4. Los 4 `*-font-size` consumen la **coordenada del bundle**
(`--size-sm-font-size`), no el primitivo — lo exigió el guard
`recipe-css-contract` («recipes consume the size bundle»), y es
value-preserving.
Verificación (artefactos):
- **Diff de computed = 0**: sonda Playwright antes/después, **6.612 valores**
comparados en 7 estados (reposo · 5 tallas · hover) sobre 34 nodos. El único
diff que apareció era la transición de hover de un `Button` compuesto
capturada a mitad — demostrado bajando la espera de asentamiento en el árbol
YA modificado y reproduciendo el valor interpolado (`oklab(… / 0.646795)`).
- **Centinela: 72/72 tokens alcanzan.** 49 desde el ámbito del componente en
reposo; 12 coordenadas de talla + 2 de preset seleccionado verificadas
forzando su estado; `hover-preset-border` con hover real; los 4 del editor de
color **sólo desde `:root`** (el panel viaja por portal — anotado en el README).
- **`preview-bg` + `checker-color`/`-cell` no mueven píxel hoy**: el wrapper
pinta el degradado con el shorthand `background` inline, que resetea
`background-image` y tapa el damero. El token es real; el damero no se ve.
Deuda anotada (corregirlo mueve píxel ⇒ decisión).
- Guards: `component:audit` PASS · `eidos-lint` 0 invalid · suite eidos sin
rojos nuevos (queda el conocido `skin-media-player`) · `rtl:check` 0 ·
`docs:check` 0 · `check` sin errores en los ficheros tocados · captura 2×
revisada.
⚠ **La ficha dice «Eje `size`: no»** porque el detector busca `data-size` en el
CSS de la receta y ahora la cascada por talla vive en el generado. El componente
SÍ tiene eje `size` — límite del instrumento, no del componente.
<!-- veredicto:end -->

@ -49,25 +49,25 @@ parts explicitly to customise the layout:
## Parts
| Part | Composes | Notes |
| ------------ | --------------------- | ----------------------------------------------------------------- |
| `Provider` | soma Provider | The framed card. Sets a size/variant visual context. |
| `Preview` | — | Decorative live gradient bar (checker behind for alpha). |
| `Track` | soma Track | Paints a left→right ramp of the live stops; hosts the Stops. |
| `Stop` | soma Stop | One handle, positioned `left:%` and filled with the stop color. |
| `StopColor` | **ColorPicker** | Inline Area + hue/alpha sliders + hex, bound to the selected stop.|
| `AngleDial` | **Slider** | 0–360° with a label + degree readout (linear / conic only). |
| `KindSwitch` | **ToggleGroup** | linear / radial / conic. |
| `AddStop` | **Button** + `Plus` | Adds a stop at 0.5. |
| Part | Composes | Notes |
| ------------ | ------------------- | ------------------------------------------------------------------ |
| `Provider` | soma Provider | The framed card. Sets a size/variant visual context. |
| `Preview` | — | Decorative live gradient bar (checker behind for alpha). |
| `Track` | soma Track | Paints a left→right ramp of the live stops; hosts the Stops. |
| `Stop` | soma Stop | One handle, positioned `left:%` and filled with the stop color. |
| `StopColor` | **ColorPicker** | Inline Area + hue/alpha sliders + hex, bound to the selected stop. |
| `AngleDial` | **Slider** | 0–360° with a label + degree readout (linear / conic only). |
| `KindSwitch` | **ToggleGroup** | linear / radial / conic. |
| `AddStop` | **Button** + `Plus` | Adds a stop at 0.5. |
## Props
| Prop | Type | Default | Description |
| --------- | ----------------------------------- | ----------- | ------------------------------------ |
| `value` | `Gradient` | 2-stop | The gradient being edited. Bindable. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Editor + child control size. |
| `variant` | `'surface' \| 'outline' \| 'ghost'` | `'surface'` | The card framing. |
| `disabled`| `boolean` | `false` | Disables all editing. |
| Prop | Type | Default | Description |
| ---------- | ----------------------------------- | ----------- | ------------------------------------ |
| `value` | `Gradient` | 2-stop | The gradient being edited. Bindable. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Editor + child control size. |
| `variant` | `'surface' \| 'outline' \| 'ghost'` | `'surface'` | The card framing. |
| `disabled` | `boolean` | `false` | Disables all editing. |
## Stop ↔ ColorPicker bridge
@ -80,24 +80,58 @@ selected stop reads as one surface. The seed effect tracks only `selectedIndex`
## Recipe
Self-contained `--_gradient-builder-*` tokens — private by choice (graduated
from the dev track 2026-07-10; the R-rules pass with the private set, and a
public `--gradient-builder-*` API waits for real theming demand, see Gaps).
The stop handle centers with the `translate` property (not `transform`) so a
sema press-squeeze can't fight the centering. Selectors target only
morfo-promised attrs + the eidos-only composition wrappers stamped by the
wrappers.
### Talla y tema
Todo knob de apariencia pasa por un token **público** `--gradient-builder-*`
(`lib/recipes/base.ts`, eje theme-reach 2026-08-20): un tema mueve el editor sin
tocar el sistema. Los ejes dimensionales siguen el canon de dos piezas —
**coordenada por talla** + **un nombre resuelto**, que es lo único que consume
el CSS:
| Eje | Coordenadas | Nombre resuelto |
| ------------------------ | ----------------------------------- | ---------------------------------- |
| Alto del preview | `preview-height-{sm,md,lg}` | `preview-height` |
| Alto del raíl | `track-height-{sm,md,lg}` | `track-height` |
| Diámetro de la parada | `stop-size-{sm,md,lg}` | `stop-size` |
| Separación de la tarjeta | `gap-{sm,md,lg}` | `gap` |
| Relleno de la tarjeta | `padding-{block,inline}-{sm,md,lg}` | `padding-block` · `padding-inline` |
La geometría es **propia** (px × `--scaling`), no del bundle `--size-{k}-*`: un
raíl de edición no tiene coordenada de control-height. La tipografía sí consume
el bundle (`--size-sm-font-size`), como exige theming §5 y guarda
`recipe-css-contract`.
Cromo, damero, parada, dial, lista de paradas y presets tienen su token por
parte y estado (`preview-*`, `track-*`, `stop-*`, `checker-color` / `-cell`,
`angle-dial-*`, `stop-list-*`, `preset-*`, con `hover-` / `selected-` delante
donde manda el estado). Los `--_gradient-builder-*` que quedan son nombres
internos y **todos derivan de un público**.
Dos notas medidas al tokenizar (2026-08-20, centinela por token):
- Los tokens del editor de color (`stop-color-*`) se emiten en `:root` porque el
panel viaja por **portal**: un override en el ámbito del componente no lo
alcanza.
- `preview-bg` y `checker-color` / `-cell` tienen consumidor real en la receta,
pero el wrapper pinta el degradado con el **shorthand `background` inline**,
que resetea `background-image`: el damero de transparencia no llega a verse
bajo el preview. Deuda anotada — corregirlo mueve píxel, así que es decisión.
El asa de la parada se centra con la propiedad `translate` (no `transform`) para
que un press-squeeze de sema no pelee con el centrado. Los selectores targetean
sólo attrs prometidos por morfo + los envoltorios de composición eidos-only que
estampan los wrappers.
## Comparativa
| Capacidad | UIX | Figma | Photoshop | Pickers web / gradient.style |
| --- | --- | --- | --- | --- |
| Modelo compartido con el token engine (OKLCH) | **✓** (`$libs/gradient`) | No (RGBA interno) | No | No (string CSS) |
| Stops editables por teclado (`role=slider`) | **✓** | No | No | No |
| Geometría explícita per-kind (angle / shape+size+at / from+at) | ✓ | Matriz afín | Matriz | Parcial |
| Alpha per-stop | ✓ | ✓ | ✓ | ✓ |
| Round-trip sin pérdida a CSS | ✓ | Export muerto | Export muerto | ✓ (string) |
| Semántica perceptiva del gesto (pack handle+commit) | ✓ | — | — | — |
| Capacidad | UIX | Figma | Photoshop | Pickers web / gradient.style |
| -------------------------------------------------------------- | ------------------------ | ----------------- | ------------- | ---------------------------- |
| Modelo compartido con el token engine (OKLCH) | **✓** (`$libs/gradient`) | No (RGBA interno) | No | No (string CSS) |
| Stops editables por teclado (`role=slider`) | **✓** | No | No | No |
| Geometría explícita per-kind (angle / shape+size+at / from+at) | ✓ | Matriz afín | Matriz | Parcial |
| Alpha per-stop | ✓ | ✓ | ✓ | ✓ |
| Round-trip sin pérdida a CSS | ✓ | Export muerto | Export muerto | ✓ (string) |
| Semántica perceptiva del gesto (pack handle+commit) | ✓ | — | — | — |
Referencias: research competitivo en
[`CONTINUE.md`](../../../soma/components/gradient-builder/CONTINUE.md) (no
@ -124,13 +158,13 @@ re-derivar) · [APG Slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) ·
## Gaps
| Gap | Disposición | Detalle |
| --- | --- | --- |
| Mesh editor (puntos 2D) | **diferir** (fast-follow declarado) | El eje ya publica `aurora()`; el editor de mesh reutilizará el patrón stop=slider. Diamond descartado en el research. |
| Midpoints (color hints) + `parseGradient` (paste-to-edit) | **diferir** (Phase-2.5 declarada) | Con el research ya hecho en CONTINUE.md. |
| Gizmo on-canvas (superficie dual barra + lienzo) | **diferir** | v1 = la barra precisa; el gizmo espera el caso mesh. |
| API pública de tokens (`--gradient-builder-*` en recipes/base.ts) | **diferir** | Los privados pasan las R-rules; registrar cuando haya demanda real de theming del editor. |
| Demo v2 (D-1.2/D-3.1 de la máquina) | **implementar** | Pase de demos de esta iniciativa (ver sesión S9). |
| Gap | Disposición | Detalle |
| ----------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Mesh editor (puntos 2D) | **diferir** (fast-follow declarado) | El eje ya publica `aurora()`; el editor de mesh reutilizará el patrón stop=slider. Diamond descartado en el research. |
| Midpoints (color hints) + `parseGradient` (paste-to-edit) | **diferir** (Phase-2.5 declarada) | Con el research ya hecho en CONTINUE.md. |
| Gizmo on-canvas (superficie dual barra + lienzo) | **diferir** | v1 = la barra precisa; el gizmo espera el caso mesh. |
| API pública de tokens (`--gradient-builder-*` en recipes/base.ts) | **diferir** | Los privados pasan las R-rules; registrar cuando haya demanda real de theming del editor. |
| Demo v2 (D-1.2/D-3.1 de la máquina) | **implementar** | Pase de demos de esta iniciativa (ver sesión S9). |
## Notes

@ -15,5 +15,9 @@
</script>
<!-- The handle IS the slider thumb (soma owns drag + keyboard). Eidos positions
it along the rail and paints it with the stop's color via `--gb-stop-fill`. -->
<GradientBuilder.Stop {...rest} {index} style="left: {pos}%; --gb-stop-fill: {fill};" />
it along the rail and paints it with the stop's color via `--_gradient-builder-stop-fill`. -->
<GradientBuilder.Stop
{...rest}
{index}
style="left: {pos}%; --_gradient-builder-stop-fill: {fill};"
/>

@ -1,9 +1,11 @@
/* GradientBuilder — visual layer.
*
* Self-contained recipe (gradient-builder is on the active dev track, so it
* does NOT register tokens in recipes/base.ts yet). Internal `--_gradient-builder-*`
* tokens derive from global tokens; the child controls (Slider / ToggleGroup /
* Button / ColorPicker) bring their own recipes.
* Every knob is a PUBLIC token of the component (`recipes/base.ts`): a theme
* moves the editor without touching the system. The dimensional axes follow the
* two-tier canon — per-size coordinates + ONE resolved name the CSS consumes —
* and the remaining `--_gradient-builder-*` privates all derive from a public.
* The child controls (Slider / ToggleGroup / Button / ColorPicker) bring their
* own recipes.
*
* Scaling/density: every gap + padding uses `--space-*` (density- AND
* scaling-aware); every fixed dimension is `calc(px * var(--scaling, 1))` so the
@ -12,51 +14,36 @@
*/
[data-gradient-builder] {
--_gradient-builder-preview-height: calc(128px * var(--scaling, 1));
--_gradient-builder-track-height: calc(24px * var(--scaling, 1));
--_gradient-builder-stop-size: calc(20px * var(--scaling, 1));
--_gradient-builder-radius: var(--radius-md);
--_gradient-builder-border: var(--color-border-default);
--_gradient-builder-stop-ring: var(--color-surface-default);
--_gradient-builder-label: var(--color-content-secondary);
--_gradient-builder-preview-height: var(--gradient-builder-preview-height);
--_gradient-builder-track-height: var(--gradient-builder-track-height);
--_gradient-builder-stop-size: var(--gradient-builder-stop-size);
--_gradient-builder-radius: var(--gradient-builder-preview-radius);
--_gradient-builder-border: var(--gradient-builder-border);
--_gradient-builder-stop-ring: var(--gradient-builder-stop-ring);
--_gradient-builder-label: var(--gradient-builder-label-fg);
/* Transparency checker revealed under the preview where stops are
translucent. Kept subtle so it doesn't fight the gradient in dark mode. */
--gb-checker-cell: calc(7px * var(--scaling, 1));
--gb-checker:
linear-gradient(45deg, var(--color-border-subtle) 25%, transparent 25%),
linear-gradient(-45deg, var(--color-border-subtle) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--color-border-subtle) 75%),
linear-gradient(-45deg, transparent 75%, var(--color-border-subtle) 75%);
--_gradient-builder-checker-cell: var(--gradient-builder-checker-cell);
--_gradient-builder-checker:
linear-gradient(45deg, var(--gradient-builder-checker-color) 25%, transparent 25%),
linear-gradient(-45deg, var(--gradient-builder-checker-color) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--gradient-builder-checker-color) 75%),
linear-gradient(-45deg, transparent 75%, var(--gradient-builder-checker-color) 75%);
display: flex;
flex-direction: column;
gap: var(--space-4);
gap: var(--gradient-builder-gap);
inline-size: 100%;
min-inline-size: 0;
box-sizing: border-box;
padding: var(--space-4);
padding-block: var(--gradient-builder-padding-block);
padding-inline: var(--gradient-builder-padding-inline);
/* `raised` + a defined border so the editor reads as a distinct panel in
both light and dark (surface-default blended into the page background). */
background: var(--color-surface-raised, var(--color-surface-default));
border: var(--border-width) solid var(--color-border-default);
border-radius: var(--radius-lg);
}
[data-gradient-builder][data-size='sm'] {
--_gradient-builder-preview-height: calc(56px * var(--scaling, 1));
--_gradient-builder-track-height: calc(20px * var(--scaling, 1));
--_gradient-builder-stop-size: calc(18px * var(--scaling, 1));
gap: var(--space-3);
padding: var(--space-3);
}
[data-gradient-builder][data-size='lg'] {
--_gradient-builder-preview-height: calc(88px * var(--scaling, 1));
--_gradient-builder-track-height: calc(28px * var(--scaling, 1));
--_gradient-builder-stop-size: calc(22px * var(--scaling, 1));
gap: var(--space-5);
padding: var(--space-5);
background: var(--gradient-builder-bg);
border: var(--gradient-builder-border-width) solid var(--_gradient-builder-border);
border-radius: var(--gradient-builder-radius);
}
/* Variant — the editor card framing. */
@ -88,15 +75,16 @@
position: relative;
block-size: var(--_gradient-builder-preview-height);
border-radius: var(--_gradient-builder-radius);
border: var(--border-width) solid var(--color-border-subtle);
background-color: var(--color-surface-default);
background-image: var(--gb-checker);
background-size: calc(var(--gb-checker-cell) * 2) calc(var(--gb-checker-cell) * 2);
border: var(--gradient-builder-border-width) solid var(--gradient-builder-preview-border);
background-color: var(--gradient-builder-preview-bg);
background-image: var(--_gradient-builder-checker);
background-size: calc(var(--_gradient-builder-checker-cell) * 2)
calc(var(--_gradient-builder-checker-cell) * 2);
background-position:
0 0,
0 var(--gb-checker-cell),
var(--gb-checker-cell) calc(-1 * var(--gb-checker-cell)),
calc(-1 * var(--gb-checker-cell)) 0;
0 var(--_gradient-builder-checker-cell),
var(--_gradient-builder-checker-cell) calc(-1 * var(--_gradient-builder-checker-cell)),
calc(-1 * var(--_gradient-builder-checker-cell)) 0;
isolation: isolate;
}
@ -106,8 +94,8 @@
block-size: var(--_gradient-builder-track-height);
/* Room so the edge handles (left:0 / 100%, centered by translate) clear the card. */
margin-inline: calc(var(--_gradient-builder-stop-size) / 2);
border-radius: var(--_gradient-builder-radius);
border: var(--border-width) solid var(--color-border-subtle);
border-radius: var(--gradient-builder-track-radius);
border: var(--gradient-builder-border-width) solid var(--gradient-builder-track-border);
touch-action: none;
}
@ -120,12 +108,10 @@
translate: -50% -50%;
inline-size: var(--_gradient-builder-stop-size);
block-size: var(--_gradient-builder-stop-size);
border-radius: var(--radius-full);
border: calc(2px * var(--scaling, 1)) solid var(--_gradient-builder-stop-ring);
background: var(--gb-stop-fill, var(--color-surface-default));
box-shadow:
0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent),
0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 30%, transparent);
border-radius: var(--gradient-builder-stop-radius);
border: var(--gradient-builder-stop-ring-width) solid var(--_gradient-builder-stop-ring);
background: var(--_gradient-builder-stop-fill, var(--gradient-builder-stop-bg));
box-shadow: var(--gradient-builder-stop-shadow);
cursor: grab;
touch-action: none;
outline: none;
@ -136,9 +122,7 @@
}
[data-gradient-builder-stop][data-selected] {
box-shadow:
0 0 0 calc(2px * var(--scaling, 1)) var(--color-primary-solid),
0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 35%, transparent);
box-shadow: var(--gradient-builder-selected-stop-shadow);
}
[data-gradient-builder-stop]:focus-visible {
@ -151,7 +135,7 @@
[data-gradient-builder-stop-actions] {
display: flex;
align-items: center;
gap: var(--space-2);
gap: var(--gradient-builder-stop-actions-gap);
}
/* ── Kind switch — full-width segmented control ──────────────────────────── */
@ -168,22 +152,22 @@
[data-gradient-builder-angle-dial] {
display: flex;
flex-direction: column;
gap: var(--space-2);
gap: var(--gradient-builder-angle-dial-gap);
}
[data-gradient-builder-angle-dial-head] {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-2);
gap: var(--gradient-builder-angle-dial-head-gap);
}
[data-gradient-builder-angle-dial-label] {
font-size: var(--font-size-sm);
font-size: var(--gradient-builder-label-font-size);
color: var(--_gradient-builder-label);
}
[data-gradient-builder-angle-dial-value] {
font-size: var(--font-size-sm);
font-size: var(--gradient-builder-value-font-size);
font-variant-numeric: tabular-nums;
color: var(--color-content-primary);
color: var(--gradient-builder-value-fg);
}
/* ── Stop color editor — a popover anchored to the rail, opened by a stop click ── */
@ -192,14 +176,14 @@
display: block;
}
[data-gradient-builder-stop-color-title] {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium, 500);
color: var(--color-content-primary);
font-size: var(--gradient-builder-stop-color-title-font-size);
font-weight: var(--gradient-builder-stop-color-title-font-weight);
color: var(--gradient-builder-stop-color-title-fg);
}
[data-gradient-builder-stop-color-sliders] {
display: flex;
flex-direction: column;
gap: var(--space-2);
gap: var(--gradient-builder-stop-color-sliders-gap);
/* Rails span 90% of the panel, centred; taller track than the md default. */
inline-size: 90%;
margin-inline: auto;
@ -217,27 +201,28 @@
[data-gradient-builder-stop-list] {
display: flex;
flex-direction: column;
gap: var(--space-1);
gap: var(--gradient-builder-stop-list-gap);
}
[data-gradient-builder-stop-list-row] {
display: flex;
align-items: center;
gap: var(--space-2);
gap: var(--gradient-builder-stop-list-row-gap);
}
/* The value button reads like a ColorField value: swatch + hex + position. */
[data-gradient-builder-stop-list-value] {
display: flex;
flex: 1 1 auto;
align-items: center;
gap: var(--space-2);
gap: var(--gradient-builder-stop-list-value-gap);
min-inline-size: 0;
padding: var(--space-1) var(--space-2);
border: var(--border-width) solid var(--color-border-subtle);
border-radius: var(--radius-md);
background: var(--color-surface-default);
color: var(--color-content-primary);
padding-block: var(--gradient-builder-stop-list-value-padding-block);
padding-inline: var(--gradient-builder-stop-list-value-padding-inline);
border: var(--gradient-builder-border-width) solid var(--gradient-builder-stop-list-value-border);
border-radius: var(--gradient-builder-stop-list-value-radius);
background: var(--gradient-builder-stop-list-value-bg);
color: var(--gradient-builder-stop-list-value-fg);
font: inherit;
font-size: var(--font-size-sm);
font-size: var(--gradient-builder-stop-list-value-font-size);
text-align: start;
cursor: pointer;
}
@ -245,7 +230,7 @@
background-image: linear-gradient(var(--state-hover), var(--state-hover));
}
[data-gradient-builder-stop-list-value][data-selected] {
border-color: var(--color-primary-border);
border-color: var(--gradient-builder-selected-stop-list-value-border);
}
[data-gradient-builder-stop-list-value]:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
@ -256,7 +241,7 @@
}
[data-gradient-builder-stop-list-pos] {
margin-inline-start: auto;
color: var(--color-content-secondary);
color: var(--gradient-builder-stop-list-pos-fg);
font-variant-numeric: tabular-nums;
}
@ -264,23 +249,23 @@
[data-gradient-builder-presets] {
display: flex;
flex-direction: column;
gap: var(--space-2);
gap: var(--gradient-builder-presets-gap);
}
[data-gradient-builder-presets] .gb-preset-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: var(--space-2);
gap: var(--gradient-builder-preset-grid-gap);
}
[data-gradient-builder-preset] {
inline-size: 100%;
block-size: calc(32px * var(--scaling, 1));
block-size: var(--gradient-builder-preset-height);
padding: 0;
border: var(--border-width) solid var(--color-border-subtle);
border-radius: var(--radius-md);
border: var(--gradient-builder-border-width) solid var(--gradient-builder-preset-border);
border-radius: var(--gradient-builder-preset-radius);
cursor: pointer;
}
[data-gradient-builder-preset]:hover {
border-color: var(--color-border-strong);
border-color: var(--gradient-builder-hover-preset-border);
}
[data-gradient-builder-preset]:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
@ -288,7 +273,7 @@
}
/* The selected saved preset — highlighted; it flips the action button to Delete. */
[data-gradient-builder-preset][data-selected] {
border-color: var(--color-primary-border);
outline: var(--focus-ring-width) solid var(--color-primary-border);
border-color: var(--gradient-builder-selected-preset-border);
outline: var(--focus-ring-width) solid var(--gradient-builder-selected-preset-outline);
outline-offset: 2px;
}

@ -1657,6 +1657,72 @@
--button-loss-solid-hover: var(--color-loss-solid-hover);
--button-loss-text: var(--color-loss-text);
--button-loss-contrast: var(--color-loss-contrast);
--gradient-builder-preview-height-sm: calc(56px * var(--scaling, 1));
--gradient-builder-preview-height-md: calc(128px * var(--scaling, 1));
--gradient-builder-preview-height-lg: calc(88px * var(--scaling, 1));
--gradient-builder-track-height-sm: calc(20px * var(--scaling, 1));
--gradient-builder-track-height-md: calc(24px * var(--scaling, 1));
--gradient-builder-track-height-lg: calc(28px * var(--scaling, 1));
--gradient-builder-stop-size-sm: calc(18px * var(--scaling, 1));
--gradient-builder-stop-size-md: calc(20px * var(--scaling, 1));
--gradient-builder-stop-size-lg: calc(22px * var(--scaling, 1));
--gradient-builder-gap-sm: var(--space-3);
--gradient-builder-gap-md: var(--space-4);
--gradient-builder-gap-lg: var(--space-5);
--gradient-builder-padding-block-sm: var(--space-3);
--gradient-builder-padding-block-md: var(--space-4);
--gradient-builder-padding-block-lg: var(--space-5);
--gradient-builder-padding-inline-sm: var(--space-3);
--gradient-builder-padding-inline-md: var(--space-4);
--gradient-builder-padding-inline-lg: var(--space-5);
--gradient-builder-radius: var(--radius-lg);
--gradient-builder-bg: var(--color-surface-raised, var(--color-surface-default));
--gradient-builder-border: var(--color-border-default);
--gradient-builder-border-width: var(--border-width);
--gradient-builder-preview-radius: var(--radius-md);
--gradient-builder-preview-bg: var(--color-surface-default);
--gradient-builder-preview-border: var(--color-border-subtle);
--gradient-builder-track-radius: var(--radius-md);
--gradient-builder-track-border: var(--color-border-subtle);
--gradient-builder-checker-color: var(--color-border-subtle);
--gradient-builder-checker-cell: calc(7px * var(--scaling, 1));
--gradient-builder-stop-radius: var(--radius-full);
--gradient-builder-stop-bg: var(--color-surface-default);
--gradient-builder-stop-ring: var(--color-surface-default);
--gradient-builder-stop-ring-width: calc(2px * var(--scaling, 1));
--gradient-builder-stop-shadow: 0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 30%, transparent);
--gradient-builder-selected-stop-shadow: 0 0 0 calc(2px * var(--scaling, 1)) var(--color-primary-solid), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 35%, transparent);
--gradient-builder-stop-actions-gap: var(--space-2);
--gradient-builder-angle-dial-gap: var(--space-2);
--gradient-builder-angle-dial-head-gap: var(--space-2);
--gradient-builder-label-font-size: var(--size-sm-font-size);
--gradient-builder-label-fg: var(--color-content-secondary);
--gradient-builder-value-font-size: var(--size-sm-font-size);
--gradient-builder-value-fg: var(--color-content-primary);
--gradient-builder-stop-color-title-font-size: var(--size-sm-font-size);
--gradient-builder-stop-color-title-font-weight: var(--font-weight-medium, 500);
--gradient-builder-stop-color-title-fg: var(--color-content-primary);
--gradient-builder-stop-color-sliders-gap: var(--space-2);
--gradient-builder-stop-list-gap: var(--space-1);
--gradient-builder-stop-list-row-gap: var(--space-2);
--gradient-builder-stop-list-value-gap: var(--space-2);
--gradient-builder-stop-list-value-padding-block: var(--space-1);
--gradient-builder-stop-list-value-padding-inline: var(--space-2);
--gradient-builder-stop-list-value-radius: var(--radius-md);
--gradient-builder-stop-list-value-bg: var(--color-surface-default);
--gradient-builder-stop-list-value-fg: var(--color-content-primary);
--gradient-builder-stop-list-value-border: var(--color-border-subtle);
--gradient-builder-stop-list-value-font-size: var(--size-sm-font-size);
--gradient-builder-selected-stop-list-value-border: var(--color-primary-border);
--gradient-builder-stop-list-pos-fg: var(--color-content-secondary);
--gradient-builder-presets-gap: var(--space-2);
--gradient-builder-preset-grid-gap: var(--space-2);
--gradient-builder-preset-height: calc(32px * var(--scaling, 1));
--gradient-builder-preset-radius: var(--radius-md);
--gradient-builder-preset-border: var(--color-border-subtle);
--gradient-builder-hover-preset-border: var(--color-border-strong);
--gradient-builder-selected-preset-border: var(--color-primary-border);
--gradient-builder-selected-preset-outline: var(--color-primary-border);
--calendar-gap: var(--space-3);
--calendar-padding-xs: var(--space-2);
--calendar-padding-sm: var(--space-3);
@ -4866,6 +4932,33 @@
--_grid-list-palette-solid: var(--palette-solid, var(--color-primary-solid));
}
[data-gradient-builder] {
--gradient-builder-preview-height: var(--gradient-builder-preview-height-md);
--gradient-builder-track-height: var(--gradient-builder-track-height-md);
--gradient-builder-stop-size: var(--gradient-builder-stop-size-md);
--gradient-builder-gap: var(--gradient-builder-gap-md);
--gradient-builder-padding-block: var(--gradient-builder-padding-block-md);
--gradient-builder-padding-inline: var(--gradient-builder-padding-inline-md);
}
[data-gradient-builder][data-size='sm'] {
--gradient-builder-preview-height: var(--gradient-builder-preview-height-sm);
--gradient-builder-track-height: var(--gradient-builder-track-height-sm);
--gradient-builder-stop-size: var(--gradient-builder-stop-size-sm);
--gradient-builder-gap: var(--gradient-builder-gap-sm);
--gradient-builder-padding-block: var(--gradient-builder-padding-block-sm);
--gradient-builder-padding-inline: var(--gradient-builder-padding-inline-sm);
}
[data-gradient-builder][data-size='lg'] {
--gradient-builder-preview-height: var(--gradient-builder-preview-height-lg);
--gradient-builder-track-height: var(--gradient-builder-track-height-lg);
--gradient-builder-stop-size: var(--gradient-builder-stop-size-lg);
--gradient-builder-gap: var(--gradient-builder-gap-lg);
--gradient-builder-padding-block: var(--gradient-builder-padding-block-lg);
--gradient-builder-padding-inline: var(--gradient-builder-padding-inline-lg);
}
[data-clipboard] {
--_clipboard-palette-solid: var(--color-primary-solid);
--_clipboard-palette-element: var(--color-primary-element);

@ -1988,6 +1988,134 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-element': { value: 'var(--color-primary-element)', scope: 'host' }
},
// ─────────────────────────────────────────────────────────────────────
// GradientBuilder — the gradient editor's own chrome. Sized on its OWN
// geometry (px × `--scaling`), not on the `--size-{k}-*` bundle: an editor
// rail has no control-height coordinate. The two-tier shape is the canon
// (theme-reach F2): per-size coordinates at a stable scope + ONE resolved
// name per axis (`host` = md, `size:` overrides) which is the only thing the
// recipe CSS consumes.
'gradient-builder': {
'preview-height-sm': 'calc(56px * var(--scaling, 1))',
'preview-height-md': 'calc(128px * var(--scaling, 1))',
'preview-height-lg': 'calc(88px * var(--scaling, 1))',
'track-height-sm': 'calc(20px * var(--scaling, 1))',
'track-height-md': 'calc(24px * var(--scaling, 1))',
'track-height-lg': 'calc(28px * var(--scaling, 1))',
'stop-size-sm': 'calc(18px * var(--scaling, 1))',
'stop-size-md': 'calc(20px * var(--scaling, 1))',
'stop-size-lg': 'calc(22px * var(--scaling, 1))',
'gap-sm': 'var(--space-3)',
'gap-md': 'var(--space-4)',
'gap-lg': 'var(--space-5)',
'padding-block-sm': 'var(--space-3)',
'padding-block-md': 'var(--space-4)',
'padding-block-lg': 'var(--space-5)',
'padding-inline-sm': 'var(--space-3)',
'padding-inline-md': 'var(--space-4)',
'padding-inline-lg': 'var(--space-5)',
'preview-height': {
declarations: [
{ value: 'var(--gradient-builder-preview-height-md)', scope: 'host' },
{ value: 'var(--gradient-builder-preview-height-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-preview-height-lg)', scope: 'size:lg' }
]
},
'track-height': {
declarations: [
{ value: 'var(--gradient-builder-track-height-md)', scope: 'host' },
{ value: 'var(--gradient-builder-track-height-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-track-height-lg)', scope: 'size:lg' }
]
},
'stop-size': {
declarations: [
{ value: 'var(--gradient-builder-stop-size-md)', scope: 'host' },
{ value: 'var(--gradient-builder-stop-size-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-stop-size-lg)', scope: 'size:lg' }
]
},
gap: {
declarations: [
{ value: 'var(--gradient-builder-gap-md)', scope: 'host' },
{ value: 'var(--gradient-builder-gap-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-gap-lg)', scope: 'size:lg' }
]
},
'padding-block': {
declarations: [
{ value: 'var(--gradient-builder-padding-block-md)', scope: 'host' },
{ value: 'var(--gradient-builder-padding-block-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-padding-block-lg)', scope: 'size:lg' }
]
},
'padding-inline': {
declarations: [
{ value: 'var(--gradient-builder-padding-inline-md)', scope: 'host' },
{ value: 'var(--gradient-builder-padding-inline-sm)', scope: 'size:sm' },
{ value: 'var(--gradient-builder-padding-inline-lg)', scope: 'size:lg' }
]
},
// The card
radius: 'var(--radius-lg)',
bg: 'var(--color-surface-raised, var(--color-surface-default))',
border: 'var(--color-border-default)',
'border-width': 'var(--border-width)',
// Preview + track
'preview-radius': 'var(--radius-md)',
'preview-bg': 'var(--color-surface-default)',
'preview-border': 'var(--color-border-subtle)',
'track-radius': 'var(--radius-md)',
'track-border': 'var(--color-border-subtle)',
// The transparency checker: the CELL COLOUR is the knob; the four-gradient
// pattern that composes it stays private to the recipe.
'checker-color': 'var(--color-border-subtle)',
'checker-cell': 'calc(7px * var(--scaling, 1))',
// Stop handle
'stop-radius': 'var(--radius-full)',
'stop-bg': 'var(--color-surface-default)',
'stop-ring': 'var(--color-surface-default)',
'stop-ring-width': 'calc(2px * var(--scaling, 1))',
'stop-shadow':
'0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 30%, transparent)',
'selected-stop-shadow':
'0 0 0 calc(2px * var(--scaling, 1)) var(--color-primary-solid), 0 1px 3px color-mix(in srgb, var(--color-neutral-contrast) 35%, transparent)',
// Rows + angle dial
'stop-actions-gap': 'var(--space-2)',
'angle-dial-gap': 'var(--space-2)',
'angle-dial-head-gap': 'var(--space-2)',
'label-font-size': 'var(--size-sm-font-size)',
'label-fg': 'var(--color-content-secondary)',
'value-font-size': 'var(--size-sm-font-size)',
'value-fg': 'var(--color-content-primary)',
// Stop color editor
'stop-color-title-font-size': 'var(--size-sm-font-size)',
'stop-color-title-font-weight': 'var(--font-weight-medium, 500)',
'stop-color-title-fg': 'var(--color-content-primary)',
'stop-color-sliders-gap': 'var(--space-2)',
// Stop list
'stop-list-gap': 'var(--space-1)',
'stop-list-row-gap': 'var(--space-2)',
'stop-list-value-gap': 'var(--space-2)',
'stop-list-value-padding-block': 'var(--space-1)',
'stop-list-value-padding-inline': 'var(--space-2)',
'stop-list-value-radius': 'var(--radius-md)',
'stop-list-value-bg': 'var(--color-surface-default)',
'stop-list-value-fg': 'var(--color-content-primary)',
'stop-list-value-border': 'var(--color-border-subtle)',
'stop-list-value-font-size': 'var(--size-sm-font-size)',
'selected-stop-list-value-border': 'var(--color-primary-border)',
'stop-list-pos-fg': 'var(--color-content-secondary)',
// Presets
'presets-gap': 'var(--space-2)',
'preset-grid-gap': 'var(--space-2)',
'preset-height': 'calc(32px * var(--scaling, 1))',
'preset-radius': 'var(--radius-md)',
'preset-border': 'var(--color-border-subtle)',
'hover-preset-border': 'var(--color-border-strong)',
'selected-preset-border': 'var(--color-primary-border)',
'selected-preset-outline': 'var(--color-primary-border)'
},
clipboard: {
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-element': { value: 'var(--color-primary-element)', scope: 'host' }

Loading…
Cancel
Save

Powered by TurnKey Linux.