70 lines
2.7 KiB
TypeScript
70 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useReducedMotion } from "framer-motion";
|
|
|
|
// A 2400-wide path holding six 400px periods. Sliding it exactly -1200
|
|
// (three periods) loops seamlessly, so the drift never visibly restarts.
|
|
const WAVE =
|
|
"M0 150 Q100 80 200 150 T400 150 T600 150 T800 150 T1000 150 T1200 150 T1400 150 T1600 150 T1800 150 T2000 150 T2200 150 T2400 150";
|
|
|
|
// Opacities are tuned for a pure white canvas. On the old lavender background
|
|
// these read as texture; on white anything stronger reads as clutter and pulls
|
|
// attention off the phone.
|
|
const LAYERS = [
|
|
{ y: 0, scaleY: 1, opacity: 0.1, duration: 12, width: 2 },
|
|
{ y: 26, scaleY: 0.62, opacity: 0.07, duration: 16, width: 1.5 },
|
|
{ y: -22, scaleY: 1.35, opacity: 0.055, duration: 21, width: 1.5 },
|
|
];
|
|
|
|
// Elegant voice waves behind the phone. Purely decorative, low opacity, and
|
|
// animated on transform only — it should reinforce "this is voice" without
|
|
// ever pulling attention off the screen.
|
|
export function VoiceWaves({ className = "" }: { className?: string }) {
|
|
const reduced = useReducedMotion();
|
|
|
|
return (
|
|
<div
|
|
aria-hidden="true"
|
|
className={`pointer-events-none absolute inset-0 overflow-hidden ${className}`}
|
|
style={{
|
|
maskImage: "radial-gradient(60% 55% at 50% 50%, #000 35%, transparent 100%)",
|
|
WebkitMaskImage: "radial-gradient(60% 55% at 50% 50%, #000 35%, transparent 100%)",
|
|
}}
|
|
>
|
|
{LAYERS.map((layer, i) => (
|
|
<motion.svg
|
|
key={i}
|
|
viewBox="0 0 2400 300"
|
|
preserveAspectRatio="none"
|
|
className="absolute left-1/2 top-1/2 h-[52%] w-[200%]"
|
|
style={{
|
|
x: "-50%",
|
|
y: `calc(-50% + ${layer.y}px)`,
|
|
opacity: layer.opacity,
|
|
}}
|
|
animate={reduced ? undefined : { translateX: ["0%", "-25%"] }}
|
|
transition={{ duration: layer.duration, repeat: Infinity, ease: "linear" }}
|
|
>
|
|
<defs>
|
|
<linearGradient id={`voice-wave-${i}`} x1="0" y1="0" x2="1" y2="0">
|
|
<stop offset="0%" stopColor="#7C3AED" stopOpacity="0" />
|
|
<stop offset="35%" stopColor="#A855F7" stopOpacity="1" />
|
|
<stop offset="65%" stopColor="#C084FC" stopOpacity="1" />
|
|
<stop offset="100%" stopColor="#7C3AED" stopOpacity="0" />
|
|
</linearGradient>
|
|
</defs>
|
|
<path
|
|
d={WAVE}
|
|
fill="none"
|
|
stroke={`url(#voice-wave-${i})`}
|
|
strokeWidth={layer.width}
|
|
strokeLinecap="round"
|
|
vectorEffect="non-scaling-stroke"
|
|
transform={`translate(0 ${150 - 150 * layer.scaleY}) scale(1 ${layer.scaleY})`}
|
|
/>
|
|
</motion.svg>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|