# Color formula Color is **not hand-picked**. Every chart color does exactly one of four jobs, and a palette is legal only if it passes six checks. The checks are the product — they are what makes a palette safe to change and what lets the same method run on any design system's ramps. ## The four jobs | Job | What it encodes | Structure | |---|---|---| | **Categorical** | identity (which series) | 8 hues, fixed order, assigned in sequence, never cycled | | **Ordinal** | position in a sequence (funnel stage, tier, bucket) | one hue, monotone lightness steps; light end still ≥ 2:1 on surface | | **Sequential** | magnitude (how much) | one hue, steps 100→700, light→dark; flips anchor in dark | | **Diverging** | polarity (which side of a baseline) | two hues + a neutral gray midpoint; equal steps per arm | | **Status** | state (good→critical) | a small fixed scale, reserved meaning, always icon+label | **Categorical or ordinal?** If swapping the category order would change the meaning — funnel stages, size tiers (S/M/L), age bands, cohort buckets — it is **ordinal** and takes a one-hue ramp so the reader sees the order in the color. If swapping would not — product names, teams, regions, endpoints — it is **nominal categorical** and each bar takes the *same* slot-1 hue (one series, so no legend box — the title names it), or slots 1..N when there are N separate series. Never color nominal bars by their value: that spends the identity channel re-encoding what bar length already shows. ## The six checks Every categorical color — current or proposed — must pass all six. 1. **Fixed hue anchors.** Eight families in a fixed order. The order is the CVD-safety mechanism; it never changes. *(structural — enforced, not measured)* 2. **Lightness band per mode.** OKLCH L ≈ 0.43–0.77 light; ≈ 0.48–0.67 dark. *(validator)* 3. **Chroma floor.** OKLCH C ≥ ~0.10 — below it a hue reads as gray and stops doing identity work. *(validator)* 4. **CVD separation.** Machado-2009 ΔE ≥ 12 target / ≥ 8 floor (floor legal only with secondary encoding), under protanopia & deuteranopia. *Adjacent* pairs for stacks/bars/lines (only neighbors touch — assignment never skips); **all pairs for scatter, bubble, choropleth, and small-multiples**, where any two marks can sit side by side — pass `--pairs all` there or a real collapse stays hidden. *(validator)* 5. **Contrast vs surface.** ≥ 3:1 for marks; conditionally relaxed where values are readable another way (visible labels or the table view). *(validator)* 6. **Documented palette only.** Every slot is a hex from the instance file (`palette.md` or its equivalent) — no eyeballed values. *(structural; for a customer's ramps, snap to nearest — below)* ## Run the checks — never eyeball them ``` node scripts/validate_palette.js \ "#2a78d6,#1baf7a,#eda100,#008300,#4a3aa7,#e34948,#e87ba4,#eb6834" --mode light ``` (`scripts/` is relative to this skill's base directory, shown at the top of the prompt.) (or load it as `