{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "attract-button",
  "type": "registry:component",
  "title": "Attract Button",
  "description": "Button whose scattered particles spring to center on hover (KokonutUI, MIT), bridged to contract tokens.",
  "dependencies": [
    "framer-motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://design.subconscious.ai/r/utils.json",
    "button"
  ],
  "files": [
    {
      "path": "registry/components/attract-button.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Attract Button\n * @version: 1.0.0\n * @date: 2025-06-26\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n */\n\nimport { Magnet } from \"lucide-react\";\nimport { motion, useAnimation } from \"framer-motion\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/registry/lib/utils\";\n\ninterface AttractButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  particleCount?: number;\n  attractRadius?: number;\n}\n\ninterface Particle {\n  id: number;\n  x: number;\n  y: number;\n}\n\nexport default function AttractButton({\n  className,\n  particleCount = 12,\n  attractRadius = 50,\n  ...props\n}: AttractButtonProps) {\n  const [isAttracting, setIsAttracting] = useState(false);\n  const [particles, setParticles] = useState<Particle[]>([]);\n  const particlesControl = useAnimation();\n\n  useEffect(() => {\n    const newParticles = Array.from({ length: particleCount }, (_, i) => ({\n      id: i,\n      x: Math.random() * 360 - 180,\n      y: Math.random() * 360 - 180,\n    }));\n    setParticles(newParticles);\n  }, [particleCount]);\n\n  const handleInteractionStart = useCallback(async () => {\n    setIsAttracting(true);\n    await particlesControl.start({\n      x: 0,\n      y: 0,\n      transition: {\n        type: \"spring\",\n        stiffness: 50,\n        damping: 10,\n      },\n    });\n  }, [particlesControl]);\n\n  const handleInteractionEnd = useCallback(async () => {\n    setIsAttracting(false);\n    await particlesControl.start((i) => ({\n      x: particles[i].x,\n      y: particles[i].y,\n      transition: {\n        type: \"spring\",\n        stiffness: 100,\n        damping: 15,\n      },\n    }));\n  }, [particlesControl, particles]);\n\n  return (\n    <Button\n      className={cn(\n        \"relative min-w-40 touch-none\",\n        \"transition-all duration-300\",\n        className\n      )}\n      onMouseEnter={handleInteractionStart}\n      onMouseLeave={handleInteractionEnd}\n      onTouchEnd={handleInteractionEnd}\n      onTouchStart={handleInteractionStart}\n      style={{\n        background: \"color-mix(in srgb, var(--color) 8%, transparent)\",\n        color: \"var(--color)\",\n        border: \"1px solid color-mix(in srgb, var(--color) 15%, transparent)\",\n      }}\n      {...props}\n    >\n      {particles.map((_, index) => (\n        <motion.div\n          animate={particlesControl}\n          className={cn(\n            \"absolute h-1.5 w-1.5 rounded-full\",\n            \"transition-opacity duration-300\",\n            isAttracting ? \"opacity-100\" : \"opacity-40\"\n          )}\n          style={{ background: \"var(--color)\" }}\n          custom={index}\n          initial={{ x: particles[index].x, y: particles[index].y }}\n          key={index}\n        />\n      ))}\n      <span className=\"relative flex w-full items-center justify-center gap-2\">\n        <Magnet\n          className={cn(\n            \"h-4 w-4 transition-transform duration-300\",\n            isAttracting && \"scale-110\"\n          )}\n        />\n        {isAttracting ? \"Attracting\" : \"Hover me\"}\n      </span>\n    </Button>\n  );\n}\n"
    }
  ]
}
