{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "SideRays-JS-CSS",
	"title": "SideRays",
	"description": "Animated light rays emanating from the side with customizable colors and speed.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "SideRays.css",
			"target": "@components/SideRays.css",
			"content": ".side-rays-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n  pointer-events: none;\n  z-index: 3;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "SideRays.jsx",
			"content": "import { useRef, useEffect, useState } from 'react';\nimport { Renderer, Program, Triangle, Mesh } from 'ogl';\nimport './SideRays.css';\n\nconst hexToRgb = hex => {\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 => {\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 = 1.6,\n  opacity = 1.0,\n  className = ''\n}) => {\n  const containerRef = useRef(null);\n  const uniformsRef = useRef(null);\n  const rendererRef = useRef(null);\n  const animationIdRef = useRef(null);\n  const meshRef = useRef(null);\n  const cleanupFunctionRef = useRef(null);\n  const [isVisible, setIsVisible] = useState(false);\n  const observerRef = useRef(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(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] },\n        iSpeed: { value: speed },\n        iRayColor1: { value: hexToRgb(rayColor1) },\n        iRayColor2: { value: hexToRgb(rayColor2) },\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 => {\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 <div ref={containerRef} className={`side-rays-container ${className}`.trim()} />;\n};\n\nexport default SideRays;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}