Theming: add `scaling` zoom axis (Radix parity), separate from density
Introduce a global zoom axis independent of density, in parity with Radix
Themes' `scaling` (90/95/100/105/110%). Scaling zooms px metrics INCLUDING
typography (font-size, icon-size, space, control-height); density only moves
layout rhythm + control height and leaves text fixed. The two axes compose
multiplicatively.
- config-types: SCALING_KEYS / ScalingKey / DEFAULT_SCALING; DensityPrimitiveSet
drops the dead `scale` + `contentScale` (kept spaceScale, controlScale).
- primitives/static: STATIC_SCALING (0.9..1.1).
- render-css: appendScaledMetricDeclarations wraps metrics in
calc(<raw>[ * var(--density-x-scale)] * var(--scaling)); appendScalingDeclarations
emits --scaling-{key} + --scaling default; renderScalingBlocks emits
[data-scaling] blocks. line-height/radius/border/shadow excluded.
- config + contract: prune the removed density scalars.
- active-eidos: `scaling` / `scalingSource` options, getScaling() on the
preference source, data-scaling projection + dispose cleanup.
- docs: THEMING.md section 23 + 20.1 reconcile; README density/scaling; SCALING_RFC.md.
Verified: npm run check (0 new errors), vitest eidos (0 new regressions),
browser cascade at 90/100/110 scales font/space/control x0.9/x1.1 and leaves
radius/border fixed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
|
|
|
|
# RFC — Eje `scaling` (zoom global), separado de la densidad
|
|
|
|
|
|
|
|
|
|
|
|
> **Estado: ✅ IMPLEMENTADO.** (diseño 2026-06-01 · implementado 2026-06-02)
|
|
|
|
|
|
> Surge de P1-2: la tipografía-por-densidad (`content-scale`) estaba mal planteada.
|
|
|
|
|
|
> Densidad y zoom son cosas distintas; este RFC introduce el zoom como su propio eje.
|
|
|
|
|
|
>
|
|
|
|
|
|
> **Código**: `lib/config-types.ts` (`SCALING_KEYS` / `ScalingKey` / `DEFAULT_SCALING`),
|
|
|
|
|
|
> `lib/primitives/static.ts` (`STATIC_SCALING`), `lib/render-css.ts`
|
|
|
|
|
|
> (`appendScalingDeclarations` / `appendScaledMetricDeclarations` / `renderScalingBlocks`),
|
|
|
|
|
|
> `active-eidos.svelte.ts` (`scaling` / `scalingSource` / `getScaling()` / `data-scaling`).
|
|
|
|
|
|
> Doc consolidada en THEMING.md §23. `--density-scale` y `--density-content-scale`
|
|
|
|
|
|
> fueron eliminados en este sprint.
|
|
|
|
|
|
|
|
|
|
|
|
## 0. Motivación
|
|
|
|
|
|
|
|
|
|
|
|
`scaling` en Radix Themes (90/95/100/105/110%) es un **zoom global** que escala
|
|
|
|
|
|
espacio, tipografía, alturas y line-box con UN factor — accesibilidad/zoom, no
|
|
|
|
|
|
densidad. Eidos tenía un `--density-content-scale` (±4%) muerto que intentaba
|
|
|
|
|
|
escalar tipografía bajo el eje de densidad, lo cual **confunde densidad con zoom**:
|
|
|
|
|
|
|
|
|
|
|
|
- **Densidad** (`compact` / `comfortable` / `spacious`): aprieta layout (espacio +
|
|
|
|
|
|
altura de control), **texto estable** (legibilidad — Material/Carbon). Ya hecho.
|
|
|
|
|
|
- **Scaling** (este RFC): zoom — escala las métricas px **incluida la tipografía**.
|
|
|
|
|
|
|
|
|
|
|
|
Los dos ejes son **ortogonales** y **componen** (puedes tener `compact` + `110%`).
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Decisiones
|
|
|
|
|
|
|
|
|
|
|
|
- **Discreto**, paridad Radix: `90 / 95 / 100 / 105 / 110` (%). Default `100`.
|
|
|
|
|
|
Se proyecta como `[data-scaling='90']` (bloques, cacheable) + `--scaling`.
|
|
|
|
|
|
- **Escala** (px): `font-size`, `space`, `control-height`, `icon-size`.
|
|
|
|
|
|
- **NO escala**:
|
|
|
|
|
|
- `line-height` → es **ratio unitless** (1.45…); ya escala vía el font-size
|
|
|
|
|
|
escalado. Multiplicarlo lo **duplicaría** (bug). Excluir.
|
|
|
|
|
|
- `border-width` (1px nítido), `radius` (independiente — como en Radix),
|
|
|
|
|
|
`shadow`, `z-index`, `opacity`, `motion`. Excluir.
|
|
|
|
|
|
- **Composición con densidad** (multiplicativa) en las 2 métricas compartidas:
|
|
|
|
|
|
`--space-N: calc(base × var(--density-space-scale) × var(--scaling))`,
|
|
|
|
|
|
`--control-height-N: calc(base × var(--density-control-scale) × var(--scaling))`.
|
|
|
|
|
|
- **Poda** `--density-scale` (maestro) + `--density-content-scale`: eran el intento
|
|
|
|
|
|
a-medias de lo que este eje hace bien. Reconcilia §20.1 (densidad = texto estable;
|
|
|
|
|
|
scaling = zoom incluye texto).
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Diseño
|
|
|
|
|
|
|
|
|
|
|
|
**Primitiva** (`lib/primitives/static.ts`):
|
|
|
|
|
|
```ts
|
|
|
|
|
|
export const STATIC_SCALING = { '90': 0.9, '95': 0.95, '100': 1, '105': 1.05, '110': 1.1 }
|
|
|
|
|
|
```
|
|
|
|
|
|
Default activo: `100`.
|
|
|
|
|
|
|
|
|
|
|
|
**Tipos** (`config-types.ts`): `SCALING_KEYS = ['90','95','100','105','110']`,
|
|
|
|
|
|
`ScalingKey`. La primitiva de densidad pierde `scale` + `contentScale`.
|
|
|
|
|
|
|
|
|
|
|
|
**Emisión** (`render-css.ts`):
|
|
|
|
|
|
- `:root` declara `--scaling-90: 0.9 … --scaling-110: 1.1` (constantes) y
|
|
|
|
|
|
`--scaling: var(--scaling-100)` (activo).
|
|
|
|
|
|
- Bloques `[data-scaling='90'] { --scaling: var(--scaling-90); }` … (los ≠100).
|
|
|
|
|
|
- `font-size` + `icon-size` pasan de verbatim a `calc(value × var(--scaling))`.
|
|
|
|
|
|
- `space` + `control-height` añaden `× var(--scaling)` al `calc` que ya tienen.
|
|
|
|
|
|
- `line-height`, `radius`, `border`, `shadow`, etc.: **sin cambios**.
|
|
|
|
|
|
|
|
|
|
|
|
**Runtime** (`active-eidos.svelte.ts`): nueva `scalingSource` (get/onChange,
|
|
|
|
|
|
espejo de `densitySource`); `apply()` escribe `data-scaling` en el host (junto a
|
|
|
|
|
|
`data-mode` / `data-density`); `dispose()` lo limpia. `EIDOS_SCALING_ATTR`.
|
|
|
|
|
|
|
|
|
|
|
|
**Contrato** (`contract.ts`): emite `--scaling` + las constantes como knobs.
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Conflictos (resueltos)
|
|
|
|
|
|
|
|
|
|
|
|
| Punto | Resolución |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Densidad × scaling en space/control-height | Composición multiplicativa en el `calc`. Ortogonales en intención. |
|
|
|
|
|
|
| line-height (ratio) | NO escalar — escala vía font-size. |
|
|
|
|
|
|
| radius/border/shadow/z/opacity/motion | NO escalar (paridad Radix + corrección). |
|
|
|
|
|
|
| `--density-scale` + `content-scale` muertos | Podados — superados por este eje. |
|
|
|
|
|
|
| `size` prop | Ortogonal — `size` elige tier, scaling multiplica el valor del tier. Compone. |
|
|
|
|
|
|
| Zoom de navegador | Compone multiplicativamente (ambos px). Es app-level, distinto. |
|
|
|
|
|
|
| §20.1 (tipografía no escala con densidad) | Sigue cierto — densidad no la escala; **scaling sí**. Dos ejes. |
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Plan de implementación
|
|
|
|
|
|
|
|
|
|
|
|
1. `STATIC_SCALING` + quitar `scale`/`contentScale` de `STATIC_DENSITY`.
|
|
|
|
|
|
2. Tipos (`SCALING_KEYS`/`ScalingKey`; ajustar `DensityPrimitiveSet`).
|
|
|
|
|
|
3. `render-css`: emisión scaling + bloques + `× var(--scaling)` en las 4 familias;
|
|
|
|
|
|
podar emisión de density scale/content-scale.
|
|
|
|
|
|
4. `ActiveEidos`: `scalingSource` + `data-scaling` + cleanup.
|
|
|
|
|
|
5. `contract`: tokens scaling; quitar density scale/content-scale.
|
|
|
|
|
|
6. Regenerar `generated/base.css`.
|
|
|
|
|
|
7. Tests: actualizar forma de `--space-4`/`--control-height` (× scaling); test de
|
|
|
|
|
|
font-size escalado; bloque `[data-scaling]`; test de `scalingSource`.
|
|
|
|
|
|
8. Verificar (`check` + `vitest src/uix/eidos` + navegador) + doc en THEMING.md.
|