{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LightRays-TS-CSS",
	"title": "LightRays",
	"description": "Volumetric light rays/beams with customizable direction.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "LightRays.css",
			"target": "@components/LightRays.css",
			"content": ".light-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": "LightRays.tsx",
			"content": "import { useRef, useEffect, useState } from 'react';\nimport { Renderer, Program, Triangle, Mesh } from 'ogl';\nimport './LightRays.css';\n\nexport type RaysOrigin =\n  | 'top-center'\n  | 'top-left'\n  | 'top-right'\n  | 'right'\n  | 'left'\n  | 'bottom-center'\n  | 'bottom-right'\n  | 'bottom-left';\n\ninterface LightRaysProps {\n  raysOrigin?: RaysOrigin;\n  raysColor?: string;\n  raysSpeed?: number;\n  lightSpread?: number;\n  rayLength?: number;\n  pulsating?: boolean;\n  fadeDistance?: number;\n  saturation?: number;\n  followMouse?: boolean;\n  mouseInfluence?: number;\n  noiseAmount?: number;\n  distortion?: number;\n  lightMode?: boolean;\n  className?: string;\n}\n\nconst DEFAULT_COLOR = '#ffffff';\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 getAnchorAndDir = (\n  origin: RaysOrigin,\n  w: number,\n  h: number\n): { anchor: [number, number]; dir: [number, number] } => {\n  const outside = 0.2;\n  switch (origin) {\n    case 'top-left':\n      return { anchor: [0, -outside * h], dir: [0, 1] };\n    case 'top-right':\n      return { anchor: [w, -outside * h], dir: [0, 1] };\n    case 'left':\n      return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };\n    case 'right':\n      return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };\n    case 'bottom-left':\n      return { anchor: [0, (1 + outside) * h], dir: [0, -1] };\n    case 'bottom-center':\n      return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };\n    case 'bottom-right':\n      return { anchor: [w, (1 + outside) * h], dir: [0, -1] };\n    default: // \"top-center\"\n      return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };\n  }\n};\n\ntype Vec2 = [number, number];\ntype Vec3 = [number, number, number];\n\ninterface Uniforms {\n  iTime: { value: number };\n  iResolution: { value: Vec2 };\n  rayPos: { value: Vec2 };\n  rayDir: { value: Vec2 };\n  raysColor: { value: Vec3 };\n  raysSpeed: { value: number };\n  lightSpread: { value: number };\n  rayLength: { value: number };\n  pulsating: { value: number };\n  fadeDistance: { value: number };\n  saturation: { value: number };\n  mousePos: { value: Vec2 };\n  mouseInfluence: { value: number };\n  noiseAmount: { value: number };\n  distortion: { value: number };\n  lightMode: { value: number };\n}\n\nconst LightRays: React.FC<LightRaysProps> = ({\n  raysOrigin = 'top-center',\n  raysColor = DEFAULT_COLOR,\n  raysSpeed = 1,\n  lightSpread = 1,\n  rayLength = 2,\n  pulsating = false,\n  fadeDistance = 1.0,\n  saturation = 1.0,\n  followMouse = true,\n  mouseInfluence = 0.1,\n  noiseAmount = 0.0,\n  distortion = 0.0,\n  lightMode = false,\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const uniformsRef = useRef<Uniforms | null>(null);\n  const rendererRef = useRef<Renderer | null>(null);\n  const mouseRef = useRef({ x: 0.5, y: 0.5 });\n  const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\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(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;\nvarying vec2 vUv;\nvoid main() {\n  vUv = position * 0.5 + 0.5;\n  gl_Position = vec4(position, 0.0, 1.0);\n}`;\n\n      const frag = `precision highp float;\n\nuniform float iTime;\nuniform vec2  iResolution;\n\nuniform vec2  rayPos;\nuniform vec2  rayDir;\nuniform vec3  raysColor;\nuniform float raysSpeed;\nuniform float lightSpread;\nuniform float rayLength;\nuniform float pulsating;\nuniform float fadeDistance;\nuniform float saturation;\nuniform vec2  mousePos;\nuniform float mouseInfluence;\nuniform float noiseAmount;\nuniform float distortion;\nuniform float lightMode;\n\nvarying vec2 vUv;\n\nfloat noise(vec2 st) {\n  return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);\n}\n\nfloat rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,\n                  float seedA, float seedB, float speed) {\n  vec2 sourceToCoord = coord - raySource;\n  vec2 dirNorm = normalize(sourceToCoord);\n  float cosAngle = dot(dirNorm, rayRefDirection);\n\n  float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;\n  \n  float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));\n\n  float distance = length(sourceToCoord);\n  float maxDistance = iResolution.x * rayLength;\n  float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);\n  \n  float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);\n  float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;\n\n  float baseStrength = clamp(\n    (0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +\n    (0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),\n    0.0, 1.0\n  );\n\n  return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n  vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);\n  \n  vec2 finalRayDir = rayDir;\n  if (mouseInfluence > 0.0) {\n    vec2 mouseScreenPos = mousePos * iResolution.xy;\n    vec2 mouseDirection = normalize(mouseScreenPos - rayPos);\n    finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));\n  }\n\n  vec4 rays1 = vec4(1.0) *\n               rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,\n                           1.5 * raysSpeed);\n  vec4 rays2 = vec4(1.0) *\n               rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,\n                           1.1 * raysSpeed);\n\n  fragColor = rays1 * 0.5 + rays2 * 0.4;\n\n  if (noiseAmount > 0.0) {\n    float n = noise(coord * 0.01 + iTime * 0.1);\n    fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);\n  }\n\n  float brightness = 1.0 - (coord.y / iResolution.y);\n  fragColor.x *= 0.1 + brightness * 0.8;\n  fragColor.y *= 0.3 + brightness * 0.6;\n  fragColor.z *= 0.5 + brightness * 0.5;\n\n  if (saturation != 1.0) {\n    float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));\n    fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);\n  }\n\n  fragColor.rgb *= raysColor;\n\n  if (lightMode > 0.5) {\n    vec3 mapped = vec3(1.0) - exp(-max(fragColor.rgb, vec3(0.0)) * 1.35);\n    float energy = clamp(max(mapped.r, max(mapped.g, mapped.b)), 0.0, 1.0);\n    vec3 hue = mapped / max(energy, 0.0001);\n    vec3 ink = mix(hue * 0.25, hue * 0.72, energy);\n    fragColor = vec4(mix(vec3(1.0), ink, energy), 1.0);\n  }\n}\n\nvoid main() {\n  vec4 color;\n  mainImage(color, gl_FragCoord.xy);\n  gl_FragColor  = color;\n}`;\n\n      const uniforms: Uniforms = {\n        iTime: { value: 0 },\n        iResolution: { value: [1, 1] },\n\n        rayPos: { value: [0, 0] },\n        rayDir: { value: [0, 1] },\n\n        raysColor: { value: hexToRgb(raysColor) },\n        raysSpeed: { value: raysSpeed },\n        lightSpread: { value: lightSpread },\n        rayLength: { value: rayLength },\n        pulsating: { value: pulsating ? 1.0 : 0.0 },\n        fadeDistance: { value: fadeDistance },\n        saturation: { value: saturation },\n        mousePos: { value: [0.5, 0.5] },\n        mouseInfluence: { value: mouseInfluence },\n        noiseAmount: { value: noiseAmount },\n        distortion: { value: distortion },\n        lightMode: { value: lightMode ? 1.0 : 0.0 }\n      };\n      uniformsRef.current = uniforms;\n\n      const geometry = new Triangle(gl);\n      const program = new Program(gl, {\n        vertex: vert,\n        fragment: frag,\n        uniforms\n      });\n      const mesh = new Mesh(gl, { geometry, program });\n      meshRef.current = mesh;\n\n      const updatePlacement = () => {\n        if (!containerRef.current || !renderer) return;\n\n        renderer.dpr = Math.min(window.devicePixelRatio, 2);\n\n        const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;\n        renderer.setSize(wCSS, hCSS);\n\n        const dpr = renderer.dpr;\n        const w = wCSS * dpr;\n        const h = hCSS * dpr;\n\n        uniforms.iResolution.value = [w, h];\n\n        const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);\n        uniforms.rayPos.value = anchor;\n        uniforms.rayDir.value = dir;\n      };\n\n      const loop = (t: number) => {\n        if (!rendererRef.current || !uniformsRef.current || !meshRef.current) {\n          return;\n        }\n\n        uniforms.iTime.value = t * 0.001;\n\n        if (followMouse && mouseInfluence > 0.0) {\n          const smoothing = 0.92;\n\n          smoothMouseRef.current.x = smoothMouseRef.current.x * smoothing + mouseRef.current.x * (1 - smoothing);\n          smoothMouseRef.current.y = smoothMouseRef.current.y * smoothing + mouseRef.current.y * (1 - smoothing);\n\n          uniforms.mousePos.value = [smoothMouseRef.current.x, smoothMouseRef.current.y];\n        }\n\n        try {\n          renderer.render({ scene: mesh });\n          animationIdRef.current = requestAnimationFrame(loop);\n        } catch (error) {\n          console.warn('WebGL rendering error:', error);\n          return;\n        }\n      };\n\n      window.addEventListener('resize', updatePlacement);\n      updatePlacement();\n      animationIdRef.current = requestAnimationFrame(loop);\n\n      cleanupFunctionRef.current = () => {\n        if (animationIdRef.current) {\n          cancelAnimationFrame(animationIdRef.current);\n          animationIdRef.current = null;\n        }\n\n        window.removeEventListener('resize', updatePlacement);\n\n        if (renderer) {\n          try {\n            const canvas = renderer.gl.canvas;\n            const loseContextExt = renderer.gl.getExtension('WEBGL_lose_context');\n            if (loseContextExt) {\n              loseContextExt.loseContext();\n            }\n\n            if (canvas && canvas.parentNode) {\n              canvas.parentNode.removeChild(canvas);\n            }\n          } catch (error) {\n            console.warn('Error during WebGL cleanup:', error);\n          }\n        }\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  }, [\n    isVisible,\n    raysOrigin,\n    raysColor,\n    raysSpeed,\n    lightSpread,\n    rayLength,\n    pulsating,\n    fadeDistance,\n    saturation,\n    followMouse,\n    mouseInfluence,\n    noiseAmount,\n    distortion,\n    lightMode\n  ]);\n\n  useEffect(() => {\n    if (!uniformsRef.current || !containerRef.current || !rendererRef.current) return;\n\n    const u = uniformsRef.current;\n    const renderer = rendererRef.current;\n\n    u.raysColor.value = hexToRgb(raysColor);\n    u.raysSpeed.value = raysSpeed;\n    u.lightSpread.value = lightSpread;\n    u.rayLength.value = rayLength;\n    u.pulsating.value = pulsating ? 1.0 : 0.0;\n    u.fadeDistance.value = fadeDistance;\n    u.saturation.value = saturation;\n    u.mouseInfluence.value = mouseInfluence;\n    u.noiseAmount.value = noiseAmount;\n    u.distortion.value = distortion;\n    u.lightMode.value = lightMode ? 1.0 : 0.0;\n\n    const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;\n    const dpr = renderer.dpr;\n    const { anchor, dir } = getAnchorAndDir(raysOrigin, wCSS * dpr, hCSS * dpr);\n    u.rayPos.value = anchor;\n    u.rayDir.value = dir;\n  }, [\n    raysColor,\n    raysSpeed,\n    lightSpread,\n    raysOrigin,\n    rayLength,\n    pulsating,\n    fadeDistance,\n    saturation,\n    mouseInfluence,\n    noiseAmount,\n    distortion,\n    lightMode\n  ]);\n\n  useEffect(() => {\n    const handleMouseMove = (e: MouseEvent) => {\n      if (!containerRef.current || !rendererRef.current) return;\n      const rect = containerRef.current.getBoundingClientRect();\n      const x = (e.clientX - rect.left) / rect.width;\n      const y = (e.clientY - rect.top) / rect.height;\n      mouseRef.current = { x, y };\n    };\n\n    if (followMouse) {\n      window.addEventListener('mousemove', handleMouseMove);\n      return () => window.removeEventListener('mousemove', handleMouseMove);\n    }\n  }, [followMouse]);\n\n  return <div ref={containerRef} className={`light-rays-container ${className}`.trim()} />;\n};\n\nexport default LightRays;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}