{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "photon-beam",
  "type": "registry:block",
  "title": "Photon beam",
  "description": "Photon beam",
  "files": [
    {
      "path": "components/usages/photonbeamusage.tsx",
      "content": "import { PhotonBeam } from \"@/registry/open-source/photon-beam\";\n\nexport default function PhotonBeamUsage() {\n    return (\n        <div className=\"relative h-[500px] w-full overflow-hidden rounded-lg border\">\n            <PhotonBeam\n                colorBg=\"#080808\"\n                colorLine=\"#005f6f\"\n                colorSignal=\"#00d9ff\"\n                colorSignal2=\"#00ffff\"\n                colorSignal3=\"#00b8d4\"\n                lineCount={80}\n                spreadHeight={30.33}\n                signalCount={94}\n                speedGlobal={0.345}\n                trailLength={3}\n                bloomStrength={3.0}\n                bloomRadius={0.5}\n            />\n        </div>\n    )\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/photonbeamusage.tsx",
      "content": "import { PhotonBeam } from \"@/registry/open-source/photon-beam\";\n\nexport default function PhotonBeamUsage() {\n    return (\n        <div className=\"relative h-[500px] w-full overflow-hidden rounded-lg border\">\n            <PhotonBeam\n                colorBg=\"#080808\"\n                colorLine=\"#005f6f\"\n                colorSignal=\"#00d9ff\"\n                colorSignal2=\"#00ffff\"\n                colorSignal3=\"#00b8d4\"\n                lineCount={80}\n                spreadHeight={30.33}\n                signalCount={94}\n                speedGlobal={0.345}\n                trailLength={3}\n                bloomStrength={3.0}\n                bloomRadius={0.5}\n            />\n        </div>\n    )\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/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 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"
    }
  ]
}