/* Soundwave canvas — the recurring brand motif */ const SoundwaveCanvas = ({ barCount = 80, phaseShift = 0, intensity = 1, accentEvery = 14, className = "", style = {}, }) => { const canvasRef = React.useRef(null); const containerRef = React.useRef(null); const rafRef = React.useRef(null); const accentSetRef = React.useRef(null); // Build deterministic accent indices once if (!accentSetRef.current) { const set = new Set(); // Use phaseShift as a tiny seed so two instances differ const seed = Math.abs(Math.floor(phaseShift * 31 + 7)); // Hash-based picker so density stays ~1/accentEvery for any value const hash = (n) => { let x = (n + seed) | 0; x = (x ^ (x >>> 16)) * 0x7feb352d | 0; x = (x ^ (x >>> 15)) * 0x846ca68b | 0; x = x ^ (x >>> 16); return (x >>> 0); }; for (let i = 0; i < barCount; i++) { if ((hash(i) % accentEvery) === 0) set.add(i); } accentSetRef.current = set; } React.useEffect(() => { const canvas = canvasRef.current; const container = containerRef.current; if (!canvas || !container) return; const ctx = canvas.getContext('2d'); const dpr = Math.min(window.devicePixelRatio || 1, 2); let width = 0, height = 0; const resize = () => { const r = container.getBoundingClientRect(); width = Math.max(1, r.width); height = Math.max(1, r.height); canvas.width = Math.floor(width * dpr); canvas.height = Math.floor(height * dpr); canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); }; const ro = new ResizeObserver(resize); ro.observe(container); resize(); const start = performance.now(); const accentSet = accentSetRef.current; const draw = (now) => { const t = (now - start) / 1000; ctx.clearRect(0, 0, width, height); const barWidth = 2; const barGap = 4; const totalBarSpace = barWidth + barGap; // recompute count to fit container const count = Math.min(barCount, Math.floor(width / totalBarSpace)); const totalRowWidth = count * totalBarSpace - barGap; const startX = (width - totalRowWidth) / 2; const midY = height / 2; const halfH = height / 2; const maxAmp = 0.85 * halfH; const minAmp = 0.05 * halfH; for (let i = 0; i < count; i++) { const raw = 0.5 + 0.30 * Math.sin(i * 0.12 + t * 0.9 + phaseShift) + 0.18 * Math.sin(i * 0.05 + t * 1.6 + phaseShift * 0.7) + 0.12 * Math.sin(i * 0.21 + t * 0.4); let n = Math.max(0, Math.min(1, raw)) * intensity; const amp = minAmp + (maxAmp - minAmp) * n; const x = startX + i * totalBarSpace; const y = midY - amp; const barH = amp * 2; const isAccent = accentSet.has(i); if (isAccent) { // Bloom layer ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.shadowBlur = 12; ctx.shadowColor = 'rgba(124, 92, 255, 0.6)'; ctx.fillStyle = '#7C5CFF'; if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(x, y, barWidth, barH, 999); ctx.fill(); } else { ctx.fillRect(x, y, barWidth, barH); } ctx.restore(); ctx.fillStyle = '#7C5CFF'; } else { ctx.fillStyle = 'rgba(250, 250, 250, 0.55)'; } if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(x, y, barWidth, barH, 999); ctx.fill(); } else { ctx.fillRect(x, y, barWidth, barH); } } rafRef.current = requestAnimationFrame(draw); }; rafRef.current = requestAnimationFrame(draw); return () => { cancelAnimationFrame(rafRef.current); ro.disconnect(); }; }, [barCount, phaseShift, intensity, accentEvery]); return (