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/src/libs/gradient/README.md

75 lines
3.5 KiB

feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
# `$libs/gradient` — the canonical gradient model
A pure, zero-dependency gradient **model** + CSS **serializer**, shared by two
consumers so a gradient is one thing in both places:
- the **eidos theming axis** ([`uix/eidos/lib/build-gradient.ts`](../../uix/eidos/lib/build-gradient.ts))
— derives named `--gradient-*` tokens from color roles;
- the **GradientBuilder component** (soma, Phase 2) — the interactive editor.
A gradient built in the editor is therefore also a themeable token, and a token
re-opens in the editor. No other system unifies "gradient derived from a color
system" with "hand-edited gradient" — that unification is the whole point of
keeping the model in a lib both layers can import (below soma, no layer
violation).
## The model (`model.ts`)
```ts
type Gradient = LinearGradient | RadialGradient | ConicGradient | MeshGradient
interface GradientStop {
color: StopColor // role ref | OKLCH literal | raw css
position?: number // 0..1 along the line
alpha?: number // 0..1
}
type StopColor =
| { kind: 'role'; role: string; slot?: string; step?: number } // → var(--color-…) — RE-TINTS
| { kind: 'oklch'; value: Oklch } // stored perceptual literal
| { kind: 'css'; value: string } // transparent / currentColor
```
Geometry is **explicit per kind** (`LinearGradient.angle`, `RadialGradient.shape/size/at`,
`ConicGradient.from/at`, `MeshGradient.points`) so it round-trips losslessly to
CSS and maps trivially to a future on-canvas gizmo + keyboard editing — no opaque
affine matrix. Colors are stored as **`Oklch`** (not hex): the "store OKLCH, not
hex" property that keeps gradients gamut-honest and perceptually interpolated.
### Why role stops are the headline
A `{ kind: 'role', … }` stop serializes to `var(--color-{role}-{slot})` rather
than a frozen color. So a single `--gradient-{name}` token **re-tints with the
brand seed and flips light/dark for free** — Tailwind / Open Props / Panda blend
two literal endpoints; Material 3 has no gradient axis at all. Nobody derives the
stops themselves from a color system.
## The serializer (`to-css.ts`)
```ts
gradientToCss(g: Gradient): string // model → a CSS <gradient> (or stacked-radial mesh) string
meshToCss(m: MeshGradient): string
stopColorToCss(c: StopColor, alpha?): string
```
- Role stops → `var(--color-…)`; OKLCH → `oklch(L% C H / a)`; css → verbatim.
- Interpolation defaults to **`in oklch`** (perceptual; not the muddy sRGB
midpoint dead-zone) — emitted geometry-first then `in <space> [<hue>]`, the
ordering Tailwind v4 ships in production. Hue paths (`longer`/`shorter`) drive
aurora / iridescent sweeps from two role endpoints.
- Mesh serializes to **layered `radial-gradient`s over a base** — the only
portable CSS mesh today (native CSS mesh is unshipped). Re-tints because each
blob is a role/OKLCH color in the model.
The OKLCH→hex **fallback** sibling (wide-gamut honesty for pinned literals) is a
*consumer* concern (eidos has `$color`); this lib stays pure and emits the
`oklch()` form.
## Status
- ✅ model + `gradientToCss` (this lib) — tested in [`to-css.test.ts`](./to-css.test.ts).
- ✅ eidos axis (`buildGradient` / `applyGradients`) — see `uix/eidos`.
- ⏳ `parseGradient` (CSS → model, lossless round-trip) — Phase 2, what the
GradientBuilder needs (paste-to-edit). The model is shaped for it (`hints`,
explicit geometry) but it is not implemented yet.

Powered by TurnKey Linux.