From e8839b15a0e305cb49ad61841957118ab85f3d8b Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Sun, 7 Jun 2026 11:48:59 -0400 Subject: [PATCH] Full light/dark theme toggle; brand-aware connector lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Theme is now class-based (.dark on ) with a System/Light/Dark toggle in the sidebar, persisted to localStorage and applied pre-paint by an inline script (no flash). Replaces the prefers-color-scheme-only behavior, so a phone on a light OS theme can still choose dark and vice versa. - New brand-derived --line token (Ink at 55%): a dark line on the light paper, light on dark. The family-chart tree connectors had the library's default white stroke and were invisible in light mode — now they use --line, as do the pedigree brackets and the fan-chart sectors. - Light/dark tokens use the exact brand palette (Ink/Muted flip; Bronze/Paper constant). Frontend only — no migration. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/app/globals.css | 29 ++++++++------ frontend/app/layout.tsx | 9 ++++- frontend/app/trees/[id]/tree/chart.css | 5 +++ frontend/components/app-sidebar.tsx | 7 +++- frontend/components/fan-chart.tsx | 2 +- frontend/components/theme-toggle.tsx | 54 ++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 frontend/components/theme-toggle.tsx diff --git a/frontend/app/globals.css b/frontend/app/globals.css index dfbe818..a7415be 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -11,23 +11,28 @@ --font-serif: var(--font-fraunces), Georgia, "Times New Roman", ui-serif, serif; } -/* Adaptive tokens — ink/paper flip for light/dark; bronze + paper are constant. */ +/* Adaptive tokens — ink/paper flip for light/dark; bronze + paper are constant. + Theme is class-based (.dark on ) so it can be toggled manually; an inline + script in the root layout sets it pre-paint from the saved choice or the OS. */ :root { --background: #f7f3ec; --foreground: #1a1a17; --muted: #6b6862; --surface: #fffdf9; --border: #e6ddcc; + /* Connector "lines between people" (pedigree + tree chart). Derived from Ink + (the brand mark color): a dark line on light, light on dark. */ + --line: color-mix(in srgb, var(--foreground) 55%, transparent); + color-scheme: light; } -@media (prefers-color-scheme: dark) { - :root { - --background: #161410; - --foreground: #f2eee6; - --muted: #9a968e; - --surface: #211d17; - --border: #353029; - } +.dark { + --background: #161410; + --foreground: #f2eee6; + --muted: #9a968e; + --surface: #211d17; + --border: #353029; + color-scheme: dark; } body { @@ -78,7 +83,7 @@ h3, left: -2.5rem; top: 50%; width: 2.5rem; - border-top: 1px solid var(--border); + border-top: 1px solid var(--line); } .ped-leaf { position: relative; @@ -90,7 +95,7 @@ h3, left: 0; top: 50%; width: 1.5rem; - border-top: 1px solid var(--border); + border-top: 1px solid var(--line); } .ped-leaf::after { content: ""; @@ -98,7 +103,7 @@ h3, left: 0; top: 0; bottom: 0; - border-left: 1px solid var(--border); + border-left: 1px solid var(--line); } .ped-leaf:first-child::after { top: 50%; diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 9c8ad77..7669e6d 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -18,9 +18,16 @@ export const metadata: Metadata = { icons: { icon: "/favicon.svg" }, }; +// Sets the theme class before first paint to avoid a flash; reads the saved +// choice ("light"/"dark"/"system") or falls back to the OS preference. +const themeScript = `(function(){try{var t=localStorage.getItem("theme");var d=t==="dark"||((!t||t==="system")&&window.matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);}catch(e){}})();`; + export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + + +