// theme.ts owns the named colour schemes the app can be displayed in. import * as canvas from "./canvas.js"; export type PlayerColors = { stone: string; home: string }; export type Chrome = { pageBg: string; panelBg: string; text: string; mutedText: string; titleAccent: string; swatchBorder: string; buttonBg: string; buttonText: string; buttonBorder: string; danger: string; dangerText: string; success: string; successText: string; }; export type Theme = { id: string; // stable, stored in localStorage name: string; // shown in the picker board: canvas.Palette; players: readonly PlayerColors[]; chrome: Chrome; }; // Byte-for-byte today's look, so the default start state is a no-op change. const goban: Theme = { id: "goban", name: "Goban", board: { boardFill: "#dcb35c", line: "#3a2e1f", stoneOutline: "rgba(0, 0, 0, 0.4)", provisionalOutline: "rgba(255, 255, 255, 0.9)", emblemFill: "#e8b923", emblemStroke: "#3a2e1f", }, players: [ { stone: "black", home: "#2b2b3a" }, { stone: "white", home: "#f2ede0" }, { stone: "#e63946", home: "#c1121f" }, { stone: "#457b9d", home: "#2a5d7c" }, ], chrome: { pageBg: "#111", panelBg: "#16161a", text: "rgba(255, 255, 255, 0.85)", mutedText: "rgba(255, 255, 255, 0.6)", titleAccent: "#ff6fb3", swatchBorder: "rgba(255, 255, 255, 0.35)", buttonBg: "#2a2a30", buttonText: "rgba(255, 255, 255, 0.85)", buttonBorder: "rgba(255, 255, 255, 0.25)", // Deliberately not #e63946, which is player 3's stone colour in this theme. danger: "#b3202c", dangerText: "#fff1f1", success: "#2f9e44", successText: "#eafbea", }, }; const kanagawa: Theme = { id: "kanagawa", name: "Kanagawa", board: { boardFill: "#1F1F28", line: "#54546D", stoneOutline: "rgba(22, 22, 29, 0.6)", provisionalOutline: "#DCD7BA", emblemFill: "#E6C384", emblemStroke: "#16161D", }, players: [ { stone: "#7E9CD8", home: "#4C6C9C" }, { stone: "#D27E99", home: "#9E5C73" }, { stone: "#98BB6C", home: "#6E8F4A" }, { stone: "#FFA066", home: "#C4703F" }, ], chrome: { pageBg: "#16161D", panelBg: "#1F1F28", text: "#DCD7BA", mutedText: "#727169", titleAccent: "#D27E99", swatchBorder: "rgba(220, 215, 186, 0.35)", buttonBg: "#2A2A37", buttonText: "#DCD7BA", buttonBorder: "#54546D", danger: "#C34043", dangerText: "#DCD7BA", // Deliberately not #98BB6C/#6E8F4A, which are player 3's stone/home // colours in this theme. success: "#76946A", successText: "#DCD7BA", }, }; const catppuccinMocha: Theme = { id: "catppuccin-mocha", name: "Catppuccin Mocha", board: { boardFill: "#1e1e2e", line: "#45475a", stoneOutline: "rgba(17, 17, 27, 0.55)", provisionalOutline: "#cdd6f4", emblemFill: "#f9e2af", emblemStroke: "#11111b", }, players: [ { stone: "#89b4fa", home: "#5c7fc9" }, { stone: "#f5c2e7", home: "#c48bb6" }, { stone: "#a6e3a1", home: "#6fae6b" }, { stone: "#fab387", home: "#c88257" }, ], chrome: { pageBg: "#11111b", panelBg: "#181825", text: "#cdd6f4", mutedText: "#a6adc8", titleAccent: "#cba6f7", swatchBorder: "rgba(205, 214, 244, 0.35)", buttonBg: "#313244", buttonText: "#cdd6f4", buttonBorder: "#45475a", // Mocha's red is light, so it takes dark text. danger: "#f38ba8", dangerText: "#11111b", // Mocha's teal, not #a6e3a1/#6fae6b (player 3's stone/home colours in // this theme) -- also light, so it takes dark text for the same reason // danger does. success: "#94e2d5", successText: "#11111b", }, }; export const themes: readonly Theme[] = [goban, kanagawa, catppuccinMocha]; export const defaultTheme: Theme = themes[0]; export const byId = (id: string): Theme | null => themes.find((theme) => theme.id === id) ?? null; // A theme with fewer palettes than players degrades to repeated colours // rather than crashing or rendering undefined; all three shipped themes // supply 4, so this only matters for a hypothetical future theme with fewer. export const playerColors = (theme: Theme, index: number): PlayerColors => theme.players[index % theme.players.length];