1143 lines
51 KiB
JavaScript
1143 lines
51 KiB
JavaScript
/* ===================================================
|
||
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 🚧 VISITORS: <b>000' + (1000 + Math.floor(Math.random() * 9000)) + '</b> <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),
|
||
};
|
||
})();
|