import "./themes"; const KEY = "codecraft.theme"; const MODE_KEY = "codecraft.themeMode"; /** * Base themes. Each has a dark and a light variant (CSS files * `themes/-dark.css` / `-light.css`); the mode picks the variant. */ export const THEMES = [ { id: "gruvbox", name: "Gruvbox", source: "morhetz/gruvbox", license: "MIT" }, { id: "ash", name: "Ash", source: "zink/trilium-themes", license: "MIT" }, { id: "harbor", name: "Harbor", source: "HANCORE-linux/omarchy-harbor-theme", license: "MIT" }, { id: "coffee", name: "Coffee", source: "megabyte0x/omarchy-coffee-theme", license: "MIT" }, { id: "solarized", name: "Solarized", source: "Ethan Schoonover", license: "MIT" }, { id: "tokyonight", name: "Tokyo Night", source: "folke/tokyonight.nvim", license: "Apache-2.0" }, { id: "nord", name: "Nord", source: "nordtheme/nord", license: "MIT" }, { id: "vscode", name: "VS Code", source: "microsoft/vscode", license: "MIT" }, { id: "github", name: "GitHub", source: "primer/github-vscode-theme", license: "MIT" }, { id: "catppuccin", name: "Catppuccin", source: "catppuccin/catppuccin", license: "MIT" }, { id: "one", name: "One Dark / Light", source: "atom/atom", license: "MIT" }, { id: "dracula", name: "Dracula / Alucard", source: "dracula/dracula-theme", license: "MIT" }, { id: "rosepine", name: "Rosé Pine", source: "rose-pine/rose-pine-theme", license: "MIT" }, { id: "everforest", name: "Everforest", source: "sainnhe/everforest", license: "MIT" }, { id: "kanagawa", name: "Kanagawa", source: "rebelot/kanagawa.nvim", license: "MIT" }, ] as const; export type Theme = (typeof THEMES)[number]["id"]; export type ThemeMode = "auto" | "light" | "dark"; export const THEME_MODES: ThemeMode[] = ["auto", "light", "dark"]; export function getTheme(): Theme { const saved = localStorage.getItem(KEY) ?? ""; // Migrate old values like "gruvbox-dark", "dark", "system". const base = saved.replace(/-(dark|light)$/, ""); return THEMES.some((t) => t.id === base) ? (base as Theme) : "gruvbox"; } export function getThemeMode(): ThemeMode { const saved = localStorage.getItem(MODE_KEY); if (saved === "light" || saved === "dark" || saved === "auto") return saved; // Migrate the old combined setting. const old = localStorage.getItem(KEY) ?? ""; if (old.endsWith("dark")) return "dark"; if (old.endsWith("light")) return "light"; return "auto"; } const media = matchMedia("(prefers-color-scheme: dark)"); const listeners = new Set<() => void>(); export function onThemeChange(fn: () => void) { listeners.add(fn); return () => void listeners.delete(fn); } export function applyTheme(theme: Theme = getTheme(), mode: ThemeMode = getThemeMode()) { localStorage.setItem(KEY, theme); localStorage.setItem(MODE_KEY, mode); const dark = mode === "auto" ? media.matches : mode === "dark"; const root = document.documentElement; root.dataset.theme = `${theme}-${dark ? "dark" : "light"}`; root.dataset.mode = dark ? "dark" : "light"; const color = getComputedStyle(root).getPropertyValue("--theme-color").trim(); document.querySelector('meta[name="theme-color"]')?.setAttribute("content", color || "#282828"); listeners.forEach((fn) => fn()); } /** Cycle auto → light → dark (quick switcher). */ export function cycleThemeMode() { const modes = THEME_MODES; applyTheme(getTheme(), modes[(modes.indexOf(getThemeMode()) + 1) % modes.length]); } media.addEventListener("change", () => { if (getThemeMode() === "auto") applyTheme(); });