Grafito theme: rename from untitled-ui + 32-scale palette + Color 3-group

- Rename theme untitled-ui -> Grafito: route /temas/grafito, theme ids
  grafito-light/dark, grafitoConfig, brand/hero/footer/index.
- Palette: Grafito now carries the full 32-scale library (spreads the
  framework's 31 Radix scales + brand carbon, overriding the brand-tuned
  gray/violet/blue + intents). Each directly usable as --scale-{name}-{step}.
- Roles: hierarchy explicit (primary->carbon, secondary->violet,
  tertiary->blue); the 6 intents auto-derive from the palette by convention
  (step 9). THEME_SCALES derived from palette keys, labelled by COLOR NAME.
- Demo Color section -> 3 groups (Paleta / Roles jerarquia / Intents
  auto-derivados) + scaling control in the topbar.

Verified in browser: 32 distinct scales emitted, intents auto-derive
(affirm=teal, risk=amber, loss=plum), no validation error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 152d2ad960
commit 89147d0e1b

@ -1,12 +1,12 @@
<script lang="ts">
// Índice de temas. Página plana (sin runtime Eidos) que enlaza a cada
// tema. El primero es Untitled UI.
// tema. El primero es Grafito.
const themes = [
{
slug: 'untitled-ui',
name: 'Untitled UI',
desc: 'Inter · brand violeta #7F56D9 · grises fríos · sombras en capas',
accent: '#7F56D9'
slug: 'grafito',
name: 'Grafito',
desc: 'Carbón monocromo · Inter · paleta derivada de Untitled UI (31 escalas) · intents auto-derivados',
accent: '#1f2024'
}
]
</script>

@ -1,5 +1,5 @@
/**
* Untitled UI — tema para activeUIX / Eidos.
* Grafito — tema para activeUIX / Eidos (paleta derivada de Untitled UI, variada).
*
* Reproduce el lenguaje visual de https://www.untitledui.com con el
* sistema de theming canónico de Eidos: NO inventa variants ni roles
@ -31,7 +31,11 @@
* escalas no coincide con los roles, y el validador de Eidos lo
* rechazaría.)
*/
import { createThemeBaseEidosConfig } from '$uix/eidos/lib/themes/base'
import {
createThemeBaseEidosConfig,
THEME_BASE_LIGHT_COLOR_SCALES,
THEME_BASE_DARK_COLOR_SCALES
} from '$uix/eidos/lib/themes/base'
import type { ColorScale, ColorRoleMap, EidosConfig } from '$uix/eidos/lib/config-types'
const s = (
@ -65,6 +69,10 @@ const s = (
// ── Escalas — modo claro ──────────────────────────────────────────────
// prettier-ignore
const LIGHT = {
// Hereda la paleta base del framework (31 escalas Radix). Grafito sobreescribe
// abajo las escalas de marca (carbon, gray, violet, blue + intents UUI-tuned) y
// hereda las demás → ~32 escalas en la paleta del tema.
...THEME_BASE_LIGHT_COLOR_SCALES,
// Carbon — grafito casi negro. Primario monocromo (Vercel / Linear style).
// 9 = sólido oscuro del botón; con slots.contrast='1' el texto (step 1)
// queda casi blanco. En dark la escala se invierte y todo encaja solo.
@ -95,6 +103,8 @@ const LIGHT = {
// 10 = `-500` (más claro para hover sobre oscuro).
// prettier-ignore
const DARK = {
...THEME_BASE_DARK_COLOR_SCALES,
// ── Escalas de marca Grafito (dark) ──
// Carbon invertido: 1 = casi negro, 9 = botón claro, contrast(1) = oscuro.
carbon: s('#0b0c0e','#131418','#1b1c20','#232428','#2c2d32','#3a3b41','#565760','#7c7d86','#f4f4f6','#ffffff','#cdced4','#fbfbfc'),
gray: s('#0c111d','#101828','#1d2939','#243144','#2c3a4f','#344054','#475467','#5d6b7e','#667085','#98a2b3','#ced5de','#f5f6f8'),
@ -109,18 +119,17 @@ const DARK = {
// ── Mapeo role → escala (los 9 roles canónicos de Eidos) ──────────────
const ROLES: ColorRoleMap = {
// Carbon monocromo. `slots.contrast: '1'` hace que el texto sobre el
// sólido sea el step 1 — casi blanco en claro, casi negro en oscuro —
// invirtiéndose solo con el modo (el step 9 también se invierte).
// Solo la JERARQUÍA se mapea explícitamente (decisión de marca). Carbon
// monocromo: `slots.contrast: '1'` hace que el texto sobre el sólido sea el
// step 1 — casi blanco en claro, casi negro en oscuro — invirtiéndose con el
// modo (el step 9 también se invierte).
primary: { scale: 'carbon', slots: { contrast: '1' } },
secondary: 'violet',
tertiary: 'blue',
neutral: 'gray',
affirm: 'teal',
fulfill: 'green',
risk: 'amber',
threat: 'red',
loss: 'plum'
tertiary: 'blue'
// Los 6 intents (neutral + affirm/fulfill/risk/threat/loss) NO se cablean:
// auto-derivan de la paleta por la convención del libro
// (gray/teal/green/amber/red/plum — todas presentes arriba), identidad = step 9.
// Un tema puede overridear un intent mapeándolo explícitamente.
}
// ── Superficies / contenido / bordes / focus ──────────────────────────
@ -253,19 +262,19 @@ const base = createThemeBaseEidosConfig({
})
/**
* Config completo del tema Untitled UI. Se pasa a
* `ActiveEidos.create({ config: untitledUiConfig, theme: 'untitled-ui' })`.
* Config completo del tema Grafito. Se pasa a
* `ActiveEidos.create({ config: grafitoConfig, theme: 'grafito' })`.
*/
export const untitledUiConfig: EidosConfig = {
export const grafitoConfig: EidosConfig = {
primitives: base.primitives,
recipes: base.recipes,
semantics: { color: { roles: ROLES } },
themes: {
'untitled-ui-light': {
'grafito-light': {
color: { scales: LIGHT, ...LIGHT_SEMANTICS },
shadow: LIGHT_SHADOW
},
'untitled-ui-dark': {
'grafito-dark': {
color: { scales: DARK, ...DARK_SEMANTICS },
shadow: DARK_SHADOW
}
@ -281,30 +290,35 @@ export interface ThemeRoleInfo {
readonly note: string
}
/** Los 9 roles canónicos, con la escala Untitled UI a la que mapean. */
export const THEME_ROLES: readonly ThemeRoleInfo[] = [
/** Roles de JERARQUÍA — alias explícito a una escala de la paleta (marca). Los
* intents NO van aquí: auto-derivan de la paleta por convención (ver THEME_INTENTS). */
export const THEME_HIERARCHY: readonly ThemeRoleInfo[] = [
{ role: 'primary', scale: 'carbon', label: 'Primary', note: 'Carbón · grafito #1F2024' },
{ role: 'secondary', scale: 'violet', label: 'Secondary', note: 'Brand UUI · violeta #7F56D9' },
{ role: 'tertiary', scale: 'blue', label: 'Tertiary', note: 'Acento · azul #1570EF' },
{ role: 'neutral', scale: 'gray', label: 'Neutral', note: 'Gris frío Untitled UI' },
{ role: 'affirm', scale: 'teal', label: 'Affirm', note: 'Positivo ligero · teal' },
{ role: 'fulfill', scale: 'green', label: 'Fulfill', note: 'Éxito / completado · verde' },
{ role: 'risk', scale: 'amber', label: 'Risk', note: 'Aviso · warning #DC6803' },
{ role: 'threat', scale: 'red', label: 'Threat', note: 'Error / peligro · #D92D20' },
{ role: 'loss', scale: 'plum', label: 'Loss', note: 'Pérdida grave · violeta sombrío' }
{ role: 'secondary', scale: 'violet', label: 'Secondary', note: 'Violeta de marca #7F56D9' },
{ role: 'tertiary', scale: 'blue', label: 'Tertiary', note: 'Azul acento #1570EF' }
]
/** Escalas físicas del theme, en orden de presentación. */
export const THEME_SCALES: readonly { name: string; label: string }[] = [
{ name: 'carbon', label: 'Carbon' },
{ name: 'violet', label: 'Violet · brand' },
{ name: 'blue', label: 'Blue' },
{ name: 'gray', label: 'Gray' },
{ name: 'teal', label: 'Teal' },
{ name: 'green', label: 'Success' },
{ name: 'amber', label: 'Warning' },
{ name: 'red', label: 'Error' },
{ name: 'plum', label: 'Plum · grave' }
/**
* La paleta COMPLETA del tema (~32 escalas), derivada de las claves de `LIGHT`
* para no desincronizarse. Etiquetada por NOMBRE DE COLOR — nunca por intent
* (los intents son una capa semántica encima; ver THEME_INTENTS).
*/
export const THEME_SCALES: readonly { name: string; label: string }[] = Object.keys(LIGHT)
.sort()
.map((name) => ({ name, label: name }))
/**
* Los 6 intents y la escala de la paleta de la que **auto-derivan** (convención
* del libro). El tema NO los cablea — el engine los rellena por nombre,
* identidad = step 9. Esto son solo metadatos para documentar el mapeo en el demo.
*/
export const THEME_INTENTS: readonly { role: string; label: string; scale: string; note: string }[] = [
{ role: 'neutral', label: 'Neutral', scale: 'gray', note: 'Sin valencia · gris de trabajo' },
{ role: 'affirm', label: 'Affirm', scale: 'teal', note: 'Positivo ligero · teal' },
{ role: 'fulfill', label: 'Fulfill', scale: 'green', note: 'Éxito / completado · verde' },
{ role: 'risk', label: 'Risk', scale: 'amber', note: 'Aviso · ámbar' },
{ role: 'threat', label: 'Threat', scale: 'red', note: 'Error / peligro · rojo' },
{ role: 'loss', label: 'Loss', scale: 'plum', note: 'Pérdida grave · violeta sombrío' }
]
export const COLOR_STEPS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] as const

@ -1,6 +1,6 @@
<script lang="ts">
/**
* Shell del tema Untitled UI.
* Shell del tema Grafito.
*
* Arranca su propio `ActiveUix` + `Soma` + `ActiveEidos` (igual que la
* docs-site de UIX) pero con el patch de tema `untitled-ui`. Es la
@ -17,7 +17,7 @@
import { Soma } from '$soma/core/soma.svelte'
import { siumLangs } from '$sium/langs/langs'
import { secsLangs } from '$libs/secs'
import { ActiveEidos } from '$uix/eidos'
import { ActiveEidos, SCALING_KEYS, type ScalingKey } from '$uix/eidos'
import {
accordionSema,
checkboxSema,
@ -36,10 +36,10 @@
toolbarSema,
tooltipSema
} from '$uix/sema/components'
import { untitledUiConfig } from '../_lib/untitled-ui'
import { grafitoConfig } from '../_lib/grafito'
import '@/uix/eidos/index.css'
import './theme.css'
import './untitled-ui.css'
import './grafito.css'
let { children } = $props()
@ -80,14 +80,16 @@
// ── Modo + densidad (fuentes visuales para Eidos) ─────────────────
let mode = $state<ThemeEffective>('light')
let density = $state<Density>('comfortable')
let scaling = $state<ScalingKey>('100')
const modeListeners = new Set<(v: ThemeEffective) => void>()
const densityListeners = new Set<(v: Density) => void>()
const scalingListeners = new Set<(v: ScalingKey) => void>()
const activeEidos = ActiveEidos.create({
applyDom: true,
config: untitledUiConfig,
theme: 'untitled-ui',
config: grafitoConfig,
theme: 'grafito',
modeSource: {
get: () => mode,
onChange(handler) {
@ -101,6 +103,13 @@
densityListeners.add(handler)
return () => densityListeners.delete(handler)
}
},
scalingSource: {
get: () => scaling,
onChange(handler) {
scalingListeners.add(handler)
return () => scalingListeners.delete(handler)
}
}
})
@ -112,6 +121,10 @@
const d = density
for (const l of densityListeners) l(d)
})
$effect(() => {
const s = scaling
for (const l of scalingListeners) l(s)
})
const DENSITIES: { id: Density; label: string }[] = [
{ id: 'compact', label: 'Compact' },
@ -138,9 +151,9 @@
<div data-temas-shell>
<header data-temas-topbar>
<div data-temas-brand>
<span data-temas-brand-mark>U</span>
<span data-temas-brand-mark>G</span>
<div data-temas-brand-text>
<strong>Untitled UI</strong>
<strong>Grafito</strong>
<small>Tema · activeUIX / Eidos</small>
</div>
</div>
@ -158,6 +171,18 @@
{/each}
</div>
<div data-temas-segmented role="group" aria-label="Scaling">
{#each SCALING_KEYS as s}
<button
type="button"
data-active={scaling === s ? 'true' : 'false'}
onclick={() => (scaling = s)}
>
{s}%
</button>
{/each}
</div>
<button
type="button"
data-temas-mode-btn

File diff suppressed because it is too large Load Diff

@ -122,14 +122,103 @@
/* ── Canvas ─────────────────────────────────────────────────────── */
[data-temas-canvas] {
max-inline-size: 1120px;
max-inline-size: 1240px;
margin-inline: auto;
padding: var(--space-10) var(--space-6) var(--space-16);
display: flex;
flex-direction: column;
gap: var(--space-12);
}
/* ── 2-pane: TOC sticky + contenido ─────────────────────────────── */
[data-temas-layout] {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
gap: var(--space-10);
align-items: start;
}
[data-temas-toc] {
position: sticky;
inset-block-start: var(--space-16);
align-self: start;
max-block-size: calc(100dvh - var(--space-16) - var(--space-4));
overflow-y: auto;
}
[data-temas-toc] nav {
display: flex;
flex-direction: column;
}
[data-temas-toc-group] {
margin: var(--space-4) 0 var(--space-1-5);
font-size: var(--font-size-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--color-content-muted);
}
[data-temas-toc-group]:first-child {
margin-block-start: 0;
}
[data-temas-toc] ul {
list-style: none;
margin: 0 0 var(--space-1);
padding: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
[data-temas-toc] a {
display: block;
padding: var(--space-1-5) var(--space-2-5);
border-radius: var(--radius-md);
border-inline-start: 2px solid transparent;
color: var(--color-content-secondary);
text-decoration: none;
font-size: var(--font-size-sm);
transition: background 120ms, color 120ms, border-color 120ms;
}
[data-temas-toc] a:hover {
background: var(--color-surface-raised);
color: var(--color-content-primary);
}
[data-temas-toc] a[data-active='true'] {
background: var(--color-primary-surface);
border-inline-start-color: var(--color-primary-solid);
color: var(--color-primary-text);
font-weight: 600;
}
[data-temas-content] {
min-inline-size: 0;
display: flex;
flex-direction: column;
gap: var(--space-16);
}
[data-temas-group-head] {
margin: 0;
font-size: var(--font-size-sm);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-content-muted);
padding-block-end: var(--space-2);
border-block-end: 2px solid var(--color-border-default);
}
[data-temas-group-head]:not(:first-child) {
margin-block-start: var(--space-4);
}
@media (max-width: 768px) {
[data-temas-layout] {
grid-template-columns: minmax(0, 1fr);
}
[data-temas-toc] {
display: none;
}
}
/* ── Hero ───────────────────────────────────────────────────────── */
[data-temas-hero] {
display: flex;
@ -180,6 +269,7 @@
display: flex;
flex-direction: column;
gap: var(--space-5);
scroll-margin-block-start: var(--space-16);
}
[data-temas-section-head] {
display: flex;
@ -322,6 +412,23 @@
font-variant-numeric: tabular-nums;
}
/* leyenda de anatomía de los 12 pasos (estilo Radix) */
[data-temas-step-legend] {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-block-start: var(--space-1);
}
[data-temas-step-legend] span {
font-size: var(--font-size-xs);
color: var(--color-content-secondary);
padding: var(--space-1) var(--space-2);
background: var(--color-surface-muted);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
font-variant-numeric: tabular-nums;
}
/* ── Surfaces row ───────────────────────────────────────────────── */
[data-temas-surfaces] {
display: grid;
@ -477,3 +584,41 @@
font-size: var(--font-size-xs);
color: var(--color-content-secondary);
}
/* ── Densidad & Scaling: notas + métricas en vivo ───────────────── */
[data-temas-axis-note] {
margin: 0;
font-size: var(--font-size-sm);
color: var(--color-content-secondary);
line-height: 1.5;
}
[data-temas-axis-metrics] {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-block-start: var(--space-1);
}
[data-temas-axis-metrics] > div {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
background: var(--color-surface-muted);
border-radius: var(--radius-md);
}
[data-temas-axis-metrics] code {
font-family: var(--font-mono);
font-size: var(--font-size-xs);
color: var(--color-content-muted);
}
[data-temas-axis-metrics] strong {
font-variant-numeric: tabular-nums;
font-size: var(--font-size-sm);
font-weight: 600;
}
/* scroll suave para los anclajes del TOC (solo aplica en esta ruta) */
html {
scroll-behavior: smooth;
}

@ -1,836 +0,0 @@
<script lang="ts">
/**
* Página de theming — Untitled UI.
* Muestra todos los elementos del tema: paletas de color (roles + rampas
* de 12 pasos + superficies), tipografía, espaciado, radios, sombras,
* tamaños y el aspecto real de los controles con todas sus variantes.
*/
import { onMount } from 'svelte'
import Button from '$uix/eidos/components/button'
import Badge from '$uix/eidos/components/badge'
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 Progress from '$uix/eidos/components/progress'
import Banner from '$uix/eidos/components/banner'
import Avatar from '$uix/eidos/components/avatar'
import Separator from '$uix/eidos/components/separator'
import Tabs from '$uix/eidos/components/tabs'
import Accordion from '$uix/eidos/components/accordion'
import Toggle from '$uix/eidos/components/toggle'
import ToggleGroup from '$uix/eidos/components/toggle-group'
import Toolbar from '$uix/eidos/components/toolbar'
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'
import {
THEME_ROLES,
THEME_SCALES,
COLOR_STEPS
} from '../_lib/untitled-ui'
// ── Hex resolución (lee los literales de las escalas en runtime) ──
let hex = $state<Record<string, string>>({})
function readHex() {
if (typeof document === 'undefined') return
const cs = getComputedStyle(document.documentElement)
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
}
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 ? '#101828' : '#ffffff'
onMount(() => {
readHex()
const obs = new MutationObserver(() => readHex())
obs.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-mode', 'data-theme']
})
return () => obs.disconnect()
})
// ── Datos de las secciones de tokens ──────────────────────────────
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: 'body', label: 'Body', 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 = "untitled-ui"' }
]
const SPACE = ['0', '0-5', '1', '1-5', '2', '2-5', '3', '3-5', '4', '5', '6', '8', '10', '12', '16']
const RADII = ['none', 'sm', 'md', 'lg', 'xl', 'xxl']
const SHADOWS = ['1', '2', '3', '4', '5', '6']
const SIZES = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl']
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
// ── Estado de los controles interactivos ──────────────────────────
let email = $state('olivia@untitledui.com')
let fruit = $state<string[]>(['mango'])
let agree = $state(true)
let notify = $state(false)
let plan = $state('pro')
let wifi = $state(true)
let analytics = $state(false)
let volume = $state([64])
let tab = $state('overview')
let tabsPills = $state('account')
let faq = $state<string[]>(['what'])
let bold = $state(true)
let fmt = $state<string[]>(['bold'])
let align = $state<string[]>(['left'])
let toolbarFmt = $state<string[]>(['bold'])
// ── Estado de los overlays / menús ────────────────────────────────
let menuNotifications = $state(true)
let menuTheme = $state('system')
let menubarValue = $state('')
</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}
<!-- ═══ Hero ═══════════════════════════════════════════════════════ -->
<section data-temas-hero>
<h1>Untitled UI · Carbon</h1>
<p>
Tema para activeUIX sobre el sistema de theming de Eidos. Primario <strong>carbón</strong>
monocromo, tipografía Inter, grises fríos, radios de 8px, sombras en capas y anillos de foco
suaves — los 9 roles canónicos retintados, sin inventar variants ni roles nuevos.
</p>
<div data-temas-hero-meta>
<span data-temas-pill><i style="background: var(--color-primary-solid)"></i> Carbon · #1F2024</span>
<span data-temas-pill><i style="background: var(--color-content-primary)"></i> Inter</span>
<span data-temas-pill><i style="background: var(--color-fulfill-solid)"></i> 9 roles · 5 variants</span>
<span data-temas-pill><i style="background: var(--color-border-strong)"></i> Light · Dark · Density</span>
</div>
</section>
<!-- ═══ Color ══════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Color</h2>
<p>Los 9 roles canónicos mapeados a las paletas de Untitled UI. El theme retinta; los roles son canon fijo del framework.</p>
</div>
<div data-temas-stack>
<p data-temas-subhead>Roles semánticos</p>
<div data-temas-roles>
{#each THEME_ROLES as r}
<div data-temas-role>
<div data-temas-role-solid style="background: var(--color-{r.role}-solid)"></div>
<div data-temas-role-body>
<strong>{r.label}</strong>
<small>{r.note}</small>
<div data-temas-role-tints>
<i style="background: var(--color-{r.role}-track)"></i>
<i style="background: var(--color-{r.role}-element)"></i>
<i style="background: var(--color-{r.role}-border)"></i>
<i style="background: var(--color-{r.role}-text)"></i>
</div>
</div>
</div>
{/each}
</div>
</div>
<div data-temas-stack>
<p data-temas-subhead>Escalas físicas · 12 pasos</p>
{#each THEME_SCALES as sc}
<div data-temas-ramp>
<div data-temas-ramp-head>
<strong>{sc.label}</strong>
<code>{hex[`${sc.name}-9`] ?? `--scale-${sc.name}-9`}</code>
</div>
<div data-temas-ramp-steps>
{#each COLOR_STEPS as step}
<div
data-temas-step
style="background: var(--scale-{sc.name}-{step}); color: {stepInk(`${sc.name}-${step}`)}"
title={hex[`${sc.name}-${step}`] ?? ''}
>
{step}
</div>
{/each}
</div>
</div>
{/each}
</div>
<div data-temas-stack>
<p data-temas-subhead>Superficies, contenido y bordes</p>
<div data-temas-surfaces>
<div data-temas-surface style="background: var(--color-surface-default)">
<span>surface</span><code>default</code>
</div>
<div data-temas-surface style="background: var(--color-surface-raised)">
<span>surface</span><code>raised</code>
</div>
<div data-temas-surface style="background: var(--color-surface-muted)">
<span>surface</span><code>muted</code>
</div>
<div data-temas-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-temas-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-temas-surface style="background: var(--color-surface-raised); border-color: var(--color-border-strong); border-width: 2px">
<span>border</span><code>strong</code>
</div>
</div>
</div>
</section>
<!-- ═══ Tipografía ═════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Tipografía</h2>
<p>Inter para display y texto. Named styles del sistema (hero → caption + code).</p>
</div>
<div data-temas-panel>
{#each TYPE_STYLES as t}
<div data-temas-type-row>
<div data-temas-type-meta>
<strong>{t.label}</strong>
<code>--style-{t.key}</code>
</div>
<div
data-temas-type-sample
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}
</div>
</div>
{/each}
</div>
</section>
<!-- ═══ Espaciado / Radio / Sombra / Tamaño ════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Espaciado, radio, sombra y tamaño</h2>
<p>Primitivas estables del tema. La escala base es de 4px; controles a 8px de radio.</p>
</div>
<div data-temas-fieldset>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Spacing</p>
{#each SPACE as k}
<div data-temas-space-row>
<code>--space-{k}</code>
<div data-temas-space-bar style="inline-size: var(--space-{k})"></div>
</div>
{/each}
</div>
<div data-temas-stack>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Radius</p>
<div data-temas-tiles>
{#each RADII as k}
<div data-temas-radius-tile style="border-radius: var(--radius-{k})">{k}</div>
{/each}
</div>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Sizes · control-height</p>
{#each SIZES as k}
<div data-temas-size-row>
<div data-temas-size-bar style="block-size: var(--size-{k}-control-height)">{k}</div>
<code style="color: var(--color-content-muted); font-family: var(--font-mono); font-size: var(--font-size-xs)">var(--size-{k}-control-height)</code>
</div>
{/each}
</div>
</div>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Shadows</p>
<div data-temas-tiles>
{#each SHADOWS as k}
<div data-temas-shadow-tile style="box-shadow: var(--shadow-{k})">shadow-{k}</div>
{/each}
</div>
</div>
</section>
<!-- ═══ Botones ════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Botones</h2>
<p>6 variants · 9 roles · 5 tamaños · estados. El primario es carbón; el texto sobre sólidos de color resuelve a blanco.</p>
</div>
<div data-temas-demo>
<div data-temas-demo-block>
<p data-temas-subhead>Variants</p>
<div data-temas-cluster>
{#each BTN_VARIANTS as v}
<Button variant={v}>{v}</Button>
{/each}
</div>
</div>
<div data-temas-demo-block>
<p data-temas-subhead>Roles e intents</p>
<div data-temas-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>
<div data-temas-demo-block>
<p data-temas-subhead>Tamaños</p>
<div data-temas-cluster>
{#each BTN_SIZES as sz}
<Button size={sz}>Button {sz}</Button>
{/each}
</div>
</div>
<div data-temas-demo-block>
<p data-temas-subhead>Con icono y estados</p>
<div data-temas-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>
</div>
</section>
<!-- ═══ Badges ═════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Badges</h2>
<p>Pills de estado en los 4 chip-variants y la paleta completa de roles.</p>
</div>
<div data-temas-demo>
<div data-temas-demo-block>
<p data-temas-subhead>Colores (soft)</p>
<div data-temas-cluster>
{#each BADGE_COLORS as c}
<Badge color={c} dot>{c}</Badge>
{/each}
</div>
</div>
<div data-temas-demo-block>
<p data-temas-subhead>Variants</p>
<div data-temas-cluster>
{#each CHIP_VARIANTS as v}
<Badge color="primary" variant={v}>{v}</Badge>
{/each}
<Badge color="fulfill" variant="soft">Activo</Badge>
<Badge color="risk" variant="soft" removable onRemove={() => {}}>Filtro</Badge>
</div>
</div>
</div>
</section>
<!-- ═══ Formulario ═════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Controles de formulario</h2>
<p>Field, Select, Checkbox, Radio, Switch y Slider con el aspecto del tema.</p>
</div>
<div data-temas-fieldset>
<div data-temas-panel data-temas-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.Item value="papaya" label="Papaya">Papaya<Select.ItemIndicator /></Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select>
</Field>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Selección</p>
<label data-temas-cluster style="cursor: pointer">
<Checkbox bind:checked={agree}><Checkbox.Indicator /></Checkbox>
<span>Acepto los términos del servicio</span>
</label>
<label data-temas-cluster style="cursor: pointer">
<Checkbox bind:checked={notify} color="affirm"><Checkbox.Indicator /></Checkbox>
<span>Enviarme novedades del producto</span>
</label>
<Separator />
<RadioGroup bind:value={plan}>
<RadioGroup.Label data-temas-subhead>Plan</RadioGroup.Label>
<label data-temas-cluster style="cursor: pointer">
<RadioGroup.Item value="starter"><RadioGroup.Indicator /></RadioGroup.Item>
<span>Starter</span>
</label>
<label data-temas-cluster style="cursor: pointer">
<RadioGroup.Item value="pro"><RadioGroup.Indicator /></RadioGroup.Item>
<span>Pro</span>
</label>
<label data-temas-cluster style="cursor: pointer">
<RadioGroup.Item value="enterprise"><RadioGroup.Indicator /></RadioGroup.Item>
<span>Enterprise</span>
</label>
</RadioGroup>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Switch & Slider</p>
<label data-temas-cluster style="justify-content: space-between; cursor: pointer">
<span>Wi-Fi</span>
<Switch bind:checked={wifi}><Switch.Thumb /></Switch>
</label>
<label data-temas-cluster style="justify-content: space-between; cursor: pointer">
<span>Analítica</span>
<Switch bind:checked={analytics} intent="affirm"><Switch.Thumb /></Switch>
</label>
<Separator />
<div data-temas-stack style="gap: var(--space-2)">
<span data-temas-subhead 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>
<!-- ═══ Feedback ═══════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Feedback y estado</h2>
<p>Banners por intent, progreso, avatares y tarjetas.</p>
</div>
<div data-temas-stack>
<Banner intent="primary" variant="soft">Tu prueba gratuita termina en 7 días. Mejora tu plan cuando quieras.</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. Revisa tus datos.</Banner>
</div>
<div data-temas-fieldset>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Progress</p>
<Progress value={64} max={100}>
<Progress.Label>Subiendo archivo</Progress.Label>
<Progress.ValueText />
<Progress.Indicator />
</Progress>
<div data-temas-cluster>
<Progress value={72} max={100} shape="circular"><Progress.Indicator /></Progress>
<Progress value={30} max={100} shape="circular"><Progress.Indicator /></Progress>
</div>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Avatares</p>
<div data-temas-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>
<Avatar color="risk" ring="soft"><Avatar.Fallback>SK</Avatar.Fallback></Avatar>
</div>
</div>
<Card variant="soft" color="primary">
<Card.Header>
<Card.Title>Plan Pro</Card.Title>
<Card.Description>Todo lo del Starter, más 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>
</div>
</section>
<!-- ═══ Tabs ═══════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Tabs</h2>
<p>Variantes line y pills. Indicador animado, foco con anillo carbón, activo en color primario.</p>
</div>
<div data-temas-fieldset>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Line</p>
<Tabs bind:value={tab} variant="line">
<Tabs.List aria-label="Secciones de ejemplo">
<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 y métricas clave del mes.</Tabs.Content>
<Tabs.Content value="activity">Registro de actividad reciente del equipo.</Tabs.Content>
<Tabs.Content value="settings">Preferencias de notificación y seguridad.</Tabs.Content>
</Tabs>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Pills</p>
<Tabs bind:value={tabsPills} variant="pills">
<Tabs.List aria-label="Secciones de ejemplo">
<Tabs.Trigger value="account">Account</Tabs.Trigger>
<Tabs.Trigger value="team">Team</Tabs.Trigger>
<Tabs.Trigger value="billing">Billing</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="account">Datos de perfil y sesión.</Tabs.Content>
<Tabs.Content value="team">Miembros e invitaciones.</Tabs.Content>
<Tabs.Content value="billing">Plan, método de pago y facturas.</Tabs.Content>
</Tabs>
</div>
</div>
</section>
<!-- ═══ Disclosure ═════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Disclosure</h2>
<p>Accordion (outline) — apertura única, chevron y foco coherentes con el tema.</p>
</div>
<div data-temas-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 — donde el theming vive en
eidos y retinta los roles canónicos.
</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(&#123; config &#125;)</code>: se remapean los 9 roles a paletas
y se ajustan primitivas estables (tipografía, radio, sombra).
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="dark">
<Accordion.Header><Accordion.Trigger>¿Y el modo oscuro?</Accordion.Trigger></Accordion.Header>
<Accordion.Content>
El tema declara escalas light y dark; el primario carbón se invierte solo gracias al slot
<code>contrast</code>.
</Accordion.Content>
</Accordion.Item>
</Accordion>
</div>
</section>
<!-- ═══ Toggles ════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Toggles</h2>
<p>Toggle suelto y toggle-group (segmentado, single + multiple). Activo en color primario.</p>
</div>
<div data-temas-demo>
<div data-temas-demo-block>
<p data-temas-subhead>Toggle</p>
<div data-temas-cluster>
<Toggle bind:pressed={bold}>Bold</Toggle>
<Toggle variant="outline">Outline</Toggle>
<Toggle variant="ghost">Ghost</Toggle>
<Toggle disabled>Disabled</Toggle>
</div>
</div>
<div data-temas-demo-block>
<p data-temas-subhead>Toggle group · segmentado</p>
<div data-temas-cluster>
<ToggleGroup type="multiple" attached bind:value={fmt} aria-label="Formato de texto">
<ToggleGroup.Item value="bold">B</ToggleGroup.Item>
<ToggleGroup.Item value="italic">I</ToggleGroup.Item>
<ToggleGroup.Item value="underline">U</ToggleGroup.Item>
</ToggleGroup>
<ToggleGroup type="single" attached bind:value={align} aria-label="Alineación">
<ToggleGroup.Item value="left">Izq.</ToggleGroup.Item>
<ToggleGroup.Item value="center">Centro</ToggleGroup.Item>
<ToggleGroup.Item value="right">Der.</ToggleGroup.Item>
</ToggleGroup>
</div>
</div>
</div>
</section>
<!-- ═══ Toolbar ════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Toolbar</h2>
<p>Barra de herramientas con botones, separador y grupo segmentado.</p>
</div>
<div data-temas-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.Separator />
<Toolbar.Button>Compartir</Toolbar.Button>
</Toolbar>
</div>
</section>
<!-- ═══ Overlays ═══════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Overlays</h2>
<p>Popover y dialog — paneles portaleados con la superficie, borde y sombra del tema.</p>
</div>
<div data-temas-panel data-temas-cluster>
<Popover>
<Popover.Trigger>Abrir popover</Popover.Trigger>
<Popover.Portal>
<Popover.Content side="bottom" align="start" sideOffset={8}>
<Popover.Arrow />
<Popover.Title>Dimensiones</Popover.Title>
<Popover.Description>Ajusta el ancho del panel lateral.</Popover.Description>
<div data-temas-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>Abrir dialog</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>¿Eliminar proyecto?</Dialog.Title>
<Dialog.Description>
Esta acción es permanente y no se puede deshacer.
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Dialog.Close>Cancelar</Dialog.Close>
<Button intent="threat">Eliminar</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
</div>
</section>
<!-- ═══ Menús ══════════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Menús</h2>
<p>Dropdown, menubar, context menu y tooltip — todos portaleados, con la sombra del tema.</p>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Menubar</p>
<Menubar bind:value={menubarValue}>
<Menubar.Menu value="file">
<Menubar.Trigger>Archivo</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item>Nuevo documento</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.Menu value="view">
<Menubar.Trigger>Ver</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.CheckboxItem bind:checked={menuNotifications}>
Mostrar barra lateral
</Menubar.CheckboxItem>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar>
<div data-temas-cluster>
<DropdownMenu>
<DropdownMenu.Trigger variant="outline">Opciones</DropdownMenu.Trigger>
<DropdownMenu.Content side="bottom" align="start" sideOffset={6}>
<DropdownMenu.Group>
<DropdownMenu.GroupHeading>Cuenta</DropdownMenu.GroupHeading>
<DropdownMenu.Item>Perfil</DropdownMenu.Item>
<DropdownMenu.Item>Facturación</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem bind:checked={menuNotifications}>
Notificaciones
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator />
<DropdownMenu.RadioGroup bind:value={menuTheme}>
<DropdownMenu.RadioItem value="light">Claro</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="dark">Oscuro</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="system">Sistema</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu>
<Tooltip.Group delayDuration={250}>
<Tooltip>
<Tooltip.Trigger>Pasa el ratón</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>
<Tooltip.Arrow />
Tooltip con la superficie y la sombra del tema
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
</Tooltip.Group>
</div>
<ContextMenu>
<ContextMenu.Trigger>
<div data-temas-ctx-target>Click derecho aquí</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>
</section>
<!-- ═══ Semántica ══════════════════════════════════════════════════ -->
<section data-temas-section>
<div data-temas-section-head>
<h2>Semántica (sema)</h2>
<p>
La capa perceptual está activa: cada interacción emite una señal con familia + intent que se
materializa en <strong>sonido</strong> (earcon), <strong>haptic</strong> (Vibration API) y una
<strong>reacción visual</strong> <code>data-event-*</code> teñida por el intent del tema.
Pulsa los botones — en un navegador con gesto de usuario sonarán.
</p>
</div>
<div data-temas-panel data-temas-stack>
<p data-temas-subhead>Intents perceptuales</p>
<div data-temas-cluster>
<Button intent="affirm">Affirm</Button>
<Button intent="fulfill">Fulfill</Button>
<Button intent="risk" variant="soft">Risk</Button>
<Button intent="threat" variant="soft">Threat</Button>
<Button intent="loss" variant="soft">Loss</Button>
</div>
<p style="color: var(--color-content-muted); font-size: var(--font-size-sm)">
El motor (<code>EngineSemantic</code>) se cablea en el layout vía
<code>createActiveUix(&#123; events: &#123; sound, haptic, components &#125; &#125;)</code> con
los packs sema por componente. Sonido y haptic son opt-in; aquí están encendidos.
</p>
</div>
</section>
<footer data-temas-foot>
Tema <code>untitled-ui</code> · construido con <code>ActiveEidos.create(&#123; config &#125;)</code> ·
primario carbón + 9 roles canónicos retintados sobre Untitled UI.
</footer>
Loading…
Cancel
Save

Powered by TurnKey Linux.