|
|
|
|
@ -0,0 +1,457 @@
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* Typography demo — the Eidos type system, same shape as the Color demo.
|
|
|
|
|
*
|
|
|
|
|
* Everything renders through the live CSS custom properties (so it reflects the
|
|
|
|
|
* active theme + light/dark):
|
|
|
|
|
* - the SCALE: a live modular/fluid ladder built by `buildTypeScale` (the runtime
|
|
|
|
|
* `applyTypeScale` engine) — drag the ratio + the whole ladder re-derives, named
|
|
|
|
|
* styles included (they reference `--font-size-*` via `var()`);
|
|
|
|
|
* - the FAMILIES: the config-driven `@font-face` slots (primary/secondary/mono/display);
|
|
|
|
|
* - the AXES: tracking · leading · measure · wrap · numeric driven live through the
|
|
|
|
|
* `--_text-*` per-instance vars the recipe reads.
|
|
|
|
|
*
|
|
|
|
|
* Self-contained: imports the eidos foundation CSS, renders raw elements with the
|
|
|
|
|
* recipe `data-*` attrs. `data-theme` on `.root` activates light/dark.
|
|
|
|
|
*/
|
|
|
|
|
import '@/uix/eidos/index.css'
|
|
|
|
|
import { buildTypeScale } from '$uix/eidos'
|
|
|
|
|
|
|
|
|
|
let theme = $state<'light' | 'dark'>('light')
|
|
|
|
|
|
|
|
|
|
const SIZES = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'xxxl'] as const
|
|
|
|
|
const STYLES = [
|
|
|
|
|
{ style: 'hero', label: 'Hero', sample: 'Diseñando lo que ocurre' },
|
|
|
|
|
{ style: 'h1', label: 'H1', sample: 'Una escala que respira' },
|
|
|
|
|
{ style: 'h2', label: 'H2', sample: 'Ritmo y jerarquía' },
|
|
|
|
|
{ style: 'h3', label: 'H3', sample: 'Cada paso, intencional' },
|
|
|
|
|
{ style: 'h4', label: 'H4', sample: 'Detalle tipográfico' },
|
|
|
|
|
{ style: 'body', label: 'Body', sample: 'El cuerpo de texto sostiene la lectura larga con una medida cómoda y un interlineado generoso.' },
|
|
|
|
|
{ style: 'caption', label: 'Caption', sample: 'Metadatos, pies y notas al margen' }
|
|
|
|
|
] as const
|
|
|
|
|
|
|
|
|
|
// ── Live type scale (buildTypeScale — the applyTypeScale engine) ──────────
|
|
|
|
|
let base = $state(16)
|
|
|
|
|
let ratio = $state(1.25)
|
|
|
|
|
let fluid = $state(true)
|
|
|
|
|
let ratioMax = $state(1.333)
|
|
|
|
|
let vw = $state(820)
|
|
|
|
|
|
|
|
|
|
const RATIOS = [
|
|
|
|
|
{ value: 1.125, name: 'segunda mayor' },
|
|
|
|
|
{ value: 1.2, name: 'tercera menor' },
|
|
|
|
|
{ value: 1.25, name: 'tercera mayor' },
|
|
|
|
|
{ value: 1.333, name: 'cuarta justa' },
|
|
|
|
|
{ value: 1.414, name: 'tritono' },
|
|
|
|
|
{ value: 1.5, name: 'quinta justa' },
|
|
|
|
|
{ value: 1.618, name: 'áurea' }
|
|
|
|
|
] as const
|
|
|
|
|
|
|
|
|
|
const scale = $derived(
|
|
|
|
|
buildTypeScale({
|
|
|
|
|
base,
|
|
|
|
|
ratio,
|
|
|
|
|
// keep the fluid endpoint ≥ the base ratio so the scale never inverts on wide screens
|
|
|
|
|
ratioMax: fluid ? Math.max(ratio, ratioMax) : undefined,
|
|
|
|
|
minVw: 480,
|
|
|
|
|
maxVw: 1280
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
const scaleVars = $derived(scale.variables.join(' '))
|
|
|
|
|
const ratioName = $derived(RATIOS.find((r) => Math.abs(r.value - ratio) < 0.001)?.name ?? 'custom')
|
|
|
|
|
|
|
|
|
|
// px at a chosen viewport, for the readout (linear interp between min/max viewport).
|
|
|
|
|
function pxAt(min: number, max: number): number {
|
|
|
|
|
if (min === max) return min
|
|
|
|
|
const t = Math.min(1, Math.max(0, (vw - 480) / (1280 - 480)))
|
|
|
|
|
return Math.round((min + (max - min) * t) * 100) / 100
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Axes playground ───────────────────────────────────────────────────────
|
|
|
|
|
const TRACKINGS = ['tighter', 'tight', 'normal', 'wide', 'wider'] as const
|
|
|
|
|
const LEADINGS = ['tight', 'snug', 'normal', 'relaxed', 'loose'] as const
|
|
|
|
|
const MEASURES = ['narrow', 'normal', 'wide'] as const
|
|
|
|
|
const WRAPS = ['pretty', 'balance', 'nowrap'] as const
|
|
|
|
|
|
|
|
|
|
let tracking = $state<(typeof TRACKINGS)[number]>('normal')
|
|
|
|
|
let leading = $state<(typeof LEADINGS)[number]>('normal')
|
|
|
|
|
let measure = $state<(typeof MEASURES)[number]>('normal')
|
|
|
|
|
let wrap = $state<(typeof WRAPS)[number]>('pretty')
|
|
|
|
|
let numeric = $state(false)
|
|
|
|
|
|
|
|
|
|
const axisStyle = $derived(
|
|
|
|
|
[
|
|
|
|
|
`--_text-letter-spacing: var(--tracking-${tracking})`,
|
|
|
|
|
`--_text-line-height: var(--leading-${leading})`,
|
|
|
|
|
`--_text-measure: var(--measure-${measure})`,
|
|
|
|
|
`--_text-wrap: ${wrap}`,
|
|
|
|
|
`--_text-font-feature: ${numeric ? 'var(--font-feature-tabular)' : 'normal'}`
|
|
|
|
|
].join('; ')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const FAMILIES = [
|
|
|
|
|
{ slot: 'primary', label: 'Primary', note: 'UI · cuerpo' },
|
|
|
|
|
{ slot: 'display', label: 'Display', note: 'titulares' },
|
|
|
|
|
{ slot: 'secondary', label: 'Secondary', note: 'prosa' },
|
|
|
|
|
{ slot: 'mono', label: 'Mono', note: 'código · cifras' }
|
|
|
|
|
] as const
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head><title>Tipografía · Eidos</title></svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="root" data-theme={theme}>
|
|
|
|
|
<header class="masthead">
|
|
|
|
|
<div class="mast-row">
|
|
|
|
|
<p class="kicker">Eidos · sistema tipográfico</p>
|
|
|
|
|
<button class="theme-toggle" onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}>
|
|
|
|
|
{theme === 'light' ? '◐ claro' : '◑ oscuro'}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<h1 class="mast-title" style="font-size: var(--font-size-xxxl)">Tipografía que se construye sola</h1>
|
|
|
|
|
<p class="mast-lede">
|
|
|
|
|
Escala fluida derivada de un ratio · familias config-driven · ejes ópticos en vivo.
|
|
|
|
|
Todo a través de los tokens del motor — mueve un control y mira recomponerse.
|
|
|
|
|
</p>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<!-- ── Escala (live) ──────────────────────────────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">Escala</h2>
|
|
|
|
|
<p class="sec-note">
|
|
|
|
|
<code>buildTypeScale</code> · el motor de <code>applyTypeScale</code>. Un ratio modular
|
|
|
|
|
deriva los 8 pasos desde <code>md</code>; con fluido el ratio interpola entre 480 y 1280px.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="controls">
|
|
|
|
|
<label class="ctrl">
|
|
|
|
|
<span class="ctrl-label">base <em>{base}px</em></span>
|
|
|
|
|
<input type="range" min="13" max="20" step="1" bind:value={base} />
|
|
|
|
|
</label>
|
|
|
|
|
<label class="ctrl">
|
|
|
|
|
<span class="ctrl-label">ratio <em>{ratio.toFixed(3)} · {ratioName}</em></span>
|
|
|
|
|
<input type="range" min="1.1" max="1.7" step="0.001" bind:value={ratio} />
|
|
|
|
|
</label>
|
|
|
|
|
<div class="ctrl chips-ctrl">
|
|
|
|
|
<span class="ctrl-label">presets</span>
|
|
|
|
|
<div class="chips">
|
|
|
|
|
{#each RATIOS as r}
|
|
|
|
|
<button class="chip" data-on={Math.abs(r.value - ratio) < 0.001} onclick={() => (ratio = r.value)}>{r.value}</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="ctrl switch-ctrl">
|
|
|
|
|
<span class="ctrl-label">fluido</span>
|
|
|
|
|
<input type="checkbox" bind:checked={fluid} />
|
|
|
|
|
</label>
|
|
|
|
|
{#if fluid}
|
|
|
|
|
<label class="ctrl">
|
|
|
|
|
<span class="ctrl-label">ratio máx <em>{ratioMax.toFixed(3)}</em></span>
|
|
|
|
|
<input type="range" min={ratio} max="1.8" step="0.001" bind:value={ratioMax} />
|
|
|
|
|
</label>
|
|
|
|
|
<label class="ctrl">
|
|
|
|
|
<span class="ctrl-label">viewport <em>{vw}px</em></span>
|
|
|
|
|
<input type="range" min="480" max="1280" step="10" bind:value={vw} />
|
|
|
|
|
</label>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="ladder" style={scaleVars}>
|
|
|
|
|
{#each scale.sizes as s}
|
|
|
|
|
<div class="rung">
|
|
|
|
|
<span class="rung-key">{s.key}</span>
|
|
|
|
|
<span class="rung-sample" style="font-size: var(--font-size-{s.key})">Grumpy wizards</span>
|
|
|
|
|
<span class="rung-val">
|
|
|
|
|
{#if s.minPx === s.maxPx}{s.minPx}px{:else}{pxAt(s.minPx, s.maxPx)}px <i>·{s.minPx}–{s.maxPx}</i>{/if}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── Estilos nombrados (reflejan la escala) ─────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">Estilos nombrados</h2>
|
|
|
|
|
<p class="sec-note">
|
|
|
|
|
<code>hero · h1–h6 · body · caption</code> referencian <code>--font-size-*</code> por
|
|
|
|
|
<code>var()</code> → re-escalan con el ratio de arriba.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="styles" style={scaleVars}>
|
|
|
|
|
{#each STYLES as st}
|
|
|
|
|
<div class="style-row">
|
|
|
|
|
<span class="style-tag">{st.label}</span>
|
|
|
|
|
{#if st.style === 'body' || st.style === 'caption'}
|
|
|
|
|
<p data-text data-style={st.style} class="style-sample">{st.sample}</p>
|
|
|
|
|
{:else}
|
|
|
|
|
<div data-heading data-style={st.style} class="style-sample">{st.sample}</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── Familias ───────────────────────────────────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">Familias</h2>
|
|
|
|
|
<p class="sec-note"><code>@font-face</code> config-driven · fallback con métricas (anti-CLS).</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="families">
|
|
|
|
|
{#each FAMILIES as f}
|
|
|
|
|
<div class="family">
|
|
|
|
|
<div class="family-meta"><span class="family-label">{f.label}</span><span class="family-note">{f.note}</span></div>
|
|
|
|
|
<div class="family-sample" style="font-family: var(--font-family-{f.slot})">
|
|
|
|
|
<span class="family-aa">Aa</span>
|
|
|
|
|
<span class="family-glyphs">Habría jugado · 0123456789 · ¿Qué? ¡Ya!</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── Ejes (live) ────────────────────────────────────────────────────── -->
|
|
|
|
|
<section class="section">
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
<h2 class="sec-title">Ejes ópticos</h2>
|
|
|
|
|
<p class="sec-note">tracking · leading · measure · wrap · numeric — vía los <code>--_text-*</code> que lee el recipe.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="controls">
|
|
|
|
|
<div class="ctrl chips-ctrl">
|
|
|
|
|
<span class="ctrl-label">tracking</span>
|
|
|
|
|
<div class="chips">{#each TRACKINGS as t}<button class="chip" data-on={tracking === t} onclick={() => (tracking = t)}>{t}</button>{/each}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctrl chips-ctrl">
|
|
|
|
|
<span class="ctrl-label">leading</span>
|
|
|
|
|
<div class="chips">{#each LEADINGS as l}<button class="chip" data-on={leading === l} onclick={() => (leading = l)}>{l}</button>{/each}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctrl chips-ctrl">
|
|
|
|
|
<span class="ctrl-label">measure</span>
|
|
|
|
|
<div class="chips">{#each MEASURES as m}<button class="chip" data-on={measure === m} onclick={() => (measure = m)}>{m}</button>{/each}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ctrl chips-ctrl">
|
|
|
|
|
<span class="ctrl-label">wrap</span>
|
|
|
|
|
<div class="chips">{#each WRAPS as w}<button class="chip" data-on={wrap === w} onclick={() => (wrap = w)}>{w}</button>{/each}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="ctrl switch-ctrl">
|
|
|
|
|
<span class="ctrl-label">tabular</span>
|
|
|
|
|
<input type="checkbox" bind:checked={numeric} />
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="axis-stage">
|
|
|
|
|
<p data-text data-style="prose" class="axis-sample" style={axisStyle}>
|
|
|
|
|
La tipografía no es lo que ves, es lo que ocurre cuando lees: 1.250 palabras, 48px de
|
|
|
|
|
salto, 66ch de medida. El tracking se afina, el interlineado respira, la columna se
|
|
|
|
|
contiene — y el texto desaparece para dejar pasar el sentido.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.root {
|
|
|
|
|
min-block-size: 100vh;
|
|
|
|
|
background: 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) 8rem;
|
|
|
|
|
--rule: color-mix(in srgb, var(--color-border-default) 100%, transparent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.masthead {
|
|
|
|
|
max-inline-size: 60rem;
|
|
|
|
|
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);
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
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-weight: 600;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
letter-spacing: -0.03em;
|
|
|
|
|
margin: 0 0 1.25rem;
|
|
|
|
|
text-wrap: balance;
|
|
|
|
|
}
|
|
|
|
|
.mast-lede {
|
|
|
|
|
max-inline-size: 42ch;
|
|
|
|
|
font-size: var(--font-size-lg);
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: var(--color-content-secondary);
|
|
|
|
|
margin: 0;
|
|
|
|
|
text-wrap: pretty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section { margin-block-end: clamp(3.5rem, 9vw, 7rem); }
|
|
|
|
|
.sec-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 0.75rem 2rem;
|
|
|
|
|
padding-block-end: 1.25rem;
|
|
|
|
|
margin-block-end: 2rem;
|
|
|
|
|
border-block-end: 1px solid var(--rule);
|
|
|
|
|
}
|
|
|
|
|
.sec-title {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
letter-spacing: 0.16em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
color: var(--color-content-primary);
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
.sec-note {
|
|
|
|
|
margin: 0;
|
|
|
|
|
max-inline-size: 56ch;
|
|
|
|
|
font-size: var(--font-size-sm);
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: var(--color-content-muted);
|
|
|
|
|
}
|
|
|
|
|
.sec-note code, .rung-val i, .family-note {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
}
|
|
|
|
|
.sec-note code {
|
|
|
|
|
font-size: 0.92em;
|
|
|
|
|
color: var(--color-content-secondary);
|
|
|
|
|
background: var(--color-surface-muted);
|
|
|
|
|
padding: 0.1em 0.4em;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Controls */
|
|
|
|
|
.controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 1.25rem 2.5rem;
|
|
|
|
|
margin-block-end: 2.5rem;
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
background: var(--color-surface-raised);
|
|
|
|
|
border: 1px solid var(--rule);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
}
|
|
|
|
|
.ctrl { display: flex; flex-direction: column; gap: 0.6rem; min-inline-size: 13rem; }
|
|
|
|
|
.ctrl-label {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
color: var(--color-content-muted);
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
}
|
|
|
|
|
.ctrl-label em { font-style: normal; color: var(--color-content-primary); text-transform: none; letter-spacing: 0; }
|
|
|
|
|
input[type='range'] { width: 100%; accent-color: var(--color-primary-solid); cursor: pointer; }
|
|
|
|
|
.switch-ctrl { flex-direction: row; align-items: center; gap: 0.75rem; min-inline-size: 0; }
|
|
|
|
|
.switch-ctrl input { accent-color: var(--color-primary-solid); inline-size: 1.1rem; block-size: 1.1rem; }
|
|
|
|
|
.chips-ctrl { min-inline-size: 0; }
|
|
|
|
|
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
|
|
|
.chip {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
color: var(--color-content-secondary);
|
|
|
|
|
background: var(--color-surface-default);
|
|
|
|
|
border: 1px solid var(--rule);
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
padding: 0.3rem 0.6rem;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.chip[data-on='true'] {
|
|
|
|
|
color: var(--color-primary-on-solid, #fff);
|
|
|
|
|
background: var(--color-primary-solid);
|
|
|
|
|
border-color: var(--color-primary-solid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ladder */
|
|
|
|
|
.ladder { display: flex; flex-direction: column; }
|
|
|
|
|
.rung {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 3.5rem 1fr auto;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 1.5rem;
|
|
|
|
|
padding-block: 0.6rem;
|
|
|
|
|
border-block-end: 1px solid color-mix(in srgb, var(--rule) 55%, transparent);
|
|
|
|
|
}
|
|
|
|
|
.rung-key {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
color: var(--color-content-muted);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
}
|
|
|
|
|
.rung-sample {
|
|
|
|
|
font-family: var(--font-family-display);
|
|
|
|
|
line-height: 1.05;
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
color: var(--color-content-primary);
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
.rung-val {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
color: var(--color-content-secondary);
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.rung-val i { color: var(--color-content-muted); font-style: normal; }
|
|
|
|
|
|
|
|
|
|
/* Named styles */
|
|
|
|
|
.styles { display: flex; flex-direction: column; gap: 1.75rem; }
|
|
|
|
|
.style-row { display: grid; grid-template-columns: 4rem 1fr; gap: 1.5rem; align-items: baseline; }
|
|
|
|
|
.style-tag {
|
|
|
|
|
font-family: var(--font-family-mono);
|
|
|
|
|
font-size: var(--font-size-xs);
|
|
|
|
|
color: var(--color-content-muted);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
padding-block-start: 0.4em;
|
|
|
|
|
}
|
|
|
|
|
.style-sample { margin: 0; color: var(--color-content-primary); text-wrap: balance; }
|
|
|
|
|
|
|
|
|
|
/* Families */
|
|
|
|
|
.families { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 14px; overflow: hidden; }
|
|
|
|
|
.family { background: var(--color-surface-default); padding: 1.75rem; display: flex; flex-direction: column; gap: 1.1rem; }
|
|
|
|
|
.family-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
|
|
|
|
|
.family-label { font-family: var(--font-family-mono); font-size: var(--font-size-xs); letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-content-primary); }
|
|
|
|
|
.family-note { font-size: var(--font-size-xs); color: var(--color-content-muted); }
|
|
|
|
|
.family-sample { display: flex; flex-direction: column; gap: 0.5rem; color: var(--color-content-primary); }
|
|
|
|
|
.family-aa { font-size: var(--font-size-xxl); line-height: 1; letter-spacing: -0.02em; }
|
|
|
|
|
.family-glyphs { font-size: var(--font-size-sm); color: var(--color-content-secondary); }
|
|
|
|
|
|
|
|
|
|
/* Axis stage */
|
|
|
|
|
.axis-stage {
|
|
|
|
|
padding: clamp(1.5rem, 4vw, 3.5rem);
|
|
|
|
|
background: var(--color-surface-raised);
|
|
|
|
|
border: 1px solid var(--rule);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
}
|
|
|
|
|
.axis-sample { margin: 0; color: var(--color-content-primary); font-size: var(--font-size-lg); }
|
|
|
|
|
</style>
|