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

@@ -15,6 +15,22 @@
(is (= "--accent" (gen/token->css-var :accent)))
(is (= "--bg-0" (gen/token->css-var :bg-0)))))
(deftest generate-color-scale-test
(testing "generates CSS variables for a color scale"
(let [scale (gen/generate-color-scale
:gray {:hue 240 :saturation 18
:steps [[50 97] [950 5]]})]
(is (str/includes? scale "--gray-50:"))
(is (str/includes? scale "--gray-950:"))
(is (str/includes? scale "#"))))
(testing "per-step saturation override"
(let [scale (gen/generate-color-scale
:accent {:hue 252 :saturation 96
:steps [[500 67 96] [400 75 93]]})]
(is (str/includes? scale "--accent-500:"))
(is (str/includes? scale "--accent-400:")))))
(deftest generate-css-test
(let [token-data (gen/read-tokens "src/theme/tokens.edn")
css (gen/generate-css token-data)]
@@ -31,6 +47,17 @@
(is (str/includes? css (str "--" (name token) ":"))
(str "Missing token: " (name token)))))
(testing "contains color scale variables"
(doseq [scale ["gray" "accent" "danger" "success" "warning"]]
(doseq [step [50 100 200 300 400 500 600 700 800 900 950]]
(is (str/includes? css (str "--" scale "-" step ":"))
(str "Missing color: " scale "-" step)))))
(testing "color scales only in :root, not in dark theme blocks"
(let [dark-block (second (str/split css #"\[data-theme=\"dark\"\]"))]
(is (not (str/includes? dark-block "--gray-50:")))
(is (not (str/includes? dark-block "--accent-500:")))))
(testing "contains dark theme data attribute selector"
(is (str/includes? css "[data-theme=\"dark\"]")))