// Animated seismic waveform — irregular amplitude, like real seismograph output. // Uses a few stacked sine + decay components with seeded noise to look natural, // scrolls horizontally on a slow loop, and has a brighter trace overlay on top. function SeismicWaveform({ height = 460, mode = 'panel' }) { // mode 'panel' = sized to fit hero right column; 'bg' = full-bleed background const W = 1200; const H = height; const midY = H / 2; const [t, setT] = React.useState(0); React.useEffect(() => { let raf; let last = performance.now(); const tick = (now) => { const dt = (now - last) / 1000; last = now; setT((p) => p + dt); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, []); // Build a long irregular trace by composing several sines with noise. // Sampling 280 points keeps the path smooth without overwhelming the GPU. const path = React.useMemo(() => { const N = 280; const points = []; // Seeded pseudo-random for stable irregularity let seed = 7; const rand = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }; // Pre-compute amplitude envelope — a few bursts along the wave const bursts = []; for (let i = 0; i < 8; i++) { bursts.push({ x: rand() * W, w: 80 + rand() * 160, amp: 30 + rand() * 90 }); } for (let i = 0; i <= N; i++) { const x = (i / N) * W; let env = 12; for (const b of bursts) { const d = Math.abs(x - b.x); if (d < b.w) env += b.amp * Math.pow(1 - d / b.w, 2); } // Multiple frequencies + tiny noise const yOff = env * (Math.sin(x * 0.05) * 0.6 + Math.sin(x * 0.13 + 1.7) * 0.3 + Math.sin(x * 0.31 + 3.1) * 0.18) + (rand() - 0.5) * 4; points.push([x, midY + yOff * 0.55]); } return points.map((p, i) => (i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`)).join(' '); }, [H]); // Slow horizontal scroll — translate the entire group const scroll = (-t * 22) % W; return ( ); } window.SeismicWaveform = SeismicWaveform;