docs(eidos): TYPOGRAPHY_ENGINE_RFC — plan to reach reference-grade typography

Mirrors the color RFC approach (audit -> compare -> extend additively behind the frozen
token contract, by phases). Covers: the current state + the gap vs Utopia/Tailwind v4/
Material 3/Apple/Carbon; the inclusion model (extend TypographyPrimitiveSet, never
rename tokens); the fluid clamp() formula (rem-based, with --scaling composing on top);
woff2 + variable fonts + anti-CLS metric-override fallbacks; tracking/leading/features/
measure/text-wrap; the <SText> measurement link; canon-vs-theme doctrine; token contract
additions; and a 5-phase plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 0c1f202186
commit b273ecfb69

@ -0,0 +1,161 @@
# RFC — Motor de tipografía reference-grade (eidos)
> **Estado: PROPUESTA (2026-06-05).** Plan para elevar la tipografía de eidos a la par
> o por encima de los frameworks de referencia, replicando el enfoque del color
> (`COLOR_ENGINE_RFC.md`): auditar → comparar → extender de forma ADITIVA detrás del
> contrato de tokens congelado, por fases.
## 1. Estado actual (auditoría)
`TypographyPrimitiveSet` (`lib/primitives/typography.ts` + `config-types.ts`) →
emisión en `render-css.ts` → tokens → componentes `<Text>` / `<Heading>` / `<SText>`.
- **4 familias** (primary Instrument Sans · secondary/display Lora · mono Azeret Mono).
- **8 tamaños** `xxs 10px … xxxl 80px`, cada uno `size` / `lineHeight` / `letterSpacing`
**fijos en px**.
- **4 pesos** (400/500/600/700).
- **13 estilos nombrados** (hero, h1–h6, body, prose, label, caption, code) con size
responsive por breakpoint en algunos (`{ base, md }`).
- **Eje `--scaling`** (zoom global 90–110%) que multiplica los px.
- Tokens: `--font-family-*`, `--font-size-*` (`calc(px * --scaling)`),
`--font-line-height-*`, `--font-letter-spacing-*`, `--font-weight-*`, `--style-{name}-*`.
- **Brazo de medición**: el motor `canvas-text` (`<SText>`) cuenta líneas exactas
replicando el navegador (productionizado — ver `lib/canvas-text/fix-stext.md`).
**Bueno hoy**: estilos semánticos nombrados, sizes responsive, eje de scaling (poco
común), contrato de tokens + theming, medición exacta de líneas.
## 2. Comparación con referencia (el gap)
| Capacidad | Referencia | Eidos hoy |
|---|---|---|
| Tipografía **fluida** (`clamp()` por viewport) | Utopia, Carbon, Tailwind(opt) | ❌ px fijos + breakpoints manuales |
| **Variable fonts** (1 archivo, wght + opsz) | Material 3, Apple | ❌ TTF estáticas por peso |
| **WOFF2 + subsetting + fallback con métricas** (anti-CLS) | Next/Fontaine | ❌ TTF, sin subset, sin `size-adjust` |
| **Optical sizing** (`opsz`) | Apple, Material | ❌ |
| **Tracking por tamaño** (óptico) | Material, Apple, Radix | ❌ todo `0` |
| **`text-wrap: balance`/`pretty`** | Tailwind, Chakra | ❌ |
| **Measure** (ancho de línea ~65ch) | clásico, Tailwind prose | ❌ |
| **`font-feature-settings`** (tabular-nums…) | Tailwind, Radix | ❌ |
| Estilos nombrados · eje de scaling | Chakra/Material · Apple(parcial) | ✅ |
## 3. Modelo de inclusión: extender, no reemplazar
**Principio rector (idéntico a color)**: todo lo nuevo es **ADITIVO** sobre
`TypographyPrimitiveSet`, detrás del **contrato de tokens congelado**. Lo viejo (px
fijos) sigue funcionando; lo nuevo se activa por config. Los nombres de token
(`--font-size-X`, `--style-Y-*`) **no cambian** — solo cambia la *fórmula del valor*
(igual que en color el token `--scale-*` pasó de hex a `oklch()` sin renombrar).
Capas de la implementación, paralelas a color:
| Pieza | Dónde | Análogo en color |
|---|---|---|
| Matemática pura (fluid scale Utopia) | `eidos/lib/type-scale.ts` (puro) | `arts/color` / `build-scheme.ts` |
| Tipo + config | `config-types.ts` (`TypographyPrimitiveSet`) | `EidosConfig` |
| Emisión CSS | `render-css.ts` (`appendTypographyDeclarations`) | `appendColorScaleDeclarations` |
| Fuentes | `themes/fonts.css` (woff2/variable) | `themes/base` (paleta) |
| Componentes | `<Text>` / `<Heading>` (props nuevas) | `<Button>` etc. |
| Runtime opcional | `eidos.applyTypeScale(seed)` | `eidos.applyColorScheme(seed)` |
## 4. Tipografía fluida (Fase 1, el corazón)
**Fórmula (estilo Utopia)** — un tamaño se vuelve un `clamp(min, fluido, max)` en
**rem** (a11y: respeta el zoom del navegador), donde el tramo fluido interpola entre
dos viewports:
```
slope = (maxRem − minRem) / (maxVw − minVw)
interceptRem = minRem − slope · minVw
size = clamp(minRem, interceptRem + slope·100vw, maxRem)
```
- **rem, no px**: mejora la accesibilidad (el zoom de fuente del SO/navegador escala).
El token `--font-size-X` mantiene su nombre; su valor pasa de `calc(px*scaling)` a
`calc(clamp(...rem...) * var(--scaling))` — **transparente para los consumidores**.
- **El eje `--scaling`** (zoom discreto del sistema) compone **encima** del clamp vía
`calc(clamp(...) * var(--scaling))`. Dos ejes ortogonales: fluid (continuo, por
viewport) + scaling (discreto, preferencia).
- **Simplifica los estilos nombrados**: hero/h1/h2 ya no necesitan size responsive
`{ base, md }` manual — el `clamp` cubre el viewport de forma continua. El emisor de
`@media` por breakpoint para size se puede retirar (menos CSS).
- **Tipo**: `TextMetric.size` acepta hoy `string`; se extiende a
`string | FluidSize` donde `FluidSize = { min: string; max: string; minVw?: string; maxVw?: string }`.
`string` (px fijo) sigue válido → backward-compatible.
## 5. Fuentes modernas (Fase 2, mayor golpe perf+calidad)
- **WOFF2 + variable**: migrar `fonts.css` de TTF estáticas por peso a **woff2
variable** (Instrument Sans / Lora / Azeret Mono tienen versión variable con eje
`wght`; Lora además `ital`). Un archivo por familia en vez de 3–10.
- **Anti-CLS**: por cada familia, un `@font-face` de **fallback con métricas
sobreescritas** (`size-adjust`, `ascent-override`, `descent-override`,
`line-gap-override`) que iguala la geometría de la webfont al fallback de sistema →
cero salto de layout al cargar (enfoque Fontaine / `next/font`). Las métricas se
precalculan por fuente.
- **`unicode-range`** subsetting (latin / latin-ext separados) → cargar solo lo usado.
- **`preload`** de la familia crítica (primary regular).
- **Tipo**: `FontFamily` += `axes?: { wght?: [min,max]; opsz?: [min,max] }` +
`sizeAdjust?` / overrides de métricas + `unicodeRanges?`. `source` ya existe.
- **Optical sizing**: `font-optical-sizing: auto` cuando la familia declara `opsz`.
## 6. Tracking · leading · features · measure · wrap (Fase 3)
- **Tracking/leading como escalas** (estilo Tailwind): `TypographyPrimitiveSet` +=
`tracking` + `leading` → `--tracking-{key}` / `--leading-{key}`. Tracking **óptico
por defecto** (negativo en display, ~0 en body, positivo en xs/caption).
- **`font-feature-settings`**: `features?: Record<string,string>` → `--font-feature-{key}`
(presets `tabular`, `oldstyle`, `ligatures`, `smallcaps`). Prop `numeric` en componentes.
- **Measure**: token `--measure-{key}` (`60ch`/`66ch`/`72ch`) para prosa legible.
- **`text-wrap`**: props `wrap: 'balance' | 'pretty' | 'nowrap'` en `<Text>`/`<Heading>`
(keyword CSS, sin token); default `balance` en headings, `pretty` en prose.
## 7. Vínculo con `<SText>` (brazo de medición)
El motor `canvas-text` cuenta líneas exactas replicando el navegador (ya
productionizado). Es la **otra mitad** del sistema: la tipografía *visual* (este RFC) y
la *medición* (SText). Al emitir fluid/tracking, `<SText>` los mide igual (lee
`getComputedStyle` real), así que el conteo sigue siendo fiel sin cambios — el epoch de
`useFontReady` (T1) ya cubre el re-cálculo tras cargar variable fonts.
## 8. Doctrina (paralela a color)
- **Escala + estilos nombrados = canon del eidos** (como roles/variants de color):
valores themeables, pero el *set* de estilos es canon.
- **Fuentes = dato del tema** (como la paleta): el tema trae las suyas.
- **Fluid / tracking / features = capacidades del motor**, default-on con fallback
(como el wide-gamut OKLCH): sRGB-idéntico donde no hay soporte.
- **Theme = retintar/retipar lo perceptualmente fijo**: cambia QUÉ fuente es `primary`,
no QUÉ significa `h1`.
## 9. Contrato de tokens (añadidos)
Congelados, bare-prefixed (§6 THEMING). Nuevos:
`--tracking-{key}`, `--leading-{key}`, `--font-feature-{key}`, `--measure-{key}`.
Reformulados (mismo nombre, valor fluido): `--font-size-{key}`.
Sin cambios de nombre → cero rotura de componentes.
## 10. Fases
1. **Motor fluido** — `type-scale.ts` (puro) + `FluidSize` en el tipo + `clamp()` en
`appendTypographyDeclarations` (rem, scaling compone); retirar el `@media` de size
en estilos nombrados. Tests del generador. Demo.
2. **Fuentes** — woff2 variable + fallback anti-CLS + `unicode-range` + preload;
`FontFamily.axes`.
3. **Tracking/leading/features/measure/wrap** — escalas + props de componente.
4. **Escala canónica** — racionalizar ratios (documentado) sin romper estilos.
5. **Docs + demo** — THEMING §typography + demo `/temas/tipografia` (estilo
`/temas/color`) + `applyTypeScale` runtime opcional.
## Verificación por fase
- `npm run check` 0 errores; `render-css` snapshot regenerado (`generated/base.css`).
- Backward-compat: un config con sizes en px (string) sigue emitiendo igual.
- Navegador: el fluid escala con el viewport; `<SText>` cuenta líneas correctas; sin CLS
al cargar las variable fonts.
## Fuera de alcance
- Justificación / guionado / balanceo de líneas propio (el navegador + `text-wrap` lo
hacen; `<SText>` solo mide).
- Subsetting dinámico en runtime (los `unicode-range` estáticos bastan).
Loading…
Cancel
Save

Powered by TurnKey Linux.