import { test } from "node:test"; import assert from "node:assert/strict"; import { ACCENT_CHART_SLOT, fittedHex, perBaseColor, siteChartColors, siteColor } from "./siteColor"; import { CHART_SLOTS, seriesColor } from "./homepageChart"; import { resolveAccent } from "./accent"; import { ACCENTS, ACCENT_IDS, BASE_GROUNDS, BASE_GROUND_IDS, contrastRatio, type BaseGround } from "./brand"; // What the browser paints for a perBaseColor value on `base`: tokens.css sets // `--base-` to 1 and the other to 0 (`null`: no token sheet at all, // so every var() takes its fallback). Returns "#rrggbb". function paint(css: string, base: BaseGround | null): string { const flags = css.replace(/var\(--base-(light|dark), ([01])\)/g, (_, b, fallback) => base === null ? fallback : b === base ? "1" : "0", ); const channels = [...flags.matchAll(/calc\(([^()]*)\)/g)].map((m) => m[1].split("+").reduce((sum, term) => { const [a, b] = term.split("*").map(Number); return sum + a * b; }, 0), ); assert.match(flags, /^rgb\(calc\([^()]*\) calc\([^()]*\) calc\([^()]*\)\)$/, css); assert.equal(channels.length, 3, css); return `#${channels.map((c) => c.toString(16).padStart(2, "0")).join("")}`; } // ── siteColor: the mark colour ─────────────────────────────────────────────── test("siteColor: a named accent is its per-base swatch, whatever hex rides along", () => { for (const id of ACCENT_IDS) { assert.equal( siteColor({ accentId: id, accent: ACCENTS[id].onDark }, seriesColor(3)), `var(--swatch-${id})`, ); } // The id wins even without the hex (a hand-built summary). assert.equal(siteColor({ accentId: "brass" }, seriesColor(0)), "var(--swatch-brass)"); }); test("siteColor: a custom hex is fitted to each base, as the site's own pages fit it", () => { // A pale hex: 1.43:1 on the light ground as published, so the old card // painted it nearly invisible on light. const pale = "#f4c2d7"; const fitted = resolveAccent(pale); const css = siteColor({ accent: "#F4C2D7" }, seriesColor(0)); assert.equal(css, perBaseColor(fitted)); for (const base of BASE_GROUND_IDS) { assert.equal(paint(css, base), fitted[base], base); assert.ok(contrastRatio(paint(css, base), BASE_GROUNDS[base]) >= 4.5, base); } // Fitted, not as published, where the ground needs it; kept where it reads. assert.deepEqual( { light: paint(css, "light"), dark: paint(css, "dark") }, { light: "#846974", dark: pale }, ); // A hex that already reads on a ground is kept there exactly (#cc3366 on // light), and the chart colour is never used for one. assert.equal(paint(siteColor({ accent: "#cc3366" }, seriesColor(4)), "light"), "#cc3366"); assert.equal(paint(siteColor({ accent: "#cc3366" }, seriesColor(4)), "dark"), resolveAccent("#cc3366").dark); }); test("perBaseColor: one value, each base's colour; the light one with no token sheet", () => { const v = { light: "#010203", dark: "#ffeedd" }; const css = perBaseColor(v); for (const base of BASE_GROUND_IDS) assert.equal(paint(css, base), v[base]); assert.equal(paint(css, null), v.light); // Plain text a style attribute takes as is: no quotes, no url(), no `;`. assert.doesNotMatch(css, /["';]|url\(/); }); test("perBaseColor: anything but a #rrggbb per base throws, never paints rgb(NaN …)", () => { const ok = { light: "#010203", dark: "#ffeedd" }; for (const bad of ["#fff", "red", "", "var(--brand)", "#12345g", "#1234567"]) { assert.throws(() => perBaseColor({ ...ok, dark: bad }), RangeError, bad); } assert.throws(() => perBaseColor({ light: "#010203" } as never), /dark/); }); test("fittedHex: a published hex fitted per base; anything else is undefined", () => { assert.equal(fittedHex("#F4C2D7"), perBaseColor(resolveAccent("#f4c2d7"))); // A named accent's published on-dark hex, with no id beside it (a visitor's // added archive): fitted like any custom hex, readable on every ground. const brass = fittedHex(ACCENTS.brass.onDark)!; for (const base of BASE_GROUND_IDS) { assert.ok(contrastRatio(paint(brass, base), BASE_GROUNDS[base]) >= 4.5, base); } for (const v of [undefined, "", "red", "url(https://x.example/p.png)", "var(--brand)", 7]) { assert.equal(fittedHex(v), undefined, String(v)); } }); test("siteColor: no accent is the site's chart colour", () => { assert.equal(siteColor({}, seriesColor(0)), "var(--chart-1)"); assert.equal(siteColor({}, "var(--chart-4)"), "var(--chart-4)"); assert.equal(siteColor({}, seriesColor(7)), seriesColor(7)); }); test("siteColor: anything unrecognised falls through, never into the style", () => { // Not an accent id: the hex, fitted (or the chart colour), stands. assert.equal( siteColor({ accentId: "gold", accent: "#112233" }, seriesColor(0)), perBaseColor(resolveAccent("#112233")), ); assert.equal(siteColor({ accentId: "red);x:y" }, seriesColor(1)), seriesColor(1)); // Not a #rrggbb. assert.equal(siteColor({ accent: "red" }, seriesColor(1)), seriesColor(1)); assert.equal(siteColor({ accent: "var(--brand)" }, seriesColor(0)), seriesColor(0)); }); // ── siteChartColors: the chart colour ──────────────────────────────────────── const named = (accentId: string) => ({ accentId }); const none = {}; test("siteChartColors: each family takes its hue's slot of --chart-1..6", () => { // --chart-1..6 is blue, green, violet, amber, magenta, rust on every base. assert.deepEqual(ACCENT_CHART_SLOT, { blue: 0, green: 1, violet: 2, brass: 3, sakura: 4, vermilion: 5, }); // Today's homepage order with the operator's mapping (2026-09-27). assert.deepEqual( siteChartColors(["brass", "sakura", "violet", "blue", "green"].map(named)), ["var(--chart-4)", "var(--chart-5)", "var(--chart-3)", "var(--chart-1)", "var(--chart-2)"], ); // The colour follows the site, not its place: Hasanalyzer and Rekietalyzer // swapped keep their colours. assert.deepEqual( siteChartColors(["brass", "sakura", "violet", "green", "blue"].map(named)), ["var(--chart-4)", "var(--chart-5)", "var(--chart-3)", "var(--chart-2)", "var(--chart-1)"], ); // Jasolyzer (Vermilion) published beside them, wherever it ranks: the rust // slot, not the golden angle's hue 328 beside Anilyzer's magenta. const five = ["brass", "sakura", "blue", "violet", "green"]; for (let at = 0; at <= five.length; at++) { const ids = [...five.slice(0, at), "vermilion", ...five.slice(at)]; const out = siteChartColors(ids.map(named)); assert.equal(out[at], "var(--chart-6)", `vermilion at ${at}`); } }); test("seriesColor: six validated slots, then the golden angle", () => { assert.equal(CHART_SLOTS, 6); assert.deepEqual( Array.from({ length: 6 }, (_, i) => seriesColor(i)), [1, 2, 3, 4, 5, 6].map((n) => `var(--chart-${n})`), ); assert.match(seriesColor(6), /^hsl\(/); }); test("siteChartColors: no accents is seriesColor(0..n-1), as before", () => { const sites = Array.from({ length: 8 }, () => none); assert.deepEqual(siteChartColors(sites), sites.map((_, i) => seriesColor(i))); assert.deepEqual(siteChartColors([]), []); }); test("siteChartColors: no family (signal, a custom hex, an unknown id) falls back to its index", () => { assert.deepEqual( siteChartColors([named("signal"), { accentId: undefined }, named("gold")]), [seriesColor(0), seriesColor(1), seriesColor(2)], ); }); test("siteChartColors: two sites in one family — the later takes a free slot", () => { // Both blue: the first keeps chart-1; the second's own index (1) is free. assert.deepEqual(siteChartColors([named("blue"), named("blue")]), [ "var(--chart-1)", "var(--chart-2)", ]); // Both brass, and the later one's own index (3) is brass's: the lowest free. assert.deepEqual( siteChartColors([named("brass"), none, none, named("brass")]), ["var(--chart-4)", "var(--chart-2)", "var(--chart-3)", "var(--chart-1)"], ); }); test("siteChartColors: a fallback whose index a family holds takes the lowest free slot", () => { // Site 0 has no accent; site 1 is blue and holds chart-1 (index 0). assert.deepEqual(siteChartColors([none, named("blue")]), ["var(--chart-2)", "var(--chart-1)"]); // Five sites: 0 none (chart-1 held by blue), 3 none (chart-4 held by brass). assert.deepEqual( siteChartColors([none, named("brass"), named("blue"), none, named("sakura")]), ["var(--chart-2)", "var(--chart-4)", "var(--chart-1)", "var(--chart-3)", "var(--chart-5)"], ); // A sixth site with no free family takes the sixth slot; a seventh, past // the palette, the golden-angle hues, never one already worn. const seven = siteChartColors([ named("blue"), named("green"), named("violet"), named("brass"), named("sakura"), named("blue"), named("vermilion"), ]); assert.equal(seven[6], "var(--chart-6)"); assert.equal(seven[5], seriesColor(6)); }); test("siteChartColors: any six sites wear the six validated slots, each once", () => { // Whatever the accents — families, duplicates, Signal, a custom hex, none — // up to six published sites never fall past --chart-6 to the golden angle // (whose sixth hue was within a degree of --chart-5's magenta). const ids = [...ACCENT_IDS, undefined]; const validated = [1, 2, 3, 4, 5, 6].map((n) => `var(--chart-${n})`); let seed = 11; const rand = () => (seed = (seed * 1103515245 + 12345) % 2 ** 31) / 2 ** 31; for (let run = 0; run < 500; run++) { const n = 1 + Math.floor(rand() * 6); const sites = Array.from({ length: n }, () => ({ accentId: ids[Math.floor(rand() * ids.length)], })); const out = siteChartColors(sites); assert.equal(new Set(out).size, n, JSON.stringify(sites)); for (const c of out) assert.ok(validated.includes(c), `${c} for ${JSON.stringify(sites)}`); if (n === 6) assert.deepEqual([...out].sort(), validated); } }); test("siteChartColors: never two sites in one colour", () => { const ids = [...ACCENT_IDS, undefined]; // A deterministic walk over many mixes of families, duplicates and none. let seed = 7; const rand = () => (seed = (seed * 1103515245 + 12345) % 2 ** 31) / 2 ** 31; for (let run = 0; run < 500; run++) { const n = 1 + Math.floor(rand() * 9); const sites = Array.from({ length: n }, () => ({ accentId: ids[Math.floor(rand() * ids.length)], })); const out = siteChartColors(sites); assert.equal(out.length, n); assert.equal(new Set(out).size, n, JSON.stringify(sites)); // The FIRST site of each family wears its family's slot. const seen = new Set(); sites.forEach((s, i) => { const slot = s.accentId ? ACCENT_CHART_SLOT[s.accentId as keyof typeof ACCENT_CHART_SLOT] : undefined; if (slot === undefined || seen.has(s.accentId!)) return; seen.add(s.accentId!); assert.equal(out[i], seriesColor(slot)); }); } });