-
diff --git a/web/src/styles.css b/web/src/styles.css
index de9aff1..fc11853 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -1,4 +1,35 @@
+/* Dark is the default. theme.ts resolves the user's choice (dark, light or
+ follow the system) onto , so the stylesheet only needs the
+ one override below. */
:root {
+ --bg: #111715;
+ --bg-elev: #19211e;
+ --bg-sunken: #0c100f;
+ --fg: #e6ece9;
+ --fg-muted: #9aa8a2;
+ --fg-faint: #6a7772;
+ --line: #26312d;
+ --line-strong: #3a4742;
+ --accent: #3fae90;
+ --accent-fg: #06110d;
+ --accent-soft: #163a31;
+ --ok: #3fc275;
+ --ok-soft: #12321f;
+ --warn: #e0a84a;
+ --warn-soft: #3a2c10;
+ --bad: #ef6b62;
+ --bad-soft: #3d1815;
+ --info: #5f9de0;
+ --rx: #5f9de0;
+ --tx: #3fae90;
+ --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
+ --radius: 10px;
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
+ --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ color-scheme: dark;
+}
+
+:root[data-theme="light"] {
--bg: #f4f6f5;
--bg-elev: #ffffff;
--bg-sunken: #e9edeb;
@@ -20,39 +51,9 @@
--rx: #2a6fb0;
--tx: #1f6f5c;
--shadow: 0 1px 2px rgba(20, 30, 26, 0.06), 0 8px 24px rgba(20, 30, 26, 0.06);
- --radius: 10px;
- --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
- --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color-scheme: light;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --bg: #111715;
- --bg-elev: #19211e;
- --bg-sunken: #0c100f;
- --fg: #e6ece9;
- --fg-muted: #9aa8a2;
- --fg-faint: #6a7772;
- --line: #26312d;
- --line-strong: #3a4742;
- --accent: #3fae90;
- --accent-fg: #06110d;
- --accent-soft: #163a31;
- --ok: #3fc275;
- --ok-soft: #12321f;
- --warn: #e0a84a;
- --warn-soft: #3a2c10;
- --bad: #ef6b62;
- --bad-soft: #3d1815;
- --info: #5f9de0;
- --rx: #5f9de0;
- --tx: #3fae90;
- --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
- color-scheme: dark;
- }
-}
-
* { box-sizing: border-box; }
html, body, #root { height: 100%; }
body {
@@ -89,10 +90,7 @@ button, input, select, textarea { font: inherit; color: inherit; }
height: 100vh;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 4px 8px 16px; }
-.brand-mark {
- width: 30px; height: 30px; border-radius: 8px; background: var(--accent);
- display: grid; place-items: center; color: var(--accent-fg); flex: none;
-}
+.brand-mark { display: grid; place-items: center; flex: none; line-height: 0; }
.brand-name { font-weight: 700; font-size: 16px; letter-spacing: 0.02em; }
.brand-sub { font-size: 11px; color: var(--fg-muted); }
.nav a {
diff --git a/web/src/theme.ts b/web/src/theme.ts
new file mode 100644
index 0000000..a820a9f
--- /dev/null
+++ b/web/src/theme.ts
@@ -0,0 +1,45 @@
+// Theme preference: dark by default, light on request, or follow the OS.
+// The choice is a per-browser convenience kept in localStorage; the resolved
+// theme lands on so the stylesheet needs only one selector.
+
+export type ThemeChoice = "dark" | "light" | "system";
+
+const KEY = "wgx.theme";
+const media = window.matchMedia("(prefers-color-scheme: light)");
+
+export function getThemeChoice(): ThemeChoice {
+ try {
+ const v = localStorage.getItem(KEY);
+ if (v === "light" || v === "system" || v === "dark") return v;
+ } catch {
+ /* storage unavailable */
+ }
+ return "dark";
+}
+
+function resolve(choice: ThemeChoice): "dark" | "light" {
+ if (choice === "system") return media.matches ? "light" : "dark";
+ return choice;
+}
+
+export function applyTheme(choice: ThemeChoice) {
+ document.documentElement.dataset.theme = resolve(choice);
+}
+
+export function setThemeChoice(choice: ThemeChoice) {
+ try {
+ localStorage.setItem(KEY, choice);
+ } catch {
+ /* storage unavailable */
+ }
+ applyTheme(choice);
+}
+
+// Called once at startup: paints the right theme before React renders and
+// keeps "system" honest when the OS switches.
+export function initTheme() {
+ applyTheme(getThemeChoice());
+ media.addEventListener("change", () => {
+ if (getThemeChoice() === "system") applyTheme("system");
+ });
+}