jaeswift-website/js/sitewide-effects.js

1143 lines
51 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ===================================================
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 = '<iframe src="' + pick + '" loading="lazy"></iframe>';
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 🚧 &nbsp; VISITORS: <b>000' + (1000 + Math.floor(Math.random() * 9000)) + '</b> &nbsp; <blink>BEST VIEWED IN NETSCAPE</blink>';
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 <stdio.h>', '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),
};
})();