/* global React */ /* Interactive "bending line" — drag the cursor through it and the hairline flexes, then springs back. Sits in the existing gap between sections with ~0 layout height so it never changes the spacing around it. */ const { useRef: useWaveRef, useEffect: useWaveEffect } = React; function WavePath({ className }) { const wrap = useWaveRef(null); const path = useWaveRef(null); const s = useWaveRef({ progress: 0, x: 0.5, time: Math.PI / 2, reqId: null }); const widthOf = () => (wrap.current ? wrap.current.getBoundingClientRect().width : window.innerWidth * 0.7); const setPath = (progress) => { const p = path.current; if (!p) return; const w = widthOf(); p.setAttributeNS(null, "d", `M0 100 Q${w * s.current.x} ${100 + progress * 0.6}, ${w} 100`); }; useWaveEffect(() => { setPath(0); const onResize = () => setPath(s.current.progress); window.addEventListener("resize", onResize); return () => { window.removeEventListener("resize", onResize); if (s.current.reqId) cancelAnimationFrame(s.current.reqId); }; }, []); const lerp = (a, b, t) => a * (1 - t) + b * t; const resetAnim = () => { s.current.time = Math.PI / 2; s.current.progress = 0; }; const animateOut = () => { const st = s.current; const newProgress = st.progress * Math.sin(st.time); st.progress = lerp(st.progress, 0, 0.025); st.time += 0.2; setPath(newProgress); if (Math.abs(st.progress) > 0.75) { st.reqId = requestAnimationFrame(animateOut); } else { resetAnim(); } }; const onEnter = () => { const st = s.current; if (st.reqId) { cancelAnimationFrame(st.reqId); st.reqId = null; resetAnim(); } }; const onMove = (e) => { const st = s.current; const b = e.currentTarget.getBoundingClientRect(); st.x = Math.max(0, Math.min(1, (e.clientX - b.left) / b.width)); st.progress += e.movementY; setPath(st.progress); }; const onLeave = () => animateOut(); return (
); } Object.assign(window, { WavePath });