"use client"; import { useEffect, useRef } from "react"; /** * A small "terminal crosshair" cursor that grows into a reading ring over * interactive targets. Pointer-device only; hidden for touch / reduced * motion. Purely decorative (aria-hidden) — never the only affordance. */ export default function Cursor() { const ring = useRef(null); const label = useRef(null); useEffect(() => { const fine = window.matchMedia("(pointer: fine)").matches; const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (!fine || reduce) return; const el = ring.current; const lab = label.current; if (!el || !lab) return; let x = window.innerWidth / 2; let y = window.innerHeight / 2; let cx = x; let cy = y; let raf = 0; const move = (e: PointerEvent) => { x = e.clientX; y = e.clientY; const t = e.target as HTMLElement; const tgt = t?.closest("a, button, [data-cursor]"); el.dataset.active = tgt ? "1" : "0"; const txt = (tgt as HTMLElement)?.dataset?.cursor; lab.textContent = txt || ""; el.dataset.hasLabel = txt ? "1" : "0"; }; const loop = () => { cx += (x - cx) * 0.2; cy += (y - cy) * 0.2; el.style.transform = `translate3d(${cx}px, ${cy}px, 0) translate(-50%, -50%)`; raf = requestAnimationFrame(loop); }; document.body.classList.add("has-cursor"); window.addEventListener("pointermove", move); raf = requestAnimationFrame(loop); return () => { window.removeEventListener("pointermove", move); cancelAnimationFrame(raf); document.body.classList.remove("has-cursor"); }; }, []); return ( ); }