/* global React, ReactDOM, TweaksPanel, useTweaks, TweakSection, TweakSlider, TweakRadio, TweakSelect, TweakToggle, TweakColor, TweakButton */ const { useState, useEffect, useRef, useMemo, useCallback } = React; const LOGO = "logo.png"; // ============================================================ // SCENES // ============================================================ function ScenePulse({ glitch }) { return (
// PULSE — 124 BPM
); } const GLITCH_WORDS = ["DECAY", "VOID", "MACHINE", "FLESH", "STATIC", "NEON", "BLOOD", "CRTRL", "DRONE", "PULSE", "FAITH", "CIRCUIT", "CORPSE", "BINARY", "EUTHANOIZE"]; function SceneGlitchText({ glitch }) { const [w, setW] = useState(GLITCH_WORDS[0]); useEffect(() => { let i = 0; const id = setInterval(() => { i = (i + 1) % GLITCH_WORDS.length; setW(GLITCH_WORDS[i]); }, 1100); return () => clearInterval(id); }, []); return (

{w}

SIG CORRUPT
BUF 0xFA9E
RX {Math.floor(Math.random()*9999)}
); } function SceneTunnel({ tunnelOpts }) { const { rings = 18, cycle = 5, rotSpeed = 4, // seconds per full 360° rotation shape = "square", color = "white", yaw = true, } = tunnelOpts || {}; const items = []; for (let i = 0; i < rings; i++) { const flyDelay = -(cycle / rings) * i; const colorClass = color === "accent" ? "tr-accent" : color === "mix" ? (i % 2 === 0 ? "tr-accent" : "") : ""; items.push(
); } return (
{items}
); } function SceneStrobe({ glitch }) { return (
▰▰▰ 124 BPM ▰▰▰ KICK ▰▰▰ KICK ▰▰▰
); } function SceneOscilloscope() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); let raf; const resize = () => { canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; }; resize(); window.addEventListener("resize", resize); let t = 0; const draw = () => { const w = canvas.width, h = canvas.height; ctx.clearRect(0, 0, w, h); // Glow trails ctx.lineCap = "round"; ctx.shadowBlur = 18; ctx.shadowColor = "oklch(0.78 0.13 220)"; ctx.strokeStyle = "rgba(190,230,255,0.95)"; ctx.lineWidth = 2.5; ctx.beginPath(); const cy = h / 2; for (let x = 0; x < w; x += 2) { const px = x / w; const y = Math.sin(px * 18 + t * 1.3) * 60 + Math.sin(px * 5 + t * 0.6) * 100 * Math.sin(t * 0.4) + Math.sin(px * 60 + t * 4) * 12 + Math.cos(px * 3 + t * 0.8) * 80; if (x === 0) ctx.moveTo(x, cy + y); else ctx.lineTo(x, cy + y); } ctx.stroke(); // Second wave (inverse, red) ctx.shadowColor = "oklch(0.55 0.22 25)"; ctx.strokeStyle = "rgba(220,90,90,0.6)"; ctx.lineWidth = 1.5; ctx.beginPath(); for (let x = 0; x < w; x += 2) { const px = x / w; const y = Math.sin(px * 9 - t * 0.9) * 80 + Math.sin(px * 25 + t * 2) * 40 + Math.cos(px * 4 - t * 0.5) * 60; if (x === 0) ctx.moveTo(x, cy + y); else ctx.lineTo(x, cy + y); } ctx.stroke(); t += 0.04; raf = requestAnimationFrame(draw); }; draw(); return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); }; }, []); return (
OSC▸▸ INPUT 01 124.0 HZ
); } function SceneOccult() { // distribute tick marks around an outer ring const ticks = 24; const tickEls = []; for (let i = 0; i < ticks; i++) { const ang = (i / ticks) * 360; const label = ["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"][i % 12]; tickEls.push(
{label}
); } return (
{tickEls}
— I N — N O M I N E — M A C H I N A —
); } function SceneRorschach() { const [phrase, setPhrase] = useState("INDUSTRIAL"); const phrases = ["INDUSTRIAL", "ELECTRO·GOTH", "EBM", "CLUB·HELL", "DEUS·EX", "NOIR"]; useEffect(() => { let i = 0; const id = setInterval(() => { i = (i + 1) % phrases.length; setPhrase(phrases[i]); }, 2400); return () => clearInterval(id); }, []); return (
{phrase} // RORSCHACH ▸ LIVE
); } function SceneData() { // Random bar widths memoized so the stripe is stable const barcode = useMemo(() => { const arr = []; for (let i = 0; i < 80; i++) { arr.push(2 + Math.floor(Math.random() * 9)); } return arr; }, []); return (
CLUBHELL
▶ INDUSTRIAL
EBM
CLUBHELL
▶ ELECTRO
GOTH
CLUBHELL
▶ INDUSTRIAL
EBM
CLUBHELL
▶ ELECTRO
GOTH
FREQ·124
{barcode.map((w, i) => ( ))}
SIG·OK
EUTHANOIZE
▮ STATIC
FLESH
VOID
▮ NEON
DRONE
EUTHANOIZE
▮ STATIC
FLESH
VOID
▮ NEON
DRONE
); } // ============================================================ // SCENE LIST + PLAYER // ============================================================ const SCENES = [ { id: "pulse", name: "PULSE", Comp: ScenePulse, dur: 16 }, { id: "glitch", name: "GLITCH TEXT", Comp: SceneGlitchText, dur: 18 }, { id: "tunnel", name: "TUNNEL", Comp: SceneTunnel, dur: 20 }, { id: "strobe", name: "STROBE", Comp: SceneStrobe, dur: 14 }, { id: "osc", name: "OSCILLOSCOPE", Comp: SceneOscilloscope, dur: 18 }, { id: "occult", name: "OCCULT GRID", Comp: SceneOccult, dur: 22 }, { id: "ror", name: "RORSCHACH", Comp: SceneRorschach, dur: 16 }, { id: "data", name: "DATA STREAM", Comp: SceneData, dur: 16 }, ]; function App() { const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS); const [idx, setIdx] = useState(0); const wipeRef = useRef(null); const wsRef = useRef(null); const pausedRef = useRef(t.paused); // updated every render, guards the timer callback // Refs so WS closure always has latest setters without reconnecting const setTweakRef = useRef(setTweak); const setIdxRef = useRef(setIdx); setTweakRef.current = setTweak; setIdxRef.current = setIdx; pausedRef.current = t.paused; // Filter scenes based on which are enabled const activeScenes = useMemo( () => SCENES.filter(s => t.enabled[s.id] !== false), [t.enabled] ); // safety const safeIdx = activeScenes.length === 0 ? 0 : idx % activeScenes.length; const current = activeScenes[safeIdx] || SCENES[0]; // auto-advance useEffect(() => { if (activeScenes.length === 0) return; if (t.paused) return; const dur = (current.dur * 1000) / t.speed; const id = setTimeout(() => { if (pausedRef.current) return; // guard: pause pressed while timer was running if (wipeRef.current) { wipeRef.current.classList.remove("flash"); void wipeRef.current.offsetWidth; wipeRef.current.classList.add("flash"); } setIdx(i => (i + 1) % activeScenes.length); }, dur); return () => clearTimeout(id); }, [safeIdx, activeScenes.length, t.speed, t.paused, current.dur]); // HUD update useEffect(() => { const el = document.getElementById("hud-scn"); if (el) el.textContent = String(safeIdx + 1).padStart(2, "0"); }, [safeIdx]); // Apply palette to CSS variables useEffect(() => { const r = document.documentElement; const palettes = { blood: { accent: "oklch(0.55 0.22 25)", cool: "oklch(0.78 0.13 220)" }, cyan: { accent: "oklch(0.78 0.13 220)", cool: "oklch(0.55 0.22 25)" }, mono: { accent: "#f4ebe4", cool: "#a8a39e" }, acid: { accent: "oklch(0.78 0.20 130)", cool: "oklch(0.65 0.20 320)" }, }; const p = palettes[t.palette] || palettes.blood; r.style.setProperty("--accent", p.accent); r.style.setProperty("--cool", p.cool); }, [t.palette]); // Random vignette flicker useEffect(() => { const fl = document.getElementById("flicker"); if (!fl) return; let raf, last = 0; const tick = (now) => { if (now - last > 500 + Math.random() * 4000) { last = now; if (Math.random() < 0.5 * t.flicker) { fl.style.opacity = String(0.7 + Math.random() * 0.3); fl.style.background = Math.random() < 0.7 ? "#000" : "#fff"; setTimeout(() => { fl.style.opacity = "0"; }, 60 + Math.random() * 80); } } raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [t.flicker]); // Attach wipeRef to the DOM element (it lives outside the React root) useEffect(() => { wipeRef.current = document.getElementById('wipe'); }, []); // WebSocket — connect once, reconnect on close useEffect(() => { function triggerFlash() { const wipe = document.getElementById('wipe'); if (wipe) { wipe.classList.remove('flash'); void wipe.offsetWidth; wipe.classList.add('flash'); } const overlay = document.getElementById('euthanoize-overlay'); if (overlay) { overlay.classList.remove('show'); void overlay.offsetWidth; overlay.classList.add('show'); } } function connect() { let ws; const url = window.WS_URL || `ws://${location.host}`; try { ws = new WebSocket(url); } catch(e) { setTimeout(connect, 3000); return; } wsRef.current = ws; ws.onmessage = (evt) => { try { const msg = JSON.parse(evt.data); if (msg.type === 'SET_TWEAK') setTweakRef.current(msg.key, msg.value); if (msg.type === 'JUMP_SCENE') setIdxRef.current(msg.index); if (msg.type === 'FLASH') triggerFlash(); } catch(e) {} }; ws.onclose = () => setTimeout(connect, 2000); } connect(); return () => { if (wsRef.current) wsRef.current.close(); }; }, []); // Broadcast state whenever scene or tweaks change useEffect(() => { const ws = wsRef.current; if (!ws || ws.readyState !== WebSocket.OPEN) return; ws.send(JSON.stringify({ type: 'STATE', state: { currentScene: safeIdx, sceneId: current.id, sceneName: current.name, totalScenes: activeScenes.length, tweaks: t, scenes: activeScenes.map(s => ({ id: s.id, name: s.name })), } })); }, [safeIdx, t, activeScenes.length]); // Manual scene jump useEffect(() => { const handler = (e) => { if (e.key === "ArrowRight" || e.key === " ") setIdx(i => (i + 1) % Math.max(activeScenes.length, 1)); if (e.key === "ArrowLeft") setIdx(i => (i - 1 + Math.max(activeScenes.length,1)) % Math.max(activeScenes.length, 1)); if (e.key === "f" || e.key === "F") { if (!document.fullscreenElement) document.documentElement.requestFullscreen?.(); else document.exitFullscreen?.(); } if (e.key === "h" || e.key === "H") { document.body.classList.toggle("show-cursor"); const hud = document.getElementById("hud"); if (hud) hud.style.display = hud.style.display === "none" ? "" : "none"; } if (e.key === "t" || e.key === "T") { // Toggle Tweaks panel via host protocol const open = document.querySelector(".twk-panel"); if (open) { window.postMessage({ type: "__deactivate_edit_mode" }, "*"); window.parent.postMessage({ type: "__edit_mode_dismissed" }, "*"); } else { window.postMessage({ type: "__activate_edit_mode" }, "*"); } } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [activeScenes.length]); const Comp = current.Comp; return ( <> setTweak("speed", v)} /> setTweak("paused", v)} /> setTweak("glitch", v)} /> setTweak("flicker", v)} /> setTweak("palette", v)} /> setTweak("tunnel", { ...t.tunnel, rings: v })} /> setTweak("tunnel", { ...t.tunnel, cycle: v })} /> setTweak("tunnel", { ...t.tunnel, rotSpeed: v })} /> setTweak("tunnel", { ...t.tunnel, shape: v })} /> setTweak("tunnel", { ...t.tunnel, color: v })} /> setTweak("tunnel", { ...t.tunnel, yaw: v })} /> {SCENES.map((s, i) => ( setTweak("enabled", { ...t.enabled, [s.id]: v })} /> ))} {activeScenes.map((s, i) => ( setIdx(i)} /> ))}
T · toggle this panel
← / → · jump scene
SPACE · next
F · fullscreen
H · hide HUD
); } // Tweak defaults — written to disk via __edit_mode_set_keys window.TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "speed": 1, "glitch": 1, "flicker": 1, "paused": false, "palette": "blood", "enabled": { "pulse": true, "glitch": true, "tunnel": true, "strobe": true, "osc": true, "occult": true, "ror": true, "data": true }, "tunnel": { "rings": 18, "cycle": 5, "rotSpeed": 4, "shape": "square", "color": "white", "yaw": true } }/*EDITMODE-END*/; ReactDOM.createRoot(document.getElementById("root")).render();