feat(temas/color): subtle intent harmonization (slider, default 12%)

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>
active-uix
dev 4 months ago
parent 60af383efa
commit daac8b2e54

@ -326,7 +326,10 @@ expone con un input de color por fila + «auto» para volver a derivado.
**Los 6 intents NO se derivan** — son hues canónicos del libro (un error es rojo **Los 6 intents NO se derivan** — son hues canónicos del libro (un error es rojo
siempre). `harmonize(color, toward, amount)` (M3 `blend.harmonize`) los empuja hacia siempre). `harmonize(color, toward, amount)` (M3 `blend.harmonize`) los empuja hacia
la marca un % si el builder quiere cohesión — opt-in, conserva L y C. la marca un % si el builder quiere cohesión — opt-in, conserva L y C. Un valor
**sutil (~10-15%)** hace que los intents se sientan de la familia **sin perder su
lectura** (rojo sigue rojo); el builder de `/temas/color` lo usa como default (slider
0 = canónico puro → fuerte). Resuelve el "los intents puros chirrían con la marca".
**Caveat del +60°**: la rotación de Material puede caer cerca de un intent según el **Caveat del +60°**: la rotación de Material puede caer cerca de un intent según el
primary (p. ej. `purple + 60° = H6 ≈ red/threat`). Por eso el tema base afinó su primary (p. ej. `purple + 60° = H6 ≈ red/threat`). Por eso el tema base afinó su

@ -118,7 +118,9 @@
let seedHex = $state('#8e4ec6') let seedHex = $state('#8e4ec6')
let builderVariant = $state<SchemeVariant>('tonal') let builderVariant = $state<SchemeVariant>('tonal')
let harmonizeIntents = $state(false) // Subtle by default: intents lean toward the brand enough to feel cohesive but
// stay recognizable (red is still red). 0 = pure canonical. (M3 harmonize.)
let harmonizeAmount = $state(0.12)
// Per-role overrides the designer pinned (role → hex). Empty = fully derived. // Per-role overrides the designer pinned (role → hex). Empty = fully derived.
let overrides = $state<Record<string, string>>({}) let overrides = $state<Record<string, string>>({})
@ -184,11 +186,12 @@
for (const { role, seed } of schemeSeeds) { for (const { role, seed } of schemeSeeds) {
if (role !== 'neutralVariant') emitRole(parts, role, seed, bg) if (role !== 'neutralVariant') emitRole(parts, role, seed, bg)
} }
if (harmonizeIntents) { if (harmonizeAmount > 0) {
for (const intent of INTENTS) { for (const intent of INTENTS) {
if (intent === 'neutral') continue if (intent === 'neutral') continue
const baseHex = activeScales[CANONICAL_INTENT_SCALES[intent]]?.['9'] const baseHex = activeScales[CANONICAL_INTENT_SCALES[intent]]?.['9']
if (baseHex) emitRole(parts, intent, harmonize(parseColor(baseHex), seedOklch, 0.22), bg) if (baseHex)
emitRole(parts, intent, harmonize(parseColor(baseHex), seedOklch, harmonizeAmount), bg)
} }
} }
return parts.join(';') return parts.join(';')
@ -252,8 +255,9 @@
con su pick on-solid (APCA), y <strong>aplica el tema a TODA la página en vivo</strong> con su pick on-solid (APCA), y <strong>aplica el tema a TODA la página en vivo</strong>
(override de <code>--primitive-{'{role}'}-*</code> en <code>.root</code> → reproyecta toda (override de <code>--primitive-{'{role}'}-*</code> en <code>.root</code> → reproyecta toda
la jerarquía + el chrome neutral; en una app real sería <code>setCssVariables</code>). La la jerarquía + el chrome neutral; en una app real sería <code>setCssVariables</code>). La
librería de 31 escalas y los 6 intents canónicos NO cambian — salvo que actives librería de 31 escalas NO cambia. Los 6 intents se <strong>armonizan suavemente</strong>
<strong>harmonize intents</strong> (los empuja hacia la temperatura del seed). Y puedes hacia la marca (slider; 0 = canónico puro) para que no se sientan desentonados, sin perder su
lectura — rojo sigue siendo rojo. Y puedes
<strong>fijar</strong> cualquier rol con su propio color (el input de cada fila); los demás <strong>fijar</strong> cualquier rol con su propio color (el input de cada fila); los demás
se siguen derivando del seed. se siguen derivando del seed.
</p> </p>
@ -276,8 +280,9 @@
{/each} {/each}
</div> </div>
<label class="builder-harmonize"> <label class="builder-harmonize">
<input type="checkbox" bind:checked={harmonizeIntents} />
harmonize intents harmonize intents
<input type="range" min="0" max="0.35" step="0.01" bind:value={harmonizeAmount} />
<code>{harmonizeAmount === 0 ? 'canónico' : `${Math.round(harmonizeAmount * 100)}%`}</code>
</label> </label>
</div> </div>
<div class="builder-out"> <div class="builder-out">
@ -332,7 +337,9 @@
Escalas funcionales (1-2 fondos · 3-5 componente · 6-8 bordes · <strong>9 sólido</strong> · Escalas funcionales (1-2 fondos · 3-5 componente · 6-8 bordes · <strong>9 sólido</strong> ·
10 hover · 11-12 texto). El <strong>representativo</strong> de cada escala es el 10 hover · 11-12 texto). El <strong>representativo</strong> de cada escala es el
<strong>paso 9</strong>. La paleta la trae el tema; el framework envía estas 31 (valores <strong>paso 9</strong>. La paleta la trae el tema; el framework envía estas 31 (valores
Radix exactos, listos para autoría OKLCH). Radix exactos, listos para autoría OKLCH). Es la <strong>librería fija</strong> de
referencia — el seed del builder NO la cambia: deriva los <strong>roles</strong> (arriba),
que son escalas generadas nuevas, no miembros de esta lista.
</p> </p>
</div> </div>
<div class="palette"> <div class="palette">
@ -829,6 +836,10 @@
color: var(--color-content-secondary); color: var(--color-content-secondary);
cursor: pointer; cursor: pointer;
} }
.builder-harmonize input[type='range'] {
max-inline-size: 120px;
accent-color: var(--color-primary-solid);
}
.role-pin { .role-pin {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

Loading…
Cancel
Save

Powered by TurnKey Linux.