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.
svelte-kit-vice/web/routes/temas/grafito/+page.svelte

841 lines
39 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts">
/**
* Grafito — espécimen técnico-editorial del tema.
*
* No es una galería: es un *type specimen* de un sistema de diseño grafito.
* Monocromo + la paleta de 32 como único color. Fundamentos derivados de los
* sets de tokens canónicos del módulo (exhaustivo, sin drift). Componentes
* por familia. Estética en `theme.css` (chrome editorial); los componentes
* se muestran con la tipografía/tokens REALES de Grafito.
*/
import { onMount } from 'svelte'
// ── Componentes (representativos por familia) ─────────────────────
import Button from '$uix/eidos/components/button'
import Toggle from '$uix/eidos/components/toggle'
import ToggleGroup from '$uix/eidos/components/toggle-group'
import Toolbar from '$uix/eidos/components/toolbar'
import Badge from '$uix/eidos/components/badge'
import Avatar from '$uix/eidos/components/avatar'
import Card from '$uix/eidos/components/card'
import Field from '$uix/eidos/components/field'
import Select from '$uix/eidos/components/select'
import Checkbox from '$uix/eidos/components/checkbox'
import RadioGroup from '$uix/eidos/components/radio-group'
import Switch from '$uix/eidos/components/switch'
import Slider from '$uix/eidos/components/slider'
import Banner from '$uix/eidos/components/banner'
import Progress from '$uix/eidos/components/progress'
import Separator from '$uix/eidos/components/separator'
import Tabs from '$uix/eidos/components/tabs'
import Accordion from '$uix/eidos/components/accordion'
import Popover from '$uix/eidos/components/popover'
import Dialog from '$uix/eidos/components/dialog'
import DropdownMenu from '$uix/eidos/components/dropdown-menu'
import ContextMenu from '$uix/eidos/components/context-menu'
import Menubar from '$uix/eidos/components/menubar'
import Tooltip from '$uix/eidos/components/tooltip'
// ── Token key-sets canónicos + metadatos del tema ────────────────
import {
TEXT_SIZES,
RADIUS_KEYS,
SHADOW_KEYS,
CONTAINER_WIDTH_KEYS,
CONTENT_WIDTH_KEYS,
ASPECT_RATIO_KEYS,
BORDER_WIDTH_KEYS,
BORDER_STYLE_KEYS,
DURATION_KEYS,
EASE_KEYS,
MOTION_DISTANCE_KEYS,
MOTION_SCALE_KEYS,
OPACITY_KEYS,
Z_INDEX_KEYS,
ICON_STROKE_WIDTH_KEYS,
SIZES
} from '$uix/eidos'
import { THEME_SCALES, THEME_HIERARCHY, THEME_INTENTS, COLOR_STEPS } from '../_lib/grafito'
// ── Token sets sin export canónico (espejo de los primitives) ────
const SPACE_KEYS = ['0', '0-5', '1', '1-5', '2', '2-5', '3', '3-5', '4', '4-5', '5', '5-5', '6', '7', '8', '10', '12', '16']
const ICON_SIZE_KEYS = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl']
const FONT_FAMILIES = [
{ key: 'primary', label: 'Primary', note: 'Inter — UI / texto' },
{ key: 'secondary', label: 'Secondary', note: 'Inter' },
{ key: 'display', label: 'Display', note: 'Inter — titulares' },
{ key: 'mono', label: 'Mono', note: 'Roboto Mono — código' }
]
const FONT_WEIGHTS = [
{ key: 'regular', value: 400 },
{ key: 'medium', value: 500 },
{ key: 'semibold', value: 600 },
{ key: 'bold', value: 700 }
]
const TYPE_STYLES = [
{ key: 'hero', label: 'Hero', sample: 'Diseña lo que ocurre' },
{ key: 'h1', label: 'Heading 1', sample: 'The quick brown fox' },
{ key: 'h2', label: 'Heading 2', sample: 'The quick brown fox' },
{ key: 'h3', label: 'Heading 3', sample: 'The quick brown fox jumps' },
{ key: 'h4', label: 'Heading 4', sample: 'The quick brown fox jumps over' },
{ key: 'h5', label: 'Heading 5', sample: 'The quick brown fox jumps over' },
{ key: 'h6', label: 'Heading 6', sample: 'The quick brown fox jumps over' },
{ key: 'body', label: 'Body', sample: 'The quick brown fox jumps over the lazy dog.' },
{ key: 'prose', label: 'Prose', sample: 'The quick brown fox jumps over the lazy dog.' },
{ key: 'label', label: 'Label', sample: 'Email address' },
{ key: 'caption', label: 'Caption', sample: 'Last updated 3 minutes ago' },
{ key: 'code', label: 'Code', sample: 'const theme = "grafito"' }
]
const BTN_VARIANTS = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain'] as const
const BTN_SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const
const CHIP_VARIANTS = ['soft', 'solid', 'outline', 'ghost'] as const
const BADGE_COLORS = ['primary', 'neutral', 'affirm', 'fulfill', 'risk', 'threat', 'loss'] as const
// Override por componente: cualquier escala de la paleta.
// A component's color comes from two real axes (NOT arbitrary scale names):
// `color` = hierarchy override, `intent` = evaluative / semantic palette.
const HIERARCHY_OVERRIDES = ['primary', 'secondary', 'neutral'] as const
const INTENT_OVERRIDES = ['affirm', 'fulfill', 'risk', 'threat', 'loss'] as const
// ── Lectura de valores en runtime (precisión) ─────────────────────
let hex = $state<Record<string, string>>({})
let probe = $state<HTMLDivElement | null>(null)
// resolved px metrics keyed by css var name
let resolved = $state<Record<string, string>>({})
function luminance(value: string): number {
const m = value.replace('#', '')
if (m.length < 6) return 0.5
const r = parseInt(m.slice(0, 2), 16) / 255
const g = parseInt(m.slice(2, 4), 16) / 255
const b = parseInt(m.slice(4, 6), 16) / 255
return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
const stepInk = (key: string) => (luminance(hex[key] ?? '#000000') > 0.55 ? '#101114' : '#ffffff')
function read() {
if (typeof document === 'undefined') return
const cs = getComputedStyle(document.documentElement)
// scale hexes
const out: Record<string, string> = {}
for (const sc of THEME_SCALES) {
for (const step of COLOR_STEPS) {
const v = cs.getPropertyValue(`--scale-${sc.name}-${step}`).trim()
if (v) out[`${sc.name}-${step}`] = v
}
}
hex = out
// resolved px metrics via probe
if (!probe) return
const res: Record<string, string> = {}
const measure = (cssVar: string, prop: 'width' | 'height' | 'fontSize') => {
probe!.style.setProperty('--m', `var(${cssVar})`)
probe!.style.width = prop === 'width' ? 'var(--m)' : ''
probe!.style.height = prop === 'height' ? 'var(--m)' : ''
probe!.style.fontSize = prop === 'fontSize' ? 'var(--m)' : ''
const pc = getComputedStyle(probe!)
res[cssVar] = prop === 'width' ? pc.width : prop === 'height' ? pc.height : pc.fontSize
}
for (const k of SPACE_KEYS) measure(`--space-${k}`, 'width')
for (const k of SIZES) measure(`--control-height-${k}`, 'height')
for (const k of TEXT_SIZES) measure(`--font-size-${k}`, 'fontSize')
for (const k of ICON_SIZE_KEYS) measure(`--icon-size-${k}`, 'width')
resolved = res
}
const raw = (cssVar: string) => {
if (typeof document === 'undefined') return ''
return getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim()
}
let rawState = $state<Record<string, string>>({})
function readRaw() {
const o: Record<string, string> = {}
for (const k of RADIUS_KEYS) o[`--radius-${k}`] = raw(`--radius-${k}`)
for (const k of BORDER_WIDTH_KEYS) o[`--border-width-${k}`] = raw(`--border-width-${k}`)
for (const k of SHADOW_KEYS) o[`--shadow-${k}`] = raw(`--shadow-${k}`)
for (const k of CONTAINER_WIDTH_KEYS) o[`--container-width-${k}`] = raw(`--container-width-${k}`)
for (const k of CONTENT_WIDTH_KEYS) o[`--content-width-${k}`] = raw(`--content-width-${k}`)
for (const k of ASPECT_RATIO_KEYS) o[`--aspect-ratio-${k}`] = raw(`--aspect-ratio-${k}`)
for (const k of OPACITY_KEYS) o[`--opacity-${k}`] = raw(`--opacity-${k}`)
for (const k of Z_INDEX_KEYS) o[`--z-index-${k}`] = raw(`--z-index-${k}`)
for (const k of DURATION_KEYS) o[`--duration-${k}`] = raw(`--duration-${k}`)
for (const k of MOTION_DISTANCE_KEYS) o[`--motion-distance-${k}`] = raw(`--motion-distance-${k}`)
for (const k of ICON_STROKE_WIDTH_KEYS) o[`--icon-stroke-width-${k}`] = raw(`--icon-stroke-width-${k}`)
rawState = o
}
// ── TOC + scrollspy ───────────────────────────────────────────────
const TOC = [
{
group: 'Color',
items: [{ id: 'color', n: '01', label: 'Color & paleta' }]
},
{
group: 'Fundamentos',
items: [
{ id: 'type', n: '02', label: 'Tipografía' },
{ id: 'space', n: '03', label: 'Espaciado & tamaño' },
{ id: 'shape', n: '04', label: 'Radio · borde · sombra' },
{ id: 'icon', n: '05', label: 'Iconos & focus' },
{ id: 'layout', n: '06', label: 'Layout' },
{ id: 'system', n: '07', label: 'Z-index · opacidad · motion' },
{ id: 'axes', n: '08', label: 'Densidad & scaling' }
]
},
{
group: 'Componentes',
items: [
{ id: 'actions', n: '09', label: 'Acciones' },
{ id: 'forms', n: '10', label: 'Formulario' },
{ id: 'feedback', n: '11', label: 'Feedback' },
{ id: 'nav', n: '12', label: 'Navegación & disclosure' },
{ id: 'overlays', n: '13', label: 'Overlays & menús' }
]
},
{ group: 'Sistema', items: [{ id: 'sema', n: '14', label: 'Semántica' }] }
]
let activeSection = $state('color')
// ── Estado de controles interactivos ─────────────────────────────
let email = $state('olivia@grafito.dev')
let fruit = $state<string[]>(['mango'])
let agree = $state(true)
let plan = $state('pro')
let wifi = $state(true)
let volume = $state([64])
let tab = $state('overview')
let faq = $state<string[]>(['what'])
let bold = $state(true)
let fmt = $state<string[]>(['bold'])
let toolbarFmt = $state<string[]>(['bold'])
let menuNotifications = $state(true)
let menuTheme = $state('system')
let menubarValue = $state('')
let axis = $state({ density: 'comfortable', scaling: '100' })
onMount(() => {
read()
readRaw()
const refresh = () => {
read()
readRaw()
axis = {
density: document.documentElement.getAttribute('data-density') ?? 'comfortable',
scaling: document.documentElement.getAttribute('data-scaling') ?? '100'
}
}
refresh()
const mo = new MutationObserver(refresh)
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-mode', 'data-theme', 'data-density', 'data-scaling']
})
const sections = [...document.querySelectorAll('section[data-spec-section][id]')]
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) if (e.isIntersecting) activeSection = (e.target as HTMLElement).id
},
{ rootMargin: '-12% 0px -70% 0px' }
)
for (const s of sections) io.observe(s)
return () => {
mo.disconnect()
io.disconnect()
}
})
</script>
{#snippet plusIcon()}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
{/snippet}
{#snippet arrowIcon()}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
{/snippet}
<!-- sonda oculta para métricas resueltas -->
<div bind:this={probe} aria-hidden="true" data-spec-probe></div>
<!-- ═══ Hero ═══════════════════════════════════════════════════════ -->
<header data-spec-hero>
<p data-spec-eyebrow>activeUIX · Eidos · type specimen</p>
<h1 data-spec-display>Grafito</h1>
<p data-spec-lede>
Un sistema de diseño <em>grafito</em>: carbón monocromo, una paleta de <strong>32 escalas</strong>,
y la gramática de color del libro. La jerarquía aliasa la paleta; los intents auto-derivan.
</p>
<dl data-spec-stats>
<div><dt>Paleta</dt><dd>32 escalas</dd></div>
<div><dt>Roles</dt><dd>3 jerarquía · 6 intents</dd></div>
<div><dt>Tipografía</dt><dd>Inter · {TYPE_STYLES.length} estilos</dd></div>
<div><dt>Modos</dt><dd>light · dark · density · scaling</dd></div>
</dl>
</header>
<div data-spec-layout>
<aside data-spec-toc>
<nav aria-label="Índice del espécimen">
{#each TOC as grp}
<p data-spec-toc-group>{grp.group}</p>
<ul>
{#each grp.items as it}
<li>
<a href="#{it.id}" data-active={activeSection === it.id ? 'true' : 'false'} onclick={() => (activeSection = it.id)}>
<span data-spec-toc-n>{it.n}</span>{it.label}
</a>
</li>
{/each}
</ul>
{/each}
</nav>
</aside>
<main data-spec-content>
<!-- ═══ 01 · Color ═══════════════════════════════════════════ -->
<section data-spec-section id="color">
<div data-spec-head>
<span data-spec-n>01</span>
<div>
<h2 data-spec-display>Color & paleta</h2>
<p>Tres capas: la <strong>paleta</strong> (escalas, directamente usables) → los <strong>roles</strong> de jerarquía la aliasan → los <strong>intents</strong> auto-derivan por la convención del libro (identidad step 9).</p>
</div>
</div>
<p data-spec-label>Paleta · 32 escalas · cada paso usable como <code>var(--scale-blue-10)</code></p>
<div data-spec-palette>
{#each THEME_SCALES as sc}
<div data-spec-scale>
<div data-spec-scale-head>
<strong>{sc.label}</strong>
<code>{hex[`${sc.name}-9`] ?? ''}</code>
</div>
<div data-spec-scale-ramp>
{#each COLOR_STEPS as step}
<i style="background: var(--scale-{sc.name}-{step})" title="{sc.name}-{step} · {hex[`${sc.name}-${step}`] ?? ''}"></i>
{/each}
</div>
</div>
{/each}
</div>
<div data-spec-legend>
<span>1–2 fondos</span><span>3–5 componente</span><span>6–8 bordes</span><span>9–10 sólido</span><span>11–12 texto</span>
</div>
<div data-spec-cols2>
<div>
<p data-spec-label>Roles de jerarquía · alias a la paleta</p>
<div data-spec-roles>
{#each THEME_HIERARCHY as r}
<div data-spec-role>
<span data-spec-role-chip style="background: var(--color-{r.role}-solid)"></span>
<div>
<strong>{r.label}</strong>
<small>→ <code>{r.scale}</code></small>
</div>
</div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Intents · auto-derivados (step 9)</p>
<div data-spec-roles>
{#each THEME_INTENTS as it}
<div data-spec-role>
<span data-spec-role-chip style="background: var(--color-{it.role}-solid)"></span>
<div>
<strong>{it.label}</strong>
<small>auto → <code>{it.scale}</code></small>
</div>
</div>
{/each}
</div>
</div>
</div>
<p data-spec-label>Superficies · contenido · bordes</p>
<div data-spec-surfaces>
<div data-spec-surface style="background: var(--color-surface-default)"><span>surface</span><code>default</code></div>
<div data-spec-surface style="background: var(--color-surface-raised)"><span>surface</span><code>raised</code></div>
<div data-spec-surface style="background: var(--color-surface-muted)"><span>surface</span><code>muted</code></div>
<div data-spec-surface style="background: var(--color-content-primary); color: var(--color-surface-default)"><span>content</span><code style="color:inherit;opacity:.7">primary</code></div>
<div data-spec-surface style="background: var(--color-content-secondary); color: var(--color-surface-default)"><span>content</span><code style="color:inherit;opacity:.7">secondary</code></div>
<div data-spec-surface style="border-color: var(--color-border-strong); border-width: 2px"><span>border</span><code>strong</code></div>
</div>
<p data-spec-label>Override jerárquico · prop <code>color</code></p>
<div data-spec-cluster>
{#each HIERARCHY_OVERRIDES as c}
<Button color={c} variant="soft">{c}</Button>
{/each}
</div>
<p data-spec-label>Color semántico · prop <code>intent</code></p>
<div data-spec-cluster>
{#each INTENT_OVERRIDES as it}
<Button intent={it} variant="soft">{it}</Button>
{/each}
</div>
</section>
<!-- ═══ 02 · Tipografía ══════════════════════════════════════ -->
<section data-spec-section id="type">
<div data-spec-head>
<span data-spec-n>02</span>
<div><h2 data-spec-display>Tipografía</h2><p>Familias, escala de tamaños, pesos y los estilos nombrados del sistema.</p></div>
</div>
<p data-spec-label>Familias</p>
<div data-spec-cols2>
{#each FONT_FAMILIES as f}
<div data-spec-type-fam>
<span style="font-family: var(--font-{f.key === 'mono' ? 'mono' : f.key === 'display' ? 'display' : f.key === 'secondary' ? 'secondary' : 'sans'})">Ag</span>
<div><strong>{f.label}</strong><small>{f.note}</small></div>
</div>
{/each}
</div>
<p data-spec-label>Escala de tamaños</p>
<div data-spec-rows>
{#each TEXT_SIZES as k}
<div data-spec-type-row>
<code>--font-size-{k}</code>
<span data-spec-type-val>{resolved[`--font-size-${k}`] ?? ''}</span>
<span data-spec-type-sample style="font-size: var(--font-size-{k})">Grafito</span>
</div>
{/each}
</div>
<div data-spec-cols2>
<div>
<p data-spec-label>Pesos</p>
<div data-spec-rows>
{#each FONT_WEIGHTS as w}
<div data-spec-type-row>
<code>{w.key}</code>
<span data-spec-type-val>{w.value}</span>
<span data-spec-type-sample style="font-weight: var(--font-weight-{w.key}); font-size: 18px">Grafito</span>
</div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Estilos nombrados</p>
<div data-spec-rows>
{#each TYPE_STYLES as t}
<div data-spec-style-row>
<code>--style-{t.key}</code>
<span style="font-family: var(--style-{t.key}-font-family); font-size: var(--style-{t.key}-font-size); font-weight: var(--style-{t.key}-font-weight); line-height: var(--style-{t.key}-line-height); letter-spacing: var(--style-{t.key}-letter-spacing)">{t.sample}</span>
</div>
{/each}
</div>
</div>
</div>
</section>
<!-- ═══ 03 · Espaciado & tamaño ══════════════════════════════ -->
<section data-spec-section id="space">
<div data-spec-head>
<span data-spec-n>03</span>
<div><h2 data-spec-display>Espaciado & tamaño</h2><p>La escala de espacio (base 4px) y las alturas de control. Ambas escalan con densidad + scaling.</p></div>
</div>
<div data-spec-cols2>
<div>
<p data-spec-label>Espaciado · {SPACE_KEYS.length} pasos</p>
<div data-spec-rows>
{#each SPACE_KEYS as k}
<div data-spec-metric-row>
<code>--space-{k}</code>
<span data-spec-metric-val>{resolved[`--space-${k}`] ?? ''}</span>
<div data-spec-metric-bar><i style="inline-size: var(--space-{k})"></i></div>
</div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Tamaños · control-height ({SIZES.length})</p>
<div data-spec-rows>
{#each SIZES as k}
<div data-spec-metric-row>
<code>--control-height-{k}</code>
<span data-spec-metric-val>{resolved[`--control-height-${k}`] ?? ''}</span>
<div data-spec-size-bar style="block-size: var(--control-height-{k})">{k}</div>
</div>
{/each}
</div>
</div>
</div>
</section>
<!-- ═══ 04 · Radio · borde · sombra ══════════════════════════ -->
<section data-spec-section id="shape">
<div data-spec-head>
<span data-spec-n>04</span>
<div><h2 data-spec-display>Radio · borde · sombra</h2><p>Geometría y elevación. No escalan con scaling (chrome nítido).</p></div>
</div>
<p data-spec-label>Radio · {RADIUS_KEYS.length}</p>
<div data-spec-tiles>
{#each RADIUS_KEYS as k}
<div data-spec-tile style="border-radius: var(--radius-{k})"><span>{k}</span><code>{rawState[`--radius-${k}`] ?? ''}</code></div>
{/each}
</div>
<div data-spec-cols2>
<div>
<p data-spec-label>Borde · anchos ({BORDER_WIDTH_KEYS.length}) + estilos ({BORDER_STYLE_KEYS.length})</p>
<div data-spec-rows>
{#each BORDER_WIDTH_KEYS as k}
<div data-spec-metric-row>
<code>--border-width-{k}</code>
<span data-spec-metric-val>{rawState[`--border-width-${k}`] ?? ''}</span>
<div data-spec-border-bar style="border-block-end: var(--border-width-{k}) solid var(--color-content-primary)"></div>
</div>
{/each}
{#each BORDER_STYLE_KEYS as k}
<div data-spec-metric-row>
<code>--border-style-{k}</code>
<span data-spec-metric-val>{k}</span>
<div data-spec-border-bar style="border-block-end: 2px {k} var(--color-content-primary)"></div>
</div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Sombra · {SHADOW_KEYS.length}</p>
<div data-spec-tiles>
{#each SHADOW_KEYS as k}
<div data-spec-shadow-tile style="box-shadow: var(--shadow-{k})">{k}</div>
{/each}
</div>
</div>
</div>
</section>
<!-- ═══ 05 · Iconos & focus ══════════════════════════════════ -->
<section data-spec-section id="icon">
<div data-spec-head>
<span data-spec-n>05</span>
<div><h2 data-spec-display>Iconos & focus</h2><p>Tamaños de icono, grosor de trazo y el anillo de foco.</p></div>
</div>
<p data-spec-label>Tamaños de icono · {ICON_SIZE_KEYS.length}</p>
<div data-spec-rows>
{#each ICON_SIZE_KEYS as k}
<div data-spec-metric-row>
<code>--icon-size-{k}</code>
<span data-spec-metric-val>{resolved[`--icon-size-${k}`] ?? ''}</span>
<svg style="inline-size: var(--icon-size-{k}); block-size: var(--icon-size-{k})" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M12 7v10M7 12h10" /></svg>
</div>
{/each}
</div>
<p data-spec-label>Stroke-width ({ICON_STROKE_WIDTH_KEYS.length}) · focus ring</p>
<div data-spec-cluster>
{#each ICON_STROKE_WIDTH_KEYS as k}
<svg style="inline-size: 28px; block-size: 28px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width={rawState[`--icon-stroke-width-${k}`] || 1.5} aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
{/each}
<button type="button" data-spec-focus-demo>focus ring</button>
</div>
</section>
<!-- ═══ 06 · Layout ══════════════════════════════════════════ -->
<section data-spec-section id="layout">
<div data-spec-head>
<span data-spec-n>06</span>
<div><h2 data-spec-display>Layout</h2><p>Anchos de contenedor y de contenido, y relaciones de aspecto.</p></div>
</div>
<div data-spec-cols2>
<div>
<p data-spec-label>Container widths ({CONTAINER_WIDTH_KEYS.length}) · content widths ({CONTENT_WIDTH_KEYS.length})</p>
<div data-spec-rows>
{#each CONTAINER_WIDTH_KEYS as k}
<div data-spec-metric-row><code>--container-width-{k}</code><span data-spec-metric-val>{rawState[`--container-width-${k}`] ?? ''}</span></div>
{/each}
{#each CONTENT_WIDTH_KEYS as k}
<div data-spec-metric-row><code>--content-width-{k}</code><span data-spec-metric-val>{rawState[`--content-width-${k}`] ?? ''}</span></div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Aspect ratios · {ASPECT_RATIO_KEYS.length}</p>
<div data-spec-tiles>
{#each ASPECT_RATIO_KEYS as k}
<div data-spec-aspect-tile style="aspect-ratio: var(--aspect-ratio-{k})"><span>{k}</span><code>{rawState[`--aspect-ratio-${k}`] ?? ''}</code></div>
{/each}
</div>
</div>
</div>
</section>
<!-- ═══ 07 · Z-index · opacidad · motion ═════════════════════ -->
<section data-spec-section id="system">
<div data-spec-head>
<span data-spec-n>07</span>
<div><h2 data-spec-display>Z-index · opacidad · motion</h2><p>Capas, transparencias y la gramática de movimiento.</p></div>
</div>
<div data-spec-cols3>
<div>
<p data-spec-label>Z-index · {Z_INDEX_KEYS.length}</p>
<div data-spec-rows>
{#each Z_INDEX_KEYS as k}
<div data-spec-metric-row><code>--z-index-{k}</code><span data-spec-metric-val>{rawState[`--z-index-${k}`] ?? ''}</span></div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Opacidad · {OPACITY_KEYS.length}</p>
<div data-spec-rows>
{#each OPACITY_KEYS as k}
<div data-spec-metric-row>
<code>--opacity-{k}</code><span data-spec-metric-val>{rawState[`--opacity-${k}`] ?? ''}</span>
<div data-spec-opacity-chip style="opacity: var(--opacity-{k})"></div>
</div>
{/each}
</div>
</div>
<div>
<p data-spec-label>Motion · {DURATION_KEYS.length} dur · {EASE_KEYS.length} ease · {MOTION_DISTANCE_KEYS.length} dist · {MOTION_SCALE_KEYS.length} scale</p>
<div data-spec-rows>
{#each DURATION_KEYS as k}
<div data-spec-metric-row><code>--duration-{k}</code><span data-spec-metric-val>{rawState[`--duration-${k}`] ?? ''}</span></div>
{/each}
{#each EASE_KEYS as k}
<div data-spec-metric-row><code>--ease-{k}</code></div>
{/each}
</div>
</div>
</div>
</section>
<!-- ═══ 08 · Densidad & scaling ══════════════════════════════ -->
<section data-spec-section id="axes">
<div data-spec-head>
<span data-spec-n>08</span>
<div><h2 data-spec-display>Densidad & scaling</h2><p>Dos ejes ortogonales (en la barra superior). Densidad = ritmo de layout; scaling = zoom global incluida la tipografía.</p></div>
</div>
<div data-spec-cols2>
<div data-spec-axis-card>
<p data-spec-label>Densidad · {axis.density}</p>
<p data-spec-axis-note>Espacio + altura de control. La tipografía NO cambia.</p>
<div data-spec-axis-metrics>
<div><code>--space-4</code><strong>{resolved['--space-4'] ?? ''}</strong></div>
<div><code>--control-height-md</code><strong>{resolved['--control-height-md'] ?? ''}</strong></div>
</div>
</div>
<div data-spec-axis-card>
<p data-spec-label>Scaling · {axis.scaling}%</p>
<p data-spec-axis-note>Zoom global — escala todo, incluida la tipografía.</p>
<div data-spec-axis-metrics>
<div><code>--font-size-md</code><strong>{resolved['--font-size-md'] ?? ''}</strong></div>
<div><code>--space-4</code><strong>{resolved['--space-4'] ?? ''}</strong></div>
</div>
</div>
</div>
</section>
<!-- ═══ 09 · Acciones ════════════════════════════════════════ -->
<section data-spec-section id="actions">
<div data-spec-head>
<span data-spec-n>09</span>
<div><h2 data-spec-display>Acciones</h2><p>Button · Toggle · ToggleGroup · Toolbar. El texto sobre sólidos claros (amber/orange) resuelve a oscuro por luminancia.</p></div>
</div>
<p data-spec-label>Variants · roles · tamaños · estados</p>
<div data-spec-stack>
<div data-spec-cluster>{#each BTN_VARIANTS as v}<Button variant={v}>{v}</Button>{/each}</div>
<div data-spec-cluster>
<Button color="primary">Primary</Button>
<Button color="secondary" variant="surface">Secondary</Button>
<Button color="neutral" variant="outline">Neutral</Button>
<Button intent="affirm">Affirm</Button>
<Button intent="fulfill">Fulfill</Button>
<Button intent="risk">Risk</Button>
<Button intent="threat">Threat</Button>
<Button intent="loss">Loss</Button>
</div>
<div data-spec-cluster>{#each BTN_SIZES as sz}<Button size={sz}>{sz}</Button>{/each}</div>
<div data-spec-cluster>
<Button icon={plusIcon}>Nuevo</Button>
<Button variant="outline" endIcon={arrowIcon}>Continuar</Button>
<Button iconOnly icon={plusIcon} aria-label="Añadir" />
<Button loading>Guardando</Button>
<Button disabled>Desactivado</Button>
<Button intent="threat" variant="soft">Eliminar</Button>
</div>
</div>
<p data-spec-label>Toggle · ToggleGroup · Toolbar</p>
<div data-spec-cluster>
<Toggle bind:pressed={bold}>Bold</Toggle>
<Toggle variant="outline">Outline</Toggle>
<ToggleGroup type="multiple" attached bind:value={fmt} aria-label="Formato">
<ToggleGroup.Item value="bold">B</ToggleGroup.Item>
<ToggleGroup.Item value="italic">I</ToggleGroup.Item>
<ToggleGroup.Item value="underline">U</ToggleGroup.Item>
</ToggleGroup>
</div>
<div data-spec-panel>
<Toolbar aria-label="Editor">
<Toolbar.Button>Deshacer</Toolbar.Button>
<Toolbar.Button>Rehacer</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Group type="multiple" bind:value={toolbarFmt} aria-label="Formato">
<Toolbar.GroupItem value="bold">B</Toolbar.GroupItem>
<Toolbar.GroupItem value="italic">I</Toolbar.GroupItem>
<Toolbar.GroupItem value="underline">U</Toolbar.GroupItem>
</Toolbar.Group>
</Toolbar>
</div>
</section>
<!-- ═══ 10 · Formulario ══════════════════════════════════════ -->
<section data-spec-section id="forms">
<div data-spec-head>
<span data-spec-n>10</span>
<div><h2 data-spec-display>Formulario</h2><p>Field · Select · Checkbox · Radio · Switch · Slider.</p></div>
</div>
<div data-spec-cols2>
<div data-spec-panel data-spec-stack>
<Field name="email">
<Field.Label>Email <Field.RequiredIndicator /></Field.Label>
<Field.Control><Field.Input type="email" bind:value={email} placeholder="tú@empresa.com" /></Field.Control>
<Field.HelperText>Usaremos esto para iniciar sesión.</Field.HelperText>
</Field>
<Field name="fruit">
<Field.Label>Fruta favorita</Field.Label>
<Select bind:value={fruit}>
<Select.Trigger><Select.Value placeholder="Elige una fruta" /><Select.Indicator /></Select.Trigger>
<Select.Portal><Select.Content sideOffset={6}><Select.Viewport>
<Select.Item value="mango" label="Mango">Mango<Select.ItemIndicator /></Select.Item>
<Select.Item value="lychee" label="Lichi">Lichi<Select.ItemIndicator /></Select.Item>
<Select.Item value="guava" label="Guayaba">Guayaba<Select.ItemIndicator /></Select.Item>
</Select.Viewport></Select.Content></Select.Portal>
</Select>
</Field>
</div>
<div data-spec-panel data-spec-stack>
<label data-spec-cluster style="cursor:pointer"><Checkbox bind:checked={agree}><Checkbox.Indicator /></Checkbox><span>Acepto los términos</span></label>
<Separator />
<RadioGroup bind:value={plan}>
<RadioGroup.Label data-spec-label>Plan</RadioGroup.Label>
<label data-spec-cluster style="cursor:pointer"><RadioGroup.Item value="starter"><RadioGroup.Indicator /></RadioGroup.Item><span>Starter</span></label>
<label data-spec-cluster style="cursor:pointer"><RadioGroup.Item value="pro"><RadioGroup.Indicator /></RadioGroup.Item><span>Pro</span></label>
</RadioGroup>
<Separator />
<label data-spec-cluster style="justify-content:space-between;cursor:pointer"><span>Wi-Fi</span><Switch bind:checked={wifi}><Switch.Thumb /></Switch></label>
<div data-spec-stack style="gap: var(--space-2)">
<span data-spec-label style="margin:0">Volumen · {volume[0]}%</span>
<Slider bind:value={volume} min={0} max={100} aria-label="Volumen"><Slider.Range /><Slider.Thumb index={0} /></Slider>
</div>
</div>
</div>
</section>
<!-- ═══ 11 · Feedback ════════════════════════════════════════ -->
<section data-spec-section id="feedback">
<div data-spec-head>
<span data-spec-n>11</span>
<div><h2 data-spec-display>Feedback & estado</h2><p>Banner · Progress · Avatar · Badge · Card.</p></div>
</div>
<div data-spec-stack>
<Banner intent="primary" variant="soft">Tu prueba gratuita termina en 7 días.</Banner>
<Banner intent="fulfill" variant="soft">Cambios guardados correctamente.</Banner>
<Banner intent="risk" variant="soft">Tu método de pago caduca pronto.</Banner>
<Banner intent="threat" variant="soft">No se pudo procesar el pago.</Banner>
</div>
<div data-spec-cols2>
<div data-spec-panel data-spec-stack>
<p data-spec-label>Progress · Badges</p>
<Progress value={64} max={100}><Progress.Label>Subiendo</Progress.Label><Progress.ValueText /><Progress.Indicator /></Progress>
<div data-spec-cluster>{#each BADGE_COLORS as c}<Badge color={c} dot>{c}</Badge>{/each}</div>
<div data-spec-cluster>{#each CHIP_VARIANTS as v}<Badge color="primary" variant={v}>{v}</Badge>{/each}</div>
</div>
<div data-spec-panel data-spec-stack>
<p data-spec-label>Avatares</p>
<div data-spec-cluster>
<Avatar><Avatar.Fallback>OV</Avatar.Fallback></Avatar>
<Avatar color="primary"><Avatar.Fallback>AM</Avatar.Fallback></Avatar>
<Avatar color="affirm" variant="soft"><Avatar.Fallback>JV</Avatar.Fallback><Avatar.Badge color="fulfill" position="bottom-end" dot /></Avatar>
</div>
</div>
</div>
<Card variant="soft" color="primary">
<Card.Header><Card.Title>Plan Pro</Card.Title><Card.Description>Colaboración ilimitada.</Card.Description></Card.Header>
<Card.Body><strong style="font-size: var(--font-size-xl)">$29</strong><span style="color: var(--color-content-muted)"> / mes</span></Card.Body>
<Card.Footer><Button block icon={arrowIcon}>Elegir Pro</Button></Card.Footer>
</Card>
</section>
<!-- ═══ 12 · Navegación & disclosure ═════════════════════════ -->
<section data-spec-section id="nav">
<div data-spec-head>
<span data-spec-n>12</span>
<div><h2 data-spec-display>Navegación & disclosure</h2><p>Tabs · Accordion.</p></div>
</div>
<div data-spec-panel data-spec-stack>
<Tabs bind:value={tab} variant="line">
<Tabs.List aria-label="Secciones"><Tabs.Trigger value="overview">Overview</Tabs.Trigger><Tabs.Trigger value="activity">Activity</Tabs.Trigger><Tabs.Trigger value="settings">Settings</Tabs.Trigger><Tabs.Indicator /></Tabs.List>
<Tabs.Content value="overview">Resumen de la cuenta.</Tabs.Content>
<Tabs.Content value="activity">Actividad reciente.</Tabs.Content>
<Tabs.Content value="settings">Preferencias.</Tabs.Content>
</Tabs>
</div>
<div data-spec-panel>
<Accordion bind:value={faq} type="single" variant="outline">
<Accordion.Item value="what"><Accordion.Header><Accordion.Trigger>¿Qué es activeUIX?</Accordion.Trigger></Accordion.Header><Accordion.Content>Un sistema de UI en cuatro capas — morfo, soma, sema y eidos.</Accordion.Content></Accordion.Item>
<Accordion.Item value="theme"><Accordion.Header><Accordion.Trigger>¿Cómo se crea un tema?</Accordion.Trigger></Accordion.Header><Accordion.Content>Con <code>ActiveEidos.create</code>: paleta + roles; los intents auto-derivan.</Accordion.Content></Accordion.Item>
</Accordion>
</div>
</section>
<!-- ═══ 13 · Overlays & menús ════════════════════════════════ -->
<section data-spec-section id="overlays">
<div data-spec-head>
<span data-spec-n>13</span>
<div><h2 data-spec-display>Overlays & menús</h2><p>Popover · Dialog · Dropdown · Context · Menubar · Tooltip — portaleados con la superficie y sombra del tema.</p></div>
</div>
<div data-spec-panel data-spec-cluster>
<Popover>
<Popover.Trigger>Popover</Popover.Trigger>
<Popover.Portal><Popover.Content side="bottom" align="start" sideOffset={8}><Popover.Arrow /><Popover.Title>Dimensiones</Popover.Title><Popover.Description>Ajusta el panel.</Popover.Description><div data-spec-cluster style="margin-block-start: var(--space-3)"><Button size="sm">Aplicar</Button><Popover.Close>Cerrar</Popover.Close></div></Popover.Content></Popover.Portal>
</Popover>
<Dialog>
<Dialog.Trigger>Dialog</Dialog.Trigger>
<Dialog.Portal><Dialog.Overlay /><Dialog.Content><Dialog.Header><Dialog.Title>¿Eliminar?</Dialog.Title><Dialog.Description>Acción permanente.</Dialog.Description></Dialog.Header><Dialog.Footer><Dialog.Close>Cancelar</Dialog.Close><Button intent="threat">Eliminar</Button></Dialog.Footer></Dialog.Content></Dialog.Portal>
</Dialog>
<DropdownMenu>
<DropdownMenu.Trigger variant="outline">Dropdown</DropdownMenu.Trigger>
<DropdownMenu.Content side="bottom" align="start" sideOffset={6}>
<DropdownMenu.Item>Perfil</DropdownMenu.Item>
<DropdownMenu.Item>Facturación</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem bind:checked={menuNotifications}>Notificaciones</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup bind:value={menuTheme}><DropdownMenu.RadioItem value="light">Claro</DropdownMenu.RadioItem><DropdownMenu.RadioItem value="dark">Oscuro</DropdownMenu.RadioItem></DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu>
<Tooltip.Group delayDuration={250}><Tooltip><Tooltip.Trigger>Tooltip</Tooltip.Trigger><Tooltip.Portal><Tooltip.Content><Tooltip.Arrow />Con la sombra del tema</Tooltip.Content></Tooltip.Portal></Tooltip></Tooltip.Group>
<ContextMenu>
<ContextMenu.Trigger><div data-spec-ctx>Click derecho</div></ContextMenu.Trigger>
<ContextMenu.Content><ContextMenu.Item>Copiar</ContextMenu.Item><ContextMenu.Item>Pegar</ContextMenu.Item><ContextMenu.Separator /><ContextMenu.Item>Duplicar</ContextMenu.Item></ContextMenu.Content>
</ContextMenu>
</div>
<div data-spec-panel>
<Menubar bind:value={menubarValue}>
<Menubar.Menu value="file"><Menubar.Trigger>Archivo</Menubar.Trigger><Menubar.Portal><Menubar.Content><Menubar.Item>Nuevo</Menubar.Item><Menubar.Item>Abrir…</Menubar.Item><Menubar.Separator /><Menubar.Item>Guardar</Menubar.Item></Menubar.Content></Menubar.Portal></Menubar.Menu>
<Menubar.Menu value="edit"><Menubar.Trigger>Editar</Menubar.Trigger><Menubar.Portal><Menubar.Content><Menubar.Item>Deshacer</Menubar.Item><Menubar.Item>Rehacer</Menubar.Item></Menubar.Content></Menubar.Portal></Menubar.Menu>
</Menubar>
</div>
</section>
<!-- ═══ 14 · Semántica ═══════════════════════════════════════ -->
<section data-spec-section id="sema">
<div data-spec-head>
<span data-spec-n>14</span>
<div><h2 data-spec-display>Semántica</h2><p>La capa perceptual: cada interacción emite una señal (familia + intent) → sonido, haptic y reacción visual <code>data-event-*</code>. Pulsa — con gesto de usuario suenan.</p></div>
</div>
<div data-spec-panel data-spec-cluster>
<Button intent="affirm">Affirm</Button>
<Button intent="fulfill">Fulfill</Button>
<Button intent="risk">Risk</Button>
<Button intent="threat">Threat</Button>
<Button intent="loss">Loss</Button>
</div>
</section>
<footer data-spec-foot>
<span data-spec-display>Grafito</span>
<p>Tema sobre el sistema de theming de Eidos · paleta de 32 escalas · jerarquía aliasada, intents auto-derivados por la gramática del libro.</p>
</footer>
</main>
</div>

Powered by TurnKey Linux.