Custom Cursor with Text Ring

Rounded Figma-style arrow plus a lagging ring of rotating text, blended with difference mode.

GSAPCursormix-blend-mode

Prompt

Build a custom cursor: a rounded Figma-style arrow plus a ring of rotating text that follows with a lag.

Stack: Next.js (App Router), React, TypeScript and Tailwind CSS v4. Use gsap.

SETUP
- A client component mounted once in the root layout. Only run when matchMedia("(pointer: fine)") matches.
- Global CSS:
  - Inside @media (pointer: fine), set cursor: none !important on *, *::before and *::after.
  - Inside @media (pointer: coarse), hide the component.
- Wrapper: aria-hidden, pointer-events none, position fixed, inset 0, z-index 9999, mix-blend-mode: difference. Everything inside is pure white (#fff), so it inverts whatever is underneath.
- IMPORTANT: give <html> its own background color (the same as body). Otherwise the body background moves to the canvas, the difference blend has nothing to blend with, and the cursor shows plain white.

ARROW
- A 28×28 SVG with path "M6 5v17.5l5.2-5.2h7.8Z", drawn twice for rounded corners:
  - First with a 4px stroke, round line joins and paint-order stroke.
  - Then with a 1.2px stroke and round joins.
- Translate it by (-6px, -5px) so the tip sits exactly on the pointer.
- Follows the pointer with gsap.quickTo on x/y, duration 0.05.

RING
- A 124×124 SVG with a thin circle (r 38, 0.75px stroke).
- Text on a circular path of r 48: "WELCOME • SCROLL DOWN •".
  - 8.5px, uppercase, letter-spacing 0.5.
  - Use textLength 301 (≈ 2π×48) with lengthAdjust="spacing" so the text fills the circle evenly.
- Spins with a CSS keyframe rotate(360deg), 10s linear infinite.
- Centered with xPercent/yPercent -50. Follows with gsap.quickTo, duration 0.45, ease "power3.out", offset (+4px, +8px) so it centers on the arrow's body.

BEHAVIOR
- On the first pointermove, fade both to opacity 1 (0.2s). On pointerleave of the document, fade both out.
- When the pointer is over any a or button (check with closest), shrink the ring's inner wrapper to scale 0.5 and opacity 0 (0.25s, power2.out).
- When it leaves, bring the ring back after a 0.5s delay. Use overwrite: true so hovering another link cancels a pending reveal.

More in Animations