{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particle-field",
  "type": "registry:component",
  "title": "Particle Field",
  "description": "Canvas-based floating particles that connect with lines on hover",
  "dependencies": [],
  "files": [
    {
      "path": "components/kraavon-ui/ParticleField.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect, useRef } from \"react\";\n\ninterface ParticleFieldProps {\n  particleCount?: number;\n  color?: string;\n  speed?: number;\n  className?: string;\n  style?: React.CSSProperties;\n}\n\ninterface Particle {\n  x: number;\n  y: number;\n  vx: number;\n  vy: number;\n  radius: number;\n}\n\nexport default function ParticleField({\n  particleCount = 80,\n  color = \"#6366f1\",\n  speed = 0.5,\n  className,\n  style,\n}: ParticleFieldProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const animRef = useRef<number>(0);\n  const particlesRef = useRef<Particle[]>([]);\n  const mouseRef = useRef({ x: -1000, y: -1000 });\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    const resize = () => {\n      canvas.width = canvas.offsetWidth;\n      canvas.height = canvas.offsetHeight;\n    };\n    resize();\n    window.addEventListener(\"resize\", resize);\n\n    particlesRef.current = Array.from({ length: particleCount }, () => ({\n      x: Math.random() * canvas.width,\n      y: Math.random() * canvas.height,\n      vx: (Math.random() - 0.5) * speed,\n      vy: (Math.random() - 0.5) * speed,\n      radius: Math.random() * 2 + 1,\n    }));\n\n    const hex2rgba = (hex: string, alpha: number) => {\n      const r = parseInt(hex.slice(1, 3), 16);\n      const g = parseInt(hex.slice(3, 5), 16);\n      const b = parseInt(hex.slice(5, 7), 16);\n      return `rgba(${r},${g},${b},${alpha})`;\n    };\n\n    const draw = () => {\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      const particles = particlesRef.current;\n\n      particles.forEach((p) => {\n        p.x += p.vx;\n        p.y += p.vy;\n        if (p.x < 0 || p.x > canvas.width) p.vx *= -1;\n        if (p.y < 0 || p.y > canvas.height) p.vy *= -1;\n\n        ctx.beginPath();\n        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);\n        ctx.fillStyle = hex2rgba(color, 0.7);\n        ctx.fill();\n      });\n\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          if (dist < 120) {\n            ctx.beginPath();\n            ctx.moveTo(particles[i].x, particles[i].y);\n            ctx.lineTo(particles[j].x, particles[j].y);\n            ctx.strokeStyle = hex2rgba(color, 1 - dist / 120);\n            ctx.lineWidth = 0.6;\n            ctx.stroke();\n          }\n        }\n      }\n\n      animRef.current = requestAnimationFrame(draw);\n    };\n\n    draw();\n\n    const handleMouseMove = (e: MouseEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      mouseRef.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };\n    };\n    canvas.addEventListener(\"mousemove\", handleMouseMove);\n\n    return () => {\n      cancelAnimationFrame(animRef.current);\n      window.removeEventListener(\"resize\", resize);\n      canvas.removeEventListener(\"mousemove\", handleMouseMove);\n    };\n  }, [particleCount, color, speed]);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      className={className}\n      style={{ display: \"block\", width: \"100%\", height: \"100%\", ...style }}\n    />\n  );\n}\n",
      "type": "registry:component"
    }
  ]
}