"use client"; import { useEffect, useState } from "react"; export type ThemePref = "light" | "dark" | "system"; const KEY = "vantage-hq-theme"; /* * globals.css has always defined the dark tokens under BOTH * :root[data-theme="dark"] and :root[data-theme="light"], specifically so an * in-page control can win over the OS preference in either direction. Nothing * ever set the attribute. This is that missing half. * * "system" removes the attribute rather than writing a value, which hands the * decision back to the prefers-color-scheme block. */ export function applyTheme(pref: ThemePref) { const root = document.documentElement; if (pref === "system") root.removeAttribute("data-theme"); else root.setAttribute("data-theme", pref); } export function readTheme(): ThemePref { if (typeof localStorage === "undefined") return "system"; const v = localStorage.getItem(KEY); return v === "light" || v === "dark" ? v : "system"; } export function useTheme(): [ThemePref, (p: ThemePref) => void] { // Starts at "system" on both server and first client render so hydration // matches; the real value lands in the effect below. The inline script in // app/layout.tsx has already painted the correct colours by then, so there // is no flash — only this control's own highlight settles a tick late. const [pref, setPref] = useState("system"); useEffect(() => setPref(readTheme()), []); return [ pref, (next: ThemePref) => { setPref(next); if (next === "system") localStorage.removeItem(KEY); else localStorage.setItem(KEY, next); applyTheme(next); }, ]; } /* * Runs before first paint, so a dark-preferring user never sees a white flash. * Inlined as a string because it has to execute ahead of React. */ export const THEME_BOOT_SCRIPT = `try{var t=localStorage.getItem(${JSON.stringify( KEY, )});if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}`;