{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-dots",
  "type": "registry:ui",
  "title": "Aurora Dots",
  "description": "A captivating background effect featuring softly glowing, animated dots that mimic the ethereal beauty of the aurora borealis.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/aurora-dots.tsx",
      "content": "'use client';\nimport React, { useEffect, useRef } from 'react';\nimport { cn } from '@/lib/utils';\n\ninterface Cluster {\n  cx: number;\n  cy: number;\n  radius: number;\n  count: number;\n}\n\ninterface Particle {\n  x: number;\n  y: number;\n  baseOpacity: number;\n  phase: number;\n}\n\ninterface AuroraDotsProps {\n  particleColor?: string;\n  particleSize?: number;\n  glowIntensity?: number;\n  hoverGlowIntensity?: number;\n  animationSpeed?: number;\n  hoverRadius?: number;\n  interactive?: boolean;\n  clusters?: Cluster[];\n  className?: string;\n  children?: React.ReactNode;\n}\n\nexport function AuroraDots({\n  particleColor = '34, 211, 238',\n  particleSize = 2,\n  glowIntensity = 0.3,\n  hoverGlowIntensity = 0.5,\n  animationSpeed = 3,\n  hoverRadius = 10,\n  interactive = true,\n  clusters = [\n    { cx: 20, cy: 15, radius: 8, count: 45 },\n    { cx: 45, cy: 12, radius: 6, count: 35 },\n    { cx: 70, cy: 18, radius: 10, count: 60 },\n    { cx: 85, cy: 14, radius: 7, count: 40 },\n    { cx: 15, cy: 35, radius: 9, count: 50 },\n    { cx: 35, cy: 40, radius: 7, count: 42 },\n    { cx: 55, cy: 38, radius: 8, count: 48 },\n    { cx: 75, cy: 35, radius: 6, count: 38 },\n    { cx: 88, cy: 40, radius: 7, count: 40 },\n    { cx: 10, cy: 60, radius: 8, count: 45 },\n    { cx: 30, cy: 58, radius: 9, count: 52 },\n    { cx: 50, cy: 62, radius: 7, count: 42 },\n    { cx: 68, cy: 60, radius: 10, count: 58 },\n    { cx: 85, cy: 65, radius: 8, count: 46 },\n    { cx: 18, cy: 82, radius: 7, count: 40 },\n    { cx: 42, cy: 85, radius: 8, count: 48 },\n    { cx: 65, cy: 80, radius: 9, count: 50 },\n    { cx: 82, cy: 88, radius: 6, count: 35 },\n  ],\n  className,\n  children,\n}: AuroraDotsProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const particlesRef = useRef<Particle[]>([]);\n  const mouseRef = useRef({ x: -1000, y: -1000 });\n  const animationRef = useRef<number | undefined>(undefined);\n  const autoHoverPosRef = useRef({ x: 0.5, y: 0.5, angle: 0 });\n  const isVisibleRef = useRef(true);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        isVisibleRef.current = entry.isIntersecting;\n        if (entry.isIntersecting && !animationRef.current) {\n          animate();\n        }\n      },\n      { threshold: 0 },\n    );\n\n    observer.observe(container);\n\n    const resizeCanvas = () => {\n      const rect = container.getBoundingClientRect();\n      canvas.width = rect.width;\n      canvas.height = rect.height;\n    };\n\n    resizeCanvas();\n    window.addEventListener('resize', resizeCanvas);\n\n    const particles: Particle[] = [];\n    clusters.forEach((cluster) => {\n      for (let i = 0; i < cluster.count; i++) {\n        const angle = (i / cluster.count) * Math.PI * 2;\n        const radiusVariation = Math.random() * cluster.radius;\n        const angleOffset = (Math.random() - 0.5) * 0.5;\n        const x =\n          (cluster.cx + Math.cos(angle + angleOffset) * radiusVariation) / 100;\n        const y =\n          (cluster.cy + Math.sin(angle + angleOffset) * radiusVariation) / 100;\n\n        particles.push({\n          x,\n          y,\n          baseOpacity: 0.2 + Math.random() * 0.1,\n          phase: Math.random() * Math.PI * 2,\n        });\n      }\n    });\n\n    particlesRef.current = particles;\n\n    const [r, g, b] = particleColor.split(',').map(Number);\n    const startTime = Date.now();\n    const fpsInterval = 1000 / 30;\n    let lastFrameTime = Date.now();\n\n    const animate = () => {\n      if (!isVisibleRef.current) {\n        animationRef.current = undefined;\n        return;\n      }\n\n      const now = Date.now();\n      const elapsed = now - lastFrameTime;\n\n      if (elapsed > fpsInterval) {\n        lastFrameTime = now - (elapsed % fpsInterval);\n        const totalElapsed = (now - startTime) / 1000;\n\n        autoHoverPosRef.current.angle += 0.01;\n        const radius = 0.3;\n        autoHoverPosRef.current.x =\n          0.5 + Math.cos(autoHoverPosRef.current.angle) * radius;\n        autoHoverPosRef.current.y =\n          0.5 + Math.sin(autoHoverPosRef.current.angle) * radius;\n\n        ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n        particles.forEach((particle) => {\n          const px = particle.x * canvas.width;\n          const py = particle.y * canvas.height;\n\n          const wave = Math.sin(totalElapsed / animationSpeed + particle.phase);\n          let opacity =\n            particle.baseOpacity + wave * glowIntensity + glowIntensity;\n          let size = particleSize;\n          let blur = particleSize * 3;\n          let glowAlpha = glowIntensity;\n\n          const autoHoverX = autoHoverPosRef.current.x * canvas.width;\n          const autoHoverY = autoHoverPosRef.current.y * canvas.height;\n          const autoDx = px - autoHoverX;\n          const autoDy = py - autoHoverY;\n          const autoDistance = Math.sqrt(autoDx * autoDx + autoDy * autoDy);\n          const autoNormalizedDistance =\n            autoDistance / ((canvas.width * hoverRadius) / 100);\n\n          if (autoNormalizedDistance < 1) {\n            const factor = 1 - autoNormalizedDistance;\n            opacity = Math.min(1, opacity + factor * 0.6);\n            size *= 1 + factor * 0.5;\n            blur = size * 5;\n            glowAlpha = Math.min(1, glowAlpha + factor * hoverGlowIntensity);\n          }\n\n          if (interactive) {\n            const dx = px - mouseRef.current.x;\n            const dy = py - mouseRef.current.y;\n            const distance = Math.sqrt(dx * dx + dy * dy);\n            const normalizedDistance =\n              distance / ((canvas.width * hoverRadius) / 100);\n\n            if (normalizedDistance < 1) {\n              const factor = 1 - normalizedDistance;\n              opacity = Math.min(1, opacity + factor * 0.5);\n              size *= 1 + factor * 0.4;\n              blur = size * 5;\n              glowAlpha = hoverGlowIntensity;\n            }\n          }\n\n          ctx.save();\n          ctx.shadowBlur = blur;\n          ctx.shadowColor = `rgba(${r}, ${g}, ${b}, ${glowAlpha})`;\n          ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${Math.min(\n            1,\n            Math.max(0, opacity),\n          )})`;\n          ctx.beginPath();\n          ctx.arc(px, py, size / 2, 0, Math.PI * 2);\n          ctx.fill();\n          ctx.restore();\n        });\n      }\n\n      animationRef.current = requestAnimationFrame(animate);\n    };\n\n    animate();\n\n    return () => {\n      window.removeEventListener('resize', resizeCanvas);\n      observer.disconnect();\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current);\n      }\n    };\n  }, [\n    clusters,\n    particleColor,\n    particleSize,\n    glowIntensity,\n    hoverGlowIntensity,\n    animationSpeed,\n    hoverRadius,\n    interactive,\n  ]);\n\n  const handleMouseMove = (e: React.MouseEvent) => {\n    if (!interactive || !containerRef.current) return;\n\n    const rect = containerRef.current.getBoundingClientRect();\n    mouseRef.current = {\n      x: e.clientX - rect.left,\n      y: e.clientY - rect.top,\n    };\n  };\n\n  const handleMouseLeave = () => {\n    mouseRef.current = { x: -1000, y: -1000 };\n  };\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn('relative w-full h-full', className)}\n      onMouseMove={handleMouseMove}\n      onMouseLeave={handleMouseLeave}\n    >\n      <canvas\n        ref={canvasRef}\n        className='absolute inset-0 w-full h-full pointer-events-none'\n      />\n      {children && (\n        <div className='relative z-10 w-full h-full'>{children}</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}"
    }
  ]
}
