You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/gradient-finish-plan-2026-0...

177 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Gradient finish — plan de implementación (2026-07)
> Estado: **v1 en ejecución** (2026-07-15). Diseño cerrado tras tres análisis + revisión
> de coherencia + investigación de frameworks de referencia + mockup interactivo
> verificado en Chrome real. Este documento es el plan operativo; la doctrina queda
> en `docs/theming/reference.md` (sección «Gradient finish») cuando aterrice.
## La decisión (resumen ejecutivo)
**El gradiente es un ACABADO (finish) del fill, no una identidad de color.**
- `color` sigue diciendo *quién* es el componente (rol / escala / custom) — intacto.
- El prop nuevo `gradient` decide *cómo se pinta su fill saturado*: el wrapper eidos
estampa el attr `data-gradient` (eidos-only, familia `data-variant`/`data-size`;
NO se declara en morfo — ver la fila del checklist: el runtime lo resolvería desde
el espacio de props de soma y clobbearía el stamp).
- Taxonomía: color = identidad · variant = porte · depth/frost = material ·
state layer = feedback → el gradiente entra como **material/acabado**, pariente
de frost, jamás como valor de `data-color`. (El diseño «gradiente como valor del
eje color» fue analizado y descartado: un `<image>` no puede cumplir el contrato
del eje — 10 slots derivados mecánicamente + toda variante lo expresa. La historia
completa vive en la memoria del proyecto y en el mockup.)
## La receta v1 (rampa derivada, un dial)
Fill por capas:
```
background-color = la base sólida (--_{c}-bg — sin cambio; degrada en forced-colors)
background-image = acabado (--_{c}-fill-finish) [+ veil del state layer, ver «Diferido»]
```
El acabado se compone EN CSS desde los slots que la identidad ya provee, escalado
por **un solo dial del tema**.
> **Desviación declarada al implementar (2026-07-15):** el hover del recipe es
> `[data-button]:hover:not(...):not(...)` = (0,4,0) con shorthand `background:` —
> un re-assert genérico del generador a (0,3,0) PERDERÍA. Reparto final:
> **el generador emite solo la VAR** (`--_{c}-fill-finish`) y **el recipe pinta**
> (base + su propio hover guard) — coherente con «el generador fabrica, el
> recipe decide dónde pinta», como las variantes.
> **Rectificación medida (2026-07-15, antes de commitear):** el lift global
> hacia blanco rompía la tinta heredada en **52/84** combos a 26% (sonda con
> floors APCA≥60∧WCAG≥3; techo global seguro = 0% — teal 1%, azules/verdes de
> modo oscuro 0%: el paso 9 no tiene margen hacia el blanco en media paleta).
> Forma FINAL: **rampa ANCLADA al lado de sombra de la tinta** («la rampa huye
> de la tinta») — tinta blanca → ancla `#000`, extremo fuerte abajo (CTA
> sombreado); tinta oscura → ancla `#fff`, extremo fuerte arriba (glossy).
> Ancla + ángulo se resuelven por color×modo con el MISMO flip del slot
> `contrast`. El contraste solo puede mejorar → seguridad constructiva, dial
> sin topes (0 regresiones hasta 40%). Guard: `gradient-finish-guard.test.ts`.
```css
/* generated/base.css — renderRecipeGradientFinish (gate v1: button, badge) */
[data-button][data-gradient] {
--_button-finish-anchor: var(--color-primary-finish-anchor);
--_button-finish-angle: var(--color-primary-finish-angle);
--_button-fill-finish: linear-gradient(
var(--_button-finish-angle),
color-mix(in oklch, var(--button-palette-solid), var(--_button-finish-anchor) calc(var(--gradient-finish-lift) / 3)),
color-mix(in oklch, var(--button-palette-solid-hover), var(--_button-finish-anchor) var(--gradient-finish-lift))
);
}
[data-button][data-gradient][data-color] {
--_button-finish-anchor: var(--palette-finish-anchor, var(--color-primary-finish-anchor));
--_button-finish-angle: var(--palette-finish-angle, var(--color-primary-finish-angle));
}
/* button.css — el recipe pinta el fill saturado + re-assert en hover */
[data-button][data-gradient][data-variant='solid'] {
background-image: var(--_button-fill-finish);
}
[data-button][data-gradient][data-variant='solid']:hover:not([data-disabled]):not([data-loading]) {
background-image: var(--_button-fill-finish);
}
```
- **Dial**: `--gradient-finish-lift` (default `26%`), emitido por el generador desde
`primitives.gradientFinish.lift` — mismo patrón que las magnitudes del state layer
(config data, §38/§40 del theming reference). `lift: 0%` ≈ acabado apagado.
- **Derivación**: claro = `solid + lift` · profundo = `solid-hover − lift/3`.
Un dial escala toda la rampa. Override por instancia gratis (cascada).
- **Tinta**: HEREDADA (`palette-contrast`) — el extremo profundo (paso 10) ya está
validado por práctica shipped; el extremo claro (lift) lo acota el guard de build
(pendiente, ver Cola).
- **Por qué no saltar pasos (9→7 / 9→11)**: la escala de 12 pasos es funcional y
relativa al modo (7 = borde, 11 = texto; en oscuro el 11 es claro `#d19dff` →
la rampa se invertiría y mataría la tinta; en claro 9/10/11 están apiñados,
ΔL ≈ 0.04). El único ancla estable entre modos es el solid — por eso el lift se
define relativo al solid. Valores verificados en `generated/base.css`.
## Fases
### v1 — rampa derivada en Button (EN EJECUCIÓN)
| Pieza | Fichero | Estado |
| --- | --- | --- |
| Config `gradientFinish.lift` | `src/uix/eidos/lib/config-types.ts` | ✅ 2026-07-15 |
| Default `26%` | `src/uix/eidos/lib/primitives/static.ts` | ✅ |
| Emisión `--gradient-finish-lift` | `src/uix/eidos/lib/render-css.ts` | ✅ |
| Var finish por recipe (gate v1: button) + pintura en recipe | `render-css.ts` + `button.css` | ✅ (ver desviación) |
| Attr `data-gradient` | eidos-only de wrapper (familia `data-variant`) — **NO morfo**: declararlo hizo que el runtime lo resolviera desde props de soma → `undefined` → `mergeProps` clobbereó el stamp (bug cazado en vivo 2026-07-15 y revertido) | ✅ |
| Prop `gradient?: boolean` + estampado | `src/uix/eidos/components/button/{button.svelte,types.ts}` | ✅ |
| Regenerar `generated/base.css` | `npm run generate:eidos-css` | ✅ (dial en `:root:224`, var en `:4123`) |
| README de Button (prop nueva) | `src/uix/eidos/components/button/README.md` | ✅ |
| Lab del eje | `web/routes/temas/gradientes/+page.svelte` | ✅ |
| Doctrina + changelog | `reference.md §39` + `changelog.md §42` | ✅ |
| Checks | contrato 26/28 (2 fallos preexistentes de `proof-of-human`, ajenos) · audit 144/144 · eidos-lint button `invalid: 0` · svelte-check sin errores nuevos | ✅ |
| Navegador real (lab + dial + hover + claro/oscuro) | — | ☐ |
### v1.x — cola inmediata (no bloquea v1)
- ✅ **Guard del dial** (2026-07-15): `src/uix/eidos/gradient-finish-guard.test.ts`
— no-regresión ≤40% sobre 84 combos + set flat-fail clavado (cyan/orange,
deuda preexistente del on-solid). Con la rampa anclada el guard pasa 3/3.
- ✅ **Badge** (2026-07-15): gate ampliado (paleta privada; sin `solid-hover` →
extremo profundo cae a `solid`); attr eidos-only de wrapper (NO morfo).
- ✅ **Override por tema/modo del lift** (2026-07-15):
`ThemeDefinition.gradientFinish.lift` → re-emisión en el bloque de tema
(misma especificidad, después en cascada → el tema gana; `0%` apaga por
tema). Test: `active-eidos-config.test.ts` («per-theme gradient-finish dial
override»). **v1.x COMPLETA.**
- **Composición del veil del state layer**: `background-image: veil, var(--_{c}-fill-finish, none)`
a nivel archetype — solo necesaria cuando un componente que DEPENDE del veil
(item/option) gane gradiente; Button/Badge no lo necesitan (su hover re-asserta).
### v1.5 — kind `spread` ✅ (2026-07-15)
MEDIDO ANTES DE IMPLEMENTAR (doctrina D6b, esta vez en orden): (1) sonda de
84 combos — la rotación pura a L constante rompía grass (±4°) y gold (±27°)
porque L de OKLCH ≠ luminancia; con la mezcla débil del ancla (lift/3) en
ambos stops: **0 fallos hasta ±45°**; (2) RCS en Chromium por valores
computados — el canal `h` es `<number>`: `calc(h ± 30deg)` computa `none` →
el token `--gradient-finish-spread` es SIN unidad (`'30'`). Shipped:
`gradient="spread"` en Button+Badge (`data-gradient='spread'` overridea la
var del acabado; cero cambios de recipe CSS), guard 4/4, lab con especímenes
reales (incl. gris ≈plano, C≈0). Registro: D10 en
`docs/theming/gradient-finish.md`.
### v2 — named finishes + Surface
- ✅ **Primitiva `Surface`** (v2.1, 2026-07-15): Box + tratamiento — compone
`<Box>` (patrón Section) y estampa `data-surface` + `color`/`variant`
(soft/solid)/`gradient` (ambos kinds)/`rounded`; recipe palette-tint espejo
de Card sin chrome (`_palette-*` 5×8 → THM-2: roles + 33 escalas gratis);
morfo declarativo patrón Box (0 eventos justificados); audit **145/145**;
verificada por valores en navegador (rampa/spread/soft/tinta contrast).
Ficha con Decisiones/Gaps propios: `components/surface/README.md`.
- ✅ **Named finishes** (v2.2, 2026-07-15 — D11): `gradientFinish.named` opta
gradientes del open cage como acabado CON tinta autorada obligatoria
(`--gradient-{name}-ink`, override de `--_{c}-fg` a (0,3,0)); la base sigue
siendo el solid de la identidad (fill por capas) → `aurora` shipped como
blobs de rol con alfa SIN color base final. Honestidad: la validación
numérica al peor stop solo es posible para gradientes de modelo
(`buildGradient`) — diferida y documentada; el guard clava la sanidad del
config (named ⊆ gradients + ink presente).
- ✅ **`on`/`data-on` mínimo** (v2.2 — D12): `<Surface on="dark">` re-vincula
`--color-content-*`/`--color-border-default` para el subárbol (verificado
en vivo: muted = tinta del contexto al 64%); forced-colors → `CanvasText`.
Límites POR CONSTRUCCIÓN: anidados con tokens propios y portales excluidos
— la inversión COMPLETA de subárbol sigue siendo iniciativa independiente
(Card solid la necesita hoy sin gradientes).
**EL PLAN ESTÁ COMPLETO (2026-07-15).** Fuera del plan quedan, declarados:
la iniciativa de inversión completa de subárbol, la demo propia
`web/routes/uix/components/surface` (gap en la ficha de Surface) y la
validación de modelo para tintas de named finishes.
## Referencias
- Mockup interactivo de los 9 casos (verificado en Chrome real, dial en vivo):
artifact `38814ae1-7922-46b2-b344-12dc9528316d` (claude.ai).
- Memoria del proyecto: `project_gradient_as_color_2026-07-14` (historia completa:
diseño A descartado, mapa de 3 mecanismos de fondo, research de referencia,
revisión de coherencia).

Powered by TurnKey Linux.