From 2c498efbfbbfdf93686973abb387c2c795199730 Mon Sep 17 00:00:00 2001 From: jae Date: Mon, 20 Apr 2026 08:19:33 +0000 Subject: [PATCH] =?UTF-8?q?feat(effects):=20sitewide=20effects=20suite=20?= =?UTF-8?q?=E2=80=94=2025=20CLI-triggered=20visual=20modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/data/changelog.json | 259 +++++---- armoury/lab.html | 2 + css/sitewide-effects.css | 331 +++++++++++ depot/contraband.html | 2 + depot/recon.html | 2 + hq/briefing.html | 2 + hq/index.html | 2 + hq/leaderboards.html | 2 + hq/logs.html | 2 + hq/profile.html | 2 + hq/telemetry.html | 2 + index.html | 2 + js/chat-cli.js | 229 ++++---- js/sitewide-effects.js | 1143 ++++++++++++++++++++++++++++++++++++++ recon/index.html | 2 + transmissions/radar.html | 2 + 16 files changed, 1747 insertions(+), 239 deletions(-) create mode 100644 css/sitewide-effects.css create mode 100644 js/sitewide-effects.js diff --git a/api/data/changelog.json b/api/data/changelog.json index 4db9522..b2f9216 100644 --- a/api/data/changelog.json +++ b/api/data/changelog.json @@ -1,42 +1,65 @@ { "site": "jaeswift.xyz", "entries": [ + { + "version": "1.39.0", + "date": "20/04/2026", + "category": "FEATURE", + "title": "Sitewide Effects Suite \u2014 25 CLI-triggered visual modes", + "changes": [ + "New /js/sitewide-effects.js (~1100 lines) + /css/sitewide-effects.css (~330 lines) \u2014 single central module, exposes window.__jaeEffects (toggle/enable/disable/active/disableAll)", + "Injected into 12 pages: index, hq/{telemetry,leaderboards,logs,briefing,profile,index}, depot/{contraband,recon}, recon, armoury/lab, transmissions/radar", + "VISUAL DISTORTION (7): /crt CRT scanlines + phosphor flicker + vignette; /vhs chromatic shift + roll bar + noise + glitch blocks; /glitch periodic text corruption + chroma shake; /redalert (/red alias) sepia-red filter + pulsing inset + Web Audio two-tone klaxon + DEFCON 1 banner; /invert; /blueprint hue-shift + grid overlay; /typewriter retype all text with click beeps", + "PHYSICS & MOTION (5): /gravity \u2014 panels + headings become fixed, fall with bounce physics; /earthquake \u2014 10s viewport shake; /lowgravity \u2014 1.2s transitions + floating bob; /melt \u2014 progressive vertical drip over 15s; /shuffle \u2014 scramble word order across visible text", + "ENVIRONMENTAL (5): /rain 200 drops + thunder flash + filtered-noise ambient; /snow 150 drifting flakes with sine sway; /fog dark overlay + torchlight circle around cursor; /night body dim + cursor spotlight via CSS vars; /underwater hue-rotate + wave overlay + bubble particles + lowpass noise", + "MIND-BENDING (3): /dimensions 3D perspective tilt tracking mouse; /portal \u2014 150px circular iframe lens following cursor showing random other page; /retro <1995|2005|2015|now> applies themed HTML classes (Comic Sans/yellow, Web 2.0 glossy, Material flat)", + "BONUS (5): /partymode disco hue-rotate + confetti + panel flashes + 120 BPM kick drum; /ghostmode invisible cursor + text fade until hover; /quantum panels teleport every 3s; /sneak replace all a/button labels with CLASSIFIED/REDACTED/etc; /hacker full-viewport scrolling fake code", + "Meta command /effects \u2014 shows ASCII status table of active effects; /effects off disables all; /effects all enables everything (chaos mode)", + "/matrix and /cmatrix migrated out of chat-cli.js into the sitewide-effects.js registry \u2014 commands stay identical but share the same single-source-of-truth module; dead toggleMatrix/toggleCMatrix code removed", + "Mobile + prefers-reduced-motion gating: all effects downgrade particle counts / disable audio gracefully on small screens or reduced-motion preference", + "Boss key (Escape / Ctrl+Shift+B) now calls window.__jaeEffects.disableAll() for a universal panic exit", + "Z-index ladder: 9000-9500 for overlays, 9600-9700 for loud UI (banners), leaves 9999+ for modals/toasts", + "CSS classes prefixed fx-* applied to when active; each effect self-contained with enable/disable lifecycle, proper cleanup of RAF / intervals / AudioContext nodes / canvases", + "/help output extended with new EFFECTS category listing all 25 commands + /effects meta", + "Files: NEW js/sitewide-effects.js (1143 lines), NEW css/sitewide-effects.css (331 lines); MOD js/chat-cli.js (1142 \u2192 ~1129 lines \u2014 stripped 110 lines of matrix/cmatrix internals, added ~90 lines of thin command wrappers + EFFECTS help category); MOD 12 HTML files (+2 lines each for + + \ No newline at end of file diff --git a/css/sitewide-effects.css b/css/sitewide-effects.css new file mode 100644 index 0000000..a87c757 --- /dev/null +++ b/css/sitewide-effects.css @@ -0,0 +1,331 @@ +/* ================================================= + JAESWIFT Sitewide Effects CSS + ================================================= */ + +/* 1. CRT */ +html.fx-crt { + filter: contrast(1.1) saturate(1.2) brightness(0.95); + animation: fx-crt-flicker 0.15s infinite; +} +html.fx-crt body { + transform: perspective(1500px) rotateX(0.5deg); + transform-origin: center top; + background: radial-gradient(ellipse at center, #0a1a0a 0%, #000 100%); +} +@keyframes fx-crt-flicker { + 0%, 98% { opacity: 1; } + 99% { opacity: 0.94; } + 100% { opacity: 1; } +} +.fx-crt-overlay { + position: fixed; inset: 0; pointer-events: none; z-index: 9400; + background: + repeating-linear-gradient( + to bottom, + rgba(0,0,0,0) 0px, + rgba(0,0,0,0) 2px, + rgba(0,0,0,0.18) 3px, + rgba(0,0,0,0.18) 4px + ), + radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.7) 100%); + mix-blend-mode: multiply; +} +.fx-crt-overlay::before { + content: ''; + position: absolute; inset: 0; + background: linear-gradient(90deg, rgba(255,0,0,0.03), transparent 3%, transparent 97%, rgba(0,255,255,0.03)); + mix-blend-mode: screen; +} + +/* 2. VHS */ +html.fx-vhs body { + filter: saturate(1.4) contrast(1.1) hue-rotate(-4deg); + text-shadow: -1px 0 rgba(255,0,0,0.5), 1px 0 rgba(0,255,255,0.5); +} +.fx-vhs-roll { + position: fixed; left: 0; right: 0; height: 60px; + background: linear-gradient(180deg, transparent, rgba(255,255,255,0.09), transparent); + pointer-events: none; z-index: 9300; + animation: fx-vhs-roll 8s linear infinite; +} +@keyframes fx-vhs-roll { + 0% { top: -10%; } 100% { top: 110%; } +} +.fx-vhs-glitch-block { + position: fixed; left: 0; right: 0; + background: rgba(0,255,255,0.4); + mix-blend-mode: difference; + pointer-events: none; z-index: 9310; + transform: translateX(5px); +} + +/* 3. GLITCH */ +html.fx-glitch body { + animation: fx-glitch-shake 3.5s infinite steps(1); +} +html.fx-glitch h1, html.fx-glitch h2, html.fx-glitch .panel-title { + position: relative; + animation: fx-glitch-chroma 2.8s infinite steps(1); +} +@keyframes fx-glitch-shake { + 0%, 97%, 100% { transform: translate(0, 0); } + 98% { transform: translate(2px, -1px); } + 99% { transform: translate(-2px, 1px); } +} +@keyframes fx-glitch-chroma { + 0%, 95%, 100% { text-shadow: none; } + 96% { text-shadow: -2px 0 red, 2px 0 cyan; } + 97% { text-shadow: 3px 0 lime, -3px 0 magenta; } + 98% { text-shadow: -1px 0 red, 1px 0 cyan; } +} + +/* 4. RED ALERT */ +html.fx-redalert body { + filter: sepia(1) hue-rotate(-50deg) saturate(4) brightness(0.95); + animation: fx-redalert-pulse 1s infinite; +} +@keyframes fx-redalert-pulse { + 0%, 100% { box-shadow: inset 0 0 60px 10px rgba(255,0,0,0.3); } + 50% { box-shadow: inset 0 0 120px 20px rgba(255,0,0,0.7); } +} +html.fx-redalert body::after { + content: ''; + position: fixed; inset: 0; + border: 8px solid transparent; + box-shadow: inset 0 0 80px 20px rgba(255,0,0,0.5); + pointer-events: none; z-index: 9400; + animation: fx-redalert-pulse 1s infinite; +} +.fx-redalert-banner { + position: fixed; top: 0; left: 0; right: 0; + background: rgba(120,0,0,0.95); color: #fff; + text-align: center; padding: 8px; + font-family: 'Orbitron', monospace; letter-spacing: 4px; + font-size: 14px; font-weight: bold; + z-index: 9700; + animation: fx-redalert-blink 0.8s infinite; + border-bottom: 2px solid #ff3333; +} +@keyframes fx-redalert-blink { + 0%, 49% { opacity: 1; } + 50%, 100% { opacity: 0.4; } +} + +/* 5. INVERT */ +html.fx-invert { filter: invert(1) hue-rotate(180deg); } + +/* 6. BLUEPRINT */ +html.fx-blueprint body { + filter: sepia(1) hue-rotate(180deg) saturate(3) brightness(0.9); +} +.fx-blueprint-grid { + position: fixed; inset: 0; pointer-events: none; z-index: 9050; + background-image: + linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px); + background-size: 40px 40px; + mix-blend-mode: screen; +} + +/* 7. TYPEWRITER (no special styling, just spans) */ +.fx-typewriter-span { display: inline; } + +/* 10. LOW GRAVITY */ +html.fx-lowgrav .panel, html.fx-lowgrav .card { + transition: transform 1.2s ease-out !important; +} +.fx-lowgrav-bob { + animation: fx-lowgrav-bob 4s ease-in-out infinite; +} +@keyframes fx-lowgrav-bob { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-6px); } +} + +/* 13. RAIN thunder flash */ +html.fx-rain-flash body { + filter: brightness(1.3); +} + +/* 15. FOG */ +.fx-fog { + position: fixed; inset: 0; pointer-events: none; z-index: 9300; + background: radial-gradient( + circle 150px at var(--fx-fog-x, 50%) var(--fx-fog-y, 50%), + transparent 0%, + rgba(20,25,35,0.5) 40%, + rgba(10,15,25,0.92) 100% + ); +} + +/* 16. NIGHT */ +html.fx-night body { + filter: brightness(0.5) contrast(1.2); + cursor: crosshair; +} +.fx-night-spot { + position: fixed; inset: 0; pointer-events: none; z-index: 9300; + background: radial-gradient( + circle 200px at var(--fx-spot-x, 50%) var(--fx-spot-y, 50%), + rgba(255,240,180,0.3) 0%, + transparent 100% + ); + mix-blend-mode: screen; +} + +/* 17. UNDERWATER */ +html.fx-underwater body { + filter: hue-rotate(180deg) saturate(1.3) brightness(0.85); + animation: fx-underwater-sway 6s ease-in-out infinite; +} +@keyframes fx-underwater-sway { + 0%, 100% { transform: skewX(0deg); } + 50% { transform: skewX(0.5deg); } +} + +/* 18. DIMENSIONS */ +html.fx-dimensions body { + transform: perspective(1000px) + rotateX(var(--fx-tilt-x, 0deg)) + rotateY(var(--fx-tilt-y, 0deg)); + transition: transform 0.3s ease-out; + transform-style: preserve-3d; +} +html.fx-dimensions .panel { + transform: translateZ(20px); + transform-style: preserve-3d; +} + +/* 19. PORTAL */ +.fx-portal { + position: fixed; + width: 150px; height: 150px; + border-radius: 50%; + overflow: hidden; + pointer-events: none; + z-index: 9400; + border: 2px solid #00ffc8; + box-shadow: + 0 0 30px rgba(0,255,200,0.6), + inset 0 0 30px rgba(0,255,200,0.4); + animation: fx-portal-ripple 1.5s ease-in-out infinite; +} +.fx-portal iframe { + width: 1200px; height: 900px; + border: 0; + pointer-events: none; +} +@keyframes fx-portal-ripple { + 0%, 100% { box-shadow: 0 0 30px rgba(0,255,200,0.6), inset 0 0 30px rgba(0,255,200,0.4); } + 50% { box-shadow: 0 0 60px rgba(0,255,200,0.9), inset 0 0 60px rgba(0,255,200,0.7); } +} + +/* 20. RETRO */ +html.retro-1995 body { + font-family: 'Comic Sans MS', 'Comic Sans', cursive !important; + background: #ffff99 !important; + color: #000 !important; +} +html.retro-1995 a { color: #0000ee !important; text-decoration: underline !important; } +html.retro-1995 a:visited { color: #551a8b !important; } +html.retro-1995 .panel { + border: 3px ridge #c0c0c0 !important; + background: #e0e0e0 !important; + color: #000 !important; +} +html.retro-1995 h1, html.retro-1995 h2, html.retro-1995 h3 { + color: #ff0066 !important; + text-shadow: 2px 2px 0 #ffff00 !important; +} +.fx-retro-1995-banner { + position: fixed; top: 0; left: 0; right: 0; + background: #ffcc00; color: #000; + text-align: center; padding: 6px; + font-family: 'Comic Sans MS', cursive; + font-weight: bold; font-size: 13px; + border-bottom: 3px ridge #ff0000; + z-index: 9700; +} +.fx-retro-1995-banner blink { + animation: fx-blink 1s steps(2) infinite; +} +@keyframes fx-blink { to { visibility: hidden; } } + +html.retro-2005 body { + background: linear-gradient(180deg, #cfe8ff 0%, #9dc5f0 100%) !important; + font-family: 'Trebuchet MS', 'Verdana', sans-serif !important; +} +html.retro-2005 .panel { + background: linear-gradient(180deg, #ffffff 0%, #d6e8ff 100%) !important; + border: 1px solid #4a90d9 !important; + border-radius: 12px !important; + box-shadow: 0 4px 12px rgba(74,144,217,0.3), inset 0 1px 0 #fff !important; + color: #0a3a6a !important; +} +html.retro-2005 h1, html.retro-2005 h2 { + background: linear-gradient(180deg, #1a5eae, #0a3a6a) !important; + -webkit-background-clip: text !important; + background-clip: text !important; + color: transparent !important; + text-shadow: none !important; +} + +html.retro-2015 body { + background: #fafafa !important; + color: #212121 !important; + font-family: 'Roboto', 'Arial', sans-serif !important; +} +html.retro-2015 .panel { + background: #fff !important; + border: none !important; + border-radius: 4px !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.08) !important; + color: #212121 !important; +} +html.retro-2015 h1 { color: #ff5722 !important; font-weight: 300 !important; } +html.retro-2015 h2 { color: #2196f3 !important; font-weight: 300 !important; } +html.retro-2015 a { color: #2196f3 !important; } + +/* 21. PARTY */ +html.fx-party body { + animation: fx-party-hue 2s linear infinite; +} +@keyframes fx-party-hue { + 0% { filter: hue-rotate(0deg); } + 100% { filter: hue-rotate(360deg); } +} + +/* 22. GHOST */ +html.fx-ghost * { + cursor: none !important; +} +html.fx-ghost body, +html.fx-ghost .panel, +html.fx-ghost a, +html.fx-ghost button, +html.fx-ghost h1, html.fx-ghost h2, html.fx-ghost h3, html.fx-ghost p, html.fx-ghost span { + transition: opacity 0.3s; +} +html.fx-ghost .panel, +html.fx-ghost h1, html.fx-ghost h2, html.fx-ghost h3, +html.fx-ghost p, html.fx-ghost a, html.fx-ghost button, html.fx-ghost li { + opacity: 0.08; +} +html.fx-ghost .panel:hover, +html.fx-ghost h1:hover, html.fx-ghost h2:hover, html.fx-ghost h3:hover, +html.fx-ghost p:hover, html.fx-ghost a:hover, html.fx-ghost button:hover, html.fx-ghost li:hover { + opacity: 1; +} + +/* 25. HACKER */ +html.fx-hacker body > *:not(canvas):not(script):not(style) { + filter: brightness(0.25); +} + +/* Reduced motion: neutralize heavy animations */ +@media (prefers-reduced-motion: reduce) { + html.fx-crt, html.fx-redalert body, html.fx-party body, + .fx-vhs-roll, .fx-redalert-banner { + animation: none !important; + } +} diff --git a/depot/contraband.html b/depot/contraband.html index b9e771d..17214e6 100644 --- a/depot/contraband.html +++ b/depot/contraband.html @@ -10,6 +10,7 @@ +
@@ -66,5 +67,6 @@ + diff --git a/depot/recon.html b/depot/recon.html index 29c4fcc..d7d4237 100644 --- a/depot/recon.html +++ b/depot/recon.html @@ -9,6 +9,7 @@ +
@@ -63,5 +64,6 @@ + diff --git a/hq/briefing.html b/hq/briefing.html index 124dbd4..235af31 100644 --- a/hq/briefing.html +++ b/hq/briefing.html @@ -8,6 +8,7 @@ +
@@ -66,5 +67,6 @@ + \ No newline at end of file diff --git a/hq/index.html b/hq/index.html index 5b9f588..4c8b573 100644 --- a/hq/index.html +++ b/hq/index.html @@ -8,6 +8,7 @@ +
@@ -78,5 +79,6 @@ + diff --git a/hq/leaderboards.html b/hq/leaderboards.html index d008ebe..7aae011 100644 --- a/hq/leaderboards.html +++ b/hq/leaderboards.html @@ -12,6 +12,7 @@ + @@ -112,5 +113,6 @@ + diff --git a/hq/logs.html b/hq/logs.html index 8db078b..47a9e32 100644 --- a/hq/logs.html +++ b/hq/logs.html @@ -9,6 +9,7 @@ +
@@ -53,5 +54,6 @@ + diff --git a/hq/profile.html b/hq/profile.html index d120ce4..36daf54 100644 --- a/hq/profile.html +++ b/hq/profile.html @@ -8,6 +8,7 @@ +
@@ -66,5 +67,6 @@ + \ No newline at end of file diff --git a/hq/telemetry.html b/hq/telemetry.html index 9ed6065..dfceafd 100644 --- a/hq/telemetry.html +++ b/hq/telemetry.html @@ -9,6 +9,7 @@ +
@@ -226,5 +227,6 @@ + diff --git a/index.html b/index.html index 24cf876..d4fb8fb 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ + @@ -597,5 +598,6 @@ + diff --git a/js/chat-cli.js b/js/chat-cli.js index f7617b4..dcefe92 100644 --- a/js/chat-cli.js +++ b/js/chat-cli.js @@ -10,7 +10,6 @@ const HIST_KEY = 'jaeCliHist'; let cmdHistory = []; let devMode = false; - let matrixActive = false; let sessionCmdCount = 0; let sessionPerCmd = {}; let mode = null; // null | 'ssh' | 'adventure' | 'ttyper' @@ -190,6 +189,15 @@ ' /adventure cyberpunk text adventure', ' /ttyper typing speed test', '', + '— EFFECTS (sitewide visual overlays) —', + ' /crt /vhs /glitch /redalert /invert /blueprint /typewriter', + ' /gravity /earthquake /lowgravity /melt /shuffle', + ' /rain /snow /fog /night /underwater', + ' /dimensions /portal /retro <1995|2005|2015|now>', + ' /partymode /ghostmode /quantum /sneak /hacker', + ' /matrix /cmatrix matrix rain overlays', + ' /effects show active · /effects off · /effects all (chaos)', + '', '— STATS —', ' /achievements your unlocked commands', ' /leaderboard session & all-time stats', @@ -334,11 +342,10 @@ }; commands.matrix = function () { - matrixActive = !matrixActive; - toggleMatrix(matrixActive); - return matrixActive - ? 'Matrix rain enabled. Follow the white rabbit.' - : 'Matrix rain disabled. Back to the desert of the real.'; + if (!window.__jaeEffects) return 'effects module not loaded.'; + const on = window.__jaeEffects.toggle('matrix'); + return on ? 'Matrix rain enabled. Follow the white rabbit.' + : 'Matrix rain disabled. Back to the desert of the real.'; }; commands.clear = function () { @@ -837,79 +844,100 @@ ].filter(Boolean)); } - // ─── /cmatrix — enhanced matrix rain ───────────── - let cmatrixCanvas = null, cmatrixRAF = null, cmatrixAudio = null, cmatrixStart = 0; - function toggleCMatrix(on) { - if (on) { - cmatrixCanvas = document.createElement('canvas'); - cmatrixCanvas.id = 'cmatrixRain'; - Object.assign(cmatrixCanvas.style, { - position: 'fixed', inset: '0', width: '100%', height: '100%', - pointerEvents: 'none', zIndex: '8', opacity: '0.32', - }); - document.body.appendChild(cmatrixCanvas); - const ctx = cmatrixCanvas.getContext('2d'); - function resize() { cmatrixCanvas.width = innerWidth; cmatrixCanvas.height = innerHeight; } - resize(); - window.addEventListener('resize', resize); - const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ01JAESWIFT░▒▓█'.split(''); - const size = 14; - const cols = Math.floor(cmatrixCanvas.width / size); - const drops = new Array(cols).fill(0).map(() => Math.random() * 40); - cmatrixStart = Date.now(); - try { - const AC = window.AudioContext || window.webkitAudioContext; - if (AC) cmatrixAudio = new AC(); - } catch (e) {} - let tick = 0; - function draw() { - const t = (Date.now() - cmatrixStart) / 1000; - const phase = (t % 30) / 30; - const speed = 1 + phase * 2.5; - ctx.fillStyle = 'rgba(0, 0, 0, 0.08)'; - ctx.fillRect(0, 0, cmatrixCanvas.width, cmatrixCanvas.height); - ctx.font = size + 'px JetBrains Mono, monospace'; - for (let i = 0; i < drops.length; i++) { - const ch = chars[Math.floor(Math.random() * chars.length)]; - ctx.fillStyle = (i % 2 === 0) ? '#00ff66' : '#33ddff'; - ctx.fillText(ch, i * size, drops[i] * size); - if (drops[i] * size > cmatrixCanvas.height && Math.random() > 0.97) drops[i] = 0; - drops[i] += speed; - } - tick++; - if (cmatrixAudio && tick % 4 === 0) { - try { - const osc = cmatrixAudio.createOscillator(); - const g = cmatrixAudio.createGain(); - osc.type = 'square'; - osc.frequency.value = 1600 + Math.random() * 800; - g.gain.value = 0.0035; - osc.connect(g); g.connect(cmatrixAudio.destination); - osc.start(); - osc.stop(cmatrixAudio.currentTime + 0.02); - } catch (e) {} - } - cmatrixRAF = requestAnimationFrame(draw); - } - draw(); - } else { - if (cmatrixRAF) cancelAnimationFrame(cmatrixRAF); - if (cmatrixCanvas) cmatrixCanvas.remove(); - if (cmatrixAudio) { try { cmatrixAudio.close(); } catch(e){} } - cmatrixCanvas = null; cmatrixAudio = null; - } - } - let cmatrixActive = false; + // ─── /cmatrix — migrated to sitewide-effects.js ── commands.cmatrix = function () { - cmatrixActive = !cmatrixActive; - toggleCMatrix(cmatrixActive); - return cmatrixActive - ? 'CMATRIX engaged. Dual-colour rain with audio. Toggle off with /cmatrix.' - : 'CMATRIX disengaged.'; + if (!window.__jaeEffects) return 'effects module not loaded.'; + const on = window.__jaeEffects.toggle('cmatrix'); + return on ? 'CMATRIX engaged. Dual-colour rain with audio. Toggle off with /cmatrix.' + : 'CMATRIX disengaged.'; + }; + + // ─── EFFECTS SUITE — thin wrappers over window.__jaeEffects ── + function fx(name, onMsg, offMsg) { + return function () { + if (!window.__jaeEffects) return 'effects module not loaded.'; + const on = window.__jaeEffects.toggle(name); + return on ? onMsg : offMsg; + }; + } + commands.crt = fx('crt', 'CRT mode engaged. [VINTAGE BEAM ONLINE]', 'CRT mode disengaged.'); + commands.vhs = fx('vhs', 'VHS tape loaded. Tracking...', 'VHS ejected.'); + commands.glitch = fx('glitch', 'Reality corrupted. Good luck.', 'Reality stabilised.'); + commands.redalert = fx('redalert', '⚠ DEFCON 1. Battle stations. ⚠', 'Red alert stood down.'); + commands.red = commands.redalert; + commands.invert = fx('invert', 'Polarity inverted.', 'Polarity restored.'); + commands.blueprint = fx('blueprint', 'Blueprint mode. Architect vision enabled.', 'Blueprint mode disabled.'); + commands.typewriter = fx('typewriter', 'Typewriter engaged. Retyping...', 'Typewriter reset.'); + commands.gravity = fx('gravity', '🌍 Gravity enabled. Everything falls.', '🌍 Gravity disabled. Restored.'); + commands.earthquake = fx('earthquake', '🌋 7.5 magnitude — hold on (10s).', '🌋 Earthquake over.'); + commands.lowgravity = fx('lowgravity', '🚀 Low-gravity environment active.', '🚀 Gravity normalised.'); + commands.melt = fx('melt', 'Pixels liquifying...', 'Reformed.'); + commands.shuffle = fx('shuffle', 'Words scrambled.', 'Words restored.'); + commands.rain = fx('rain', '🌧️ Rain enabled. Manchester vibes.', '🌧️ Rain stopped.'); + commands.snow = fx('snow', '❄️ Snow enabled. Winter is here.', '❄️ Thaw complete.'); + commands.fog = fx('fog', '🌫️ Fog of war. Mind the torchlight.', '🌫️ Fog lifted.'); + commands.night = fx('night', '🌙 Night-vision enabled. Spotlight tracking cursor.', '🌙 Dawn.'); + commands.underwater = fx('underwater', '🌊 Submerged. Pressure nominal.', '🌊 Surfaced.'); + commands.dimensions = fx('dimensions', '🔺 3D tilt engaged. Follow the cursor.', '🔺 2D restored.'); + commands.portal = fx('portal', '🌀 Portal opened. Cursor is now a window.', '🌀 Portal closed.'); + commands.partymode = fx('partymode', '🎉 PARTY MODE. Can someone get the disco ball?', '🎉 Party over. Clean-up on aisle 4.'); + commands.ghostmode = fx('ghostmode', '👻 Ghost mode. Only the brave shall see.', '👻 Visible again.'); + commands.quantum = fx('quantum', '⚛️ Quantum uncertainty enabled. Panels teleporting.', '⚛️ Observation collapsed. Panels reset.'); + commands.sneak = fx('sneak', 'All labels redacted. █████.', 'Labels restored.'); + commands.hacker = fx('hacker', 'H4X0R M0D3 3NG4G3D. We are in.', 'Exited.'); + + commands.retro = function (args) { + if (!window.__jaeEffects) return 'effects module not loaded.'; + const year = (args[0] || '').toLowerCase(); + const fx = window.__jaeEffects.registry.retro; + if (!year || year === 'now' || year === 'off') { + fx.disable(); + return 'Retro mode disabled. Welcome back to 2026.'; + } + if (!['1995','2005','2015'].includes(year)) { + return 'usage: /retro <1995|2005|2015|now>'; + } + fx.disable(); + fx.enable(year); + const flavour = { + '1995': 'Welcome to the World Wide Web! 🌐 Best viewed in Netscape Navigator.', + '2005': 'Web 2.0, baby. Glossy gradients incoming.', + '2015': 'Material Design. Clean. Flat. Safe.', + }; + return flavour[year]; + }; + + commands.effects = function (args) { + if (!window.__jaeEffects) return 'effects module not loaded.'; + const sub = (args[0] || '').toLowerCase(); + if (sub === 'off') { + const n = window.__jaeEffects.active().length; + window.__jaeEffects.disableAll(); + return `Disabled ${n} effect(s).`; + } + if (sub === 'all') { + const list = window.__jaeEffects.list().filter(n => n !== 'retro'); + list.forEach(n => { try { window.__jaeEffects.enable(n); } catch(e){} }); + return '⚠ CHAOS MODE. ' + list.length + ' effects enabled simultaneously. RIP your CPU.'; + } + const active = window.__jaeEffects.active(); + if (!active.length) return 'No effects currently active. Try /help for the EFFECTS list.'; + const lines = [ + '═══ ACTIVE EFFECTS ═══', + '┌─────────────────────┬────────┐', + '│ NAME │ STATUS │', + '├─────────────────────┼────────┤', + ]; + active.forEach(n => { + lines.push('│ ' + n.padEnd(20) + '│ ACTIVE │'); + }); + lines.push('└─────────────────────┴────────┘'); + lines.push(`Total: ${active.length} · /effects off to disable all`); + return lines.join('\n'); }; // ─── /achievements ─────────────────────────────── - const TOTAL_COMMANDS = 24; + const TOTAL_COMMANDS = 50; commands.achievements = function () { const unlocked = [...achievements].sort(); const n = unlocked.length; @@ -964,49 +992,8 @@ ]); }; - // ─── Matrix rain overlay ───────────────────────── - let matrixCanvas = null; - let matrixRAF = null; - function toggleMatrix(on) { - if (on) { - matrixCanvas = document.createElement('canvas'); - matrixCanvas.id = 'matrixRain'; - Object.assign(matrixCanvas.style, { - position: 'fixed', inset: '0', width: '100%', height: '100%', - pointerEvents: 'none', zIndex: '7', opacity: '0.18', - }); - document.body.appendChild(matrixCanvas); - const ctx = matrixCanvas.getContext('2d'); - function resize() { - matrixCanvas.width = window.innerWidth; - matrixCanvas.height = window.innerHeight; - } - resize(); - window.addEventListener('resize', resize); - const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ01JAESWIFT'.split(''); - const size = 16; - const cols = Math.floor(matrixCanvas.width / size); - const drops = new Array(cols).fill(1); - function draw() { - ctx.fillStyle = 'rgba(0, 0, 0, 0.06)'; - ctx.fillRect(0, 0, matrixCanvas.width, matrixCanvas.height); - ctx.fillStyle = '#00cc33'; - ctx.font = size + 'px JetBrains Mono, monospace'; - for (let i = 0; i < drops.length; i++) { - const ch = chars[Math.floor(Math.random() * chars.length)]; - ctx.fillText(ch, i * size, drops[i] * size); - if (drops[i] * size > matrixCanvas.height && Math.random() > 0.975) drops[i] = 0; - drops[i]++; - } - matrixRAF = requestAnimationFrame(draw); - } - draw(); - } else { - if (matrixRAF) cancelAnimationFrame(matrixRAF); - if (matrixCanvas) matrixCanvas.remove(); - matrixCanvas = null; - } - } + // ─── Matrix rain migrated to sitewide-effects.js ── + // ─── Konami Code → Developer Mode ──────────────── const KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a']; @@ -1096,8 +1083,7 @@ // ─── Boss key — Escape / Ctrl+Shift+B ──────────── function bossKeyActivate() { try { - if (matrixActive) { matrixActive = false; toggleMatrix(false); } - if (cmatrixActive) { cmatrixActive = false; toggleCMatrix(false); } + if (window.__jaeEffects) { try { window.__jaeEffects.disableAll(); } catch(e){} } const badge = document.getElementById('devModeBadge'); if (badge) badge.style.display = 'none'; const toasts = document.querySelectorAll('.jae-cli-toast'); @@ -1123,7 +1109,8 @@ const isCtrlShiftB = (e.ctrlKey && e.shiftKey && (e.key === 'B' || e.key === 'b')); if (isEsc || isCtrlShiftB) { // Don't nuke on Escape while user is typing in chat unless something is actually "hot" - const hot = matrixActive || cmatrixActive || document.getElementById('devModeBadge') || document.querySelector('.jae-cli-toast'); + const anyFx = window.__jaeEffects && window.__jaeEffects.active().length > 0; + const hot = anyFx || document.getElementById('devModeBadge') || document.querySelector('.jae-cli-toast'); if (isCtrlShiftB || (isEsc && hot)) { bossKeyActivate(); if (isCtrlShiftB) e.preventDefault(); diff --git a/js/sitewide-effects.js b/js/sitewide-effects.js new file mode 100644 index 0000000..28d4376 --- /dev/null +++ b/js/sitewide-effects.js @@ -0,0 +1,1143 @@ +/* =================================================== + JAESWIFT.XYZ — Sitewide Visual Effects Suite + 27 effects (25 new + matrix + cmatrix migration) + Exposed as window.__jaeEffects + Each effect: { enable(), disable(), active: bool } + =================================================== */ +(function () { + 'use strict'; + + // ─── Environment gates ───────────────────────── + const reducedMotion = () => window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; + const isMobile = () => window.matchMedia && window.matchMedia('(max-width: 640px)').matches; + const shouldDowngrade = () => reducedMotion() || isMobile(); + + // ─── Shared audio context (created on demand) ───────── + let _audioCtx = null; + function getAudio() { + if (!_audioCtx) { + try { + const AC = window.AudioContext || window.webkitAudioContext; + if (AC) _audioCtx = new AC(); + } catch (e) {} + } + if (_audioCtx && _audioCtx.state === 'suspended') { + try { _audioCtx.resume(); } catch (e) {} + } + return _audioCtx; + } + + // ─── Helpers ────────────────────────────────────── + function makeFullCanvas(z, opacity) { + const c = document.createElement('canvas'); + Object.assign(c.style, { + position: 'fixed', inset: '0', width: '100%', height: '100%', + pointerEvents: 'none', zIndex: String(z || 9100), + opacity: String(opacity != null ? opacity : 1), + }); + function resize() { c.width = window.innerWidth; c.height = window.innerHeight; } + resize(); + c._resize = resize; + window.addEventListener('resize', resize); + document.body.appendChild(c); + return c; + } + function destroyCanvas(c) { + if (!c) return; + if (c._resize) window.removeEventListener('resize', c._resize); + c.remove(); + } + function injectStyle(id, css) { + if (document.getElementById(id)) return; + const s = document.createElement('style'); + s.id = id; + s.textContent = css; + document.head.appendChild(s); + } + function ejectStyle(id) { + const s = document.getElementById(id); + if (s) s.remove(); + } + function walkText(root, skipSelector) { + // Return array of text nodes in visible elements (skipping nav/inputs/scripts) + const skip = /^(SCRIPT|STYLE|NOSCRIPT|NAV|INPUT|TEXTAREA|CODE|PRE|CANVAS|SVG|IFRAME|BUTTON)$/i; + const out = []; + const w = document.createTreeWalker(root || document.body, NodeFilter.SHOW_TEXT, { + acceptNode: (n) => { + if (!n.parentElement) return NodeFilter.FILTER_REJECT; + let p = n.parentElement; + while (p && p !== document.body) { + if (skip.test(p.tagName)) return NodeFilter.FILTER_REJECT; + if (p.classList && (p.classList.contains('chat-messages') || p.classList.contains('chat-msg') || p.id === 'chatMessages' || p.id === 'chatInput')) return NodeFilter.FILTER_REJECT; + p = p.parentElement; + } + if (skipSelector && n.parentElement.closest(skipSelector)) return NodeFilter.FILTER_REJECT; + if (!/\S/.test(n.nodeValue)) return NodeFilter.FILTER_REJECT; + return NodeFilter.FILTER_ACCEPT; + } + }); + let node; + while ((node = w.nextNode())) out.push(node); + return out; + } + function snapshotRect(el) { + const r = el.getBoundingClientRect(); + return { left: r.left, top: r.top, width: r.width, height: r.height }; + } + + // ================================================== + // EFFECT REGISTRY + // ================================================== + const R = {}; + + // --- MATRIX (migrated) --- + R.matrix = (function () { + let canvas = null, raf = null; + return { + active: false, + enable() { + if (this.active) return; + this.active = true; + canvas = makeFullCanvas(9050, 0.18); + const ctx = canvas.getContext('2d'); + const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ01JAESWIFT'.split(''); + const size = 16; + let cols = Math.floor(canvas.width / size); + let drops = new Array(cols).fill(1); + const onResize = () => { cols = Math.floor(canvas.width / size); drops = new Array(cols).fill(1); }; + window.addEventListener('resize', onResize); + canvas._onResize2 = onResize; + function draw() { + ctx.fillStyle = 'rgba(0,0,0,0.06)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#00cc33'; + ctx.font = size + 'px JetBrains Mono, monospace'; + for (let i = 0; i < drops.length; i++) { + const ch = chars[Math.floor(Math.random() * chars.length)]; + ctx.fillText(ch, i * size, drops[i] * size); + if (drops[i] * size > canvas.height && Math.random() > 0.975) drops[i] = 0; + drops[i]++; + } + raf = requestAnimationFrame(draw); + } + draw(); + }, + disable() { + if (!this.active) return; + this.active = false; + if (raf) cancelAnimationFrame(raf); + if (canvas && canvas._onResize2) window.removeEventListener('resize', canvas._onResize2); + destroyCanvas(canvas); + canvas = null; raf = null; + } + }; + })(); + + // --- CMATRIX (migrated, enhanced) --- + R.cmatrix = (function () { + let canvas = null, raf = null, audio = null, start = 0; + return { + active: false, + enable() { + if (this.active) return; + this.active = true; + canvas = makeFullCanvas(9060, 0.32); + const ctx = canvas.getContext('2d'); + const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ01JAESWIFT'.split(''); + const size = 16; + let cols = Math.floor(canvas.width / size); + let drops = new Array(cols).fill(1); + const onResize = () => { cols = Math.floor(canvas.width / size); drops = new Array(cols).fill(1); }; + window.addEventListener('resize', onResize); + canvas._onResize2 = onResize; + start = Date.now(); + audio = getAudio(); + let tick = 0; + function draw() { + const t = (Date.now() - start) / 1000; + const phase = Math.min(1, t / 30); + const speed = 1 + phase * 2.5; + ctx.fillStyle = 'rgba(0,0,0,0.07)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.font = size + 'px JetBrains Mono, monospace'; + for (let i = 0; i < drops.length; i++) { + const ch = chars[Math.floor(Math.random() * chars.length)]; + ctx.fillStyle = (i % 7 === 0) ? '#33ddff' : '#00ff66'; + ctx.fillText(ch, i * size, drops[i] * size); + if (drops[i] * size > canvas.height && Math.random() > 0.97) drops[i] = 0; + drops[i] += speed; + } + if (audio && tick % 4 === 0) { + try { + const osc = audio.createOscillator(); + const g = audio.createGain(); + osc.type = 'square'; + osc.frequency.value = 1600 + Math.random() * 800; + g.gain.value = 0.0035; + osc.connect(g); g.connect(audio.destination); + osc.start(); + osc.stop(audio.currentTime + 0.02); + } catch (e) {} + } + tick++; + raf = requestAnimationFrame(draw); + } + draw(); + }, + disable() { + if (!this.active) return; + this.active = false; + if (raf) cancelAnimationFrame(raf); + if (canvas && canvas._onResize2) window.removeEventListener('resize', canvas._onResize2); + destroyCanvas(canvas); + canvas = null; raf = null; audio = null; + } + }; + })(); + + // --- 1. CRT --- + R.crt = { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-crt'); + const overlay = document.createElement('div'); + overlay.className = 'fx-crt-overlay'; + overlay.id = 'fx-crt-overlay'; + document.body.appendChild(overlay); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-crt'); + const o = document.getElementById('fx-crt-overlay'); if (o) o.remove(); + } + }; + + // --- 2. VHS --- + R.vhs = (function () { + let noiseCanvas = null, noiseRaf = null, glitchInterval = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-vhs'); + const rollBar = document.createElement('div'); + rollBar.className = 'fx-vhs-roll'; rollBar.id = 'fx-vhs-roll'; + document.body.appendChild(rollBar); + noiseCanvas = makeFullCanvas(9200, 0.12); + const ctx = noiseCanvas.getContext('2d'); + function noise() { + const w = noiseCanvas.width, h = noiseCanvas.height; + const img = ctx.createImageData(w, h); + for (let i = 0; i < img.data.length; i += 4) { + const v = Math.random() * 255 | 0; + img.data[i] = v; img.data[i + 1] = v; img.data[i + 2] = v; img.data[i + 3] = 60; + } + ctx.putImageData(img, 0, 0); + noiseRaf = setTimeout(() => requestAnimationFrame(noise), 80); + } + if (!shouldDowngrade()) noise(); + glitchInterval = setInterval(() => { + const b = document.createElement('div'); + b.className = 'fx-vhs-glitch-block'; + b.style.top = Math.random() * 100 + '%'; + b.style.height = (4 + Math.random() * 14) + 'px'; + document.body.appendChild(b); + setTimeout(() => b.remove(), 80); + }, 3000 + Math.random() * 2000); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-vhs'); + const r = document.getElementById('fx-vhs-roll'); if (r) r.remove(); + if (noiseRaf) clearTimeout(noiseRaf); + destroyCanvas(noiseCanvas); noiseCanvas = null; + if (glitchInterval) clearInterval(glitchInterval); glitchInterval = null; + document.querySelectorAll('.fx-vhs-glitch-block').forEach(e => e.remove()); + } + }; + })(); + + // --- 3. GLITCH --- + R.glitch = (function () { + let scrambleInterval = null; + const origValues = new WeakMap(); + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-glitch'); + if (shouldDowngrade()) return; + scrambleInterval = setInterval(() => { + const nodes = walkText(); + if (!nodes.length) return; + const picks = []; + for (let i = 0; i < 3; i++) picks.push(nodes[Math.floor(Math.random() * nodes.length)]); + picks.forEach(n => { + if (!n || !n.nodeValue || n.nodeValue.length < 3) return; + if (!origValues.has(n)) origValues.set(n, n.nodeValue); + const orig = origValues.get(n); + const arr = orig.split(''); + const idx = Math.floor(Math.random() * (arr.length - 1)); + const tmp = arr[idx]; arr[idx] = arr[idx + 1]; arr[idx + 1] = tmp; + n.nodeValue = arr.join(''); + setTimeout(() => { try { n.nodeValue = orig; } catch (e) {} }, 80); + }); + }, 2000); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-glitch'); + if (scrambleInterval) clearInterval(scrambleInterval); scrambleInterval = null; + } + }; + })(); + + // --- 4. RED ALERT --- + R.redalert = (function () { + let banner = null, oscA = null, gain = null, toggle = null, audio = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-redalert'); + banner = document.createElement('div'); + banner.id = 'fx-redalert-banner'; + banner.className = 'fx-redalert-banner'; + banner.textContent = '⚠ DEFCON 1 — RED ALERT ⚠'; + document.body.appendChild(banner); + audio = getAudio(); + if (audio && !shouldDowngrade()) { + try { + oscA = audio.createOscillator(); + gain = audio.createGain(); + gain.gain.value = 0.03; + oscA.type = 'sine'; + oscA.frequency.value = 400; + oscA.connect(gain); gain.connect(audio.destination); + oscA.start(); + let hi = false; + toggle = setInterval(() => { + hi = !hi; + try { oscA.frequency.setValueAtTime(hi ? 800 : 400, audio.currentTime); } catch (e) {} + }, 500); + } catch (e) {} + } + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-redalert'); + if (banner) banner.remove(); banner = null; + if (toggle) clearInterval(toggle); toggle = null; + if (oscA) { try { oscA.stop(); } catch (e) {} oscA = null; } + gain = null; + } + }; + })(); + + // --- 5. INVERT --- + R.invert = { + active: false, + enable() { if (this.active) return; this.active = true; document.documentElement.classList.add('fx-invert'); }, + disable() { if (!this.active) return; this.active = false; document.documentElement.classList.remove('fx-invert'); } + }; + + // --- 6. BLUEPRINT --- + R.blueprint = { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-blueprint'); + const g = document.createElement('div'); g.id = 'fx-blueprint-grid'; g.className = 'fx-blueprint-grid'; + document.body.appendChild(g); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-blueprint'); + const g = document.getElementById('fx-blueprint-grid'); if (g) g.remove(); + } + }; + + // --- 7. TYPEWRITER --- + R.typewriter = (function () { + const wrapped = []; + let running = false; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + if (shouldDowngrade()) return; + const nodes = walkText(); + const audio = getAudio(); + nodes.forEach(n => { + const text = n.nodeValue; + if (text.length < 2) return; + const span = document.createElement('span'); + span.className = 'fx-typewriter-span'; + n.parentNode.replaceChild(span, n); + wrapped.push({ span, text, parent: span.parentNode }); + span.textContent = ''; + }); + running = true; + let idx = 0; + const self = this; + function typeNext() { + if (!running || idx >= wrapped.length) return; + const { span, text } = wrapped[idx]; + let i = 0; + const interval = setInterval(() => { + if (!running) { clearInterval(interval); return; } + if (i >= text.length) { + clearInterval(interval); + idx++; + setTimeout(typeNext, 40); + return; + } + span.textContent += text[i]; + if (audio && i % 5 === 0) { + try { + const osc = audio.createOscillator(); + const g = audio.createGain(); + osc.frequency.value = 1200; g.gain.value = 0.004; + osc.connect(g); g.connect(audio.destination); + osc.start(); osc.stop(audio.currentTime + 0.01); + } catch (e) {} + } + i++; + }, 30); + } + typeNext(); + }, + disable() { + if (!this.active) return; this.active = false; + running = false; + wrapped.forEach(({ span, text }) => { + if (span.parentNode) { + const tn = document.createTextNode(text); + span.parentNode.replaceChild(tn, span); + } + }); + wrapped.length = 0; + } + }; + })(); + + // --- 8. GRAVITY --- + R.gravity = (function () { + let items = [], raf = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + const selectors = '.panel, .card, h1, h2, h3'; + const els = Array.from(document.querySelectorAll(selectors)).slice(0, 40); + items = els.map(el => { + const r = snapshotRect(el); + const data = { + el, + origStyle: el.getAttribute('style') || '', + x: r.left, y: r.top, w: r.width, h: r.height, + vx: (Math.random() - 0.5) * 3, vy: -Math.random() * 4, + }; + el.style.position = 'fixed'; + el.style.left = r.left + 'px'; + el.style.top = r.top + 'px'; + el.style.width = r.width + 'px'; + el.style.margin = '0'; + el.style.zIndex = '8000'; + return data; + }); + function step() { + const floor = window.innerHeight; + items.forEach(it => { + it.vy += 0.6; + it.x += it.vx; + it.y += it.vy; + if (it.y + it.h >= floor) { + it.y = floor - it.h; + it.vy *= -0.3; + it.vx *= 0.8; + if (Math.abs(it.vy) < 0.5) it.vy = 0; + } + if (it.x < 0) { it.x = 0; it.vx *= -0.5; } + if (it.x + it.w > window.innerWidth) { it.x = window.innerWidth - it.w; it.vx *= -0.5; } + it.el.style.left = it.x + 'px'; + it.el.style.top = it.y + 'px'; + }); + raf = requestAnimationFrame(step); + } + step(); + }, + disable() { + if (!this.active) return; this.active = false; + if (raf) cancelAnimationFrame(raf); raf = null; + items.forEach(it => { + if (it.origStyle) it.el.setAttribute('style', it.origStyle); + else it.el.removeAttribute('style'); + }); + items = []; + } + }; + })(); + + // --- 9. EARTHQUAKE --- + R.earthquake = (function () { + let interval = null, stopTimer = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + const self = this; + interval = setInterval(() => { + const x = (Math.random() - 0.5) * 20; + const y = (Math.random() - 0.5) * 20; + document.body.style.transform = `translate(${x}px, ${y}px)`; + }, 50); + stopTimer = setTimeout(() => self.disable(), 10000); + }, + disable() { + if (!this.active) return; this.active = false; + if (interval) clearInterval(interval); interval = null; + if (stopTimer) clearTimeout(stopTimer); stopTimer = null; + document.body.style.transform = ''; + } + }; + })(); + + // --- 10. LOW GRAVITY --- + R.lowgravity = { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-lowgrav'); + document.querySelectorAll('.panel, .card').forEach((el, i) => { + el.style.animationDelay = (Math.random() * 4) + 's'; + el.classList.add('fx-lowgrav-bob'); + }); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-lowgrav'); + document.querySelectorAll('.fx-lowgrav-bob').forEach(el => { + el.classList.remove('fx-lowgrav-bob'); + el.style.animationDelay = ''; + }); + } + }; + + // --- 11. MELT --- + R.melt = (function () { + let items = [], raf = null, t0 = 0; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + const els = Array.from(document.querySelectorAll('p, h1, h2, h3, h4, li, span, a')).filter(el => el.offsetParent !== null).slice(0, 200); + items = els.map(el => ({ el, origTransform: el.style.transform || '', drop: window.innerHeight - el.getBoundingClientRect().top - 20 + Math.random() * 60, jitter: (Math.random() - 0.5) * 10 })); + t0 = performance.now(); + const self = this; + function step(t) { + const elapsed = (t - t0) / 15000; // 15s to fully melt + const p = Math.min(1, elapsed); + items.forEach(it => { + const y = it.drop * easeInCubic(p); + it.el.style.transform = `translate(${it.jitter * p}px, ${y}px)`; + it.el.style.transition = 'transform 0.1s linear'; + }); + if (p < 1 && self.active) raf = requestAnimationFrame(step); + } + function easeInCubic(x) { return x * x * x; } + step(t0); + }, + disable() { + if (!this.active) return; this.active = false; + if (raf) cancelAnimationFrame(raf); raf = null; + items.forEach(it => { it.el.style.transform = it.origTransform; it.el.style.transition = ''; }); + items = []; + } + }; + })(); + + // --- 12. SHUFFLE --- + R.shuffle = (function () { + const saved = []; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + const nodes = walkText(); + nodes.forEach(n => { + const text = n.nodeValue; + if (!/\s/.test(text)) return; + saved.push({ node: n, orig: text }); + const words = text.split(/(\s+)/); + const content = words.filter(w => /\S/.test(w)); + for (let i = content.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [content[i], content[j]] = [content[j], content[i]]; + } + let ci = 0; + const shuffled = words.map(w => /\S/.test(w) ? content[ci++] : w).join(''); + n.nodeValue = shuffled; + }); + }, + disable() { + if (!this.active) return; this.active = false; + saved.forEach(({ node, orig }) => { try { node.nodeValue = orig; } catch (e) {} }); + saved.length = 0; + } + }; + })(); + + // --- 13. RAIN --- + R.rain = (function () { + let canvas = null, raf = null, drops = [], thunder = null, audio = null, noiseNode = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + canvas = makeFullCanvas(9100, 0.55); + const ctx = canvas.getContext('2d'); + const count = shouldDowngrade() ? 60 : 200; + drops = []; + for (let i = 0; i < count; i++) { + drops.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, len: 8 + Math.random() * 12, spd: 10 + Math.random() * 8 }); + } + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.strokeStyle = 'rgba(180,220,255,0.55)'; + ctx.lineWidth = 1; + drops.forEach(d => { + ctx.beginPath(); + ctx.moveTo(d.x, d.y); + ctx.lineTo(d.x - 2, d.y + d.len); + ctx.stroke(); + d.y += d.spd; + if (d.y > canvas.height) { d.y = -d.len; d.x = Math.random() * canvas.width; } + }); + raf = requestAnimationFrame(draw); + } + draw(); + // thunder flicker + function flash() { + document.documentElement.classList.add('fx-rain-flash'); + setTimeout(() => document.documentElement.classList.remove('fx-rain-flash'), 80); + thunder = setTimeout(flash, 15000 + Math.random() * 15000); + } + thunder = setTimeout(flash, 15000); + // ambient noise + audio = getAudio(); + if (audio && !shouldDowngrade()) { + try { + const bufferSize = 2 * audio.sampleRate; + const buffer = audio.createBuffer(1, bufferSize, audio.sampleRate); + const data = buffer.getChannelData(0); + let lastOut = 0; + for (let i = 0; i < bufferSize; i++) { + const white = Math.random() * 2 - 1; + data[i] = (lastOut + (0.02 * white)) / 1.02; + lastOut = data[i]; + data[i] *= 3.5; + } + noiseNode = audio.createBufferSource(); + noiseNode.buffer = buffer; noiseNode.loop = true; + const filter = audio.createBiquadFilter(); + filter.type = 'highpass'; filter.frequency.value = 800; + const g = audio.createGain(); g.gain.value = 0.04; + noiseNode.connect(filter); filter.connect(g); g.connect(audio.destination); + noiseNode.start(); + } catch (e) {} + } + }, + disable() { + if (!this.active) return; this.active = false; + if (raf) cancelAnimationFrame(raf); raf = null; + if (thunder) clearTimeout(thunder); thunder = null; + destroyCanvas(canvas); canvas = null; + if (noiseNode) { try { noiseNode.stop(); } catch (e) {} noiseNode = null; } + document.documentElement.classList.remove('fx-rain-flash'); + } + }; + })(); + + // --- 14. SNOW --- + R.snow = (function () { + let canvas = null, raf = null, flakes = []; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + canvas = makeFullCanvas(9100, 0.75); + const ctx = canvas.getContext('2d'); + const count = shouldDowngrade() ? 50 : 150; + flakes = []; + for (let i = 0; i < count; i++) { + flakes.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: 1 + Math.random() * 3, vy: 0.5 + Math.random() * 1.5, phase: Math.random() * Math.PI * 2 }); + } + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = 'rgba(255,255,255,0.85)'; + flakes.forEach(f => { + f.phase += 0.02; + f.y += f.vy; + const x = f.x + Math.sin(f.phase) * 12; + ctx.beginPath(); + ctx.arc(x, f.y, f.r, 0, Math.PI * 2); + ctx.fill(); + if (f.y > canvas.height) { f.y = -5; f.x = Math.random() * canvas.width; } + }); + raf = requestAnimationFrame(draw); + } + draw(); + }, + disable() { + if (!this.active) return; this.active = false; + if (raf) cancelAnimationFrame(raf); raf = null; + destroyCanvas(canvas); canvas = null; + } + }; + })(); + + // --- 15. FOG --- + R.fog = (function () { + let overlay = null, onMove = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + overlay = document.createElement('div'); + overlay.id = 'fx-fog'; overlay.className = 'fx-fog'; + document.body.appendChild(overlay); + onMove = (e) => { + overlay.style.setProperty('--fx-fog-x', e.clientX + 'px'); + overlay.style.setProperty('--fx-fog-y', e.clientY + 'px'); + }; + window.addEventListener('mousemove', onMove); + }, + disable() { + if (!this.active) return; this.active = false; + if (onMove) window.removeEventListener('mousemove', onMove); onMove = null; + if (overlay) overlay.remove(); overlay = null; + } + }; + })(); + + // --- 16. NIGHT --- + R.night = (function () { + let overlay = null, onMove = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-night'); + overlay = document.createElement('div'); + overlay.id = 'fx-night-spot'; overlay.className = 'fx-night-spot'; + document.body.appendChild(overlay); + onMove = (e) => { + overlay.style.setProperty('--fx-spot-x', e.clientX + 'px'); + overlay.style.setProperty('--fx-spot-y', e.clientY + 'px'); + }; + window.addEventListener('mousemove', onMove); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-night'); + if (onMove) window.removeEventListener('mousemove', onMove); onMove = null; + if (overlay) overlay.remove(); overlay = null; + } + }; + })(); + + // --- 17. UNDERWATER --- + R.underwater = (function () { + let canvas = null, raf = null, bubbles = [], audio = null, noiseNode = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-underwater'); + canvas = makeFullCanvas(9100, 0.6); + const ctx = canvas.getContext('2d'); + const count = shouldDowngrade() ? 15 : 30; + bubbles = []; + for (let i = 0; i < count; i++) { + bubbles.push({ x: Math.random() * canvas.width, y: canvas.height + Math.random() * canvas.height, r: 3 + Math.random() * 8, vy: 0.5 + Math.random() * 1.2, phase: Math.random() * Math.PI * 2 }); + } + let t = 0; + function draw() { + t += 0.02; + ctx.clearRect(0, 0, canvas.width, canvas.height); + // wave overlay + ctx.fillStyle = 'rgba(80,140,200,0.08)'; + for (let y = 0; y < canvas.height; y += 30) { + const off = Math.sin(t + y * 0.02) * 10; + ctx.fillRect(off, y, canvas.width, 15); + } + // bubbles + ctx.strokeStyle = 'rgba(200,230,255,0.8)'; + ctx.lineWidth = 1; + bubbles.forEach(b => { + b.phase += 0.05; + b.y -= b.vy; + const x = b.x + Math.sin(b.phase) * 8; + ctx.beginPath(); + ctx.arc(x, b.y, b.r, 0, Math.PI * 2); + ctx.stroke(); + if (b.y + b.r < 0) { b.y = canvas.height + 10; b.x = Math.random() * canvas.width; } + }); + raf = requestAnimationFrame(draw); + } + draw(); + audio = getAudio(); + if (audio && !shouldDowngrade()) { + try { + const bufferSize = 2 * audio.sampleRate; + const buffer = audio.createBuffer(1, bufferSize, audio.sampleRate); + const data = buffer.getChannelData(0); + for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.5; + noiseNode = audio.createBufferSource(); + noiseNode.buffer = buffer; noiseNode.loop = true; + const filter = audio.createBiquadFilter(); + filter.type = 'lowpass'; filter.frequency.value = 400; + const g = audio.createGain(); g.gain.value = 0.03; + noiseNode.connect(filter); filter.connect(g); g.connect(audio.destination); + noiseNode.start(); + } catch (e) {} + } + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-underwater'); + if (raf) cancelAnimationFrame(raf); raf = null; + destroyCanvas(canvas); canvas = null; + if (noiseNode) { try { noiseNode.stop(); } catch (e) {} noiseNode = null; } + } + }; + })(); + + // --- 18. DIMENSIONS --- + R.dimensions = (function () { + let onMove = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-dimensions'); + onMove = (e) => { + const rx = ((e.clientY / window.innerHeight) - 0.5) * -10; + const ry = ((e.clientX / window.innerWidth) - 0.5) * 10; + document.body.style.setProperty('--fx-tilt-x', rx + 'deg'); + document.body.style.setProperty('--fx-tilt-y', ry + 'deg'); + }; + window.addEventListener('mousemove', onMove); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-dimensions'); + if (onMove) window.removeEventListener('mousemove', onMove); onMove = null; + document.body.style.removeProperty('--fx-tilt-x'); + document.body.style.removeProperty('--fx-tilt-y'); + } + }; + })(); + + // --- 19. PORTAL --- + R.portal = (function () { + let portal = null, onMove = null; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + portal = document.createElement('div'); + portal.id = 'fx-portal'; portal.className = 'fx-portal'; + const targets = ['/hq/telemetry', '/depot/contraband', '/recon', '/armoury/lab', '/transmissions/radar', '/hq/leaderboards']; + const pick = targets[Math.floor(Math.random() * targets.length)]; + portal.innerHTML = ''; + document.body.appendChild(portal); + onMove = (e) => { + portal.style.left = (e.clientX - 75) + 'px'; + portal.style.top = (e.clientY - 75) + 'px'; + const iframe = portal.querySelector('iframe'); + if (iframe) { + const tx = -((e.clientX / window.innerWidth) * 1000) + 400; + const ty = -((e.clientY / window.innerHeight) * 800) + 300; + iframe.style.transform = `translate(${tx}px, ${ty}px)`; + } + }; + window.addEventListener('mousemove', onMove); + }, + disable() { + if (!this.active) return; this.active = false; + if (onMove) window.removeEventListener('mousemove', onMove); onMove = null; + if (portal) portal.remove(); portal = null; + } + }; + })(); + + // --- 20. RETRO --- + R.retro = (function () { + let currentTheme = null, banner = null; + function clearAll() { + ['retro-1995', 'retro-2005', 'retro-2015'].forEach(c => document.documentElement.classList.remove(c)); + if (banner) { banner.remove(); banner = null; } + } + return { + active: false, + currentArg: null, + enable(arg) { + const year = (arg || 'now').toLowerCase(); + clearAll(); + this.currentArg = year; + if (year === '1995') { + document.documentElement.classList.add('retro-1995'); + banner = document.createElement('div'); + banner.className = 'fx-retro-1995-banner'; + banner.innerHTML = '🚧 UNDER CONSTRUCTION 🚧   VISITORS: 000' + (1000 + Math.floor(Math.random() * 9000)) + '   BEST VIEWED IN NETSCAPE'; + document.body.appendChild(banner); + this.active = true; + } else if (year === '2005') { + document.documentElement.classList.add('retro-2005'); + this.active = true; + } else if (year === '2015') { + document.documentElement.classList.add('retro-2015'); + this.active = true; + } else { + this.active = false; + } + }, + disable() { + this.active = false; + this.currentArg = null; + clearAll(); + } + }; + })(); + + // --- 21. PARTY MODE --- + R.partymode = (function () { + let canvas = null, raf = null, audio = null, confettiTimer = null, flashTimer = null, particles = [], kickInterval = null; + const palette = ['#ff3366', '#33ffcc', '#ffcc33', '#cc33ff', '#66ff33', '#3366ff']; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-party'); + canvas = makeFullCanvas(9300, 0.85); + const ctx = canvas.getContext('2d'); + function spawn() { + if (shouldDowngrade()) return; + for (let i = 0; i < 30; i++) { + particles.push({ + x: Math.random() * canvas.width, y: -10, + vx: (Math.random() - 0.5) * 4, vy: 1 + Math.random() * 4, + size: 3 + Math.random() * 5, + color: palette[Math.floor(Math.random() * palette.length)], + rot: Math.random() * Math.PI * 2, vr: (Math.random() - 0.5) * 0.2, + }); + } + } + spawn(); + confettiTimer = setInterval(spawn, 3000); + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + particles.forEach(p => { + p.vy += 0.05; + p.x += p.vx; p.y += p.vy; p.rot += p.vr; + ctx.save(); + ctx.translate(p.x, p.y); + ctx.rotate(p.rot); + ctx.fillStyle = p.color; + ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size); + ctx.restore(); + }); + particles = particles.filter(p => p.y < canvas.height + 20); + raf = requestAnimationFrame(draw); + } + draw(); + flashTimer = setInterval(() => { + const panels = document.querySelectorAll('.panel'); + if (panels.length) { + const el = panels[Math.floor(Math.random() * panels.length)]; + const c = palette[Math.floor(Math.random() * palette.length)]; + const orig = el.style.backgroundColor; + el.style.transition = 'background-color 0.2s'; + el.style.backgroundColor = c; + setTimeout(() => { el.style.backgroundColor = orig; }, 200); + } + }, 500); + audio = getAudio(); + if (audio && !shouldDowngrade()) { + kickInterval = setInterval(() => { + try { + const osc = audio.createOscillator(); + const g = audio.createGain(); + osc.frequency.setValueAtTime(120, audio.currentTime); + osc.frequency.exponentialRampToValueAtTime(40, audio.currentTime + 0.15); + g.gain.setValueAtTime(0.15, audio.currentTime); + g.gain.exponentialRampToValueAtTime(0.001, audio.currentTime + 0.15); + osc.connect(g); g.connect(audio.destination); + osc.start(); osc.stop(audio.currentTime + 0.15); + } catch (e) {} + }, 500); + } + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-party'); + if (raf) cancelAnimationFrame(raf); raf = null; + if (confettiTimer) clearInterval(confettiTimer); confettiTimer = null; + if (flashTimer) clearInterval(flashTimer); flashTimer = null; + if (kickInterval) clearInterval(kickInterval); kickInterval = null; + destroyCanvas(canvas); canvas = null; particles = []; + } + }; + })(); + + // --- 22. GHOST MODE --- + R.ghostmode = { + active: false, + enable() { + if (this.active) return; this.active = true; + document.documentElement.classList.add('fx-ghost'); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-ghost'); + } + }; + + // --- 23. QUANTUM --- + R.quantum = (function () { + let timer = null, originals = []; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + const panels = document.querySelectorAll('.panel'); + panels.forEach(p => { + originals.push({ el: p, transform: p.style.transform, transition: p.style.transition }); + p.style.transition = 'transform 0.5s ease'; + }); + timer = setInterval(() => { + panels.forEach(p => { + const x = (Math.random() - 0.5) * 60; + const y = (Math.random() - 0.5) * 60; + p.style.transform = `translate(${x}vw, ${y}vh)`; + }); + }, 3000); + }, + disable() { + if (!this.active) return; this.active = false; + if (timer) clearInterval(timer); timer = null; + originals.forEach(o => { o.el.style.transform = o.transform; o.el.style.transition = o.transition; }); + originals = []; + } + }; + })(); + + // --- 24. SNEAK --- + R.sneak = (function () { + const SNEAKY = ['CLASSIFIED', 'REDACTED', 'ACCESS GRANTED', 'DO NOT CLICK', 'FREE MONEY', 'TOP SECRET', '█████', 'COMPROMISED', 'SUSPECT', 'EYES ONLY', 'TRAP', 'HONEYPOT']; + const saved = []; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + document.querySelectorAll('a, button').forEach(el => { + if (!el.childNodes.length) return; + const hasOnlyText = Array.from(el.childNodes).every(c => c.nodeType === 3); + if (!hasOnlyText) return; + const orig = el.textContent; + if (!orig.trim()) return; + saved.push({ el, orig, title: el.getAttribute('title') }); + el.setAttribute('title', orig); + el.textContent = SNEAKY[Math.floor(Math.random() * SNEAKY.length)]; + }); + }, + disable() { + if (!this.active) return; this.active = false; + saved.forEach(({ el, orig, title }) => { + el.textContent = orig; + if (title == null) el.removeAttribute('title'); + else el.setAttribute('title', title); + }); + saved.length = 0; + } + }; + })(); + + // --- 25. HACKER --- + R.hacker = (function () { + let canvas = null, raf = null, lines = []; + const snippets = [ + '#include ', 'int main(int argc, char** argv) {', + ' for (int i = 0; i < n; i++) {', ' if (ptr[i] == NULL) return -1;', + ' }', ' exit(0);', '}', + 'def exploit(target):', ' shellcode = b"\\x31\\xc0\\x50\\x68"', + ' payload = nop_sled(128) + shellcode', ' return pwn(target, payload)', + 'mov eax, 0x1337', 'push ebp', 'call 0xdeadbeef', 'jmp short $-5', + 'INFO: Scanning 10.0.0.0/24...', 'CRIT: Buffer overflow detected', + 'WARN: Rootkit signature found', 'ALERT: Firewall breach imminent', + 'echo "pwned" > /root/.hacked', 'sudo rm -rf / --no-preserve-root', + '0x00007fff5fbff7b8: 0x00007fff8b4a3d80', 'SEGFAULT at 0x0', + 'while(1) fork();', 'cat /etc/shadow', 'nmap -p- -sS -T4 target.com', + ]; + return { + active: false, + enable() { + if (this.active) return; this.active = true; + canvas = makeFullCanvas(9500, 0.88); + const ctx = canvas.getContext('2d'); + document.documentElement.classList.add('fx-hacker'); + const size = 13; + lines = []; + for (let y = 0; y < canvas.height; y += size) { + lines.push({ text: snippets[Math.floor(Math.random() * snippets.length)], y: y }); + } + function draw() { + ctx.fillStyle = 'rgba(0,0,0,0.9)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#00ff33'; + ctx.font = size + 'px JetBrains Mono, monospace'; + lines.forEach(l => { + ctx.fillText(l.text, 10, l.y); + l.y -= 2; + if (l.y < -size) { + l.y = canvas.height + size; + l.text = snippets[Math.floor(Math.random() * snippets.length)]; + } + }); + raf = requestAnimationFrame(draw); + } + draw(); + }, + disable() { + if (!this.active) return; this.active = false; + document.documentElement.classList.remove('fx-hacker'); + if (raf) cancelAnimationFrame(raf); raf = null; + destroyCanvas(canvas); canvas = null; lines = []; + } + }; + })(); + + // ================================================== + // Public API + // ================================================== + function toggle(name, arg) { + const fx = R[name]; + if (!fx) return false; + if (fx.active) { fx.disable(); return false; } + fx.enable(arg); + return true; + } + function enable(name, arg) { const fx = R[name]; if (fx && !fx.active) fx.enable(arg); } + function disable(name) { const fx = R[name]; if (fx && fx.active) fx.disable(); } + function active() { return Object.keys(R).filter(k => R[k].active); } + function disableAll() { Object.keys(R).forEach(k => { if (R[k].active) R[k].disable(); }); } + + window.__jaeEffects = { + toggle, enable, disable, active, disableAll, + registry: R, + list: () => Object.keys(R), + }; +})(); diff --git a/recon/index.html b/recon/index.html index f559c17..da0c523 100644 --- a/recon/index.html +++ b/recon/index.html @@ -4,8 +4,10 @@ Redirecting to RECON... +

Redirecting to RECON...

+ diff --git a/transmissions/radar.html b/transmissions/radar.html index 599703b..85573f7 100644 --- a/transmissions/radar.html +++ b/transmissions/radar.html @@ -10,6 +10,7 @@ +
@@ -139,5 +140,6 @@ +