{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "TextCursor-TS-TW",
	"title": "TextCursor",
	"description": "Make any text element follow your cursor, leaving a trail of copies behind it.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "TextCursor/TextCursor.tsx",
			"content": "import React, { useState, useEffect, useRef } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\n\ninterface TextCursorProps {\n  text: string;\n  spacing?: number;\n  followMouseDirection?: boolean;\n  randomFloat?: boolean;\n  exitDuration?: number;\n  removalInterval?: number;\n  maxPoints?: number;\n}\n\ninterface TrailItem {\n  id: number;\n  x: number;\n  y: number;\n  angle: number;\n  randomX?: number;\n  randomY?: number;\n  randomRotate?: number;\n}\n\nconst TextCursor: React.FC<TextCursorProps> = ({\n  text = '⚛️',\n  spacing = 100,\n  followMouseDirection = true,\n  randomFloat = true,\n  exitDuration = 0.5,\n  removalInterval = 30,\n  maxPoints = 5\n}) => {\n  const [trail, setTrail] = useState<TrailItem[]>([]);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const lastMoveTimeRef = useRef<number>(Date.now());\n  const idCounter = useRef<number>(0);\n\n  const handleMouseMove = (e: MouseEvent) => {\n    if (!containerRef.current) return;\n    const rect = containerRef.current.getBoundingClientRect();\n    const mouseX = e.clientX - rect.left;\n    const mouseY = e.clientY - rect.top;\n\n    setTrail(prev => {\n      let newTrail = [...prev];\n      if (newTrail.length === 0) {\n        newTrail.push({\n          id: idCounter.current++,\n          x: mouseX,\n          y: mouseY,\n          angle: 0,\n          ...(randomFloat && {\n            randomX: Math.random() * 10 - 5,\n            randomY: Math.random() * 10 - 5,\n            randomRotate: Math.random() * 10 - 5\n          })\n        });\n      } else {\n        const last = newTrail[newTrail.length - 1];\n        const dx = mouseX - last.x;\n        const dy = mouseY - last.y;\n        const distance = Math.sqrt(dx * dx + dy * dy);\n        if (distance >= spacing) {\n          let rawAngle = (Math.atan2(dy, dx) * 180) / Math.PI;\n\n          rawAngle = ((rawAngle + 180) % 360) - 180;\n\n          const computedAngle = followMouseDirection ? rawAngle : 0;\n          const steps = Math.floor(distance / spacing);\n          for (let i = 1; i <= steps; i++) {\n            const t = (spacing * i) / distance;\n            const newX = last.x + dx * t;\n            const newY = last.y + dy * t;\n            newTrail.push({\n              id: idCounter.current++,\n              x: newX,\n              y: newY,\n              angle: computedAngle,\n              ...(randomFloat && {\n                randomX: Math.random() * 10 - 5,\n                randomY: Math.random() * 10 - 5,\n                randomRotate: Math.random() * 10 - 5\n              })\n            });\n          }\n        }\n      }\n      if (newTrail.length > maxPoints) {\n        newTrail = newTrail.slice(newTrail.length - maxPoints);\n      }\n      return newTrail;\n    });\n    lastMoveTimeRef.current = Date.now();\n  };\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    container.addEventListener('mousemove', handleMouseMove);\n    return () => {\n      container.removeEventListener('mousemove', handleMouseMove);\n    };\n  }, [containerRef.current]);\n\n  useEffect(() => {\n    const interval = setInterval(() => {\n      if (Date.now() - lastMoveTimeRef.current > 100) {\n        setTrail(prev => (prev.length > 0 ? prev.slice(1) : prev));\n      }\n    }, removalInterval);\n    return () => clearInterval(interval);\n  }, [removalInterval]);\n\n  return (\n    <div ref={containerRef} className=\"w-full h-full relative\">\n      <div className=\"absolute inset-0 pointer-events-none\">\n        <AnimatePresence>\n          {trail.map(item => (\n            <motion.div\n              key={item.id}\n              initial={{ opacity: 0, scale: 1, rotate: item.angle }}\n              animate={{\n                opacity: 1,\n                scale: 1,\n                x: randomFloat ? [0, item.randomX || 0, 0] : 0,\n                y: randomFloat ? [0, item.randomY || 0, 0] : 0,\n                rotate: randomFloat ? [item.angle, item.angle + (item.randomRotate || 0), item.angle] : item.angle\n              }}\n              exit={{ opacity: 0, scale: 0 }}\n              transition={{\n                opacity: { duration: exitDuration, ease: 'easeOut' },\n\n                ...(randomFloat && {\n                  x: {\n                    duration: 2,\n                    ease: 'easeInOut',\n                    repeat: Infinity,\n                    repeatType: 'mirror'\n                  },\n                  y: {\n                    duration: 2,\n                    ease: 'easeInOut',\n                    repeat: Infinity,\n                    repeatType: 'mirror'\n                  },\n                  rotate: {\n                    duration: 2,\n                    ease: 'easeInOut',\n                    repeat: Infinity,\n                    repeatType: 'mirror'\n                  }\n                })\n              }}\n              className=\"absolute select-none whitespace-nowrap text-3xl\"\n              style={{ left: item.x, top: item.y }}\n            >\n              {text}\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n};\n\nexport default TextCursor;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}