feat(eidos): wide-gamut OKLCH output, default-on (RFC Phase 2, strategy A)

Each palette step is now emitted twice: the hex as a universal fallback, then an
oklch() sibling that wins where supported (Chrome 111+/Safari 15.4+/Firefox 113+).
The token layer is now OKLCH-native and wide-gamut-ready, with NO @media and NO
config flag (it is the default behaviour).

- render-css `appendColorScaleDeclarations`: hex line + `oklch()` sibling per
  `--scale-{name}-{step}`. Only opaque, parseable steps get the sibling; empty/
  non-color values keep just the fallback. `--primitive-*`/`--color-*` are var()
  refs (untouched); alpha scales stay color-mix/rgba.
- generated/base.css regenerated (+744 oklch sibling lines: 31 scales x 12 x 2 modes).

Honest scope: the shipped Radix palette is authored in sRGB hex, so its oklch()
siblings are sRGB-equivalent (verified: --scale-purple-9 -> oklch(0.5556 0.1829
305.86) paints #8e4ec6) -- identical today. The win is the OKLCH-native foundation:
an OKLCH-authored theme or a vivid generated scheme now renders wider on P3 with no
extra work. Making the SHIPPED palette visibly wide-gamut is Phase 3.

Tests: full eidos suite green except 3 pre-existing words-track failures (confirmed
unrelated via baseline). Docs: RFC §7 (status) + THEMING §27 + README ref row.

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

@ -368,7 +368,21 @@ const result = eidos.applyColorScheme('#8e4ec6', {
## 7. Salida wide-gamut (P3 + sRGB)
Dos estrategias; el motor soporta ambas vía opción de config, default = **A**.
**Estado: estrategia A implementada, default-on** (2026-06-04). `render-css` emite por
cada paso de paleta el **hex (fallback universal)** + un hermano **`oklch()`** que gana
donde se soporta (`appendColorScaleDeclarations`). SIN flag de config: es el
comportamiento por defecto. La estrategia B (P3 explícito vía `@media`) NO está
implementada (se añadiría como opción si una app necesita afinar valores P3 a mano).
**Honestidad sobre el efecto visible**: la paleta por defecto (Radix) está autorada en
**hex sRGB**, así que su `oklch()` es **sRGB-equivalente** — no hay datos P3 que
recuperar de un sRGB, se ve idéntico hoy (verificado: `--scale-purple-9` →
`oklch(0.5556 0.1829 305.86)` pinta `#8e4ec6`). El valor es que el token-layer es ahora
**OKLCH-nativo y wide-gamut-ready**: un tema autorado en OKLCH, o un esquema generado
con un seed vívido, renderiza más saturado en P3 **sin trabajo extra**. Hacer la paleta
SHIPPED visiblemente wide-gamut es Fase 3 (autorar/generar la paleta en OKLCH).
Dos estrategias (histórico de diseño):
### A. OKLCH-directo + fallback hex (recomendado · estilo Tailwind v4)

@ -577,6 +577,7 @@ Resumen rápido de lo que cubre, para no duplicar aquí:
| **Correcciones P2: texto on-solid por luminancia + superficies translúcidas** | §24 |
| **Modelo de color: paleta (31 escalas) + roles (alias) + intents (auto-derivados)** | §25 |
| **Theme builder en runtime: `eidos.applyColorScheme(seed)` (motor `uix.color`)** | §26 |
| **Salida wide-gamut OKLCH default-on (hex fallback + `oklch()` sibling)** | §27 |
Lo que sigue en este README son las decisiones operativas de la **capa
visual como módulo** (typography sourcing, picker patterns, API

@ -55,6 +55,7 @@
24. [Correcciones P2 del engine (2026-06-02)](#24-correcciones-p2-del-engine-2026-06-02)
25. [Modelo de color — paleta + roles/intents derivados](#25-modelo-de-color--paleta--rolesintents-derivados-2026-06-02)
26. [Theme builder en runtime — `eidos.applyColorScheme`](#26-theme-builder-en-runtime--eidosapplycolorscheme-2026-06-04)
27. [Salida wide-gamut OKLCH (default-on)](#27-salida-wide-gamut-oklch-default-on-2026-06-04)
---
@ -2179,6 +2180,31 @@ Demo en vivo: `/temas/color` (el builder usa el mismo `buildScheme`). Tests:
---
## 27. Salida wide-gamut OKLCH (default-on) (2026-06-04)
RFC §7 estrategia A, **implementada por defecto**. Cada paso de paleta se emite dos
veces: el **hex como fallback universal** + un hermano **`oklch()`** que gana donde el
navegador lo soporta (Chrome 111+ / Safari 15.4+ / Firefox 113+).
```css
:root {
--scale-purple-9: #8e4ec6; /* fallback sRGB */
--scale-purple-9: oklch(0.5556 0.1829 305.86); /* gana -> gamut del display */
}
```
- **Solo las hojas opacas** `--scale-{name}-{step}` ganan el hermano; las capas
`--primitive-*` / `--color-*` son `var()` (heredan) y las alpha siguen como
`color-mix` / rgba. Valores vacíos / no-color no reciben hermano.
- **sRGB idéntico**: el hex y el `oklch()` derivado de un sRGB pintan el mismo color
(verificado: `--scale-purple-9` → `oklch(...)` pinta `#8e4ec6`). El wide-gamut REAL
aparece cuando el origen excede sRGB (tema OKLCH / esquema generado vívido). La
paleta Radix shipped es sRGB → idéntica hoy; wide-gamut **visible** de la paleta = Fase 3.
- **Default-on, sin flag**: es el comportamiento del framework.
`render-css.ts > appendColorScaleDeclarations`.
---
**Última revisión**: 2026-06-04. Si algo en este doc no coincide con
el código, el código gana — pero abre un issue para que actualicemos
el doc.

File diff suppressed because it is too large Load Diff

@ -51,7 +51,7 @@ import type {
KeyframeStops,
MotionConfig
} from '$motion'
import { apcaLc, oklchToGammaRgb, safeParseColor, wcagContrastRatio } from '$color'
import { apcaLc, oklchToCss, oklchToGammaRgb, safeParseColor, wcagContrastRatio } from '$color'
import { EidosCssVariableError, EidosThemeNotFoundError } from '../errors'
export interface RenderThemeCssOptions {
@ -1601,7 +1601,15 @@ function appendColorScaleDeclarations(
scale: ColorScale
): void {
for (const step of COLOR_SCALE_STEPS) {
declarations.push(cssVar(`scale-${scaleName}-${step}`, scale[step]))
const value = scale[step]
// Wide-gamut output (RFC §7, strategy A): the hex stays as the universal
// fallback, then an `oklch()` sibling overrides it where supported — so the
// color uses the display's gamut (more saturated on P3) automatically, with
// NO `@media`, and renders identically in sRGB. Only opaque, parseable steps
// get the sibling; empty / non-color values keep just the fallback line.
declarations.push(cssVar(`scale-${scaleName}-${step}`, value))
const oklch = safeParseColor(value)
if (oklch) declarations.push(cssVar(`scale-${scaleName}-${step}`, oklchToCss(oklch)))
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.