repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.8 KB · 82 lines · JavaScript Raw History
 1// The chart theme every dashboard on this site draws with.
 2//
 3// This file is duplicated byte for byte in analytics, logging and status the
 4// same way shipper.go and session.go are. A colour that drifts between two of
 5// these dashboards is visible, and there is no shared bundle to put it in.
 6//
 7// The series colours are punchier than the UI palette on purpose. Chrome can be
 8// muted because nothing depends on telling two borders apart, and a doughnut
 9// slice does. Same hues as the rest of the site, more chroma.
10//
11// The order is validated rather than chosen by eye. Every adjacent pair clears
12// the normal vision floor and sits in the 6 to 8 band for deuteranopia and
13// protanopia, which is only acceptable because every chart here carries a
14// legend, so colour is never the only thing telling two series apart.
15// Reordering these means checking them again.
16
17export const series = ["#57b378", "#d8a83e", "#dc6a4b", "#63a9c9"];
18
19// Anything past the fourth category folds into this rather than getting a
20// generated hue, since a fifth and sixth colour out of these five is not
21// distinguishable from the ones already used.
22export const other = "#7d7469";
23
24export const palette = [...series, other];
25
26// Status is a separate job from identity, so these are keyed by name and never
27// handed out in order.
28export const status = {
29  good: series[0],
30  warn: series[1],
31  bad: series[2],
32  info: series[3],
33  muted: other,
34};
35
36export const ink = {
37  ticks: "#9a9188",
38  grid: "rgba(107, 158, 120, 0.14)",
39  border: "rgba(107, 158, 120, 0.3)",
40  legend: "#b3aba2",
41  title: "#ede8e0",
42  body: "#ddd7cd",
43  surface: "rgba(9, 8, 6, 0.96)",
44};
45
46export const fontStack =
47  "'Monaspace Argon', ui-monospace, 'Cascadia Code', Consolas, 'SF Mono', Menlo, monospace";
48
49export const tooltipStyle = {
50  backgroundColor: ink.surface,
51  borderColor: "rgba(107, 158, 120, 0.5)",
52  borderWidth: 1,
53  titleColor: ink.title,
54  bodyColor: ink.body,
55  titleFont: { family: fontStack, size: 11 },
56  bodyFont: { family: fontStack, size: 11 },
57  padding: 10,
58  displayColors: true,
59  boxWidth: 8,
60  boxHeight: 8,
61};
62
63// Applied once per bundle. Chart.js reads these at construction, so it has to
64// run before any chart is built.
65export function applyDefaults(Chart) {
66  Chart.defaults.color = ink.ticks;
67  Chart.defaults.borderColor = ink.grid;
68  Chart.defaults.font.family = fontStack;
69  Chart.defaults.font.size = 11;
70}
71
72// Folds a ranked list down to the colours that are actually distinguishable,
73// summing the tail into one "other" row.
74export function foldToPalette(rows, limit = series.length) {
75  if (rows.length <= limit + 1) return rows;
76  const head = rows.slice(0, limit);
77  const tail = rows.slice(limit);
78  const sum = tail.reduce((n, r) => n + (r.count || 0), 0);
79  if (sum <= 0) return head;
80  return [...head, { label: "other", count: sum }];
81}