diff --git a/index.html b/index.html
index dacd81ce..8535acda 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,22 @@
ob248.com
+
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
new file mode 100644
index 00000000..823649ab
--- /dev/null
+++ b/src/components/theme-provider.tsx
@@ -0,0 +1,76 @@
+import { createContext, useContext, useEffect, useMemo, useState } from "react";
+
+type Theme = "light" | "dark" | "system";
+
+type ThemeContextValue = {
+ theme: Theme;
+ resolvedTheme: "light" | "dark";
+ setTheme: (theme: Theme) => void;
+};
+
+const ThemeContext = createContext(null);
+
+const storageKey = "theme";
+
+const getStoredTheme = (): Theme => {
+ if (typeof window === "undefined") return "system";
+ const stored = window.localStorage.getItem(storageKey);
+ if (stored === "light" || stored === "dark" || stored === "system") {
+ return stored;
+ }
+ return "system";
+};
+
+const getSystemTheme = (): "light" | "dark" => {
+ if (typeof window === "undefined") return "light";
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
+ ? "dark"
+ : "light";
+};
+
+function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setTheme] = useState(getStoredTheme);
+ const resolvedTheme = theme === "system" ? getSystemTheme() : theme;
+
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+ window.localStorage.setItem(storageKey, theme);
+
+ const root = document.documentElement;
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+
+ const applyTheme = (next: "light" | "dark") => {
+ root.classList.toggle("dark", next === "dark");
+ };
+
+ applyTheme(theme === "system" ? getSystemTheme() : theme);
+
+ const handleChange = () => {
+ if (theme === "system") {
+ applyTheme(getSystemTheme());
+ }
+ };
+
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ }, [theme]);
+
+ const value = useMemo(
+ () => ({ theme, resolvedTheme, setTheme }),
+ [theme, resolvedTheme],
+ );
+
+ return (
+ {children}
+ );
+}
+
+const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error("useTheme must be used within ThemeProvider");
+ }
+ return context;
+};
+
+export { ThemeProvider, useTheme, type Theme };
diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx
new file mode 100644
index 00000000..dfa9b784
--- /dev/null
+++ b/src/components/theme-toggle.tsx
@@ -0,0 +1,19 @@
+import { useTheme } from "@/components/theme-provider";
+import { Button } from "@/components/ui/button";
+
+function ThemeToggle() {
+ const { resolvedTheme, setTheme } = useTheme();
+ const isDark = resolvedTheme === "dark";
+
+ return (
+
+ );
+}
+
+export { ThemeToggle };
diff --git a/src/main.tsx b/src/main.tsx
index d7c55e3d..d70f50fd 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,6 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
+import { ThemeProvider } from "@/components/theme-provider";
import "./index.css";
import App from "./App.tsx";
@@ -7,6 +8,8 @@ const root = document.getElementById("root");
if (!root) throw new Error("Failed to find the root element");
createRoot(root).render(
-
+
+
+
,
);