How it works
Every keystroke in the control pane runs the exact pipeline described in DESIGN.md:
generateRamp(hex, 18)interpolates from black through your color to white in Oklab space (hue and chroma follow a rational warp curve), then re-samples that path at a constrained luminance ladder — the closest fit (subject to monotonicity and the AA floor) to the closed-form ladderY_i + 0.05 = 1.05 · 21^(-i/17), pulled toward your own hex's real luminance at its nearest step. Because contrast is(Y_hi + 0.05)/(Y_lo + 0.05), every pair 9 steps apart still clears 4.5 (5.01 on the unconstrained ladder) and every pair 8 apart clears less — soK_ideal = ⌈0.501 × 17⌉ = 9is the minimal AA span for any hue you type, by construction rather than by statistics, while the color you typed round-trips exactly whenever the AA floor allows it. SeegenerateRamp.generateThemerepeats this per role and finds eachbaseTonesindex by nearest CIEDE2000 match to your original input, sothemeColor(l, "base", role)still resolves to (approximately) the color you actually picked. Font sizes and densities pass through as-is — they're already the valuesthemeSize()/themeDensity()consume directly.setTheme(name, theme)+themeApply()register light and dark siblings under isolated theme names so the site chrome is never touched — ramps, contrast checks, and the component gallery update through real CSS custom properties.- Contrast feedback uses
contrastRatio(WCAG 2.1) on live generated ramps; ramp quality scores come from the palette engine'sRamp.score.
Reset restores default role colors; Randomize explores freely; Harmony fills every role from the current primary via a simple hue-wheel scheme. Copy setTheme() snippet pastes the current ThemeInput into your app under the name you set.
Read next: generateRamp · Palette · DESIGN.md