{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "TextCursor-JS-CSS",
	"title": "TextCursor",
	"description": "Make any text element follow your cursor, leaving a trail of copies behind it.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "TextCursor.css",
			"target": "@components/TextCursor.css",
			"content": ".text-cursor-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n}\n\n.text-cursor-inner {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  pointer-events: none;\n}\n\n.text-cursor-item {\n  position: absolute;\n  user-select: none;\n  white-space: nowrap;\n  font-size: 1.875rem;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "TextCursor.jsx",
			"content": "import { useState, useEffect, useRef } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport './TextCursor.css';\n\nconst TextCursor = ({\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([]);\n  const containerRef = useRef(null);\n  const lastMoveTimeRef = useRef(Date.now());\n  const idCounter = useRef(0);\n\n  const handleMouseMove = e => {\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    const createRandomData = () =>\n      randomFloat\n        ? {\n            randomX: Math.random() * 10 - 5,\n            randomY: Math.random() * 10 - 5,\n            randomRotate: Math.random() * 10 - 5\n          }\n        : {};\n\n    setTrail(prev => {\n      const newTrail = [...prev];\n\n      if (newTrail.length === 0) {\n        newTrail.push({\n          id: idCounter.current++,\n          x: mouseX,\n          y: mouseY,\n          angle: 0,\n          ...createRandomData()\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\n        if (distance >= spacing) {\n          let rawAngle = (Math.atan2(dy, dx) * 180) / Math.PI;\n          const computedAngle = followMouseDirection ? rawAngle : 0;\n          const steps = Math.floor(distance / spacing);\n\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\n            newTrail.push({\n              id: idCounter.current++,\n              x: newX,\n              y: newY,\n              angle: computedAngle,\n              ...createRandomData()\n            });\n          }\n        }\n      }\n\n      return newTrail.length > maxPoints ? newTrail.slice(newTrail.length - maxPoints) : newTrail;\n    });\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 () => container.removeEventListener('mousemove', handleMouseMove);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\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=\"text-cursor-container\">\n      <div className=\"text-cursor-inner\">\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                ...(randomFloat && {\n                  x: { duration: 2, ease: 'easeInOut', repeat: Infinity, repeatType: 'mirror' },\n                  y: { duration: 2, ease: 'easeInOut', repeat: Infinity, repeatType: 'mirror' },\n                  rotate: { duration: 2, ease: 'easeInOut', repeat: Infinity, repeatType: 'mirror' }\n                })\n              }}\n              className=\"text-cursor-item\"\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"
	]
}