zebra-report: dark default + theme toggle on every page — mirror source 8c90379

This commit is contained in:
russell@unturf.com 2026-06-01 22:18:12 -04:00
parent 53b29a97e8
commit 1e6a6a7c1b
No known key found for this signature in database
5 changed files with 359 additions and 15 deletions

View file

@ -68,9 +68,76 @@
/* long URLs / hashes wrap instead of pushing the page wider */
p, li { overflow-wrap: anywhere; }
}
/* ---- shared dark mode: pure black canvas, dim text, low light ---- */
html.theme-dark { background: #000; }
html.theme-dark body { background: #000; color: #ccc; }
html.theme-dark a { color: #9cf; }
html.theme-dark .sub, html.theme-dark .sub a, html.theme-dark .note,
html.theme-dark .foot, html.theme-dark label,
html.theme-dark .status-line { color: #888; }
html.theme-dark h2 { border-bottom-color: #333; }
html.theme-dark code, html.theme-dark .key-display {
background: #1a1a1a; border-color: #333; color: #ccc;
}
html.theme-dark .key-display:hover { background: #222; }
html.theme-dark button {
background: #000; color: #ccc; border-color: #444;
}
html.theme-dark button:hover:not(:disabled) { background: #1a1a1a; }
html.theme-dark button.invert, html.theme-dark .cta {
background: #ccc; color: #000; border-color: #ccc;
}
html.theme-dark button.invert:hover:not(:disabled),
html.theme-dark .cta:hover { background: #999; }
html.theme-dark input[type=text], html.theme-dark input[type=password],
html.theme-dark textarea, html.theme-dark select {
background: #0a0a0a; color: #ccc; border-color: #333;
}
html.theme-dark .dot { background: #000; border-color: #444; }
html.theme-dark .dot.on { background: #ccc; border-color: #ccc; }
html.theme-dark .dot.ok { background: #0a0; border-color: #0a0; }
html.theme-dark .meter { background: #000; border-color: #444; }
html.theme-dark .meter-fill { background: #ccc; }
html.theme-dark .status-line.ok { color: #6c6; }
html.theme-dark .status-line.err { color: #f66; }
html.theme-dark .log, html.theme-dark .diagram {
background: #050505; border-color: #333; color: #ccc;
}
html.theme-dark .log-line .ts { color: #666; }
html.theme-dark .log-line.sys { color: #888; font-style: italic; }
html.theme-dark .log-line.err { color: #f66; }
html.theme-dark hr { border-top-color: #222; }
html.theme-dark footer { color: #555 !important; }
html.theme-dark footer span { color: #555 !important; }
html.theme-dark footer a { color: #777 !important; }
.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; }
html.theme-dark .theme-toggle { background: #000; color: #ccc; border-color: #444; }
html.theme-dark .theme-toggle:hover { background: #1a1a1a; }
</style>
<script>
/* shared zebra dark-mode pre-paint script — default IS dark, only an
* explicit 'light' choice opts out so first-time visitors land dark
* without a white flash */
try {
var pref = localStorage.getItem('zebra-theme-v1');
if (!pref) pref = localStorage.getItem('zebra-spaces-theme-v1'); // legacy
if (pref !== 'light'){
document.documentElement.classList.add('theme-dark');
}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
</script>
</head>
<body>
<button id="btn-theme" class="theme-toggle" type="button" aria-label="toggle dark mode">light</button>
<h1>host your own</h1>
<p class="sub">run your own zebra community &nbsp;·&nbsp; self-hosted TURN + rendezvous on one edge box &nbsp;·&nbsp;
@ -441,10 +508,28 @@ handle /zebra-spaces-sfu* { reverse_proxy localhost:8092 { flush_interval -1
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span style="color:#777">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">35cea88368ac2591fb7a075c215e242d</span><br>
sha256 <span class="stamp-sha">9989466be3b352d495cb9dcf5b11ea721f1f24d68db90678d0570635ec191c10</span><br>
md5 <span class="stamp-md5">d66163119d2ed906b6b5860492cafe48</span><br>
sha256 <span class="stamp-sha">6625f870ff5246e12c4c953eb6b96aa469de48f432f7c33eaa39247318a038b3</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=</span>
</footer>
<script>
/* shared zebra dark-mode toggle handler */
(function(){
var btn = document.getElementById('btn-theme'); if (!btn) return;
function applyLabel(){
btn.textContent = document.documentElement.classList.contains('theme-dark') ? 'light' : 'dark';
}
applyLabel();
btn.addEventListener('click', function(){
var root = document.documentElement;
var goDark = !root.classList.contains('theme-dark');
root.classList.toggle('theme-dark', goDark);
try { localStorage.setItem('zebra-theme-v1', goDark ? 'dark' : 'light'); } catch(_){}
applyLabel();
});
})();
</script>
</body>
</html>

View file

@ -61,9 +61,76 @@
.lead { font-size: 0.95rem; }
p, li { overflow-wrap: anywhere; }
}
/* ---- shared dark mode: pure black canvas, dim text, low light ---- */
html.theme-dark { background: #000; }
html.theme-dark body { background: #000; color: #ccc; }
html.theme-dark a { color: #9cf; }
html.theme-dark .sub, html.theme-dark .sub a, html.theme-dark .note,
html.theme-dark .foot, html.theme-dark label,
html.theme-dark .status-line { color: #888; }
html.theme-dark h2 { border-bottom-color: #333; }
html.theme-dark code, html.theme-dark .key-display {
background: #1a1a1a; border-color: #333; color: #ccc;
}
html.theme-dark .key-display:hover { background: #222; }
html.theme-dark button {
background: #000; color: #ccc; border-color: #444;
}
html.theme-dark button:hover:not(:disabled) { background: #1a1a1a; }
html.theme-dark button.invert, html.theme-dark .cta {
background: #ccc; color: #000; border-color: #ccc;
}
html.theme-dark button.invert:hover:not(:disabled),
html.theme-dark .cta:hover { background: #999; }
html.theme-dark input[type=text], html.theme-dark input[type=password],
html.theme-dark textarea, html.theme-dark select {
background: #0a0a0a; color: #ccc; border-color: #333;
}
html.theme-dark .dot { background: #000; border-color: #444; }
html.theme-dark .dot.on { background: #ccc; border-color: #ccc; }
html.theme-dark .dot.ok { background: #0a0; border-color: #0a0; }
html.theme-dark .meter { background: #000; border-color: #444; }
html.theme-dark .meter-fill { background: #ccc; }
html.theme-dark .status-line.ok { color: #6c6; }
html.theme-dark .status-line.err { color: #f66; }
html.theme-dark .log, html.theme-dark .diagram {
background: #050505; border-color: #333; color: #ccc;
}
html.theme-dark .log-line .ts { color: #666; }
html.theme-dark .log-line.sys { color: #888; font-style: italic; }
html.theme-dark .log-line.err { color: #f66; }
html.theme-dark hr { border-top-color: #222; }
html.theme-dark footer { color: #555 !important; }
html.theme-dark footer span { color: #555 !important; }
html.theme-dark footer a { color: #777 !important; }
.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; }
html.theme-dark .theme-toggle { background: #000; color: #ccc; border-color: #444; }
html.theme-dark .theme-toggle:hover { background: #1a1a1a; }
</style>
<script>
/* shared zebra dark-mode pre-paint script — default IS dark, only an
* explicit 'light' choice opts out so first-time visitors land dark
* without a white flash */
try {
var pref = localStorage.getItem('zebra-theme-v1');
if (!pref) pref = localStorage.getItem('zebra-spaces-theme-v1'); // legacy
if (pref !== 'light'){
document.documentElement.classList.add('theme-dark');
}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
</script>
</head>
<body>
<button id="btn-theme" class="theme-toggle" type="button" aria-label="toggle dark mode">light</button>
<h1>zebra report</h1>
<p class="sub">how it works &nbsp;·&nbsp; a volume-modem chatroom over webrtc &nbsp;·&nbsp;
@ -275,10 +342,28 @@
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span style="color:#777">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">866696ff17414e6a79b9118d5cec1242</span><br>
sha256 <span class="stamp-sha">a99c9cda25bfaaa2691f62ce7c97d1ea4cd8bb36f39271db675af147598f1638</span><br>
md5 <span class="stamp-md5">4ace619f2fc1bf734a9fa1f31a7c3ad0</span><br>
sha256 <span class="stamp-sha">6122621e01623b4a5af42ecad202cb9edfac5e8a81f51e217c1d925118042392</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>
<script>
/* shared zebra dark-mode toggle handler */
(function(){
var btn = document.getElementById('btn-theme'); if (!btn) return;
function applyLabel(){
btn.textContent = document.documentElement.classList.contains('theme-dark') ? 'light' : 'dark';
}
applyLabel();
btn.addEventListener('click', function(){
var root = document.documentElement;
var goDark = !root.classList.contains('theme-dark');
root.classList.toggle('theme-dark', goDark);
try { localStorage.setItem('zebra-theme-v1', goDark ? 'dark' : 'light'); } catch(_){}
applyLabel();
});
})();
</script>
</body>
</html>

View file

@ -165,9 +165,76 @@
.share-box .qr-canvas { justify-self: center; max-width: 100%; }
h1 { font-size: 2.2rem; }
}
/* ---- shared dark mode: pure black canvas, dim text, low light ---- */
html.theme-dark { background: #000; }
html.theme-dark body { background: #000; color: #ccc; }
html.theme-dark a { color: #9cf; }
html.theme-dark .sub, html.theme-dark .sub a, html.theme-dark .note,
html.theme-dark .foot, html.theme-dark label,
html.theme-dark .status-line { color: #888; }
html.theme-dark h2 { border-bottom-color: #333; }
html.theme-dark code, html.theme-dark .key-display {
background: #1a1a1a; border-color: #333; color: #ccc;
}
html.theme-dark .key-display:hover { background: #222; }
html.theme-dark button {
background: #000; color: #ccc; border-color: #444;
}
html.theme-dark button:hover:not(:disabled) { background: #1a1a1a; }
html.theme-dark button.invert, html.theme-dark .cta {
background: #ccc; color: #000; border-color: #ccc;
}
html.theme-dark button.invert:hover:not(:disabled),
html.theme-dark .cta:hover { background: #999; }
html.theme-dark input[type=text], html.theme-dark input[type=password],
html.theme-dark textarea, html.theme-dark select {
background: #0a0a0a; color: #ccc; border-color: #333;
}
html.theme-dark .dot { background: #000; border-color: #444; }
html.theme-dark .dot.on { background: #ccc; border-color: #ccc; }
html.theme-dark .dot.ok { background: #0a0; border-color: #0a0; }
html.theme-dark .meter { background: #000; border-color: #444; }
html.theme-dark .meter-fill { background: #ccc; }
html.theme-dark .status-line.ok { color: #6c6; }
html.theme-dark .status-line.err { color: #f66; }
html.theme-dark .log, html.theme-dark .diagram {
background: #050505; border-color: #333; color: #ccc;
}
html.theme-dark .log-line .ts { color: #666; }
html.theme-dark .log-line.sys { color: #888; font-style: italic; }
html.theme-dark .log-line.err { color: #f66; }
html.theme-dark hr { border-top-color: #222; }
html.theme-dark footer { color: #555 !important; }
html.theme-dark footer span { color: #555 !important; }
html.theme-dark footer a { color: #777 !important; }
.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; }
html.theme-dark .theme-toggle { background: #000; color: #ccc; border-color: #444; }
html.theme-dark .theme-toggle:hover { background: #1a1a1a; }
</style>
<script>
/* shared zebra dark-mode pre-paint script — default IS dark, only an
* explicit 'light' choice opts out so first-time visitors land dark
* without a white flash */
try {
var pref = localStorage.getItem('zebra-theme-v1');
if (!pref) pref = localStorage.getItem('zebra-spaces-theme-v1'); // legacy
if (pref !== 'light'){
document.documentElement.classList.add('theme-dark');
}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
</script>
</head>
<body>
<button id="btn-theme" class="theme-toggle" type="button" aria-label="toggle dark mode">light</button>
<h1>zebra report</h1>
<p class="sub">volume modem chatroom &nbsp;·&nbsp; e2e encrypted &nbsp;·&nbsp; webrtc carrier &nbsp;·&nbsp;
@ -1653,10 +1720,28 @@ logLine('sys', 'chat content lives in encrypted SRTP audio. no IP packets carry
</script>
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span style="color:#777">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">74f0583d5b47b420665b7bb923cfbd1b</span><br>
sha256 <span class="stamp-sha">5e06ebed815d304d9d3d4a9df270d00e0fba6a636e0c814f39346a13715b86bd</span><br>
md5 <span class="stamp-md5">0fb2dca42673efdde9164f5bc24d0200</span><br>
sha256 <span class="stamp-sha">ba3b4ce8b96e2649f76f9b0338bde38ddcdbea148adbdfb0e23094a85999ee2b</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>
<script>
/* shared zebra dark-mode toggle handler */
(function(){
var btn = document.getElementById('btn-theme'); if (!btn) return;
function applyLabel(){
btn.textContent = document.documentElement.classList.contains('theme-dark') ? 'light' : 'dark';
}
applyLabel();
btn.addEventListener('click', function(){
var root = document.documentElement;
var goDark = !root.classList.contains('theme-dark');
root.classList.toggle('theme-dark', goDark);
try { localStorage.setItem('zebra-theme-v1', goDark ? 'dark' : 'light'); } catch(_){}
applyLabel();
});
})();
</script>
</body>
</html>

View file

@ -75,9 +75,76 @@
/* drop the fixed mute-button width on phones so the row fits */
#btn-mute { width: auto; padding: 0.45rem 0.9rem; }
}
/* ---- shared dark mode: pure black canvas, dim text, low light ---- */
html.theme-dark { background: #000; }
html.theme-dark body { background: #000; color: #ccc; }
html.theme-dark a { color: #9cf; }
html.theme-dark .sub, html.theme-dark .sub a, html.theme-dark .note,
html.theme-dark .foot, html.theme-dark label,
html.theme-dark .status-line { color: #888; }
html.theme-dark h2 { border-bottom-color: #333; }
html.theme-dark code, html.theme-dark .key-display {
background: #1a1a1a; border-color: #333; color: #ccc;
}
html.theme-dark .key-display:hover { background: #222; }
html.theme-dark button {
background: #000; color: #ccc; border-color: #444;
}
html.theme-dark button:hover:not(:disabled) { background: #1a1a1a; }
html.theme-dark button.invert, html.theme-dark .cta {
background: #ccc; color: #000; border-color: #ccc;
}
html.theme-dark button.invert:hover:not(:disabled),
html.theme-dark .cta:hover { background: #999; }
html.theme-dark input[type=text], html.theme-dark input[type=password],
html.theme-dark textarea, html.theme-dark select {
background: #0a0a0a; color: #ccc; border-color: #333;
}
html.theme-dark .dot { background: #000; border-color: #444; }
html.theme-dark .dot.on { background: #ccc; border-color: #ccc; }
html.theme-dark .dot.ok { background: #0a0; border-color: #0a0; }
html.theme-dark .meter { background: #000; border-color: #444; }
html.theme-dark .meter-fill { background: #ccc; }
html.theme-dark .status-line.ok { color: #6c6; }
html.theme-dark .status-line.err { color: #f66; }
html.theme-dark .log, html.theme-dark .diagram {
background: #050505; border-color: #333; color: #ccc;
}
html.theme-dark .log-line .ts { color: #666; }
html.theme-dark .log-line.sys { color: #888; font-style: italic; }
html.theme-dark .log-line.err { color: #f66; }
html.theme-dark hr { border-top-color: #222; }
html.theme-dark footer { color: #555 !important; }
html.theme-dark footer span { color: #555 !important; }
html.theme-dark footer a { color: #777 !important; }
.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; }
html.theme-dark .theme-toggle { background: #000; color: #ccc; border-color: #444; }
html.theme-dark .theme-toggle:hover { background: #1a1a1a; }
</style>
<script>
/* shared zebra dark-mode pre-paint script — default IS dark, only an
* explicit 'light' choice opts out so first-time visitors land dark
* without a white flash */
try {
var pref = localStorage.getItem('zebra-theme-v1');
if (!pref) pref = localStorage.getItem('zebra-spaces-theme-v1'); // legacy
if (pref !== 'light'){
document.documentElement.classList.add('theme-dark');
}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
</script>
</head>
<body>
<button id="btn-theme" class="theme-toggle" type="button" aria-label="toggle dark mode">light</button>
<h1>zebra audio</h1>
<p class="sub">encrypted voice &nbsp;·&nbsp; webrtc &nbsp;·&nbsp; rendezvous &nbsp;·&nbsp;
@ -660,10 +727,28 @@ else wirePuppet();
<footer style="max-width:560px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">71881c48a78ce9205fad6fc2921e316a</span><br>
sha256 <span class="stamp-sha">bdc65afcb98c2e38dfd40e4b0cd1a2b8f26af740bced1f376deb99cd6b9ec912</span><br>
md5 <span class="stamp-md5">97b7b9f30c280b37917e4cd94befc4ba</span><br>
sha256 <span class="stamp-sha">1ed57fa7bca6be80d98ac418cccc30a6681b45341b36b6d263e9b090e7f3e8df</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>
<script>
/* shared zebra dark-mode toggle handler */
(function(){
var btn = document.getElementById('btn-theme'); if (!btn) return;
function applyLabel(){
btn.textContent = document.documentElement.classList.contains('theme-dark') ? 'light' : 'dark';
}
applyLabel();
btn.addEventListener('click', function(){
var root = document.documentElement;
var goDark = !root.classList.contains('theme-dark');
root.classList.toggle('theme-dark', goDark);
try { localStorage.setItem('zebra-theme-v1', goDark ? 'dark' : 'light'); } catch(_){}
applyLabel();
});
})();
</script>
</body>
</html>

View file

@ -308,13 +308,17 @@
}
</style>
<script>
/* apply theme before first paint to avoid the brief white flash that
* would happen if we waited for the deferred main script */
/* apply theme before first paint to avoid a flash of the wrong theme.
* Default IS dark — only the explicit 'light' choice opts out, so a
* first-time visitor lands in dark without a flash. One unified key
* across every zebra page so the preference follows the user. */
try {
if (localStorage.getItem('zebra-spaces-theme-v1') === 'dark'){
var pref = localStorage.getItem('zebra-theme-v1');
if (!pref) pref = localStorage.getItem('zebra-spaces-theme-v1'); // legacy migration
if (pref !== 'light'){
document.documentElement.classList.add('theme-dark');
}
} catch(_){}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
</script>
</head>
<body>
@ -517,7 +521,7 @@ 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';
const THEME_KEY = 'zebra-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
@ -2179,8 +2183,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">0e924d8e993d1b869f7554421455636e</span><br>
sha256 <span class="stamp-sha">8677be259cd940bf3d290598171533e47b6ec04a85a855e18670ecd8aff12945</span><br>
md5 <span class="stamp-md5">df76b79d099ed55cab1336afdf62e8a8</span><br>
sha256 <span class="stamp-sha">f2a8f4316571f5accc3ed4b6be564ab31328c732323f2f64ce62675d3de1701c</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>