feat(frontend): add light mode with runtime theme toggle

- globals.css: split theme tokens into :root (light default) + .dark
  overrides; switch @theme inline → @theme so utilities reference
  var(--color-*) and a runtime class swap actually re-skins the UI
  (inline inlines literal values and ignores .dark overrides)
- layout.tsx: drop the beforeInteractive inline theme script (it caused
  hydration instability); theme is applied client-side only
- top-nav: apply persisted theme on mount, default light, toggle
  updates <html> class + localStorage
- glass-intense: light variant (white card on light canvas); chart grid
  line uses var(--color-border); attachment chip uses bg-glass-bg
- Verified in static export: toggle dark↔light both directions,
  chatbot panel renders clean in both themes
This commit is contained in:
asepharyana
2026-08-03 07:20:09 +07:00
parent 831254bb71
commit 9abb09dd33
5 changed files with 98 additions and 36 deletions
@@ -74,7 +74,7 @@ export function ActivityChart({ data = [] }: ActivityChartProps) {
</defs>
<CartesianGrid
strokeDasharray="3 3"
stroke="oklch(1 0 0 / 0.05)"
stroke="var(--color-border)"
vertical={false}
/>
<XAxis
@@ -10,9 +10,15 @@ export function TopNav() {
const router = useRouter();
const [theme, setTheme] = useState<"light" | "dark">("dark");
// Apply persisted theme on mount (client-side only — avoids SSR hydration
// mismatch from touching <html> during server render).
useEffect(() => {
const stored = localStorage.getItem("theme") as "light" | "dark" | null;
if (stored) setTheme(stored);
const initial =
stored === "light" ? "light" : stored === "dark" ? "dark" : "light";
setTheme(initial);
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(initial);
}, []);
const toggleTheme = () => {
@@ -54,7 +54,7 @@ export function AttachmentsGrid({
{others.map((att) => (
<div
key={att.id}
className="flex items-center gap-1.5 rounded-md bg-white/5 px-2 py-1 text-xs text-text-secondary"
className="flex items-center gap-1.5 rounded-md bg-glass-bg px-2 py-1 text-xs text-text-secondary"
>
<ImageIcon className="size-3 text-text-secondary/50" />
<span className="font-mono max-w-40 truncate">