docs(theming): los 21 de la mañana recuperan su README y su pestaña Tokens

La revisión de hoy (`REVIEW-theming-2026-08-23.md`, punto P4) destapó que
NINGUNO de los commits de tokenización de la mañana del 23 hizo los pasos 8 y 9
del protocolo: `git show --stat` de los diecisiete sólo tocaba CSS, base.ts, el
ledger y las fichas. Veintidós componentes tokenizados sin la tabla de tokens en
su README y veintiuno sin la pestaña que la enseña viva.

QUÉ ENTRA

- **README «Talla y tema»** en 22 (los 21 con demo + `surface`, que no tiene
  ruta). La tabla NO está escrita a mano: se DERIVA de `lib/recipes/base.ts` y
  de `theming-sentinel-exceptions.ts`, así que lo que el lector ve es lo que el
  contrato dice. Hasta 30 claves va la tabla completa clave → valor por defecto;
  por encima, agrupada por parte (con la paleta en su propio grupo, que si no se
  come la tabla: button tiene 70 claves de tono de 106). Debajo, lo que el guard
  da por silencioso con su razón medida, y la excepción POR PATRÓN cuando la
  razón es la cascada de paleta.
- **Pestaña `Tokens`** en los 21 con demo, con el conteo real de claves.

VERIFICADO EN EL NAVEGADOR, LOS 21

Cada pestaña abre y lista exactamente sus claves: menubar 35 · field 68 ·
onion-menu 19 · button 106 · callout 34 · empty-state 26 · dropdown-menu 28 ·
context-menu 28 · checkbox 47 · image 23 · anchor-nav 12 · banner 53 ·
date-range-field 7 · time-range-field 7 · tooltip 23 · section 4 · sticky 1 ·
button-group 1 · qr-code 10 · toggle-group 1 · split-button 1.

⚠ Y el verificador mintió DOS veces antes de acertar: primero buscaba el rótulo
de la cabecera del panel en `innerText` (no lo encontraba aunque la tabla estaba
ahí), y después clicaba con `element.click()` tras `waitUntil: 'load'` — la
página aún NO ha hidratado, así que el `onclick` de Svelte no está enganchado y
el clic no hace nada. Con `networkidle` + el clic de Playwright (que espera a
que el nodo sea accionable), los 21 en verde. Es la ley de siempre: el
instrumento miente primero.

`audio-player` queda fuera: no tiene README propio — hueco aparte, anotado.

Verificación: `npm run check` sin errores nuevos en las 21 demos (los 72
globales son ajenos y preexistentes) · `docs:check` 0 · cada README conserva su
estructura de encabezados y **ninguno gana un hunk de prettier** (mi sección se
formateó dentro de su propio recorte, sin tocar lo ajeno).

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

@ -32,6 +32,27 @@ floor = v1). Headless behavior:
spec-precise value for "current position within a page" (NOT `page`). Only spec-precise value for "current position within a page" (NOT `page`). Only
1 of 5 references sets `aria-current` at all. 1 of 5 references sets `aria-current` at all.
## Talla y tema
**12 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `anchor-nav`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--anchor-nav-…`) | Valor por defecto |
| ------------------------- | --------------------------------- |
| `indent` | `var(--space-3)` |
| `rail-width` | `2px` |
| `rail-fg` | `var(--color-border-subtle)` |
| `active-rail-fg` | `var(--color-primary-solid)` |
| `font-size` | `var(--size-sm-font-size)` |
| `line-height` | `var(--size-sm-font-line-height)` |
| `link-padding-block` | `var(--space-1)` |
| `link-radius` | `var(--radius-sm)` |
| `link-fg` | `var(--color-content-muted)` |
| `hover-link-fg` | `var(--color-content-primary)` |
| `active-link-fg` | `var(--color-primary-text)` |
| `active-link-font-weight` | `var(--font-weight-medium)` |
## Comparativa ## Comparativa
| Ref | Qué adoptamos | Qué no | | Ref | Qué adoptamos | Qué no |

@ -52,6 +52,18 @@ Air's CSS prefix (`air-banner`) is dropped; selectors target
`[data-banner]` directly. CSS variables renamed from `--air-banner-*` to `[data-banner]` directly. CSS variables renamed from `--air-banner-*` to
`--banner-*` (public) and `--_banner-*` (internal recipe-local). `--banner-*` (public) and `--_banner-*` (internal recipe-local).
## Talla y tema
**53 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `banner`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `raíz` | `gap` · `radius` · `line-height` · `border-width` |
| `escalas` | `padding-block-sm` · `padding-block-md` · `padding-block-lg` · `padding-inline-sm` · `padding-inline-md` · `padding-inline-lg` · `font-size-sm` · `font-size-md` · `font-size-lg` |
| `paleta` | `primary-solid` · `primary-contrast` · `primary-track` · `primary-border` · `primary-text` · `secondary-solid` · `secondary-contrast` · `secondary-track` · `secondary-border` · `secondary-text` · `neutral-solid` · `neutral-contrast` · `neutral-track` · `neutral-border` · `neutral-text` · `affirm-solid` · `affirm-contrast` · `affirm-track` · `affirm-border` · `affirm-text` · `fulfill-solid` · `fulfill-contrast` · `fulfill-track` · `fulfill-border` · `fulfill-text` · `risk-solid` · `risk-contrast` · `risk-track` · `risk-border` · `risk-text` · `threat-solid` · `threat-contrast` · `threat-track` · `threat-border` · `threat-text` · `loss-solid` · `loss-contrast` · `loss-track` · `loss-border` · `loss-text` |
## Comparativa ## Comparativa
| Capacidad | UIX (eidos) | Chakra UI Alert | Mantine Alert | MUI Alert | | Capacidad | UIX (eidos) | Chakra UI Alert | Mantine Alert | MUI Alert |

@ -43,6 +43,16 @@ codebase:
roving focus, `commit-toggle` event) with its own `ToggleGroup.Item`; roving focus, `commit-toggle` event) with its own `ToggleGroup.Item`;
ButtonGroup is **passive** and takes real standalone Buttons. ButtonGroup is **passive** and takes real standalone Buttons.
## Talla y tema
**1 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `button-group`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--button-group-…`) | Valor por defecto |
| -------------------------- | ----------------- |
| `divider-strength` | `18%` |
## Comparativa ## Comparativa
| Capability | Chakra v2 | MUI | Mantine | Bootstrap | Radix Themes | **UIX** | | Capability | Chakra v2 | MUI | Mantine | Bootstrap | Radix Themes | **UIX** |

@ -70,6 +70,47 @@ font-size/font-weight/icon-size` por talla (`-xs`…`-xl`), `font-family`,
que la cascada `--button-palette-*` resuelve según `data-color` (piloto del que la cascada `--button-palette-*` resuelve según `data-color` (piloto del
patrón; THEMING §25 admite escalas donantes per-instance). patrón; THEMING §25 admite escalas donantes per-instance).
## Talla y tema
**106 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `button`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `escalas` | `height-xs` · `height-sm` · `height-md` · `height-lg` · `height-xl` · `padding-inline-xs` · `padding-inline-sm` · `padding-inline-md` · `padding-inline-lg` · `padding-inline-xl` · `gap-xs` · `gap-sm` · `gap-md` · `gap-lg` · `gap-xl` · `font-size-xs` · `font-size-sm` · `font-size-md` · `font-size-lg` · `font-size-xl` · `font-weight-xs` · `font-weight-sm` · `font-weight-md` · `font-weight-lg` · `font-weight-xl` · `radius-sm` · `radius-md` · `radius-lg` · `radius-xl` · `radius-full` |
| `raíz` | `font-family` · `line-height` · `letter-spacing` · `border-width` · `shadow` · `transition-duration` · `transition-ease` |
| `icon` | `icon-size-xs` · `icon-size-sm` · `icon-size-md` · `icon-size-lg` · `icon-size-xl` |
| `estado` | `disabled-opacity` |
| `paleta` | `primary-track` · `primary-element` · `primary-border` · `primary-solid` · `primary-solid-hover` · `primary-text` · `primary-contrast` · `secondary-track` · `secondary-element` · `secondary-border` · `secondary-solid` · `secondary-solid-hover` · `secondary-text` · `secondary-contrast` · `neutral-track` · `neutral-element` · `neutral-border` · `neutral-solid` · `neutral-solid-hover` · `neutral-text` · `neutral-contrast` · `affirm-track` · `affirm-element` · `affirm-border` · `affirm-solid` · `affirm-solid-hover` · `affirm-text` · `affirm-contrast` · `fulfill-track` · `fulfill-element` · `fulfill-border` · `fulfill-solid` · `fulfill-solid-hover` · `fulfill-text` · `fulfill-contrast` · `risk-track` · `risk-element` · `risk-border` · `risk-solid` · `risk-solid-hover` · `risk-text` · `risk-contrast` · `threat-track` · `threat-element` · `threat-border` · `threat-solid` · `threat-solid-hover` · `threat-text` · `threat-contrast` · `loss-track` · `loss-element` · `loss-border` · `loss-solid` · `loss-solid-hover` · `loss-text` · `loss-contrast` · `palette-track` · `palette-element` · `palette-border` · `palette-solid` · `palette-solid-hover` · `palette-text` · `palette-contrast` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `icon-size-xs` — the demo button carries no icon; a real <svg> child mounted and the size forced -> reaches (14px -> 123px)
- `icon-size-sm` — same, no icon in the demo; mounted -> reaches (16px -> 123px)
- `icon-size-md` — same, no icon in the demo; mounted -> reaches (18px -> 123px)
- `icon-size-lg` — same, no icon in the demo; mounted -> reaches (20px -> 123px)
- `icon-size-xl` — same, no icon in the demo; mounted -> reaches (32px -> 123px)
- `radius-sm` — the radius scale is an opt-in prop ([data-rounded]) and the demo does not set it; forced -> reaches (4px -> 1234px)
- `radius-lg` — same opt-in attribute; forced -> reaches (10px -> 1234px)
- `radius-xl` — same opt-in attribute; forced -> reaches (16px -> 1234px)
- `radius-full` — same opt-in attribute; forced data-rounded=full -> reaches (9999px -> 1234px)
- `transition-duration` — the guard freezes transitions to measure and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s, five properties)
- `transition-ease` — same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))
- `disabled-opacity` — no disabled button in the demo; forced data-disabled -> reaches (0.4 -> 0.137)
- `primary-track` — the FALLBACK of the palette forward: it acts only on an instance with NO data-color, and the demo stamps data-color=primary. Removed the attribute -> reaches. See the pattern entry below for…
- `primary-element` — same fallback, same condition
- `primary-border` — same fallback, same condition
- `primary-solid` — same fallback: without data-color it repaints (oklch(0.5556 0.1829 305.86) -> rgb(1,2,3)); with data-color=primary the generic forward rule supersedes it
- `primary-solid-hover` — same fallback, same condition
- `primary-text` — same fallback, same condition
- `primary-contrast` — same fallback, same condition
- `palette-track` — a RESOLVED name, declared on [data-button] by the forward, so a :root write loses BY DESIGN - a theme moves the tone or the scale. Measured on a soft button: from :root nothing, from the nod…
- `palette-element` — same resolved name; the theme moves its coordinate
- `palette-border` — same resolved name; the theme moves its coordinate
- **Por patrón** `/^(secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|element|border|solid|solid-hover|text|contrast)$/` — la cascada de paleta: el bloque genérico `[data-button][data-color]` se emite el ÚLTIMO y gana por orden, así que la pintura sale del `--palette-*` global (§13, pendiente de firma).
## Comparativa ## Comparativa
| Capacidad | Soma/Eidos | React Aria | Radix Themes | Chakra | Mantine | | Capacidad | Soma/Eidos | React Aria | Radix Themes | Chakra | Mantine |

@ -24,6 +24,25 @@ rule (parity floor = v1).
33 donor scales AND raw CSS colors resolve through the THM-2 shared layer 33 donor scales AND raw CSS colors resolve through the THM-2 shared layer
with zero extra CSS. with zero extra CSS.
## Talla y tema
**34 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `callout`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `raíz` | `gap` · `padding` · `accent-width` · `radius` · `font-size` · `line-height` · `fg` |
| `row` | `row-gap` |
| `icon` | `icon-size` |
| `title` | `title-font-weight` |
| `paleta` | `primary-track` · `primary-text` · `primary-solid` · `secondary-track` · `secondary-text` · `secondary-solid` · `neutral-track` · `neutral-text` · `neutral-solid` · `affirm-track` · `affirm-text` · `affirm-solid` · `fulfill-track` · `fulfill-text` · `fulfill-solid` · `risk-track` · `risk-text` · `risk-solid` · `threat-track` · `threat-text` · `threat-solid` · `loss-track` · `loss-text` · `loss-solid` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- **Por patrón** `/^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|text|solid)$/` — la cascada de paleta: el bloque genérico `[data-callout][data-color]` se emite el ÚLTIMO y gana por orden, así que la pintura sale del `--palette-*` global (§13, pendiente de firma).
## Comparativa ## Comparativa
| Ref | Equivalente | Qué adoptamos | Qué no | | Ref | Equivalente | Qué adoptamos | Qué no |

@ -23,6 +23,40 @@ Radix, Bits UI, Reka/Ark y shadcn-svelte.
Partes publicas: `Indicator`, `HiddenInput`, `Group`, `GroupLabel`. Partes publicas: `Indicator`, `HiddenInput`, `Group`, `GroupLabel`.
## Talla y tema
**47 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `checkbox`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `raíz` | `size-xs-box-size` · `size-xs-indicator-size` · `size-xs-indicator-stroke` · `size-sm-box-size` · `size-sm-indicator-size` · `size-sm-indicator-stroke` · `size-md-box-size` · `size-md-indicator-size` · `size-md-indicator-stroke` · `size-lg-box-size` · `size-lg-indicator-size` · `size-lg-indicator-stroke` · `size-xl-box-size` · `size-xl-indicator-size` · `size-xl-indicator-stroke` · `radius` · `border-width` · `transition-duration` · `transition-ease` · `stroke-duration` · `stroke-ease` · `bg-off` · `border-off` · `group-gap` · `group-label-font-family` · `group-label-font-size` · `group-label-font-weight` · `group-label-line-height` · `group-label-fg` |
| `estado` | `invalid-border` · `invalid-ring` · `invalid-ring-gap` · `hover-border-off` · `disabled-bg` · `disabled-border` · `disabled-opacity` · `disabled-indicator-fg` · `disabled-group-label-fg` |
| `indicator` | `indicator-fg` |
| `paleta` | `primary-solid` · `primary-solid-hover` · `neutral-solid` · `neutral-solid-hover` · `secondary-solid` · `secondary-solid-hover` · `affirm-solid` · `affirm-solid-hover` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `size-xs-indicator-stroke` — the glyph exists only inside a CHECKED box and the first checkbox of the demo is empty; measured on a checked one with data-size forced -> reaches (2px -> 7px)
- `size-sm-indicator-stroke` — same, forced -> reaches (2px -> 7px)
- `size-md-indicator-stroke` — same, forced -> reaches (2px -> 7px)
- `size-lg-indicator-stroke` — same, forced -> reaches (2px -> 7px)
- `size-xl-indicator-stroke` — same, forced -> reaches (2px -> 7px)
- `invalid-border` — only under [data-invalid]; forced -> reaches (oklch(0.9555 0.0207 13.86) -> rgb(1,2,3))
- `invalid-ring` — the invalid focus ring: needs [data-invalid] AND :focus-visible, which the guard blurs on purpose; focused by hand -> reaches (the 4px ring turns rgb(1,2,3))
- `invalid-ring-gap` — same double ring; forced -> reaches (the 2px gap turns rgb(4,5,6))
- `disabled-bg` — only under [data-disabled]; forced -> reaches
- `disabled-border` — only under [data-disabled]; forced -> reaches
- `disabled-opacity` — only under [data-disabled]; forced -> reaches (0.4 -> 0.137)
- `disabled-indicator-fg` — needs [data-disabled] AND a checked box (the glyph); forced on a checked one -> reaches
- `disabled-group-label-fg` — the label carries its OWN [data-disabled] - it is not inherited from the group, so forcing the group does nothing; set on the label -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))
- `transition-duration` — the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s)
- `transition-ease` — same instrument limit; measured unfrozen -> reaches
- `stroke-duration` — same, on the check path dash animation; measured unfrozen -> reaches (0.12s -> 11.5s)
- **Por patrón** `/^(primary|secondary|neutral|affirm)-(solid|solid-hover)$/` — la cascada de paleta: el bloque genérico `[data-checkbox][data-color]` se emite el ÚLTIMO y gana por orden, así que la pintura sale del `--palette-*` global (§13, pendiente de firma).
## Comparativa ## Comparativa
| Capacidad | Eidos/Soma | APG | Radix | Bits UI | Reka/Ark | shadcn-svelte | | Capacidad | Eidos/Soma | APG | Radix | Bits UI | Reka/Ark | shadcn-svelte |

@ -68,6 +68,43 @@ Soma owns:
- Same keyboard navigation as DropdownMenu inside the open Content - Same keyboard navigation as DropdownMenu inside the open Content
- Focus management on open / close / sub navigation - Focus management on open / close / sub navigation
## Talla y tema
**28 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `context-menu`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--context-menu-…`) | Valor por defecto |
| ---------------------------- | -------------------------------- |
| `content-fg` | `var(--color-content-primary)` |
| `item-fg` | `var(--color-content-primary)` |
| `item-line-height` | `var(--leading-ui)` |
| `chevron-stroke` | `var(--border-width-medium)` |
| `heading-fg` | `var(--color-content-muted)` |
| `heading-font-weight` | `var(--style-label-font-weight)` |
| `heading-letter-spacing` | `var(--tracking-caps)` |
| `open-trigger-outline-width` | `var(--border-width)` |
| `open-trigger-outline` | `var(--color-border-default)` |
| `open-trigger-radius` | `var(--radius-sm)` |
| `content-min-width` | `12rem` |
| `content-max-height` | `min(60vh, 22rem)` |
| `content-padding` | `var(--space-1)` |
| `content-radius` | `var(--radius-md)` |
| `content-font-size` | `var(--size-sm-font-size)` |
| `item-gap` | `var(--space-2)` |
| `item-height` | `2rem` |
| `item-padding-inline` | `var(--space-2)` |
| `item-padding-block` | `var(--space-1)` |
| `item-radius` | `var(--radius-sm)` |
| `hover-item-bg` | `var(--color-primary-element)` |
| `hover-item-fg` | `var(--color-content-primary)` |
| `item-disabled-opacity` | `0.55` |
| `heading-padding-inline` | `var(--space-2)` |
| `heading-padding-block` | `var(--space-1)` |
| `heading-font-size` | `var(--size-xs-font-size)` |
| `separator-bg` | `var(--color-neutral-separator)` |
| `separator-margin-block` | `var(--space-1)` |
## Comparativa ## Comparativa
| Lib | Trigger source | Long-press (touch) | Shift+F10 | Sub-menus | Cursor anchor | | Lib | Trigger source | Long-press (touch) | Shift+F10 | Sub-menus | Cursor anchor |

@ -82,6 +82,22 @@ WAI-ARIA Spinbutton pattern (per segment):
Soma owns: segment focus / arrow stepping per endpoint, paste Soma owns: segment focus / arrow stepping per endpoint, paste
distribution, validation, hidden form inputs (ISO date strings). distribution, validation, hidden form inputs (ISO date strings).
## Talla y tema
**7 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `date-range-field`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--date-range-field-…`) | Valor por defecto |
| ------------------------------ | ------------------------------ |
| `column-gap` | `var(--space-3)` |
| `row-gap` | `var(--space-1-5)` |
| `label-fg` | `var(--color-content-primary)` |
| `label-font-size` | `var(--size-sm-font-size)` |
| `label-font-weight` | `var(--font-weight-regular)` |
| `separator-fg` | `var(--color-content-muted)` |
| `separator-glyph` | `"'—'"` |
## Comparativa ## Comparativa
| Lib | Two endpoints | Shared validate | Segment scoping | Range tint | | Lib | Two endpoints | Shared validate | Segment scoping | Range tint |

@ -122,6 +122,43 @@ ContextMenu has its own recipe; Menubar's per-menu panel already carries
`data-dropdown-menu-content` and re-uses `DropdownMenu.Item`, so it inherits the `data-dropdown-menu-content` and re-uses `DropdownMenu.Item`, so it inherits the
rhythm + the item preset and only adds the `[data-stagger]` marker. rhythm + the item preset and only adds the `[data-stagger]` marker.
## Talla y tema
**28 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `dropdown-menu`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--dropdown-menu-…`) | Valor por defecto |
| --------------------------- | --------------------------------- |
| `content-z` | `var(--z-index-overlay-floating)` |
| `content-fg` | `var(--color-content-primary)` |
| `item-fg` | `var(--color-content-primary)` |
| `item-line-height` | `var(--leading-ui)` |
| `chevron-stroke` | `var(--border-width-medium)` |
| `chevron-size` | `0.625rem` |
| `heading-fg` | `var(--color-content-muted)` |
| `heading-font-weight` | `var(--style-label-font-weight)` |
| `heading-letter-spacing` | `var(--tracking-caps)` |
| `separator-thickness` | `var(--border-width)` |
| `content-min-width` | `12rem` |
| `content-max-height` | `min(60vh, 22rem)` |
| `content-padding` | `var(--space-1)` |
| `content-radius` | `var(--radius-md)` |
| `content-font-size` | `var(--size-sm-font-size)` |
| `item-gap` | `var(--space-2)` |
| `item-height` | `2rem` |
| `item-padding-inline` | `var(--space-2)` |
| `item-padding-block` | `var(--space-1)` |
| `item-radius` | `var(--radius-sm)` |
| `hover-item-bg` | `var(--color-primary-element)` |
| `hover-item-fg` | `var(--color-content-primary)` |
| `item-disabled-opacity` | `0.55` |
| `heading-padding-inline` | `var(--space-2)` |
| `heading-padding-block` | `var(--space-1)` |
| `heading-font-size` | `var(--size-xs-font-size)` |
| `separator-bg` | `var(--color-neutral-separator)` |
| `separator-margin-block` | `var(--space-1)` |
## Comparativa ## Comparativa
| Lib | Sub-menus | CheckboxItem / RadioItem | CheckboxGroup | Arrow | Modal toggle | | Lib | Sub-menus | CheckboxItem / RadioItem | CheckboxGroup | Arrow | Modal toggle |

@ -26,6 +26,41 @@ reference floor fixed by the research dossier
the title reads ONE discrete bundle size above the component size the title reads ONE discrete bundle size above the component size
(visual hierarchy decoupled from the semantic `level`). (visual hierarchy decoupled from the semantic `level`).
## Talla y tema
**26 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `empty-state`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--empty-state-…`) | Valor por defecto |
| -------------------------- | --------------------------------- |
| `gap` | `var(--space-3)` |
| `actions-gap` | `var(--space-2)` |
| `media-bg` | `var(--color-neutral-track)` |
| `media-fg` | `var(--color-content-muted)` |
| `media-radius` | `var(--radius-lg)` |
| `description-measure` | `45ch` |
| `padding-block-sm` | `var(--space-4)` |
| `padding-block-md` | `var(--space-8)` |
| `padding-block-lg` | `var(--space-12)` |
| `padding-inline-sm` | `var(--space-3)` |
| `padding-inline-md` | `var(--space-4)` |
| `padding-inline-lg` | `var(--space-5)` |
| `title-font-size-sm` | `var(--size-md-font-size)` |
| `title-font-size-md` | `var(--size-lg-font-size)` |
| `title-font-size-lg` | `var(--size-xl-font-size)` |
| `description-font-size-sm` | `var(--size-sm-font-size)` |
| `description-font-size-md` | `var(--size-md-font-size)` |
| `description-font-size-lg` | `var(--size-md-font-size)` |
| `media-glyph-size-sm` | `var(--size-sm-icon-size)` |
| `media-glyph-size-md` | `var(--size-lg-icon-size)` |
| `media-glyph-size-lg` | `var(--size-xl-icon-size)` |
| `title-font-weight` | `var(--font-weight-semibold)` |
| `title-line-height` | `var(--size-sm-font-line-height)` |
| `title-fg` | `var(--color-content-primary)` |
| `description-line-height` | `var(--size-md-font-line-height)` |
| `description-fg` | `var(--color-content-muted)` |
## Comparativa ## Comparativa
| Ref | Anatomy | What we adopted | What we didn't | | Ref | Anatomy | What we adopted | What we didn't |

@ -54,6 +54,43 @@ alinearse con el contrato Soma/Morfo.
| Visual orientation | Eidos | `vertical`, `horizontal`, `responsive` | | Visual orientation | Eidos | `vertical`, `horizontal`, `responsive` |
| Intent color | Eidos | Focus/required accent; invalid sigue usando estado de error | | Intent color | Eidos | Focus/required accent; invalid sigue usando estado de error |
## Talla y tema
**68 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `field`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `escalas` | `gap-xs` · `gap-sm` · `gap-md` · `gap-lg` · `gap-xl` · `control-height-xs` · `control-height-sm` · `control-height-md` · `control-height-lg` · `control-height-xl` · `control-padding-inline-xs` · `control-padding-inline-sm` · `control-padding-inline-md` · `control-padding-inline-lg` · `control-padding-inline-xl` · `control-gap-xs` · `control-gap-sm` · `control-gap-md` · `control-gap-lg` · `control-gap-xl` · `control-font-size-xs` · `control-font-size-sm` · `control-font-size-md` · `control-font-size-lg` · `control-font-size-xl` |
| `raíz` | `font-family` · `floating-label-top` · `floating-label-padding-inline` · `layout-column-gap` · `required-fg` · `optional-fg` · `optional-font-weight` · `control-line-height` · `control-font-weight` · `control-radius` · `control-border-width` · `control-border` · `control-bg` · `control-ghost-border` · `control-ghost-bg` · `control-bg-readonly` · `control-fg` · `control-placeholder-fg` · `affix-fg` · `control-trigger-size` · `control-trigger-radius` · `control-trigger-fg` · `message-line-height` · `helper-fg` · `error-fg` · `transition-duration` · `transition-ease` |
| `label` | `label-gap` · `label-font-weight` · `label-line-height` · `label-fg` |
| `estado` | `invalid-label-fg` · `disabled-label-fg` · `hover-control-border` · `focus-control-border` · `invalid-control-border` · `disabled-control-bg` · `hover-control-trigger-fg` · `focus-control-trigger-fg` · `disabled-opacity` |
| `segment` | `segment-height` · `segment-active-bg` · `segment-active-text` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `invalid-label-fg` — only under [data-invalid] and the demo boots valid; forced -> reaches (oklch(0.5855 0.1743 42.74) -> rgb(1,2,3))
- `disabled-label-fg` — only under [data-disabled]; forced -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))
- `invalid-control-border` — only under [data-invalid]; forced -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1,2,3))
- `disabled-control-bg` — only under [data-disabled]; forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))
- `control-bg-readonly` — only under [data-readonly]; forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))
- `disabled-opacity` — it lives on the CONTROL, not on the field root, so the guard read the wrong node; forced data-disabled and read on the control -> reaches (0.4 -> 0.137)
- `control-ghost-border` — the ghost variant and the demo boots surface; forced data-variant=ghost -> reaches (transparent -> rgb(1,2,3))
- `control-ghost-bg` — same ghost variant; forced -> reaches (transparent -> rgb(1,2,3))
- `layout-column-gap` — only in the horizontal orientation; forced data-orientation=horizontal -> reaches (16px -> 1234px)
- `focus-control-border` — only under :focus-within, and the guard blurs ON PURPOSE (the click-focus poisoning fix); focused from the keyboard -> reaches (the focus ring colour -> rgb(1,2,3))
- `focus-control-trigger-fg` — only under :focus-visible on the picker trigger, which the guard blurs; focused from the keyboard on /date-picker -> reaches (oklch(0.2435 0 0) -> rgb(1,2,3))
- `floating-label-top` — floatingLabel is a demo switch that boots OFF, and the label must also be floated (focused or filled); switch on + focus -> reaches (0px -> 1234px)
- `floating-label-padding-inline` — same switch; turned on -> reaches (4px -> 1234px)
- `error-fg` — the error text only RENDERS when the field is invalid, and it is the demo switch that mounts it - forcing the data-invalid attribute does not make Svelte render the node; switch on -> reache…
- `control-placeholder-fg` — the ::placeholder box exists only when the input HAS a placeholder attribute, and the demo input has none: getComputedStyle then returns the ELEMENT style and the token reads dead. With a pl…
- `segment-active-bg` — the ACTIVE segment being edited: no segment on the field route and none focused; measured on /date-field with the segment clicked -> reaches (the 40% mix -> rgb(1,2,3))
- `segment-active-text` — same segment; measured on /date-field -> reaches (oklch(0.5168 0.1733 305.88) -> rgb(1,2,3))
- `segment-height` — a FAMILY token that field OWNS and the pickers CONSUME - nothing on the field routes reads it - and it is RESOLVED (declared per [data-field][data-size]), so a :root write loses BY DESIGN: a…
- `transition-duration` — the guard freezes transitions to measure and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s)
## Comparativa externa ## Comparativa externa
| Plataforma | Modelo | Eidos/UIX | | Plataforma | Modelo | Eidos/UIX |

@ -45,6 +45,44 @@ estado + passthrough nativo completo.
`placeholderColor` (fondo mientras carga), `width`/`height`. `placeholderColor` (fondo mientras carga), `width`/`height`.
- Textos localizados: `loading` / `error` para AT. - Textos localizados: `loading` / `error` para AT.
## Talla y tema
**23 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `image`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--image-…`) | Valor por defecto |
| ---------------------- | -------------------------------- |
| `size-xs` | `64px` |
| `size-sm` | `96px` |
| `size-md` | `160px` |
| `size-lg` | `240px` |
| `size-xl` | `360px` |
| `radius-none` | `0` |
| `radius-sm` | `var(--radius-sm)` |
| `radius-md` | `var(--radius-md)` |
| `radius-lg` | `var(--radius-lg)` |
| `radius-full` | `var(--radius-full)` |
| `fade-duration` | `var(--duration-normal)` |
| `fade-ease` | `var(--ease-out)` |
| `placeholder-base` | `var(--color-surface-raised)` |
| `skeleton-bg` | `var(--gradient-shimmer)` |
| `shimmer-duration` | `1.8s` |
| `font-family` | `var(--style-label-font-family)` |
| `fallback-fg` | `var(--color-content-secondary)` |
| `fallback-font-size` | `var(--size-sm-font-size)` |
| `fallback-line-height` | `var(--leading-ui)` |
| `error-bg` | `var(--color-surface-raised)` |
| `error-fg` | `var(--color-content-muted)` |
| `error-font-size` | `var(--size-sm-font-size)` |
| `error-line-height` | `var(--leading-ui)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `fade-duration` — the guard freezes transitions and this token IS the fade; measured unfrozen 2026-08-23 -> reaches (0.18s -> 11.5s)
- `fade-ease` — same instrument limit; measured unfrozen -> reaches (cubic-bezier(0, 0, 0.2, 1) -> steps(7))
## Comparativa ## Comparativa
| Capacidad | UIX | Radix Avatar | Chakra Image | Next/Image | | Capacidad | UIX | Radix Avatar | Chakra Image | Next/Image |

@ -53,6 +53,20 @@ las partes internas por-menú ya no son propias — son re-exports de
entre triggers; Home/End extremos; dentro del menú rige el teclado de entre triggers; Home/End extremos; dentro del menú rige el teclado de
DropdownMenu. DropdownMenu.
## Talla y tema
**35 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `menubar`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Grupo | Claves |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `raíz` | `bg` · `border-fg` · `border-width` · `radius` |
| `escalas` | `gap-sm` · `gap-md` · `gap-lg` · `padding-sm` · `padding-md` · `padding-lg` · `height-sm` · `height-md` · `height-lg` |
| `trigger` | `trigger-fg` · `trigger-font-family` · `trigger-font-weight` · `trigger-line-height` · `trigger-font-size-sm` · `trigger-font-size-md` · `trigger-font-size-lg` · `trigger-padding-inline-sm` · `trigger-padding-inline-md` · `trigger-padding-inline-lg` |
| `estado` | `open-trigger-bg` · `open-trigger-fg` · `disabled-trigger-fg` |
| `panel` | `panel-z` · `panel-min-width` · `panel-max-height` · `panel-padding` · `panel-radius` · `panel-fg` · `panel-font-size-sm` · `panel-font-size-md` · `panel-font-size-lg` |
## Comparativa ## Comparativa
| Capacidad | UIX | Radix | Bits UI | shadcn | React Aria | | Capacidad | UIX | Radix | Bits UI | shadcn | React Aria |

@ -161,6 +161,42 @@ APG (el morfo lo cita): trigger `aria-haspopup` → `role="menu"` de
`menuitem`s; la geometría radial y el color por profundidad salen de los `menuitem`s; la geometría radial y el color por profundidad salen de los
engines del framework. engines del framework.
## Talla y tema
**19 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `onion-menu`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--onion-menu-…`) | Valor por defecto |
| ------------------------- | -------------------------------- |
| `trigger-lift` | `var(--fab-lift)` |
| `trigger-shadow` | `var(--fab-shadow)` |
| `trigger-radius` | `var(--radius-full)` |
| `trigger-fg` | `var(--color-content-on-solid)` |
| `sector-shadow` | `var(--shadow-raised)` |
| `sector-ring-fg` | `var(--color-content-on-solid)` |
| `sector-ring-width` | `2.5` |
| `sector-hover-ring-width` | `2` |
| `label-font-family` | `var(--style-label-font-family)` |
| `label-font-weight` | `var(--style-label-font-weight)` |
| `glyph-size` | `42%` |
| `glyph-bar-thickness` | `2px` |
| `glyph-bar-radius` | `var(--radius-full)` |
| `muted-opacity` | `0.85` |
| `hover-ring-opacity` | `0.65` |
| `rim-width` | `2` |
| `rim-glow-width` | `9` |
| `rim-glow-opacity` | `var(--opacity-45)` |
| `rim-glow-blur` | `var(--blur-sm)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `trigger-lift` — the lift TRANSITIONS, so an immediate read after the write returns the old value; hovered with transitions frozen -> reaches (translate 0 -2px -> 0 -123px)
- `rim-width` — the luminous look is not rendered by the component demo; measured on /active/docs/agnt -> reaches (2px -> 17px)
- `rim-glow-width` — same look, same route -> reaches (9px -> 17px)
- `rim-glow-blur` — same look, same route -> reaches (blur(4px) -> blur(13px))
## Comparativa ## Comparativa
| Capacidad | UIX | Pie menus (Blender/juegos) | Marking menus | Radix/Ark/RAC | | Capacidad | UIX | Pie menus (Blender/juegos) | Marking menus | Radix/Ark/RAC |

@ -11,6 +11,34 @@ Themeable, accessible QR code — **own zero-dependency encoder**, framework
No Air baseline — new component (2026-06-12). Nothing in the legacy layers No Air baseline — new component (2026-06-12). Nothing in the legacy layers
rendered QR codes; the encoder itself is also new (`$libs/qr`, zero-dep). rendered QR codes; the encoder itself is also new (`$libs/qr`, zero-dep).
## Talla y tema
**10 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `qr-code`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--qr-code-…`) | Valor por defecto |
| --------------------- | ------------------ |
| `fg` | `#18181b` |
| `bg` | `#ffffff` |
| `size-xs` | `96px` |
| `size-sm` | `128px` |
| `size-md` | `160px` |
| `size-lg` | `200px` |
| `size-xl` | `256px` |
| `radius` | `var(--radius-md)` |
| `overlay-bg` | `#ffffff` |
| `overlay-radius` | `var(--radius-sm)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `size-xs` — one step per instance: the wrapper writes var(--qr-code-size-{k}) inline from the size prop and the demo runs lg. Forced -> reaches (96px -> 123px)
- `size-sm` — same, one step per instance
- `size-md` — same, one step per instance
- `size-xl` — same, one step per instance
- `fg` — painted as the SVG fill of the module path, not as color on the root; read on the path -> reaches (rgb(24,24,27) -> rgb(1,2,3))
## Comparativa — why a new one (vs the references) ## Comparativa — why a new one (vs the references)
| Capability | ark-ui (zag + `uqr`) | qr-code-styling | react-qr-code | **QrCode (ours)** | | Capability | ark-ui (zag + `uqr`) | qr-code-styling | react-qr-code | **QrCode (ours)** |

@ -28,6 +28,25 @@ Origen: `air/components/layout/section`. Adaptación estándar.
`region` sólo cuando lleva `aria-label`/`aria-labelledby`, que es el default `region` sólo cuando lleva `aria-label`/`aria-labelledby`, que es el default
correcto para una página que apila varias. correcto para una página que apila varias.
## Talla y tema
**4 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `section`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--section-…`) | Valor por defecto |
| --------------------- | ----------------------------- |
| `padding-block-sm` | `var(--space-8)` |
| `padding-block-md` | `var(--space-12)` |
| `padding-block-lg` | `var(--space-16)` |
| `padding-block-xl` | `calc(var(--space-16) * 1.5)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `padding-block-sm` — one step per instance; the demo runs md. Forced the step inline -> reaches
- `padding-block-lg` — same, one step per instance
## Comparativa ## Comparativa
| Capacidad | UIX | Radix Themes Section | Chakra | Mantine | | Capacidad | UIX | Radix Themes Section | Chakra | Mantine |

@ -34,6 +34,16 @@ components — nothing reinvented:
icon-only Button) + the menu (`.Content` → `<SplitButton.Menu>`, `.Item`, …). icon-only Button) + the menu (`.Content` → `<SplitButton.Menu>`, `.Item`, …).
- **`<SvgChevron>`** — the default ▾ glyph (rotates while open). - **`<SvgChevron>`** — the default ▾ glyph (rotates while open).
## Talla y tema
**1 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `split-button`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--split-button-…`) | Valor por defecto |
| -------------------------- | ----------------- |
| `trigger-padding-inline` | `var(--space-1)` |
## Comparativa ## Comparativa
| Capability | Fluent UI | Ant `Dropdown.Button` | PrimeReact | Bootstrap | MUI (recipe) | **UIX** | | Capability | Fluent UI | Ant `Dropdown.Button` | PrimeReact | Bootstrap | MUI (recipe) | **UIX** |

@ -30,6 +30,16 @@ Reference floor + reflow doctrine: the research dossier
blesses; `uix.perf`'s reflow detector can prove the demo does zero blesses; `uix.perf`'s reflow detector can prove the demo does zero
forced-reflow — a measurable quality claim no reference library makes. forced-reflow — a measurable quality claim no reference library makes.
## Talla y tema
**1 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `sticky`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--sticky-…`) | Valor por defecto |
| -------------------- | ----------------------- |
| `z-index` | `var(--z-index-sticky)` |
## Comparativa ## Comparativa
| Ref | Equivalente | Qué adoptamos | Qué no | | Ref | Equivalente | Qué adoptamos | Qué no |

@ -115,6 +115,45 @@ espacio de props de soma y clobbeara el estampado).
- **Custom color** (`data-color-custom`) — **diferir**: la vía existe en Card - **Custom color** (`data-color-custom`) — **diferir**: la vía existe en Card
si surge el caso real. si surge el caso real.
## Talla y tema
**25 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `surface`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--surface-…`) | Valor por defecto |
| --------------------- | --------------------------------- |
| `radius` | `var(--radius-lg)` |
| `neutral-track` | `var(--color-neutral-track)` |
| `neutral-solid` | `var(--color-neutral-solid)` |
| `neutral-contrast` | `var(--color-neutral-contrast)` |
| `primary-track` | `var(--color-primary-track)` |
| `primary-solid` | `var(--color-primary-solid)` |
| `primary-contrast` | `var(--color-primary-contrast)` |
| `secondary-track` | `var(--color-secondary-track)` |
| `secondary-solid` | `var(--color-secondary-solid)` |
| `secondary-contrast` | `var(--color-secondary-contrast)` |
| `affirm-track` | `var(--color-affirm-track)` |
| `affirm-solid` | `var(--color-affirm-solid)` |
| `affirm-contrast` | `var(--color-affirm-contrast)` |
| `fulfill-track` | `var(--color-fulfill-track)` |
| `fulfill-solid` | `var(--color-fulfill-solid)` |
| `fulfill-contrast` | `var(--color-fulfill-contrast)` |
| `risk-track` | `var(--color-risk-track)` |
| `risk-solid` | `var(--color-risk-solid)` |
| `risk-contrast` | `var(--color-risk-contrast)` |
| `threat-track` | `var(--color-threat-track)` |
| `threat-solid` | `var(--color-threat-solid)` |
| `threat-contrast` | `var(--color-threat-contrast)` |
| `loss-track` | `var(--color-loss-track)` |
| `loss-solid` | `var(--color-loss-solid)` |
| `loss-contrast` | `var(--color-loss-contrast)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- **Por patrón** `/^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|solid|contrast)$/` — la cascada de paleta: el bloque genérico `[data-surface][data-color]` se emite el ÚLTIMO y gana por orden, así que la pintura sale del `--palette-*` global (§13, pendiente de firma).
## Comparativa ## Comparativa
| | UIX Surface | Chakra `Box bg=` | Radix Themes | MD3 | | | UIX Surface | Chakra `Box bg=` | Radix Themes | MD3 |

@ -63,6 +63,22 @@ Soma posee: composición de un TimeField real por endpoint (valor por slice,
placeholder controlado compartido para que un endpoint no mueva el ancla del placeholder controlado compartido para que un endpoint no mueva el ancla del
otro), validación de rango, flags mergeados con `Field.Provider` (OR). otro), validación de rango, flags mergeados con `Field.Provider` (OR).
## Talla y tema
**7 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `time-range-field`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--time-range-field-…`) | Valor por defecto |
| ------------------------------ | ------------------------------ |
| `column-gap` | `var(--space-3)` |
| `row-gap` | `var(--space-1-5)` |
| `label-fg` | `var(--color-content-primary)` |
| `label-font-size` | `var(--size-sm-font-size)` |
| `label-font-weight` | `var(--font-weight-regular)` |
| `separator-fg` | `var(--color-content-muted)` |
| `separator-glyph` | `"'—'"` |
## Comparativa ## Comparativa
| Lib | TimeRangeField dedicado | Endpoints segmentados | Validación de rango compartida | | Lib | TimeRangeField dedicado | Endpoints segmentados | Validación de rango compartida |

@ -89,6 +89,16 @@ The group itself needs an accessible name (`aria-label` or
`aria-labelledby`) per APG. Soma warns at mount in dev when both are `aria-labelledby`) per APG. Soma warns at mount in dev when both are
missing. missing.
## Talla y tema
**1 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `toggle-group`).
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--toggle-group-…`) | Valor por defecto |
| -------------------------- | ----------------- |
| `gap` | `var(--space-1)` |
## Comparativa ## Comparativa
| Lib | Selection | Variant | Color | Attached | Block | Notes | | Lib | Selection | Variant | Color | Attached | Block | Notes |

@ -7,6 +7,49 @@ Fecha de revision: 2026-05-17.
Air no tenia `tooltip` en `glm-5:src/uix/air/components`; este componente es Air no tenia `tooltip` en `glm-5:src/uix/air/components`; este componente es
una envoltura visual sobre Soma/Morfo, no un port de Air. una envoltura visual sobre Soma/Morfo, no un port de Air.
## Talla y tema
**23 clave(s) pública(s)** en `lib/recipes/base.ts` (bloque `tooltip`), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: la pestaña **Tokens** de su demo lista estas mismas
claves y las resuelve sobre el escenario.
| Token (`--tooltip-…`) | Valor por defecto |
| ---------------------- | -------------------------------------------------------------------------- |
| `content-z` | `var(--z-index-overlay-tooltip)` |
| `max-width` | `22rem` |
| `padding-block-sm` | `var(--space-1)` |
| `padding-inline-sm` | `var(--space-2)` |
| `padding-block-md` | `var(--space-1-5)` |
| `padding-inline-md` | `var(--space-2-5)` |
| `padding-block-lg` | `var(--space-2)` |
| `padding-inline-lg` | `var(--space-3)` |
| `font-size-sm` | `var(--size-sm-font-size)` |
| `font-size-md` | `var(--size-md-font-size)` |
| `font-size-lg` | `var(--size-lg-font-size)` |
| `font-family` | `var(--style-label-font-family)` |
| `font-weight` | `var(--font-weight-regular)` |
| `line-height` | `var(--leading-text)` |
| `radius` | `var(--radius-md)` |
| `border-width` | `var(--border-width)` |
| `bg` | `var(--color-surface-overlay)` |
| `fg` | `var(--color-content-primary)` |
| `border` | `color-mix( in srgb, var(--color-border-default) 84%, var(--tooltip-bg) )` |
| `shadow` | `var(--depth-overlay-shadow), var(--depth-overlay-halo)` |
| `ghost-blur` | `var(--blur-md)` |
| `outline-arrow-bg` | `var(--color-surface-default)` |
| `outline-arrow-border` | `var(--color-border-strong)` |
**Lo que el guard R-5.4 da por silencioso, con su razón medida**
(`scripts/theming-sentinel-exceptions.ts`):
- `content-z` — it lands on the floating WRAPPER, which carries no data-tooltip-\* attr and so is outside the measured set; read on the wrapper -> reaches (90 -> 4321)
- `bg` — SUPERSEDED by the overlay plane: [data-depth=overlay] declares background at the same specificity and later in the cascade, so the recipe loses. Measured: --tooltip-bg moves nothing, --depth…
- `border` — same supersession: --depth-overlay-border repaints it, --tooltip-border does not
- `shadow` — same supersession: --depth-overlay-shadow repaints it, --tooltip-shadow does not
- `font-family` — same plane, the typography half of it (PLAN 12.9, unsigned)
- `line-height` — same plane, same open decision
- `outline-arrow-bg` — the arrow is painted through a SIBLING selector that needs the content carrying data-variant=outline AND the panel open; measured with the demo switch -> reaches (oklch(0.9911 0 0) -> rgb(1,…
## Comparativa ## Comparativa
| Referencia | Superficie relevante | Estado Eidos | | Referencia | Superficie relevante | Estado Eidos |

@ -8,11 +8,13 @@
import MotionPanel from '../../lib/MotionPanel.svelte'; import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte'; import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace } from '../../lib/harness.svelte'; import { DemoTrace } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
// prettier-ignore // prettier-ignore
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
const trace = new DemoTrace(); const trace = new DemoTrace();
@ -173,6 +175,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>
Recipe Recipe
</button> </button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>12</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>
@ -350,6 +355,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="anchor-nav" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -8,8 +8,10 @@
} from '$uix/eidos/components/banner'; } from '$uix/eidos/components/banner';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { bannerMorfo } from '@/uix/morfo/components/banner'; import { bannerMorfo } from '@/uix/morfo/components/banner';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -201,6 +203,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>53</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>
@ -453,6 +458,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="banner" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -9,8 +9,10 @@
import { Button } from '$uix/eidos/components/button'; import { Button } from '$uix/eidos/components/button';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { buttonGroupMorfo } from '@/uix/morfo/components/button-group'; import { buttonGroupMorfo } from '@/uix/morfo/components/button-group';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -184,6 +186,9 @@
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
> >
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>1</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>
@ -452,6 +457,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="button-group" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -25,6 +25,7 @@
import MotionPanel from '../../lib/MotionPanel.svelte'; import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte'; import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace, PALETTE_SCALES } from '../../lib/harness.svelte'; import { DemoTrace, PALETTE_SCALES } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
@ -41,7 +42,8 @@
check: Icon.Check check: Icon.Check
} as const; } as const;
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
const variants: ButtonVariant[] = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain']; const variants: ButtonVariant[] = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain'];
@ -218,6 +220,9 @@
<span data-uix-layer-badge="morfo">morfo</span><span data-uix-tab-count>{partsList.length}p · {actions.length}e</span> <span data-uix-layer-badge="morfo">morfo</span><span data-uix-tab-count>{partsList.length}p · {actions.length}e</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>106</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>
@ -469,6 +474,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="button" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -9,10 +9,12 @@
import MotionPanel from '../../lib/MotionPanel.svelte'; import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte'; import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace } from '../../lib/harness.svelte'; import { DemoTrace } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Stage + System axes ────────────────────────────────────────────── // ── Stage + System axes ──────────────────────────────────────────────
@ -175,6 +177,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>
Recipe Recipe
</button> </button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>34</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>
@ -478,6 +483,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="callout" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -11,11 +11,13 @@
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import { page } from '$app/state'; import { page } from '$app/state';
import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte'; import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
const params = page.url.searchParams; const params = page.url.searchParams;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── State ────────────────────────────────────────────────────────────── // ── State ──────────────────────────────────────────────────────────────
@ -263,6 +265,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>47</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>
@ -570,6 +575,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="checkbox" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -3,10 +3,12 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { contextMenuMorfo } from '@/uix/morfo/components/context-menu'; import { contextMenuMorfo } from '@/uix/morfo/components/context-menu';
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' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
@ -331,6 +333,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>28</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>
@ -932,6 +937,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="context-menu" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -19,10 +19,12 @@
type HourCycle type HourCycle
} from '$libs/days'; } from '$libs/days';
import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte'; import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte';
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' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
type Direction = 'ltr' | 'rtl'; type Direction = 'ltr' | 'rtl';
type ValueProfile = 'date' | 'datetime' | 'zoned'; type ValueProfile = 'date' | 'datetime' | 'zoned';
@ -462,6 +464,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>7</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>
@ -954,6 +959,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="date-range-field" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -3,10 +3,12 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { dropdownMenuMorfo } from '@/uix/morfo/components/dropdown-menu'; import { dropdownMenuMorfo } from '@/uix/morfo/components/dropdown-menu';
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' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
@ -361,6 +363,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>28</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>
@ -1043,6 +1048,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="dropdown-menu" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -15,10 +15,12 @@
import MotionPanel from '../../lib/MotionPanel.svelte'; import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte'; import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace } from '../../lib/harness.svelte'; import { DemoTrace } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Stage + System axes ────────────────────────────────────────────── // ── Stage + System axes ──────────────────────────────────────────────
@ -225,6 +227,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>
Recipe Recipe
</button> </button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>26</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button> <button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div> </div>
@ -522,6 +527,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="empty-state" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -8,8 +8,10 @@
} from '$uix/eidos/components/field'; } from '$uix/eidos/components/field';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { fieldMorfo } from '@/uix/morfo/components/field'; import { fieldMorfo } from '@/uix/morfo/components/field';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type FieldState = 'idle' | 'error' | 'disabled' | 'readonly' | 'loading'; type FieldState = 'idle' | 'error' | 'disabled' | 'readonly' | 'loading';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
@ -209,6 +211,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>68</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>
@ -481,6 +486,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="field" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -12,8 +12,10 @@
import { AspectRatio } from '$uix/eidos/components/aspect-ratio'; import { AspectRatio } from '$uix/eidos/components/aspect-ratio';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { imageMorfo } from '@/uix/morfo/components/image'; import { imageMorfo } from '@/uix/morfo/components/image';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -177,6 +179,9 @@
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
> >
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>23</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>
@ -898,6 +903,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="image" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -9,10 +9,12 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { menubarMorfo } from '@/uix/morfo/components/menubar'; import { menubarMorfo } from '@/uix/morfo/components/menubar';
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' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
@ -433,6 +435,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>35</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>
@ -752,6 +757,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="menubar" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -11,10 +11,12 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { onionMenuMorfo } from '@/uix/morfo/components/onion-menu'; import { onionMenuMorfo } from '@/uix/morfo/components/onion-menu';
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' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -253,6 +255,9 @@
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
> >
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>19</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>
@ -572,6 +577,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="onion-menu" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -7,8 +7,10 @@
} from '$uix/eidos/components/qr-code'; } from '$uix/eidos/components/qr-code';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { qrCodeMorfo } from '@/uix/morfo/components/qr-code'; import { qrCodeMorfo } from '@/uix/morfo/components/qr-code';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'examples' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'examples' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// The encoder takes ANY UTF-8 string — not just URLs. The reader decides how // The encoder takes ANY UTF-8 string — not just URLs. The reader decides how
@ -130,6 +132,9 @@
<span data-uix-tab-count>{partsList.length}p</span> <span data-uix-tab-count>{partsList.length}p</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>10</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>
@ -263,6 +268,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="qr-code" />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -3,8 +3,10 @@
import { Box } from '$uix/eidos/components/box'; import { Box } from '$uix/eidos/components/box';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { sectionMorfo } from '@/uix/morfo/components/section'; import { sectionMorfo } from '@/uix/morfo/components/section';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -149,6 +151,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>4</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>
@ -325,6 +330,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="section" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -7,8 +7,10 @@
} from '$uix/eidos/components/split-button'; } from '$uix/eidos/components/split-button';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { splitButtonMorfo } from '@/uix/morfo/components/split-button'; import { splitButtonMorfo } from '@/uix/morfo/components/split-button';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -193,6 +195,9 @@
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
> >
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>1</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>
@ -473,6 +478,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="split-button" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -8,11 +8,13 @@
import MotionPanel from '../../lib/MotionPanel.svelte'; import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte'; import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace } from '../../lib/harness.svelte'; import { DemoTrace } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
// prettier-ignore // prettier-ignore
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y'; type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Stage + System axes ────────────────────────────────────────────── // ── Stage + System axes ──────────────────────────────────────────────
@ -192,6 +194,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>
Recipe Recipe
</button> </button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>1</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>
@ -397,6 +402,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="sticky" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -8,6 +8,7 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { timeRangeFieldMorfo } from '@/uix/morfo/components/time-range-field'; import { timeRangeFieldMorfo } from '@/uix/morfo/components/time-range-field';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
import { import {
Time, Time,
type TimeValue, type TimeValue,
@ -16,7 +17,8 @@
type HourCycle type HourCycle
} from '$libs/days'; } from '$libs/days';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
type Direction = 'ltr' | 'rtl'; type Direction = 'ltr' | 'rtl';
@ -239,6 +241,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>7</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>
@ -488,6 +493,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="time-range-field" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -12,11 +12,13 @@
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import { page } from '$app/state'; import { page } from '$app/state';
import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte'; import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
const params = page.url.searchParams; const params = page.url.searchParams;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
// ── Live state ────────────────────────────────────────────────── // ── Live state ──────────────────────────────────────────────────
@ -234,6 +236,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button> <button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>1</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>
@ -707,6 +712,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="toggle-group" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

@ -5,12 +5,14 @@
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import type { Side, Align } from '$soma/layers/floating'; import type { Side, Align } from '$soma/layers/floating';
import { page } from '$app/state'; import { page } from '$app/state';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); const uix = getActiveUix();
const params = page.url.searchParams; const params = page.url.searchParams;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens'
| 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number }; type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]); let trace = $state<TraceEntry[]>([]);
@ -272,6 +274,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>23</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>
@ -901,6 +906,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="tooltip" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.