import React, { useState, useEffect, useRef, useCallback } from 'react'; export default function App() { const canvasRef = useRef(null); const [started, setStarted] = useState(false); const [volume, setVolume] = useState(0); const [memory, setMemory] = useState([]); const particles = useRef([]); const animationFrame = useRef(null); const audioContext = useRef(null); const analyser = useRef(null); const dataArray = useRef(null); const initParticles = useCallback(() => { particles.current = Array.from({ length: 300 }, () => ({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, vx: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2, size: Math.random() * 2 + 1, color: `hsl(${200 + Math.random() * 80}, 80%, 60%)` })); }, []); const draw = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); // Fading effect for "living memory" ctx.fillStyle = 'rgba(0, 0, 0, 0.15)'; ctx.fillRect(0, 0, canvas.width, canvas.height); particles.current.forEach((p, i) => { // Audio-reactive physics const speedMod = 1 + volume * 5; p.x += p.vx * speedMod; p.y += p.vy * speedMod; // Bounce if (p.x < 0 || p.x > canvas.width) p.vx *= -1; if (p.y < 0 || p.y > canvas.height) p.vy *= -1; // Draw particle ctx.beginPath(); ctx.arc(p.x, p.y, p.size + volume * 2, 0, Math.PI * 2); ctx.fillStyle = p.color; ctx.fill(); }); animationFrame.current = requestAnimationFrame(draw); }, [volume]); const startAudio = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); audioContext.current = new (window.AudioContext || window.webkitAudioContext)(); const source = audioContext.current.createMediaStreamSource(stream); analyser.current = audioContext.current.createAnalyser(); analyser.current.fftSize = 256; source.connect(analyser.current); dataArray.current = new Uint8Array(analyser.current.frequencyBinCount); initParticles(); setStarted(true); const updateAudio = () => { analyser.current.getByteFrequencyData(dataArray.current); const avg = dataArray.current.reduce((a, b) => a + b) / dataArray.current.length; setVolume(avg / 128); requestAnimationFrame(updateAudio); }; updateAudio(); draw(); } catch (e) { console.error("Audio Access Denied", e); } }; useEffect(() => { const handleResize = () => { if (canvasRef.current) { canvasRef.current.width = window.innerWidth; canvasRef.current.height = window.innerHeight; } }; window.addEventListener('resize', handleResize); handleResize(); return () => { window.removeEventListener('resize', handleResize); cancelAnimationFrame(animationFrame.current); }; }, []); return (
{!started ? (

ECHOES BETWEEN SIGNALS

) : ( )} {/* Hidden Control Panel */}
); }