diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-18 23:29:27 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-18 23:29:27 +0300 |
| commit | 795cdaa73b48f7d63c85693f1b5e5e8f8f55e2d6 (patch) | |
| tree | 544726302756c9a1d8b14ce4232b6624953da053 | |
| parent | 7fc2aca9a97894f28c3cc52aab0ad919dcb3eb27 (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.tmpl | 26 | ||||
| -rw-r--r-- | internal/generator/templates/themes/aurora.tmpl | 27 | ||||
| -rw-r--r-- | internal/generator/templates/themes/brutalist.tmpl | 34 | ||||
| -rw-r--r-- | internal/generator/templates/themes/cosmos.tmpl | 30 | ||||
| -rw-r--r-- | internal/generator/templates/themes/dos.tmpl | 27 | ||||
| -rw-r--r-- | internal/generator/templates/themes/matrix.tmpl | 25 | ||||
| -rw-r--r-- | internal/generator/templates/themes/neon.tmpl | 41 | ||||
| -rw-r--r-- | internal/generator/templates/themes/ocean.tmpl | 43 | ||||
| -rw-r--r-- | internal/generator/templates/themes/plasma.tmpl | 29 | ||||
| -rw-r--r-- | internal/generator/templates/themes/retro.tmpl | 32 | ||||
| -rw-r--r-- | internal/generator/templates/themes/retrofuture.tmpl | 32 | ||||
| -rw-r--r-- | internal/generator/templates/themes/spaceage.tmpl | 29 | ||||
| -rw-r--r-- | internal/generator/templates/themes/synthwave.tmpl | 33 | ||||
| -rw-r--r-- | internal/generator/templates/themes/terminal.tmpl | 29 | ||||
| -rw-r--r-- | internal/generator/templates/themes/volcano.tmpl | 44 |
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" .}} |
