theme-extraction
Theme extraction
Inspect the source site's CSS for colors (ranked hex codes from inline
styles + <style> blocks) and fonts (ranked font families). This file
describes the deterministic algorithm to derive a daisyUI theme from
that input.
Step 1: classify each color
For each hex color from css_colors, convert to HSL. Then bucket:
| Saturation | Lightness | Bucket |
|---|---|---|
< 0.10 |
any | neutral (gray) |
≥ 0.10 |
< 0.30 |
dark-vivid |
≥ 0.10 |
0.30 - 0.70 |
vivid |
≥ 0.10 |
> 0.70 |
light-vivid |
Step 2: assign semantic roles
- primary — most frequent
vividcolor (highest saturation × frequency) - secondary — second-most-frequent
vividcolor with hue Δ ≥ 60° from primary - accent — third-most-frequent
vividcolor (or a complementary hue if none) - neutral — most frequent
neutralcolor - base-100 — lightest frequent
neutralcolor (default: white if none) - base-200/300 — auto-derived from base-100 by lowering lightness 3% / 6%
If no vivid colors exist in the source, fall back to daisyUI defaults (blue 240 hue) and note this in your rebuild summary.
Step 3: convert hex to OKLCH
Use the OKLCH conversion. Ruby snippet (already available via the
color gem if bundled, otherwise compute):
# Pseudocode — actual conversion uses CSS Color 4 spec
def hex_to_oklch(hex)
rgb = parse_hex(hex)
linear = rgb.map { |c| gamma_decode(c / 255.0) }
# ... linear → XYZ → OKLab → OKLCH
"#{lightness.round(0)}% #{chroma.round(3)} #{hue.round(0)}"
end
For OKLCH values, hue (h) is in degrees 0-360, lightness (L) is 0-100%, chroma (C) is typically 0-0.4.
Step 4: derive *-content colors
For each semantic color, the *-content color must contrast ≥4.5:1
(WCAG AA). Quick heuristic:
- If the parent color has lightness > 60% in OKLCH, content is dark
(use
oklch(20% 0.05 <same-hue>)) - Otherwise content is light (
oklch(98% 0.01 <same-hue>))
Step 5: assemble theme CSS
Wrap in [data-theme="custom"] { ... } block. Pass to set_theme tool.
The platform's Website::Theme::ConfigNormalizer may auto-wrap; check
the response.
If base-100 is dark (sanity check below), add color-scheme: dark;
to the block — platform and browser UI detect darkness from it.
Sanity check
Before calling set_theme:
- primary and secondary should be visually distinguishable
- base-100 should be a light color (for a light theme) OR a very dark color (for dark)
- accent should pop against base-100