{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "side-rays",
  "type": "registry:block",
  "title": "Side rays",
  "description": "Side rays",
  "files": [
    {
      "path": "components/usages/sideraysusage.tsx",
      "content": "import SideRays from \"@/registry/open-source/side-rays\";\n\nexport default function Usage() {\n    return (\n        <div>\n            <SideRays\n                speed={2.5}\n                rayColor1=\"#EAB308\"\n                rayColor2=\"#96c8ff\"\n                intensity={2}\n                spread={2}\n                origin=\"top-right\"\n                tilt={0}\n                saturation={1.5}\n                blend={0.75}\n                falloff={1.6}\n                opacity={1}\n            />\t\t</div>\n    );\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/sideraysusage.tsx",
      "content": "import SideRays from \"@/registry/open-source/side-rays\";\n\nexport default function Usage() {\n    return (\n        <div>\n            <SideRays\n                speed={2.5}\n                rayColor1=\"#EAB308\"\n                rayColor2=\"#96c8ff\"\n                intensity={2}\n                spread={2}\n                origin=\"top-right\"\n                tilt={0}\n                saturation={1.5}\n                blend={0.75}\n                falloff={1.6}\n                opacity={1}\n            />\t\t</div>\n    );\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/side-rays.tsx",
      "content": "import { useRef, useEffect, useState } from 'react';\nimport { Renderer, Program, Triangle, Mesh } from 'ogl';\n\n// Credit:\n// https://www.reactbits.dev/backgrounds/side-rays\n\ntype Origin = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';\n\ninterface SideRaysProps {\n    speed?: number;\n    rayColor1?: string;\n    rayColor2?: string;\n    intensity?: number;\n    spread?: number;\n    origin?: Origin;\n    tilt?: number;\n    saturation?: number;\n    blend?: number;\n    falloff?: number;\n    opacity?: number;\n    className?: string;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n    const m = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n    return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1];\n};\n\nconst originToFlip = (origin: Origin): [number, number] => {\n    switch (origin) {\n        case 'top-left': return [1, 0];\n        case 'bottom-right': return [0, 1];\n        case 'bottom-left': return [1, 1];\n        default: return [0, 0];\n    }\n};\n\nconst SideRays = ({\n    speed = 2.5,\n    rayColor1 = '#EAB308',\n    rayColor2 = '#96c8ff',\n    intensity = 2,\n    spread = 2,\n    origin = 'top-right',\n    tilt = 0,\n    saturation = 1.5,\n    blend = 0.75,\n    falloff = 2.0,\n    opacity = 1.0,\n    className = ''\n}: SideRaysProps) => {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const uniformsRef = useRef<Record<string, { value: number | number[] }> | null>(null);\n    const rendererRef = useRef<Renderer | null>(null);\n    const animationIdRef = useRef<number | null>(null);\n    const meshRef = useRef<Mesh | null>(null);\n    const cleanupFunctionRef = useRef<(() => void) | null>(null);\n    const [isVisible, setIsVisible] = useState(false);\n    const observerRef = useRef<IntersectionObserver | null>(null);\n\n    useEffect(() => {\n        if (!containerRef.current) return;\n\n        observerRef.current = new IntersectionObserver(\n            entries => {\n                const entry = entries[0];\n                setIsVisible(entry.isIntersecting);\n            },\n            { threshold: 0.1 }\n        );\n\n        observerRef.current.observe(containerRef.current);\n\n        return () => {\n            if (observerRef.current) {\n                observerRef.current.disconnect();\n                observerRef.current = null;\n            }\n        };\n    }, []);\n\n    useEffect(() => {\n        if (!isVisible || !containerRef.current) return;\n\n        if (cleanupFunctionRef.current) {\n            cleanupFunctionRef.current();\n            cleanupFunctionRef.current = null;\n        }\n\n        const initializeWebGL = async () => {\n            if (!containerRef.current) return;\n\n            await new Promise<void>(resolve => setTimeout(resolve, 10));\n\n            if (!containerRef.current) return;\n\n            const renderer = new Renderer({\n                dpr: Math.min(window.devicePixelRatio, 2),\n                alpha: true\n            });\n            rendererRef.current = renderer;\n\n            const gl = renderer.gl;\n            gl.canvas.style.width = '100%';\n            gl.canvas.style.height = '100%';\n\n            while (containerRef.current.firstChild) {\n                containerRef.current.removeChild(containerRef.current.firstChild);\n            }\n            containerRef.current.appendChild(gl.canvas);\n\n            const vert = `\nattribute vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}`;\n\n            const frag = `precision highp float;\n\nuniform float iTime;\nuniform vec2 iResolution;\nuniform float iSpeed;\nuniform vec3 iRayColor1;\nuniform vec3 iRayColor2;\nuniform float iIntensity;\nuniform float iSpread;\nuniform float iFlipX;\nuniform float iFlipY;\nuniform float iTilt;\nuniform float iSaturation;\nuniform float iBlend;\nuniform float iFalloff;\nuniform float iOpacity;\n\nfloat rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed) {\n  vec2 sourceToCoord = coord - raySource;\n  float cosAngle = dot(normalize(sourceToCoord), rayRefDirection);\n  return clamp(\n    (0.45 + 0.15 * sin(cosAngle * seedA + iTime * speed)) +\n    (0.3 + 0.2 * cos(-cosAngle * seedB + iTime * speed)),\n    0.0, 1.0) *\n    clamp((iResolution.x - length(sourceToCoord)) / iResolution.x, 0.5, 1.0);\n}\n\nvoid main() {\n  vec2 fragCoord = gl_FragCoord.xy;\n  if (iFlipX > 0.5) fragCoord.x = iResolution.x - fragCoord.x;\n  if (iFlipY > 0.5) fragCoord.y = iResolution.y - fragCoord.y;\n\n  vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);\n  vec2 rayPos = vec2(iResolution.x * 1.1, -0.5 * iResolution.y);\n\n  float tiltRad = iTilt * 3.14159265 / 180.0;\n  float cs = cos(tiltRad);\n  float sn = sin(tiltRad);\n  vec2 rel = coord - rayPos;\n  vec2 tiltedCoord = vec2(rel.x * cs - rel.y * sn, rel.x * sn + rel.y * cs) + rayPos;\n\n  float halfSpread = iSpread * 0.275;\n  vec2 rayRefDir1 = normalize(vec2(cos(0.785398 + halfSpread), sin(0.785398 + halfSpread)));\n  vec2 rayRefDir2 = normalize(vec2(cos(0.785398 - halfSpread), sin(0.785398 - halfSpread)));\n\n  vec4 rays1 = vec4(iRayColor1, 1.0) * rayStrength(rayPos, rayRefDir1, tiltedCoord, 36.2214, 21.11349, iSpeed);\n  vec4 rays2 = vec4(iRayColor2, 1.0) * rayStrength(rayPos, rayRefDir2, tiltedCoord, 22.3991, 18.0234, iSpeed * 0.2);\n\n  vec4 color = rays1 * (1.0 - iBlend) * 0.9 + rays2 * iBlend * 0.9;\n\n  float distanceToLight = length(fragCoord.xy - vec2(rayPos.x, iResolution.y - rayPos.y)) / iResolution.y;\n  float brightness = iIntensity * 0.4 / pow(max(distanceToLight, 0.001), iFalloff);\n  color.rgb *= brightness;\n\n  float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n  color.rgb = mix(vec3(gray), color.rgb, iSaturation);\n\n  color.a = max(color.r, max(color.g, color.b)) * iOpacity;\n  gl_FragColor = color;\n}`;\n\n            const [flipX, flipY] = originToFlip(origin);\n            const uniforms = {\n                iTime: { value: 0 },\n                iResolution: { value: [1, 1] as number[] },\n                iSpeed: { value: speed },\n                iRayColor1: { value: hexToRgb(rayColor1) as number[] },\n                iRayColor2: { value: hexToRgb(rayColor2) as number[] },\n                iIntensity: { value: intensity },\n                iSpread: { value: spread },\n                iFlipX: { value: flipX },\n                iFlipY: { value: flipY },\n                iTilt: { value: tilt },\n                iSaturation: { value: saturation },\n                iBlend: { value: blend },\n                iFalloff: { value: falloff },\n                iOpacity: { value: opacity }\n            };\n            uniformsRef.current = uniforms;\n\n            const geometry = new Triangle(gl);\n            const program = new Program(gl, { vertex: vert, fragment: frag, uniforms });\n            const mesh = new Mesh(gl, { geometry, program });\n            meshRef.current = mesh;\n\n            const updateSize = () => {\n                if (!containerRef.current || !renderer) return;\n                renderer.dpr = Math.min(window.devicePixelRatio, 2);\n                const { clientWidth: w, clientHeight: h } = containerRef.current;\n                renderer.setSize(w, h);\n                uniforms.iResolution.value = [w * renderer.dpr, h * renderer.dpr];\n            };\n\n            const loop = (t: number) => {\n                if (!rendererRef.current || !uniformsRef.current || !meshRef.current) return;\n                uniforms.iTime.value = t * 0.001;\n                try {\n                    renderer.render({ scene: mesh });\n                    animationIdRef.current = requestAnimationFrame(loop);\n                } catch (e) {\n                    return;\n                }\n            };\n\n            window.addEventListener('resize', updateSize);\n            updateSize();\n            animationIdRef.current = requestAnimationFrame(loop);\n\n            cleanupFunctionRef.current = () => {\n                if (animationIdRef.current) {\n                    cancelAnimationFrame(animationIdRef.current);\n                    animationIdRef.current = null;\n                }\n                window.removeEventListener('resize', updateSize);\n                if (renderer) {\n                    try {\n                        const loseCtx = renderer.gl.getExtension('WEBGL_lose_context');\n                        if (loseCtx) loseCtx.loseContext();\n                        const canvas = renderer.gl.canvas;\n                        if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n                    } catch (e) { }\n                }\n                rendererRef.current = null;\n                uniformsRef.current = null;\n                meshRef.current = null;\n            };\n        };\n\n        initializeWebGL();\n\n        return () => {\n            if (cleanupFunctionRef.current) {\n                cleanupFunctionRef.current();\n                cleanupFunctionRef.current = null;\n            }\n        };\n    }, [isVisible, speed, rayColor1, rayColor2, intensity, spread, origin, tilt, saturation, blend, falloff, opacity]);\n\n    useEffect(() => {\n        if (!uniformsRef.current) return;\n        const u = uniformsRef.current;\n        u.iSpeed.value = speed;\n        u.iRayColor1.value = hexToRgb(rayColor1);\n        u.iRayColor2.value = hexToRgb(rayColor2);\n        u.iIntensity.value = intensity;\n        u.iSpread.value = spread;\n        const [flipX, flipY] = originToFlip(origin);\n        u.iFlipX.value = flipX;\n        u.iFlipY.value = flipY;\n        u.iTilt.value = tilt;\n        u.iSaturation.value = saturation;\n        u.iBlend.value = blend;\n        u.iFalloff.value = falloff;\n        u.iOpacity.value = opacity;\n    }, [speed, rayColor1, rayColor2, intensity, spread, origin, tilt, saturation, blend, falloff, opacity]);\n\n    return (\n        <div\n            ref={containerRef}\n            className={`relative w-full h-full overflow-hidden pointer-events-none z-[3] ${className}`.trim()}\n        />\n    );\n};\n\nexport default SideRays;",
      "type": "registry:ui"
    }
  ]
}