|
|
|
@ -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).
|