1 import { createContext, useContext, useEffect, useState } from "react";
2
3 export type ThemeMode = "dark" | "light" | "amoled" | "system";
4 export type AccentColor = "indigo" | "cyan" | "emerald" | "amber" | "rose" | "violet" | "slate";
5 export type RadiusStyle = "rounded" | "tech" | "sharp";
6 export type DensityStyle = "comfortable" | "compact";
7
8 export interface ThemeConfig {
9 mode: ThemeMode;
10 accent: AccentColor;
11 radius: RadiusStyle;
12 density: DensityStyle;
13 }
14
15 export const ACCENT_COLORS: Record<AccentColor, { label: string; primaryHsl: string; ringHsl: string; bgHex: string }> = {
16 indigo: { label: "Electric Indigo", primaryHsl: "243 75% 59%", ringHsl: "243 75% 59%", bgHex: "#4f46e5" },
17 cyan: { label: "Cyber Cyan", primaryHsl: "199 89% 48%", ringHsl: "199 89% 48%", bgHex: "#0284c7" },
18 emerald: { label: "Matrix Emerald", primaryHsl: "158 64% 45%", ringHsl: "158 64% 45%", bgHex: "#10b981" },
19 amber: { label: "Sunset Amber", primaryHsl: "38 92% 50%", ringHsl: "38 92% 50%", bgHex: "#f59e0b" },
20 rose: { label: "Ruby Crimson", primaryHsl: "346 77% 49%", ringHsl: "346 77% 49%", bgHex: "#e11d48" },
21 violet: { label: "Neon Violet", primaryHsl: "271 81% 56%", ringHsl: "271 81% 56%", bgHex: "#9333ea" },
22 slate: { label: "Steel Monochrome", primaryHsl: "215 25% 40%", ringHsl: "215 25% 40%", bgHex: "#475569" },
23 };
24
25 export const RADIUS_STYLES: Record<RadiusStyle, { label: string; value: string }> = {
26 rounded: { label: "Modern Rounded (1.25rem)", value: "1.25rem" },
27 tech: { label: "Sleek Tech (0.75rem)", value: "0.75rem" },
28 sharp: { label: "Sharp Industrial (0.25rem)", value: "0.25rem" },
29 };
30
31 type ThemeProviderProps = {
32 children: React.ReactNode;
33 defaultTheme?: ThemeMode;
34 storageKey?: string;
35 };
36
37 type ThemeProviderState = {
38 theme: ThemeMode;
39 setTheme: (theme: ThemeMode) => void;
40 accent: AccentColor;
41 setAccent: (accent: AccentColor) => void;
42 radius: RadiusStyle;
43 setRadius: (radius: RadiusStyle) => void;
44 density: DensityStyle;
45 setDensity: (density: DensityStyle) => void;
46 resetTheme: () => void;
47 };
48
49 const defaultState: ThemeConfig = {
50 mode: "dark",
51 accent: "indigo",
52 radius: "rounded",
53 density: "comfortable",
54 };
55
56 const ThemeProviderContext = createContext<ThemeProviderState>({
57 theme: "dark",
58 setTheme: () => null,
59 accent: "indigo",
60 setAccent: () => null,
61 radius: "rounded",
62 setRadius: () => null,
63 density: "comfortable",
64 setDensity: () => null,
65 resetTheme: () => null,
66 });
67
68 export function ThemeProvider({
69 children,
70 defaultTheme = "dark",
71 storageKey = "jcr-guide-theme-v2",
72 ...props
73 }: ThemeProviderProps) {
74 const [config, setConfig] = useState<ThemeConfig>(() => {
75 try {
76 const saved = localStorage.getItem(storageKey);
77 if (saved) {
78 return { ...defaultState, ...JSON.parse(saved) };
79 }
80 } catch {
81 // ignore
82 }
83 return { ...defaultState, mode: defaultTheme };
84 });
85
86 useEffect(() => {
87 const root = window.document.documentElement;
88 root.classList.remove("light", "dark", "amoled");
89
90 let effectiveMode = config.mode;
91 if (config.mode === "system") {
92 effectiveMode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
93 }
94 root.classList.add(effectiveMode);
95
96 // Apply accent color
97 const accentData = ACCENT_COLORS[config.accent] || ACCENT_COLORS.indigo;
98 root.style.setProperty("--primary", `hsl(${accentData.primaryHsl})`);
99 root.style.setProperty("--ring", `hsl(${accentData.ringHsl})`);
100
101 // Apply radius style
102 const radiusData = RADIUS_STYLES[config.radius] || RADIUS_STYLES.rounded;
103 root.style.setProperty("--radius", radiusData.value);
104
105 // Apply density class
106 root.classList.toggle("density-compact", config.density === "compact");
107
108 try {
109 localStorage.setItem(storageKey, JSON.stringify(config));
110 } catch {
111 // ignore
112 }
113 }, [config, storageKey]);
114
115 const value: ThemeProviderState = {
116 theme: config.mode,
117 setTheme: (mode: ThemeMode) => setConfig(prev => ({ ...prev, mode })),
118 accent: config.accent,
119 setAccent: (accent: AccentColor) => setConfig(prev => ({ ...prev, accent })),
120 radius: config.radius,
121 setRadius: (radius: RadiusStyle) => setConfig(prev => ({ ...prev, radius })),
122 density: config.density,
123 setDensity: (density: DensityStyle) => setConfig(prev => ({ ...prev, density })),
124 resetTheme: () => setConfig(defaultState),
125 };
126
127 return (
128 <ThemeProviderContext.Provider {...props} value={value}>
129 {children}
130 </ThemeProviderContext.Provider>
131 );
132 }
133
134 export const useTheme = () => {
135 const context = useContext(ThemeProviderContext);
136 if (context === undefined)
137 throw new Error("useTheme must be used within a ThemeProvider");
138 return context;
139 };