docs(color): sync arts/color README + COLOR_ENGINE_RFC with implemented API

Found stale color docs while verifying currency:

- arts/color/README.md: said "Status — Phase 0 ... nothing consumes it yet" (false —
  consumed at build via render-css + runtime via applyColorScheme) and used
  `ActiveEidos.setCssVariables` as the theme-builder mechanism (the real API is
  applyColorScheme; setCssVariables is for contract knobs). Updated status, added
  deriveScheme/temper/harmonize to the API table, documented temper as the canonical
  intent-cohesion tool (vs harmonize for brand accents), fixed the builder pipeline to
  buildScheme + applyColorScheme, and corrected the wide-gamut note (strategy A is
  live + default-on, not "deferred").

- COLOR_ENGINE_RFC.md: 4 remaining `setCssVariables` references for the runtime
  white-label builder -> applyColorScheme (only §6.2 was fixed earlier). Marked
  Fase 4-bis (runtime generation) as IMPLEMENTED.

COLOR_MODEL_RFC.md verified current (RESUELTO; loss->plum correct; the anchor-hex
examples are the documented-discarded proposal = history). THEMING/audit/README were
already synced in their own commits.

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

@ -2,13 +2,14 @@
Isomorphic **color math engine**. Pure, deterministic, DOM-free — the same Isomorphic **color math engine**. Pure, deterministic, DOM-free — the same
functions run at **build** (eidos `render-css` emits static CSS) and at **runtime** functions run at **build** (eidos `render-css` emits static CSS) and at **runtime**
(`ActiveEidos.setCssVariables` for live / white-label theming). Because every (`ActiveEidos.applyColorScheme` for live / white-label theming). Because every
decision is computed in JS **before** a value is written, **introspection (APCA decision is computed in JS **before** a value is written, **introspection (APCA
on-solid pick) and alpha fidelity are preserved in every mode**. on-solid pick) and alpha fidelity are preserved in every mode**.
> **Status — Phase 0** of [`COLOR_ENGINE_RFC.md`](../../uix/eidos/COLOR_ENGINE_RFC.md). > **Status — consumed** ([`COLOR_ENGINE_RFC.md`](../../uix/eidos/COLOR_ENGINE_RFC.md)
> This module + its tests exist; **nothing consumes it yet** (zero behavior change, > Phases 0/1/2/4). eidos's `render-css` consumes it at build (APCA on-solid pick +
> safe to merge alone). The eidos wiring (build + runtime) lands in later phases. > OKLCH-native palette output); `ActiveEidos.applyColorScheme` consumes it at runtime
> (the live theme builder). Wide-gamut output (`oklch()` + hex fallback) is default-on.
## Why an art (and not an eidos lib) ## Why an art (and not an eidos lib)
@ -44,6 +45,9 @@ shifts the hue). The `oklch(...)` string keeps the full gamut for the browser.
| `generateScale(seed, template, { solidStep? })` | seed → 12 OKLCH steps (morph; solid anchored to seed) | | `generateScale(seed, template, { solidStep? })` | seed → 12 OKLCH steps (morph; solid anchored to seed) |
| `pickOnSolid(solid, { onSolid, onSolidContrast }, floor?)` | APCA pick of on-solid text + `passes` | | `pickOnSolid(solid, { onSolid, onSolidContrast }, floor?)` | APCA pick of on-solid text + `passes` |
| `alphaOverBackground(solid, background)` | translucent fill `(rgb, alpha)` that over `background` = solid | | `alphaOverBackground(solid, background)` | translucent fill `(rgb, alpha)` that over `background` = solid |
| `deriveScheme(seed, variant?)` | brand seed → hierarchy role seeds (Material 3 `CorePalette`) |
| `temper(color, reference, amount?)` | match a reference's L+C **keeping hue** (cohere intents) |
| `harmonize(color, toward, amount?)` | rotate hue toward a reference (M3 blend; brand accents) |
## The generator (template morph) ## The generator (template morph)
@ -63,19 +67,28 @@ brand seed → the hierarchy role seeds (`primary` / `secondary` / `tertiary` /
chroma · **neutral** = same hue, near-zero chroma. (Structure is exact; chroma is chroma · **neutral** = same hue, near-zero chroma. (Structure is exact; chroma is
recalibrated HCT→OKLCH. Tone→contrast is NOT ported — APCA decides contrast.) recalibrated HCT→OKLCH. Tone→contrast is NOT ported — APCA decides contrast.)
- **Variants** (`SchemeVariant`): `tonal` (default), `vibrant`, `monochrome`. - **Variants** (`SchemeVariant`): `tonal` (default), `vibrant`, `monochrome`.
- The **6 canonical intents are NOT derived** (an error is always red). - The **6 canonical intents are NOT derived** (an error is always red). To cohere them
`harmonize(color, toward, amount?)` (M3 `blend.harmonize`) nudges a hue toward the with a brand WITHOUT losing meaning, `temper(color, reference, amount?)` matches the
brand for cohesion — opt-in, keeps L + C. brand's **perceptual temperature** (L + C) while **keeping the hue** (red stays red) —
the right tool for intents. `harmonize` (rotates hue) also exists, but for brand
Feed each derived seed to `generateScale`. **Live builder**: seed → `deriveScheme` → ACCENTS, not semantic intents (rotating an intent's hue erodes its meaning).
`generateScale` → `ActiveEidos.setCssVariables` — same code at build or runtime. It
produces only VALUES behind the frozen `--color-{role}-{slot}` contract, so it **Composition + apply**: `buildScheme(seed, opts)` (in `eidos/lib`, pure) composes
touches **no component**. Full spec: `../../uix/eidos/COLOR_ENGINE_RFC.md` §6.2. `deriveScheme` + `generateScale` + APCA + alpha into the `--primitive-{role}-*` token
map; **`ActiveEidos.applyColorScheme(seed, opts)`** writes it as a managed style block
(hex fallback + `oklch()` wide-gamut), follows light/dark, and returns the scheme for
introspection. Same math at build or runtime; only VALUES behind the frozen
`--color-{role}-{slot}` contract, so it touches **no component**. `opts`: `variant`
(tonal/vibrant/monochrome) + `temper` (intent cohesion) + per-role `overrides`. Full
spec: `../../uix/eidos/COLOR_ENGINE_RFC.md` §6.2 + `THEMING.md` §26.
## Notes ## Notes
- **APCA is a WCAG 3 draft**, not legal conformance. `pickOnSolid` decides by APCA - **APCA is a WCAG 3 draft**, not legal conformance. `pickOnSolid` decides by APCA
but `onSolidWcagRatio` is available as a documented cross-check / safety floor. but `onSolidWcagRatio` is available as a documented cross-check / safety floor.
- Display-P3 explicit output (`color(display-p3 …)`) is **deferred** — the default - **Wide-gamut output is live + default-on** (RFC §7 strategy A): the palette emits a
output strategy is `oklch()` + hex fallback, which gets wide-gamut automatically hex fallback + an `oklch()` sibling that wins where supported → wide-gamut on P3 with
and needs no P3 conversion. P3-explicit lands only if strategy B is chosen (RFC §7). no `@media`. Explicit `color(display-p3 …)` (strategy B) stays deferred — only if an
app needs hand-tuned P3 values. NOTE: an sRGB-authored palette renders identically in
`oklch()` (no P3 data to recover); real P3 needs a wide-gamut SOURCE (a vivid seed /
OKLCH-authored theme), which the generator preserves (raw OKLCH, never gamut-clamped).

@ -50,7 +50,8 @@ inverse-alpha) es **pura, sin DOM**, así que corre idéntica en build **y** en
- **build** → temas estáticos (camino `render-css.ts`). - **build** → temas estáticos (camino `render-css.ts`).
- **runtime JS** → white-label / live theming: el usuario elige un hex, el motor - **runtime JS** → white-label / live theming: el usuario elige un hex, el motor
genera la escala + dark + P3 y la escribe vía `setCssVariables(...)`. genera la escala + dark (hex fallback + `oklch()` wide-gamut) y la escribe vía
`ActiveEidos.applyColorScheme(seed)`.
En **ambos** modos la salida son **valores estáticos ya resueltos** (contraste En **ambos** modos la salida son **valores estáticos ya resueltos** (contraste
elegido por APCA, alpha invertido) → **cero sacrificio en cualquier modo**. Lo único elegido por APCA, alpha invertido) → **cero sacrificio en cualquier modo**. Lo único
@ -256,7 +257,7 @@ export function alphaOverBackground(solid: Oklch, bg: Oklch): string // i
| Modo | Quién llama | Qué hace con el resultado | | Modo | Quién llama | Qué hace con el resultado |
|---|---|---| |---|---|---|
| **Build** | `render-css.ts` (temas estáticos del framework + app) | concatena strings CSS (camino actual) | | **Build** | `render-css.ts` (temas estáticos del framework + app) | concatena strings CSS (camino actual) |
| **Runtime JS** | `uix.color.generate(seed)` (white-label / editor de temas) | escribe vía `ActiveEidos.setCssVariables(...)` | | **Runtime JS** | `buildScheme(seed)` (composición pura; white-label / editor de temas) | `ActiveEidos.applyColorScheme(seed)` escribe el bloque scheme (hex + `oklch()`) |
En **ambos** la salida son **valores estáticos resueltos** (el `contrast` ya elegido En **ambos** la salida son **valores estáticos resueltos** (el `contrast` ya elegido
por APCA, el `aN` ya invertido). Por eso **no se sacrifica introspección ni alpha en por APCA, el `aN` ya invertido). Por eso **no se sacrifica introspección ni alpha en
@ -288,8 +289,8 @@ SEEDS de los roles de jerarquía). Es el núcleo de un theme builder:
``` ```
seed de marca → deriveScheme(seed, variant) → { primary, secondary, tertiary, neutral, neutralVariant } seed de marca → deriveScheme(seed, variant) → { primary, secondary, tertiary, neutral, neutralVariant }
→ generateScale(cada seed) → escalas de 12 pasos → generateScale(cada seed) → escalas de 12 pasos (todo dentro de buildScheme)
→ setCssVariables(...) → tema en vivo → applyColorScheme(seed) → tema en vivo (bloque hex + oklch)
``` ```
**La fórmula** es la de Material 3 (`CorePalette` HCT) portada a OKLCH: **La fórmula** es la de Material 3 (`CorePalette` HCT) portada a OKLCH:
@ -527,11 +528,13 @@ la forma de `scales` autoradas cambia; las escalas 12-hex viejas se leen igual.
- `THEMING.md`: §25 gana una subsección «capa física» que apunta aquí; §22 marca P3-1 - `THEMING.md`: §25 gana una subsección «capa física» que apunta aquí; §22 marca P3-1
resuelto. Marcar `COLOR_MODEL_RFC §5` fase 3 como resuelta por este RFC. resuelto. Marcar `COLOR_MODEL_RFC §5` fase 3 como resuelta por este RFC.
### Fase 4-bis — Generación en runtime (white-label / live theming), pilar, no azúcar ### Fase 4-bis — Generación en runtime (white-label / live theming) — ✅ IMPLEMENTADO
- `uix.color.generate(seed)` corre el **mismo generador isomórfico** (§6.1) en JS y - `ActiveEidos.applyColorScheme(seed, opts)` corre el **mismo generador isomórfico**
escribe la escala + dark + P3 + **slots de rol resueltos** vía (§6.1) en JS (vía el helper puro `buildScheme`) y escribe la escala + **slots de rol
`ActiveEidos.setCssVariables(...)`. Conserva pick APCA + alpha (se calculan antes de resueltos** (`--color-{role}-contrast`) como un **bloque de estilo gestionado** (hex
escribir). Cubre el caso white-label del documento de bloat **sin** CSS-relative. fallback + `oklch()` wide-gamut). Conserva pick APCA + alpha (se calculan antes de
escribir) y **sigue light/dark** (re-deriva al cambiar de modo). Cubre el white-label
**sin** CSS-relative. Ver §6.2 + THEMING.md §26.
- **Verifica**: un hex de marca elegido en vivo produce escala AA + dark + P3 - **Verifica**: un hex de marca elegido en vivo produce escala AA + dark + P3
coherentes; cambiar de marca reescribe solo las vars del rol afectado. coherentes; cambiar de marca reescribe solo las vars del rol afectado.

Loading…
Cancel
Save

Powered by TurnKey Linux.