0.75 0.13 191.6
0.64 0.13 216.0
0.52 0.14 240.5
0.41 0.15 264.9
0.29 0.15 289.3
0.39 0.16 313.0
0.50 0.17 336.7
0.60 0.18 0.4
0.70 0.19 24.1
0.74 0.19 41.1
0.79 0.18 58.2
0.83 0.18 75.2
0.87 0.18 92.2
0.81 0.17 113.3
0.77 0.16 137.1
0.75 0.14 163.9
0.985 0 0
0.967 0.001 286
0.920 0.004 286
0.705 0.015 286
0.552 0.016 286
0.274 0.006 286
0.210 0.006 286
0.141 0.005 286
Documentation
Color System
Why OKLch
Verdigris uses OKLch as the canonical color space. All color tokens are defined in OKLch; other formats (HSL, hex, RGB) are generated by the build pipeline.
Why not HSL?
HSL is perceptually non-uniform — hsl(60, 100%, 50%) (yellow) appears far brighter than hsl(240, 100%, 50%) (blue) despite identical L values. This makes palette interpolation unpredictable and accessibility auditing unreliable.
OKLch fixes this:
- L (lightness) is perceptually linear — equal numeric changes produce equal visual changes
- C (chroma) controls saturation without shifting perceived brightness
- h (hue) rotates through the color wheel
This means Patina’s gradient palette (teal → purple → red → yellow) was generated by rotating the hue while keeping lightness and chroma in controlled ranges. The result is a harmonious palette that “feels” balanced across all hues.
Browser support: OKLch is supported in all evergreen browsers since 2023. The build pipeline generates HSL fallbacks for email clients and legacy contexts.
Brand Palette
The palette is a hue-rotation gradient anchored at four points:
| Name | OKLch | Approx Hex | Role |
|---|---|---|---|
| Verdigris (teal) | oklch(0.75 0.1286 191.57) |
#0fc8c3 | Primary brand, CTAs, links |
| Midnight Purple | oklch(0.29 0.1506 289.33) |
~#1a0a4a | Deep accent, dark sections |
| Pastel Red | oklch(0.7 0.1909 24.11) |
~#e85d3a | Warm accent, sidebar active |
| Cyber Yellow | oklch(0.87 0.1786 92.23) |
~#d4c520 | Highlight, attention |
Between each anchor, three interpolation steps create a smooth 16-color chart palette. See tokens/color/base.json for all values.
Gradient Logic
Teal (191°) → step1 (216°) → step2 (240°) → step3 (265°)
→ Purple (289°) → step1 (313°) → step2 (337°) → step3 (0°)
→ Red (24°) → step1 (41°) → step2 (58°) → step3 (75°)
→ Yellow (92°) → step1 (113°) → step2 (137°) → step3 (164°)
→ (back to Teal)
This creates a full-spectrum palette from a single generative rule — add or remove steps by interpolating between the anchors.
Palette Semantics — What the Colors Mean
The brand palette has 16 tokens but the design system must define what they mean, not just what they are. This is critical for AI agents and evaluator pipelines that select colors without human visual judgment.
The palette divides into 6 semantic regions based on content category:
Trust (Teal → Blue, hue 191°–240°)
Tokens: verdigris, mix-step-1, mix-step-2
Content: Technology, platform capabilities, data quality, primary brand expression
Why: The anchor of the Verdigris brand. Bright, authoritative, technically precise. This is what a customer should associate with the company name.
- Use as section accents for technology and platform content
- Safe as text on dark backgrounds (12.3:1 contrast on neutral.950)
- NOT safe as text on white (fails WCAG at ~2.9:1)
- Dark mode tint:
oklch(0.75 0.1286 191.57 / 15%)over neutral.950
Depth (Deep Blue → Purple, hue 265°–289°)
Tokens: mix-step-3, midnight-purple
Content: AI/intelligence features, premium sections, dark hero backgrounds, footer
Why: The darkest region (L=0.29–0.41). Creates gravitas and sophistication. Works best as backgrounds, not foreground elements.
- Use for dark section backgrounds and hero overlays
- NOT as text — too dark, invisible on dark backgrounds
- Dark mode tint: use 18%+ opacity — below 15%, midnight-purple (chroma 0.15) is indistinguishable from neutral gray
- Excellent for gradient endpoints paired with teal
Energy (Purple → Magenta, hue 313°–0°)
Tokens: midnight-purple-step-1, midnight-purple-step-2, midnight-purple-step-3
Content: Fault detection, real-time monitoring, anomaly indicators, alerting
Why: High chroma, vibrant mid-range. These colors demand attention without being aggressive — appropriate for detection and signal content.
mp-step-2(L=0.50) andmp-step-3(L=0.60) work as text on both light and dark backgrounds- Use as accent borders, icon tints, alert section backgrounds
- Dark mode tint:
oklch(0.495 0.1708 336.72 / 12%)over neutral.950
Warmth (Red → Coral, hue 24°–41°)
Tokens: pastel-red, pastel-red-step-1
Content: Contact/conversation, CTA hover states, team sections, customer stories
Why: Warm tones invite human connection. The sidebar-primary color in Patina. Appropriate for sections where the brand becomes personal.
- Use for CTA hover shifts (teal → warm on hover)
- Pastel red (L=0.70) works as text on dark backgrounds only
- Dark mode tint:
oklch(0.7 0.1909 24.11 / 10%)over neutral.950
Results (Orange → Yellow, hue 58°–92°)
Tokens: pastel-red-step-2, pastel-red-step-3, cyber-yellow
Content: Metrics, ROI, stranded capacity recovery, financial outcomes, proof points
Why: Bright, optimistic tones (L=0.79–0.87) that say “look at these numbers.” Natural for stats and outcomes.
- HIGH CONTRAST RISK — very light tokens. Only use on dark backgrounds.
- Use as metric/stat highlights, warm section accents
- Dark mode tint:
oklch(0.87 0.1786 92.23 / 8%)— visible despite low opacity due to high lightness
Growth (Yellow → Green, hue 113°–164°)
Tokens: cyber-yellow-step-1, cyber-yellow-step-2, cyber-yellow-step-3
Content: M&V results, capacity recovery, sustainability, environmental impact, completion
Why: Green-teal tones that close the chromatic loop back to brand teal. Natural for sustainability and recovery outcomes.
- Light tokens (L=0.75–0.81) — dark backgrounds only
- Dark mode tint:
oklch(0.7698 0.1588 137.1 / 10%)over neutral.950 - Bridges back to the trust region — use for sections that resolve/conclude
Neutral (Breathing Room)
Tokens: neutral.50 through neutral.950
Content: Body text, structural sections, form backgrounds, breathing room between colored sections
Rule: At least 30% of page sections should use neutral backgrounds to prevent chromatic fatigue.
Taste Boundaries — Restraint Rules
The palette semantics without ceilings will produce a rainbow. The brand pillars define the boundaries:
- “Controlled color palette, no visual clutter.” (Masterful)
- “Subtle use of the brand gradient.” (Refined)
- “Every element earns its place.” (Precision)
- “Credibility through restraint. Sophisticated buyers, not impulse shoppers.”
Color is earned, not assigned by lookup table. An accent region appears because it makes the page better, not because a mapping table says it should.
Teal dominance: Teal + neutrals must comprise at least 70% of any surface’s color expression. Other palette regions are rare accents, not co-primaries. The brand IS teal.
Maximum regions per surface:
| Surface | Max non-neutral regions | Rationale |
|---|---|---|
| Web page (homepage) | 2 (trust + 1 accent) | Controlled palette; 2 accents only for 10+ section pages with justification |
| Landing page | 1 (brand teal only) | Focused message, no chromatic distraction |
| Ad banner | 1 (brand teal only) | Instant recognition, not variety |
| 1 (brand teal only) | Inbox is noisy — stay clean | |
| White paper | 2 (trust + 1 accent for data) | Professional restraint |
| Physical goods | 1 (brand teal only) | Hardware/packaging: maximum brand clarity |
| Data visualization | 6 (full palette) | This is what the gradient was designed for |
| Slide deck | 2 per slide | One accent per point |
Section limits (web pages):
- No more than 30% of sections should have non-neutral palette accents — the rest should breathe
- Never place two colored sections adjacent — always separate with neutral
- Accents are subtle (background tints and label colors) — NOT full-saturation backgrounds
Section Flow — Lightness Rhythm
Pages have a lightness direction. Alternating dark and light sections creates a strobe — the eye has to re-adapt at every boundary. Commit to a direction and vary within it.
Light-mode page flow:
Dark hero → white → neutral.100 → white → [one dark accent] → white → footer
- Content sections flow through white and neutral.100 (subtle tonal shift, not a contrast flip)
- At most ONE dark-background section as a deliberate accent moment (e.g., detection callout, CTA strip)
- Hero and footer are always dark — they don’t count toward the limit
Dark-mode page flow:
neutral.950 → neutral.900 → neutral.950 → [tinted accent] → neutral.900 → footer
- Content sections flow through neutral.950 and neutral.900
- Brand-tinted sections provide variety without lightness jumps
Rules:
- No strobe — never alternate dark/light/dark/light across consecutive sections
- Max lightness jump in content flow: 0.15 OKLch L* — white (L=1.0) to neutral.100 (L=0.967) is fine; white to neutral.900 (L=0.21) is not
- Consistent text color — light flow = dark text throughout; dark flow = light text throughout. Don’t force the reader to re-adapt.
Usage Rules
- Teal first — teal + neutrals dominate every surface (70%+ minimum)
- Max 2 regions on a web page — trust + at most 1 accent region (earned, not automatic)
- No random assignment — section accent colors must match the content category above
- Breathing room — at least 70% neutral sections; never two colored sections adjacent
- Subtlety — palette accents are tints and labels, not saturated backgrounds
- Section flow — commit to a lightness direction; max one dark accent moment per light page
- Dark tint minimum — depth region needs 18%+ opacity; all others need 10%+
- Verify contrast — text on brand-tinted backgrounds must still pass WCAG AA (4.5:1)
See rules/visual-rules.yml → color.palette_semantics for the machine-consumable version of this guidance.
Neutral Scale
The neutral scale is zinc-tinted (hue ~286°) rather than pure gray. This gives surfaces a subtle warmth that complements the teal brand color. Extracted from Patina’s production CSS.
| Token | OKLch | Role |
|---|---|---|
| neutral.50 | oklch(0.985 0 0) |
Near-white |
| neutral.100 | oklch(0.967 0.001 286.375) |
Secondary/muted bg |
| neutral.200 | oklch(0.92 0.004 286.32) |
Borders, inputs |
| neutral.400 | oklch(0.705 0.015 286.067) |
Ring, muted-fg (dark) |
| neutral.500 | oklch(0.552 0.016 285.938) |
Muted-fg (light), ring (dark) |
| neutral.800 | oklch(0.274 0.006 286.033) |
Dark mode secondary |
| neutral.900 | oklch(0.21 0.006 285.885) |
Light primary, dark card |
| neutral.950 | oklch(0.141 0.005 285.823) |
Light foreground, dark bg |
Dark Mode Strategy
Both codebases use the same mechanism: CSS custom properties toggled via a .dark class on the HTML element. The semantic tokens (background, foreground, primary, etc.) swap values between light and dark.
Key dark mode choices:
- Background swaps from white to neutral.950 (near-black)
- Primary inverts from near-black to light gray — ensuring contrast in both modes
- Borders become semi-transparent white (
oklch(1 0 0 / 10%)) for a subtle glass effect - Sidebar primary stays pastel-red in both modes — brand consistency
See tokens/color/semantic-dark.json for all overrides.
WCAG Contrast
The www site darkened the brand teal from hsl(178, 86%, 42%) to hsl(178, 86%, 28%) for WCAG AA text contrast on white backgrounds (~4.9:1 ratio). This is documented as legacy.www-primary-light in the token set.
Recommendation: Use the original bright teal (brand.verdigris) for decorative/non-text use (backgrounds, illustrations, data viz). Use neutral.900 (Patina’s approach) or the darkened teal (www’s approach) for text/interactive elements where contrast matters.
Multi-Format Output
The build pipeline generates:
| Format | File | Consumer |
|---|---|---|
| OKLch CSS vars | css/oklch.css |
Patina, modern browsers |
| HSL CSS vars | css/hsl.css |
www (until OKLch migration) |
| Hex JSON | hex/colors.json |
Email templates, Figma, print |
| Tailwind preset | tailwind/preset.js |
Both codebases via config |
Known Discrepancies (www vs Patina)
| Token | www | Patina | Resolution |
|---|---|---|---|
| Color space | HSL | OKLch | OKLch is canonical; HSL output available for compatibility |
| Primary (light) | Darkened teal hsl(178,86%,28%) |
Near-black oklch(0.21...) |
Different strategies — both valid |
| Ring/focus | Green-shifted hsl(153,67%,38%) |
Neutral oklch(0.705...) |
Neutral ring is canonical |
| Brand teal | hsl(178,86%,42%) |
oklch(0.75,0.1286,191.57) |
Equivalent values — confirmed |