{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particles",
  "type": "registry:ui",
  "title": "Particles",
  "description": "Dynamic 3D particle system with customizable properties",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["three", "@types/three"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/particles.tsx",
      "content": "\"use client\";\nimport { useEffect, useRef } from \"react\";\nimport * as THREE from \"three\";\n\ninterface ParticlesProps {\n  color?: string;\n  particleCount?: number;\n  particleSize?: number;\n  animate?: boolean;\n  className?: string;\n}\n\nexport function Particles({\n  color = \"#ff3366\",\n  particleCount = 10000,\n  particleSize = 35,\n  animate = true,\n  className = \"\",\n}: ParticlesProps) {\n  const mountRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const container = mountRef.current;\n    if (!container) return;\n\n    let camera: THREE.PerspectiveCamera;\n    let scene: THREE.Scene;\n    let material: THREE.PointsMaterial;\n    let animationFrameId: number;\n    let mouseX = 0;\n    let mouseY = 0;\n\n    const init = () => {\n      camera = new THREE.PerspectiveCamera(\n        55,\n        window.innerWidth / window.innerHeight,\n        2,\n        2000\n      );\n      camera.position.z = 1000;\n\n      scene = new THREE.Scene();\n      scene.fog = new THREE.FogExp2(0x000000, 0.001);\n\n      const geometry = new THREE.BufferGeometry();\n      const vertices: number[] = [];\n\n      for (let i = 0; i < particleCount; i++) {\n        vertices.push(\n          2000 * Math.random() - 1000,\n          2000 * Math.random() - 1000,\n          2000 * Math.random() - 1000\n        );\n      }\n\n      geometry.setAttribute(\n        \"position\",\n        new THREE.Float32BufferAttribute(vertices, 3)\n      );\n\n      const sprite = new THREE.TextureLoader().load(\"/assets/disc.png\");\n      material = new THREE.PointsMaterial({\n        size: particleSize,\n        sizeAttenuation: true,\n        map: sprite,\n        alphaTest: 0.5,\n        transparent: true,\n      });\n      material.color.setStyle(color);\n\n      const particles = new THREE.Points(geometry, material);\n      scene.add(particles);\n\n      const renderer = new THREE.WebGLRenderer({\n        antialias: true,\n        alpha: true,\n      });\n      renderer.setPixelRatio(window.devicePixelRatio);\n      renderer.setSize(window.innerWidth, window.innerHeight);\n      container.appendChild(renderer.domElement);\n\n      return renderer;\n    };\n\n    const handleResize = () => {\n      if (!camera || !renderer) return;\n      camera.aspect = window.innerWidth / window.innerHeight;\n      camera.updateProjectionMatrix();\n      renderer.setSize(window.innerWidth, window.innerHeight);\n    };\n\n    const handlePointerMove = (event: PointerEvent) => {\n      if (event.isPrimary) {\n        mouseX = event.clientX - window.innerWidth / 2;\n        mouseY = event.clientY - window.innerHeight / 2;\n      }\n    };\n\n    const animateScene = () => {\n      if (!camera || !scene || !renderer || !material) return;\n\n      if (animate) {\n        const time = Date.now() * 0.00005;\n        const h = ((360 * (1.0 + time)) % 360) / 360;\n        material.color.setHSL(h, 0.5, 0.5);\n      }\n\n      camera.position.x += (mouseX - camera.position.x) * 0.05;\n      camera.position.y += (-mouseY - camera.position.y) * 0.05;\n      camera.lookAt(scene.position);\n\n      renderer.render(scene, camera);\n      animationFrameId = requestAnimationFrame(animateScene);\n    };\n\n    const renderer = init();\n    window.addEventListener(\"resize\", handleResize);\n    window.addEventListener(\"pointermove\", handlePointerMove);\n    animateScene();\n\n    return () => {\n      window.removeEventListener(\"resize\", handleResize);\n      window.removeEventListener(\"pointermove\", handlePointerMove);\n      cancelAnimationFrame(animationFrameId);\n\n      if (renderer) {\n        renderer.dispose();\n        container.removeChild(renderer.domElement);\n      }\n\n      if (material) material.dispose();\n    };\n  }, [color, particleCount, particleSize, animate]);\n\n  return (\n    <div\n      ref={mountRef}\n      className={`absolute top-0 left-0 w-full h-full pointer-events-none ${className}`}\n    />\n  );\n}\n"
    }
  ]
}
