var SNONUX_SOUNDS = (typeof window !== "undefined" && window.SNONUX_SOUNDS) || {}; // Inject wild-mode badge used by all themes (function() { var b=document.createElement('div'); b.id='sno-wild-badge'; b.textContent='WILD MODE'; document.body.appendChild(b); })(); const SNONUX_WILD_PRESETS = { aurora: { banner: 'SOLAR STORM', ticker: ['FIELD INTERFERENCE', 'PLASMA DRIFT', 'PARTICLE BOMBARDMENT', 'CHROMATIC SPIKE'], scraps: ['MAGNETIC SHEAR', 'SOLAR WIND', 'AURORA NOISE', 'ION STORM', 'POLAR ARC'], flash: 'rgba(220,255,240,0.72)', emoji: ['\u2728','\u2604\uFE0F','\u{1F320}','\u{1F30C}','\u2B50'] }, brutalist: { banner: 'STRUCTURAL COLLAPSE', ticker: ['CONDEMNED', 'REBAR EXPOSED', 'FOUNDATION FAILURE', 'CRACK PROPAGATION'], scraps: ['CONDEMNED', 'RUST BLEED', 'SHEAR WALL LOST', 'LOAD PATH BROKEN', 'SPALLING'], flash: 'rgba(255,210,190,0.58)', emoji: ['\u{1F9F1}','\u2692\uFE0F','\u26A0\uFE0F','\u{1F6A7}','\u{1F4A5}'] }, cosmos: { banner: 'SUPERNOVA', ticker: ['SINGULARITY LENSING', 'GAMMA BURST', 'SHOCKWAVE EXPANDING', 'SPACETIME TEAR'], scraps: ['WHITEOUT', 'EVENT HORIZON', 'RADIATION FRONT', 'LENS LOCK', 'CORE BREACH'], flash: 'rgba(255,255,255,0.8)', emoji: ['\u{1F30C}','\u2604\uFE0F','\u{1F4AB}','\u2B50','\u{1FA90}'] }, dos: { banner: 'KERNEL PANIC', ticker: ['ABORT, RETRY, FAIL?', 'MEMORY CORRUPTION', 'STACK DUMP', 'SEGMENT FAULT'], scraps: ['DEAD BEEF', 'C0FFEE', 'BAD SECTOR', 'IRQ STORM', 'NULL PTR', 'HEX DUMP'], flash: 'rgba(255,255,255,0.88)', emoji: ['\u{1F4BE}','\u{1F4BB}','\u26A1','\u2620\uFE0F','\u{1F41B}'] }, matrix: { banner: 'CASCADE FAILURE', ticker: ['SENTINEL TRACE', 'GLYPH SATURATION', 'PHOSPHOR BURN-IN', 'RAIN AT TERMINAL VELOCITY'], scraps: ['SENTINEL', '0XDECODE', 'OVERRIDE', 'TRACE LOST', 'MACHINE DREAM'], flash: 'rgba(180,255,190,0.68)', emoji: ['\u{1F441}\uFE0F','\u{1F4A0}','\u{1F916}','\u{1F50D}','\u26D3\uFE0F'] }, neon: { banner: 'GAS DISCHARGE', ticker: ['TUBE ARC', 'ULTRAVIOLET BLEED', 'STROBE LOCK', 'SHORT CIRCUIT'], scraps: ['ARC OVERLOAD', 'PLASMA SIGN', 'NOBLE GAS', 'HARD STROBE', 'OVERDRIVE'], flash: 'rgba(255,245,170,0.72)', emoji: ['\u26A1','\u{1F4A5}','\u{1F52E}','\u2728','\u{1F388}'] }, ocean: { banner: 'HADAL DESCENT', ticker: ['PRESSURE SPIKE', 'BIOLUMINESCENT SWARM', 'ABYSSAL DRAG', 'TSUNAMI FRONT'], scraps: ['NO SURFACE', 'CRUSH DEPTH', 'TENTACLE DRIFT', 'SONAR LOST', 'DEEP CURRENT'], flash: 'rgba(200,255,255,0.54)', emoji: ['\u{1F419}','\u{1F420}','\u{1F30A}','\u{1F41A}','\u{1F9DC}'] }, plasma: { banner: 'FUSION BREACH', ticker: ['CONTAINMENT FAILURE', 'TOKAMAK DISTORTION', 'THERMAL RUNAWAY', 'WHITE-BLUE CORE'], scraps: ['ION SPRAY', 'FIELD LOSS', 'HEAT HAZE', 'QUENCH', 'ARC SHELL'], flash: 'rgba(230,250,255,0.78)', emoji: ['\u{1F300}','\u26A1','\u{1F4A0}','\u2728','\u{1F52C}'] }, retro: { banner: 'TAPE EAT', ticker: ['TRACKING LOSS', 'CHROMA SPLIT', 'MAGNETIC SNOW', 'CLICK-EJECT'], scraps: ['NO SIGNAL', 'HEAD DRAG', 'ROLL HOLD', 'SNOW PACK', 'EJECT CYCLE'], flash: 'rgba(255,226,178,0.6)', emoji: ['\u{1F4FC}','\u{1F4FA}','\u{1F3AE}','\u{1F579}\uFE0F','\u{1F4FB}'] }, retrofuture: { banner: 'ATOMIC TWILIGHT', ticker: ['GEIGER STATIC', 'FALLOUT DUST', 'RADIATION BURN', 'IRRADIATED SEPIA'], scraps: ['FALLOUT', 'BETA LEAK', 'ASH DRIFT', 'HALF-LIFE', 'GLOW CLOUD'], flash: 'rgba(255,240,180,0.62)', emoji: ['\u2622\uFE0F','\u{1F4A3}','\u{1F3ED}','\u2623\uFE0F','\u{1F9EA}'] }, spaceage: { banner: 'RE-ENTRY BURN', ticker: ['HEAT SHIELD LOSS', 'PLASMA BLACKOUT', 'COMMS STATIC', 'G-FORCE COMPRESSION'], scraps: ['BLACKOUT', 'SPARK SHOWER', 'PLASMA SHEATH', 'HULL GLOW', 'COMMS LOST'], flash: 'rgba(255,220,190,0.68)', emoji: ['\u{1F680}','\u{1F6F8}','\u{1FA90}','\u{1F30D}','\u2B50'] }, synthwave: { banner: 'GRID COLLAPSE', ticker: ['VOID PERSPECTIVE', 'MOLTEN SUN', 'CHROMA TEAR', 'OUT OF MEMORY'], scraps: ['VOID GRID', 'SUN DRIP', 'NEON PANIC', 'FRAME DROP', 'MEMORY STARVE'], flash: 'rgba(255,210,255,0.68)', emoji: ['\u{1F305}','\u{1F3B6}','\u{1F3B9}','\u{1F338}','\u{1F52E}'] }, terminal: { banner: 'FORK BOMB', ticker: ['PROCESS STORM', 'STACK TRACE WATERFALL', 'MEMORY GARBAGE', 'BSOD CREEP'], scraps: ['PID 65535', 'STACK OVERFLOW', 'OOM KILL', 'PANIC', '(:'], flash: 'rgba(180,255,180,0.7)', emoji: ['\u{1F4BB}','\u{1F41B}','\u2620\uFE0F','\u{1F5A5}\uFE0F','\u26A1'] }, tropicale: { banner: 'CATEGORY 5', ticker: ['HORIZONTAL RAIN', 'STORM SURGE', 'DEBRIS FIELD', 'WIND SHEAR'], scraps: ['PALM SNAP', 'SURGE LINE', 'SPRAY WALL', 'FLYING ROOF', 'LANDFALL'], flash: 'rgba(240,255,255,0.74)', emoji: ['\u{1F334}','\u{1F3D6}\uFE0F','\u{1F940}','\u{1F965}','\u{1F30A}'] }, noir: { banner: 'BLACKOUT DISTRICT', ticker: ['BLINDS SLAMMED SHUT', 'SIREN SWEEP', 'PROJECTOR BURN', 'MIDNIGHT DOWNPOUR'], scraps: ['NO WITNESSES', 'WET ASPHALT', 'RED CHANNEL', 'BLUE CHANNEL', 'SMOKE CURTAIN'], flash: 'rgba(255,245,225,0.66)', emoji: ['\u{1F576}\uFE0F','\u{1F52B}','\u{1F3A9}','\u{1F6AC}','\u{1F5DD}\uFE0F'] }, cathedral: { banner: 'LAST JUDGMENT', ticker: ['BELL SHOCKWAVE', 'INCENSE FIRESTORM', 'ROSE WINDOW FRACTURE', 'APSE IN FLAME'], scraps: ['REQUIEM', 'SHARD RAIN', 'VESPER BURN', 'GLORIA STATIC', 'NAVE COLLAPSE'], flash: 'rgba(255,239,202,0.72)', emoji: ['\u{1F54E}','\u{1F56F}\uFE0F','\u271D\uFE0F','\u{1F54A}\uFE0F','\u{1F3F0}'] }, surveillance: { banner: 'TOTAL COMPROMISE', ticker: ['CAMERA MESH BREACH', 'TRACKING LOSS', 'MULTIPLEX PANIC', 'ALERT CASCADE'], scraps: ['FLAGGED', 'OVERRIDDEN', 'TRACE LOOP', 'BOX LOST', 'ALERT 99'], flash: 'rgba(210,255,225,0.72)', emoji: ['\u{1F4F9}','\u{1F441}\uFE0F','\u{1F6A8}','\u{1F50D}','\u{1F4E1}'] }, biomech: { banner: 'CONTAINMENT RUPTURE', ticker: ['SYNAPSE STORM', 'TISSUE ARC', 'MEMBRANE TEAR', 'HYBRID OVERDRIVE'], scraps: ['VENTRICLE', 'MYCELIUM', 'RUPTURE', 'BIOFILM', 'NERVE GRID'], flash: 'rgba(255,205,220,0.7)', emoji: ['\u{1F9EC}','\u{1F9E0}','\u{1F9A0}','\u{1F52C}','\u{1FAC0}'] }, paper: { banner: 'PRESS JAM', ticker: ['TONER BLIZZARD', 'INK BLEED', 'COPY LAMP WHITEOUT', 'PAGE STORM'], scraps: ['MISPRINT', 'SKEWED FEED', 'RAG EDGE', 'CARBON DUST', 'REDACTION'], flash: 'rgba(255,250,236,0.82)', emoji: ['\u{1F4C4}','\u270F\uFE0F','\u{1F4CE}','\u2702\uFE0F','\u{1F5DE}\uFE0F'] }, volcano: { banner: 'PYROCLASTIC SURGE', ticker: ['ASH CASCADE', 'LAVA BOMB IMPACT', 'EARTHQUAKE SHAKE', 'SULFUR CLOUD'], scraps: ['ASHFALL', 'VENT BLAST', 'PYROCLAST', 'SEISMIC HIT', 'MAGMA SPRAY'], flash: 'rgba(255,220,150,0.72)', emoji: ['\u{1F30B}','\u{1F525}','\u{1F4A5}','\u2668\uFE0F','\u{1FAA8}'] } }; function snonuxDetectThemeName() { // The shell sets window.SNONUX_CURRENT_THEME synchronously in . // Falls back to the splash class for resilience if something raced. if (typeof window !== 'undefined' && window.SNONUX_CURRENT_THEME) { return window.SNONUX_CURRENT_THEME; } var splash = document.getElementById('splash-overlay'); if (splash) { for (var i = 0; i < splash.classList.length; i++) { var cls = splash.classList[i]; if (cls.indexOf('splash-') === 0 && cls !== 'splash-overlay' && cls.indexOf('splash--') !== 0) { return cls.slice(7); } } } return 'neon'; } // 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 (_) {} // 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