"use client"; /** * Cursor — a character cursor with a difference-blend "lens". * * A small dot tracks tightly; a larger ring trails with easing and inverts the * content beneath it (mix-blend-mode: difference). Over interactive elements it * grows; over elements carrying data-cursor it swaps in a contextual label * (e.g. "ver"). Disabled on touch / coarse pointers. Native cursor remains as a * fallback so the page is always usable. */ import { useEffect, useRef } from "react"; import { gsap } from "gsap"; export default function Cursor() { const dot = useRef(null); const ring = useRef(null); const label = useRef(null); useEffect(() => { if (window.matchMedia("(pointer: coarse)").matches) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; if (!dot.current || !ring.current) return; document.body.classList.add("has-custom-cursor"); const xD = gsap.quickTo(dot.current, "x", { duration: 0.12, ease: "power3" }); const yD = gsap.quickTo(dot.current, "y", { duration: 0.12, ease: "power3" }); const xR = gsap.quickTo(ring.current, "x", { duration: 0.55, ease: "power3" }); const yR = gsap.quickTo(ring.current, "y", { duration: 0.55, ease: "power3" }); const move = (e: MouseEvent) => { xD(e.clientX); yD(e.clientY); xR(e.clientX); yR(e.clientY); }; const over = (e: Event) => { const t = (e.target as HTMLElement).closest( "a,button,.hoverable,[data-cursor]" ); if (!t) return; ring.current?.classList.add("cursor-ring--big"); const text = t.getAttribute("data-cursor"); if (text && label.current) { label.current.textContent = text; ring.current?.classList.add("cursor-ring--label"); } }; const out = (e: Event) => { const t = (e.target as HTMLElement).closest( "a,button,.hoverable,[data-cursor]" ); if (!t) return; ring.current?.classList.remove("cursor-ring--big", "cursor-ring--label"); }; window.addEventListener("mousemove", move, { passive: true }); document.addEventListener("mouseover", over); document.addEventListener("mouseout", out); return () => { document.body.classList.remove("has-custom-cursor"); window.removeEventListener("mousemove", move); document.removeEventListener("mouseover", over); document.removeEventListener("mouseout", out); }; }, []); return ( <>