"use client"; /** * Magnetic — wraps a single interactive child and pulls it toward the cursor * within a radius, springing back on leave. Disabled for coarse pointers and * reduced-motion. Purely visual; does not alter semantics of the child. */ import { useEffect, useRef, type ReactNode } from "react"; import { gsap } from "gsap"; export default function Magnetic({ children, strength = 0.4, className = "", }: { children: ReactNode; strength?: number; className?: string; }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; if (window.matchMedia("(pointer: coarse)").matches) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const target = el.firstElementChild as HTMLElement | null; if (!target) return; const xTo = gsap.quickTo(target, "x", { duration: 0.5, ease: "elastic.out(1, 0.4)" }); const yTo = gsap.quickTo(target, "y", { duration: 0.5, ease: "elastic.out(1, 0.4)" }); const move = (e: MouseEvent) => { const r = el.getBoundingClientRect(); const mx = e.clientX - (r.left + r.width / 2); const my = e.clientY - (r.top + r.height / 2); xTo(mx * strength); yTo(my * strength); }; const leave = () => { xTo(0); yTo(0); }; el.addEventListener("mousemove", move); el.addEventListener("mouseleave", leave); return () => { el.removeEventListener("mousemove", move); el.removeEventListener("mouseleave", leave); }; }, [strength]); return ( {children} ); }