{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "venom-beam",
  "type": "registry:ui",
  "title": "Venom Beam",
  "description": "A glowing particle canvas with motion-reactive trails. Perfect for immersive, high-tech hero sections.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": [""],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/venom-beam.tsx",
      "content": "'use client';\n\nimport React, { useEffect, useRef } from 'react';\n\ninterface Particle {\n  x: number;\n  y: number;\n  vx: number;\n  vy: number;\n  life: number;\n  maxLife: number;\n  size: number;\n  opacity: number;\n}\n\ninterface VenomBeamProps {\n  children?: React.ReactNode;\n  className?: string;\n}\n\nconst VenomBeam: React.FC<VenomBeamProps> = ({ children, className = '' }) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const animationRef = useRef<number>(0);\n  const particlesRef = useRef<Particle[]>([]);\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const isDarkRef = useRef(false);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    const resizeCanvas = () => {\n      const rect = canvas.getBoundingClientRect();\n      canvas.width = rect.width;\n      canvas.height = rect.height;\n\n      const ctx = canvas.getContext('2d');\n      if (ctx) {\n        isDarkRef.current = document.documentElement.classList.contains('dark');\n        if (isDarkRef.current) {\n          ctx.fillStyle = '#000000';\n        } else {\n          ctx.fillStyle = '#f8f8ff';\n        }\n        ctx.fillRect(0, 0, canvas.width, canvas.height);\n      }\n    };\n\n    resizeCanvas();\n\n    let resizeTimeout: NodeJS.Timeout;\n    const handleResize = () => {\n      clearTimeout(resizeTimeout);\n      resizeTimeout = setTimeout(() => {\n        resizeCanvas();\n        initParticles();\n      }, 100);\n    };\n\n    window.addEventListener('resize', handleResize);\n\n    const initParticles = () => {\n      particlesRef.current = [];\n      for (let i = 0; i < 80; i++) {\n        particlesRef.current.push({\n          x: Math.random() * canvas.width,\n          y: Math.random() * canvas.height,\n          vx: (Math.random() - 0.5) * 2,\n          vy: (Math.random() - 0.5) * 2,\n          life: 0,\n          maxLife: Math.random() * 100 + 50,\n          size: Math.random() * 3 + 1,\n          opacity: Math.random() * 0.8 + 0.2,\n        });\n      }\n    };\n\n    initParticles();\n\n    const handleMouseMove = (e: MouseEvent) => {\n      mouseRef.current = {\n        x: e.clientX,\n        y: e.clientY,\n      };\n    };\n\n    canvas.addEventListener('mousemove', handleMouseMove);\n\n    const animate = () => {\n      isDarkRef.current = document.documentElement.classList.contains('dark');\n\n      if (isDarkRef.current) {\n        ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';\n      } else {\n        ctx.fillStyle = 'rgba(248, 248, 255, 0.1)';\n      }\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n      particlesRef.current.forEach((particle) => {\n        particle.x += particle.vx;\n        particle.y += particle.vy;\n        particle.life++;\n\n        const dx = mouseRef.current.x - particle.x;\n        const dy = mouseRef.current.y - particle.y;\n        const distance = Math.sqrt(dx * dx + dy * dy);\n\n        if (distance < 150) {\n          const force = (150 - distance) / 150;\n          particle.vx += (dx / distance) * force * 0.1;\n          particle.vy += (dy / distance) * force * 0.1;\n        }\n\n        particle.vx *= 0.99;\n        particle.vy *= 0.99;\n\n        if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -0.8;\n        if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -0.8;\n\n        particle.x = Math.max(0, Math.min(canvas.width, particle.x));\n        particle.y = Math.max(0, Math.min(canvas.height, particle.y));\n\n        if (particle.life > particle.maxLife) {\n          particle.x = Math.random() * canvas.width;\n          particle.y = Math.random() * canvas.height;\n          particle.vx = (Math.random() - 0.5) * 2;\n          particle.vy = (Math.random() - 0.5) * 2;\n          particle.life = 0;\n          particle.maxLife = Math.random() * 100 + 50;\n        }\n\n        const alpha = particle.opacity * (1 - particle.life / particle.maxLife);\n        ctx.beginPath();\n        ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);\n\n        const gradient = ctx.createRadialGradient(\n          particle.x,\n          particle.y,\n          0,\n          particle.x,\n          particle.y,\n          particle.size * 2,\n        );\n\n        if (isDarkRef.current) {\n          gradient.addColorStop(0, `rgba(200, 200, 255, ${alpha})`);\n          gradient.addColorStop(0.5, `rgba(150, 150, 200, ${alpha * 0.8})`);\n          gradient.addColorStop(1, `rgba(100, 100, 150, ${alpha * 0.3})`);\n        } else {\n          gradient.addColorStop(0, `rgba(60, 60, 120, ${alpha})`);\n          gradient.addColorStop(0.5, `rgba(80, 80, 140, ${alpha * 0.8})`);\n          gradient.addColorStop(1, `rgba(100, 100, 160, ${alpha * 0.3})`);\n        }\n\n        ctx.fillStyle = gradient;\n        ctx.fill();\n      });\n\n      particlesRef.current.forEach((particle, i) => {\n        particlesRef.current.slice(i + 1).forEach((otherParticle) => {\n          const dx = particle.x - otherParticle.x;\n          const dy = particle.y - otherParticle.y;\n          const distance = Math.sqrt(dx * dx + dy * dy);\n\n          if (distance < 100) {\n            const alpha = ((100 - distance) / 100) * 0.3;\n            ctx.beginPath();\n            ctx.moveTo(particle.x, particle.y);\n            ctx.lineTo(otherParticle.x, otherParticle.y);\n\n            if (isDarkRef.current) {\n              ctx.strokeStyle = `rgba(150, 150, 200, ${alpha})`;\n            } else {\n              ctx.strokeStyle = `rgba(80, 80, 140, ${alpha})`;\n            }\n            ctx.lineWidth = 0.5;\n            ctx.stroke();\n          }\n        });\n      });\n\n      animationRef.current = requestAnimationFrame(animate);\n    };\n\n    animate();\n\n    return () => {\n      window.removeEventListener('resize', handleResize);\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      if (resizeTimeout) clearTimeout(resizeTimeout);\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current);\n      }\n    };\n  }, []);\n\n  return (\n    <div className='relative  h-96 md:h-screen w-full overflow-hidden bg-white dark:bg-black'>\n      <canvas\n        ref={canvasRef}\n        className='absolute inset-0 w-full h-full bg-linear-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900'\n      />\n\n      <div className='absolute inset-0 bg-linear-to-b from-transparent via-white/20 to-white/60 dark:via-black/20 dark:to-black/60' />\n\n      <div className={`absolute inset-0 ${className}`}>{children}</div>\n\n      <div className='absolute top-20 left-10 w-2 h-2 bg-blue-600 dark:bg-blue-400 rounded-full animate-pulse opacity-60' />\n      <div className='absolute top-40 right-20 w-1 h-1 bg-purple-600 dark:bg-purple-400 rounded-full animate-pulse opacity-40' />\n      <div className='absolute bottom-32 left-1/4 w-1.5 h-1.5 bg-blue-500 dark:bg-blue-300 rounded-full animate-pulse opacity-50' />\n      <div className='absolute bottom-20 right-1/3 w-1 h-1 bg-purple-500 dark:bg-purple-300 rounded-full animate-pulse opacity-30' />\n    </div>\n  );\n};\n\nexport default VenomBeam;\n"
    }
  ]
}
