{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particle-button",
  "type": "registry:component",
  "title": "Particle Button",
  "description": "Button that bursts a canvas-confetti cheer on click (KokonutUI, MIT).",
  "dependencies": [
    "canvas-confetti",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://design.subconscious.ai/r/utils.json",
    "button"
  ],
  "files": [
    {
      "path": "registry/components/particle-button.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier (original kokonut)\n * @description: Particle Button — bursts a confetti cheer on click\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n *\n * Replaces the original framer-motion particle physics with canvas-confetti\n * (catdad/canvas-confetti, 15k★, MIT, 2.4KB gzipped). The hand-rolled physics\n * rendered 14 same-color motion.divs staggered over 1.85s — imperceptible at\n * desktop reading distance even after the bridge tried to fix it. See #508.\n *\n * Theme-tracked: reads --color, --red, and --grey-500 from the document root\n * at click time so the burst uses the active theme palette and tracks\n * data-theme / data-mode without re-renders.\n *\n * Direction-aware: data-dir on the button sets the burst angle (canvas-confetti\n * native `angle` + `spread` props). \"c\" (or unset) burst omnidirectionally.\n * Read from dataset to sidestep HTML's reserved `dir` attribute (LTR/RTL).\n *\n * a11y: `disableForReducedMotion: true` honors prefers-reduced-motion at the\n * library level — no animation when the user has reduced-motion set.\n */\n\nimport { MousePointerClick } from \"lucide-react\";\nimport { useRef, useState } from \"react\";\nimport confetti from \"canvas-confetti\";\nimport type { ButtonProps } from \"@/components/ui/button\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/registry/lib/utils\";\n\ninterface ParticleButtonProps extends ButtonProps {\n  onSuccess?: () => void;\n  successDuration?: number;\n}\n\n/* Maps data-dir to canvas-confetti `angle` (degrees: 0 = right, 90 = up). */\nconst DIR_ANGLES: Record<string, number> = {\n  n: 90, ne: 45, e: 0, se: 315, s: 270, sw: 225, w: 180, nw: 135, c: 90,\n};\n\nfunction readThemeColors(): string[] {\n  if (typeof window === \"undefined\") return [\"#000\"];\n  const s = getComputedStyle(document.documentElement);\n  const pick = (name: string) => s.getPropertyValue(name).trim();\n  return [pick(\"--color\"), pick(\"--red\"), pick(\"--grey-500\")].filter(Boolean);\n}\n\nexport default function ParticleButton({\n  children,\n  onClick,\n  onSuccess,\n  successDuration = 700,\n  className,\n  ...props\n}: ParticleButtonProps) {\n  const [pressed, setPressed] = useState(false);\n  const buttonRef = useRef<HTMLButtonElement>(null);\n\n  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n    setPressed(true);\n    onClick?.(e);\n    onSuccess?.();\n\n    const btn = buttonRef.current;\n    if (btn && typeof window !== \"undefined\") {\n      const r = btn.getBoundingClientRect();\n      const dir = btn.dataset.dir ?? \"c\";\n      const angle = DIR_ANGLES[dir] ?? 90;\n      const spread = dir === \"c\" ? 360 : 55;\n\n      confetti({\n        particleCount: 60,\n        startVelocity: 35,\n        spread,\n        angle,\n        ticks: 80,\n        gravity: dir === \"c\" ? 0.4 : 0.8,\n        scalar: 0.9,\n        origin: {\n          x: (r.left + r.width / 2) / window.innerWidth,\n          y: (r.top + r.height / 2) / window.innerHeight,\n        },\n        colors: readThemeColors(),\n        disableForReducedMotion: true,\n      });\n    }\n\n    setTimeout(() => setPressed(false), successDuration);\n  };\n\n  return (\n    <Button\n      className={cn(\n        \"relative\",\n        pressed && \"scale-95\",\n        \"transition-transform duration-100\",\n        className\n      )}\n      onClick={handleClick}\n      ref={buttonRef}\n      {...props}\n    >\n      {children}\n      <MousePointerClick className=\"h-4 w-4\" />\n    </Button>\n  );\n}\n"
    }
  ]
}
