{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badges-particle",
  "title": "Particle badges",
  "description": "Styled badges: the Particle set. 5 decorative badges variations (SparkBadge, BurstBadge, TrailBadge, ConfettiBadge, GlitterBadge) 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/badges-particle.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Particle badge family: 5 badges emitting token particles. Colour comes ONLY from tokens (alpha via color-mix or a utility), the size is aligned with the base badge scale (+xl). framer-motion (motion/react) is used only for the particle motion; every particle stops under reduced-motion. The positions are deterministic (no Math.random). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst box: Record<StyledSize, string> = {\n  sm: \"h-5 gap-1 rounded-md px-1.5 text-xs\",\n  md: \"h-6 gap-1.5 rounded-md px-2 text-xs\",\n  lg: \"h-7 gap-1.5 rounded-lg px-2.5 text-sm\",\n  xl: \"h-8 gap-2 rounded-lg px-3 text-sm\",\n}\n\nconst base =\n  \"relative inline-flex w-fit shrink-0 items-center justify-center whitespace-nowrap font-medium [&_svg]:size-3 [&_svg]:shrink-0 [&_i]:text-xs [&_i]:leading-none\"\n\ntype Props = React.ComponentProps<\"span\"> & { size?: StyledSize }\n\n/* Spark: birkac token kivilcim yukari dogru dongude suzulur. */\nexport function SparkBadge({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const sparks = [\n    { left: \"22%\", delay: 0 },\n    { left: \"50%\", delay: 0.55 },\n    { left: \"78%\", delay: 1.1 },\n  ]\n  return (\n    <span\n      data-slot=\"styled-badge\"\n      className={cn(base, box[size], \"overflow-visible border border-border bg-secondary text-secondary-foreground\", className)}\n      {...props}\n    >\n      {!reduce &&\n        sparks.map((s, i) => (\n          <motion.span\n            key={i}\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute bottom-1 size-1 rounded-full bg-info\"\n            style={{ left: s.left }}\n            animate={{ y: [0, -9, -12], opacity: [0, 1, 0], scale: [0.4, 1, 0.3] }}\n            transition={{ duration: 1.6, delay: s.delay, ease: \"easeOut\", repeat: Infinity }}\n          />\n        ))}\n      <span className=\"relative\">{children}</span>\n    </span>\n  )\n}\n\n/* Burst: token particles scatter outwards on hover. */\nexport function BurstBadge({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [on, setOn] = React.useState(false)\n  const angles = [0, 45, 90, 135, 180, 225, 270, 315]\n  return (\n    <span\n      data-slot=\"styled-badge\"\n      className={cn(base, box[size], \"overflow-visible border border-border bg-secondary text-secondary-foreground\", className)}\n      {...props}\n      onMouseEnter={(e) => {\n        setOn(true)\n        props.onMouseEnter?.(e)\n      }}\n      onMouseLeave={(e) => {\n        setOn(false)\n        props.onMouseLeave?.(e)\n      }}\n    >\n      {!reduce &&\n        angles.map((a, i) => {\n          const rad = (a * Math.PI) / 180\n          return (\n            <motion.span\n              key={i}\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute left-1/2 top-1/2 size-1 rounded-full bg-info\"\n              initial={false}\n              animate={\n                on\n                  ? { x: Math.cos(rad) * 14, y: Math.sin(rad) * 14, opacity: [1, 0], scale: [1, 0.3] }\n                  : { x: 0, y: 0, opacity: 0, scale: 0.3 }\n              }\n              transition={{ duration: 0.5, ease: \"easeOut\" }}\n            />\n          )\n        })}\n      <span className=\"relative\">{children}</span>\n    </span>\n  )\n}\n\n/* Trail: kucuk bir token kuyruklu yildiz label etrafinda yorunge cizer. */\nexport function TrailBadge({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <span\n      data-slot=\"styled-badge\"\n      className={cn(base, box[size], \"overflow-visible border border-border bg-secondary text-secondary-foreground\", className)}\n      {...props}\n    >\n      {!reduce && (\n        <motion.span\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0\"\n          animate={{ rotate: 360 }}\n          transition={{ duration: 3, ease: \"linear\", repeat: Infinity }}\n        >\n          <span className=\"absolute left-1/2 top-0 size-1.5 -translate-x-1/2 rounded-full bg-info shadow-[0_0_6px_var(--color-info)]\" />\n        </motion.span>\n      )}\n      <span className=\"relative\">{children}</span>\n    </span>\n  )\n}\n\n/* Confetti: tiny multi-token confetti falls on hover. */\nexport function ConfettiBadge({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const [on, setOn] = React.useState(false)\n  const bits = [\n    { left: \"18%\", tone: \"bg-info\", dx: -6 },\n    { left: \"38%\", tone: \"bg-success\", dx: 4 },\n    { left: \"58%\", tone: \"bg-warning\", dx: -3 },\n    { left: \"78%\", tone: \"bg-danger\", dx: 6 },\n  ]\n  return (\n    <span\n      data-slot=\"styled-badge\"\n      className={cn(base, box[size], \"overflow-visible border border-border bg-secondary text-secondary-foreground\", className)}\n      {...props}\n      onMouseEnter={(e) => {\n        setOn(true)\n        props.onMouseEnter?.(e)\n      }}\n      onMouseLeave={(e) => {\n        setOn(false)\n        props.onMouseLeave?.(e)\n      }}\n    >\n      {!reduce &&\n        bits.map((b, i) => (\n          <motion.span\n            key={i}\n            aria-hidden=\"true\"\n            className={cn(\"pointer-events-none absolute -top-1 size-1 rounded-[1px]\", b.tone)}\n            style={{ left: b.left }}\n            initial={false}\n            animate={on ? { y: [0, 14], x: [0, b.dx], opacity: [1, 0], rotate: [0, 120] } : { y: 0, opacity: 0 }}\n            transition={{ duration: 0.6, ease: \"easeIn\" }}\n          />\n        ))}\n      <span className=\"relative\">{children}</span>\n    </span>\n  )\n}\n\n/* Glitter: label etrafinda parildayan token noktalar. */\nexport function GlitterBadge({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const dots = [\n    { left: \"8%\", top: \"20%\", delay: 0 },\n    { left: \"92%\", top: \"30%\", delay: 0.4 },\n    { left: \"15%\", top: \"80%\", delay: 0.8 },\n    { left: \"85%\", top: \"75%\", delay: 1.2 },\n  ]\n  return (\n    <span\n      data-slot=\"styled-badge\"\n      className={cn(base, box[size], \"overflow-visible border border-border bg-secondary text-secondary-foreground\", className)}\n      {...props}\n    >\n      {!reduce &&\n        dots.map((d, i) => (\n          <motion.span\n            key={i}\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute size-1 rounded-full bg-warning\"\n            style={{ left: d.left, top: d.top }}\n            animate={{ opacity: [0, 1, 0], scale: [0.4, 1, 0.4] }}\n            transition={{ duration: 1.4, delay: d.delay, ease: \"easeInOut\", repeat: Infinity }}\n          />\n        ))}\n      <span className=\"relative\">{children}</span>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/badges-particle.tsx"
    }
  ],
  "categories": [
    "styled",
    "badges"
  ],
  "type": "registry:component"
}