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));
});
}
});