From 4a1936b5f6504ce0f3b2a344291c15f2d5b3122d Mon Sep 17 00:00:00 2001
From: dev
Date: Thu, 4 Jun 2026 20:04:50 +0200
Subject: [PATCH] feat(temas): live theme builder in /temas/color (deriveScheme
end-to-end)
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)
---
web/routes/temas/color/+page.svelte | 141 +++++++++++++++++++++++++++-
1 file changed, 140 insertions(+), 1 deletion(-)
diff --git a/web/routes/temas/color/+page.svelte b/web/routes/temas/color/+page.svelte
index fbb806ea1..419895652 100644
--- a/web/routes/temas/color/+page.svelte
+++ b/web/routes/temas/color/+page.svelte
@@ -32,7 +32,20 @@
type ColorRole
} from '$uix/eidos/lib/config-types'
import { INTENTS } from '$uix/intent'
- import { apcaLc, oklchToGammaRgb, parseColor, wcagContrastRatio } from '$color'
+ import {
+ apcaLc,
+ deriveScheme,
+ generateScale,
+ oklchToGammaRgb,
+ oklchToHex,
+ parseColor,
+ pickNearestTemplate,
+ pickOnSolid,
+ scaleToTemplate,
+ wcagContrastRatio,
+ type ScaleSteps,
+ type SchemeVariant
+ } from '$color'
let theme = $state<'light' | 'dark'>('light')
@@ -77,6 +90,41 @@
const SURFACES = ['default', 'raised', 'overlay', 'muted'] as const
const CONTENTS = ['primary', 'secondary', 'muted', 'disabled'] as const
const BORDERS = ['subtle', 'default', 'strong'] as const
+
+ // ── Theme builder (deriveScheme + generateScale, live) ────────────────────
+ // Curve donors for the generator: the base light scales as OKLCH templates.
+ const TEMPLATES: Record = Object.fromEntries(
+ Object.entries(THEME_BASE_LIGHT_COLOR_SCALES).map(([name, scale]) => [
+ name,
+ scaleToTemplate(STEPS.map((s) => scale[s]))
+ ])
+ )
+ const BUILDER_VARIANTS = ['tonal', 'vibrant', 'monochrome'] as const satisfies readonly SchemeVariant[]
+
+ let seedHex = $state('#8e4ec6')
+ let builderVariant = $state('tonal')
+
+ interface BuilderRole {
+ role: string
+ steps: string[]
+ solidHex: string
+ onSolid: string
+ }
+ const builderRoles = $derived.by((): BuilderRole[] => {
+ let seed: ReturnType
+ try {
+ seed = parseColor(seedHex)
+ } catch {
+ return []
+ }
+ const scheme = deriveScheme(seed, builderVariant)
+ return Object.entries(scheme).map(([role, roleSeed]) => {
+ const tpl = TEMPLATES[pickNearestTemplate(roleSeed, TEMPLATES)]
+ const steps = generateScale(roleSeed, tpl).map(oklchToHex)
+ const pick = pickOnSolid(roleSeed, { onSolid: '#ffffff', onSolidContrast: '#1c1917' }, 60)
+ return { role, steps, solidHex: steps[8], onSolid: oklchToHex(pick.color) }
+ })
+ })
@@ -119,6 +167,63 @@
activas, así que cambia con el tema.
+
+
+
+ uix.color · deriveScheme + generateScale
+
Theme builder (1 color → esquema completo · en vivo)
+
+ Elige un color de marca y una variante: el motor deriva los roles de
+ jerarquía (fórmula de Material 3 en OKLCH) y genera cada escala de 12 pasos
+ con su pick on-solid (APCA). Los 6 intents NO se derivan (canon del libro). Es el theme
+ builder de punta a punta — en runtime se aplicaría con setCssVariables.
+