{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "buttons-particle",
  "title": "Particle buttons",
  "description": "Styled buttons: the Particle set. 5 decorative buttons variations (BurstButton, ConfettiButton, SparkleButton, RiseButton, PopButton) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/buttons-particle.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Particle button family: 5 variations on particle emission. The particles fly and fade with framer-motion at fixed angles (deterministic, nothing random). All are sized, take colour ONLY from tokens, and there are no particles under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst sizes: Record<StyledSize, string> = {\n  sm: \"h-8 gap-1.5 rounded-md px-3 text-sm\",\n  md: \"h-9 gap-2 rounded-lg px-4 text-sm\",\n  lg: \"h-10 gap-2 rounded-lg px-5 text-sm\",\n  xl: \"h-12 gap-2.5 rounded-xl px-6 text-base\",\n}\n\nconst base =\n  \"relative inline-flex shrink-0 select-none items-center justify-center bg-primary font-medium whitespace-nowrap text-primary-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst RADIAL = Array.from({ length: 10 }, (_, i) => {\n  const a = (i / 10) * Math.PI * 2\n  return { dx: Math.cos(a) * 26, dy: Math.sin(a) * 26 }\n})\nconst CONFETTI_TONES = [\"bg-info\", \"bg-success\", \"bg-warning\", \"bg-danger\", \"bg-brand\"]\n\ntype Props = React.ComponentProps<\"button\"> & { size?: StyledSize }\n\n/* Burst: token dots fly outwards on hover (replays on every hover). */\nexport function BurstButton({ className, size = \"md\", children, onPointerEnter, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [burst, setBurst] = React.useState(0)\n  return (\n    <button\n      data-slot=\"styled-button\"\n      onPointerEnter={(e) => {\n        if (!reduce) setBurst((b) => b + 1)\n        onPointerEnter?.(e)\n      }}\n      className={cn(base, sizes[size], \"rounded-lg\", className)}\n      {...props}\n    >\n      <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 overflow-visible\">\n        <AnimatePresence>\n          {burst > 0 && (\n            <span key={burst} className=\"absolute left-1/2 top-1/2\">\n              {RADIAL.map((p, i) => (\n                <motion.span\n                  key={i}\n                  className=\"absolute size-1 rounded-full bg-info\"\n                  initial={{ x: 0, y: 0, opacity: 0.9, scale: 1 }}\n                  animate={{ x: p.dx, y: p.dy, opacity: 0, scale: 0.4 }}\n                  transition={{ duration: 0.5, ease: \"easeOut\" }}\n                />\n              ))}\n            </span>\n          )}\n        </AnimatePresence>\n      </span>\n      <span className=\"relative flex items-center gap-2\">{children}</span>\n    </button>\n  )\n}\n\n/* Confetti: tiklamada cok tonlu (token) parcaciklar yukari sacilir. */\nexport function ConfettiButton({ className, size = \"md\", children, onPointerDown, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [pop, setPop] = React.useState(0)\n  return (\n    <button\n      data-slot=\"styled-button\"\n      onPointerDown={(e) => {\n        if (!reduce) setPop((b) => b + 1)\n        onPointerDown?.(e)\n      }}\n      className={cn(base, sizes[size], \"rounded-lg\", className)}\n      {...props}\n    >\n      <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 overflow-visible\">\n        <AnimatePresence>\n          {pop > 0 && (\n            <span key={pop} className=\"absolute left-1/2 top-0\">\n              {Array.from({ length: 10 }).map((_, i) => {\n                const dx = (i - 4.5) * 8\n                return (\n                  <motion.span\n                    key={i}\n                    className={cn(\"absolute size-1.5 rounded-[1px]\", CONFETTI_TONES[i % CONFETTI_TONES.length])}\n                    initial={{ x: 0, y: 0, opacity: 1, rotate: 0 }}\n                    animate={{ x: dx, y: -22 - (i % 3) * 6, opacity: 0, rotate: 180 }}\n                    transition={{ duration: 0.6, ease: \"easeOut\" }}\n                  />\n                )\n              })}\n            </span>\n          )}\n        </AnimatePresence>\n      </span>\n      <span className=\"relative flex items-center gap-2\">{children}</span>\n    </button>\n  )\n}\n\n/* Sparkle: cevrede surekli parildayan token yildiz noktalari. */\nexport function SparkleButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const spots = [\n    { left: \"8%\", top: \"20%\", delay: 0 },\n    { left: \"88%\", top: \"30%\", delay: 0.4 },\n    { left: \"20%\", top: \"78%\", delay: 0.8 },\n    { left: \"70%\", top: \"72%\", delay: 1.2 },\n    { left: \"50%\", top: \"12%\", delay: 0.6 },\n  ]\n  return (\n    <button data-slot=\"styled-button\" className={cn(base, sizes[size], \"relative rounded-lg\", className)} {...props}>\n      <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0\">\n        {!reduce &&\n          spots.map((s, i) => (\n            <motion.span\n              key={i}\n              className=\"absolute size-1 rounded-full bg-primary-foreground\"\n              style={{ left: s.left, top: s.top }}\n              animate={{ opacity: [0, 1, 0], scale: [0.4, 1, 0.4] }}\n              transition={{ duration: 1.6, ease: \"easeInOut\", repeat: Infinity, delay: s.delay }}\n            />\n          ))}\n      </span>\n      <span className=\"relative flex items-center gap-2\">{children as React.ReactNode}</span>\n    </button>\n  )\n}\n\n/* Rise: tabandan surekli yukselen token kivilcimlar (kor/ates hissi). */\nexport function RiseButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const cols = [15, 32, 50, 68, 85]\n  return (\n    <button data-slot=\"styled-button\" className={cn(base, sizes[size], \"relative overflow-hidden rounded-lg\", className)} {...props}>\n      <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0\">\n        {!reduce &&\n          cols.map((left, i) => (\n            <motion.span\n              key={i}\n              className=\"absolute bottom-0 size-1 rounded-full bg-warning\"\n              style={{ left: `${left}%` }}\n              animate={{ y: [4, -18], opacity: [0, 0.9, 0] }}\n              transition={{ duration: 1.4, ease: \"easeOut\", repeat: Infinity, delay: i * 0.25 }}\n            />\n          ))}\n      </span>\n      <span className=\"relative flex items-center gap-2\">{children as React.ReactNode}</span>\n    </button>\n  )\n}\n\n/* Pop: tiklamada birkac buyuk token parcacik disari firlar. */\nexport function PopButton({ className, size = \"md\", children, onPointerDown, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [pop, setPop] = React.useState(0)\n  const dirs = Array.from({ length: 6 }, (_, i) => {\n    const a = (i / 6) * Math.PI * 2\n    return { dx: Math.cos(a) * 30, dy: Math.sin(a) * 30 }\n  })\n  return (\n    <button\n      data-slot=\"styled-button\"\n      onPointerDown={(e) => {\n        if (!reduce) setPop((b) => b + 1)\n        onPointerDown?.(e)\n      }}\n      className={cn(base, sizes[size], \"rounded-lg\", className)}\n      {...props}\n    >\n      <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 overflow-visible\">\n        <AnimatePresence>\n          {pop > 0 && (\n            <span key={pop} className=\"absolute left-1/2 top-1/2\">\n              {dirs.map((p, i) => (\n                <motion.span\n                  key={i}\n                  className=\"absolute size-1.5 rounded-full bg-brand\"\n                  initial={{ x: 0, y: 0, opacity: 1, scale: 1.2 }}\n                  animate={{ x: p.dx, y: p.dy, opacity: 0, scale: 0.5 }}\n                  transition={{ duration: 0.45, ease: \"easeOut\" }}\n                />\n              ))}\n            </span>\n          )}\n        </AnimatePresence>\n      </span>\n      <span className=\"relative flex items-center gap-2\">{children}</span>\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/buttons-particle.tsx"
    }
  ],
  "categories": [
    "styled",
    "buttons"
  ],
  "type": "registry:component"
}