diff --git a/src/lib/theme.ts b/src/lib/theme.ts new file mode 100644 index 0000000..abc85b3 --- /dev/null +++ b/src/lib/theme.ts @@ -0,0 +1,361 @@ +/** + * Theme system — dynamic CSS variable manager + * Each theme maps to a full set of CSS custom properties. + */ + +export interface ThemeColors { + background: string; + foreground: string; + card: string; + cardForeground: string; + popover: string; + popoverForeground: string; + primary: string; + primaryForeground: string; + secondary: string; + secondaryForeground: string; + muted: string; + mutedForeground: string; + accent: string; + accentForeground: string; + destructive: string; + border: string; + input: string; + ring: string; + sidebar: string; + sidebarForeground: string; + sidebarPrimary: string; + sidebarPrimaryForeground: string; + sidebarAccent: string; + sidebarAccentForeground: string; + sidebarBorder: string; + sidebarRing: string; +} + +export interface ThemePreset { + id: string; + name: string; + description: string; + colors: ThemeColors; + radius: string; + isDark: boolean; +} + +export const themePresets: ThemePreset[] = [ + { + id: "pure-white", + name: "Pure White", + description: "Minimal white with crisp blacks and subtle borders", + isDark: false, + radius: "0.875rem", + colors: { + background: "#ffffff", + foreground: "#111111", + card: "#ffffff", + cardForeground: "#111111", + popover: "#ffffff", + popoverForeground: "#111111", + primary: "#111111", + primaryForeground: "#ffffff", + secondary: "#f5f5f5", + secondaryForeground: "#111111", + muted: "#f5f5f5", + mutedForeground: "#737373", + accent: "#f5f5f5", + accentForeground: "#111111", + destructive: "#ef4444", + border: "#e8e8e8", + input: "#e8e8e8", + ring: "#111111", + sidebar: "#fafafa", + sidebarForeground: "#111111", + sidebarPrimary: "#111111", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#f5f5f5", + sidebarAccentForeground: "#111111", + sidebarBorder: "#e8e8e8", + sidebarRing: "#111111", + }, + }, + { + id: "soft-gray", + name: "Soft Gray", + description: "Warm gray tones with gentle contrast", + isDark: false, + radius: "0.875rem", + colors: { + background: "#f8f8f7", + foreground: "#1c1c1c", + card: "#ffffff", + cardForeground: "#1c1c1c", + popover: "#ffffff", + popoverForeground: "#1c1c1c", + primary: "#1c1c1c", + primaryForeground: "#ffffff", + secondary: "#eeedec", + secondaryForeground: "#1c1c1c", + muted: "#eeedec", + mutedForeground: "#6e6d6c", + accent: "#eeedec", + accentForeground: "#1c1c1c", + destructive: "#ef4444", + border: "#e4e3e2", + input: "#e4e3e2", + ring: "#1c1c1c", + sidebar: "#f3f2f1", + sidebarForeground: "#1c1c1c", + sidebarPrimary: "#1c1c1c", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#eeedec", + sidebarAccentForeground: "#1c1c1c", + sidebarBorder: "#e4e3e2", + sidebarRing: "#1c1c1c", + }, + }, + { + id: "cool-slate", + name: "Cool Slate", + description: "Icy blue-gray palette inspired by modern dashboards", + isDark: false, + radius: "0.875rem", + colors: { + background: "#f6f7f9", + foreground: "#1a1d23", + card: "#ffffff", + cardForeground: "#1a1d23", + popover: "#ffffff", + popoverForeground: "#1a1d23", + primary: "#3b5bdb", + primaryForeground: "#ffffff", + secondary: "#e9ecf1", + secondaryForeground: "#1a1d23", + muted: "#e9ecf1", + mutedForeground: "#5c6370", + accent: "#e9ecf1", + accentForeground: "#1a1d23", + destructive: "#ef4444", + border: "#dde0e6", + input: "#dde0e6", + ring: "#3b5bdb", + sidebar: "#f0f1f4", + sidebarForeground: "#1a1d23", + sidebarPrimary: "#3b5bdb", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#e9ecf1", + sidebarAccentForeground: "#1a1d23", + sidebarBorder: "#dde0e6", + sidebarRing: "#3b5bdb", + }, + }, + { + id: "warm-cream", + name: "Warm Cream", + description: "Cozy off-white with warm amber undertones", + isDark: false, + radius: "0.875rem", + colors: { + background: "#fdfbf7", + foreground: "#1c1917", + card: "#ffffff", + cardForeground: "#1c1917", + popover: "#ffffff", + popoverForeground: "#1c1917", + primary: "#b45309", + primaryForeground: "#ffffff", + secondary: "#f5f0e8", + secondaryForeground: "#1c1917", + muted: "#f5f0e8", + mutedForeground: "#78716c", + accent: "#f5f0e8", + accentForeground: "#1c1917", + destructive: "#ef4444", + border: "#e7e0d5", + input: "#e7e0d5", + ring: "#b45309", + sidebar: "#f8f5f0", + sidebarForeground: "#1c1917", + sidebarPrimary: "#b45309", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#f5f0e8", + sidebarAccentForeground: "#1c1917", + sidebarBorder: "#e7e0d5", + sidebarRing: "#b45309", + }, + }, + { + id: "forest", + name: "Forest", + description: "Nature-inspired green tones for calm focus", + isDark: false, + radius: "0.875rem", + colors: { + background: "#f6f7f4", + foreground: "#1a1c18", + card: "#ffffff", + cardForeground: "#1a1c18", + popover: "#ffffff", + popoverForeground: "#1a1c18", + primary: "#15803d", + primaryForeground: "#ffffff", + secondary: "#e8ebe4", + secondaryForeground: "#1a1c18", + muted: "#e8ebe4", + mutedForeground: "#5f6358", + accent: "#e8ebe4", + accentForeground: "#1a1c18", + destructive: "#ef4444", + border: "#dde1d8", + input: "#dde1d8", + ring: "#15803d", + sidebar: "#eff1ec", + sidebarForeground: "#1a1c18", + sidebarPrimary: "#15803d", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#e8ebe4", + sidebarAccentForeground: "#1a1c18", + sidebarBorder: "#dde1d8", + sidebarRing: "#15803d", + }, + }, + { + id: "dark", + name: "Dark", + description: "Clean dark mode with balanced contrast", + isDark: true, + radius: "0.875rem", + colors: { + background: "#0f1115", + foreground: "#ececf1", + card: "#16181d", + cardForeground: "#ececf1", + popover: "#16181d", + popoverForeground: "#ececf1", + primary: "#5E6AD2", + primaryForeground: "#ffffff", + secondary: "#1f2128", + secondaryForeground: "#ececf1", + muted: "#1f2128", + mutedForeground: "#8a8f98", + accent: "#1f2128", + accentForeground: "#ececf1", + destructive: "#ef4444", + border: "rgba(255,255,255,0.08)", + input: "rgba(255,255,255,0.08)", + ring: "#5E6AD2", + sidebar: "#0f1115", + sidebarForeground: "#ececf1", + sidebarPrimary: "#5E6AD2", + sidebarPrimaryForeground: "#ffffff", + sidebarAccent: "#1f2128", + sidebarAccentForeground: "#ececf1", + sidebarBorder: "rgba(255,255,255,0.08)", + sidebarRing: "#5E6AD2", + }, + }, + { + id: "midnight", + name: "Midnight", + description: "Deep navy dark theme for late-night sessions", + isDark: true, + radius: "0.875rem", + colors: { + background: "#0a0e1a", + foreground: "#d8dce8", + card: "#111827", + cardForeground: "#d8dce8", + popover: "#111827", + popoverForeground: "#d8dce8", + primary: "#60a5fa", + primaryForeground: "#0a0e1a", + secondary: "#1e293b", + secondaryForeground: "#d8dce8", + muted: "#1e293b", + mutedForeground: "#7a8299", + accent: "#1e293b", + accentForeground: "#d8dce8", + destructive: "#f87171", + border: "rgba(255,255,255,0.07)", + input: "rgba(255,255,255,0.07)", + ring: "#60a5fa", + sidebar: "#0d1220", + sidebarForeground: "#d8dce8", + sidebarPrimary: "#60a5fa", + sidebarPrimaryForeground: "#0a0e1a", + sidebarAccent: "#1e293b", + sidebarAccentForeground: "#d8dce8", + sidebarBorder: "rgba(255,255,255,0.07)", + sidebarRing: "#60a5fa", + }, + }, +]; + +export const defaultThemeId = "pure-white"; + +const STORAGE_KEY = "gitdataai-theme"; + +export function getSavedThemeId(): string { + try { + return localStorage.getItem(STORAGE_KEY) || defaultThemeId; + } catch { + return defaultThemeId; + } +} + +export function saveThemeId(id: string) { + try { + localStorage.setItem(STORAGE_KEY, id); + } catch { + /* ignore */ + } +} + +export function applyTheme(preset: ThemePreset) { + const root = document.documentElement; + const c = preset.colors; + + root.style.setProperty("--background", c.background); + root.style.setProperty("--foreground", c.foreground); + root.style.setProperty("--card", c.card); + root.style.setProperty("--card-foreground", c.cardForeground); + root.style.setProperty("--popover", c.popover); + root.style.setProperty("--popover-foreground", c.popoverForeground); + root.style.setProperty("--primary", c.primary); + root.style.setProperty("--primary-foreground", c.primaryForeground); + root.style.setProperty("--secondary", c.secondary); + root.style.setProperty("--secondary-foreground", c.secondaryForeground); + root.style.setProperty("--muted", c.muted); + root.style.setProperty("--muted-foreground", c.mutedForeground); + root.style.setProperty("--accent", c.accent); + root.style.setProperty("--accent-foreground", c.accentForeground); + root.style.setProperty("--destructive", c.destructive); + root.style.setProperty("--border", c.border); + root.style.setProperty("--input", c.input); + root.style.setProperty("--ring", c.ring); + root.style.setProperty("--radius", preset.radius); + root.style.setProperty("--sidebar", c.sidebar); + root.style.setProperty("--sidebar-foreground", c.sidebarForeground); + root.style.setProperty("--sidebar-primary", c.sidebarPrimary); + root.style.setProperty("--sidebar-primary-foreground", c.sidebarPrimaryForeground); + root.style.setProperty("--sidebar-accent", c.sidebarAccent); + root.style.setProperty("--sidebar-accent-foreground", c.sidebarAccentForeground); + root.style.setProperty("--sidebar-border", c.sidebarBorder); + root.style.setProperty("--sidebar-ring", c.sidebarRing); + + // Chart colors — derive from primary/muted + root.style.setProperty("--chart-1", c.primary); + root.style.setProperty("--chart-2", c.mutedForeground); + root.style.setProperty("--chart-3", preset.isDark ? "#8a8f98" : "#4b5563"); + root.style.setProperty("--chart-4", preset.isDark ? "#6e7681" : "#374151"); + root.style.setProperty("--chart-5", preset.isDark ? "#4b5563" : "#1f2937"); + + // Toggle dark class for any tailwind dark: variants still in use + if (preset.isDark) { + root.classList.add("dark"); + } else { + root.classList.remove("dark"); + } +} + +export function getThemeById(id: string): ThemePreset | undefined { + return themePresets.find((t) => t.id === id); +}