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.
+
+
+
+
+ semilla
+
+ {seedHex}
+
+
+ {#each BUILDER_VARIANTS as v}
+ (builderVariant = v)}
+ >
+ {v}
+
+ {/each}
+
+
+
+ {#each builderRoles as r}
+
+
{r.role}
+
+ {#each r.steps as hex, i}
+
+ {/each}
+
+
+ {/each}
+
+
+ {#each builderRoles as r}
+
+ {r.role}
+ {r.solidHex}
+
+ {/each}
+
+
+
@@ -593,4 +698,38 @@
font-size: var(--font-size-xs);
color: var(--color-content-secondary);
}
+
+ /* Theme builder */
+ .builder-controls {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--space-4);
+ }
+ .builder-seed {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ font-size: var(--font-size-sm);
+ color: var(--color-content-secondary);
+ }
+ .builder-seed input[type='color'] {
+ inline-size: 44px;
+ block-size: 30px;
+ padding: 0;
+ border: 1px solid var(--color-border-default);
+ border-radius: var(--radius-sm);
+ background: none;
+ cursor: pointer;
+ }
+ .builder-out {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+ }
+ .builder-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-2);
+ }