{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "target-cursor",
  "type": "registry:block",
  "title": "Target cursor",
  "description": "Target cursor",
  "files": [
    {
      "path": "components/usages/targetcursorusage.tsx",
      "content": "import TargetCursor from \"@/registry/open-source/target-cursor\";\n\nexport default function App() {\n\treturn (\n\t\t<div>\n\t\t\t<TargetCursor spinDuration={2} hideDefaultCursor={true} />\n\n\t\t\t<h1>Hover over the elements below</h1>\n\t\t\t<button className=\"cursor-target\">Click me!</button>\n\t\t\t<div className=\"cursor-target\">Hover target</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/targetcursorusage.tsx",
      "content": "import TargetCursor from \"@/registry/open-source/target-cursor\";\n\nexport default function App() {\n\treturn (\n\t\t<div>\n\t\t\t<TargetCursor spinDuration={2} hideDefaultCursor={true} />\n\n\t\t\t<h1>Hover over the elements below</h1>\n\t\t\t<button className=\"cursor-target\">Click me!</button>\n\t\t\t<div className=\"cursor-target\">Hover target</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/target-cursor.tsx",
      "content": "import React, {\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\n\nimport { gsap } from \"gsap\";\n\nimport { cn } from \"@/registry/utilities/cn\";\n\n// Credit:\n// https://www.reactbits.dev/animations/target-cursor\n\nexport interface TargetCursorProps {\n\ttargetSelector?: string;\n\tspinDuration?: number;\n\thideDefaultCursor?: boolean;\n}\n\nconst TargetCursor: React.FC<TargetCursorProps> = ({\n\ttargetSelector = \".cursor-target\",\n\tspinDuration = 2,\n\thideDefaultCursor = true,\n}) => {\n\tconst cursorRef = useRef<HTMLDivElement>(null);\n\tconst cornersRef = useRef<NodeListOf<HTMLDivElement>>(null);\n\tconst spinTl = useRef<gsap.core.Timeline>(null);\n\n\tconst constants = useMemo(\n\t\t() => ({\n\t\t\tborderWidth: 3,\n\t\t\tcornerSize: 12,\n\t\t\tparallaxStrength: 0.00005,\n\t\t}),\n\t\t[]\n\t);\n\n\tconst moveCursor = useCallback((x: number, y: number) => {\n\t\tif (!cursorRef.current) return;\n\t\tgsap.to(cursorRef.current, {\n\t\t\tx,\n\t\t\ty,\n\t\t\tduration: 0.1,\n\t\t\tease: \"power3.out\",\n\t\t});\n\t}, []);\n\n\tuseEffect(() => {\n\t\tif (!cursorRef.current) return;\n\n\t\tconst originalCursor = document.body.style.cursor;\n\t\tif (hideDefaultCursor) {\n\t\t\tdocument.body.style.cursor = \"none\";\n\t\t}\n\n\t\tconst cursor = cursorRef.current;\n\t\tcornersRef.current = cursor.querySelectorAll<HTMLDivElement>(\n\t\t\t\".target-cursor-corner\"\n\t\t);\n\n\t\tlet activeTarget: Element | null = null;\n\t\tlet currentTargetMove: ((ev: Event) => void) | null = null;\n\t\tlet currentLeaveHandler: (() => void) | null = null;\n\t\tlet isAnimatingToTarget = false;\n\t\tlet resumeTimeout: ReturnType<typeof setTimeout> | null = null;\n\n\t\tconst cleanupTarget = (target: Element) => {\n\t\t\tif (currentTargetMove) {\n\t\t\t\ttarget.removeEventListener(\"mousemove\", currentTargetMove);\n\t\t\t}\n\t\t\tif (currentLeaveHandler) {\n\t\t\t\ttarget.removeEventListener(\"mouseleave\", currentLeaveHandler);\n\t\t\t}\n\t\t\tcurrentTargetMove = null;\n\t\t\tcurrentLeaveHandler = null;\n\t\t};\n\n\t\tgsap.set(cursor, {\n\t\t\txPercent: -50,\n\t\t\tyPercent: -50,\n\t\t\tx: window.innerWidth / 2,\n\t\t\ty: window.innerHeight / 2,\n\t\t});\n\n\t\tconst createSpinTimeline = () => {\n\t\t\tif (spinTl.current) {\n\t\t\t\tspinTl.current.kill();\n\t\t\t}\n\t\t\tspinTl.current = gsap.timeline({ repeat: -1 }).to(cursor, {\n\t\t\t\trotation: \"+=360\",\n\t\t\t\tduration: spinDuration,\n\t\t\t\tease: \"none\",\n\t\t\t});\n\t\t};\n\n\t\tcreateSpinTimeline();\n\n\t\tconst moveHandler = (e: MouseEvent) => moveCursor(e.clientX, e.clientY);\n\t\twindow.addEventListener(\"mousemove\", moveHandler);\n\n\t\tconst enterHandler = (e: MouseEvent) => {\n\t\t\tconst directTarget = e.target as Element;\n\n\t\t\tconst allTargets: Element[] = [];\n\t\t\tlet current = directTarget;\n\t\t\twhile (current && current !== document.body) {\n\t\t\t\tif (current.matches(targetSelector)) {\n\t\t\t\t\tallTargets.push(current);\n\t\t\t\t}\n\t\t\t\tcurrent = current.parentElement!;\n\t\t\t}\n\n\t\t\tconst target = allTargets[0] || null;\n\t\t\tif (!target || !cursorRef.current || !cornersRef.current) return;\n\n\t\t\tif (activeTarget === target) return;\n\n\t\t\tif (activeTarget) {\n\t\t\t\tcleanupTarget(activeTarget);\n\t\t\t}\n\n\t\t\tif (resumeTimeout) {\n\t\t\t\tclearTimeout(resumeTimeout);\n\t\t\t\tresumeTimeout = null;\n\t\t\t}\n\n\t\t\tactiveTarget = target;\n\n\t\t\tgsap.killTweensOf(cursorRef.current, \"rotation\");\n\t\t\tspinTl.current?.pause();\n\n\t\t\tgsap.set(cursorRef.current, { rotation: 0 });\n\n\t\t\tconst updateCorners = (mouseX?: number, mouseY?: number) => {\n\t\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\t\tconst cursorRect = cursorRef.current!.getBoundingClientRect();\n\n\t\t\t\tconst cursorCenterX = cursorRect.left + cursorRect.width / 2;\n\t\t\t\tconst cursorCenterY = cursorRect.top + cursorRect.height / 2;\n\n\t\t\t\tconst [tlc, trc, brc, blc] = Array.from(cornersRef.current!);\n\n\t\t\t\tconst { borderWidth, cornerSize, parallaxStrength } = constants;\n\n\t\t\t\tlet tlOffset = {\n\t\t\t\t\tx: rect.left - cursorCenterX - borderWidth,\n\t\t\t\t\ty: rect.top - cursorCenterY - borderWidth,\n\t\t\t\t};\n\t\t\t\tlet trOffset = {\n\t\t\t\t\tx: rect.right - cursorCenterX + borderWidth - cornerSize,\n\t\t\t\t\ty: rect.top - cursorCenterY - borderWidth,\n\t\t\t\t};\n\t\t\t\tlet brOffset = {\n\t\t\t\t\tx: rect.right - cursorCenterX + borderWidth - cornerSize,\n\t\t\t\t\ty: rect.bottom - cursorCenterY + borderWidth - cornerSize,\n\t\t\t\t};\n\t\t\t\tlet blOffset = {\n\t\t\t\t\tx: rect.left - cursorCenterX - borderWidth,\n\t\t\t\t\ty: rect.bottom - cursorCenterY + borderWidth - cornerSize,\n\t\t\t\t};\n\n\t\t\t\tif (mouseX !== undefined && mouseY !== undefined) {\n\t\t\t\t\tconst targetCenterX = rect.left + rect.width / 2;\n\t\t\t\t\tconst targetCenterY = rect.top + rect.height / 2;\n\t\t\t\t\tconst mouseOffsetX = (mouseX - targetCenterX) * parallaxStrength;\n\t\t\t\t\tconst mouseOffsetY = (mouseY - targetCenterY) * parallaxStrength;\n\n\t\t\t\t\ttlOffset.x += mouseOffsetX;\n\t\t\t\t\ttlOffset.y += mouseOffsetY;\n\t\t\t\t\ttrOffset.x += mouseOffsetX;\n\t\t\t\t\ttrOffset.y += mouseOffsetY;\n\t\t\t\t\tbrOffset.x += mouseOffsetX;\n\t\t\t\t\tbrOffset.y += mouseOffsetY;\n\t\t\t\t\tblOffset.x += mouseOffsetX;\n\t\t\t\t\tblOffset.y += mouseOffsetY;\n\t\t\t\t}\n\n\t\t\t\tconst tl = gsap.timeline();\n\t\t\t\tconst corners = [tlc, trc, brc, blc];\n\t\t\t\tconst offsets = [tlOffset, trOffset, brOffset, blOffset];\n\n\t\t\t\tcorners.forEach((corner, index) => {\n\t\t\t\t\ttl.to(\n\t\t\t\t\t\tcorner,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tx: offsets[index].x,\n\t\t\t\t\t\t\ty: offsets[index].y,\n\t\t\t\t\t\t\tduration: 0.2,\n\t\t\t\t\t\t\tease: \"power2.out\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tisAnimatingToTarget = true;\n\t\t\tupdateCorners();\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tisAnimatingToTarget = false;\n\t\t\t}, 1);\n\n\t\t\tlet moveThrottle: number | null = null;\n\t\t\tconst targetMove = (ev: Event) => {\n\t\t\t\tif (moveThrottle || isAnimatingToTarget) return;\n\t\t\t\tmoveThrottle = requestAnimationFrame(() => {\n\t\t\t\t\tconst mouseEvent = ev as MouseEvent;\n\t\t\t\t\tupdateCorners(mouseEvent.clientX, mouseEvent.clientY);\n\t\t\t\t\tmoveThrottle = null;\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst leaveHandler = () => {\n\t\t\t\tactiveTarget = null;\n\t\t\t\tisAnimatingToTarget = false;\n\n\t\t\t\tif (cornersRef.current) {\n\t\t\t\t\tconst corners = Array.from(cornersRef.current);\n\t\t\t\t\tgsap.killTweensOf(corners);\n\n\t\t\t\t\tconst { cornerSize } = constants;\n\t\t\t\t\tconst positions = [\n\t\t\t\t\t\t{ x: -cornerSize * 1.5, y: -cornerSize * 1.5 },\n\t\t\t\t\t\t{ x: cornerSize * 0.5, y: -cornerSize * 1.5 },\n\t\t\t\t\t\t{ x: cornerSize * 0.5, y: cornerSize * 0.5 },\n\t\t\t\t\t\t{ x: -cornerSize * 1.5, y: cornerSize * 0.5 },\n\t\t\t\t\t];\n\n\t\t\t\t\tconst tl = gsap.timeline();\n\t\t\t\t\tcorners.forEach((corner, index) => {\n\t\t\t\t\t\ttl.to(\n\t\t\t\t\t\t\tcorner,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tx: positions[index].x,\n\t\t\t\t\t\t\t\ty: positions[index].y,\n\t\t\t\t\t\t\t\tduration: 0.3,\n\t\t\t\t\t\t\t\tease: \"power3.out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t0\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresumeTimeout = setTimeout(() => {\n\t\t\t\t\tif (!activeTarget && cursorRef.current && spinTl.current) {\n\t\t\t\t\t\tconst currentRotation = gsap.getProperty(\n\t\t\t\t\t\t\tcursorRef.current,\n\t\t\t\t\t\t\t\"rotation\"\n\t\t\t\t\t\t) as number;\n\t\t\t\t\t\tconst normalizedRotation = currentRotation % 360;\n\n\t\t\t\t\t\tspinTl.current.kill();\n\t\t\t\t\t\tspinTl.current = gsap\n\t\t\t\t\t\t\t.timeline({ repeat: -1 })\n\t\t\t\t\t\t\t.to(cursorRef.current, {\n\t\t\t\t\t\t\t\trotation: \"+=360\",\n\t\t\t\t\t\t\t\tduration: spinDuration,\n\t\t\t\t\t\t\t\tease: \"none\",\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\tgsap.to(cursorRef.current, {\n\t\t\t\t\t\t\trotation: normalizedRotation + 360,\n\t\t\t\t\t\t\tduration: spinDuration * (1 - normalizedRotation / 360),\n\t\t\t\t\t\t\tease: \"none\",\n\t\t\t\t\t\t\tonComplete: () => {\n\t\t\t\t\t\t\t\tspinTl.current?.restart();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tresumeTimeout = null;\n\t\t\t\t}, 50);\n\n\t\t\t\tcleanupTarget(target);\n\t\t\t};\n\n\t\t\tcurrentTargetMove = targetMove;\n\t\t\tcurrentLeaveHandler = leaveHandler;\n\n\t\t\ttarget.addEventListener(\"mousemove\", targetMove);\n\t\t\ttarget.addEventListener(\"mouseleave\", leaveHandler);\n\t\t};\n\n\t\twindow.addEventListener(\"mouseover\", enterHandler, { passive: true });\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"mousemove\", moveHandler);\n\t\t\twindow.removeEventListener(\"mouseover\", enterHandler);\n\n\t\t\tif (activeTarget) {\n\t\t\t\tcleanupTarget(activeTarget);\n\t\t\t}\n\n\t\t\tspinTl.current?.kill();\n\t\t\tdocument.body.style.cursor = originalCursor;\n\t\t};\n\t}, [targetSelector, spinDuration, moveCursor, constants, hideDefaultCursor]);\n\n\tuseEffect(() => {\n\t\tif (!cursorRef.current || !spinTl.current) return;\n\n\t\tif (spinTl.current.isActive()) {\n\t\t\tspinTl.current.kill();\n\t\t\tspinTl.current = gsap.timeline({ repeat: -1 }).to(cursorRef.current, {\n\t\t\t\trotation: \"+=360\",\n\t\t\t\tduration: spinDuration,\n\t\t\t\tease: \"none\",\n\t\t\t});\n\t\t}\n\t}, [spinDuration]);\n\n\tconst [isActive, setIsActive] = useState<boolean>(false);\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\n\tuseEffect(() => {\n\t\tif (typeof window !== \"undefined\" && containerRef.current) {\n\t\t\t// Get the parent element directly from the ref\n\t\t\tconst parentElement = containerRef.current.parentElement;\n\n\t\t\tif (parentElement) {\n\t\t\t\t// Add cursor-none to parent\n\t\t\t\tparentElement.style.cursor = \"none\";\n\n\t\t\t\t// Add event listeners to parent\n\n\t\t\t\tconst handleMouseEnter = (e: MouseEvent) => {\n\t\t\t\t\tsetIsActive(true);\n\t\t\t\t};\n\n\t\t\t\tconst handleMouseLeave = () => {\n\t\t\t\t\tsetIsActive(false);\n\t\t\t\t};\n\n\t\t\t\tparentElement.addEventListener(\"mouseenter\", handleMouseEnter);\n\t\t\t\tparentElement.addEventListener(\"mouseleave\", handleMouseLeave);\n\n\t\t\t\treturn () => {\n\t\t\t\t\tparentElement.style.cursor = \"\";\n\t\t\t\t\tparentElement.removeEventListener(\n\t\t\t\t\t\t\"mouseenter\",\n\t\t\t\t\t\thandleMouseEnter\n\t\t\t\t\t);\n\t\t\t\t\tparentElement.removeEventListener(\n\t\t\t\t\t\t\"mouseleave\",\n\t\t\t\t\t\thandleMouseLeave\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}, []);\n\n\treturn (\n\t\t<>\n\t\t\t<div ref={containerRef} />\n\t\t\t<div\n\t\t\t\tref={cursorRef}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"fixed top-0 left-0 w-0 h-0 pointer-events-none z-9999 mix-blend-difference transform -translate-x-1/2 -translate-y-1/2\",\n\t\t\t\t\tisActive ? \"opacity-100\" : \"opacity-0\"\n\t\t\t\t)}\n\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"absolute left-1/2 top-1/2 w-1 h-1 bg-background rounded-full transform -translate-x-1/2 -translate-y-1/2\"\n\t\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t\t/>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"target-cursor-corner absolute left-1/2 top-1/2 w-3 h-3 border-[3px] border-white transform -translate-x-[150%] -translate-y-[150%] border-r-0 border-b-0\"\n\t\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t\t/>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"target-cursor-corner absolute left-1/2 top-1/2 w-3 h-3 border-[3px] border-white transform translate-x-1/2 -translate-y-[150%] border-l-0 border-b-0\"\n\t\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t\t/>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"target-cursor-corner absolute left-1/2 top-1/2 w-3 h-3 border-[3px] border-white transform translate-x-1/2 translate-y-1/2 border-l-0 border-t-0\"\n\t\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t\t/>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"target-cursor-corner absolute left-1/2 top-1/2 w-3 h-3 border-[3px] border-white transform -translate-x-[150%] translate-y-1/2 border-r-0 border-t-0\"\n\t\t\t\t\tstyle={{ willChange: \"transform\" }}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n};\n\nexport default TargetCursor;\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}