"use client"; import { useLayoutEffect } from "react"; import { Sun, Moon } from "lucide-react"; function getEffectiveTheme(): "light" | "dark" { const stored = document.documentElement.dataset.theme; if (stored === "light" || stored === "dark") return stored; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } // Both icons always render; CSS (mirroring the same data-theme cascade the // palette itself uses) decides which one shows — avoids a useState/useEffect // pair just to reflect the DOM attribute the no-flash script already set. export function ThemeToggle() { // The no-flash script sets data-theme before hydration, but // something in this app's streaming SSR/hydration commits again shortly // after first paint and strips it back off (confirmed: present at ~0ms, // gone by ~50ms, and it doesn't come back on its own) — suppressHydration // Warning on only silences the mismatch warning for the *first* // hydration diff, it doesn't stop a later commit from resetting an // attribute React doesn't know about. Re-applying here, once this client // component has mounted (i.e. after that reset has already happened), // wins the race and makes it stick. useLayoutEffect(() => { const stored = localStorage.getItem("theme"); if (stored === "light" || stored === "dark") { document.documentElement.dataset.theme = stored; } }, []); function toggle() { const next = getEffectiveTheme() === "dark" ? "light" : "dark"; document.documentElement.dataset.theme = next; localStorage.setItem("theme", next); } return ( ); }