+
nav
- jk↑↓ post
- hl←→ page
- PgUpPgDn scroll
- Enter open
- Esc close
+
+
+
+
+
+
fx
-
-
-
-
-
-
-
- t theme
+
+
+
+
+
+
+
+
{{end}}
-
{{define "navmodal"}}
{{end}}
-
diff --git a/internal/generator/templates/shared/shared.css b/internal/generator/templates/shared/shared.css
index 5fab9ac..35f4aef 100644
--- a/internal/generator/templates/shared/shared.css
+++ b/internal/generator/templates/shared/shared.css
@@ -21,10 +21,12 @@
gap:clamp(16px,4vw,48px); }
/* Flex column layout: let #post-content shrink so overflow-y scrolls; footer stays visible */
#post-content.content { min-height:0; }
-.page-nav-footer { flex-shrink:0; width:100%; box-sizing:border-box; }
+ .page-nav-footer { flex-shrink:0; width:100%; box-sizing:border-box; }
.page-nav-footer .page-nav { margin:0; }
/* ~Half-height footer bar vs default .page-nav padding */
.page-nav-footer .page-nav a { padding-top:4px; padding-bottom:4px; }
+/* Reorganized nav-hints shared layout */
+.nav-hints { display:flex; flex-direction:column; gap:0; padding:0 !important; }
/* Shared nav FX keyframes — themes apply these classes for brief effects */
/* Modal open: post zooms/flies into the modal overlay */
@keyframes sno-modal-zoom { 0%{transform:scale(0.82) translateY(18px);opacity:0} 60%{transform:scale(1.02) translateY(-3px);opacity:1} 100%{transform:scale(1) translateY(0);opacity:1} }
@@ -947,9 +949,56 @@ body[data-sno-theme="matrix"] .post-text img,
body[data-sno-theme="terminal"] .post-text img,
body[data-sno-theme="dos"] .post-text img { animation:sno-matrix-decode 0.8s ease-out both; }
-/* Mobile touch-target and padding overrides */
+/* Mobile touch-target and layout overrides */
@media(max-width:640px) {
+ .nav-hints { padding:4px 14px !important; gap:0; position:sticky; top:0; z-index:25; }
+ /* Remove navigation row on mobile */
+ .nav-hints-nav { display:none !important; }
+ /* FX row: single horizontal line of icon-only tiles */
+ .nav-hints-fx { display:flex !important; flex-direction:row !important;
+ flex-wrap:nowrap; padding:0; gap:4px; overflow-x:auto;
+ -webkit-overflow-scrolling:touch; scrollbar-width:none; }
+ .nav-hints-fx::-webkit-scrollbar { display:none; }
+ .nav-hints-label, .nav-hints-spacer,
+ .nav-hints-row kbd, .sno-btn-text { display:none !important; }
+ .nav-fx-button {
+ flex:1 1 auto; min-height:44px; min-width:36px; max-width:none;
+ justify-content:center; align-items:center; gap:0; padding:0;
+ font-size:0; width:auto;
+ }
+ .nav-fx-button i, .nav-fx-button i.fas { font-size:1rem; display:inline-block; }
+ /* Suppress the theme select dropdown in the bar */
+ .nav-hints-fx #sno-theme-select { display:none !important; }
.modal-inner { padding:20px 14px !important; }
.transmit-btn { padding-top:11px !important; padding-bottom:11px !important; min-height:44px !important; }
.page-nav a { padding-top:10px !important; padding-bottom:10px !important; min-height:44px !important; }
+ /* Floating action button for quick one-handed control */
+ #sno-fab {
+ position:fixed; right:14px; bottom:14px; z-index:5001;
+ width:56px; height:56px; border-radius:50%;
+ border:0; background:rgba(0,0,0,0.75); color:#fff;
+ font-size:1.2rem; cursor:pointer; display:flex; align-items:center; justify-content:center;
+ box-shadow:0 4px 16px rgba(0,0,0,0.48); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
+ transition:transform 0.18s ease, box-shadow 0.18s ease;
+ -webkit-tap-highlight-color:transparent;
+ }
+ #sno-fab:focus-visible { outline:2px solid #fff; outline-offset:3px; }
+ #sno-fab:active { transform:scale(0.94); }
+ #sno-fab .sno-fab-menu {
+ position:absolute; bottom:64px; right:0;
+ display:flex; flex-direction:column; gap:10px;
+ opacity:0; pointer-events:none; transform:translateY(6px);
+ transition:opacity 0.2s ease, transform 0.2s ease;
+ }
+ #sno-fab.open .sno-fab-menu { opacity:1; pointer-events:auto; transform:translateY(0); }
+ #sno-fab .sno-fab-menu button {
+ width:48px; height:48px; border-radius:50%;
+ border:0; background:rgba(0,0,0,0.72); color:#fff;
+ font-size:1.05rem; display:flex; align-items:center; justify-content:center;
+ box-shadow:0 2px 10px rgba(0,0,0,0.35); backdrop-filter:blur(4px);
+ -webkit-tap-highlight-color:transparent;
+ }
+ #sno-fab .sno-fab-menu button:active { transform:scale(0.92); }
+ /* Swipe hint for first-time mobile visitors */
+ #sno-swipe-hint { display:flex; }
}
diff --git a/internal/generator/templates/shared/shared.js b/internal/generator/templates/shared/shared.js
index 09de580..1e29831 100644
--- a/internal/generator/templates/shared/shared.js
+++ b/internal/generator/templates/shared/shared.js
@@ -471,6 +471,9 @@
var melodyIndex = 0;
var drumTimer = null;
var drumIndex = 0;
+ var ambientAudio = null;
+ var ambientAudioSrc = '';
+ var ambientAudioFadeTimer = null;
function wildifyPreset(base) {
if (!base) return base;
@@ -493,9 +496,108 @@
var ambient = SNONUX_SOUNDS.ambient;
if (!ambient) return null;
var base = isWild ? (ambient.wild || ambient.normal) : ambient.normal;
+ if (base && !base.file && ambient.normal && ambient.normal.file) {
+ var inherited = {};
+ for (var k in base) inherited[k] = base[k];
+ inherited.file = ambient.normal.file;
+ if (inherited.volume == null && ambient.normal.volume != null) inherited.volume = ambient.normal.volume;
+ base = inherited;
+ }
return isWild ? wildifyPreset(base) : base;
}
+ function resolveAmbientFile(file) {
+ if (!file) return '';
+ if (/^(https?:)?\/\//.test(file) || file.charAt(0) === '/') return file;
+ if (file.indexOf('themes/') === 0) return file;
+ return 'themes/' + snonuxDetectThemeName() + '/' + file;
+ }
+
+ function ambientFileVolume(preset) {
+ var v = preset && preset.volume != null ? preset.volume : 0.46;
+ if (isWild) v = Math.min(v * 1.08, 0.8);
+ return Math.max(0, Math.min(v, 0.8));
+ }
+
+ function clearAmbientAudioFade() {
+ if (ambientAudioFadeTimer) {
+ clearInterval(ambientAudioFadeTimer);
+ ambientAudioFadeTimer = null;
+ }
+ }
+
+ function fadeAmbientAudioTo(target, duration, done) {
+ if (!ambientAudio) {
+ if (done) done();
+ return;
+ }
+ clearAmbientAudioFade();
+ var start = ambientAudio.volume || 0;
+ var dur = Math.max(duration || 0.35, 0.01);
+ var started = Date.now();
+ ambientAudioFadeTimer = setInterval(function() {
+ if (!ambientAudio) {
+ clearAmbientAudioFade();
+ if (done) done();
+ return;
+ }
+ var t = Math.min((Date.now() - started) / (dur * 1000), 1);
+ ambientAudio.volume = start + (target - start) * t;
+ if (t >= 1) {
+ clearAmbientAudioFade();
+ if (done) done();
+ }
+ }, 30);
+ }
+
+ function stopFileAudio(resetPosition) {
+ clearAmbientAudioFade();
+ if (!ambientAudio) return;
+ try {
+ ambientAudio.pause();
+ ambientAudio.volume = 0;
+ if (resetPosition) ambientAudio.currentTime = 0;
+ } catch (_) {}
+ }
+
+ function startFileAudio(preset) {
+ var src = resolveAmbientFile(preset.file);
+ if (!src) return;
+ currentPreset = preset;
+
+ if (!ambientAudio || ambientAudioSrc !== src) {
+ if (ambientAudio) {
+ try { ambientAudio.pause(); } catch (_) {}
+ }
+ ambientAudio = new Audio(src);
+ ambientAudio.loop = true;
+ ambientAudio.preload = 'auto';
+ ambientAudio.volume = 0.22;
+ ambientAudioSrc = src;
+ }
+
+ isPlaying = true;
+ var target = ambientFileVolume(preset);
+ if (ambientAudio.volume < 0.18) ambientAudio.volume = Math.min(target, 0.22);
+ var playResult;
+ try {
+ playResult = ambientAudio.play();
+ } catch (_) {
+ isPlaying = false;
+ return;
+ }
+ if (playResult && playResult.then) {
+ playResult.then(function() {
+ if (!isPlaying) return;
+ fadeAmbientAudioTo(target, 0.16);
+ }).catch(function() {
+ isPlaying = false;
+ });
+ } else {
+ fadeAmbientAudioTo(target, 0.16);
+ }
+ }
+
function ensureCtx() {
if (!ctx) {
ctx = new (window.AudioContext || window.webkitAudioContext)();
@@ -732,6 +834,7 @@
stopDrums();
stopDrones();
stopNoise();
+ stopFileAudio(true);
}
// ── drum synthesizer ──────────────────────────────────────────────
@@ -849,6 +952,10 @@
var preset = getPreset();
if (!preset) return;
currentPreset = preset;
+ if (preset.file) {
+ startFileAudio(preset);
+ return;
+ }
ensureCtx();
// Begin scheduling only once the AudioContext is running.
@@ -887,7 +994,12 @@
function pauseEngine() {
isPlaying = false;
- var fadeOut = currentPreset && currentPreset.release != null ? currentPreset.release : 0.5;
+ var fadeOut = ambientAudio ? 0.5 : (currentPreset && currentPreset.release != null ? currentPreset.release : 0.5);
+ if (ambientAudio) {
+ fadeAmbientAudioTo(0, fadeOut, function() {
+ if (!isPlaying) stopFileAudio(false);
+ });
+ }
fadeMasterTo(0, fadeOut);
setTimeout(function() {
if (!isPlaying) stopAll();
@@ -919,10 +1031,15 @@
return;
}
fadeMasterTo(0, 0.3);
+ if (ambientAudio) fadeAmbientAudioTo(0, 0.3);
setTimeout(function() {
if (!isPlaying) return;
stopAll();
currentPreset = newPreset;
+ if (newPreset.file) {
+ startFileAudio(newPreset);
+ return;
+ }
melodyIndex = 0;
startDrones(newPreset);
startNoise(newPreset);
@@ -946,6 +1063,10 @@
// we keep hearing whatever was cached as silence on top of it.
stopAll();
currentPreset = preset;
+ if (preset.file) {
+ startFileAudio(preset);
+ return;
+ }
melodyIndex = 0;
startDrones(preset);
startNoise(preset);
@@ -1741,18 +1862,117 @@
blank: toggleBlankMode,
ambient: toggleAmbientMode,
flash: triggerFlashEffect,
- scatter: triggerScatterEffect
+ scatter: triggerScatterEffect,
+ theme: function() {
+ var pick = snonuxRandomTheme();
+ if (pick) snonuxSwitchTheme(pick);
+ }
+ };
+ var navHandlers = {
+ 'prev-post': function() {
+ if (currentIndex <= 0) { bounceEffect('up'); }
+ else { selectPost(currentIndex - 1, 'up'); }
+ },
+ 'next-post': function() {
+ if (currentIndex >= posts.length - 1) { bounceEffect('down'); }
+ else { selectPost(currentIndex + 1, 'down'); }
+ },
+ 'prev-page': function() {
+ if (prevPageURL) { window.location.href = prevPageURL; }
+ else { bounceEffect('left'); }
+ },
+ 'next-page': function() {
+ if (nextPageURL) { window.location.href = nextPageURL; }
+ else { bounceEffect('right'); }
+ },
+ 'open': function() { openPostAt(currentIndex, true); },
+ 'close': function() {
+ if (document.getElementById('post-modal').classList.contains('active')) closeModal();
+ }
};
document.querySelectorAll('.nav-fx-button').forEach(function(button) {
button.addEventListener('click', function(e) {
e.preventDefault();
var fx = button.getAttribute('data-sno-fx');
if (fxHandlers[fx]) fxHandlers[fx]();
+ var nav = button.getAttribute('data-sno-nav');
+ if (navHandlers[nav]) navHandlers[nav]();
});
});
syncFxButtonStates();
})();
+ // Bind clickables on splash overlay so it is easy to tap-open.
+ (function touchSplashDismiss() {
+ var splash = document.getElementById('splash-overlay');
+ if (!splash || splash.classList.contains('splash--dismissed')) return;
+ splash.addEventListener('click', function() {
+ if (window._snonuxDismissSplash) window._snonuxDismissSplash();
+ });
+ })();
+
+ // Mobile floating action button (FAB): appears only on narrow viewports when nav-hints
+ // are visible but too cramped. Provides one-tap access to wild, music, and flash.
+ (function mobileFAB() {
+ if (document.getElementById('sno-fab')) return;
+ var btn = document.createElement('button');
+ btn.id = 'sno-fab';
+ btn.setAttribute('aria-label', 'Open controls menu');
+ btn.setAttribute('aria-haspopup', 'true');
+ btn.setAttribute('aria-expanded', 'false');
+ btn.innerHTML = '
' +
+ '';
+ document.body.appendChild(btn);
+
+ var fxHandlers = {
+ wild: function() { toggleWildMode(); },
+ crt: toggleCrtMode,
+ ghost: toggleGhostMode,
+ blank: toggleBlankMode,
+ ambient: toggleAmbientMode,
+ flash: triggerFlashEffect,
+ scatter: triggerScatterEffect,
+ theme: function() {
+ var pick = snonuxRandomTheme();
+ if (pick) snonuxSwitchTheme(pick);
+ }
+ };
+
+ var menu = btn.querySelector('.sno-fab-menu');
+ var open = false;
+ function setOpen(o) {
+ open = o;
+ btn.classList.toggle('open', open);
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ menu.setAttribute('aria-hidden', open ? 'false' : 'true');
+ }
+ btn.addEventListener('click', function(e) {
+ if (e.target.closest('[role="menuitem"]')) return;
+ e.preventDefault();
+ setOpen(!open);
+ });
+ btn.querySelectorAll('[role="menuitem"]').forEach(function(item) {
+ item.addEventListener('click', function(e) {
+ e.stopPropagation();
+ var fx = item.getAttribute('data-sno-fx');
+ if (fxHandlers[fx]) fxHandlers[fx]();
+ setOpen(false);
+ });
+ });
+ document.addEventListener('click', function(e) {
+ if (open && !btn.contains(e.target)) setOpen(false);
+ });
+ })();
+
// Restore ambient preference on load (opt-in; default off).
(function initAmbientPreference() {
if (snonuxAmbientLoadPreference() && window.snonuxAmbientStart) {
diff --git a/internal/generator/templates/themes/aurora/MUSIC_LICENSE.txt b/internal/generator/templates/themes/aurora/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..8b49cf4
--- /dev/null
+++ b/internal/generator/templates/themes/aurora/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Aurora
+Author: Trex0n
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/aurora-0
+
+Bundled as ambient.ogg for the snonux aurora theme.
diff --git a/internal/generator/templates/themes/aurora/ambient.ogg b/internal/generator/templates/themes/aurora/ambient.ogg
new file mode 100644
index 0000000..918b7b3
Binary files /dev/null and b/internal/generator/templates/themes/aurora/ambient.ogg differ
diff --git a/internal/generator/templates/themes/aurora/sounds.json b/internal/generator/templates/themes/aurora/sounds.json
index 1440fe9..35aa20d 100644
--- a/internal/generator/templates/themes/aurora/sounds.json
+++ b/internal/generator/templates/themes/aurora/sounds.json
@@ -345,7 +345,9 @@
"_",
"hat",
"clap"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 170,
@@ -441,7 +443,9 @@
"_",
"hat",
"clap"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/aurora/theme.css b/internal/generator/templates/themes/aurora/theme.css
index 26f4c39..dd8bc7c 100644
--- a/internal/generator/templates/themes/aurora/theme.css
+++ b/internal/generator/templates/themes/aurora/theme.css
@@ -70,7 +70,7 @@
box-shadow:0 0 60px rgba(0,255,179,0.25); padding:40px; }
.modal-close { float:right; background:none; border:none; color:var(--teal);
font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:12px 18px;} .content{padding:14px 18px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="aurora"] .splash-overlay {
background: radial-gradient(ellipse 120% 80% at 50% 120%, rgba(0,207,232,0.12) 0%, transparent 45%),
radial-gradient(ellipse 90% 60% at 20% 20%, rgba(192,132,252,0.08) 0%, transparent 50%),
diff --git a/internal/generator/templates/themes/biomech/MUSIC_LICENSE.txt b/internal/generator/templates/themes/biomech/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..a29563a
--- /dev/null
+++ b/internal/generator/templates/themes/biomech/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Factory ambiance
+Author: yd
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/factory-ambiance
+
+Bundled as ambient.ogg for the snonux biomech theme.
diff --git a/internal/generator/templates/themes/biomech/ambient.ogg b/internal/generator/templates/themes/biomech/ambient.ogg
new file mode 100644
index 0000000..ca4aff5
Binary files /dev/null and b/internal/generator/templates/themes/biomech/ambient.ogg differ
diff --git a/internal/generator/templates/themes/biomech/sounds.json b/internal/generator/templates/themes/biomech/sounds.json
index ecb54ae..9cc28d5 100644
--- a/internal/generator/templates/themes/biomech/sounds.json
+++ b/internal/generator/templates/themes/biomech/sounds.json
@@ -370,7 +370,9 @@
"_",
"kick",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 190,
@@ -486,7 +488,9 @@
"_",
"kick",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/biomech/theme.css b/internal/generator/templates/themes/biomech/theme.css
index 50d9fcf..bf0c0aa 100644
--- a/internal/generator/templates/themes/biomech/theme.css
+++ b/internal/generator/templates/themes/biomech/theme.css
@@ -53,7 +53,7 @@
.post-modal.active { display:block; }
.modal-inner { max-width:760px; margin:0 auto; background:rgba(11,9,16,0.98); border:1px solid rgba(147,255,216,0.18); padding:34px; box-shadow:0 22px 76px rgba(0,0,0,0.72); }
.modal-close { float:right; background:none; border:none; color:var(--acid); font-family:'IBM Plex Mono',monospace; font-size:0.78rem; cursor:pointer; letter-spacing:0.18em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="biomech"] .splash-overlay {
background:
radial-gradient(circle at 50% 22%, rgba(245,91,125,0.14) 0%, transparent 28%),
diff --git a/internal/generator/templates/themes/brutalist/MUSIC_LICENSE.txt b/internal/generator/templates/themes/brutalist/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..b201504
--- /dev/null
+++ b/internal/generator/templates/themes/brutalist/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Insistent: background loop
+Author: yd
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/insistent-background-loop
+
+Bundled as ambient.ogg for the snonux brutalist theme.
diff --git a/internal/generator/templates/themes/brutalist/ambient.ogg b/internal/generator/templates/themes/brutalist/ambient.ogg
new file mode 100644
index 0000000..4329605
Binary files /dev/null and b/internal/generator/templates/themes/brutalist/ambient.ogg differ
diff --git a/internal/generator/templates/themes/brutalist/sounds.json b/internal/generator/templates/themes/brutalist/sounds.json
index ffafee1..05871e4 100644
--- a/internal/generator/templates/themes/brutalist/sounds.json
+++ b/internal/generator/templates/themes/brutalist/sounds.json
@@ -220,7 +220,9 @@
"kick",
"_",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 180,
@@ -336,7 +338,9 @@
"kick",
"_",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/brutalist/theme.css b/internal/generator/templates/themes/brutalist/theme.css
index c8b415a..86a623f 100644
--- a/internal/generator/templates/themes/brutalist/theme.css
+++ b/internal/generator/templates/themes/brutalist/theme.css
@@ -53,7 +53,7 @@
box-shadow:8px 8px 0 var(--red); }
.modal-close { float:right; background:none; border:none; color:var(--red);
font-family:Impact; font-size:1.3rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .logo-mark{font-size:2rem;} .content{padding:12px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:10px 16px;} .logo-mark{font-size:2rem;} .content{padding:12px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="brutalist"] .splash-overlay { background:#000; }
[data-sno-theme="brutalist"] .splash-frame {
border:6px solid #fff; padding:clamp(1.5rem,5vw,2.5rem) clamp(1.25rem,4vw,2rem);
diff --git a/internal/generator/templates/themes/cathedral/MUSIC_LICENSE.txt b/internal/generator/templates/themes/cathedral/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..7e1ecfd
--- /dev/null
+++ b/internal/generator/templates/themes/cathedral/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Cathedral
+Author: Umplix
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/cathedral-0
+
+Bundled as ambient.ogg for the snonux cathedral theme.
diff --git a/internal/generator/templates/themes/cathedral/ambient.ogg b/internal/generator/templates/themes/cathedral/ambient.ogg
new file mode 100644
index 0000000..5a9c0a2
Binary files /dev/null and b/internal/generator/templates/themes/cathedral/ambient.ogg differ
diff --git a/internal/generator/templates/themes/cathedral/sounds.json b/internal/generator/templates/themes/cathedral/sounds.json
index 8fab3fe..ca0837b 100644
--- a/internal/generator/templates/themes/cathedral/sounds.json
+++ b/internal/generator/templates/themes/cathedral/sounds.json
@@ -450,7 +450,9 @@
"kick",
"kick",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 180,
@@ -566,7 +568,9 @@
"kick",
"kick",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/cathedral/theme.css b/internal/generator/templates/themes/cathedral/theme.css
index 3451e50..29fe7ec 100644
--- a/internal/generator/templates/themes/cathedral/theme.css
+++ b/internal/generator/templates/themes/cathedral/theme.css
@@ -76,7 +76,7 @@
.post-modal.active { display:block; }
.modal-inner { max-width:800px; margin:0 auto; background:rgba(15,11,18,0.98); border:1px solid rgba(224,196,127,0.2); padding:38px; box-shadow:0 28px 84px rgba(0,0,0,0.72); }
.modal-close { float:right; background:none; border:none; color:var(--gold); font-family:'Cinzel',serif; font-size:0.8rem; font-weight:400; cursor:pointer; letter-spacing:0.14em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="cathedral"] .splash-overlay {
background:
radial-gradient(circle at 50% 18%, rgba(224,196,127,0.18) 0%, transparent 30%),
diff --git a/internal/generator/templates/themes/cosmos/MUSIC_LICENSE.txt b/internal/generator/templates/themes/cosmos/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..6e17e16
--- /dev/null
+++ b/internal/generator/templates/themes/cosmos/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Space Music: Out There
+Author: yd
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://lpc.opengameart.org/content/space-music-out-there
+
+Bundled as ambient.ogg for the snonux cosmos theme.
diff --git a/internal/generator/templates/themes/cosmos/ambient.ogg b/internal/generator/templates/themes/cosmos/ambient.ogg
new file mode 100644
index 0000000..fea0ef4
Binary files /dev/null and b/internal/generator/templates/themes/cosmos/ambient.ogg differ
diff --git a/internal/generator/templates/themes/cosmos/sounds.json b/internal/generator/templates/themes/cosmos/sounds.json
index 3387d82..59ef4c2 100644
--- a/internal/generator/templates/themes/cosmos/sounds.json
+++ b/internal/generator/templates/themes/cosmos/sounds.json
@@ -370,7 +370,9 @@
"_",
"_",
"_"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 180,
@@ -491,7 +493,9 @@
"kick",
"kick",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/cosmos/theme.css b/internal/generator/templates/themes/cosmos/theme.css
index ed09691..5493ac4 100644
--- a/internal/generator/templates/themes/cosmos/theme.css
+++ b/internal/generator/templates/themes/cosmos/theme.css
@@ -70,7 +70,7 @@
box-shadow:0 0 60px rgba(255,209,102,0.25); padding:40px; backdrop-filter:blur(16px); }
.modal-close { float:right; background:none; border:none; color:var(--gold);
font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:12px 18px;} .content{padding:14px 18px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="cosmos"] .splash-overlay { background: radial-gradient(ellipse 100% 80% at 50% 100%, rgba(155,93,229,0.2) 0%, transparent 55%), var(--bg); }
[data-sno-theme="cosmos"] .splash-stars {
position:absolute; inset:0; pointer-events:none; opacity:0.5;
diff --git a/internal/generator/templates/themes/dos/MUSIC_LICENSE.txt b/internal/generator/templates/themes/dos/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..183d435
--- /dev/null
+++ b/internal/generator/templates/themes/dos/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Insert Coin
+Author: megupets
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/insert-coin
+
+Bundled as ambient.ogg for the snonux dos theme.
diff --git a/internal/generator/templates/themes/dos/ambient.ogg b/internal/generator/templates/themes/dos/ambient.ogg
new file mode 100644
index 0000000..66ca906
Binary files /dev/null and b/internal/generator/templates/themes/dos/ambient.ogg differ
diff --git a/internal/generator/templates/themes/dos/sounds.json b/internal/generator/templates/themes/dos/sounds.json
index f418ad1..377afbc 100644
--- a/internal/generator/templates/themes/dos/sounds.json
+++ b/internal/generator/templates/themes/dos/sounds.json
@@ -205,7 +205,9 @@
"_",
"hat",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 160,
@@ -361,7 +363,9 @@
"_",
"hat",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/dos/theme.css b/internal/generator/templates/themes/dos/theme.css
index 5a90699..56515f1 100644
--- a/internal/generator/templates/themes/dos/theme.css
+++ b/internal/generator/templates/themes/dos/theme.css
@@ -73,7 +73,7 @@
font-size:1rem; cursor:pointer; padding:2px 8px; }
.modal-close:hover { background:var(--dos-blue); color:var(--dos-bwhite);
border-style:inset; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:6px 12px;} .content{padding:8px 12px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:6px 12px;} .content{padding:8px 12px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="dos"] .splash-overlay { background:var(--dos-blue); font-family:var(--dos-font); }
[data-sno-theme="dos"] .splash-inner { position:relative; z-index:1; }
[data-sno-theme="dos"] .splash-title {
diff --git a/internal/generator/templates/themes/matrix/MUSIC_LICENSE.txt b/internal/generator/templates/themes/matrix/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..ec8803f
--- /dev/null
+++ b/internal/generator/templates/themes/matrix/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Loading screen loop
+Author: Brandon Morris, submitted by HaelDB
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/loading-screen-loop
+
+Bundled as ambient.ogg for the snonux matrix theme.
diff --git a/internal/generator/templates/themes/matrix/ambient.ogg b/internal/generator/templates/themes/matrix/ambient.ogg
new file mode 100644
index 0000000..99562b5
Binary files /dev/null and b/internal/generator/templates/themes/matrix/ambient.ogg differ
diff --git a/internal/generator/templates/themes/matrix/sounds.json b/internal/generator/templates/themes/matrix/sounds.json
index 5087473..c3c1f4c 100644
--- a/internal/generator/templates/themes/matrix/sounds.json
+++ b/internal/generator/templates/themes/matrix/sounds.json
@@ -161,7 +161,9 @@
"_",
"hat",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 180,
@@ -277,7 +279,9 @@
"_",
"hat",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/matrix/theme.css b/internal/generator/templates/themes/matrix/theme.css
index a305daf..3d19413 100644
--- a/internal/generator/templates/themes/matrix/theme.css
+++ b/internal/generator/templates/themes/matrix/theme.css
@@ -68,7 +68,7 @@
box-shadow:0 0 40px rgba(0,255,65,0.25); }
.modal-close { float:right; background:none; border:none; color:var(--g2);
font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:10px 16px;} .content{padding:10px 16px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="matrix"] .splash-overlay { background: #000; font-family:'VT323','Courier New',monospace; }
[data-sno-theme="matrix"] .splash-rain {
position:absolute; inset:0; overflow:hidden; pointer-events:none; opacity:0.35; z-index:1;
diff --git a/internal/generator/templates/themes/neon/MUSIC_LICENSE.txt b/internal/generator/templates/themes/neon/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..bf7ee33
--- /dev/null
+++ b/internal/generator/templates/themes/neon/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Electric
+Author: Sudocolon
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/electric-0
+
+Bundled as ambient.ogg for the snonux neon theme.
diff --git a/internal/generator/templates/themes/neon/ambient.ogg b/internal/generator/templates/themes/neon/ambient.ogg
new file mode 100644
index 0000000..546e482
Binary files /dev/null and b/internal/generator/templates/themes/neon/ambient.ogg differ
diff --git a/internal/generator/templates/themes/neon/sounds.json b/internal/generator/templates/themes/neon/sounds.json
index a7a29cf..8498b16 100644
--- a/internal/generator/templates/themes/neon/sounds.json
+++ b/internal/generator/templates/themes/neon/sounds.json
@@ -220,7 +220,9 @@
"_",
"hat",
"clap"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 220,
@@ -331,7 +333,9 @@
"_",
"hat",
"clap"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/noir/MUSIC_LICENSE.txt b/internal/generator/templates/themes/noir/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..68d3724
--- /dev/null
+++ b/internal/generator/templates/themes/noir/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Moil
+Author: Ruskerdax
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/moil
+
+Bundled as ambient.ogg for the snonux noir theme.
diff --git a/internal/generator/templates/themes/noir/ambient.ogg b/internal/generator/templates/themes/noir/ambient.ogg
new file mode 100644
index 0000000..d65b510
Binary files /dev/null and b/internal/generator/templates/themes/noir/ambient.ogg differ
diff --git a/internal/generator/templates/themes/noir/sounds.json b/internal/generator/templates/themes/noir/sounds.json
index 67a5273..1762cd3 100644
--- a/internal/generator/templates/themes/noir/sounds.json
+++ b/internal/generator/templates/themes/noir/sounds.json
@@ -370,7 +370,9 @@
"_",
"kick",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.46
},
"wild": {
"bpm": 190,
@@ -466,7 +468,9 @@
"_",
"kick",
"hat"
- ]
+ ],
+ "file": "ambient.ogg",
+ "volume": 0.54
}
}
}
diff --git a/internal/generator/templates/themes/noir/theme.css b/internal/generator/templates/themes/noir/theme.css
index c1b5b0d..d4f3587 100644
--- a/internal/generator/templates/themes/noir/theme.css
+++ b/internal/generator/templates/themes/noir/theme.css
@@ -79,7 +79,7 @@
.modal-inner { max-width:760px; margin:0 auto; background:rgba(10,10,10,0.98); border:1px solid rgba(240,234,214,0.22);
padding:38px; box-shadow:0 28px 80px rgba(0,0,0,0.72); }
.modal-close { float:right; background:none; border:none; color:var(--lamp); font-family:'IBM Plex Mono',monospace; font-size:0.82rem; cursor:pointer; letter-spacing:0.2em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
+ @media(max-width:640px) { .nav-hints{padding:6px 16px} .nav-hints .nav-hints-row{padding:6px 0;gap:8px} .nav-hints kbd{display:none} .nav-hints-label,.nav-hints-spacer{display:none} .nav-fx-button{padding:10px 0;min-height:44px;flex:1 1 auto;justify-content:center;gap:0.2rem} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
[data-sno-theme="noir"] .splash-overlay {
background:
radial-gradient(ellipse 40% 65% at 52% 24%, rgba(240,234,214,0.2) 0%, rgba(240,234,214,0.06) 26%, transparent 58%),
diff --git a/internal/generator/templates/themes/nukem/MUSIC_LICENSE.txt b/internal/generator/templates/themes/nukem/MUSIC_LICENSE.txt
new file mode 100644
index 0000000..e7720f8
--- /dev/null
+++ b/internal/generator/templates/themes/nukem/MUSIC_LICENSE.txt
@@ -0,0 +1,7 @@
+Track: Once More [Metal]
+Author: nene
+License: CC0 1.0 Universal / public domain dedication
+License URL: https://creativecommons.org/publicdomain/zero/1.0/
+Source: https://opengameart.org/content/once-more-metal
+
+Bundled as ambient.ogg for the snonux nukem theme.
diff --git a/internal/generator/templates/themes/nukem/ambient.ogg b/internal/generator/templates/themes/nukem/ambient.ogg
new file mode 100644
index 0000000..266944d
Binary files /dev/null and b/internal/generator/templates/themes/nukem/ambient.ogg differ
diff --git a/internal/generator/templates/themes/nukem/sounds.json b/internal/generator/templates/themes/nukem/sounds.json
index 42b9a82..867e9d5 100644
--- a/internal/generator/templates/themes/nukem/sounds.json
+++ b/internal/generator/templates/themes/nukem/sounds.json
@@ -205,7 +205,9 @@
"_",
"hat",
"kick"
- ]
+ ],
+ "file": "ambient.ogg",
+