< script lang = "ts" >
/**
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
* Depth demo — the Eidos depth channel, made visceral, at reference depth.
*
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
* Not abstract tiles: real UI that RESPONDS, plus the full catalog surface.
* 1. Reacciona — cards lift on hover (state moment).
* 2. Estados — depth responds to EVERY interaction state (dynamic elevation).
* 3. Asciende — a dialog RISES over a scrim on open (event moment: `present-rise`).
* 4. Escalera — the 5 planes as an ascending z-stack (the elevation scale).
* 5. Cada plano… — the planes shown as the real components they're for.
* 6. Catálogo — resting-plane table (the Material-style spec, our vocabulary).
* 7. Luz vs sombra — light/dark side-by-side: how the cue adapts to the mode.
* 8. Jaula abierta — retune the depth cue live (escape hatch).
* 9. Accesibilidad — depth is never the only signal; degrades by preference.
*
* Self-contained: eidos foundation (planes + firma) + `data-theme`.
*/
import '@/uix/eidos/index.css'
let theme = $state< 'light' | 'dark'>('dark')
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
// Mirror the theme onto < html > . The `--depth-*` foundation tokens live at
// `:root` and reference `var(--color-surface-*)` / `var(--shadow-*)`; custom
// properties resolve var() at their declaration site, so the theme must be
// active at `:root` (not just on a descendant div) for the depth surface +
// shadow to stay mode-aware. This is the faithful real-app setup.
$effect(() => {
const el = document.documentElement
const prev = el.getAttribute('data-theme')
el.setAttribute('data-theme', theme)
return () => {
if (prev === null) el.removeAttribute('data-theme')
else el.setAttribute('data-theme', prev)
}
})
// ── Ascenso: a dialog that rises over a scrim (the event moment) ────────────
let open = $state(false)
let evt = $state< { event? : string ; phase? : string } >({} )
function summon(): void {
open = true
evt = {}
requestAnimationFrame(() => {
evt = { event : 'present' , phase : 'active' }
})
}
function dismiss(): void {
open = false
evt = {}
}
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
// ── Jaula abierta: retune the depth cue live (escape hatch) ─────────────────
const SHADOW_VARIANTS = [
{ key : 'canon' , label : 'canónica' , shadow : 'var(--depth-overlay-shadow)' } ,
{ key : 'intense' , label : 'intensa' , shadow : '0 30px 70px rgb(0 0 0 / 0.5)' } ,
{
key: 'brand',
label: 'de marca',
shadow: '0 24px 56px color-mix(in oklab, var(--color-primary-solid) 55%, transparent)'
},
{ key : 'raw' , label : 'primitivo crudo' , shadow : 'var(--shadow-3)' }
] as const
let shadowKey = $state< string > ('canon')
const activeShadow = $derived(
SHADOW_VARIANTS.find((v) => v.key === shadowKey)?.shadow ?? SHADOW_VARIANTS[0].shadow
)
// ── Estados de interacción: la profundidad responde a CADA estado (dynamic elevation) ──
const STATE_LADDER = [
{ state : 'reposo' , moves : 'en su plano' , shadow : 'var(--depth-raised-shadow)' , lift : '0px' } ,
{ state : 'hover' , moves : 'sube un plano' , shadow : 'var(--depth-overlay-shadow)' , lift : '-5px' } ,
{ state : 'focus' , moves : 'sube + anillo' , shadow : 'var(--depth-overlay-shadow)' , lift : '-5px' } ,
{ state : 'pressed' , moves : 'se hunde' , shadow : 'var(--depth-raised-shadow)' , lift : '1px' } ,
{ state : 'disabled' , moves : 'sin relieve' , shadow : 'none' , lift : '0px' }
] as const
// ── Catálogo de planos en reposo (resting elevation — el equivalente a la tabla de Material) ──
const PLANE_CATALOG = [
{ plane : 'recessed' , z : 'base' , use : 'inputs activos · pozos · tracks · sliders' } ,
{ plane : 'flush' , z : 'base' , use : 'chips · badges · separadores · botones planos' } ,
{ plane : 'raised' , z : 'raised' , use : 'tarjetas · paneles · app bar · acordeones' } ,
{ plane : 'overlay' , z : 'popover' , use : 'menús · popovers · tooltips · selects · toasts' } ,
{ plane : 'modal' , z : 'modal' , use : 'diálogos · sheets · drawers · command palette' }
] as const
// The 5 planes bottom→top, for the z-stack ladder.
const PLANE_LADDER = ['recessed', 'flush', 'raised', 'overlay', 'modal'] as const
// The rim-light halo (canonical overlay value) — written inline in the light/dark panels so
// each resolves against its own nested `data-theme` (the `--depth-*` tokens can't, see $effect).
const RIM_HALO = 'inset 0 1px 0 0 color-mix(in oklab, white 7%, transparent)'
< / script >
< svelte:head > < title > Profundidad · Eidos< / title > < / svelte:head >
< div class = "root" data-theme = { theme } >
< header class = "masthead" >
< div class = "mast-row" >
< p class = "kicker" > Eidos · canal de profundidad< / p >
< button class = "theme-toggle" onclick = {() => ( theme = theme === 'light' ? 'dark' : 'light' )} >
{ theme === 'light' ? '◐ claro' : '◑ oscuro' }
< / button >
< / div >
< h1 class = "mast-title" > La profundidad < em > ocurre< / em > < / h1 >
< p class = "mast-lede" >
No es una propiedad que un elemento < i > tiene< / i > — es algo que < i > sucede< / i > . Mira: la
profundidad < strong > responde< / strong > al cursor y < strong > asciende< / strong > cuando algo emerge.
< / p >
< / header >
<!-- ── 1 · Reacciona (estado · hover) ─────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Reacciona< / h2 >
< p class = "sec-note" > Pasa el cursor — cada tarjeta sube de plano (raised → overlay): la sombra crece y se eleva.< / p >
< / div >
< div class = "lift-row" >
{ #each [{ t : 'Ingresos' , v : '€48.2k' , d : '+12% este mes' , c : 'fulfill' }, { t : 'Sesiones' , v : '9 312' , d : '+4% vs ayer' , c : 'primary' }, { t : 'Alertas' , v : '3' , d : 'requieren acción' , c : 'risk' }] as card }
< article class = "lift-card" data-depth = "raised" >
< span class = "lift-tag" data-c = { card . c } > { card . t } </span >
< span class = "lift-value" > { card . v } </ span >
< span class = "lift-delta" > { card . d } </ span >
< / article >
{ /each }
< / div >
< / section >
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
<!-- ── 2 · Estados (la profundidad responde a CADA estado) ──────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Responde a cada estado< / h2 >
< p class = "sec-note" >
No solo al hover. Reposo, foco, pulsado, deshabilitado — la profundidad < em > cambia con cada
estado< / em > . Donde Material publica una tabla de niveles por estado, aquí es un solo canal que
reacciona. El último control es real: pásale el cursor, enfócalo con < kbd > Tab< / kbd > , púlsalo.
< / p >
< / div >
< div class = "states" >
{ #each STATE_LADDER as s }
< figure class = "state" >
< div class = "state-stage" >
< span
class="state-chip"
data-state={ s . state }
style="box-shadow: { s . shadow } ; translate: 0 { s . lift } ;"
>Guardar< / span >
< / div >
< figcaption >< b > { s . state } </ b > · { s . moves } </ figcaption >
< / figure >
{ /each }
< figure class = "state" >
< div class = "state-stage" >
< button class = "state-chip state-btn" > Guardar< / button >
< / div >
< figcaption > < b > en vivo< / b > · hover · foco · pulsa< / figcaption >
< / figure >
< / div >
< / section >
<!-- ── 3 · Asciende (evento · la firma) ───────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Asciende< / h2 >
< p class = "sec-note" >
Abre el panel: < em > asciende< / em > sobre el contenido (que recede tras el scrim). La sombra
crece desde plano hasta su elevación — la < em > firma< / em > del evento, no un estilo fijo.
< / p >
< / div >
< div class = "stage" >
< div class = "stage-app" class:dimmed = { open } >
< div class = "app-bar" >
< span class = "app-dot" > < / span > < span class = "app-dot" > < / span > < span class = "app-dot" > < / span >
< span class = "app-title" > panel · proyecto< / span >
< / div >
{ #each [ 'Revisar borrador de diseño' , 'Publicar el sprint de profundidad' , 'Cerrar incidencias abiertas' ] as row , i }
< div class = "app-row" >< span class = "app-check" data-on = { i === 1 } > </span > { row } </ div >
{ /each }
< / div >
{ #if open }
< button class = "scrim" aria-label = "cerrar" onclick = { dismiss } > </button >
< div
class="risen"
data-depth="modal"
data-event={ evt . event }
data-event-phase={ evt . phase }
>
< h3 class = "risen-title" > Acabo de ascender< / h3 >
< p class = "risen-body" >
Mi sombra creció desde la superficie hasta el plano < code > modal< / code > mientras subía — y
el contenido de detrás recedió. Eso es la profundidad < em > ocurriendo< / em > .
< / p >
< div class = "risen-actions" >
< button class = "btn btn-primary" onclick = { dismiss } > Entendido</button >
< button class = "btn btn-ghost" onclick = { dismiss } > Cerrar</button >
< / div >
< / div >
{ /if }
< button class = "stage-trigger" onclick = {() => ( open ? dismiss () : summon ())} >
{ open ? '↓ cerrar' : '↑ abrir panel' }
< / button >
< / div >
< / section >
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
<!-- ── 4 · Escalera de planos (la jerarquía de un vistazo) ──────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > La escalera de planos< / h2 >
< p class = "sec-note" >
Cinco posiciones en la jerarquía de atención, de hundido a cima:
< code > recessed → flush → raised → overlay → modal< / code > . Cada peldaño compone superficie +
sombra + z — míralos trepar hacia ti.
< / p >
< / div >
< div class = "ladder" >
{ #each PLANE_LADDER as plane , i }
< div class = "ladder-step" data-depth = { plane } style="--i: { i } ;" >
< span class = "ladder-name" > { plane } </ span >
< span class = "ladder-z" > z · { PLANE_CATALOG . find (( p ) => p . plane === plane ) ? . z } </ span >
< / div >
{ /each }
< / div >
< / section >
<!-- ── 5 · Cada plano, un rol ─────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Cada plano, un rol< / h2 >
< p class = "sec-note" >
Los 5 planos son los componentes que los necesitan. < code > data-depth< / code > cohere
superficie + sombra + z; en dark, "más arriba" = más claro (mode-aware).
< / p >
< / div >
< div class = "roles" >
<!-- flush -->
< figure class = "role" >
< div class = "role-stage" >
< div class = "chips" >
< span class = "chip" data-depth = "flush" > diseño< / span >
< span class = "chip" data-depth = "flush" > activo< / span >
< span class = "chip" data-depth = "flush" > v2< / span >
< / div >
< / div >
< figcaption > < b > flush< / b > · chips, a ras de superficie< / figcaption >
< / figure >
<!-- raised -->
< figure class = "role" >
< div class = "role-stage" >
< div class = "mini-card" data-depth = "raised" >
< span class = "mini-avatar" > A< / span >
< div > < span class = "mini-name" > Tarjeta< / span > < span class = "mini-sub" > contenido en reposo< / span > < / div >
< / div >
< / div >
< figcaption > < b > raised< / b > · tarjetas, paneles< / figcaption >
< / figure >
<!-- overlay -->
< figure class = "role" >
< div class = "role-stage" >
< div class = "mini-menu" data-depth = "overlay" >
< div class = "menu-item" > Duplicar< / div >
< div class = "menu-item" data-on > Mover a…< / div >
< div class = "menu-item" > Eliminar< / div >
< / div >
< / div >
< figcaption > < b > overlay< / b > · menús, popovers< / figcaption >
< / figure >
<!-- modal -->
< figure class = "role" >
< div class = "role-stage" >
< div class = "mini-dialog" data-depth = "modal" >
< span class = "mini-name" > ¿Eliminar?< / span >
< span class = "mini-sub" > No se puede deshacer.< / span >
< span class = "mini-btn" > Eliminar< / span >
< / div >
< / div >
< figcaption > < b > modal< / b > · diálogos, cima< / figcaption >
< / figure >
<!-- recessed -->
< figure class = "role" >
< div class = "role-stage" >
< div class = "mini-field" >
< span class = "field-label" > Buscar< / span >
< span class = "field-input" data-depth = "recessed" > escribe aquí…< / span >
< / div >
< / div >
< figcaption > < b > recessed< / b > · inputs, pozos< / figcaption >
< / figure >
< / div >
< / section >
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
<!-- ── 6 · Catálogo de planos en reposo (la tabla, en nuestro vocabulario) ─ -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Catálogo de planos en reposo< / h2 >
< p class = "sec-note" >
La elevación en reposo de cada familia de componente — el equivalente a la tabla de
especificación de Material, pero en planos nombrados por su rol, no en milímetros.
< / p >
< / div >
< div class = "catalog" role = "table" aria-label = "planos en reposo" >
< div class = "cat-row cat-head" role = "row" >
< span role = "columnheader" > Plano< / span >
< span role = "columnheader" > Banda z< / span >
< span role = "columnheader" > Componentes< / span >
< span role = "columnheader" > Muestra< / span >
< / div >
{ #each PLANE_CATALOG as p }
< div class = "cat-row" role = "row" >
< span class = "cat-plane" role = "cell" >< b > { p . plane } </ b ></ span >
< span class = "cat-z" role = "cell" > { p . z } </ span >
< span class = "cat-use" role = "cell" > { p . use } </ span >
< span class = "cat-sample" role = "cell" >< span class = "cat-tile" data-depth = { p . plane } > </span ></ span >
< / div >
{ /each }
< / div >
< / section >
<!-- ── 7 · Luz vs sombra (cómo la señal se adapta al modo — Fase 2) ─────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Luz vs sombra< / h2 >
< p class = "sec-note" >
La misma tarjeta < code > overlay< / code > , los dos modos a la vez. En claro manda la
< strong > sombra< / strong > proyectada y el halo es invisible. En oscuro la sombra apenas se ve —
toma el relevo el < strong > halo< / strong > : un rim de luz computado en oklab. La señal se
< em > adapta< / em > al modo en vez de mentir.
< / p >
< / div >
< div class = "modes" >
< div class = "mode-panel" data-theme = "light" >
< div class = "mode-card" style = "box-shadow: var(--shadow-overlay), { RIM_HALO } ;" >
< span class = "mode-label" > claro< / span >
< span class = "mode-cue" > la sombra define el relieve< / span >
< / div >
< / div >
< div class = "mode-panel" data-theme = "dark" >
< div class = "mode-card" style = "box-shadow: var(--shadow-overlay), { RIM_HALO } ;" >
< span class = "mode-label" > oscuro< / span >
< span class = "mode-cue" > el halo toma el relevo< / span >
< / div >
< / div >
< / div >
< / section >
<!-- ── 8 · La jaula abierta (escape hatch en vivo) ────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > La jaula abierta< / h2 >
< p class = "sec-note" >
Opinión fuerte que < em > nunca< / em > atrapa. El plano < code > overlay< / code > trae una sombra
canónica — pero la mezcla es tuya: retúnea por config, anula por componente, o baja al
primitivo crudo. La misma tarjeta, cuatro mezclas — cámbiala y míralo en vivo.
< / p >
< / div >
< div class = "cage" >
< div class = "cage-stage" >
< div class = "cage-card" data-depth = "overlay" style = "box-shadow: { activeShadow } " >
< span class = "cage-card-tag" > data-depth="overlay"< / span >
< span class = "cage-card-title" > Sombra { SHADOW_VARIANTS . find (( v ) => v . key === shadowKey ) ? . label } </ span >
< span class = "cage-card-sub" > el plano no cambia — su mezcla, sí< / span >
< / div >
< / div >
< div class = "cage-panel" >
< div class = "cage-chips" role = "group" aria-label = "variante de sombra" >
{ #each SHADOW_VARIANTS as v }
< button
class="cage-chip"
data-active={ v . key === shadowKey }
onclick={() => ( shadowKey = v . key )}
>
{ v . label }
< / button >
{ /each }
< / div >
< code class = "cage-code" > box-shadow: { activeShadow } ;</ code >
< p class = "cage-foot" >
< b > canónica< / b > = el plano sin tocar · < b > de marca< / b > = mezcla OKLCH con el color
primario · < b > primitivo crudo< / b > = < code > var(--shadow-3)< / code > , siempre a un paso. En
una app real esto vive en < code > EidosConfig.depth< / code > o en un override de recipe — el
inline aquí es solo el atajo más corto a la misma puerta.
< / p >
< / div >
< / div >
< / section >
<!-- ── 9 · Accesibilidad (la profundidad nunca es la única señal) ───────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Accesibilidad< / h2 >
< p class = "sec-note" > La profundidad acompaña — nunca carga sola con el significado.< / p >
< / div >
< div class = "a11y" >
{ #each [{ t : 'Nunca el único canal' , d : 'Cada plano va con borde, superficie y posición — no solo sombra. Quítale la sombra y la jerarquía sigue legible.' }, { t : 'Respeta reduced-motion' , d : 'El ascenso eventful (la firma) degrada a un cambio de estado instantáneo cuando se prefiere menos movimiento.' }, { t : 'Sobrevive a forced-colors' , d : 'En alto contraste el box-shadow desaparece; el plano se apoya en borde y superficie, que el modo sí respeta.' }, { t : 'Contraste, no relieve' , d : 'La profundidad no sustituye al contraste de texto: el contenido cumple APCA en cualquier plano.' }] as item }
< div class = "a11y-card" data-depth = "raised" >
< span class = "a11y-title" > { item . t } </ span >
< span class = "a11y-body" > { item . d } </ span >
< / div >
{ /each }
< / div >
< / section >
< footer class = "footnote" >
< p >
< strong > Default fuerte, jaula abierta.< / strong > Planos config-driven, primitivos
(< code > --shadow-*< / code > , < code > z-index< / code > ) a un paso, firma anulable; la capa eventful
es aditiva y degrada con < code > prefers-reduced-motion< / code > . Un solo evento mueve
profundidad + posición + sound + haptic, coordinados.
< / p >
< / footer >
< / div >
< style >
.root {
min-block-size: 100vh;
background:
radial-gradient(130% 90% at 50% -15%, var(--color-surface-raised), transparent 55%),
var(--color-surface-default);
color: var(--color-content-primary);
font-family: var(--font-family-primary);
padding: clamp(1.5rem, 5vw, 5rem) clamp(1.25rem, 5vw, 6rem) 6rem;
--rule: var(--color-border-default);
}
.masthead { max - inline - size : 56rem ; margin - block - end : clamp ( 3 rem , 8 vw , 6 rem ); }
.mast-row { display : flex ; align - items : center ; justify - content : space - between ; gap : 1rem ; margin - block - end : 2.5rem ; }
.kicker { margin : 0 ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); letter - spacing : 0.18em ; text - transform : uppercase ; color : var ( -- color - content - muted ); }
.theme-toggle { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - secondary ); background : var ( -- color - surface - raised ); border : 1px solid var ( -- rule ); border - radius : 999px ; padding : 0.5rem 1 rem ; cursor : pointer ; }
.theme-toggle:hover { color : var ( -- color - content - primary ); }
.mast-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - xxxl ); font - weight : 600 ; line - height : 1 ; letter - spacing : - 0.03 em ; margin : 0 0 1.25 rem ; text - wrap : balance ; }
.mast-title em { font - style : italic ; color : var ( -- color - primary - text , var ( -- color - content - primary )); }
.mast-lede { max - inline - size : 48ch ; font - size : var ( -- font - size - lg ); line - height : 1.5 ; color : var ( -- color - content - secondary ); margin : 0 ; text - wrap : pretty ; }
.mast-lede i { font - style : italic ; }
.mast-lede strong, .sec-note em { color : var ( -- color - content - primary ); font - weight : 600 ; font - style : normal ; }
.section { margin - block - end : clamp ( 3.5 rem , 9 vw , 7 rem ); }
.sec-head { margin - block - end : 2.5rem ; }
.sec-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - xl ); font - weight : 600 ; letter - spacing : - 0.01 em ; margin : 0 0 0.5 rem ; }
.sec-note { margin : 0 ; max - inline - size : 62ch ; font - size : var ( -- font - size - sm ); line - height : 1.55 ; color : var ( -- color - content - muted ); }
code { font - family : var ( -- font - family - mono ); font - size : 0.9em ; color : var ( -- color - content - secondary ); background : var ( -- color - surface - muted ); padding : 0.1em 0.4 em ; border - radius : 4px ; }
/* 1 · Reacciona (hover lift) */
.lift-row { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 14 rem ), 1 fr )); gap : 1.5rem ; }
.lift-card {
background: var(--color-surface-raised);
border: 1px solid var(--rule);
border-radius: var(--radius-lg);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
cursor: default;
transition: box-shadow 240ms var(--ease-out, cubic-bezier(0.2, 0, 0, 1)), translate 240ms var(--ease-out, cubic-bezier(0.2, 0, 0, 1)), border-color 240ms;
}
.lift-card:hover {
box-shadow: var(--depth-overlay-shadow);
translate: 0 -6px;
border-color: color-mix(in srgb, var(--color-primary-solid) 40%, var(--rule));
}
.lift-tag { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); letter - spacing : 0.04em ; text - transform : uppercase ; color : var ( -- color - content - muted ); }
.lift-tag[data-c='fulfill'] { color : var ( -- color - fulfill - text , var ( -- color - content - secondary )); }
.lift-tag[data-c='primary'] { color : var ( -- color - primary - text , var ( -- color - content - secondary )); }
.lift-tag[data-c='risk'] { color : var ( -- color - risk - text , var ( -- color - content - secondary )); }
.lift-value { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - xxl ); line - height : 1 ; letter - spacing : - 0.02 em ; color : var ( -- color - content - primary ); }
.lift-delta { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
@media (prefers-reduced-motion: reduce) { . lift - card { transition : box - shadow 240 ms ; } . lift - card :hover { translate : none ; } }
/* 2 · Asciende (the stage + rising dialog + scrim) */
.stage {
position: relative;
overflow: hidden;
border-radius: var(--radius-lg);
border: 1px solid var(--rule);
background: var(--color-surface-default);
min-block-size: 22rem;
padding: clamp(1.25rem, 3vw, 2rem);
}
.stage-app { transition : filter 280 ms var ( -- ease - out , ease ), opacity 280 ms ; max - inline - size : 30rem ; }
.stage-app.dimmed { filter : saturate ( 0.7 ); opacity : 0.5 ; }
.app-bar { display : flex ; align - items : center ; gap : 0.4rem ; padding - block - end : 1rem ; margin - block - end : 1rem ; border - block - end : 1px solid var ( -- rule ); }
.app-dot { inline - size : 0.6rem ; block - size : 0.6rem ; border - radius : 50 % ; background : var ( -- color - surface - muted ); }
.app-title { margin - inline - start : 0.5rem ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.app-row { display : flex ; align - items : center ; gap : 0.75rem ; padding - block : 0.6rem ; font - size : var ( -- font - size - sm ); color : var ( -- color - content - secondary ); border - block - end : 1px solid color - mix ( in srgb , var ( -- rule ) 50 % , transparent ); }
.app-check { inline - size : 1.05rem ; block - size : 1.05rem ; border - radius : 5px ; border : 1.5px solid var ( -- rule ); flex : none ; }
.app-check[data-on='true'] { background : var ( -- color - primary - solid ); border - color : var ( -- color - primary - solid ); }
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
/* transparent click-catcher only — a dark scrim would swallow the rising dialog's
shadow (the whole point of a depth demo). The content recedes via `.dimmed` instead. */
.scrim {
position: absolute;
inset: 0;
border: 0;
padding: 0;
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
background: transparent;
cursor: pointer;
}
.risen {
position: absolute;
inset-block-start: 50%;
inset-inline-start: 50%;
translate: -50% -50%;
inline-size: min(24rem, calc(100% - 3rem));
background: var(--depth-modal-surface, var(--color-surface-overlay));
border: 1px solid var(--rule);
border-radius: var(--radius-xl);
padding: 1.75rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.risen-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - xl ); margin : 0 ; color : var ( -- color - content - primary ); }
.risen-body { margin : 0 ; font - size : var ( -- font - size - sm ); line - height : 1.55 ; color : var ( -- color - content - secondary ); }
.risen-actions { display : flex ; gap : 0.6rem ; margin - block - start : 0.75rem ; }
.btn { font - family : var ( -- font - family - primary ); font - size : var ( -- font - size - sm ); border - radius : var ( -- radius - md ); padding : 0.55rem 1.1 rem ; cursor : pointer ; border : 1px solid transparent ; }
.btn-primary { background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); }
.btn-ghost { background : transparent ; color : var ( -- color - content - secondary ); border - color : var ( -- rule ); }
.stage-trigger {
position: absolute;
inset-block-end: 1.25rem;
inset-inline-end: 1.25rem;
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
color: var(--color-primary-on-solid, #fff);
background: var(--color-primary-solid);
border: 0;
border-radius: var(--radius-md);
padding: 0.65rem 1.1rem;
cursor: pointer;
z-index: 1;
box-shadow: var(--depth-raised-shadow);
}
/* 3 · Cada plano, un rol */
.roles { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 13 rem ), 1 fr )); gap : clamp ( 1.25 rem , 3 vw , 2.5 rem ); }
.role { margin : 0 ; display : flex ; flex - direction : column ; gap : 1rem ; }
.role-stage { min - block - size : 8rem ; display : grid ; place - items : center ; padding : 1rem ; border - radius : var ( -- radius - lg ); background : var ( -- color - surface - default ); border : 1px solid color - mix ( in srgb , var ( -- rule ) 60 % , transparent ); }
.role figcaption { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.role figcaption b { color : var ( -- color - content - primary ); font - weight : 600 ; }
.chips { display : flex ; flex - wrap : wrap ; gap : 0.5rem ; justify - content : center ; }
.chip { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); padding : 0.3rem 0.7 rem ; border - radius : 999px ; background : var ( -- color - surface - muted ); color : var ( -- color - content - secondary ); border : 1px solid color - mix ( in srgb , var ( -- rule ) 60 % , transparent ); }
.mini-card { display : flex ; align - items : center ; gap : 0.75rem ; padding : 0.9rem 1.1 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - raised ); }
.mini-avatar { inline - size : 2rem ; block - size : 2rem ; border - radius : 50 % ; display : grid ; place - items : center ; background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); font - size : var ( -- font - size - sm ); font - weight : 600 ; }
.mini-name { display : block ; font - size : var ( -- font - size - sm ); font - weight : 600 ; color : var ( -- color - content - primary ); }
.mini-sub { display : block ; font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.mini-menu { inline - size : 9rem ; padding : 0.4rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - overlay ); display : flex ; flex - direction : column ; }
.menu-item { padding : 0.45rem 0.65 rem ; border - radius : var ( -- radius - sm ); font - size : var ( -- font - size - sm ); color : var ( -- color - content - secondary ); }
.menu-item[data-on] { background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); }
.mini-dialog { inline - size : 11rem ; padding : 1rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - overlay ); display : flex ; flex - direction : column ; gap : 0.35rem ; }
.mini-btn { margin - block - start : 0.5rem ; align - self : flex - start ; font - size : var ( -- font - size - xs ); padding : 0.35rem 0.7 rem ; border - radius : var ( -- radius - sm ); background : var ( -- color - threat - solid , var ( -- color - primary - solid )); color : # fff ; }
.mini-field { display : flex ; flex - direction : column ; gap : 0.4rem ; inline - size : 100 % ; max - inline - size : 11rem ; }
.field-label { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.field-input { padding : 0.6rem 0.8 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - muted ); color : var ( -- color - content - muted ); font - size : var ( -- font - size - sm ); }
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla):
- New `halo` cue per plane: a top-edge rim-light computed in oklab
(color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal).
The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light
surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop
shadow barely shows) — the mode-adaptive answer to "shadow lies in dark",
scoped to the depth channel (global --shadow-* untouched).
- Wired through config-types (DepthPlane.halo) + render-css (declare + compose)
+ config validation + STATIC_DEPTH + regenerated generated/base.css.
Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches
Material elevation catalog breadth and adds the two axes it lacks (eventful +
open cage):
- Responde a cada estado — dynamic elevation, live interactive control
- La escalera de planos — z-stack of the 5 planes
- Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary
- Luz vs sombra — light/dark side-by-side showing the halo mechanism
- Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast
- Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware
Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29.
Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow
assertion to the shadow,halo composition). Pre-existing `words` recipe-contract
failures unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
/* 4 · La jaula abierta */
.cage { display : grid ; grid - template - columns : 1fr ; gap : clamp ( 1.5 rem , 4 vw , 3 rem ); align - items : center ; }
@media (min-width: 48rem) { . cage { grid - template - columns : 1.05fr 1 fr ; } }
.cage-stage { min - block - size : 17rem ; display : grid ; place - items : center ; padding : clamp ( 1.5 rem , 4 vw , 3 rem ); border - radius : var ( -- radius - lg ); background : var ( -- color - surface - default ); border : 1px solid color - mix ( in srgb , var ( -- rule ) 60 % , transparent ); }
.cage-card {
inline-size: min(18rem, 100%);
padding: 1.75rem;
border-radius: var(--radius-xl);
background: var(--color-surface-overlay);
border: 1px solid var(--rule);
display: flex;
flex-direction: column;
gap: 0.4rem;
transition: box-shadow 320ms var(--ease-out, cubic-bezier(0.2, 0, 0, 1));
}
.cage-card-tag { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); letter - spacing : 0.02em ; color : var ( -- color - content - muted ); }
.cage-card-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - lg ); font - weight : 600 ; color : var ( -- color - content - primary ); }
.cage-card-sub { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.cage-panel { display : flex ; flex - direction : column ; gap : 1rem ; }
.cage-chips { display : flex ; flex - wrap : wrap ; gap : 0.5rem ; }
.cage-chip { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); padding : 0.45rem 0.9 rem ; border - radius : 999px ; cursor : pointer ; background : var ( -- color - surface - raised ); color : var ( -- color - content - secondary ); border : 1px solid var ( -- rule ); transition : color 160 ms , background 160 ms , border - color 160 ms ; }
.cage-chip:hover { color : var ( -- color - content - primary ); }
.cage-chip[data-active='true'] { background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); border - color : var ( -- color - primary - solid ); }
.cage-code { display : block ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); line - height : 1.5 ; padding : 0.85rem 1 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - muted ); color : var ( -- color - content - secondary ); word - break : break - all ; }
.cage-foot { margin : 0 ; font - size : var ( -- font - size - xs ); line - height : 1.6 ; color : var ( -- color - content - muted ); }
.cage-foot b { color : var ( -- color - content - secondary ); font - weight : 600 ; }
/* 2 · Estados (dynamic elevation) */
.states { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 8.5 rem ), 1 fr )); gap : 1.25rem ; }
.state { margin : 0 ; display : flex ; flex - direction : column ; gap : 0.75rem ; }
.state-stage { min - block - size : 7rem ; display : grid ; place - items : center ; padding : 1rem ; border - radius : var ( -- radius - lg ); background : var ( -- color - surface - default ); border : 1px solid color - mix ( in srgb , var ( -- rule ) 60 % , transparent ); }
.state-chip { font - family : var ( -- font - family - primary ); font - size : var ( -- font - size - sm ); font - weight : 600 ; padding : 0.6rem 1.15 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - raised ); color : var ( -- color - content - primary ); border : 1px solid var ( -- rule ); }
.state-chip[data-state='focus'] { outline : 2px solid var ( -- color - primary - solid ); outline - offset : 2px ; }
.state-chip[data-state='pressed'] { background : var ( -- color - surface - muted ); }
.state-chip[data-state='disabled'] { opacity : 0.45 ; }
.state-btn { cursor : pointer ; box - shadow : var ( -- depth - raised - shadow ); transition : box - shadow 200 ms var ( -- ease - out , cubic - bezier ( 0.2 , 0 , 0 , 1 )), translate 200 ms var ( -- ease - out , cubic - bezier ( 0.2 , 0 , 0 , 1 )); }
.state-btn:hover { box - shadow : var ( -- depth - overlay - shadow ); translate : 0 - 5 px ; }
.state-btn:focus-visible { box - shadow : var ( -- depth - overlay - shadow ); translate : 0 - 5 px ; outline : 2px solid var ( -- color - primary - solid ); outline - offset : 2px ; }
.state-btn:active { box - shadow : var ( -- depth - raised - shadow ); translate : 0 1 px ; }
.state figcaption { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.state figcaption b { color : var ( -- color - content - primary ); font - weight : 600 ; text - transform : capitalize ; }
@media (prefers-reduced-motion: reduce) { . state - btn { transition : box - shadow 200 ms ; } . state - btn :hover , . state - btn :focus - visible , . state - btn :active { translate : none ; } }
kbd { font - family : var ( -- font - family - mono ); font - size : 0.85em ; padding : 0.1em 0.4 em ; border - radius : 4px ; background : var ( -- color - surface - raised ); border : 1px solid var ( -- rule ); color : var ( -- color - content - secondary ); }
/* 4 · Escalera de planos (z-stack) */
.ladder { display : flex ; flex - direction : column ; align - items : center ; padding : clamp ( 2 rem , 7 vw , 5 rem ) 1 rem clamp ( 3 rem , 8 vw , 6 rem ); }
.ladder-step {
inline-size: min(20rem, 80%);
padding: 1rem 1.4rem;
border-radius: var(--radius-lg);
background: var(--color-surface-raised);
border: 1px solid var(--rule);
display: flex;
align-items: center;
justify-content: space-between;
translate: calc(var(--i) * 1.4rem - 2.8rem) calc(var(--i) * -0.35rem);
margin-block-start: -0.6rem;
}
.ladder-step:first-child { margin - block - start : 0 ; }
.ladder-name { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - sm ); color : var ( -- color - content - primary ); font - weight : 600 ; }
.ladder-z { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
/* 6 · Catálogo (resting elevation table) */
.catalog { display : flex ; flex - direction : column ; border : 1px solid var ( -- rule ); border - radius : var ( -- radius - lg ); overflow : hidden ; background : var ( -- color - surface - raised ); }
.cat-row { display : grid ; grid - template - columns : 7rem 5 rem 1 fr 4 rem ; gap : 1rem ; align - items : center ; padding : 0.9rem 1.25 rem ; border - block - end : 1px solid color - mix ( in srgb , var ( -- rule ) 50 % , transparent ); }
.cat-row:last-child { border - block - end : 0 ; }
.cat-head { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); letter - spacing : 0.04em ; text - transform : uppercase ; color : var ( -- color - content - muted ); background : var ( -- color - surface - muted ); }
.cat-plane { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - sm ); }
.cat-plane b { color : var ( -- color - content - primary ); font - weight : 600 ; }
.cat-z { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - secondary ); }
.cat-use { font - size : var ( -- font - size - sm ); color : var ( -- color - content - secondary ); }
.cat-sample { display : flex ; justify - content : flex - end ; }
.cat-tile { inline - size : 3rem ; block - size : 1.75rem ; border - radius : var ( -- radius - sm ); background : var ( -- color - surface - default ); border : 1px solid color - mix ( in srgb , var ( -- rule ) 50 % , transparent ); }
@media (max-width: 34rem) {
.cat-row { grid - template - columns : 6rem 1 fr ; }
.cat-z, .cat-sample, .cat-head span:nth-child(2), .cat-head span:nth-child(4) { display : none ; }
}
/* 7 · Luz vs sombra (mode-adaptive cue) */
.modes { display : grid ; grid - template - columns : 1fr ; gap : 1.5rem ; }
@media (min-width: 38rem) { . modes { grid - template - columns : 1fr 1 fr ; } }
.mode-panel { border - radius : var ( -- radius - lg ); padding : clamp ( 2 rem , 6 vw , 4 rem ); display : grid ; place - items : center ; min - block - size : 14rem ; background : var ( -- color - surface - default ); border : 1px solid var ( -- color - border - default ); }
.mode-card { inline - size : min ( 16 rem , 100 % ); padding : 1.5rem ; border - radius : var ( -- radius - lg ); background : var ( -- color - surface - overlay ); border : 1px solid var ( -- color - border - default ); display : flex ; flex - direction : column ; gap : 0.35rem ; }
.mode-label { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); text - transform : uppercase ; letter - spacing : 0.08em ; color : var ( -- color - content - muted ); }
.mode-cue { font - size : var ( -- font - size - sm ); color : var ( -- color - content - primary ); }
/* 9 · Accesibilidad */
.a11y { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 15 rem ), 1 fr )); gap : 1.25rem ; }
.a11y-card { background : var ( -- color - surface - raised ); border : 1px solid var ( -- rule ); border - radius : var ( -- radius - lg ); padding : 1.4rem ; display : flex ; flex - direction : column ; gap : 0.5rem ; }
.a11y-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - md ); font - weight : 600 ; color : var ( -- color - content - primary ); }
.a11y-body { font - size : var ( -- font - size - sm ); line - height : 1.55 ; color : var ( -- color - content - muted ); }
.footnote { max - inline - size : 60rem ; padding - block - start : 2rem ; border - block - start : 1px solid var ( -- rule ); }
.footnote p { margin : 0 ; font - size : var ( -- font - size - sm ); line - height : 1.6 ; color : var ( -- color - content - muted ); text - wrap : pretty ; }
< / style >