diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html
index 9df1f42..170dfc7 100644
--- a/zebra-report/zebra-spaces.html
+++ b/zebra-report/zebra-spaces.html
@@ -16,6 +16,24 @@
font-family: monospace; background: #fff; color: #000;
padding: 1rem 1.5rem; max-width: none; margin: 0 auto;
}
+ /* Dark mode = literal invert. CSS filter flips every painted colour on
+ * the page in one shot, then hue-rotate corrects what would otherwise
+ * read as cyan instead of orange / magenta instead of green. Re-invert
+ * the things we want to look RIGHT instead of merely opposite: video
+ * tiles, the QR canvas, and any inline raster. */
+ html.theme-dark body { filter: invert(1) hue-rotate(180deg); background: #fff; }
+ html.theme-dark video,
+ html.theme-dark canvas,
+ html.theme-dark img { filter: invert(1) hue-rotate(180deg); }
+ /* the theme toggle button itself sits inside the inverted area, so
+ * its own colours flip automatically — no extra rule needed */
+ .theme-toggle {
+ position: fixed; top: 0.6rem; right: 0.8rem; z-index: 9999;
+ background: #fff; color: #000; border: 1px solid #000;
+ padding: 0.2rem 0.55rem; font-family: monospace; font-size: 0.7rem;
+ cursor: pointer; line-height: 1;
+ }
+ .theme-toggle:hover { background: #f0f0f0; }
/* two-column page on desktop: left = timeline (1fr, takes remaining
* space — hosts screen shares + the running event feed), right =
* controls (kept narrow so the screen share gets maximum pixels).
@@ -233,8 +251,18 @@
.mod-actions { padding-left: 5.55rem; }
}
+
+
@@ -433,6 +461,23 @@ const HANDLE_KEY = 'zebra-spaces-handle-v1';
const MUSIC_MODE_KEY = 'zebra-spaces-music-mode-v1';
const MIC_DEV_KEY = 'zebra-spaces-mic-device-v1';
const CAM_DEV_KEY = 'zebra-spaces-cam-device-v1';
+const THEME_KEY = 'zebra-spaces-theme-v1';
+
+/* theme toggle. The class is already applied pre-paint by the head
+ * script, so this just wires the click + keeps the button label in
+ * sync with the current state. */
+function applyThemeLabel(){
+ const dark = document.documentElement.classList.contains('theme-dark');
+ const btn = $('btn-theme'); if (btn) btn.textContent = dark ? 'light' : 'dark';
+}
+applyThemeLabel();
+$('btn-theme').addEventListener('click', () => {
+ const root = document.documentElement;
+ const goDark = !root.classList.contains('theme-dark');
+ root.classList.toggle('theme-dark', goDark);
+ try { localStorage.setItem(THEME_KEY, goDark ? 'dark' : 'light'); } catch(_){}
+ applyThemeLabel();
+});
const VAULT_PREFIX = 'zspc-id-v1|';
const PBKDF2_ITER = 600000;
@@ -2073,8 +2118,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');