|
|
|
|
@ -0,0 +1,264 @@
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* Structural demo — the three structural axes, live on one real panel.
|
|
|
|
|
*
|
|
|
|
|
* 1. El taller — a settings panel whose every gap/padding is `var(--space-*)`, retuned live by
|
|
|
|
|
* four controls: densidad (compacidad) · zoom (scaling) · unidad base (el ritmo) · fluido
|
|
|
|
|
* (clamp con el viewport). Un solo panel, tres ejes ortogonales + el builder de espacio.
|
|
|
|
|
* 2. El ritmo — la escalera de espacio (base × N) a la base/fluido actuales.
|
|
|
|
|
*
|
|
|
|
|
* Self-contained: eidos foundation + the pure `buildSpaceScale` (the applySpacing engine).
|
|
|
|
|
* Density / scaling apply via `data-*` on the panel; the space ladder via inline `--space-*`.
|
|
|
|
|
*/
|
|
|
|
|
import '@/uix/eidos/index.css'
|
|
|
|
|
import { buildSpaceScale } from '$uix/eidos'
|
|
|
|
|
|
|
|
|
|
let theme = $state<'light' | 'dark'>('dark')
|
|
|
|
|
$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)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const DENSITIES = ['compact', 'comfortable', 'spacious'] as const
|
|
|
|
|
type Density = (typeof DENSITIES)[number]
|
|
|
|
|
const SCALINGS = ['90', '95', '100', '105', '110'] as const
|
|
|
|
|
type Scaling = (typeof SCALINGS)[number]
|
|
|
|
|
|
|
|
|
|
let density = $state<Density>('comfortable')
|
|
|
|
|
let scaling = $state<Scaling>('100')
|
|
|
|
|
let base = $state(4)
|
|
|
|
|
let growth = $state(1)
|
|
|
|
|
|
|
|
|
|
// The pure builder — exactly what ActiveEidos.applySpacing runs. Drag base/fluido → the whole
|
|
|
|
|
// `--space-*` ladder re-derives; density/scaling compose via the preserved calc().
|
|
|
|
|
const scale = $derived(buildSpaceScale({ base, growth, minVw: 480, maxVw: 1280 }))
|
|
|
|
|
const spaceVars = $derived(scale.variables.join(' '))
|
|
|
|
|
const fluid = $derived(growth > 1)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head><title>Estructura · Eidos</title></svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="root" data-theme={theme}>
|
|
|
|
|
<header class="masthead">
|
|
|
|
|
<div class="mast-row">
|
|
|
|
|
<p class="kicker">Eidos · sistemas estructurales</p>
|
|
|
|
|
<button class="theme-toggle" onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}>
|
|
|
|
|
{theme === 'light' ? '◐ claro' : '◑ oscuro'}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<h1 class="mast-title">El espacio es <em>ritmo</em></h1>
|
|
|
|
|
<p class="mast-lede">
|
|
|
|
|
Todos shippean una escala de espacio plana y estática. Aquí el espacio es un
|
|
|
|
|
<strong>ritmo</strong> derivado de una unidad base, <strong>fluido</strong> (respira con el
|
|
|
|
|
viewport) y compuesto con <strong>densidad</strong> y <strong>zoom</strong> — tres ejes desde
|
|
|
|
|
una semilla. Toca los cuatro mandos y mira el mismo panel recomponerse.
|
|
|
|
|
</p>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<!-- ── 1 · El taller estructural ─────────────────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">El taller</h2>
|
|
|
|
|
<p class="sec-note">
|
|
|
|
|
Cada hueco y relleno del panel es <code>var(--space-*)</code>. Densidad aprieta el
|
|
|
|
|
layout, zoom escala todo (incluido el texto), la unidad base reentona el ritmo y el fluido
|
|
|
|
|
lo hace respirar. <em>Estira la ventana</em> con fluido activo para verlo crecer.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="lab">
|
|
|
|
|
<!-- the live sample: a settings panel, all spacing from --space-* -->
|
|
|
|
|
<div class="panel" style={spaceVars} data-density={density} data-scaling={scaling}>
|
|
|
|
|
<div class="panel-head">
|
|
|
|
|
<span class="panel-avatar">N</span>
|
|
|
|
|
<div class="panel-id">
|
|
|
|
|
<span class="panel-name">Nora Vega</span>
|
|
|
|
|
<span class="panel-mail">nora@activething.com</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="panel-plan">Pro</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-list">
|
|
|
|
|
{#each [['Notificaciones', 'Activadas'], ['Idioma', 'Español'], ['Tema', 'Sistema'], ['Privacidad', '2 activas']] as [k, v]}
|
|
|
|
|
<div class="panel-row">
|
|
|
|
|
<span class="panel-k">{k}</span>
|
|
|
|
|
<span class="panel-v">{v}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-foot">
|
|
|
|
|
<button class="p-btn p-ghost">Cancelar</button>
|
|
|
|
|
<button class="p-btn p-solid">Guardar</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- control panel -->
|
|
|
|
|
<div class="ctl-panel">
|
|
|
|
|
<div class="ctl">
|
|
|
|
|
<span class="ctl-label">densidad</span>
|
|
|
|
|
<div class="chips">
|
|
|
|
|
{#each DENSITIES as d}
|
|
|
|
|
<button class="chip" data-active={density === d} onclick={() => (density = d)}>{d}</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctl">
|
|
|
|
|
<span class="ctl-label">zoom (scaling)</span>
|
|
|
|
|
<div class="chips">
|
|
|
|
|
{#each SCALINGS as s}
|
|
|
|
|
<button class="chip" data-active={scaling === s} onclick={() => (scaling = s)}>{s}</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctl">
|
|
|
|
|
<label class="ctl-row">
|
|
|
|
|
<span class="ctl-label">unidad base</span>
|
|
|
|
|
<input type="range" min="3" max="6" step="0.5" bind:value={base} />
|
|
|
|
|
<span class="ctl-val">{base}px</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctl">
|
|
|
|
|
<label class="ctl-row">
|
|
|
|
|
<span class="ctl-label">fluido ×</span>
|
|
|
|
|
<input type="range" min="1" max="1.6" step="0.05" bind:value={growth} />
|
|
|
|
|
<span class="ctl-val">{growth.toFixed(2)}</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<code class="ctl-code"
|
|
|
|
|
>--space-4: {scale.steps.find((s) => s.key === '4')?.value} · density · scaling</code
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── 2 · El ritmo ──────────────────────────────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">El ritmo</h2>
|
|
|
|
|
<p class="sec-note">
|
|
|
|
|
La escalera completa a <code>base {base}px</code>{fluid ? ` · fluido ×${growth.toFixed(2)}` : ''}.
|
|
|
|
|
Cada peldaño = base × N (modular). {fluid
|
|
|
|
|
? 'Con fluido, cada barra crece de su mínimo a su máximo entre 480 y 1280px.'
|
|
|
|
|
: 'Sube el fluido para que respiren.'}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ladder">
|
|
|
|
|
{#each scale.steps as step}
|
|
|
|
|
{#if step.minPx > 0}
|
|
|
|
|
<div class="ladder-row">
|
|
|
|
|
<span class="ladder-key">space-{step.key}</span>
|
|
|
|
|
<span class="ladder-bar-wrap">
|
|
|
|
|
<span class="ladder-bar" style="inline-size: {step.minPx}px;"></span>
|
|
|
|
|
{#if fluid && step.maxPx > step.minPx}
|
|
|
|
|
<span class="ladder-bar-grow" style="inline-size: {step.maxPx - step.minPx}px;"></span>
|
|
|
|
|
{/if}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="ladder-px">{step.minPx}{fluid ? `–${step.maxPx}` : ''}px</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<footer class="footnote">
|
|
|
|
|
<p>
|
|
|
|
|
<strong>El espacio como ritmo, no como tabla de píxeles.</strong> El campo entero (Tailwind ·
|
|
|
|
|
Material · Radix · Chakra · Mantine · Ant · Carbon · Fluent) shippea una escala de space
|
|
|
|
|
plana y estática. Eidos la deriva de una unidad base, la hace <strong>fluida</strong> (como su
|
|
|
|
|
tipo fluido — casi nadie lo hace para el espacio) y la compone con densidad × zoom. Builder
|
|
|
|
|
runtime: <code>applySpacing(seed)</code>. La escala authored sigue intacta — el builder es la
|
|
|
|
|
alternativa matemática opt-in. (Lo estructural es solo-estado: el escenario, no el suceso.)
|
|
|
|
|
</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: 52ch; font-size: var(--font-size-lg); line-height: 1.5; color: var(--color-content-secondary); margin: 0; text-wrap: pretty; }
|
|
|
|
|
.mast-lede strong { color: var(--color-content-primary); font-weight: 600; }
|
|
|
|
|
|
|
|
|
|
.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: 64ch; font-size: var(--font-size-sm); line-height: 1.55; color: var(--color-content-muted); }
|
|
|
|
|
.sec-note em { color: var(--color-content-primary); font-style: normal; font-weight: 600; }
|
|
|
|
|
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 · El taller */
|
|
|
|
|
.lab { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
|
|
|
|
|
@media (min-width: 52rem) { .lab { grid-template-columns: 1fr 20rem; } }
|
|
|
|
|
|
|
|
|
|
/* the sample panel — ALL spacing below is var(--space-*), which the controls retune live */
|
|
|
|
|
.panel {
|
|
|
|
|
inline-size: 100%;
|
|
|
|
|
max-inline-size: 30rem;
|
|
|
|
|
background: var(--color-surface-raised);
|
|
|
|
|
border: 1px solid var(--rule);
|
|
|
|
|
border-radius: var(--radius-lg);
|
|
|
|
|
box-shadow: var(--depth-raised-shadow, var(--shadow-raised));
|
|
|
|
|
padding: var(--space-5);
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: var(--space-5);
|
|
|
|
|
}
|
|
|
|
|
.panel-head { display: flex; align-items: center; gap: var(--space-3); }
|
|
|
|
|
.panel-avatar { inline-size: var(--space-10); block-size: var(--space-10); border-radius: 50%; display: grid; place-items: center; background: var(--color-primary-solid); color: var(--color-primary-on-solid, #fff); font-weight: 600; flex: none; }
|
|
|
|
|
.panel-id { display: flex; flex-direction: column; gap: var(--space-0-5); flex: 1; min-inline-size: 0; }
|
|
|
|
|
.panel-name { font-family: var(--font-family-display); font-weight: 600; color: var(--color-content-primary); }
|
|
|
|
|
.panel-mail { font-size: var(--font-size-xs); color: var(--color-content-muted); }
|
|
|
|
|
.panel-plan { font-family: var(--font-family-mono); font-size: var(--font-size-xs); padding: var(--space-0-5) var(--space-2); border-radius: 999px; background: var(--color-surface-overlay); color: var(--color-content-secondary); border: 1px solid var(--rule); }
|
|
|
|
|
.panel-list { display: flex; flex-direction: column; gap: var(--space-1); padding-block: var(--space-3); border-block: 1px solid color-mix(in srgb, var(--rule) 60%, transparent); }
|
|
|
|
|
.panel-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-1); border-radius: var(--radius-sm); font-size: var(--font-size-sm); }
|
|
|
|
|
.panel-k { color: var(--color-content-secondary); }
|
|
|
|
|
.panel-v { color: var(--color-content-muted); font-size: var(--font-size-xs); }
|
|
|
|
|
.panel-foot { display: flex; justify-content: flex-end; gap: var(--space-2); }
|
|
|
|
|
.p-btn { font-family: var(--font-family-primary); font-size: var(--font-size-sm); padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); border: 1px solid transparent; cursor: pointer; }
|
|
|
|
|
.p-solid { background: var(--color-primary-solid); color: var(--color-primary-on-solid, #fff); }
|
|
|
|
|
.p-ghost { background: transparent; color: var(--color-content-secondary); border-color: var(--rule); }
|
|
|
|
|
|
|
|
|
|
.ctl-panel { display: flex; flex-direction: column; gap: 1.5rem; padding: clamp(1.25rem, 3vw, 2rem); border-radius: var(--radius-lg); background: var(--color-surface-default); border: 1px solid var(--rule); }
|
|
|
|
|
.ctl { display: flex; flex-direction: column; gap: 0.65rem; }
|
|
|
|
|
.ctl-row { display: flex; align-items: center; gap: 0.75rem; }
|
|
|
|
|
.ctl-row input { flex: 1; accent-color: var(--color-primary-solid); }
|
|
|
|
|
.ctl-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); }
|
|
|
|
|
.ctl-val { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-content-primary); inline-size: 3.5rem; text-align: end; }
|
|
|
|
|
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
|
|
|
.chip { font-family: var(--font-family-mono); font-size: var(--font-size-xs); padding: 0.35rem 0.75rem; border-radius: 999px; cursor: pointer; background: var(--color-surface-raised); color: var(--color-content-secondary); border: 1px solid var(--rule); }
|
|
|
|
|
.chip:hover { color: var(--color-content-primary); }
|
|
|
|
|
.chip[data-active='true'] { background: var(--color-primary-solid); color: var(--color-primary-on-solid, #fff); border-color: var(--color-primary-solid); }
|
|
|
|
|
.ctl-code { display: block; font-family: var(--font-family-mono); font-size: var(--font-size-xs); padding: 0.7rem 0.9rem; border-radius: var(--radius-md); background: var(--color-surface-muted); color: var(--color-content-secondary); word-break: break-all; }
|
|
|
|
|
|
|
|
|
|
/* 2 · El ritmo */
|
|
|
|
|
.ladder { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
|
|
|
.ladder-row { display: grid; grid-template-columns: 5.5rem 1fr 6rem; gap: 1rem; align-items: center; }
|
|
|
|
|
.ladder-key { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-content-secondary); }
|
|
|
|
|
.ladder-bar-wrap { display: flex; align-items: center; min-inline-size: 0; }
|
|
|
|
|
.ladder-bar { block-size: 0.85rem; border-radius: 3px; background: var(--color-primary-solid); flex: none; }
|
|
|
|
|
.ladder-bar-grow { block-size: 0.85rem; border-radius: 0 3px 3px 0; background: color-mix(in oklab, var(--color-primary-solid) 35%, transparent); flex: none; }
|
|
|
|
|
.ladder-px { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-content-muted); text-align: end; }
|
|
|
|
|
|
|
|
|
|
.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; }
|
|
|
|
|
.footnote strong { color: var(--color-content-primary); font-weight: 600; }
|
|
|
|
|
</style>
|