donau2space-dev/js/effects.js

200 lines
6.1 KiB
JavaScript

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DONAU2SPACE // DEV ENTITY — Visual Effects
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
import { $, sleep, random, bus } from './utils.js';
// ── CSS class-based effects ────────────────────
export function glitch(ms = 850) {
document.documentElement.classList.add('fx-glitch');
setTimeout(() => document.documentElement.classList.remove('fx-glitch'), ms);
}
export function shake(ms = 520) {
const card = $('.terminal-card');
if (!card) return;
card.classList.add('fx-shake');
setTimeout(() => card.classList.remove('fx-shake'), ms);
}
export function blinkRed(ms = 2800) {
document.body.classList.add('fx-blink-red');
setTimeout(() => document.body.classList.remove('fx-blink-red'), ms);
}
export function scanPulse() {
const sl = $('#scanlines');
if (!sl) return;
sl.classList.add('fx-scan-pulse');
setTimeout(() => sl.classList.remove('fx-scan-pulse'), 1200);
}
// ── Screen tear (built with DOM, no innerHTML) ─
export async function screenTear(duration = 1500) {
const tearEl = $('#screen-tear');
if (!tearEl) return;
tearEl.style.display = 'block';
tearEl.replaceChildren();
const strips = 12;
for (let i = 0; i < strips; i++) {
const offset = (Math.random() * 40 - 20) | 0;
const h = (100 / strips).toFixed(2);
const top = (i * 100 / strips).toFixed(2);
const strip = document.createElement('div');
strip.style.cssText = `position:absolute;top:${top}%;left:${offset}px;right:${-offset}px;height:${h}%;`
+ `background:rgba(${Math.random() > 0.5 ? '255,0,0' : '0,255,128'},0.04);`
+ `border-top:1px solid rgba(255,255,255,0.06);`;
tearEl.appendChild(strip);
}
await sleep(duration);
tearEl.replaceChildren();
tearEl.style.display = 'none';
}
// ── Color shift ────────────────────────────────
const originalColors = {
'--green': '#7ee787',
'--blue': '#6cb6ff',
'--pink': '#ff7ad9',
'--yellow': '#ffcc66',
'--red': '#ff4d4d',
'--text': '#cfe3ff',
'--bg': '#05070b',
'--muted': '#7ea3c7',
};
export function shiftColors(intensity = 0.3) {
const hueShift = random(-30, 30) * intensity;
document.documentElement.style.filter = `hue-rotate(${hueShift}deg) saturate(${1 + intensity * 0.5})`;
}
export function resetColors() {
document.documentElement.style.filter = '';
const root = document.documentElement;
for (const [k, v] of Object.entries(originalColors)) {
root.style.setProperty(k, v);
}
}
export function setTheme(name) {
const root = document.documentElement;
const themes = {
neon: { '--green': '#7ee787', '--blue': '#6cb6ff', '--pink': '#ff7ad9' },
calm: { '--green': '#9ad7ff', '--blue': '#b4f0c2', '--pink': '#ffd1a6' },
doom: { '--green': '#ffcc66', '--blue': '#ff4d4d', '--pink': '#ff4d4d' },
void: { '--green': '#4a6670', '--blue': '#3d5a6e', '--pink': '#6e3d5a', '--text': '#8ea8b8' },
matrix: { '--green': '#00ff41', '--blue': '#00ff41', '--pink': '#00ff41', '--text': '#00ff41', '--muted': '#008f11' },
};
const t = themes[name];
if (!t) return false;
for (const [k, v] of Object.entries(t)) root.style.setProperty(k, v);
return true;
}
// ── Reality Shift sequence ─────────────────────
export async function realityShift() {
bus.emit('reality-shift-start');
// Phase 1: Color distortion
shiftColors(0.8);
await sleep(800);
// Phase 2: Typography chaos
document.body.classList.add('fx-typo-shift');
await sleep(600);
// Phase 3: Screen tear
screenTear(1200);
glitch(1200);
shake(400);
await sleep(1400);
// Phase 4: Fake error flash
const overlay = $('#reality-overlay');
if (overlay) {
overlay.style.display = 'flex';
overlay.replaceChildren();
const errors = [
'SEGFAULT at 0x00000DEV',
'kernel panic - not syncing: VFS unable to mount root fs',
'ERROR: reality.sys corrupted',
'WARNING: timeline integrity at 12%',
'FATAL: narrative overflow in dev_entity.consciousness',
];
for (const err of errors) {
const div = document.createElement('div');
div.textContent = err;
overlay.appendChild(div);
await sleep(200);
}
await sleep(1000);
overlay.style.display = 'none';
}
// Phase 5: Brief blackout
document.body.classList.add('fx-blackout');
await sleep(400);
document.body.classList.remove('fx-blackout');
// Phase 6: Rebuild
document.body.classList.remove('fx-typo-shift');
resetColors();
await sleep(300);
bus.emit('reality-shift-end');
}
// ── Overlay management ─────────────────────────
export function showOverlay(text) {
const overlay = $('#overlay');
const overlayText = $('#overlay-text');
if (!overlay || !overlayText) return;
overlayText.textContent = text;
overlay.style.display = 'flex';
}
export function hideOverlay() {
const overlay = $('#overlay');
if (overlay) overlay.style.display = 'none';
}
export function isOverlayVisible() {
const overlay = $('#overlay');
return overlay && overlay.style.display === 'flex';
}
// ── Matrix rain (CSS-based, lightweight) ───────
let matrixInterval = null;
export function startMatrixRain() {
const el = $('#matrix-rain');
if (!el) return;
const charset = '01$#@*+^%&()[]{}<>/\\|;:,.=ABCDEFGHIJKLMNOPQRSTUVWXYZ';
matrixInterval = setInterval(() => {
let out = '';
for (let i = 0; i < 300; i++) {
out += charset[(Math.random() * charset.length) | 0];
if (i % 55 === 0) out += '\n';
}
el.textContent = out;
}, 180);
}
export function stopMatrixRain() {
if (matrixInterval) clearInterval(matrixInterval);
const el = $('#matrix-rain');
if (el) el.textContent = '';
}
export function setMatrixOpacity(val) {
const el = $('#matrix-rain');
if (el) el.style.opacity = val;
}