From 371c54cb5ad3793cf4b61e7451b0710d317021d6 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 27 Apr 2026 08:06:44 +0300 Subject: Add blank mode toggle (b key) and theme-hot-swap + Nukem sounds rework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New blank mode hides all UI except the WebGL canvas; toggled via the ‘blank’ nav button or the b key. - Theme switching is now in-place (no page reload), avoiding the Web Audio context reset that used to kill ambient music. - Nukem ambient/riff rebuilt around the classic Grabbag riff (E5 E5 G5 A5 …). --- internal/generator/templates/shared/nav.tmpl | 1 + internal/generator/templates/shared/shared.css | 21 ++++ internal/generator/templates/shared/shared.js | 133 +++++++++++++++++++++++-- internal/generator/theme_sounds.go | 32 +++--- 4 files changed, 162 insertions(+), 25 deletions(-) (limited to 'internal/generator') diff --git a/internal/generator/templates/shared/nav.tmpl b/internal/generator/templates/shared/nav.tmpl index 4f620a5..b04b160 100644 --- a/internal/generator/templates/shared/nav.tmpl +++ b/internal/generator/templates/shared/nav.tmpl @@ -58,6 +58,7 @@ + diff --git a/internal/generator/templates/shared/shared.css b/internal/generator/templates/shared/shared.css index 87e981e..5fab9ac 100644 --- a/internal/generator/templates/shared/shared.css +++ b/internal/generator/templates/shared/shared.css @@ -850,6 +850,27 @@ body.sno-ghost-mode .post.post-active { opacity:1 !important; box-shadow:0 0 28p body.sno-ghost-mode header, body.sno-ghost-mode .page-nav-footer, body.sno-ghost-mode .nav-hints { opacity:0.25 !important; transition:opacity 0.4s ease; } +/* Blank mode — hide everything except WebGL background */ +body.sno-blank-mode .overlay { opacity:0 !important; pointer-events:none !important; transition:opacity 0.3s ease; } +body.sno-blank-mode #sno-crt-overlay { opacity:0 !important; } +/* Hide nav hints, pagination footer, header, post modal and splash overlay in blank mode */ +body.sno-blank-mode .nav-hints { opacity:0 !important; pointer-events:none !important; transition:opacity 0.3s ease; } +body.sno-blank-mode .page-nav-footer { opacity:0 !important; pointer-events:none !important; transition:opacity 0.3s ease; } +body.sno-blank-mode header { opacity:0 !important; pointer-events:none !important; transition:opacity 0.3s ease; } +/* Keep splash hidden too if it happens to still be around */ +body.sno-blank-mode .splash-overlay { opacity:0 !important; pointer-events:none !important; } +@keyframes sno-lightning-flash { + 0% { opacity:0; } + 8% { opacity:0.9; } + 12% { opacity:0.2; } + 18% { opacity:0.85; } + 24% { opacity:0.1; } + 30% { opacity:0.7; } + 100% { opacity:0; } +} +.sno-lightning { position:fixed; inset:0; z-index:9999; pointer-events:none; + background:radial-gradient(ellipse at 50% 30%, rgba(255,255,255,0.95), rgba(200,220,255,0.6) 40%, transparent 70%); + animation:sno-lightning-flash 0.5s ease-out both; } /* Post transition animations */ @keyframes sno-enter-from-below { 0%{transform:translateY(22px) scale(0.97);opacity:0.55} 100%{transform:translateY(0) scale(1);opacity:1} } @keyframes sno-enter-from-above { 0%{transform:translateY(-22px) scale(0.97);opacity:0.55} 100%{transform:translateY(0) scale(1);opacity:1} } diff --git a/internal/generator/templates/shared/shared.js b/internal/generator/templates/shared/shared.js index 70b97e0..5a287c7 100644 --- a/internal/generator/templates/shared/shared.js +++ b/internal/generator/templates/shared/shared.js @@ -161,13 +161,95 @@ return 'neon'; } - // snonuxSwitchTheme persists the user's choice and reloads. - // The shell's boot script picks it up on the next load. + // snonuxSwitchTheme swaps every per-theme asset in place — stylesheet, + // meta markup, sounds.json, splash WebGL — without a page reload. We used + // to call location.reload() here, but that destroys the AudioContext and + // browsers can't auto-resume Web Audio without a fresh user gesture, so + // toggling themes with 't' silently killed the music until the next page. function snonuxSwitchTheme(theme) { var all = (typeof window !== 'undefined' && window.SNONUX_ALL_THEMES) || []; if (all.indexOf(theme) < 0) return; + if (theme === window.SNONUX_CURRENT_THEME) return; try { localStorage.setItem('snonuxTheme', theme); } catch (_) {} - location.reload(); + + // Tear down the previous theme's splash WebGL animation so we don't + // leak its requestAnimationFrame loop and renderer when theme.js + // re-runs against a fresh canvas. + if (typeof window._snonuxSplashWebGLCleanup === 'function') { + try { window._snonuxSplashWebGLCleanup(); } catch (_) {} + } + + var prev = window.SNONUX_CURRENT_THEME; + window.SNONUX_CURRENT_THEME = theme; + document.documentElement.setAttribute('data-sno-theme', theme); + + var splashOverlay = document.getElementById('splash-overlay'); + if (splashOverlay) { + if (prev) splashOverlay.classList.remove('splash-' + prev); + splashOverlay.classList.add('splash-' + theme); + } + + // Swap the theme stylesheet by injecting the new next to the + // old one and removing the old once the new one has loaded — that + // avoids a flash-of-default-theme between unstyled and restyled. + var bust = (window.SNONUX_BUILD || '') + '-' + Date.now(); + var oldLink = document.getElementById('sno-theme-css') || + document.querySelector('link[rel="stylesheet"][href*="/theme.css"]'); + var newLink = document.createElement('link'); + newLink.rel = 'stylesheet'; + newLink.id = 'sno-theme-css'; + newLink.href = 'themes/' + theme + '/theme.css?b=' + encodeURIComponent(bust); + if (oldLink && oldLink.parentNode) { + oldLink.parentNode.insertBefore(newLink, oldLink.nextSibling); + var dropOld = function() { if (oldLink && oldLink !== newLink) oldLink.remove(); }; + newLink.addEventListener('load', dropOld); + newLink.addEventListener('error', dropOld); + } else { + document.head.appendChild(newLink); + } + + // Fetch meta + sounds with cache-bust so the browser cannot serve a + // stale per-theme JSON across an in-page swap. + fetch('themes/' + theme + '/meta.json?b=' + encodeURIComponent(bust)) + .then(function (r) { return r.json(); }) + .then(function (m) { + if (m.title) document.title = m.title; + var headerEl = document.querySelector('header'); + if (headerEl && m.header_html) headerEl.innerHTML = m.header_html; + if (splashOverlay && m.splash_inner_html) splashOverlay.innerHTML = m.splash_inner_html; + var prevA = document.getElementById('sno-prev-page'); + if (prevA && m.prev_page_text) prevA.innerHTML = m.prev_page_text; + var nextA = document.getElementById('sno-next-page'); + if (nextA && m.next_page_text) nextA.innerHTML = m.next_page_text; + if (typeof window._snonuxRebindHeader === 'function') window._snonuxRebindHeader(); + }) + .catch(function () {}); + + fetch('themes/' + theme + '/sounds.json?b=' + encodeURIComponent(bust)) + .then(function (r) { return r.json(); }) + .then(function (s) { + window.SNONUX_SOUNDS = s; + SNONUX_SOUNDS = s; + if (window.snonuxAmbientSyncPreset) window.snonuxAmbientSyncPreset(); + }) + .catch(function () {}); + + // Refresh wild mode preset so banner/ticker/colour-wash come from the + // new theme if wild is currently active. + if (window._snoWildActive && typeof snonuxApplyWildPreset === 'function') { + snonuxApplyWildPreset(theme); + } + + // Replace theme.js so the splash WebGL is rebuilt against the new + // theme's settings. Removing the old