{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "photon-beam",
  "title": "photon-beam",
  "description": "A photon beam background component with animated light trails.",
  "dependencies": [
    "react",
    "three"
  ],
  "files": [
    {
      "path": "registry/eldoraui/photon-beam.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport * as THREE from \"three\"\nimport { EffectComposer } from \"three/examples/jsm/postprocessing/EffectComposer.js\"\nimport { RenderPass } from \"three/examples/jsm/postprocessing/RenderPass.js\"\nimport { UnrealBloomPass } from \"three/examples/jsm/postprocessing/UnrealBloomPass.js\"\n\ninterface Signal {\n  mesh: THREE.Line\n  laneIndex: number\n  speed: number\n  progress: number\n  history: THREE.Vector3[]\n  assignedColor: THREE.Color\n}\n\ninterface Params {\n  colorBg: string\n  colorLine: string\n  colorSignal: string\n  useColor2: boolean\n  colorSignal2: string\n  useColor3: boolean\n  colorSignal3: string\n  lineCount: number\n  globalRotation: number\n  positionX: number\n  positionY: number\n  spreadHeight: number\n  spreadDepth: number\n  curveLength: number\n  straightLength: number\n  curvePower: number\n  waveSpeed: number\n  waveHeight: number\n  lineOpacity: number\n  signalCount: number\n  speedGlobal: number\n  trailLength: number\n  bloomStrength: number\n  bloomRadius: number\n}\n\ninterface PhotonBeamProps {\n  colorBg?: string\n  colorLine?: string\n  colorSignal?: string\n  useColor2?: boolean\n  colorSignal2?: string\n  useColor3?: boolean\n  colorSignal3?: string\n  lineCount?: number\n  spreadHeight?: number\n  spreadDepth?: number\n  curveLength?: number\n  straightLength?: number\n  curvePower?: number\n  waveSpeed?: number\n  waveHeight?: number\n  lineOpacity?: number\n  signalCount?: number\n  speedGlobal?: number\n  trailLength?: number\n  bloomStrength?: number\n  bloomRadius?: number\n}\n\nconst CONSTANTS = {\n  segmentCount: 150,\n}\n\nexport default function PhotonBeam(props: PhotonBeamProps = {}) {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    const container = containerRef.current\n    if (!container) return\n\n    let frameId: number\n    let cancelled = false\n    let renderer: THREE.WebGLRenderer | null = null\n    let composer: InstanceType<typeof EffectComposer> | null = null\n    let handleResize: (() => void) | null = null\n    let backgroundLines: THREE.Line[] = []\n    let signals: Signal[] = []\n\n    const init = (): void => {\n      if (cancelled) return\n\n      const width = container.clientWidth\n      const height = container.clientHeight\n      if (width === 0 || height === 0) {\n        frameId = requestAnimationFrame(init)\n        return\n      }\n\n      // --- CONFIGURATION ---\n      const params: Params = {\n        colorBg: props.colorBg ?? \"#080808\",\n        colorLine: props.colorLine ?? \"#005f6f\",\n        colorSignal: props.colorSignal ?? \"#00d9ff\",\n        useColor2: props.useColor2 ?? false,\n        colorSignal2: props.colorSignal2 ?? \"#00ffff\",\n        useColor3: props.useColor3 ?? false,\n        colorSignal3: props.colorSignal3 ?? \"#00b8d4\",\n        lineCount: props.lineCount ?? 80,\n        globalRotation: 0,\n        positionX: 0,\n        positionY: 0,\n        spreadHeight: props.spreadHeight ?? 30.33,\n        spreadDepth: props.spreadDepth ?? 0,\n        curveLength: props.curveLength ?? 50,\n        straightLength: props.straightLength ?? 100,\n        curvePower: props.curvePower ?? 0.8265,\n        waveSpeed: props.waveSpeed ?? 2.48,\n        waveHeight: props.waveHeight ?? 0.145,\n        lineOpacity: props.lineOpacity ?? 0.557,\n        signalCount: props.signalCount ?? 94,\n        speedGlobal: props.speedGlobal ?? 0.345,\n        trailLength: props.trailLength ?? 3,\n        bloomStrength: props.bloomStrength ?? 3.0,\n        bloomRadius: props.bloomRadius ?? 0.5,\n      }\n\n      params.positionX = (params.curveLength - params.straightLength) / 2\n\n      // --- SCENE SETUP ---\n      const scene = new THREE.Scene()\n      scene.background = new THREE.Color(params.colorBg)\n      scene.fog = new THREE.FogExp2(params.colorBg, 0.002)\n\n      const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000)\n      camera.position.set(0, 0, 90)\n      camera.lookAt(0, 0, 0)\n\n      renderer = new THREE.WebGLRenderer({ antialias: true })\n      renderer.setSize(width, height)\n      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n      container.appendChild(renderer.domElement)\n\n      const contentGroup = new THREE.Group()\n      contentGroup.position.set(params.positionX, params.positionY, 0)\n      scene.add(contentGroup)\n\n      // --- POST-PROCESSING ---\n      const renderScene = new RenderPass(scene, camera)\n      const bloomPass = new UnrealBloomPass(\n        new THREE.Vector2(width, height),\n        1.5,\n        0.4,\n        0.85\n      )\n      bloomPass.threshold = 0\n      bloomPass.strength = params.bloomStrength\n      bloomPass.radius = params.bloomRadius\n\n      composer = new EffectComposer(renderer)\n      composer.addPass(renderScene)\n      composer.addPass(bloomPass)\n\n      // --- MATH & PATH CALCULATION ---\n      function getPathPoint(\n        t: number,\n        lineIndex: number,\n        time: number\n      ): THREE.Vector3 {\n        const totalLen = params.curveLength + params.straightLength\n        const currentX = -params.curveLength + t * totalLen\n\n        let y = 0\n        let z = 0\n        const spreadFactor = (lineIndex / params.lineCount - 0.5) * 2\n\n        if (currentX < 0) {\n          const ratio = (currentX + params.curveLength) / params.curveLength\n          let shapeFactor = (Math.cos(ratio * Math.PI) + 1) / 2\n          shapeFactor = Math.pow(shapeFactor, params.curvePower)\n\n          y = spreadFactor * params.spreadHeight * shapeFactor\n          z = spreadFactor * params.spreadDepth * shapeFactor\n\n          const waveFactor = shapeFactor\n          const wave =\n            Math.sin(time * params.waveSpeed + currentX * 0.1 + lineIndex) *\n            params.waveHeight *\n            waveFactor\n          y += wave\n        }\n\n        return new THREE.Vector3(currentX, y, z)\n      }\n\n      // --- OBJECTS MANAGEMENT ---\n      backgroundLines = []\n      signals = []\n\n      const bgMaterial = new THREE.LineBasicMaterial({\n        color: params.colorLine,\n        transparent: true,\n        opacity: params.lineOpacity,\n        depthWrite: false,\n      })\n\n      const signalMaterial = new THREE.LineBasicMaterial({\n        vertexColors: true,\n        blending: THREE.AdditiveBlending,\n        depthWrite: false,\n        depthTest: false,\n        transparent: true,\n      })\n\n      const signalColorObj1 = new THREE.Color(params.colorSignal)\n      const signalColorObj2 = new THREE.Color(params.colorSignal2)\n      const signalColorObj3 = new THREE.Color(params.colorSignal3)\n\n      function pickSignalColor(): THREE.Color {\n        const choices: THREE.Color[] = [signalColorObj1]\n        if (params.useColor2) choices.push(signalColorObj2)\n        if (params.useColor3) choices.push(signalColorObj3)\n        return choices[Math.floor(Math.random() * choices.length)]\n      }\n\n      function createSignal(): void {\n        const maxTrail = 150\n        const geometry = new THREE.BufferGeometry()\n        const positions = new Float32Array(maxTrail * 3)\n        const colors = new Float32Array(maxTrail * 3)\n\n        geometry.setAttribute(\n          \"position\",\n          new THREE.BufferAttribute(positions, 3)\n        )\n        geometry.setAttribute(\"color\", new THREE.BufferAttribute(colors, 3))\n\n        const mesh = new THREE.Line(geometry, signalMaterial)\n        mesh.frustumCulled = false\n        mesh.renderOrder = 1\n        contentGroup.add(mesh)\n\n        signals.push({\n          mesh: mesh,\n          laneIndex: Math.floor(Math.random() * params.lineCount),\n          speed: 0.2 + Math.random() * 0.5,\n          progress: Math.random(),\n          history: [],\n          assignedColor: pickSignalColor(),\n        })\n      }\n\n      function rebuildSignals(): void {\n        signals.forEach((s) => {\n          contentGroup.remove(s.mesh)\n          s.mesh.geometry.dispose()\n        })\n        signals = []\n        for (let i = 0; i < params.signalCount; i++) {\n          createSignal()\n        }\n      }\n\n      function rebuildLines(): void {\n        backgroundLines.forEach((l) => {\n          contentGroup.remove(l)\n          l.geometry.dispose()\n        })\n        backgroundLines = []\n\n        for (let i = 0; i < params.lineCount; i++) {\n          const geometry = new THREE.BufferGeometry()\n          const positions = new Float32Array(CONSTANTS.segmentCount * 3)\n          geometry.setAttribute(\n            \"position\",\n            new THREE.BufferAttribute(positions, 3)\n          )\n\n          const line = new THREE.Line(geometry, bgMaterial)\n          line.userData = { id: i }\n          line.renderOrder = 0\n          contentGroup.add(line)\n          backgroundLines.push(line)\n        }\n        rebuildSignals()\n      }\n\n      // Initial Build\n      rebuildLines()\n\n      // --- ANIMATION LOOP ---\n      const clock = new THREE.Clock()\n\n      function animate(): void {\n        if (cancelled) return\n        frameId = requestAnimationFrame(animate)\n\n        const time = clock.getElapsedTime()\n\n        // Update Lines\n        backgroundLines.forEach((line) => {\n          const positions = line.geometry.attributes.position\n            .array as Float32Array\n          const lineId = line.userData.id\n          for (let j = 0; j < CONSTANTS.segmentCount; j++) {\n            const t = j / (CONSTANTS.segmentCount - 1)\n            const vec = getPathPoint(t, lineId, time)\n            positions[j * 3] = vec.x\n            positions[j * 3 + 1] = vec.y\n            positions[j * 3 + 2] = vec.z\n          }\n          line.geometry.attributes.position.needsUpdate = true\n        })\n\n        // Update Signals\n        signals.forEach((sig) => {\n          sig.progress += sig.speed * 0.005 * params.speedGlobal\n\n          if (sig.progress > 1.0) {\n            sig.progress = 0\n            sig.laneIndex = Math.floor(Math.random() * params.lineCount)\n            sig.history = []\n            sig.assignedColor = pickSignalColor()\n          }\n\n          const pos = getPathPoint(sig.progress, sig.laneIndex, time)\n          sig.history.push(pos)\n\n          if (sig.history.length > params.trailLength + 1) {\n            sig.history.shift()\n          }\n\n          const positions = sig.mesh.geometry.attributes.position\n            .array as Float32Array\n          const colors = sig.mesh.geometry.attributes.color\n            .array as Float32Array\n\n          const drawCount = Math.max(1, params.trailLength)\n          const currentLen = sig.history.length\n\n          for (let i = 0; i < drawCount; i++) {\n            let index = currentLen - 1 - i\n            if (index < 0) index = 0\n\n            const p = sig.history[index] || new THREE.Vector3()\n\n            positions[i * 3] = p.x\n            positions[i * 3 + 1] = p.y\n            positions[i * 3 + 2] = p.z\n\n            let alpha = 1\n            if (params.trailLength > 0) {\n              alpha = Math.max(0, 1 - i / params.trailLength)\n            }\n\n            colors[i * 3] = sig.assignedColor.r * alpha\n            colors[i * 3 + 1] = sig.assignedColor.g * alpha\n            colors[i * 3 + 2] = sig.assignedColor.b * alpha\n          }\n\n          sig.mesh.geometry.setDrawRange(0, drawCount)\n          sig.mesh.geometry.attributes.position.needsUpdate = true\n          sig.mesh.geometry.attributes.color.needsUpdate = true\n        })\n\n        if (composer) composer.render()\n      }\n\n      // Resize Handler\n      handleResize = (): void => {\n        if (!container || cancelled || !renderer || !composer) return\n        const w = container.clientWidth\n        const h = container.clientHeight\n        if (w === 0 || h === 0) return\n\n        camera.aspect = w / h\n        camera.updateProjectionMatrix()\n        renderer.setSize(w, h)\n        composer.setSize(w, h)\n      }\n\n      window.addEventListener(\"resize\", handleResize)\n      animate()\n    }\n\n    frameId = requestAnimationFrame(init)\n\n    return () => {\n      cancelled = true\n      cancelAnimationFrame(frameId)\n      if (handleResize) {\n        window.removeEventListener(\"resize\", handleResize)\n      }\n      if (containerRef.current && renderer?.domElement) {\n        try {\n          containerRef.current.removeChild(renderer.domElement)\n        } catch {\n          /* element may already be removed */\n        }\n        renderer.dispose()\n      }\n      composer?.dispose()\n      backgroundLines.forEach((l) => l.geometry.dispose())\n      signals.forEach((s) => s.mesh.geometry.dispose())\n    }\n  }, [])\n\n  return (\n    <div ref={containerRef} className=\"h-full min-h-[200px] w-full bg-black\" />\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}