You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
666 lines
35 KiB
666 lines
35 KiB
<script lang="ts">
|
|
/**
|
|
* Depth demo — the Eidos depth channel, made visceral, at reference depth.
|
|
*
|
|
* 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')
|
|
|
|
// 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 = {}
|
|
}
|
|
|
|
// ── 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>
|
|
|
|
<!-- ── 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>
|
|
|
|
<!-- ── 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>
|
|
|
|
<!-- ── 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(3rem, 8vw, 6rem); }
|
|
.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 1rem; 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.03em; margin: 0 0 1.25rem; 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.5rem, 9vw, 7rem); }
|
|
.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.01em; margin: 0 0 0.5rem; }
|
|
.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.4em; border-radius: 4px; }
|
|
|
|
/* 1 · Reacciona (hover lift) */
|
|
.lift-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); 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.02em; 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 240ms; } .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 280ms var(--ease-out, ease), opacity 280ms; 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); }
|
|
|
|
/* 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;
|
|
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.1rem; 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%, 13rem), 1fr)); gap: clamp(1.25rem, 3vw, 2.5rem); }
|
|
.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.7rem; 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.1rem; 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.65rem; 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.7rem; 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.8rem; border-radius: var(--radius-md); background: var(--color-surface-muted); color: var(--color-content-muted); font-size: var(--font-size-sm); }
|
|
|
|
/* 4 · La jaula abierta */
|
|
.cage { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
|
|
@media (min-width: 48rem) { .cage { grid-template-columns: 1.05fr 1fr; } }
|
|
.cage-stage { min-block-size: 17rem; display: grid; place-items: center; padding: clamp(1.5rem, 4vw, 3rem); 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.9rem; border-radius: 999px; cursor: pointer; background: var(--color-surface-raised); color: var(--color-content-secondary); border: 1px solid var(--rule); transition: color 160ms, background 160ms, border-color 160ms; }
|
|
.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 1rem; 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.5rem), 1fr)); 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.15rem; 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 200ms var(--ease-out, cubic-bezier(0.2, 0, 0, 1)), translate 200ms var(--ease-out, cubic-bezier(0.2, 0, 0, 1)); }
|
|
.state-btn:hover { box-shadow: var(--depth-overlay-shadow); translate: 0 -5px; }
|
|
.state-btn:focus-visible { box-shadow: var(--depth-overlay-shadow); translate: 0 -5px; outline: 2px solid var(--color-primary-solid); outline-offset: 2px; }
|
|
.state-btn:active { box-shadow: var(--depth-raised-shadow); translate: 0 1px; }
|
|
.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 200ms; } .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.4em; 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(2rem, 7vw, 5rem) 1rem clamp(3rem, 8vw, 6rem); }
|
|
.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 5rem 1fr 4rem; gap: 1rem; align-items: center; padding: 0.9rem 1.25rem; 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 1fr; }
|
|
.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 1fr; } }
|
|
.mode-panel { border-radius: var(--radius-lg); padding: clamp(2rem, 6vw, 4rem); 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(16rem, 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%, 15rem), 1fr)); 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>
|