feat(theme): add HSL-based color scale generation with jon/color-tools

Replace hardcoded hex color tokens with algorithmic color scales generated
from HSL parameters. Each scale is defined by hue, saturation, and
lightness steps in tokens.edn, then converted to hex via jon/color-tools.

Color scales (gray, accent, danger, success, warning) generate 11 stops
each (50–950) into :root. Semantic tokens (bg-0, fg-0, accent, etc.)
reference scale variables with var(--gray-50), var(--accent-500), etc.
Dark theme switches which stop each semantic token points to.

Gray scale uses hue 240 with tapered saturation for a purplish tint
matching the activity-tracker aesthetic. Accent is vivid purple (hue 252).
Border radii bumped to 6/10/16px for a rounder feel.

To shift the entire palette (e.g. warm gray), change hue/saturation in
tokens.edn and run `bb build-theme`.
This commit is contained in:
Florian Schroedl
2026-03-11 11:31:47 +01:00
parent 13508f4654
commit fa38d9f9c3
5 changed files with 207 additions and 42 deletions

View File

@@ -229,7 +229,7 @@ Semantic + scale tokens in `src/theme/tokens.edn`:
- **Backgrounds**: `bg-0` (base), `bg-1` (surface), `bg-2` (elevated)
- **Foregrounds**: `fg-0` (primary text), `fg-1` (secondary), `fg-2` (muted)
- **Semantic**: `accent`, `danger`, `success` + `fg-on-*` for contrast text
- **Borders**: `border-0/1/2` (full shorthand: `1px solid #color`)
- **Borders**: `border-0/1/2` (full shorthand: `1px solid var(--gray-N)`)
- **Shadows**: `shadow-0/1/2/3` (increasing elevation)
- **Radii**: `radius-sm/md/lg`
@@ -254,7 +254,32 @@ Produces `--size-1: 0.25rem` through `--size-16: 4rem`. Use for all spacing, pad
Produces `--font-xs: 0.64rem` through `--font-3xl: 3.052rem`. Use for all font-size values.
To adjust the entire scale, change `base` or `ratio` — all values recompute on `bb build-theme`.
**Color scales** — HSL-based, generated via `jon.color-tools`:
```edn
:color {:gray {:hue 240 :saturation 18
:steps [[50 97 14] [100 95 14] ... [950 5 18]]}}
```
Each step is `[label lightness]` (uses default saturation) or `[label lightness saturation]` (per-step override). Produces `--gray-50: #hex` through `--gray-950: #hex`.
Available color scales: `gray`, `accent`, `danger`, `success`, `warning`. Each generates 11 stops (50, 100, 200900, 950).
**To change the gray tone** (e.g. warm gray, cool blue-gray, purplish), change `hue`:
- `240` → purplish gray (current, inspired by activity-tracker)
- `220` → blue-gray
- `0` → warm gray (pinkish)
- `0` with saturation `0` → pure neutral gray
**To change the accent color**, change `hue` in the accent scale:
- `252` → purple (current, matches activity-tracker)
- `220` → blue
- `142` → green
- `0` → red
Semantic tokens reference scale variables: `var(--gray-50)`, `var(--accent-500)`, etc. Dark theme overrides switch which stop is used (e.g. `bg-0` goes from `gray-50``gray-950`).
To adjust the entire scale, change `hue`, `saturation`, or `steps` — all values recompute on `bb build-theme`.
**Usage in component CSS:**
@@ -267,6 +292,7 @@ To adjust the entire scale, change `base` or `ratio` — all values recompute on
```
**Rule: never use raw `rem` values in component CSS** — always reference a scale variable.
**Rule: never use raw hex colors in component CSS** — always reference a token or scale variable.
### Adding tokens
@@ -275,10 +301,12 @@ Add to both `:tokens` (light) and `:themes > :dark` in `tokens.edn`. They must h
### Dark mode
Three CSS layers are generated:
1. `:root { ... }` — light defaults
2. `[data-theme="dark"] { ... }` — explicit dark override
1. `:root { ... }` — light defaults + all scales (size, font, color)
2. `[data-theme="dark"] { ... }` — explicit dark override (semantic tokens only)
3. `@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { ... } }` — auto dark
Color scales are generated once in `:root` and never duplicated. Dark theme just reassigns which scale stop each semantic token points to.
Toggle with: `document.documentElement.dataset.theme = "dark" | "light"`
## Squint Pitfalls