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) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 60dd14ec74
commit 4a1936b5f6

@ -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<string, ScaleSteps> = 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<SchemeVariant>('tonal')
interface BuilderRole {
role: string
steps: string[]
solidHex: string
onSolid: string
}
const builderRoles = $derived.by((): BuilderRole[] => {
let seed: ReturnType<typeof parseColor>
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) }
})
})
</script>
<svelte:head>
@ -119,6 +167,63 @@
activas, así que cambia con el tema.
</p>
<!-- Theme builder — deriveScheme + generateScale, live -->
<section class="preset">
<div class="head">
<span class="eyebrow">uix.color · deriveScheme + generateScale</span>
<h2>Theme builder <small>(1 color → esquema completo · en vivo)</small></h2>
<p>
Elige un color de marca y una variante: el motor <strong>deriva</strong> los roles de
jerarquía (fórmula de Material 3 en OKLCH) y <strong>genera</strong> 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 <code>setCssVariables</code>.
</p>
</div>
<div class="builder-controls">
<label class="builder-seed">
semilla
<input type="color" bind:value={seedHex} />
<code>{seedHex}</code>
</label>
<div class="seg" role="group" aria-label="Variante">
{#each BUILDER_VARIANTS as v}
<button
type="button"
data-active={builderVariant === v}
onclick={() => (builderVariant = v)}
>
{v}
</button>
{/each}
</div>
</div>
<div class="builder-out">
{#each builderRoles as r}
<div class="role-row">
<span class="role-name"><strong>{r.role}</strong></span>
<div class="swatches">
{#each r.steps as hex, i}
<span
class="sw"
class:nine={i === 8}
style="background: {hex}"
title="{r.role} {i + 1}"
></span>
{/each}
</div>
</div>
{/each}
</div>
<div class="builder-chips">
{#each builderRoles as r}
<div class="chip" style="background: {r.solidHex}; color: {r.onSolid}">
<strong>{r.role}</strong>
<small>{r.solidHex}</small>
</div>
{/each}
</div>
</section>
<!-- La paleta — 31 escalas × 12 pasos -->
<section class="preset">
<div class="head">
@ -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);
}
</style>

Loading…
Cancel
Save

Powered by TurnKey Linux.