{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "meteor-orbit",
  "type": "registry:ui",
  "title": "Meteor Orbit",
  "description": "orbit system featuring rotating meteors and gracefully animated icons.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/meteor-orbit.tsx",
      "content": "'use client';\n\nimport React, { useState } from 'react';\nimport { motion } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ninterface SocialIcon {\n  icon: React.ReactNode;\n  orbitIndex?: number;\n  position?: number;\n}\n\ninterface MeteorOrbitProps {\n  icons?: SocialIcon[];\n  children?: React.ReactNode;\n  rippleCount?: number;\n  meteorSpeed?: number;\n  size?: number;\n  className?: string;\n  meteorClassName?: string | string[];\n  meteorGradients?: [string, string][];\n}\n\nexport function MeteorOrbit({\n  icons = [],\n  children,\n  rippleCount = 5,\n  meteorSpeed = 4,\n  size = 500,\n  className,\n  meteorClassName = '',\n  meteorGradients = [],\n}: MeteorOrbitProps) {\n  const [animatedIcons, setAnimatedIcons] = useState<Set<number>>(new Set());\n  const uniqueId = React.useId();\n\n  React.useEffect(() => {\n    icons.forEach((_, index) => {\n      setTimeout(() => {\n        setAnimatedIcons((prev) => new Set([...prev, index]));\n      }, index * 150);\n    });\n  }, [icons]);\n\n  const baseInset = 40;\n  const rippleBoxes = Array.from({ length: rippleCount }, (_, i) => {\n    const insetPercent = baseInset - i * 8;\n    const radiusPercent = 50 - insetPercent;\n    return {\n      inset: `${insetPercent}%`,\n      radius: (size / 2) * (radiusPercent / 50),\n      zIndex: 99 - i,\n      delay: i * 0.3,\n      opacity: 1 - i * 0.15,\n    };\n  });\n\n  const calculatePosition = (\n    index: number,\n    total: number,\n    radius: number,\n    customAngle?: number,\n  ) => {\n    const angle =\n      customAngle !== undefined ? customAngle : (360 / total) * index;\n    const radian = (angle * Math.PI) / 180;\n    return { x: Math.cos(radian) * radius, y: Math.sin(radian) * radius };\n  };\n\n  const iconsByOrbit = icons.reduce(\n    (acc, icon, index) => {\n      const orbitIdx = icon.orbitIndex ?? 0;\n      if (!acc[orbitIdx]) acc[orbitIdx] = [];\n      acc[orbitIdx].push({ ...icon, originalIndex: index });\n      return acc;\n    },\n    {} as Record<number, Array<SocialIcon & { originalIndex: number }>>,\n  );\n\n  const normalizedMeteorClass = Array.isArray(meteorClassName)\n    ? meteorClassName\n    : [meteorClassName];\n\n  return (\n    <div\n      className={cn('relative', className)}\n      style={{ width: size, height: size }}\n    >\n      <div className='absolute inset-0'>\n        {rippleBoxes.map((box, i) => (\n          <div\n            key={`ripple-${i}`}\n            className='absolute rounded-full border-2 border-border/50'\n            style={{\n              width: box.radius * 2,\n              height: box.radius * 2,\n              left: '50%',\n              top: '50%',\n              marginLeft: -box.radius,\n              marginTop: -box.radius,\n              zIndex: box.zIndex,\n              opacity: box.opacity,\n              background: 'transparent',\n            }}\n          >\n            <motion.svg\n              className='absolute'\n              style={{\n                left: 0,\n                top: 0,\n                width: '100%',\n                height: '100%',\n                overflow: 'visible',\n              }}\n              viewBox={`0 0 ${box.radius * 2} ${box.radius * 2}`}\n              animate={{ rotate: [0, 360] }}\n              transition={{\n                duration: meteorSpeed + i * 0.5,\n                repeat: Infinity,\n                ease: 'linear',\n                delay: i * 0.2,\n              }}\n            >\n              <defs>\n                <linearGradient\n                  id={`${uniqueId}-gradient-${i}`}\n                  gradientUnits='userSpaceOnUse'\n                  x1={box.radius}\n                  y1={0}\n                  x2={box.radius + box.radius * Math.cos(Math.PI / 3)}\n                  y2={box.radius + box.radius * Math.sin(Math.PI / 3)}\n                >\n                  {meteorGradients[i] ? (\n                    <>\n                      <stop\n                        offset='0%'\n                        stopColor={`${meteorGradients[i][0]}00`}\n                      />\n                      <stop\n                        offset='60%'\n                        stopColor={`${meteorGradients[i][0]}99`}\n                      />\n                      <stop offset='100%' stopColor={meteorGradients[i][1]} />\n                    </>\n                  ) : (\n                    <>\n                      <stop offset='0%' stopColor='rgba(34,211,238,0)' />\n                      <stop offset='60%' stopColor='rgba(34,211,238,0.6)' />\n                      <stop offset='100%' stopColor='rgba(34,211,238,1)' />\n                    </>\n                  )}\n                </linearGradient>\n              </defs>\n              <path\n                d={`M ${box.radius} 0 A ${box.radius} ${box.radius} 0 0 1 ${\n                  box.radius + box.radius * Math.cos(Math.PI / 3)\n                } ${box.radius + box.radius * Math.sin(Math.PI / 3)}`}\n                stroke={`url(#${uniqueId}-gradient-${i})`}\n                strokeWidth='2.5'\n                fill='none'\n                strokeLinecap='round'\n                className={cn(\n                  normalizedMeteorClass[i % normalizedMeteorClass.length],\n                )}\n              />\n            </motion.svg>\n          </div>\n        ))}\n      </div>\n\n      {Object.entries(iconsByOrbit).map(([orbitIdx, orbitIcons]) => {\n        const orbitIndex = Math.min(parseInt(orbitIdx), rippleBoxes.length - 1);\n        const iconRippleRadius = rippleBoxes[orbitIndex].radius;\n        return (\n          <div\n            key={`orbit-${orbitIdx}`}\n            className='absolute'\n            style={{\n              width: iconRippleRadius * 2,\n              height: iconRippleRadius * 2,\n              left: '50%',\n              top: '50%',\n              marginLeft: -iconRippleRadius,\n              marginTop: -iconRippleRadius,\n              zIndex: 101 + parseInt(orbitIdx),\n            }}\n          >\n            {orbitIcons.map((social, localIndex) => {\n              const position = calculatePosition(\n                localIndex,\n                orbitIcons.length,\n                iconRippleRadius,\n                social.position,\n              );\n              const isAnimated = animatedIcons.has(social.originalIndex);\n              return (\n                <div\n                  key={`icon-${social.originalIndex}`}\n                  className='absolute'\n                  style={{\n                    left: '50%',\n                    top: '50%',\n                    marginLeft: -24,\n                    marginTop: -24,\n                    transform: isAnimated\n                      ? `translate(${position.x}px, ${position.y}px) scale(1)`\n                      : 'translate(0px,0px) scale(0)',\n                    transition:\n                      'transform 800ms cubic-bezier(0.34,1.56,0.64,1)',\n                    opacity: isAnimated ? 1 : 0,\n                  }}\n                >\n                  <motion.div\n                    className={cn(\n                      'flex items-center justify-center w-12 h-12 rounded-full bg-background text-foreground border border-border shadow-lg',\n                    )}\n                    whileHover={{ scale: 1.2 }}\n                  >\n                    {social.icon}\n                  </motion.div>\n                </div>\n              );\n            })}\n          </div>\n        );\n      })}\n\n      {children && (\n        <div className='absolute inset-0 flex items-center justify-center z-200 pointer-events-none'>\n          <motion.div\n            initial={{ opacity: 0, scale: 0.5 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ delay: 2, duration: 0.8, type: 'spring' }}\n          >\n            {children}\n          </motion.div>\n        </div>\n      )}\n    </div>\n  );\n}\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
