// Theme constants shared by the admin Theme page and its server action. Kept in // a plain module because a "use server" file may only export async functions. // Selectable body fonts. Web-safe stacks render with no extra request; the few // Google fonts load via a ThemeVars injects when chosen. export const FONTS: Record< string, { label: string; stack: string; google?: string } > = { nunito: { label: "Nunito (default)", stack: 'var(--font-nunito), "Nunito", sans-serif', }, system: { label: "System", stack: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif', }, inter: { label: "Inter", stack: '"Inter", sans-serif', google: "Inter:wght@400;600;700;800", }, poppins: { label: "Poppins", stack: '"Poppins", sans-serif', google: "Poppins:wght@400;600;700;800", }, roboto: { label: "Roboto", stack: '"Roboto", sans-serif', google: "Roboto:wght@400;500;700", }, georgia: { label: "Georgia (serif)", stack: 'Georgia, "Times New Roman", serif', }, trebuchet: { label: "Trebuchet", stack: '"Trebuchet MS", Verdana, sans-serif', }, verdana: { label: "Verdana", stack: "Verdana, Geneva, sans-serif" }, comic: { label: "Comic (retro)", stack: '"Comic Sans MS", "Comic Sans", cursive', }, mono: { label: "Monospace", stack: '"Courier New", ui-monospace, monospace' }, }; // The colour settings ThemeVars injects into :root. Editing these recolours the // whole site live (ThemeVars reads them on every request). export const THEME_COLOR_KEYS = [ "color_primary", "color_background", "color_surface", "color_dropdown", "color_navbar", "color_navbar_text", "color_text", "color_text_muted", "color_accent", "color_success", "color_warning", "color_error", "color_info", "button_primary_color", "button_text_color", "button_secondary_color", "button_secondary_text_color", "button_danger_color", "button_danger_text_color", "button_outline_color", "button_outline_text_color", "link_color", "link_hover_color", "border_color", "panel_border_color", "gradient_from", "gradient_to", ] as const; export type ThemeMode = "light" | "dark"; export type ThemeColorKey = (typeof THEME_COLOR_KEYS)[number]; export type ThemePalette = Record; export type ThemePreset = Record; // Curated palettes (faithful to the atom theme presets). Each is a full set of // the colour keys above so applying one fully reskins the site. const LEGACY_PALETTES: Record> = { "Atom (golden)": { color_primary: "#f59e0b", color_background: "#f8fafc", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#1e293b", color_text: "#0f172a", color_text_muted: "#64748b", color_accent: "#10b981", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#f59e0b", button_text_color: "#1e293b", border_color: "#eeb425", }, Midnight: { color_primary: "#eeb425", color_background: "#0f1117", color_surface: "#171a21", color_dropdown: "#1f242d", color_navbar: "#171a21", color_navbar_text: "#e5e7eb", color_text: "#e5e7eb", color_text_muted: "#9ca3af", color_accent: "#22c55e", color_success: "#22c55e", color_warning: "#facc15", color_error: "#f87171", color_info: "#38bdf8", button_primary_color: "#eeb425", button_text_color: "#1a1a2e", border_color: "#eeb425", }, Ocean: { color_primary: "#0ea5e9", color_background: "#f0f9ff", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#0c4a6e", color_text: "#082f49", color_text_muted: "#64748b", color_accent: "#14b8a6", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#0ea5e9", button_text_color: "#ffffff", border_color: "#38bdf8", }, Forest: { color_primary: "#16a34a", color_background: "#f3faf4", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#14532d", color_text: "#052e16", color_text_muted: "#5f6b63", color_accent: "#84cc16", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#16a34a", button_text_color: "#ffffff", border_color: "#22c55e", }, Sunset: { color_primary: "#f97316", color_background: "#fff7ed", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#7c2d12", color_text: "#431407", color_text_muted: "#78716c", color_accent: "#ec4899", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#f97316", button_text_color: "#ffffff", border_color: "#fb923c", }, Candy: { color_primary: "#ec4899", color_background: "#fdf2f8", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#831843", color_text: "#500724", color_text_muted: "#9d6b80", color_accent: "#a855f7", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#ec4899", button_text_color: "#ffffff", border_color: "#f472b6", }, Galaxy: { color_primary: "#a855f7", color_background: "#0b0a1f", color_surface: "#171430", color_dropdown: "#211d44", color_navbar: "#171430", color_navbar_text: "#e9e5ff", color_text: "#e9e5ff", color_text_muted: "#a39dcf", color_accent: "#22d3ee", color_success: "#22c55e", color_warning: "#facc15", color_error: "#f87171", color_info: "#38bdf8", button_primary_color: "#a855f7", button_text_color: "#ffffff", border_color: "#7c3aed", }, Royal: { color_primary: "#4f46e5", color_background: "#f5f6ff", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#1e1b4b", color_text: "#1e1b4b", color_text_muted: "#636681", color_accent: "#d97706", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#4f46e5", button_text_color: "#ffffff", border_color: "#818cf8", }, Cyberpunk: { color_primary: "#f0db4f", color_background: "#0d0221", color_surface: "#190933", color_dropdown: "#241046", color_navbar: "#190933", color_navbar_text: "#f0db4f", color_text: "#f5e9ff", color_text_muted: "#b08fd6", color_accent: "#ff2a6d", color_success: "#22c55e", color_warning: "#facc15", color_error: "#f87171", color_info: "#38bdf8", button_primary_color: "#ff2a6d", button_text_color: "#ffffff", border_color: "#05d9e8", }, Neon: { color_primary: "#39ff14", color_background: "#0a0a0a", color_surface: "#141414", color_dropdown: "#1d1d1d", color_navbar: "#141414", color_navbar_text: "#39ff14", color_text: "#e8ffe8", color_text_muted: "#7fae7f", color_accent: "#ff00ff", color_success: "#22c55e", color_warning: "#facc15", color_error: "#f87171", color_info: "#38bdf8", button_primary_color: "#39ff14", button_text_color: "#0a0a0a", border_color: "#39ff14", }, Coffee: { color_primary: "#a16207", color_background: "#f5efe6", color_surface: "#fffaf2", color_dropdown: "#fffaf2", color_navbar: "#fffaf2", color_navbar_text: "#3f2d1c", color_text: "#3f2d1c", color_text_muted: "#8a7560", color_accent: "#15803d", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#a16207", button_text_color: "#ffffff", border_color: "#ca8a04", }, Arctic: { color_primary: "#0891b2", color_background: "#eef6fb", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#0e3a4f", color_text: "#0e3a4f", color_text_muted: "#5b7585", color_accent: "#6366f1", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#0891b2", button_text_color: "#ffffff", border_color: "#67e8f9", }, Christmas: { color_primary: "#dc2626", color_background: "#f4faf4", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#0f3d24", color_navbar_text: "#ffffff", color_text: "#14321f", color_text_muted: "#5f7a68", color_accent: "#15803d", color_success: "#22c55e", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#dc2626", button_text_color: "#ffffff", border_color: "#16a34a", }, Dusk: { color_primary: "#e8a33d", color_background: "#1b2230", color_surface: "#232c3d", color_dropdown: "#2b3548", color_navbar: "#232c3d", color_navbar_text: "#dfe5ef", color_text: "#dfe5ef", color_text_muted: "#94a0b5", color_accent: "#5eb0c9", color_success: "#22c55e", color_warning: "#facc15", color_error: "#f87171", color_info: "#38bdf8", button_primary_color: "#e8a33d", button_text_color: "#1b2230", border_color: "#e8a33d", }, }; const LIGHT_BASE: ThemePalette = { color_primary: "#f59e0b", color_background: "#f8fafc", color_surface: "#ffffff", color_dropdown: "#ffffff", color_navbar: "#ffffff", color_navbar_text: "#1e293b", color_text: "#0f172a", color_text_muted: "#64748b", color_accent: "#10b981", color_success: "#16a34a", color_warning: "#eab308", color_error: "#ef4444", color_info: "#0ea5e9", button_primary_color: "#f59e0b", button_text_color: "#1e293b", button_secondary_color: "#22c55e", button_secondary_text_color: "#ffffff", button_danger_color: "#ef4444", button_danger_text_color: "#ffffff", button_outline_color: "#eeb425", button_outline_text_color: "#1a1a2e", link_color: "#b77900", link_hover_color: "#8a5b00", border_color: "#eeb425", panel_border_color: "#e9b124", gradient_from: "#f59e0b", gradient_to: "#10b981", }; const DARK_BASE: ThemePalette = { ...LIGHT_BASE, color_background: "#0b0d14", color_surface: "#141820", color_dropdown: "#1d222b", color_navbar: "#141820", color_navbar_text: "#e5e7eb", color_text: "#e5e7eb", color_text_muted: "#94a3b8", button_text_color: "#0b0d14", button_secondary_text_color: "#08130b", button_danger_text_color: "#160606", button_outline_text_color: "#f8fafc", link_color: "#f6c453", link_hover_color: "#ffe08a", }; const DARK_FIRST = new Set(["Midnight", "Galaxy", "Cyberpunk", "Neon", "Dusk"]); function brandValues(source: Partial): Partial { return { color_primary: source.color_primary, color_accent: source.color_accent, color_success: source.color_success, color_warning: source.color_warning, color_error: source.color_error, color_info: source.color_info, button_primary_color: source.button_primary_color ?? source.color_primary, border_color: source.border_color ?? source.color_primary, panel_border_color: source.panel_border_color ?? source.border_color ?? source.color_primary, gradient_from: source.color_primary, gradient_to: source.color_accent, button_outline_color: source.border_color ?? source.color_primary, }; } function completePalette( base: ThemePalette, values: Partial, ): ThemePalette { const merged = { ...base, ...values }; merged.button_secondary_color = values.button_secondary_color ?? merged.color_accent; merged.button_outline_color = values.button_outline_color ?? merged.border_color; merged.panel_border_color = values.panel_border_color ?? merged.border_color; merged.gradient_from = values.gradient_from ?? merged.color_primary; merged.gradient_to = values.gradient_to ?? merged.color_accent; return merged; } export const PRESETS: Record = Object.fromEntries( Object.entries(LEGACY_PALETTES).map(([name, palette]) => { const light = DARK_FIRST.has(name) ? completePalette(LIGHT_BASE, brandValues(palette)) : completePalette(LIGHT_BASE, palette); const dark = DARK_FIRST.has(name) ? completePalette(DARK_BASE, palette) : completePalette(DARK_BASE, brandValues(palette)); return [name, { light, dark } satisfies ThemePreset]; }), );