{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Particles-JS-TW",
	"title": "Particles",
	"description": "Configurable particle system.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Particles/Particles.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nconst defaultColors = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = hex => {\n  hex = hex.replace(/^#/, '');\n  if (hex.length === 3) {\n    hex = hex\n      .split('')\n      .map(c => c + c)\n      .join('');\n  }\n  const int = parseInt(hex.slice(0, 6), 16);\n  const r = ((int >> 16) & 255) / 255;\n  const g = ((int >> 8) & 255) / 255;\n  const b = (int & 255) / 255;\n  return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n  attribute vec3 position;\n  attribute vec4 random;\n  attribute vec3 color;\n  \n  uniform mat4 modelMatrix;\n  uniform mat4 viewMatrix;\n  uniform mat4 projectionMatrix;\n  uniform float uTime;\n  uniform float uSpread;\n  uniform float uBaseSize;\n  uniform float uSizeRandomness;\n  \n  varying vec4 vRandom;\n  varying vec3 vColor;\n  \n  void main() {\n    vRandom = random;\n    vColor = color;\n    \n    vec3 pos = position * uSpread;\n    pos.z *= 10.0;\n    \n    vec4 mPos = modelMatrix * vec4(pos, 1.0);\n    float t = uTime;\n    mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n    mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n    mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n    \n    vec4 mvPos = viewMatrix * mPos;\n\n    if (uSizeRandomness == 0.0) {\n      gl_PointSize = uBaseSize;\n    } else {\n      gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n    }\n\n    gl_Position = projectionMatrix * mvPos;\n  }\n`;\n\nconst fragment = /* glsl */ `\n  precision highp float;\n  \n  uniform float uTime;\n  uniform float uAlphaParticles;\n  varying vec4 vRandom;\n  varying vec3 vColor;\n  \n  void main() {\n    vec2 uv = gl_PointCoord.xy;\n    float d = length(uv - vec2(0.5));\n    \n    if(uAlphaParticles < 0.5) {\n      if(d > 0.5) {\n        discard;\n      }\n      gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n    } else {\n      float circle = smoothstep(0.5, 0.4, d) * 0.8;\n      gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n    }\n  }\n`;\n\nconst Particles = ({\n  particleCount = 200,\n  particleSpread = 10,\n  speed = 0.1,\n  particleColors,\n  moveParticlesOnHover = false,\n  particleHoverFactor = 1,\n  alphaParticles = false,\n  particleBaseSize = 100,\n  sizeRandomness = 1,\n  cameraDistance = 20,\n  disableRotation = false,\n  pixelRatio = 1,\n  className\n}) => {\n  const containerRef = useRef(null);\n  const mouseRef = useRef({ x: 0, y: 0 });\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      dpr: pixelRatio,\n      depth: false,\n      alpha: true\n    });\n    const gl = renderer.gl;\n    container.appendChild(gl.canvas);\n    gl.clearColor(0, 0, 0, 0);\n\n    const camera = new Camera(gl, { fov: 15 });\n    camera.position.set(0, 0, cameraDistance);\n\n    const resize = () => {\n      const width = container.clientWidth;\n      const height = container.clientHeight;\n      renderer.setSize(width, height);\n      camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n    };\n    window.addEventListener('resize', resize, false);\n    resize();\n\n    const handleMouseMove = e => {\n      const rect = container.getBoundingClientRect();\n      const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n      const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n      mouseRef.current = { x, y };\n    };\n\n    if (moveParticlesOnHover) {\n      container.addEventListener('mousemove', handleMouseMove);\n    }\n\n    const count = particleCount;\n    const positions = new Float32Array(count * 3);\n    const randoms = new Float32Array(count * 4);\n    const colors = new Float32Array(count * 3);\n    const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n    for (let i = 0; i < count; i++) {\n      let x, y, z, len;\n      do {\n        x = Math.random() * 2 - 1;\n        y = Math.random() * 2 - 1;\n        z = Math.random() * 2 - 1;\n        len = x * x + y * y + z * z;\n      } while (len > 1 || len === 0);\n      const r = Math.cbrt(Math.random());\n      positions.set([x * r, y * r, z * r], i * 3);\n      randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n      const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n      colors.set(col, i * 3);\n    }\n\n    const geometry = new Geometry(gl, {\n      position: { size: 3, data: positions },\n      random: { size: 4, data: randoms },\n      color: { size: 3, data: colors }\n    });\n\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms: {\n        uTime: { value: 0 },\n        uSpread: { value: particleSpread },\n        uBaseSize: { value: particleBaseSize * pixelRatio },\n        uSizeRandomness: { value: sizeRandomness },\n        uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n      },\n      transparent: true,\n      depthTest: false\n    });\n\n    const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n    let animationFrameId;\n    let lastTime = performance.now();\n    let elapsed = 0;\n\n    const update = t => {\n      animationFrameId = requestAnimationFrame(update);\n      const delta = t - lastTime;\n      lastTime = t;\n      elapsed += delta * speed;\n\n      program.uniforms.uTime.value = elapsed * 0.001;\n\n      if (moveParticlesOnHover) {\n        particles.position.x = -mouseRef.current.x * particleHoverFactor;\n        particles.position.y = -mouseRef.current.y * particleHoverFactor;\n      } else {\n        particles.position.x = 0;\n        particles.position.y = 0;\n      }\n\n      if (!disableRotation) {\n        particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n        particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n        particles.rotation.z += 0.01 * speed;\n      }\n\n      renderer.render({ scene: particles, camera });\n    };\n\n    animationFrameId = requestAnimationFrame(update);\n\n    return () => {\n      window.removeEventListener('resize', resize);\n      if (moveParticlesOnHover) {\n        container.removeEventListener('mousemove', handleMouseMove);\n      }\n      cancelAnimationFrame(animationFrameId);\n      if (container.contains(gl.canvas)) {\n        container.removeChild(gl.canvas);\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    particleCount,\n    particleSpread,\n    speed,\n    moveParticlesOnHover,\n    particleHoverFactor,\n    alphaParticles,\n    particleBaseSize,\n    sizeRandomness,\n    cameraDistance,\n    disableRotation,\n    pixelRatio\n  ]);\n\n  return <div ref={containerRef} className={`relative w-full h-full ${className}`} />;\n};\n\nexport default Particles;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}