"use client"; /** * Magnetic wrapper — the child is pulled toward the cursor while hovered, then * springs back with momentum on leave (loose spring => visible overshoot, the * "alive" feel). The label inside also drifts at a deeper strength for a subtle * parallax between the button and its text. * Transform-only (GPU). No-op on coarse pointers and with prefers-reduced-motion * so keyboard/touch users get a static, fully-clickable element. */ import { useRef } from "react"; import { motion, useMotionValue, useSpring, useTransform } from "motion/react"; import { SPRING } from "./motion"; export default function Magnetic({ children, strength = 0.35, className, }: { children: React.ReactNode; strength?: number; className?: string; }) { const ref = useRef(null); const x = useMotionValue(0); const y = useMotionValue(0); const sx = useSpring(x, SPRING.magnetic); const sy = useSpring(y, SPRING.magnetic); // Inner content drifts a touch further => parallax depth between shell + label. const innerX = useTransform(sx, (v) => v * 0.35); const innerY = useTransform(sy, (v) => v * 0.35); const onMove = (e: React.PointerEvent) => { if (e.pointerType !== "mouse") return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); x.set((e.clientX - (r.left + r.width / 2)) * strength); y.set((e.clientY - (r.top + r.height / 2)) * strength); }; const reset = () => { x.set(0); y.set(0); }; return ( {children} ); }