feat(decnet_web/Layout): topbar dark/light toggle with circular reveal

User-facing theme toggle ships now that the design system has
been audited end-to-end. A Sun/Moon button lives between the
threat indicator and the SYSTEM status pill in the topbar — same
slim 28x28 voice as the rest of the topbar controls, no chrome
shouting at the user.

Click coords drive a View Transitions API circle clip-path that
grows from the cursor to the farthest viewport corner over 520ms
with the project's standard --ease curve. Browsers without
startViewTransition (older Firefox, Safari < 18) fall through to
an unanimated swap — the hook returns instantly in that case.

Persistence is two-tier:
 - localStorage decnet_theme — the user's saved preference, the
   thing the topbar toggle writes. Survives reloads, applies
   everywhere.
 - sessionStorage decnet_theme_lab — dev-mode lab override (Task
   3). Tab-scoped, wins on boot so devs can A/B without nuking
   the saved preference.

App.tsx hydrates both on first mount in the right order so the
correct theme is on <html> before the first paint.

useThemeToggle is a small hook in lib/ rather than a Layout-only
helper so the same toggle can be reused later from a settings page
or hotkey.
This commit is contained in:
2026-05-09 04:01:24 -04:00
parent 9cab37db3a
commit 438a6e3e45
7 changed files with 262 additions and 9 deletions

View File

@@ -92,6 +92,8 @@ export { default as Terminal } from 'lucide-react/dist/esm/icons/terminal';
export { default as Timer } from 'lucide-react/dist/esm/icons/timer';
export { default as Trash2 } from 'lucide-react/dist/esm/icons/trash-2';
export { default as Upload } from 'lucide-react/dist/esm/icons/upload';
export { default as Moon } from 'lucide-react/dist/esm/icons/moon';
export { default as Sun } from 'lucide-react/dist/esm/icons/sun';
export { default as UploadCloud } from 'lucide-react/dist/esm/icons/cloud-upload';
export { default as UserPlus } from 'lucide-react/dist/esm/icons/user-plus';
export { default as Users } from 'lucide-react/dist/esm/icons/users';