summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-18 23:29:27 +0300
committerPaul Buetow <paul@buetow.org>2026-04-18 23:29:27 +0300
commit795cdaa73b48f7d63c85693f1b5e5e8f8f55e2d6 (patch)
tree544726302756c9a1d8b14ce4232b6624953da053
parent7fc2aca9a97894f28c3cc52aab0ad919dcb3eb27 (diff)
feat(themes): add per-theme special effects, wild mode & w hotkey
Each of the 14 themes now has three interactive effect hooks: - Navigation (j/k): brief thematic flash/shake/glitch per theme - Page change (h/l): zoom/warp/tunnel transition per theme - Wild mode (w key): toggles background animation into overdrive Wild-mode effects per theme: - neon: rings spin 14x, particles vortex - matrix: rain falls 10x faster - volcano: embers/smoke 8x/5x, lava glows brighter - brutalist: boxes 15x + random jitter - aurora/ocean/plasma/cosmos/dos/synthwave/terminal/retro/spaceage/retrofuture: time-offset approach accelerates all sine-based animations 8-11x Shared infrastructure added to nav.tmpl: - sno-shake/sno-zoom-fwd/sno-glitch CSS keyframes - #sno-wild-badge pulsing red overlay badge - selectPost() calls snonuxNavEffect() - h/l navigation calls snonuxPageEffect() - w key calls snonuxWildToggle() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/generator/templates/shared/nav.tmpl26
-rw-r--r--internal/generator/templates/themes/aurora.tmpl27
-rw-r--r--internal/generator/templates/themes/brutalist.tmpl34
-rw-r--r--internal/generator/templates/themes/cosmos.tmpl30
-rw-r--r--internal/generator/templates/themes/dos.tmpl27
-rw-r--r--internal/generator/templates/themes/matrix.tmpl25
-rw-r--r--internal/generator/templates/themes/neon.tmpl41
-rw-r--r--internal/generator/templates/themes/ocean.tmpl43
-rw-r--r--internal/generator/templates/themes/plasma.tmpl29
-rw-r--r--internal/generator/templates/themes/retro.tmpl32
-rw-r--r--internal/generator/templates/themes/retrofuture.tmpl32
-rw-r--r--internal/generator/templates/themes/spaceage.tmpl29
-rw-r--r--internal/generator/templates/themes/synthwave.tmpl33
-rw-r--r--internal/generator/templates/themes/terminal.tmpl29
-rw-r--r--internal/generator/templates/themes/volcano.tmpl44
15 files changed, 440 insertions, 41 deletions
diff --git a/internal/generator/templates/shared/nav.tmpl b/internal/generator/templates/shared/nav.tmpl
index e681bd6..a13b660 100644
--- a/internal/generator/templates/shared/nav.tmpl
+++ b/internal/generator/templates/shared/nav.tmpl
@@ -50,6 +50,7 @@
<span><kbd>Enter</kbd> or click post to expand</span>
<span><kbd>Esc</kbd> close</span>
<span><kbd>h</kbd><kbd>l</kbd> or <kbd>←</kbd><kbd>→</kbd> change page</span>
+ <span><kbd>w</kbd> wild mode</span>
</div>
{{end}}
@@ -73,6 +74,21 @@
.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; }
+/* Shared nav FX keyframes — themes apply these classes for brief effects */
+@keyframes sno-shake { 0%,100%{transform:translate(0)} 14%{transform:translate(-7px,4px)} 28%{transform:translate(7px,-5px)} 42%{transform:translate(-5px,6px)} 56%{transform:translate(6px,-4px)} 70%{transform:translate(-4px,3px)} 86%{transform:translate(4px,-2px)} }
+@keyframes sno-zoom-fwd { 0%{transform:scale(1)} 40%{transform:scale(1.05)} 100%{transform:scale(1)} }
+@keyframes sno-glitch { 0%,100%{transform:translate(0) skewX(0)} 20%{transform:translate(-5px,0) skewX(-4deg)} 40%{transform:translate(5px,0) skewX(4deg)} 60%{transform:translate(-3px,0)} 80%{transform:translate(3px,0)} }
+@keyframes sno-wild-pulse { 0%,100%{opacity:1} 50%{opacity:0.6} }
+.sno-fx-shake { animation:sno-shake 0.38s cubic-bezier(.36,.07,.19,.97) both !important; transform-origin:center; }
+.sno-fx-zoom { animation:sno-zoom-fwd 0.32s ease both !important; }
+.sno-fx-glitch { animation:sno-glitch 0.3s ease both !important; }
+/* Wild mode badge — visible when active */
+#sno-wild-badge { position:fixed; top:10px; right:12px; z-index:5000; padding:3px 12px;
+ font-size:0.64rem; letter-spacing:0.2em; text-transform:uppercase; border-radius:2px;
+ pointer-events:none; opacity:0; transition:opacity 0.4s;
+ background:rgba(220,0,0,0.92); color:#fff; border:1px solid rgba(255,100,100,0.8);
+ font-family:monospace; animation:sno-wild-pulse 0.9s ease-in-out infinite; }
+#sno-wild-badge.sno-wild-on { opacity:1; }
/* Host note under the site subtitle (all themes) */
.logo-host { font-size:0.65rem; opacity:0.55; margin-top:4px; letter-spacing:0.3px; line-height:1.3; }
/* Atom feed link in header (paired with transmit in .nav) */
@@ -129,6 +145,8 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
{{define "navscript"}}
<script>
const SNONUX_SOUNDS = {{.ThemeSoundsJSON}};
+ // 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); })();
function snonuxWaveType(w) {
if (w === 'square') return 'square';
if (w === 'triangle') return 'triangle';
@@ -232,6 +250,7 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
function selectPost(index) {
setActiveHighlight(index, true, true);
+ if (window.snonuxNavEffect) window.snonuxNavEffect();
}
/** Pick the post that should be active for the current viewport (anchor near top of visible area). */
@@ -391,12 +410,15 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
case 'j': case 'ArrowDown': selectPost(currentIndex + 1); e.preventDefault(); break;
case 'k': case 'ArrowUp': selectPost(currentIndex - 1); e.preventDefault(); break;
case 'h': case 'ArrowLeft':
- if (prevPageURL) { playNavSound(); window.location.href = prevPageURL; }
+ if (prevPageURL) { playNavSound(); if (window.snonuxPageEffect) window.snonuxPageEffect(); window.location.href = prevPageURL; }
e.preventDefault(); break;
case 'l': case 'ArrowRight':
- if (nextPageURL) { playNavSound(); window.location.href = nextPageURL; }
+ if (nextPageURL) { playNavSound(); if (window.snonuxPageEffect) window.snonuxPageEffect(); window.location.href = nextPageURL; }
e.preventDefault(); break;
case 'Enter': openPostAt(currentIndex, true); e.preventDefault(); break;
+ case 'w':
+ if (window.snonuxWildToggle) window.snonuxWildToggle();
+ e.preventDefault(); break;
}
});
</script>
diff --git a/internal/generator/templates/themes/aurora.tmpl b/internal/generator/templates/themes/aurora.tmpl
index ae4c938..07339db 100644
--- a/internal/generator/templates/themes/aurora.tmpl
+++ b/internal/generator/templates/themes/aurora.tmpl
@@ -217,6 +217,7 @@
// with overlapping sine waves, rendered with additive blending so they glow
// against the dark navy sky like real aurora curtains.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var RIBBON_COUNT = 6;
var SEG_W = 60; // horizontal segments per ribbon
var ribbonColors = [0x00ffb3, 0x00cfe8, 0xc084fc, 0x00ffb3, 0x48e8d0, 0xa855f7];
@@ -270,7 +271,10 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 7 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
for (var r = 0; r < ribbons.length; r++) {
var rb = ribbons[r];
@@ -291,6 +295,27 @@
}
initThree();
+
+ // Aurora nav/wild effects — snow burst on navigate, blizzard storm on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Snow burst — CSS snowflakes scatter from cursor
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }
+ // Frost flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,255,179,0.18) 0%,rgba(192,132,252,0.1) 60%,transparent 100%);transition:opacity 0.22s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 30);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/brutalist.tmpl b/internal/generator/templates/themes/brutalist.tmpl
index 5f54435..3dcab82 100644
--- a/internal/generator/templates/themes/brutalist.tmpl
+++ b/internal/generator/templates/themes/brutalist.tmpl
@@ -144,6 +144,7 @@
// Brutalist WebGL: harsh slowly-rotating boxes — solid white and wireframe red.
// No fog, no softness. Pure geometric violence against the black void.
(function() {
+ var _wild = false;
var scene, camera, renderer, clock;
var boxes = [];
@@ -189,14 +190,43 @@
function animate() {
requestAnimationFrame(animate);
+ var sm = _wild ? 15 : 1;
boxes.forEach(function(b) {
- b.mesh.rotation.x += b.rx;
- b.mesh.rotation.y += b.ry;
+ b.mesh.rotation.x += b.rx * sm;
+ b.mesh.rotation.y += b.ry * sm;
+ // Wild mode: random jitter on positions
+ if (_wild) { b.mesh.position.x += (Math.random()-0.5)*0.4; b.mesh.position.y += (Math.random()-0.5)*0.4; }
});
renderer.render(scene, camera);
}
initThree();
+
+ // Brutalist nav/wild effects — violent shake on navigate, geometric chaos on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Violent double shake + red flash
+ var ov = document.querySelector('.overlay');
+ if (ov) {
+ ov.classList.add('sno-fx-shake');
+ setTimeout(function() { ov.classList.remove('sno-fx-shake'); setTimeout(function() { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }, 50); }, 400);
+ }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,34,0,0.28);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ // Color inversion flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:#fff;mix-blend-mode:difference;transition:opacity 0.15s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/cosmos.tmpl b/internal/generator/templates/themes/cosmos.tmpl
index 101effb..9d74e40 100644
--- a/internal/generator/templates/themes/cosmos.tmpl
+++ b/internal/generator/templates/themes/cosmos.tmpl
@@ -165,6 +165,7 @@
// The planet sits at lower-right and slowly rotates; asteroids orbit it;
// nebula clouds drift with additive blending for a deep-space glow.
(function() {
+ var _wild = false;
var scene, camera, renderer, clock;
var planet, planetRings = [];
var asteroids = [];
@@ -288,12 +289,13 @@
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
- planet.rotation.y += 0.0015;
+ var sm = _wild ? 10 : 1;
+ planet.rotation.y += 0.0015 * sm;
- // Update asteroid belt positions
+ // Update asteroid belt — warp speed in wild mode
var pos = asteroids.geometry.attributes.position;
for (var i = 0; i < ASTEROID_COUNT; i++) {
- asteroidAngles[i] += asteroidSpeeds[i];
+ asteroidAngles[i] += asteroidSpeeds[i] * sm;
var a = asteroidAngles[i], r = asteroidRadii[i];
pos.setXYZ(i, Math.cos(a) * r, asteroidY[i], Math.sin(a) * r);
}
@@ -307,6 +309,28 @@
}
initThree();
+
+ // Cosmos nav/wild effects — shooting star on navigate, warp speed on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Shooting star CSS line flashes across screen
+ var d = document.createElement('div');
+ var angle = 30 + Math.random() * 30;
+ d.style.cssText = 'position:fixed;top:' + (10+Math.random()*40) + '%;left:-10%;z-index:998;pointer-events:none;width:35%;height:2px;background:linear-gradient(90deg,transparent,rgba(255,209,102,0.9),transparent);transform:rotate(-' + angle + 'deg);transition:left 0.28s ease,opacity 0.28s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.left='110%'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 320); }, 20);
+ };
+ window.snonuxPageEffect = function() {
+ // Warp flash — stars streak white
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(255,255,255,0.25) 0%,transparent 70%);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/dos.tmpl b/internal/generator/templates/themes/dos.tmpl
index b635910..9959321 100644
--- a/internal/generator/templates/themes/dos.tmpl
+++ b/internal/generator/templates/themes/dos.tmpl
@@ -170,6 +170,7 @@
{{template "navmodal" .}}
<script>
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var columns = [];
@@ -216,7 +217,10 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
for (var c = 0; c < columns.length; c++) {
var col = columns[c];
var y = col.y - t * col.speed;
@@ -229,6 +233,27 @@
}
initThree();
+
+ // DOS nav/wild effects — CRT glitch on navigate, system crash rain on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // CRT horizontal glitch
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(85,255,85,0.12);transition:opacity 0.15s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ // System crash — scanline strobe
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 40); }, 310); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/matrix.tmpl b/internal/generator/templates/themes/matrix.tmpl
index 365d008..842e13b 100644
--- a/internal/generator/templates/themes/matrix.tmpl
+++ b/internal/generator/templates/themes/matrix.tmpl
@@ -163,6 +163,7 @@
// Each column has a "head" that falls at a random speed; particles near the head
// are bright green and fade to near-black further behind, simulating digital rain.
(function() {
+ var _wild = false;
var NUM_COLS = 80; // number of rain columns
var COL_LEN = 25; // particles per column
var SPACING = 2.2; // vertical gap between particles in a column
@@ -219,8 +220,8 @@
requestAnimationFrame(animate);
for (var c = 0; c < NUM_COLS; c++) {
- // Advance the head downward each frame
- headY[c] -= speed[c];
+ // Advance the head downward each frame; wild mode accelerates rain 10x
+ headY[c] -= speed[c] * (_wild ? 10 : 1);
// When the head exits the bottom, reset to a random point above the top
if (headY[c] < Y_BOTTOM - COL_LEN * SPACING) {
headY[c] = Y_TOP + Math.random() * 20;
@@ -250,6 +251,26 @@
}
initThree();
+
+ // Matrix nav/wild effects — shake + green flash on navigate, rain storm on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,255,65,0.15);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 220); }, 30);
+ };
+ window.snonuxPageEffect = function() {
+ // Glitch bars — horizontal displacement flicker
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 320); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/neon.tmpl b/internal/generator/templates/themes/neon.tmpl
index f04c230..cb77ac4 100644
--- a/internal/generator/templates/themes/neon.tmpl
+++ b/internal/generator/templates/themes/neon.tmpl
@@ -263,7 +263,7 @@
ring.rotation.y+=ring.userData.speed;
ring.rotation.x=Math.sin(time*1.5+i)*ring.userData.axisTilt;
});
- particles.rotation.y+=0.0008;
+ particles.rotation.y+=window._snoNeonWild ? 0.012 : 0.0008;
renderer.render(scene,camera);
})();
}
@@ -275,6 +275,45 @@
});
window.onload=initThree;
</script>
+ <script>
+ // Neon nav/wild effects — lightning flash on navigate, ring frenzy on wild
+ (function() {
+ function flash(color, ms) {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:' + color + ';transition:opacity ' + (ms||180) + 'ms';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity = '0'; setTimeout(function() { d.remove(); }, ms || 180); }, 30);
+ }
+ function fxOverlay(cls, ms) {
+ var ov = document.querySelector('.overlay');
+ if (!ov) return;
+ ov.classList.add(cls);
+ setTimeout(function() { ov.classList.remove(cls); }, ms || 380);
+ }
+ var _wild = false;
+ window.snonuxNavEffect = function() {
+ flash('rgba(0,245,255,0.22)', 160);
+ fxOverlay('sno-fx-shake', 380);
+ };
+ window.snonuxPageEffect = function() {
+ flash('rgba(255,231,0,0.18)', 140);
+ fxOverlay('sno-fx-zoom', 320);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Speed up all rings and particles when wild
+ if (rings && rings.length) {
+ rings.forEach(function(r, i) {
+ r.userData.speed = _wild ? (0.008 + i * 0.003) * 14 : 0.008 + i * 0.003;
+ });
+ }
+ // Store wild state for particle rotation boost in animate loop
+ window._snoNeonWild = _wild;
+ };
+ })();
+ </script>
{{template "navscript" .}}
</body>
</html>
diff --git a/internal/generator/templates/themes/ocean.tmpl b/internal/generator/templates/themes/ocean.tmpl
index 5a852da..7bf068b 100644
--- a/internal/generator/templates/themes/ocean.tmpl
+++ b/internal/generator/templates/themes/ocean.tmpl
@@ -151,6 +151,7 @@
// Ocean WebGL: dramatic wave surface + sea rock spires + bioluminescent
// jellyfish + rising bubbles + a slow whale cruising the deep.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var waveGeo, waveMesh, sunLight;
var whale, jellyfish = [];
@@ -271,16 +272,20 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 6 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ var waveAmp = _wild ? 2.8 : 1; // tsunami amplitude in wild mode
var pos = waveGeo.attributes.position;
- // Dramatic overlapping waves — larger amplitude than before
+ // Dramatic overlapping waves — tsunami amplitude in wild mode
for (var i = 0; i < pos.count; i++) {
var x = pos.getX(i), z = pos.getZ(i);
pos.setY(i,
- Math.sin(x * 0.04 + t * 1.1) * 3.2 +
- Math.cos(z * 0.06 + t * 0.85) * 2.4 +
- Math.sin((x + z) * 0.025 + t * 0.6) * 1.5
+ Math.sin(x * 0.04 + t * 1.1) * 3.2 * waveAmp +
+ Math.cos(z * 0.06 + t * 0.85) * 2.4 * waveAmp +
+ Math.sin((x + z) * 0.025 + t * 0.6) * 1.5 * waveAmp
);
}
pos.needsUpdate = true;
@@ -298,10 +303,11 @@
if (whale.position.x > 80) whale.position.x = -80;
whale.position.y = -8 + Math.sin(t * 0.15) * 2;
- // Rising bubbles — reset when they reach the surface
+ // Rising bubbles — explode upward in wild tsunami mode
var bp = bubbleGeo.attributes.position;
+ var bMult = _wild ? 8 : 1;
for (var bi = 0; bi < BUBBLE_COUNT; bi++) {
- bubblePos[bi*3+1] += bubbleVY[bi];
+ bubblePos[bi*3+1] += bubbleVY[bi] * bMult;
if (bubblePos[bi*3+1] > 8) {
bubblePos[bi*3] = (Math.random() - 0.5) * 100;
bubblePos[bi*3+1] = -15 - Math.random() * 10;
@@ -317,6 +323,29 @@
}
initThree();
+
+ // Ocean nav/wild effects — wave surge on navigate, tsunami on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Wave surge: content skews briefly
+ var ov = document.querySelector('.overlay');
+ if (!ov) return;
+ ov.style.transition = 'transform 0.1s';
+ ov.style.transform = 'skewX(-1.5deg) translateY(-3px)';
+ setTimeout(function() { ov.style.transform = ''; setTimeout(function() { ov.style.transition = ''; }, 180); }, 110);
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,180,216,0.18);transition:opacity 0.22s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 30);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/plasma.tmpl b/internal/generator/templates/themes/plasma.tmpl
index 27d59a1..4155bab 100644
--- a/internal/generator/templates/themes/plasma.tmpl
+++ b/internal/generator/templates/themes/plasma.tmpl
@@ -161,6 +161,7 @@
// paths with additive blending — overlapping blobs mix colours and pulse
// like a lava lamp or plasma ball. Dark bg, cyan/magenta/yellow palette.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var blobs = [];
@@ -220,15 +221,37 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 7 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ var ampMult = _wild ? 3.5 : 1;
blobs.forEach(function(b) {
- b.mesh.position.x = b.bx + b.ax * Math.sin(t * b.fx + b.px);
- b.mesh.position.y = b.by + b.ay * Math.cos(t * b.fy + b.py);
+ b.mesh.position.x = b.bx + b.ax * ampMult * Math.sin(t * b.fx + b.px);
+ b.mesh.position.y = b.by + b.ay * ampMult * Math.cos(t * b.fy + b.py);
});
renderer.render(scene, camera);
}
initThree();
+
+ // Plasma nav/wild effects — blob pulse burst on navigate, supernova on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Blob pulse burst — rainbow flash radiating from center
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,240,255,0.25) 0%,rgba(255,0,224,0.15) 45%,transparent 70%);transform:scale(0.5);transition:transform 0.28s ease,opacity 0.28s ease';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform='scale(1.3)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 310); }, 20);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/retro.tmpl b/internal/generator/templates/themes/retro.tmpl
index 2ff1b7c..305c66d 100644
--- a/internal/generator/templates/themes/retro.tmpl
+++ b/internal/generator/templates/themes/retro.tmpl
@@ -158,6 +158,7 @@
// Retro WebGL scene: amber demo-scene cube + orbiting octahedrons + star particles.
// Evokes classic 80s/90s PC demo aesthetics with amber phosphor colours.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer;
var mainCube, orbiters = [];
var clock = new THREE.Clock();
@@ -213,8 +214,11 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
- // Main cube rotates on Y and X axes for classic demo-scene look
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 8 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Main cube rotates; wild mode spins dramatically
mainCube.rotation.y = t * 0.35;
mainCube.rotation.x = t * 0.18;
// Orbiters revolve around the central cube and spin individually
@@ -231,6 +235,30 @@
}
initThree();
+
+ // Retro nav/wild effects — amber ember burst on navigate, phosphor burnout on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Intensify the flicker in wild mode
+ document.body.style.animationDuration = _wild ? '1.5s' : '11s';
+ };
+ window.snonuxNavEffect = function() {
+ // Amber ember spray flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at 50% 50%,rgba(255,176,0,0.22) 0%,transparent 65%);transition:opacity 0.25s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 280); }, 25);
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }
+ };
+ window.snonuxPageEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,176,0,0.2);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/retrofuture.tmpl b/internal/generator/templates/themes/retrofuture.tmpl
index 3d94731..63faace 100644
--- a/internal/generator/templates/themes/retrofuture.tmpl
+++ b/internal/generator/templates/themes/retrofuture.tmpl
@@ -203,6 +203,7 @@
// Retrofuture WebGL: atomic orb with crossed electron rings, receding teal grid floor,
// chrome metallic star particles, and a slow drifting camera orbit.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var atomic, ringH, ringV, stars;
@@ -278,9 +279,12 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
- // Atomic orb pulses subtly; rings counter-rotate
- var pulse = 1 + 0.015 * Math.sin(t * 1.5);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Atomic orb pulses; wild meltdown mode makes it throb intensely
+ var pulse = _wild ? 1 + 0.1 * Math.sin(t * 12) : 1 + 0.015 * Math.sin(t * 1.5);
atomic.scale.setScalar(pulse);
ringH.rotation.z = t * 0.4;
ringV.rotation.z = -t * 0.3;
@@ -292,6 +296,28 @@
}
initThree();
+
+ // Retrofuture nav/wild effects — atomic pulse on navigate, meltdown on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Atomic pulse — rings expand briefly as CSS overlay
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0.1);z-index:998;pointer-events:none;width:100vmax;height:100vmax;border-radius:50%;border:3px solid rgba(0,217,192,0.7);transition:transform 0.3s ease,opacity 0.3s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform='translate(-50%,-50%) scale(1.2)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 330); }, 15);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,217,192,0.15);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/spaceage.tmpl b/internal/generator/templates/themes/spaceage.tmpl
index 5841444..3ddfa67 100644
--- a/internal/generator/templates/themes/spaceage.tmpl
+++ b/internal/generator/templates/themes/spaceage.tmpl
@@ -166,6 +166,7 @@
// Space Age WebGL: toroidal space station ring + three satellite pods orbiting it
// + a slowly rotating planet sphere + dense star field. Teal wireframe throughout.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var station, pods = [], planet;
@@ -246,9 +247,12 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 10 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
- // Station rotates slowly around Y and tilts slightly on X
+ // Station rotates; wild mode = warp-speed orbital mechanics
station.rotation.y = t * 0.12;
station.rotation.x = Math.sin(t * 0.07) * 0.3;
@@ -275,6 +279,27 @@
}
initThree();
+
+ // Spaceage nav/wild effects — star streak on navigate, hyperspace jump on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Star streak flash — radial lines from center
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,232,232,0.2) 0%,transparent 55%);transform:scale(0.4);transition:transform 0.25s ease,opacity 0.25s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform='scale(1.6)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 280); }, 15);
+ };
+ window.snonuxPageEffect = function() {
+ // Hyperspace jump — white tunnel flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(255,255,255,0.3) 0%,rgba(0,232,232,0.15) 45%,transparent 70%);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/synthwave.tmpl b/internal/generator/templates/themes/synthwave.tmpl
index d23f881..b8b7c13 100644
--- a/internal/generator/templates/themes/synthwave.tmpl
+++ b/internal/generator/templates/themes/synthwave.tmpl
@@ -170,6 +170,7 @@
// Synthwave WebGL: glowing sunset sphere with horizontal scan-line rings,
// a receding grid floor, and pink star particles. Replaces CSS sky/grid.
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock;
var sun, sunRings = [];
@@ -241,9 +242,12 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
- // Sun pulses subtly; camera drifts sideways for parallax
- var pulse = 1 + 0.015 * Math.sin(t * 1.5);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Sun pulses subtly; wild mode makes it throb dramatically
+ var pulse = _wild ? 1 + 0.12 * Math.sin(t * 8) : 1 + 0.015 * Math.sin(t * 1.5);
sun.scale.setScalar(pulse);
sunRings.forEach(function(r) { r.scale.setScalar(pulse); });
camera.position.x = Math.sin(t * 0.08) * 4;
@@ -251,6 +255,29 @@
}
initThree();
+
+ // Synthwave nav/wild effects — grid zoom on navigate, turbo drive on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Grid zoom forward
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:linear-gradient(180deg,transparent 40%,rgba(255,45,120,0.2) 100%);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
+ };
+ window.snonuxPageEffect = function() {
+ // Warp-speed tunnel flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at 50% 100%,rgba(255,107,43,0.35) 0%,transparent 65%);transform:scaleY(0.2);transition:transform 0.22s ease,opacity 0.22s ease';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform='scaleY(1.4)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 20);
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/terminal.tmpl b/internal/generator/templates/themes/terminal.tmpl
index b30214a..dbe0bf3 100644
--- a/internal/generator/templates/themes/terminal.tmpl
+++ b/internal/generator/templates/themes/terminal.tmpl
@@ -149,6 +149,7 @@
// Terminal WebGL scene: phosphor-green icosahedron wireframe + torus particle ring.
// The scene sits behind the CRT scanline overlay (z-index:999) and the UI (z-index:10).
(function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, icosa, particles;
var clock = new THREE.Clock();
@@ -201,8 +202,11 @@
function animate() {
requestAnimationFrame(animate);
- var t = clock.getElapsedTime();
- // Slow multi-axis rotation for the phosphor orb
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 11 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Slow multi-axis rotation; wild mode overloads the phosphor orb
icosa.rotation.x = t * 0.12;
icosa.rotation.y = t * 0.18;
icosa.rotation.z = t * 0.07;
@@ -213,6 +217,27 @@
}
initThree();
+
+ // Terminal nav/wild effects — cursor glitch on navigate, buffer overflow on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Toggle intense scanline strobe in wild mode
+ document.body.style.animationDuration = _wild ? '0.4s' : '9s';
+ };
+ window.snonuxNavEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(51,255,51,0.13);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 210); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 35); }, 300); }
+ };
})();
</script>
{{template "navscript" .}}
diff --git a/internal/generator/templates/themes/volcano.tmpl b/internal/generator/templates/themes/volcano.tmpl
index 04f4749..721d757 100644
--- a/internal/generator/templates/themes/volcano.tmpl
+++ b/internal/generator/templates/themes/volcano.tmpl
@@ -154,6 +154,7 @@
// Volcano WebGL: glowing lava floor, molten rock boulders, smoke plumes,
// underground furnace glow sphere, and 3000 rising ember particles.
(function() {
+ var _wild = false;
var N_EMBER = 3000;
var N_SMOKE = 800;
var scene, camera, renderer, clock;
@@ -300,12 +301,13 @@
}
lp.needsUpdate = true;
- // Embers
+ // Embers — wild mode eruption multiplies velocity 8x and shrinks lifespan
+ var emberMult = _wild ? 8 : 1;
for (var ei = 0; ei < N_EMBER; ei++) {
- eLife[ei] += dt / eMaxLife[ei];
+ eLife[ei] += dt / eMaxLife[ei] * emberMult;
if (eLife[ei] > 1.0) resetEmber(ei);
- ePY[ei] += eVY[ei];
- ePX[ei] += eVX[ei];
+ ePY[ei] += eVY[ei] * emberMult;
+ ePX[ei] += eVX[ei] * emberMult;
var idx = ei * 3, te = eLife[ei];
ePosArr[idx] = ePX[ei]; ePosArr[idx+1] = ePY[ei]; ePosArr[idx+2] = ePZ[ei];
var fade = Math.max(0, 1 - te * 1.3);
@@ -314,11 +316,12 @@
emberPoints.geometry.attributes.position.needsUpdate = true;
emberPoints.geometry.attributes.color.needsUpdate = true;
- // Smoke
+ // Smoke — wild mode makes smoke billow chaotically
+ var smokeMult = _wild ? 5 : 1;
for (var si = 0; si < N_SMOKE; si++) {
- sSLife[si] += dt / sSMaxLife[si];
+ sSLife[si] += dt / sSMaxLife[si] * smokeMult;
if (sSLife[si] > 1.0) resetSmoke(si);
- sPY[si] += sSVY[si];
+ sPY[si] += sSVY[si] * smokeMult;
sPX[si] += (Math.random() - 0.5) * 0.04;
var si3 = si * 3;
sPosArr[si3] = sPX[si]; sPosArr[si3+1] = sPY[si]; sPosArr[si3+2] = sPZ[si];
@@ -329,6 +332,33 @@
}
initThree();
+
+ // Volcano nav/wild effects — seismic tremor on navigate, full eruption on wild
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Boost lava emissive intensity in wild eruption mode
+ if (lavaFloor) lavaFloor.material.emissiveIntensity = _wild ? 2.5 : 0.6;
+ };
+ window.snonuxNavEffect = function() {
+ // Seismic tremor — strong shake
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 420); }
+ // Orange ember flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,68,0,0.2);transition:opacity 0.25s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 280); }, 30);
+ };
+ window.snonuxPageEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,140,0,0.25);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 220); }, 20);
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ };
})();
</script>
{{template "navscript" .}}