| import { useEffect, useRef } from "react"; |
|
|
| |
| export default function WaveformBackground() { |
| const ref = useRef<SVGSVGElement | null>(null); |
|
|
| useEffect(() => { |
| const svg = ref.current; |
| if (!svg) return; |
| let frame = 0; |
| let raf = 0; |
|
|
| const paths = svg.querySelectorAll<SVGPathElement>("path[data-wave]"); |
|
|
| const buildPath = ( |
| width: number, |
| height: number, |
| amp: number, |
| freq: number, |
| noise: number, |
| phase: number |
| ) => { |
| const points: string[] = []; |
| const step = 6; |
| for (let x = 0; x <= width; x += step) { |
| const base = Math.sin((x / width) * Math.PI * 2 * freq + phase) * amp; |
| const jitter = |
| noise * |
| (Math.sin((x / width) * Math.PI * 2 * (freq * 4.7) + phase * 1.3) * 0.5 + |
| Math.sin((x / width) * Math.PI * 2 * (freq * 11.3) + phase * 0.7) * 0.5); |
| const y = height / 2 + base + jitter; |
| points.push(`${x},${y.toFixed(2)}`); |
| } |
| return `M${points.join(" L")}`; |
| }; |
|
|
| const tick = () => { |
| frame += 1; |
| const w = svg.viewBox.baseVal.width || 1600; |
| const h = svg.viewBox.baseVal.height || 600; |
| const t = frame / 60; |
| paths.forEach((p, i) => { |
| const layer = i + 1; |
| const noiseRamp = 4 + 8 * Math.sin(t * 0.25 + layer * 0.7); |
| const d = buildPath( |
| w, |
| h, |
| 18 + layer * 6, |
| 1.3 + layer * 0.4, |
| Math.max(0, noiseRamp), |
| t * (0.4 + layer * 0.15) |
| ); |
| p.setAttribute("d", d); |
| }); |
| raf = requestAnimationFrame(tick); |
| }; |
| raf = requestAnimationFrame(tick); |
| return () => cancelAnimationFrame(raf); |
| }, []); |
|
|
| return ( |
| <div className="pointer-events-none fixed inset-0 z-0 overflow-hidden"> |
| {/* Faint grid */} |
| <div className="absolute inset-0 grid-bg opacity-[0.45]" /> |
| |
| {/* Animated waves */} |
| <svg |
| ref={ref} |
| className="absolute inset-0 h-full w-full" |
| viewBox="0 0 1600 600" |
| preserveAspectRatio="none" |
| aria-hidden |
| > |
| <defs> |
| <linearGradient id="waveGrad" x1="0" y1="0" x2="1" y2="0"> |
| <stop offset="0%" stopColor="rgb(var(--cyber))" stopOpacity="0.04" /> |
| <stop offset="50%" stopColor="rgb(var(--cyber))" stopOpacity="0.18" /> |
| <stop offset="100%" stopColor="rgb(var(--danger))" stopOpacity="0.08" /> |
| </linearGradient> |
| </defs> |
| <path data-wave stroke="url(#waveGrad)" strokeWidth="1.2" fill="none" /> |
| <path data-wave stroke="url(#waveGrad)" strokeWidth="1" fill="none" /> |
| <path data-wave stroke="url(#waveGrad)" strokeWidth="0.8" fill="none" /> |
| </svg> |
| |
| {/* Soft scan-line vignette */} |
| <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-bg/60" /> |
| </div> |
| ); |
| } |
|
|