From 72d4e276f43cf6215e8eb24bdefd9e56f3c93445 Mon Sep 17 00:00:00 2001 From: Trey Orr Date: Tue, 17 Feb 2026 12:06:43 -0500 Subject: [PATCH] Simplify dark mode switcher on home --- apps/web/package.json | 3 +- apps/web/src/app.css | 128 ++++++++++++++++++++++++++++- apps/web/src/app.html | 16 +++- apps/web/src/lib/theme.ts | 36 ++++++++ apps/web/src/routes/+layout.svelte | 16 ++++ apps/web/src/routes/+page.svelte | 43 ++++++++++ bun.lock | 3 + services/signaling/Cargo.lock | 2 +- 8 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/lib/theme.ts diff --git a/apps/web/package.json b/apps/web/package.json index e001199..c0c4358 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,6 +23,7 @@ "vite": "^7.3.0" }, "dependencies": { - "@treyorr/voca-svelte": "^0.3.0" + "@treyorr/voca-svelte": "^0.3.0", + "@lucide/svelte": "^0.574.0" } } \ No newline at end of file diff --git a/apps/web/src/app.css b/apps/web/src/app.css index d234630..6ef9c7d 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -10,6 +10,27 @@ :root { --pulse: 1px; + color-scheme: light; +} + +:root[data-resolved-theme="dark"] { + --color-black: #ffffff; + --color-white: #000000; + --color-voca-bg: #000000; + --color-voca-fg: #ffffff; + --color-voca-border: #ffffff; + color-scheme: dark; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-resolved-theme]) { + --color-black: #ffffff; + --color-white: #000000; + --color-voca-bg: #000000; + --color-voca-fg: #ffffff; + --color-voca-border: #ffffff; + color-scheme: dark; + } } * { @@ -21,6 +42,111 @@ body { color: var(--color-voca-fg); margin: 0; padding: 0; + transition: background-color 200ms ease-out, color 200ms ease-out; +} + +a, +button, +input, +nav, +main, +pre, +table, +thead, +tbody, +tr, +td, +th, +div { + transition: background-color 200ms ease-out, color 200ms ease-out, border-color 200ms ease-out, + box-shadow 200ms ease-out; +} + +.theme-toggle { + position: fixed; + top: 0.75rem; + right: 0.75rem; + display: inline-flex; + gap: 0; + border: 1px solid var(--color-voca-border); + background: var(--color-voca-bg); + z-index: 120; +} + +.theme-toggle button { + border: 0; + border-right: 1px solid var(--color-voca-border); + background: var(--color-voca-bg); + color: var(--color-voca-fg); + width: 2rem; + height: 2rem; + padding: 0; + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.theme-toggle button:last-child { + border-right: 0; +} + +.theme-toggle button.active { + background: var(--color-voca-fg); + color: var(--color-voca-bg); +} + +.theme-toggle button:focus-visible { + outline: 2px solid var(--color-voca-border); + outline-offset: -2px; +} + +.theme-icon { + width: 1rem; + height: 1rem; +} + +:root[data-resolved-theme="dark"] .border-black { + border-color: var(--color-voca-border) !important; +} + +:root[data-resolved-theme="dark"] .border-t-black { + border-top-color: var(--color-voca-border) !important; +} + +:root[data-resolved-theme="dark"] .border-b-black { + border-bottom-color: var(--color-voca-border) !important; +} + +:root[data-resolved-theme="dark"] .border-r-black { + border-right-color: var(--color-voca-border) !important; +} + +:root[data-resolved-theme="dark"] .bg-white { + background-color: var(--color-voca-bg) !important; +} + +:root[data-resolved-theme="dark"] .text-black { + color: var(--color-voca-fg) !important; +} + +:root[data-resolved-theme="dark"] .bg-black { + background-color: var(--color-voca-fg) !important; +} + +:root[data-resolved-theme="dark"] .text-white { + color: var(--color-voca-bg) !important; +} + +:root[data-resolved-theme="dark"] .hover\:bg-black:hover { + background-color: var(--color-voca-fg) !important; +} + +:root[data-resolved-theme="dark"] .hover\:text-white:hover { + color: var(--color-voca-bg) !important; } .brutalist-box { @@ -91,4 +217,4 @@ body { .brutalist-input::placeholder { color: #888; -} \ No newline at end of file +} diff --git a/apps/web/src/app.html b/apps/web/src/app.html index 81ad279..c8dee8c 100644 --- a/apps/web/src/app.html +++ b/apps/web/src/app.html @@ -9,6 +9,20 @@ + %sveltekit.head% @@ -16,4 +30,4 @@
%sveltekit.body%
- \ No newline at end of file + diff --git a/apps/web/src/lib/theme.ts b/apps/web/src/lib/theme.ts new file mode 100644 index 0000000..847c29e --- /dev/null +++ b/apps/web/src/lib/theme.ts @@ -0,0 +1,36 @@ +export type ThemePreference = "light" | "dark" | "system"; + +const THEME_KEY = "voca-theme"; + +const isStoredTheme = (value: string | null): value is "light" | "dark" => + value === "light" || value === "dark"; + +export const getThemePreference = (): ThemePreference => { + const stored = localStorage.getItem(THEME_KEY); + return isStoredTheme(stored) ? stored : "system"; +}; + +export const applyTheme = (preference: ThemePreference) => { + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const resolvedTheme = preference === "system" ? (prefersDark ? "dark" : "light") : preference; + + const root = document.documentElement; + root.dataset.theme = preference; + root.dataset.resolvedTheme = resolvedTheme; +}; + +export const setThemePreference = (preference: ThemePreference) => { + if (preference === "system") { + localStorage.removeItem(THEME_KEY); + } else { + localStorage.setItem(THEME_KEY, preference); + } + + applyTheme(preference); +}; + +export const getThemeFromDom = (): ThemePreference | null => { + const current = document.documentElement.dataset.theme; + if (current === "light" || current === "dark" || current === "system") return current; + return null; +}; diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte index 3fa208a..fe5a7c1 100644 --- a/apps/web/src/routes/+layout.svelte +++ b/apps/web/src/routes/+layout.svelte @@ -1,6 +1,22 @@ {@render children()} diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index b9b92b0..a50a2dd 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -1,15 +1,28 @@