The theme builder now carries REAL wide-gamut, not just sRGB reformatted. generateScale
keeps raw OKLCH (no clamp), so a seed whose chroma exceeds sRGB renders more saturated
on P3 than its hex fallback.
- build-scheme.ts: result gains `wideGamut` (oklch() per opaque step) + `roles[].stepsOklch`;
`variables` stays hex (fallback + introspection). New `schemeDeclarations(result, {fallback})`
flattens to CSS lines — dual hex+oklch stack (default) or oklch-only (fallback:false,
for inline style where the CSSOM keeps one value per prop).
- ActiveEidos.#renderSchemeCss: emits the dual stack via schemeDeclarations → the applied
scheme block is wide-gamut on P3, sRGB-safe everywhere.
- index: export schemeDeclarations + SchemeDeclarationsOptions.
- temas/color demo: new "vivacidad P3" slider pushes the seed chroma past sRGB +
a "fuera de sRGB -> P3" badge (isInSrgbGamut). themeOverride now applies oklch
(wide-gamut). Verified: vivacity x1.70 -> primary-9 chroma 0.18 -> 0.31, badge on.
- tests: wide-gamut chroma retention (stepsOklch > hex fallback) + schemeDeclarations
dual/single; active-eidos scheme block asserts oklch(). 31/31 green.
- docs: THEMING §26/§27 + RFC §6.2.
Honest scope unchanged: the AUTHORED Radix palette stays exact sRGB (no regression).
Wide-gamut lives in the generator path (vivid seeds / OKLCH-authored themes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime.
Packages the demo-only builder into a first-class, tested API.
- build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine
(deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha)
into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map.
seed -> { variables, roles }. No DOM. 6 tests.
- ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor
scales + background from the active theme, writes a managed `uix-eidos-scheme`
style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode
change (follows light/dark). Returns BuildSchemeResult for introspection. opts:
variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) +
per-role overrides + selector. 4 tests (return value, intents, DOM block
ordering + clear, mode re-derivation).
- index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types.
- temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated
emitRole/rgbaStr; identical output verified in-browser).
- generated/base.css: regenerated for the loss->plum role fix (binding layer
--primitive-loss-* now points at --scale-plum-*; keeps the contract test green).
- docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row.
Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral
chrome downstream; the 31-scale palette stays put. Math in $color, composition in
eidos/lib (pure), DOM application in ActiveEidos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two quick correctness wins for the color system:
- themes/base.ts: `loss` was `purple` -- identical to primary=purple. Map to
`plum` (canonical loss scale): a graver, more magenta violet. This closes
the last role collision in the base theme (after tertiary -> indigo).
- temas/grafito: the "override por componente" section bound raw scale names
(teal/amber/plum...) to Button.color, which only accepts the hierarchy
override (primary|secondary|neutral). That was a type error AND visually
inert (no [data-button][data-color="teal"] rule exists). Split into the two
REAL color axes a component exposes: `color` (hierarchy) + `intent`
(evaluative palette). Closes the last pre-existing svelte-check error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous split was by the text pick (white/dark) - wrong. Group the on-solid
chips by the SAME taxonomy as the Roles section: Jerarquia (primary/secondary/
tertiary) and Intents (neutral/affirm/fulfill/risk/threat/loss), reusing GROUPS.
Removed the now-unused onSolidGroups derived.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Revert ssr:false (it sidestepped the problem, did not fix it). Set the color
inputs imperatively via a `colorValue` action instead of reactive value=/bind:value.
With no Svelte-managed value, hydration no longer assigns "" (the warning) -- and
SSR stays ON. Verified: SSR HTML emits the 6 color inputs with NO value attr; the
action populates them client-side with valid hex.
- Split the "Texto on-solid" section into 2 groups by the APCA pick: "Texto blanco"
(dark solids) and "Texto oscuro" (light solids), computed from the live builder
solids (effectiveSolids) so the groups match the rendered chips. Default: white(8)
/ dark(1 = risk).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnosis: the SSR HTML already emits valid hex on all 6 <input type="color">
(verified), and the warning stack pointed to SvelteKit hydration ("await in
start"). Svelte momentarily sets a color input's value to "" while hydrating
before applying the real hex -> "specified value '' does not conform" x6. The page
is a client-only interactive tool (no SEO/SSR need), so ssr:false removes
hydration entirely -> no warning. Belt-and-suspenders with the existing value
guards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The seed <input type="color"> used bind:value; switch to value + guarded oninput
so it can never receive "" (the recurring "specified value '' does not conform"
warning, fired at hydration). Both color inputs are now guarded.
- The Roles "contrast" slot IS the on-solid text color: white for dark solids
(most intents), dark only for light ones (risk). White swatches looked invisible
on the light card; add a faint inset ring to .slots .sw so they read as white,
not "no color". Behavior unchanged - only risk needs dark text (correct, APCA).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 31-scale library was a flat list, so the 6 tinted grays (gray/mauve/slate/
sage/olive/sand) looked like near-duplicates. Group it like Radix: Grises (tinted
neutrals) - Colores - Brillantes (light solids) - Metales, with a fallback "Otros"
bucket so no scale is ever dropped. Intro note explains the grays cluster because
they are near-neutral (chroma ~0.01) with a subtle per-accent hue tint. Verified
in-browser: 31 scales, none dropped/duped, 4 group labels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-role override <input type="color"> took value={r.solidHex} directly; an
expression-driven color input warns ("specified value '' does not conform") if it
ever receives an empty string. r.solidHex is always a valid hex in practice, but
guard it (`|| '#000000'`) so the input can never receive '' — belt-and-suspenders
for a warning seen during live HMR editing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Gemini's sharp note: rotating an intent's HUE toward the brand (harmonize)
erodes its meaning — a red stops reading as "error". What coheres a palette is
sharing the chroma + lightness PROFILE, not the hue. New temper(color, reference,
amount) keeps the hue and lerps L+C toward the reference. The demo's intent
cohesion switches harmonize -> temper, and the slider MOVES to the "Roles
canonicos" section (next to the intents, dynamic). Verified in-browser: threat hue
stays 358 (red) at 0% and 40% temper, only chroma/lightness shift; affirm stays
teal. harmonize stays in the engine for brand accents. RFC §6.2 updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Canonical intents (red/green/...) can clash next to a brand color. Replace the
harmonize on/off toggle with a slider (0 = pure canonical -> 0.35 strong), default
a SUBTLE 12%: intents lean toward the brand enough to feel cohesive but stay
recognizable (red is still red). Verified: green seed -> threat #e35013 at 12%
(warmer red), pure #e5484d at 0. Also clarified that "La paleta" is the FIXED
library (does not derive from the seed; the seed derives the roles) to resolve the
recurring confusion. RFC §6.2 notes the subtle-default guidance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deriveScheme gives DEFAULTS, not a cage: each role row gets a color input that
PINS that role to the designer's exact color, while the rest keep deriving from
the seed. "auto" un-pins; changing the seed re-derives only the unpinned roles.
Mirrors M3 (custom colors per role) + Radix (pick accent/gray) + the hand-authored
path (grafito maps every role). Verified in-browser: pin secondary=blue +
tertiary=gold while primary/neutral stay derived; auto reverts to derived.
Documented in COLOR_ENGINE_RFC §6.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The seed now re-themes the ENTIRE page, not just the preview: it overrides
--primitive-{role}-* (+ contrast + surface alpha) on .root, reproyecting every
--color-{role}-* and the neutral-driven surface/content/border chrome. Mode-aware
templates (light/dark). New "harmonize intents" toggle nudges the canonical intents
toward the seed's temperature (M3 blend.harmonize); off by default so intents stay
canon. The 31-scale library stays fixed. Verified in-browser: green seed -> canonical
primary / on-solid / topbar all go green; threat stays red until harmonize.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed color + variant (tonal/vibrant/monochrome) -> deriveScheme -> generateScale
-> live preview of the 5 hierarchy role scales (12 steps each, step 9 ringed) +
their APCA on-solid chips. The full uix.color builder pipeline, reactive and
pure. Verified in-browser: blue seed -> tertiary purple (+60deg), monochrome
collapses to one gray, primary = seed verbatim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Palette (31 scales x 12 steps), the 9 canonical roles with their slots, the
APCA on-solid pick live via uix.color, translucent role surfaces, and the
neutral chrome. Rendered through live CSS custom properties with a light/dark
toggle. Verified in-browser: swatches resolve, risk flips to dark text, modes
invert. Not linked from the /temas index (it is a system demo, not a brand
theme — like /temas/animations).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relocate the motion runtime out of eidos into a runtime art (src/arts/motion,
$motion), exposed as uix.motion and consumed by BOTH soma (Presence.motion ->
motion.run) and eidos (delegates + registers presets) -- dissolving the
soma->eidos coupling. Remove DialogProps.runMotion / eidos.motionRunner; the
bridge is now EngineMotion.run (reads data-animation-style). Delete the 4
relocated dead files (lib/motion/{types,runtime,runner,presets/js}.ts); the
preset DATA (presets/css.ts) stays in eidos. Regenerate generated/base.css.
F6 - token rigor (Carbon): tokenize the raw firma durations (slower/deliberate/
emphatic/sustained holds, escalating by announce intent severity), add
--motion-distance-xl (30px shared-axis), --motion-scale-through (0.92), the
emphasized easing, and productive/expressive sets ([data-motion-set=expressive]).
F7 - extensibility + typegen: app-extensible, type-safe preset-name registry
(EidosMotionPresets, mirroring SemaChannelSignatures); MotionPresetName =
keyof EidosMotionPresets | none | (string & {}).
Also sweeps other in-progress working-tree edits (web/routes/temas/grafito).
Verify: npm run check -> 1 pre-existing error (grafito), 0 new; motion 22/22,
active-uix 25/25, Presence 2/2, Dialog 3/3. Pre-existing words-track failures
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename theme untitled-ui -> Grafito: route /temas/grafito, theme ids
grafito-light/dark, grafitoConfig, brand/hero/footer/index.
- Palette: Grafito now carries the full 32-scale library (spreads the
framework's 31 Radix scales + brand carbon, overriding the brand-tuned
gray/violet/blue + intents). Each directly usable as --scale-{name}-{step}.
- Roles: hierarchy explicit (primary->carbon, secondary->violet,
tertiary->blue); the 6 intents auto-derive from the palette by convention
(step 9). THEME_SCALES derived from palette keys, labelled by COLOR NAME.
- Demo Color section -> 3 groups (Paleta / Roles jerarquia / Intents
auto-derivados) + scaling control in the topbar.
Verified in browser: 32 distinct scales emitted, intents auto-derive
(affirm=teal, risk=amber, loss=plum), no validation error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>