{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"custom-cursor","type":"registry:block","title":"custom cursor","description":"\"add 02\" — dot tracks tight, ring lags behind. Hides itself on touch devices.","author":"Motion Menu","categories":["role-chrome","global","feel","js"],"dependencies":["gsap"],"registryDependencies":[],"meta":{"number":"02","slug":"custom-cursor","framework":"react","docs":"https://motion-menu-two.vercel.app/p/custom-cursor.md"},"files":[{"path":"motion-menu/custom-cursor.html","type":"registry:file","target":"motion-menu/custom-cursor.html","content":"<article class=\"card\">\n        <header><span class=\"num\">02</span><h3>Custom cursor</h3><div class=\"tags\"><i>JS lerp</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"custom-cursor\" href=\"/r/custom-cursor.json?framework=react\" title=\"Open the React registry output\">React ↗</a></header>\n        <div class=\"demo\" style=\"flex-direction:column; gap:1rem;\">\n          <p style=\"font-family:var(--mono); font-size:.75rem; color:var(--dim);\">it's already following you (desktop)</p>\n          <button class=\"btn\" data-cursor-hover>hover me — ring grows</button>\n        </div>\n        <p class=\"order\">Say: <b>\"add 02\"</b> — dot tracks tight, ring lags behind. Hides itself on touch devices.</p>\n      </article>"},{"path":"motion-menu/custom-cursor.css","type":"registry:file","target":"motion-menu/custom-cursor.css","content":"/* ---------- 02 cursor ---------- */\n  .cursor-dot, .cursor-ring {\n    position: fixed; top: 0; left: 0; pointer-events: none;\n    border-radius: 50%; z-index: 9999; mix-blend-mode: difference;\n  }\n\n.cursor-dot  { width: 6px;  height: 6px;  background: #fff; }\n\n.cursor-ring { width: 36px; height: 36px; border: 1px solid #fff; transition: width .25s, height .25s; }\n\n.cursor-ring.hover { width: 60px; height: 60px; }\n\n.cursor-dot, .cursor-ring { display: none; }\n\n.card {\n    border: 1px solid var(--line); border-radius: 14px; background: var(--card);\n    overflow: hidden; display: flex; flex-direction: column;\n  }\n\n.card > header { display: flex; align-items: baseline; gap: .8rem; padding: 1.1rem 1.2rem .9rem; border-bottom: 1px solid var(--line); }\n\n.card .num { font-family: var(--mono); color: var(--acc); font-size: 1.05rem; }\n\n.card h3 { font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; flex: 1; }\n\n.tags { display: flex; gap: .35rem; }\n\n.tags i {\n    font: 400 .62rem/1 var(--mono); font-style: normal; color: var(--dim);\n    border: 1px solid var(--line); border-radius: 99px; padding: .28em .6em; white-space: nowrap;\n  }\n\n.demo {\n    min-height: 250px; position: relative; display: flex; align-items: center; justify-content: center;\n    background: var(--bg2); overflow: hidden; flex: 1;\n  }\n\n.order { padding: .85rem 1.2rem; font-family: var(--mono); font-size: .72rem; color: var(--dim); border-top: 1px solid var(--line); }\n\n.order b { color: var(--txt); font-weight: 500; }\n\n.btn {\n    font: 500 .8rem/1 var(--mono); color: var(--txt); background: transparent;\n    border: 1px solid rgba(255,255,255,.25); border-radius: 99px; padding: .8em 1.6em;\n    transition: border-color .3s;\n  }\n\n.btn:hover { border-color: var(--acc); color: var(--acc); }\n\n.demo .btn.corner { position: absolute; top: 12px; right: 12px; padding: .55em 1em; font-size: .68rem; z-index: 5; }\n\n.ai-card .num { color: var(--acc2) !important; }\n\n.brief a.btn { text-decoration: none; text-align: center; }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }"},{"path":"components/motion-menu/custom-cursor.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// custom cursor — Motion Menu\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card\\\">\\n        <header><span class=\\\"num\\\">02</span><h3>Custom cursor</h3><div class=\\\"tags\\\"><i>JS lerp</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"custom-cursor\\\" href=\\\"/r/custom-cursor.json?framework=react\\\" title=\\\"Open the React registry output\\\">React \\u2197</a></header>\\n        <div class=\\\"demo\\\" style=\\\"flex-direction:column; gap:1rem;\\\">\\n          <p style=\\\"font-family:var(--mono); font-size:.75rem; color:var(--dim);\\\">it's already following you (desktop)</p>\\n          <button class=\\\"btn\\\" data-cursor-hover>hover me \\u2014 ring grows</button>\\n        </div>\\n        <p class=\\\"order\\\">Say: <b>\\\"add 02\\\"</b> \\u2014 dot tracks tight, ring lags behind. Hides itself on touch devices.</p>\\n      </article>\";\n\nexport function CustomCursor() {\n  const hostRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const host = hostRef.current;\n    if (!host) return;\n    const $ = (s: string) => host.querySelector(s) as HTMLElement | null;\n    let disposed = false;\n    const cleanup: Array<() => void> = [];\n\n    (async () => {\n      if (disposed) return;\n      try {\n        /* ============ 02 · custom cursor ============ */\n        const dot = document.querySelector('.cursor-dot');\n        const ring = document.querySelector('.cursor-ring');\n        const cur = { x: innerWidth / 2, y: innerHeight / 2, dx: innerWidth / 2, dy: innerHeight / 2, rx: innerWidth / 2, ry: innerHeight / 2 };\n        window.addEventListener('mousemove', (e) => { cur.x = e.clientX; cur.y = e.clientY; });\n        gsap.ticker.add(() => {\n          cur.dx += (cur.x - cur.dx) * 0.4;  cur.dy += (cur.y - cur.dy) * 0.4;\n          cur.rx += (cur.x - cur.rx) * 0.12; cur.ry += (cur.y - cur.ry) * 0.12;\n          dot.style.transform  = `translate(${cur.dx}px, ${cur.dy}px) translate(-50%,-50%)`;\n          ring.style.transform = `translate(${cur.rx}px, ${cur.ry}px) translate(-50%,-50%)`;\n        });\n        document.querySelectorAll('a, button, [data-cursor-hover]').forEach((el) => {\n          el.addEventListener('mouseenter', () => ring.classList.add('hover'));\n          el.addEventListener('mouseleave', () => ring.classList.remove('hover'));\n        });\n      } catch (err: any) {\n        console.warn(\"[motion-menu] custom-cursor failed to start:\", err);\n      }\n    })();\n\n    return () => {\n      disposed = true;\n      cleanup.forEach((fn) => { try { fn(); } catch (_) {} });\n    };\n  }, []);\n\n  return <div ref={hostRef} dangerouslySetInnerHTML={{ __html: MARKUP }} />;\n}\n\nexport default CustomCursor;\n"}]}