summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-22 22:28:50 +0300
committerPaul Buetow <paul@buetow.org>2026-04-22 22:28:50 +0300
commit925b64bc16af9a6b2daff8468e54d86e6935fe26 (patch)
treedd23bd2f05c382b711419ea24003929cc870561b
parentd3ed4f2b6b49917ad293746cd6300a11eafb8f4d (diff)
v0.8.0: modal drift, flying emoji, random flips, hue drift, cursor sparkle, post afterimagev0.8.0
Amp-Thread-ID: https://ampcode.com/threads/T-019db698-f351-7161-a397-1cce1fdab440 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--internal/generator/templates/shared/nav.tmpl259
-rw-r--r--internal/version/version.go2
2 files changed, 239 insertions, 22 deletions
diff --git a/internal/generator/templates/shared/nav.tmpl b/internal/generator/templates/shared/nav.tmpl
index 5cbe0d2..ae161dc 100644
--- a/internal/generator/templates/shared/nav.tmpl
+++ b/internal/generator/templates/shared/nav.tmpl
@@ -69,7 +69,7 @@
the expanded post. Theme-specific modal-inner keeps its own background. */
.post-modal { background:rgba(0,0,0,0.55) !important; backdrop-filter:blur(6px) !important; }
#post-modal.active { display:flex !important; align-items:center; justify-content:center; }
-#post-modal .modal-inner { width:fit-content; max-width:min(100%, 90vw); max-height:calc(100vh - 80px); overflow:auto; margin:0 auto !important; }
+#post-modal .modal-inner { width:fit-content; max-width:min(100%, 90vw); max-height:calc(100vh - 80px); overflow:auto; margin:0 auto !important; will-change:transform; }
/* Content area max-width across all themes */
.overlay { max-width:1200px; margin-left:auto; margin-right:auto; }
/* Pagination: newer + older in a footer bar (below scrollable posts, like the header) */
@@ -130,6 +130,26 @@
#sno-burst { position:fixed; inset:0; z-index:9999; pointer-events:none; }
#sno-burst span { position:absolute; width:6px; height:6px; border-radius:50%; background:currentColor;
animation:sno-particle-fly var(--pdur) ease-out forwards; animation-delay:var(--pdel); opacity:0; }
+/* Cursor sparkle trail (normal mode) */
+@keyframes sno-sparkle { 0%{transform:scale(1);opacity:0.8} 100%{transform:scale(0);opacity:0} }
+.sno-sparkle { position:fixed; pointer-events:none; z-index:9990; border-radius:50%; }
+/* Post afterimage — ghost of previously selected post */
+@keyframes sno-afterimage { 0%{opacity:0.4;transform:scale(1)} 100%{opacity:0;transform:scale(0.97)} }
+.sno-afterimage { position:absolute; inset:0; pointer-events:none; z-index:0;
+ border:1px solid currentColor; border-radius:inherit; opacity:0; }
+.sno-afterimage-active { animation:sno-afterimage 0.5s ease-out forwards; }
+/* Wild flying emoji */
+@keyframes sno-fly-lr { 0%{transform:translateX(-60px) translateY(var(--fy,0)) rotate(0)} 100%{transform:translateX(calc(100vw + 60px)) translateY(var(--fy,0)) rotate(var(--frot,360deg))} }
+@keyframes sno-fly-rl { 0%{transform:translateX(calc(100vw + 60px)) translateY(var(--fy,0)) rotate(0)} 100%{transform:translateX(-60px) translateY(var(--fy,0)) rotate(var(--frot,-360deg))} }
+#sno-flyzone { position:fixed; inset:0; z-index:9985; pointer-events:none; overflow:hidden; }
+#sno-flyzone span { position:absolute; top:var(--ftop,50%); font-size:clamp(1.2rem,2.5vw,2rem);
+ animation-timing-function:linear; animation-fill-mode:forwards; }
+/* Wild random post flip */
+@keyframes sno-flip-post { 0%{transform:scaleY(1)} 25%{transform:scaleY(-1)} 75%{transform:scaleY(-1)} 100%{transform:scaleY(1)} }
+.sno-fx-flip { animation:sno-flip-post 1.4s ease-in-out both !important; transform-origin:center; }
+/* Wild hue drift on body */
+@keyframes sno-hue-drift { 0%{filter:hue-rotate(0)} 100%{filter:hue-rotate(360deg)} }
+body.sno-wild-hue { animation:sno-hue-drift 12s linear infinite; }
@keyframes sno-wild-pulse { 0%,100%{opacity:1} 50%{opacity:0.6} }
/* Storm overlay that flickers like distant lightning while wild mode is on */
@keyframes sno-wild-flicker { 0%,84%,87%,91%,94%,100%{opacity:0} 85%,90%{opacity:0.75} 86%,92%{opacity:0.35} }
@@ -886,121 +906,141 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
banner: 'SOLAR STORM',
ticker: ['FIELD INTERFERENCE', 'PLASMA DRIFT', 'PARTICLE BOMBARDMENT', 'CHROMATIC SPIKE'],
scraps: ['MAGNETIC SHEAR', 'SOLAR WIND', 'AURORA NOISE', 'ION STORM', 'POLAR ARC'],
- flash: 'rgba(220,255,240,0.72)'
+ flash: 'rgba(220,255,240,0.72)',
+ emoji: ['\u2728','\u2604\uFE0F','\u{1F320}','\u{1F30C}','\u2B50']
},
brutalist: {
banner: 'STRUCTURAL COLLAPSE',
ticker: ['CONDEMNED', 'REBAR EXPOSED', 'FOUNDATION FAILURE', 'CRACK PROPAGATION'],
scraps: ['CONDEMNED', 'RUST BLEED', 'SHEAR WALL LOST', 'LOAD PATH BROKEN', 'SPALLING'],
- flash: 'rgba(255,210,190,0.58)'
+ flash: 'rgba(255,210,190,0.58)',
+ emoji: ['\u{1F9F1}','\u2692\uFE0F','\u26A0\uFE0F','\u{1F6A7}','\u{1F4A5}']
},
cosmos: {
banner: 'SUPERNOVA',
ticker: ['SINGULARITY LENSING', 'GAMMA BURST', 'SHOCKWAVE EXPANDING', 'SPACETIME TEAR'],
scraps: ['WHITEOUT', 'EVENT HORIZON', 'RADIATION FRONT', 'LENS LOCK', 'CORE BREACH'],
- flash: 'rgba(255,255,255,0.8)'
+ flash: 'rgba(255,255,255,0.8)',
+ emoji: ['\u{1F30C}','\u2604\uFE0F','\u{1F4AB}','\u2B50','\u{1FA90}']
},
dos: {
banner: 'KERNEL PANIC',
ticker: ['ABORT, RETRY, FAIL?', 'MEMORY CORRUPTION', 'STACK DUMP', 'SEGMENT FAULT'],
scraps: ['DEAD BEEF', 'C0FFEE', 'BAD SECTOR', 'IRQ STORM', 'NULL PTR', 'HEX DUMP'],
- flash: 'rgba(255,255,255,0.88)'
+ flash: 'rgba(255,255,255,0.88)',
+ emoji: ['\u{1F4BE}','\u{1F4BB}','\u26A1','\u2620\uFE0F','\u{1F41B}']
},
matrix: {
banner: 'CASCADE FAILURE',
ticker: ['SENTINEL TRACE', 'GLYPH SATURATION', 'PHOSPHOR BURN-IN', 'RAIN AT TERMINAL VELOCITY'],
scraps: ['SENTINEL', '0XDECODE', 'OVERRIDE', 'TRACE LOST', 'MACHINE DREAM'],
- flash: 'rgba(180,255,190,0.68)'
+ flash: 'rgba(180,255,190,0.68)',
+ emoji: ['\u{1F441}\uFE0F','\u{1F4A0}','\u{1F916}','\u{1F50D}','\u26D3\uFE0F']
},
neon: {
banner: 'GAS DISCHARGE',
ticker: ['TUBE ARC', 'ULTRAVIOLET BLEED', 'STROBE LOCK', 'SHORT CIRCUIT'],
scraps: ['ARC OVERLOAD', 'PLASMA SIGN', 'NOBLE GAS', 'HARD STROBE', 'OVERDRIVE'],
- flash: 'rgba(255,245,170,0.72)'
+ flash: 'rgba(255,245,170,0.72)',
+ emoji: ['\u26A1','\u{1F4A5}','\u{1F52E}','\u2728','\u{1F388}']
},
ocean: {
banner: 'HADAL DESCENT',
ticker: ['PRESSURE SPIKE', 'BIOLUMINESCENT SWARM', 'ABYSSAL DRAG', 'TSUNAMI FRONT'],
scraps: ['NO SURFACE', 'CRUSH DEPTH', 'TENTACLE DRIFT', 'SONAR LOST', 'DEEP CURRENT'],
- flash: 'rgba(200,255,255,0.54)'
+ flash: 'rgba(200,255,255,0.54)',
+ emoji: ['\u{1F419}','\u{1F420}','\u{1F30A}','\u{1F41A}','\u{1F9DC}']
},
plasma: {
banner: 'FUSION BREACH',
ticker: ['CONTAINMENT FAILURE', 'TOKAMAK DISTORTION', 'THERMAL RUNAWAY', 'WHITE-BLUE CORE'],
scraps: ['ION SPRAY', 'FIELD LOSS', 'HEAT HAZE', 'QUENCH', 'ARC SHELL'],
- flash: 'rgba(230,250,255,0.78)'
+ flash: 'rgba(230,250,255,0.78)',
+ emoji: ['\u{1F300}','\u26A1','\u{1F4A0}','\u2728','\u{1F52C}']
},
retro: {
banner: 'TAPE EAT',
ticker: ['TRACKING LOSS', 'CHROMA SPLIT', 'MAGNETIC SNOW', 'CLICK-EJECT'],
scraps: ['NO SIGNAL', 'HEAD DRAG', 'ROLL HOLD', 'SNOW PACK', 'EJECT CYCLE'],
- flash: 'rgba(255,226,178,0.6)'
+ flash: 'rgba(255,226,178,0.6)',
+ emoji: ['\u{1F4FC}','\u{1F4FA}','\u{1F3AE}','\u{1F579}\uFE0F','\u{1F4FB}']
},
retrofuture: {
banner: 'ATOMIC TWILIGHT',
ticker: ['GEIGER STATIC', 'FALLOUT DUST', 'RADIATION BURN', 'IRRADIATED SEPIA'],
scraps: ['FALLOUT', 'BETA LEAK', 'ASH DRIFT', 'HALF-LIFE', 'GLOW CLOUD'],
- flash: 'rgba(255,240,180,0.62)'
+ flash: 'rgba(255,240,180,0.62)',
+ emoji: ['\u2622\uFE0F','\u{1F4A3}','\u{1F3ED}','\u2623\uFE0F','\u{1F9EA}']
},
spaceage: {
banner: 'RE-ENTRY BURN',
ticker: ['HEAT SHIELD LOSS', 'PLASMA BLACKOUT', 'COMMS STATIC', 'G-FORCE COMPRESSION'],
scraps: ['BLACKOUT', 'SPARK SHOWER', 'PLASMA SHEATH', 'HULL GLOW', 'COMMS LOST'],
- flash: 'rgba(255,220,190,0.68)'
+ flash: 'rgba(255,220,190,0.68)',
+ emoji: ['\u{1F680}','\u{1F6F8}','\u{1FA90}','\u{1F30D}','\u2B50']
},
synthwave: {
banner: 'GRID COLLAPSE',
ticker: ['VOID PERSPECTIVE', 'MOLTEN SUN', 'CHROMA TEAR', 'OUT OF MEMORY'],
scraps: ['VOID GRID', 'SUN DRIP', 'NEON PANIC', 'FRAME DROP', 'MEMORY STARVE'],
- flash: 'rgba(255,210,255,0.68)'
+ flash: 'rgba(255,210,255,0.68)',
+ emoji: ['\u{1F305}','\u{1F3B6}','\u{1F3B9}','\u{1F338}','\u{1F52E}']
},
terminal: {
banner: 'FORK BOMB',
ticker: ['PROCESS STORM', 'STACK TRACE WATERFALL', 'MEMORY GARBAGE', 'BSOD CREEP'],
scraps: ['PID 65535', 'STACK OVERFLOW', 'OOM KILL', 'PANIC', '(:'],
- flash: 'rgba(180,255,180,0.7)'
+ flash: 'rgba(180,255,180,0.7)',
+ emoji: ['\u{1F4BB}','\u{1F41B}','\u2620\uFE0F','\u{1F5A5}\uFE0F','\u26A1']
},
tropicale: {
banner: 'CATEGORY 5',
ticker: ['HORIZONTAL RAIN', 'STORM SURGE', 'DEBRIS FIELD', 'WIND SHEAR'],
scraps: ['PALM SNAP', 'SURGE LINE', 'SPRAY WALL', 'FLYING ROOF', 'LANDFALL'],
- flash: 'rgba(240,255,255,0.74)'
+ flash: 'rgba(240,255,255,0.74)',
+ emoji: ['\u{1F334}','\u{1F3D6}\uFE0F','\u{1F940}','\u{1F965}','\u{1F30A}']
},
noir: {
banner: 'BLACKOUT DISTRICT',
ticker: ['BLINDS SLAMMED SHUT', 'SIREN SWEEP', 'PROJECTOR BURN', 'MIDNIGHT DOWNPOUR'],
scraps: ['NO WITNESSES', 'WET ASPHALT', 'RED CHANNEL', 'BLUE CHANNEL', 'SMOKE CURTAIN'],
- flash: 'rgba(255,245,225,0.66)'
+ flash: 'rgba(255,245,225,0.66)',
+ emoji: ['\u{1F576}\uFE0F','\u{1F52B}','\u{1F3A9}','\u{1F6AC}','\u{1F5DD}\uFE0F']
},
cathedral: {
banner: 'LAST JUDGMENT',
ticker: ['BELL SHOCKWAVE', 'INCENSE FIRESTORM', 'ROSE WINDOW FRACTURE', 'APSE IN FLAME'],
scraps: ['REQUIEM', 'SHARD RAIN', 'VESPER BURN', 'GLORIA STATIC', 'NAVE COLLAPSE'],
- flash: 'rgba(255,239,202,0.72)'
+ flash: 'rgba(255,239,202,0.72)',
+ emoji: ['\u{1F54E}','\u{1F56F}\uFE0F','\u271D\uFE0F','\u{1F54A}\uFE0F','\u{1F3F0}']
},
surveillance: {
banner: 'TOTAL COMPROMISE',
ticker: ['CAMERA MESH BREACH', 'TRACKING LOSS', 'MULTIPLEX PANIC', 'ALERT CASCADE'],
scraps: ['FLAGGED', 'OVERRIDDEN', 'TRACE LOOP', 'BOX LOST', 'ALERT 99'],
- flash: 'rgba(210,255,225,0.72)'
+ flash: 'rgba(210,255,225,0.72)',
+ emoji: ['\u{1F4F9}','\u{1F441}\uFE0F','\u{1F6A8}','\u{1F50D}','\u{1F4E1}']
},
biomech: {
banner: 'CONTAINMENT RUPTURE',
ticker: ['SYNAPSE STORM', 'TISSUE ARC', 'MEMBRANE TEAR', 'HYBRID OVERDRIVE'],
scraps: ['VENTRICLE', 'MYCELIUM', 'RUPTURE', 'BIOFILM', 'NERVE GRID'],
- flash: 'rgba(255,205,220,0.7)'
+ flash: 'rgba(255,205,220,0.7)',
+ emoji: ['\u{1F9EC}','\u{1F9E0}','\u{1F9A0}','\u{1F52C}','\u{1FAC0}']
},
paper: {
banner: 'PRESS JAM',
ticker: ['TONER BLIZZARD', 'INK BLEED', 'COPY LAMP WHITEOUT', 'PAGE STORM'],
scraps: ['MISPRINT', 'SKEWED FEED', 'RAG EDGE', 'CARBON DUST', 'REDACTION'],
- flash: 'rgba(255,250,236,0.82)'
+ flash: 'rgba(255,250,236,0.82)',
+ emoji: ['\u{1F4C4}','\u270F\uFE0F','\u{1F4CE}','\u2702\uFE0F','\u{1F5DE}\uFE0F']
},
volcano: {
banner: 'PYROCLASTIC SURGE',
ticker: ['ASH CASCADE', 'LAVA BOMB IMPACT', 'EARTHQUAKE SHAKE', 'SULFUR CLOUD'],
scraps: ['ASHFALL', 'VENT BLAST', 'PYROCLAST', 'SEISMIC HIT', 'MAGMA SPRAY'],
- flash: 'rgba(255,220,150,0.72)'
+ flash: 'rgba(255,220,150,0.72)',
+ emoji: ['\u{1F30B}','\u{1F525}','\u{1F4A5}','\u2668\uFE0F','\u{1FAA8}']
}
};
function snonuxDetectThemeName() {
@@ -1099,10 +1139,73 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
if (on) {
snonuxApplyWildPreset(window._snonuxWildTheme || snonuxDetectThemeName());
snonuxScheduleWildBursts();
+ body.classList.add('sno-wild-hue');
+ snonuxStartFlyingEmoji();
+ snonuxStartRandomFlips();
} else {
clearTimeout(window._snonuxWildBurstTimer);
+ body.classList.remove('sno-wild-hue');
+ snonuxStopFlyingEmoji();
+ snonuxStopRandomFlips();
}
}
+ // === WILD FLYING EMOJI ===
+ function snonuxStartFlyingEmoji() {
+ snonuxStopFlyingEmoji();
+ var zone = document.getElementById('sno-flyzone');
+ if (!zone) {
+ zone = document.createElement('div');
+ zone.id = 'sno-flyzone';
+ zone.setAttribute('aria-hidden', 'true');
+ document.body.appendChild(zone);
+ }
+ function spawn() {
+ if (!window._snoWildActive) return;
+ var preset = SNONUX_WILD_PRESETS[window._snonuxWildTheme] || SNONUX_WILD_PRESETS.neon;
+ var emojis = preset.emoji || ['\u2B50'];
+ var s = document.createElement('span');
+ s.textContent = emojis[Math.floor(Math.random() * emojis.length)];
+ var top = (5 + Math.random() * 80).toFixed(1);
+ var dur = (3 + Math.random() * 4).toFixed(2);
+ var dir = Math.random() > 0.5 ? 'sno-fly-lr' : 'sno-fly-rl';
+ var wobble = ((Math.random() - 0.5) * 60).toFixed(0);
+ var rot = (180 + Math.random() * 540).toFixed(0);
+ s.style.setProperty('--ftop', top + '%');
+ s.style.setProperty('--fy', wobble + 'px');
+ s.style.setProperty('--frot', rot + 'deg');
+ s.style.animationName = dir;
+ s.style.animationDuration = dur + 's';
+ zone.appendChild(s);
+ setTimeout(function() { s.remove(); }, parseFloat(dur) * 1000 + 200);
+ window._snoFlyTimer = setTimeout(spawn, 800 + Math.random() * 2200);
+ }
+ spawn();
+ }
+ function snonuxStopFlyingEmoji() {
+ clearTimeout(window._snoFlyTimer);
+ var zone = document.getElementById('sno-flyzone');
+ if (zone) zone.innerHTML = '';
+ }
+ // === WILD RANDOM FLIPS ===
+ function snonuxStartRandomFlips() {
+ snonuxStopRandomFlips();
+ function flip() {
+ if (!window._snoWildActive) return;
+ var allPosts = document.querySelectorAll('.post:not(.post-active)');
+ if (allPosts.length > 0) {
+ var p = allPosts[Math.floor(Math.random() * allPosts.length)];
+ p.classList.add('sno-fx-flip');
+ setTimeout(function() { p.classList.remove('sno-fx-flip'); }, 1500);
+ }
+ window._snoFlipTimer = setTimeout(flip, 2500 + Math.random() * 4000);
+ }
+ window._snoFlipTimer = setTimeout(flip, 1500 + Math.random() * 2000);
+ }
+ function snonuxStopRandomFlips() {
+ clearTimeout(window._snoFlipTimer);
+ var flipped = document.querySelectorAll('.sno-fx-flip');
+ flipped.forEach(function(el) { el.classList.remove('sno-fx-flip'); });
+ }
(function snonuxWildSetup() {
window._snoWildActive = !!window._snoWildActive;
snonuxApplyWildPreset(snonuxDetectThemeName());
@@ -1232,9 +1335,21 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
function setActiveHighlight(index, playSound, scrollIntoView) {
if (posts.length === 0) return;
+ var prevIdx = currentIndex;
if (currentIndex >= 0) posts[currentIndex].classList.remove('post-active');
currentIndex = Math.max(0, Math.min(index, posts.length - 1));
posts[currentIndex].classList.add('post-active');
+ if (prevIdx >= 0 && prevIdx !== currentIndex && posts[prevIdx]) {
+ var ghost = posts[prevIdx].querySelector('.sno-afterimage');
+ if (!ghost) {
+ ghost = document.createElement('div');
+ ghost.className = 'sno-afterimage';
+ posts[prevIdx].appendChild(ghost);
+ }
+ ghost.classList.remove('sno-afterimage-active');
+ void ghost.offsetWidth;
+ ghost.classList.add('sno-afterimage-active');
+ }
if (scrollIntoView) {
posts[currentIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
@@ -1364,6 +1479,83 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
if (wild) snonuxPulseFlash(window._snonuxWildFlashColor, 200);
}
+ // === MODAL DRIFT — arrow/hjkl push the modal around with momentum ===
+ var modalDrift = (function() {
+ var x = 0, y = 0, vx = 0, vy = 0;
+ var raf = null;
+ var PUSH = 12;
+ var FRICTION = 0.92;
+ var BOUNCE_DAMP = 0.5;
+ var STOP_THRESHOLD = 0.3;
+
+ function getInner() {
+ return document.querySelector('#post-modal .modal-inner');
+ }
+
+ function clampAndBounce() {
+ var mi = getInner();
+ if (!mi) return;
+ var w = mi.offsetWidth, h = mi.offsetHeight;
+ var maxX = (window.innerWidth - w) / 2;
+ var maxY = (window.innerHeight - h) / 2;
+ if (maxX < 0) maxX = window.innerWidth * 0.3;
+ if (maxY < 0) maxY = window.innerHeight * 0.3;
+ if (x > maxX) { x = maxX; vx = -vx * BOUNCE_DAMP; }
+ if (x < -maxX) { x = -maxX; vx = -vx * BOUNCE_DAMP; }
+ if (y > maxY) { y = maxY; vy = -vy * BOUNCE_DAMP; }
+ if (y < -maxY) { y = -maxY; vy = -vy * BOUNCE_DAMP; }
+ }
+
+ function tick() {
+ vx *= FRICTION;
+ vy *= FRICTION;
+ x += vx;
+ y += vy;
+ clampAndBounce();
+ var mi = getInner();
+ if (mi) mi.style.transform = 'translate(' + x.toFixed(1) + 'px,' + y.toFixed(1) + 'px)';
+ if (Math.abs(vx) > STOP_THRESHOLD || Math.abs(vy) > STOP_THRESHOLD) {
+ raf = requestAnimationFrame(tick);
+ } else {
+ raf = null;
+ }
+ }
+
+ function ensureLoop() {
+ if (!raf) raf = requestAnimationFrame(tick);
+ }
+
+ return {
+ keyPush: function(e) {
+ var dx = 0, dy = 0;
+ switch (e.key) {
+ case 'h': case 'ArrowLeft': dx = -PUSH; break;
+ case 'l': case 'ArrowRight': dx = PUSH; break;
+ case 'k': case 'ArrowUp': dy = -PUSH; break;
+ case 'j': case 'ArrowDown': dy = PUSH; break;
+ default: return;
+ }
+ e.preventDefault();
+ vx += dx;
+ vy += dy;
+ playNavSound();
+ ensureLoop();
+ },
+ reset: function() {
+ x = 0; y = 0; vx = 0; vy = 0;
+ var mi = getInner();
+ if (mi) mi.style.transform = '';
+ if (raf) { cancelAnimationFrame(raf); raf = null; }
+ },
+ stop: function() {
+ if (raf) { cancelAnimationFrame(raf); raf = null; }
+ var mi = getInner();
+ if (mi) mi.style.transform = '';
+ x = 0; y = 0; vx = 0; vy = 0;
+ }
+ };
+ })();
+
function openPostAt(index, scrollIntoView) {
if (posts.length === 0) return;
setActiveHighlight(index, false, !!scrollIntoView);
@@ -1374,6 +1566,7 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
var modalInner = modal ? modal.querySelector('.modal-inner') : null;
document.getElementById('modal-content').innerHTML = postText.innerHTML;
modal.classList.add('active');
+ modalDrift.reset();
if (window.snonuxOpenEffect) window.snonuxOpenEffect(post);
modal.scrollTop = 0;
if (modalInner) {
@@ -1386,6 +1579,7 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
}
function closeModal() {
+ modalDrift.stop();
document.getElementById('post-modal').classList.remove('active');
playCloseSound();
if (window.snonuxCloseEffect) window.snonuxCloseEffect();
@@ -1423,6 +1617,7 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
}
if (document.getElementById('post-modal').classList.contains('active')) {
if (e.key === 'Escape') { closeModal(); e.preventDefault(); }
+ else { modalDrift.keyPush(e); }
return;
}
switch (e.key) {
@@ -1540,5 +1735,27 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde
setTimeout(function() { burst.remove(); }, 1200);
};
})();
+
+ // === CURSOR SPARKLE TRAIL ===
+ (function cursorSparkle() {
+ var throttle = 0;
+ document.addEventListener('pointermove', function(e) {
+ var now = Date.now();
+ if (now - throttle < 60) return;
+ throttle = now;
+ var d = document.createElement('div');
+ d.className = 'sno-sparkle';
+ var size = 3 + Math.random() * 4;
+ d.style.width = size + 'px';
+ d.style.height = size + 'px';
+ d.style.left = (e.clientX - size / 2 + (Math.random() - 0.5) * 10) + 'px';
+ d.style.top = (e.clientY - size / 2 + (Math.random() - 0.5) * 10) + 'px';
+ d.style.background = 'currentColor';
+ d.style.opacity = '0.7';
+ d.style.animation = 'sno-sparkle ' + (0.35 + Math.random() * 0.25).toFixed(2) + 's ease-out forwards';
+ document.body.appendChild(d);
+ setTimeout(function() { d.remove(); }, 650);
+ }, { passive: true });
+ })();
</script>
{{end}}
diff --git a/internal/version/version.go b/internal/version/version.go
index 7194b6d..ec874b0 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -2,4 +2,4 @@
package version
// Version is the application version (semantic versioning).
-const Version = "0.7.0"
+const Version = "0.8.0"