feat(carousel): temable — 0 % → 77 %

Séptimo del bloque F2-A.

  censo    0 % → 77 % · global 42 %
  computed 2.842 valores en 7 estados: 0 diffs

**Los botones prev/next no se acuñan aquí** — componen `<IconButton>`, así que
su cromo es el del Button y un tema los viste por ahí. Esta receta sólo decide
DÓNDE se colocan (`trigger-offset`). El veredicto avisaba de comprobarlo antes
de copiar filas de trigger, y estaba en lo cierto.

**El ⚠ del informe eran DOS gaps distintos**, no un knob con dos valores: el
`gap` en columna de la raíz y el `item-gap` ENTRE DIAPOSITIVAS. Y el segundo
sólo se aparta del default en `xs`, así que su cascada declara UN override en
vez de cinco coordenadas iguales — el TSC no necesita que se le repita lo que
ya hereda.

**Los puntos del paginador son geometría propia**: 0.375–0.75rem por talla, ni
bundle ni desviación que marcar, porque el bundle no tiene coordenada que
signifique «un punto». El punto activo se estira a pastilla con un factor que
ahora es token (`active-indicator-scale`): es lo que lo hace leer como «estás
aquí» y no como un punto más gordo.

**El radio interior es concéntrico**, no un segundo radio: viewport y
diapositivas redondean por dentro del marco, así que su esquina es la del marco
menos el borde — el `2px` suelto pasa a `inner-radius-inset`.

Las tres variantes (surface / outline / ghost) entran enteras: padding del
marco, borde y superficie.

Quedan 5 literales de layout (`block-size: 100%` ×3 para que el viewport
vertical no colapse, `max-content` del raíl de puntos) y los 2 privados de
paleta.

  audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados ·
  suite: sólo el rojo conocido

Demo con tab Tokens: 30 filas, 0 sin computar.

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

@ -10,9 +10,9 @@
- **Medido**: 2026-08-20 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/` - **Medido**: 2026-08-20 · **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**? - **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **42%** — 2049 de 4856 knobs pasan por un token público del componente - **Alcance global**: **43%** — 2073 de 4856 knobs pasan por un token público del componente
- **Reparto**: público 2049 · privado 724 · global 1496 · literal 587 · sistema transversal 335 _(fuera del ratio)_ - **Reparto**: público 2073 · privado 712 · global 1484 · literal 587 · sistema transversal 335 _(fuera del ratio)_
- **Sin token público propio**: 51 · **alcance < 20 %**: 45 · **alcance 100 %**: 7 · **con eje `size`**: 74 - **Sin token público propio**: 50 · **alcance < 20 %**: 44 · **alcance 100 %**: 7 · **con eje `size`**: 73
## Cómo se lee ## Cómo se lee
@ -84,19 +84,19 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 12 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 | | 12 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 13 | [field](./field.md) | 33 | 51% | 73 | 60 | | 13 | [field](./field.md) | 33 | 51% | 73 | 60 |
| 14 | [feed](./feed.md) | 32 | 0% | 33 | 0 | | 14 | [feed](./feed.md) | 32 | 0% | 33 | 0 |
| 15 | [carousel](./carousel.md) | 31 | 0% | 36 | 0 | | 15 | [date-range-picker](./date-range-picker.md) | 30 | 40% | 53 | 22 |
| 16 | [date-range-picker](./date-range-picker.md) | 30 | 40% | 53 | 22 | | 16 | [natural-time-picker](./natural-time-picker.md) | 30 | 61% | 77 | 62 |
| 17 | [natural-time-picker](./natural-time-picker.md) | 30 | 61% | 77 | 62 | | 17 | [popover](./popover.md) | 28 | 42% | 51 | 36 |
| 18 | [popover](./popover.md) | 28 | 42% | 51 | 36 | | 18 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 |
| 19 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 | | 19 | [drawer](./drawer.md) | 27 | 55% | 62 | 44 |
| 20 | [drawer](./drawer.md) | 27 | 55% | 62 | 44 | | 20 | [grid-list](./grid-list.md) | 27 | 0% | 32 | 0 |
## Sin ningún token público propio (51) ## Sin ningún token público propio (50)
Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward
(`_palette-*`), que no son contrato: un tema no puede nombrarlos. (`_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) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.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) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.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) · [`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) · [`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) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.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) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.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) · [`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) · [`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) ## Tabla completa (162 recetas, por alcance ascendente)
@ -108,7 +108,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [month-grid](./month-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 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 | | [year-grid](./year-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – | | [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – |
| [carousel](./carousel.md) | 0% | 36 | 0 | 14 | 12 | 5 | 5 | 0 | y |
| [feed](./feed.md) | 0% | 33 | 0 | 13 | 16 | 3 | 1 | 0 | y | | [feed](./feed.md) | 0% | 33 | 0 | 13 | 16 | 3 | 1 | 0 | y |
| [grid-list](./grid-list.md) | 0% | 32 | 0 | 8 | 17 | 2 | 5 | 0 | y | | [grid-list](./grid-list.md) | 0% | 32 | 0 | 8 | 17 | 2 | 5 | 0 | y |
| [textarea](./textarea.md) | 0% | 28 | 0 | 16 | 5 | 4 | 3 | 0 | y | | [textarea](./textarea.md) | 0% | 28 | 0 | 16 | 5 | 4 | 3 | 0 | y |
@ -228,6 +227,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [accordion](./accordion.md) | 77% | 65 | 49 | 10 | 0 | 5 | 1 | 76 | y | | [accordion](./accordion.md) | 77% | 65 | 49 | 10 | 0 | 5 | 1 | 76 | y |
| [radio-group](./radio-group.md) | 77% | 75 | 56 | 10 | 7 | 0 | 2 | 61 | y | | [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-log](./chat-log.md) | 77% | 42 | 30 | 0 | 4 | 5 | 3 | 31 | – |
| [carousel](./carousel.md) | 77% | 36 | 24 | 2 | 0 | 5 | 5 | 30 | – |
| [chat-composer](./chat-composer.md) | 79% | 44 | 31 | 0 | 3 | 5 | 5 | 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 | – | | [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 | | [chat-message](./chat-message.md) | 82% | 84 | 63 | 2 | 6 | 6 | 7 | 75 | y |

@ -5,58 +5,33 @@
> Vista de conjunto: [README](./README.md) · método y protocolo: > Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-20 · **Alcance**: **0%** — 0 de 31 knobs por token público - **Medido**: 2026-08-20 · **Alcance**: **77%** — 24 de 31 knobs por token público
- **Knobs de apariencia**: 36 — público 0 · privado 14 · global 12 · literal 5 · sistema 5 _(fuera del ratio)_ - **Knobs de apariencia**: 36 — público 24 · privado 2 · global 0 · literal 5 · sistema 5 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 0 pública(s) · 2 privada(s) forward — `_palette-solid`, `_palette-element` - **Contrato hoy** (`lib/recipes/base.ts`): 30 pública(s) — `indicator-size-xs`, `indicator-size-sm`, `indicator-size-md`, `indicator-size-lg`, `indicator-size-xl`, `indicator-gap-xs`, `indicator-gap-sm`, `indicator-gap-md`, `indicator-gap-lg`, `indicator-gap-xl`, `item-gap-xs`, `item-gap-md`, `indicator-size`, `indicator-gap`, `item-gap`, `gap`, `radius`, `font-family`, `fg`, `inner-radius-inset`, `trigger-offset`, `frame-padding`, `border-width`, `border`, `surface-bg`, `vertical-block-size`, `indicator-group-padding-block`, `indicator-bg`, `indicator-radius`, `active-indicator-scale` · 2 privada(s) forward — `_palette-solid`, `_palette-element`
- **Eje `size`**: sí · **ficheros**: `carousel.css` - **Eje `size`**: no · **ficheros**: `carousel.css`
## 1. Knobs fuera de alcance ## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (12) ### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor | _Ninguno._
| ---: | --- | --- | --- | --- |
| 1 | `carousel.css:41` | `[data-carousel]` | `gap` | `var(--space-2)` | ### 1.2 A través de un privado (2)
| 2 | `carousel.css:42` | `[data-carousel]` | `font-family` | `var(--style-label-font-family)` |
| 3 | `carousel.css:43` | `[data-carousel]` | `color` | `var(--color-content-primary)` |
| 4 | `carousel.css:105` | `[data-carousel][data-variant='surface']` | `padding` | `var(--space-2)` |
| 5 | `carousel.css:106` | `[data-carousel][data-variant='surface']` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 6 | `carousel.css:108` | `[data-carousel][data-variant='surface']` | `background` | `var(--color-surface-default)` |
| 7 | `carousel.css:112` | `[data-carousel][data-variant='outline']` | `padding` | `var(--space-2)` |
| 8 | `carousel.css:113` | `[data-carousel][data-variant='outline']` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 9 | `carousel.css:120` | `[data-carousel][data-variant='ghost']` | `border` | `var(--border-width) solid transparent` |
| 10 | `carousel.css:287` | `[data-carousel-indicator-group]` | `padding-block` | `var(--space-1)` |
| 11 | `carousel.css:300` | `[data-carousel-indicator]` | `border-radius` | `var(--radius-full)` |
| 12 | `carousel.css:301` | `[data-carousel-indicator]` | `background` | `var(--color-surface-overlay)` |
### 1.2 A través de un privado (14)
| # | fichero:línea | selector | propiedad | valor | | # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- | | ---: | --- | --- | --- | --- |
| 1 | `carousel.css:70` | `[data-carousel][data-orientation='vertical']` | `block-size` | `var(--_carousel-vertical-block-size)` | | 1 | `carousel.css:288` | `[data-carousel-indicator]:hover:not([data-disabled])` | `background` | `var(--_carousel-palette-element)` |
| 2 | `carousel.css:107` | `[data-carousel][data-variant='surface']` | `border-radius` | `var(--_carousel-radius)` | | 2 | `carousel.css:292` | `[data-carousel-indicator][data-state='active']` | `background` | `var(--_carousel-palette-solid)` |
| 3 | `carousel.css:114` | `[data-carousel][data-variant='outline']` | `border-radius` | `var(--_carousel-radius)` |
| 4 | `carousel.css:173` | `[data-carousel-viewport]` | `border-radius` | `calc(var(--_carousel-radius) - 2px)` |
| 5 | `carousel.css:178` | `[data-carousel-item-group]` | `gap` | `var(--_carousel-gap)` |
| 6 | `carousel.css:194` | `[data-carousel-item]` | `border-radius` | `calc(var(--_carousel-radius) - 2px)` |
| 7 | `carousel.css:286` | `[data-carousel-indicator-group]` | `gap` | `var(--_carousel-indicator-gap)` |
| 8 | `carousel.css:296` | `[data-carousel-indicator]` | `inline-size` | `var(--_carousel-indicator-size)` |
| 9 | `carousel.css:297` | `[data-carousel-indicator]` | `block-size` | `var(--_carousel-indicator-size)` |
| 10 | `carousel.css:313` | `[data-carousel-indicator]:hover:not([data-disabled])` | `background` | `var(--_carousel-palette-element)` |
| 11 | `carousel.css:317` | `[data-carousel-indicator][data-state='active']` | `background` | `var(--_carousel-palette-solid)` |
| 12 | `carousel.css:319` | `[data-carousel-indicator][data-state='active']` | `inline-size` | `calc(var(--_carousel-indicator-size) * 2.25)` |
| 13 | `carousel.css:324` | `[data-carousel-indicator-group][data-orientation='vertical'] [data-carousel-indicator][data-state='active']` | `inline-size` | `var(--_carousel-indicator-size)` |
| 14 | `carousel.css:325` | `[data-carousel-indicator-group][data-orientation='vertical'] [data-carousel-indicator][data-state='active']` | `block-size` | `calc(var(--_carousel-indicator-size) * 2.25)` |
### 1.3 Literales (5) ### 1.3 Literales (5)
| # | fichero:línea | selector | propiedad | valor | | # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- | | ---: | --- | --- | --- | --- |
| 1 | `carousel.css:75` | `[data-carousel][data-orientation='vertical'] [data-carousel-viewport]` | `block-size` | `100%` | | 1 | `carousel.css:67` | `[data-carousel][data-orientation='vertical'] [data-carousel-viewport]` | `block-size` | `100%` |
| 2 | `carousel.css:81` | `[data-carousel][data-orientation='vertical'] [data-carousel-item-group]` | `block-size` | `100%` | | 2 | `carousel.css:73` | `[data-carousel][data-orientation='vertical'] [data-carousel-item-group]` | `block-size` | `100%` |
| 3 | `carousel.css:88` | `[data-carousel][data-orientation='vertical'] [data-carousel-item]` | `block-size` | `100%` | | 3 | `carousel.css:80` | `[data-carousel][data-orientation='vertical'] [data-carousel-item]` | `block-size` | `100%` |
| 4 | `carousel.css:94` | `[data-carousel][data-orientation='vertical'] [data-carousel-indicator-group]` | `inline-size` | `max-content` | | 4 | `carousel.css:86` | `[data-carousel][data-orientation='vertical'] [data-carousel-indicator-group]` | `inline-size` | `max-content` |
| 5 | `carousel.css:100` | `[data-carousel][data-block]` | `inline-size` | `100%` | | 5 | `carousel.css:92` | `[data-carousel][data-block]` | `inline-size` | `100%` |
## 2. Sistema transversal (5) — informativo, fuera del ratio ## 2. Sistema transversal (5) — informativo, fuera del ratio
@ -64,30 +39,21 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor | | # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- | | ---: | --- | --- | --- | --- |
| 1 | `carousel.css:144` | `[data-carousel][data-disabled]` | `opacity` | `var(--opacity-disabled)` | | 1 | `carousel.css:119` | `[data-carousel][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `carousel.css:164` | `[data-carousel]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | | 2 | `carousel.css:139` | `[data-carousel]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 3 | `carousel.css:256` | `[data-carousel-prev-trigger][data-disabled], [data-carousel-next-trigger][data-disabled]` | `opacity` | `var(--opacity-disabled)` | | 3 | `carousel.css:231` | `[data-carousel-prev-trigger][data-disabled], [data-carousel-next-trigger][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 4 | `carousel.css:329` | `[data-carousel-indicator]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | | 4 | `carousel.css:304` | `[data-carousel-indicator]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 5 | `carousel.css:334` | `[data-carousel-indicator][data-disabled]` | `opacity` | `var(--opacity-disabled)` | | 5 | `carousel.css:309` | `[data-carousel-indicator][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
## 3. Privados de la receta — ¿de dónde sale su valor? ## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? | _La receta no declara privados propios en su CSS._
| --- | ---: | --- | --- | :-: |
| `--_carousel-gap` | 2 | `var(--space-2)`, `var(--space-1)` | global | no |
| `--_carousel-radius` | 1 | `var(--radius-md)` | global | no |
| `--_carousel-indicator-size` | 5 | `0.5rem`, `0.375rem`, `0.4375rem`, `0.625rem`, `0.75rem` | literal | no |
| `--_carousel-indicator-gap` | 5 | `var(--space-2)`, `var(--space-1)`, `var(--space-3)` | global | no |
| `--_carousel-trigger-offset` | 1 | `var(--space-3)` | global | no |
| `--_carousel-vertical-block-size` | 1 | `16rem` | literal | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_carousel-palette-element`, `--_carousel-palette-solid`. Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_carousel-palette-element`, `--_carousel-palette-solid`.
## 4. Propuesta de corrección ## 4. Propuesta de corrección
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo). ### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (39)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién 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 puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
@ -97,61 +63,13 @@ no distingue lo que debería — se marca `⚠`.
| token (`--carousel-…`) | scope TSC | valor propuesto | usos | | token (`--carousel-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: | | --- | --- | --- | ---: |
| `item-group-gap` | `root` | ⚠ `var(--space-2)` / `var(--space-1)` | 2 |
| `indicator-width-md` | `size:md` | `0.5rem` | 2 |
| `indicator-width-xs` | `size:xs` | `0.375rem` | 2 |
| `indicator-width-sm` | `size:sm` | `0.4375rem` | 2 |
| `indicator-width-lg` | `size:lg` | `0.625rem` | 2 |
| `indicator-width-xl` | `size:xl` | `0.75rem` | 2 |
| `gap` | `root` | `var(--space-2)` | 1 |
| `font-family` | `root` | `var(--style-label-font-family)` | 1 |
| `fg` | `root` | `var(--color-content-primary)` | 1 |
| `height` | `root` | `16rem` | 1 |
| `indicator-group-width` | `root` | `max-content` | 1 | | `indicator-group-width` | `root` | `max-content` | 1 |
| `surface-radius` | `root` | `var(--radius-md)` | 1 |
| `surface-bg` | `root` | `var(--color-surface-default)` | 1 | ### 4.2 Sin nombre mecánico (6)
| `outline-radius` | `root` | `var(--radius-md)` | 1 |
| `viewport-radius` | `root` | `var(--radius-md)` | 1 |
| `item-radius` | `root` | `var(--radius-md)` | 1 |
| `indicator-group-gap-md` | `size:md` | `var(--space-2)` | 1 |
| `indicator-group-gap-xs` | `size:xs` | `var(--space-1)` | 1 |
| `indicator-group-gap-sm` | `size:sm` | `var(--space-1)` | 1 |
| `indicator-group-gap-lg` | `size:lg` | `var(--space-3)` | 1 |
| `indicator-group-gap-xl` | `size:xl` | `var(--space-3)` | 1 |
| `indicator-group-padding-block` | `root` | `var(--space-1)` | 1 |
| `indicator-height-md` | `size:md` | `0.5rem` | 1 |
| `indicator-height-xs` | `size:xs` | `0.375rem` | 1 |
| `indicator-height-sm` | `size:sm` | `0.4375rem` | 1 |
| `indicator-height-lg` | `size:lg` | `0.625rem` | 1 |
| `indicator-height-xl` | `size:xl` | `0.75rem` | 1 |
| `indicator-radius` | `root` | `var(--radius-full)` | 1 |
| `indicator-bg` | `root` | `var(--color-surface-overlay)` | 1 |
| `indicator-group-width-md` | `size:md` | `0.5rem` | 1 |
| `indicator-group-width-xs` | `size:xs` | `0.375rem` | 1 |
| `indicator-group-width-sm` | `size:sm` | `0.4375rem` | 1 |
| `indicator-group-width-lg` | `size:lg` | `0.625rem` | 1 |
| `indicator-group-width-xl` | `size:xl` | `0.75rem` | 1 |
| `indicator-group-height-md` | `size:md` | `0.5rem` | 1 |
| `indicator-group-height-xs` | `size:xs` | `0.375rem` | 1 |
| `indicator-group-height-sm` | `size:sm` | `0.4375rem` | 1 |
| `indicator-group-height-lg` | `size:lg` | `0.625rem` | 1 |
| `indicator-group-height-xl` | `size:xl` | `0.75rem` | 1 |
### 4.2 Sin nombre mecánico (11)
- **⚠ 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** — 4: `block-size`, `inline-size`. - **⚠ 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** — 4: `block-size`, `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)** — 2: `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)** — 3: `border`.
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 2: `background`. - **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 2: `background`.
### 4.3 Avisos sobre los tokens propuestos (5)
- **el privado `--_carousel-vertical-block-size` debe pasar a leer este público (o desaparecer)** — `--carousel-height`
- **el privado `--_carousel-radius` debe pasar a leer este público (o desaparecer)** — `--carousel-surface-radius`, `--carousel-outline-radius`, `--carousel-viewport-radius`, `--carousel-item-radius`
- **el privado `--_carousel-gap` debe pasar a leer este público (o desaparecer)** — `--carousel-item-group-gap`
- **el privado `--_carousel-indicator-gap` debe pasar a leer este público (o desaparecer)** — `--carousel-indicator-group-gap-md`, `--carousel-indicator-group-gap-xs`, `--carousel-indicator-group-gap-sm`, `--carousel-indicator-group-gap-lg`, `--carousel-indicator-group-gap-xl`
- **el privado `--_carousel-indicator-size` debe pasar a leer este público (o desaparecer)** — `--carousel-indicator-width-md`, `--carousel-indicator-width-xs`, `--carousel-indicator-width-sm`, `--carousel-indicator-width-lg`, `--carousel-indicator-width-xl`, `--carousel-indicator-height-md`, `--carousel-indicator-height-xs`, `--carousel-indicator-height-sm`, `--carousel-indicator-height-lg`, `--carousel-indicator-height-xl`, `--carousel-indicator-group-width-md`, `--carousel-indicator-group-width-xs`, `--carousel-indicator-group-width-sm`, `--carousel-indicator-group-width-lg`, `--carousel-indicator-group-width-xl`, `--carousel-indicator-group-height-md`, `--carousel-indicator-group-height-xs`, `--carousel-indicator-group-height-sm`, `--carousel-indicator-group-height-lg`, `--carousel-indicator-group-height-xl`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4) ### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer. - [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer.

@ -18,14 +18,14 @@ visuales (`size`, `variant`, `color`, `block`, `verticalBlockSize`).
## Comparativa ## Comparativa
| Capacidad | APG / WAI-ARIA | Ark UI | shadcn-svelte / Embla | UIX | | Capacidad | APG / WAI-ARIA | Ark UI | shadcn-svelte / Embla | UIX |
| --- | --- | --- | --- | --- | | ------------------------------ | -------------------------------------------- | ------------------------------ | ----------------------- | ----------------------------- |
| Root con nombre accesible | Recomienda `region` + `aria-roledescription` | Root + partes | Root visual + Embla API | Soma/Morfo | | Root con nombre accesible | Recomienda `region` + `aria-roledescription` | Root + partes | Root visual + Embla API | Soma/Morfo |
| Prev/Next como botones nativos | Recomendado | `PrevTrigger` / `NextTrigger` | `Previous` / `Next` | Soma triggers + Eidos Button | | Prev/Next como botones nativos | Recomendado | `PrevTrigger` / `NextTrigger` | `Previous` / `Next` | Soma triggers + Eidos Button |
| Indicadores | Botones/tabs de seleccion | `IndicatorGroup` / `Indicator` | Dots via composicion | Soma indicators | | Indicadores | Botones/tabs de seleccion | `IndicatorGroup` / `Indicator` | Dots via composicion | Soma indicators |
| Drag/swipe | No prescribe motor | Incluido | Embla | Soma `Gesture.base()` | | Drag/swipe | No prescribe motor | Incluido | Embla | Soma `Gesture.base()` |
| Autoplay + pausa | Debe ser controlable | `autoplay`, pause cases | Plugin Embla | Soma timer + stop/pause props | | Autoplay + pausa | Debe ser controlable | `autoplay`, pause cases | Plugin Embla | Soma timer + stop/pause props |
| Visual styling | Fuera de scope | Headless/data attrs | Tailwind recipe | Eidos recipe tokens | | Visual styling | Fuera de scope | Headless/data attrs | Tailwind recipe | Eidos recipe tokens |
Fuentes: Fuentes:
@ -60,11 +60,40 @@ Fuentes:
resuelven de esa misma fuente. Solo aplica al eje inline: en vertical los resuelven de esa misma fuente. Solo aplica al eje inline: en vertical los
chevrons son up/down y no giran. Es lo que hace shadcn con `rtl:rotate-180`. chevrons son up/down y no giran. Es lo que hace shadcn con `rtl:rotate-180`.
## Talla y tema
Contrato en `lib/recipes/base.ts > carousel` (30 claves). Alcance del censo:
**77 %** (era 0 % antes del 2026-08-21).
**Los botones prev/next NO se tematizan desde aquí**: componen `<IconButton>`,
así que su cromo es el del Button y un tema los viste por ahí. Esta receta sólo
decide DÓNDE se colocan (`trigger-offset`).
**Los puntos del paginador son geometría propia.** Sus tamaños
(0.375–0.75rem por talla) no son ni bundle ni desviación que marcar: el bundle
no tiene coordenada que signifique «un punto». Van verbatim por talla, con su
nombre resuelto. El punto activo se estira a pastilla con
`active-indicator-scale` — el factor es lo que lo hace leer como «estás aquí» y
no como un punto más gordo.
**El ⚠ del informe eran DOS gaps distintos**, no uno con dos valores: el `gap`
en columna de la raíz y el `item-gap` ENTRE DIAPOSITIVAS, que además sólo se
aparta del default en `xs` — por eso su cascada declara un único override en
vez de cinco coordenadas iguales.
**El radio interior es concéntrico, no un segundo radio**: viewport y
diapositivas redondean por dentro del marco, así que su esquina es la del marco
menos el borde (`inner-radius-inset`).
`R-5.1 exception: block-size/inline-size: 100%` ×3 y `max-content` — la cadena
de alturas que la orientación vertical necesita para que el viewport no
colapse, y el ancho del raíl de puntos. Mecánica de layout, no apariencia.
## Gaps ## Gaps
| Gap | Disposicion | Detalle | | Gap | Disposicion | Detalle |
| --- | --- | --- | | ----------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Pause/play trigger publico dentro del componente | diferir | La API imperativa ya expone `play`, `pause` y `reset`; una parte visual requiere Morfo/Soma. | | Pause/play trigger publico dentro del componente | diferir | La API imperativa ya expone `play`, `pause` y `reset`; una parte visual requiere Morfo/Soma. |
| Labels traducidos para slide/indicator generados por indice | implementar en Soma | El README de Soma ya documenta que parte del texto se arma en JS. | | Labels traducidos para slide/indicator generados por indice | implementar en Soma | El README de Soma ya documenta que parte del texto se arma en JS. |
| Tests browser-level permanentes | implementar | El fallo de click sobre SVG se verifico manualmente con Playwright; conviene convertirlo en test visual si el harness lo permite. | | Tests browser-level permanentes | implementar | El fallo de click sobre SVG se verifico manualmente con Playwright; conviene convertirlo en test visual si el harness lo permite. |
| Motion attrs por direccion | diferir | Seria un extra visual para animaciones internas de slide. | | Motion attrs por direccion | diferir | Seria un extra visual para animaciones internas de slide. |

@ -23,24 +23,16 @@
*/ */
[data-carousel] { [data-carousel] {
--_carousel-gap: var(--space-2); /* Every knob is PUBLIC and the per-size cascade is emitted by the TSC from
--_carousel-radius: var(--radius-md); * `recipes/base.ts`; writing `[data-size]` blocks here too would paint the
--_carousel-indicator-size: 0.5rem; * OLD values over the new tokens and the computed diff would read 0 through
--_carousel-indicator-gap: var(--space-2); * the old path. */
--_carousel-trigger-offset: var(--space-3);
/*
* Default vertical viewport block-size — vertical orientation needs a
* bounded block axis or the viewport collapses and items overflow.
* Override via inline style or recipe consumer.
*/
--_carousel-vertical-block-size: 16rem;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-2); gap: var(--carousel-gap);
font-family: var(--style-label-font-family); font-family: var(--carousel-font-family);
color: var(--color-content-primary); color: var(--carousel-fg);
outline: none; outline: none;
} }
@ -67,7 +59,7 @@
[data-carousel][data-orientation='vertical'] { [data-carousel][data-orientation='vertical'] {
flex-direction: row; flex-direction: row;
align-items: stretch; align-items: stretch;
block-size: var(--_carousel-vertical-block-size); block-size: var(--carousel-vertical-block-size);
} }
[data-carousel][data-orientation='vertical'] [data-carousel-viewport] { [data-carousel][data-orientation='vertical'] [data-carousel-viewport] {
@ -102,43 +94,26 @@
/* ── Variant — surface (default) ─ */ /* ── Variant — surface (default) ─ */
[data-carousel][data-variant='surface'] { [data-carousel][data-variant='surface'] {
padding: var(--space-2); padding: var(--carousel-frame-padding);
border: var(--border-width) solid var(--color-border-default); border: var(--carousel-border-width) solid var(--carousel-border);
border-radius: var(--_carousel-radius); border-radius: var(--carousel-radius);
background: var(--color-surface-default); background: var(--carousel-surface-bg);
} }
[data-carousel][data-variant='outline'] { [data-carousel][data-variant='outline'] {
padding: var(--space-2); padding: var(--carousel-frame-padding);
border: var(--border-width) solid var(--color-border-default); border: var(--carousel-border-width) solid var(--carousel-border);
border-radius: var(--_carousel-radius); border-radius: var(--carousel-radius);
background: transparent; background: transparent;
} }
[data-carousel][data-variant='ghost'] { [data-carousel][data-variant='ghost'] {
padding: 0; padding: 0;
border: var(--border-width) solid transparent; border: var(--carousel-border-width) solid transparent;
background: transparent; background: transparent;
} }
/* ── Sizes ─ */ /* ── Sizes ─ */
[data-carousel][data-size='xs'] {
--_carousel-indicator-size: 0.375rem;
--_carousel-indicator-gap: var(--space-1);
--_carousel-gap: var(--space-1);
}
[data-carousel][data-size='sm'] {
--_carousel-indicator-size: 0.4375rem;
--_carousel-indicator-gap: var(--space-1);
}
[data-carousel][data-size='lg'] {
--_carousel-indicator-size: 0.625rem;
--_carousel-indicator-gap: var(--space-3);
}
[data-carousel][data-size='xl'] {
--_carousel-indicator-size: 0.75rem;
--_carousel-indicator-gap: var(--space-3);
}
[data-carousel][data-disabled] { [data-carousel][data-disabled] {
opacity: var(--opacity-disabled); opacity: var(--opacity-disabled);
@ -170,12 +145,12 @@
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
min-inline-size: 0; min-inline-size: 0;
border-radius: calc(var(--_carousel-radius) - 2px); border-radius: calc(var(--carousel-radius) - var(--carousel-inner-radius-inset));
} }
/* ── ItemGroup ─ */ /* ── ItemGroup ─ */
[data-carousel-item-group] { [data-carousel-item-group] {
gap: var(--_carousel-gap); gap: var(--carousel-item-gap);
transition: transform var(--duration-slow) var(--ease-out); transition: transform var(--duration-slow) var(--ease-out);
} }
@ -191,7 +166,7 @@
[data-carousel-item] { [data-carousel-item] {
flex: 0 0 100%; flex: 0 0 100%;
min-inline-size: 0; min-inline-size: 0;
border-radius: calc(var(--_carousel-radius) - 2px); border-radius: calc(var(--carousel-radius) - var(--carousel-inner-radius-inset));
user-select: none; user-select: none;
} }
@ -225,11 +200,11 @@
} }
[data-carousel-prev-trigger] { [data-carousel-prev-trigger] {
inset-inline-start: var(--_carousel-trigger-offset); inset-inline-start: var(--carousel-trigger-offset);
} }
[data-carousel-next-trigger] { [data-carousel-next-trigger] {
inset-inline-end: var(--_carousel-trigger-offset); inset-inline-end: var(--carousel-trigger-offset);
} }
/* The chevron GLYPH is physical: `<SvgChevron direction='left'>` writes an /* The chevron GLYPH is physical: `<SvgChevron direction='left'>` writes an
@ -263,7 +238,7 @@
width off-axis in RTL. `translate: none` is required to cancel the block-axis width off-axis in RTL. `translate: none` is required to cancel the block-axis
centring the base rule sets, which no longer applies here. */ centring the base rule sets, which no longer applies here. */
[data-carousel][data-orientation='vertical'] [data-carousel-prev-trigger] { [data-carousel][data-orientation='vertical'] [data-carousel-prev-trigger] {
inset-block-start: var(--_carousel-trigger-offset); inset-block-start: var(--carousel-trigger-offset);
inset-inline: 0; inset-inline: 0;
margin-inline: auto; margin-inline: auto;
translate: none; translate: none;
@ -271,7 +246,7 @@
[data-carousel][data-orientation='vertical'] [data-carousel-next-trigger] { [data-carousel][data-orientation='vertical'] [data-carousel-next-trigger] {
inset-block-start: auto; inset-block-start: auto;
inset-block-end: var(--_carousel-trigger-offset); inset-block-end: var(--carousel-trigger-offset);
inset-inline: 0; inset-inline: 0;
margin-inline: auto; margin-inline: auto;
translate: none; translate: none;
@ -283,8 +258,8 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: var(--_carousel-indicator-gap); gap: var(--carousel-indicator-gap);
padding-block: var(--space-1); padding-block: var(--carousel-indicator-group-padding-block);
} }
[data-carousel-indicator-group][data-orientation='vertical'] { [data-carousel-indicator-group][data-orientation='vertical'] {
@ -293,12 +268,12 @@
/* ── Indicator ─ */ /* ── Indicator ─ */
[data-carousel-indicator] { [data-carousel-indicator] {
inline-size: var(--_carousel-indicator-size); inline-size: var(--carousel-indicator-size);
block-size: var(--_carousel-indicator-size); block-size: var(--carousel-indicator-size);
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: var(--radius-full); border-radius: var(--carousel-indicator-radius);
background: var(--color-surface-overlay); background: var(--carousel-indicator-bg);
cursor: pointer; cursor: pointer;
pointer-events: auto; pointer-events: auto;
transition: transition:
@ -316,13 +291,13 @@
[data-carousel-indicator][data-state='active'] { [data-carousel-indicator][data-state='active'] {
background: var(--_carousel-palette-solid); background: var(--_carousel-palette-solid);
/* Active indicator stretches horizontally for clearer current-position hint. */ /* Active indicator stretches horizontally for clearer current-position hint. */
inline-size: calc(var(--_carousel-indicator-size) * 2.25); inline-size: calc(var(--carousel-indicator-size) * var(--carousel-active-indicator-scale));
} }
[data-carousel-indicator-group][data-orientation='vertical'] [data-carousel-indicator-group][data-orientation='vertical']
[data-carousel-indicator][data-state='active'] { [data-carousel-indicator][data-state='active'] {
inline-size: var(--_carousel-indicator-size); inline-size: var(--carousel-indicator-size);
block-size: calc(var(--_carousel-indicator-size) * 2.25); block-size: calc(var(--carousel-indicator-size) * 2.25);
} }
[data-carousel-indicator]:focus-visible { [data-carousel-indicator]:focus-visible {

@ -1948,6 +1948,33 @@
--gradient-picker-selected-preset-border: var(--color-primary-border); --gradient-picker-selected-preset-border: var(--color-primary-border);
--gradient-picker-selected-preset-outline-offset: 2px; --gradient-picker-selected-preset-outline-offset: 2px;
--gradient-picker-actions-gap: var(--space-2); --gradient-picker-actions-gap: var(--space-2);
--carousel-indicator-size-xs: 0.375rem;
--carousel-indicator-size-sm: 0.4375rem;
--carousel-indicator-size-md: 0.5rem;
--carousel-indicator-size-lg: 0.625rem;
--carousel-indicator-size-xl: 0.75rem;
--carousel-indicator-gap-xs: var(--space-1);
--carousel-indicator-gap-sm: var(--space-1);
--carousel-indicator-gap-md: var(--space-2);
--carousel-indicator-gap-lg: var(--space-3);
--carousel-indicator-gap-xl: var(--space-3);
--carousel-item-gap-xs: var(--space-1);
--carousel-item-gap-md: var(--space-2);
--carousel-gap: var(--space-2);
--carousel-radius: var(--radius-md);
--carousel-font-family: var(--style-label-font-family);
--carousel-fg: var(--color-content-primary);
--carousel-inner-radius-inset: 2px;
--carousel-trigger-offset: var(--space-3);
--carousel-frame-padding: var(--space-2);
--carousel-border-width: var(--border-width);
--carousel-border: var(--color-border-default);
--carousel-surface-bg: var(--color-surface-default);
--carousel-vertical-block-size: 16rem;
--carousel-indicator-group-padding-block: var(--space-1);
--carousel-indicator-bg: var(--color-surface-overlay);
--carousel-indicator-radius: var(--radius-full);
--carousel-active-indicator-scale: 2.25;
--listbox-gap: var(--space-1); --listbox-gap: var(--space-1);
--listbox-padding: var(--space-1); --listbox-padding: var(--space-1);
--listbox-radius: var(--radius-md); --listbox-radius: var(--radius-md);
@ -5537,10 +5564,34 @@
} }
[data-carousel] { [data-carousel] {
--carousel-indicator-size: var(--carousel-indicator-size-md);
--carousel-indicator-gap: var(--carousel-indicator-gap-md);
--carousel-item-gap: var(--carousel-item-gap-md);
--_carousel-palette-solid: var(--color-primary-solid); --_carousel-palette-solid: var(--color-primary-solid);
--_carousel-palette-element: var(--color-primary-element); --_carousel-palette-element: var(--color-primary-element);
} }
[data-carousel][data-size='xs'] {
--carousel-indicator-size: var(--carousel-indicator-size-xs);
--carousel-indicator-gap: var(--carousel-indicator-gap-xs);
--carousel-item-gap: var(--carousel-item-gap-xs);
}
[data-carousel][data-size='sm'] {
--carousel-indicator-size: var(--carousel-indicator-size-sm);
--carousel-indicator-gap: var(--carousel-indicator-gap-sm);
}
[data-carousel][data-size='lg'] {
--carousel-indicator-size: var(--carousel-indicator-size-lg);
--carousel-indicator-gap: var(--carousel-indicator-gap-lg);
}
[data-carousel][data-size='xl'] {
--carousel-indicator-size: var(--carousel-indicator-size-xl);
--carousel-indicator-gap: var(--carousel-indicator-gap-xl);
}
[data-carousel][data-color], [data-carousel][data-color-custom] { [data-carousel][data-color], [data-carousel][data-color-custom] {
--_carousel-palette-element: var(--palette-element, var(--color-primary-element)); --_carousel-palette-element: var(--palette-element, var(--color-primary-element));
--_carousel-palette-solid: var(--palette-solid, var(--color-primary-solid)); --_carousel-palette-solid: var(--palette-solid, var(--color-primary-solid));

@ -2595,7 +2595,82 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' }, '_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-element': { value: 'var(--color-primary-element)', scope: 'host' } '_palette-element': { value: 'var(--color-primary-element)', scope: 'host' }
}, },
// Carousel. The prev/next triggers compose `<IconButton>`, so their chrome is
// NOT minted here — a theme dresses them through the Button contract.
carousel: { carousel: {
// The paginator dots. These rem values are the carousel's OWN geometry:
// the size bundle has no coordinate that means "a dot", so they are
// verbatim per size rather than a deviation to flag.
'indicator-size-xs': '0.375rem',
'indicator-size-sm': '0.4375rem',
'indicator-size-md': '0.5rem',
'indicator-size-lg': '0.625rem',
'indicator-size-xl': '0.75rem',
'indicator-gap-xs': 'var(--space-1)',
'indicator-gap-sm': 'var(--space-1)',
'indicator-gap-md': 'var(--space-2)',
'indicator-gap-lg': 'var(--space-3)',
'indicator-gap-xl': 'var(--space-3)',
// The gap BETWEEN SLIDES. It is not the root's own column gap — two
// different gaps that the audit read as one ⚠; only xs departs from the
// default, so only xs declares an override.
'item-gap-xs': 'var(--space-1)',
'item-gap-md': 'var(--space-2)',
'indicator-size': {
declarations: [
{ value: 'var(--carousel-indicator-size-md)', scope: 'host' },
{ value: 'var(--carousel-indicator-size-xs)', scope: 'size:xs' },
{ value: 'var(--carousel-indicator-size-sm)', scope: 'size:sm' },
{ value: 'var(--carousel-indicator-size-lg)', scope: 'size:lg' },
{ value: 'var(--carousel-indicator-size-xl)', scope: 'size:xl' }
]
},
'indicator-gap': {
declarations: [
{ value: 'var(--carousel-indicator-gap-md)', scope: 'host' },
{ value: 'var(--carousel-indicator-gap-xs)', scope: 'size:xs' },
{ value: 'var(--carousel-indicator-gap-sm)', scope: 'size:sm' },
{ value: 'var(--carousel-indicator-gap-lg)', scope: 'size:lg' },
{ value: 'var(--carousel-indicator-gap-xl)', scope: 'size:xl' }
]
},
'item-gap': {
declarations: [
{ value: 'var(--carousel-item-gap-md)', scope: 'host' },
{ value: 'var(--carousel-item-gap-xs)', scope: 'size:xs' }
]
},
// Chassis.
gap: 'var(--space-2)',
radius: 'var(--radius-md)',
'font-family': 'var(--style-label-font-family)',
fg: 'var(--color-content-primary)',
// The viewport and the slides round INSIDE the frame, so their corner is
// the frame's minus the border - concentric, not a second radius.
'inner-radius-inset': '2px',
// How far the prev/next controls sit from the frame edge.
'trigger-offset': 'var(--space-3)',
// Variants. `surface` and `outline` inset the frame so the slides do not
// touch its edge; `ghost` keeps the border box (no layout jump when the
// consumer switches) but paints nothing.
'frame-padding': 'var(--space-2)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'surface-bg': 'var(--color-surface-default)',
// Vertical orientation needs a bounded block axis or the viewport
// collapses and the slides spill; this is that floor.
'vertical-block-size': '16rem',
// The dot strip.
'indicator-group-padding-block': 'var(--space-1)',
'indicator-bg': 'var(--color-surface-overlay)',
'indicator-radius': 'var(--radius-full)',
// The active dot stretches into a pill; the factor is what makes it read
// as "you are here" rather than as a bigger dot.
'active-indicator-scale': '2.25',
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' }, '_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-element': { value: 'var(--color-primary-element)', scope: 'host' } '_palette-element': { value: 'var(--color-primary-element)', scope: 'host' }
}, },

@ -9,10 +9,11 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { carouselMorfo } from '@/uix/morfo/components/carousel'; import { carouselMorfo } from '@/uix/morfo/components/carousel';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y' | 'tokens';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
@ -381,6 +382,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')} <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}
>Recipe</button >Recipe</button
> >
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>30</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button> <button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div> </div>
@ -1155,6 +1159,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="carousel" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'a11y'} {#if tab === 'a11y'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2> <h2 data-uix-section-title>Accessibility</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.