{
  "$schema": "https://www.cooki-ui.design/schema/registry-item.json",
  "name": "particle-constellation-background",
  "type": "registry:ui",
  "title": "Particle Constellation Background",
  "description": "Interactive HTML5 canvas constellation network that links floating particle nodes with glowing lines when the cursor gets close.",
  "category": "backgrounds",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "registry/backgrounds/particle-constellation-background/particle-constellation-background.tsx",
      "type": "registry:ui",
      "target": "components/ui/particle-constellation-background.tsx",
      "content": "import React, { useEffect, useRef } from 'react';\n\nexport interface ParticleConstellationBackgroundProps {\n  particleCount?: number;\n  connectionDistance?: number;\n  className?: string;\n}\n\nexport const ParticleConstellationBackground: React.FC<ParticleConstellationBackgroundProps> = ({\n  particleCount = 50,\n  connectionDistance = 110,\n  className = '',\n}) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    let width = (canvas.width = canvas.parentElement?.clientWidth || 400);\n    let height = (canvas.height = canvas.parentElement?.clientHeight || 260);\n\n    const particles: { x: number; y: number; vx: number; vy: number; radius: number }[] = [];\n    for (let i = 0; i < particleCount; i++) {\n      particles.push({\n        x: Math.random() * width,\n        y: Math.random() * height,\n        vx: (Math.random() - 0.5) * 0.9,\n        vy: (Math.random() - 0.5) * 0.9,\n        radius: Math.random() * 2 + 1,\n      });\n    }\n\n    let mouse = { x: -1000, y: -1000 };\n    const handleMouseMove = (e: MouseEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      mouse = { x: e.clientX - rect.left, y: e.clientY - rect.top };\n    };\n\n    let animId: number;\n    let isVisible = true;\n    const render = () => {\n      if (!isVisible) return;\n      ctx.clearRect(0, 0, width, height);\n\n      // Draw connections\n      for (let i = 0; i < particles.length; i++) {\n        for (let j = i + 1; j < particles.length; j++) {\n          const dx = particles[i].x - particles[j].x;\n          const dy = particles[i].y - particles[j].y;\n          const dist = Math.sqrt(dx * dx + dy * dy);\n\n          if (dist < connectionDistance) {\n            const alpha = 1 - dist / connectionDistance;\n            ctx.strokeStyle = `rgba(99, 102, 241, ${alpha * 0.35})`;\n            ctx.lineWidth = 1;\n            ctx.beginPath();\n            ctx.moveTo(particles[i].x, particles[i].y);\n            ctx.lineTo(particles[j].x, particles[j].y);\n            ctx.stroke();\n          }\n        }\n\n        // Mouse connection\n        const mdx = particles[i].x - mouse.x;\n        const mdy = particles[i].y - mouse.y;\n        const mdist = Math.sqrt(mdx * mdx + mdy * mdy);\n        if (mdist < 140) {\n          const alpha = 1 - mdist / 140;\n          ctx.strokeStyle = `rgba(56, 189, 248, ${alpha * 0.6})`;\n          ctx.beginPath();\n          ctx.moveTo(particles[i].x, particles[i].y);\n          ctx.lineTo(mouse.x, mouse.y);\n          ctx.stroke();\n        }\n      }\n\n      // Draw and move particles\n      for (let p of particles) {\n        ctx.fillStyle = '#818cf8';\n        ctx.beginPath();\n        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);\n        ctx.fill();\n\n        p.x += p.vx;\n        p.y += p.vy;\n\n        if (p.x < 0 || p.x > width) p.vx *= -1;\n        if (p.y < 0 || p.y > height) p.vy *= -1;\n      }\n\n      animId = requestAnimationFrame(render);\n    };\n\n    animId = requestAnimationFrame(render);\n\n    const observer = new IntersectionObserver(([entry]) => {\n      const wasVisible = isVisible;\n      isVisible = entry.isIntersecting;\n      if (isVisible && !wasVisible) {\n        animId = requestAnimationFrame(render);\n      }\n    }, { threshold: 0.05 });\n    observer.observe(canvas);\n\n    canvas.addEventListener('mousemove', handleMouseMove);\n\n    const handleResize = () => {\n      if (!canvas.parentElement) return;\n      width = canvas.width = canvas.parentElement.clientWidth;\n      height = canvas.height = canvas.parentElement.clientHeight;\n    };\n    window.addEventListener('resize', handleResize);\n\n    return () => {\n      cancelAnimationFrame(animId);\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      window.removeEventListener('resize', handleResize);\n    };\n  }, [particleCount, connectionDistance]);\n\n  return (\n    <div className={`relative w-full h-[260px] rounded-3xl overflow-hidden bg-zinc-950 border border-white/10 ${className}`}>\n      <canvas ref={canvasRef} className=\"absolute inset-0 w-full h-full block\" />\n      <div className=\"absolute bottom-3 left-4 pointer-events-none text-[11px] font-mono text-zinc-400\">\n        Interactive Constellation Mesh\n      </div>\n    </div>\n  );\n};\n"
    }
  ]
}