{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "creepy-button",
  "type": "registry:ui",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "components/ui/creepy-button.tsx",
      "content": "\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\n\ninterface CreepyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n    children: React.ReactNode;\n    /**\n     * Optional custom class for the button container\n     */\n    className?: string;\n    /**\n     * Optional custom class for the button cover (the visible part)\n     */\n    coverClassName?: string;\n}\n\ntype Coords = {\n    x: number;\n    y: number;\n};\n\nexport const CreepyButton = ({\n    children,\n    className,\n    coverClassName,\n    onClick,\n    ...props\n}: CreepyButtonProps) => {\n    const eyesRef = useRef<HTMLSpanElement>(null);\n    const [eyeCoords, setEyeCoords] = useState<Coords>({ x: 0, y: 0 });\n    const [isHovered, setIsHovered] = useState(false);\n\n    const updateEyes = (e: React.MouseEvent | React.TouchEvent) => {\n        const userEvent =\n            \"touches\" in e ? (e as React.TouchEvent).touches[0] : (e as React.MouseEvent);\n\n        if (!eyesRef.current) return;\n\n        // get the center of the eyes container\n        const eyesRect = eyesRef.current.getBoundingClientRect();\n        const eyesCenter = {\n            x: eyesRect.left + eyesRect.width / 2,\n            y: eyesRect.top + eyesRect.height / 2,\n        };\n\n        // cursor position\n        const cursor = {\n            x: userEvent.clientX,\n            y: userEvent.clientY,\n        };\n\n        // calculate the eye angle\n        const dx = cursor.x - eyesCenter.x;\n        const dy = cursor.y - eyesCenter.y;\n        const angle = Math.atan2(-dy, dx) + Math.PI / 2;\n\n        // pupil distance from the eye center\n        const visionRangeX = 180; // Max distance to look horizontally\n        const visionRangeY = 75; // Max distance to look vertically\n        const distance = Math.hypot(dx, dy);\n\n        // Limit the movement so pupils don't go too far\n        // We normalize the distance influence\n        const x = (Math.sin(angle) * Math.min(distance, visionRangeX)) / visionRangeX;\n        const y = (Math.cos(angle) * Math.min(distance, visionRangeY)) / visionRangeY;\n\n        setEyeCoords({ x, y });\n    };\n\n    // Reset eyes when mouse leaves\n    const resetEyes = () => {\n        setEyeCoords({ x: 0, y: 0 });\n        setIsHovered(false);\n    };\n\n    const pupilStyle = {\n        transform: `translate(calc(-50% + ${eyeCoords.x * 50}%), calc(-50% + ${eyeCoords.y * 50}%))`,\n    };\n\n    return (\n        <button\n            className={cn(\n                \"relative min-w-[9em] rounded-xl bg-black cursor-pointer outline-none select-none group tap-highlight-transparent\",\n                \"focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-400\",\n                className\n            )}\n            onClick={onClick}\n            onMouseMove={(e) => {\n                updateEyes(e);\n                setIsHovered(true);\n            }}\n            onTouchMove={updateEyes}\n            onMouseLeave={resetEyes}\n            onFocus={() => setIsHovered(true)}\n            onBlur={() => setIsHovered(false)}\n            {...props}\n        >\n            {/* Eyes Container */}\n            <span\n                ref={eyesRef}\n                className=\"absolute flex items-center gap-[0.375em] right-[1em] bottom-[0.5em] h-[0.75em] z-0 pointer-events-none\"\n            >\n                {/* Left Eye */}\n                <motion.span\n                    className=\"relative w-[0.75em] bg-white rounded-full overflow-hidden\"\n                    animate={{ height: [\"0.75em\", \"0.75em\", \"0em\", \"0.75em\"] }}\n                    transition={{\n                        duration: 3,\n                        times: [0, 0.92, 0.96, 1],\n                        repeat: Infinity,\n                        ease: \"linear\",\n                    }}\n                >\n                    <span\n                        className=\"absolute top-1/2 left-1/2 w-[0.375em] h-[0.375em] bg-black rounded-full transition-transform duration-75 ease-out\"\n                        style={pupilStyle}\n                    />\n                </motion.span>\n                {/* Right Eye */}\n                <motion.span\n                    className=\"relative w-[0.75em] bg-white rounded-full overflow-hidden\"\n                    animate={{ height: [\"0.75em\", \"0.75em\", \"0em\", \"0.75em\"] }}\n                    transition={{\n                        duration: 3,\n                        times: [0, 0.92, 0.96, 1],\n                        repeat: Infinity,\n                        ease: \"linear\",\n                    }}\n                >\n                    <span\n                        className=\"absolute top-1/2 left-1/2 w-[0.375em] h-[0.375em] bg-black rounded-full transition-transform duration-75 ease-out\"\n                        style={pupilStyle}\n                    />\n                </motion.span>\n            </span>\n\n            {/* Button Cover */}\n            <motion.span\n                className={cn(\n                    \"absolute inset-0 block rounded-xl bg-blue-500 text-white font-bold tracking-wider\",\n                    \"shadow-[inset_0_0_0_0.125em_rgba(0,0,0,1)]\",\n                    \"flex items-center justify-center px-4 py-2\",\n                    \"origin-[1.25em_50%]\",\n                    coverClassName\n                )}\n                animate={{\n                    rotate: isHovered ? -12 : 0,\n                }}\n                transition={{\n                    type: \"spring\",\n                    stiffness: 300,\n                    damping: 20,\n                    mass: 0.8,\n                }}\n            >\n                {children}\n            </motion.span>\n\n            {/* Invisible placeholder to maintain size since cover is absolute */}\n            <span className=\"block opacity-0 px-4 py-2 font-bold tracking-wider min-w-[9em]\">\n                {children}\n            </span>\n        </button>\n    );\n};\n\nexport default CreepyButton;\n",
      "type": "registry:ui",
      "target": "components/ui/creepy-button.tsx"
    }
  ]
}