{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loaders-particle",
  "title": "Particle loaders",
  "description": "Styled loaders: the Particle set. 5 decorative loaders variations (SwarmLoader, ScatterLoader, ConvergeLoader, TrailLoader, FireflyLoader) 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/loaders-particle.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Particle loader family: 5 decorative indicators on a particle and scatter theme. Colour comes ONLY from tokens (currentColor / bg-current). Size = the box scale. DELIBERATE: the loader keeps moving under reduced-motion too. The angles and delays are index-derived and fixed (NO Math.random) - 8 dots land at 45-degree steps. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst box: Record<StyledSize, string> = {\n  sm: \"size-4\",\n  md: \"size-5\",\n  lg: \"size-6\",\n  xl: \"size-8\",\n}\n\ntype Props = React.ComponentProps<\"div\"> & { size?: StyledSize; label?: string }\n\nconst ANGLES = [0, 45, 90, 135, 180, 225, 270, 315]\n\n/* Swarm: 8 nokta sabit acilarda dongude nabiz atar. */\nexport function SwarmLoader({ className, size = \"md\", label = \"Loading\", ...props }: Props) {\n  return (\n    <div\n      data-slot=\"styled-loader\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block text-primary\", box[size], className)}\n      {...props}\n    >\n      {ANGLES.map((deg, i) => (\n        <span\n          key={deg}\n          className=\"absolute inset-0\"\n          style={{ transform: `rotate(${deg}deg)` }}\n        >\n          <motion.span\n            className=\"absolute left-1/2 top-0 size-1/5 -translate-x-1/2 rounded-full bg-current\"\n            animate={{ scale: [0.3, 1, 0.3], opacity: [0.3, 1, 0.3] }}\n            transition={{ duration: 1.2, ease: \"easeInOut\", repeat: Infinity, delay: i * 0.12 }}\n          />\n        </span>\n      ))}\n    </div>\n  )\n}\n\n/* Scatter: noktalar disari savrulup geri doner (dongu). */\nexport function ScatterLoader({ className, size = \"md\", label = \"Loading\", ...props }: Props) {\n  return (\n    <div\n      data-slot=\"styled-loader\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block text-info\", box[size], className)}\n      {...props}\n    >\n      {ANGLES.map((deg) => (\n        <span\n          key={deg}\n          className=\"absolute inset-0\"\n          style={{ transform: `rotate(${deg}deg)` }}\n        >\n          <motion.span\n            className=\"absolute left-1/2 top-1/2 size-1/5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-current\"\n            animate={{ y: [\"0%\", \"-260%\", \"0%\"], opacity: [1, 0.2, 1] }}\n            transition={{ duration: 1.6, ease: \"easeInOut\", repeat: Infinity }}\n          />\n        </span>\n      ))}\n    </div>\n  )\n}\n\n/* Converge: noktalar disaridan merkeze tekrar tekrar toplanir. */\nexport function ConvergeLoader({ className, size = \"md\", label = \"Loading\", ...props }: Props) {\n  return (\n    <div\n      data-slot=\"styled-loader\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block text-brand\", box[size], className)}\n      {...props}\n    >\n      {ANGLES.map((deg) => (\n        <span\n          key={deg}\n          className=\"absolute inset-0\"\n          style={{ transform: `rotate(${deg}deg)` }}\n        >\n          <motion.span\n            className=\"absolute left-1/2 top-1/2 size-1/5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-current\"\n            animate={{ y: [\"-230%\", \"0%\", \"-230%\"], opacity: [0.3, 1, 0.3] }}\n            transition={{ duration: 1.5, ease: \"easeInOut\", repeat: Infinity }}\n          />\n        </span>\n      ))}\n    </div>\n  )\n}\n\n/* Trail: lider nokta + kademeli gecikmeli iz noktalari daire boyunca. */\nexport function TrailLoader({ className, size = \"md\", label = \"Loading\", ...props }: Props) {\n  const trail = [0, 1, 2, 3, 4]\n  return (\n    <div\n      data-slot=\"styled-loader\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block text-success\", box[size], className)}\n      {...props}\n    >\n      {trail.map((i) => (\n        <motion.span\n          key={i}\n          className=\"absolute inset-0\"\n          animate={{ rotate: 360 }}\n          transition={{ duration: 1.2, ease: \"linear\", repeat: Infinity, delay: i * -0.1 }}\n        >\n          <span\n            className=\"absolute left-1/2 top-0 -translate-x-1/2 rounded-full bg-current\"\n            style={{ width: `${26 - i * 4}%`, height: `${26 - i * 4}%`, opacity: 1 - i * 0.18 }}\n          />\n        </motion.span>\n      ))}\n    </div>\n  )\n}\n\n/* Firefly: birkac nokta sabit pozisyonlarda kademeli sonup yanar. */\nexport function FireflyLoader({ className, size = \"md\", label = \"Loading\", ...props }: Props) {\n  const reduce = useReducedMotion()\n  const spots = [\n    { top: \"10%\", left: \"20%\" },\n    { top: \"25%\", left: \"70%\" },\n    { top: \"60%\", left: \"35%\" },\n    { top: \"70%\", left: \"80%\" },\n    { top: \"45%\", left: \"12%\" },\n    { top: \"82%\", left: \"55%\" },\n  ]\n  return (\n    <div\n      data-slot=\"styled-loader\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block text-warning\", box[size], className)}\n      {...props}\n    >\n      {spots.map((s, i) => (\n        <motion.span\n          key={i}\n          className=\"absolute size-1 rounded-full bg-current\"\n          style={{ top: s.top, left: s.left }}\n          animate={{ opacity: [0, 1, 0], scale: [0.5, 1, 0.5] }}\n          transition={{ duration: 1.6, ease: \"easeInOut\", repeat: Infinity, delay: reduce ? 0 : i * 0.24 }}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/loaders-particle.tsx"
    }
  ],
  "categories": [
    "styled",
    "loaders"
  ],
  "type": "registry:component"
}