{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "PrismaticBurst-JS-CSS",
	"title": "PrismaticBurst",
	"description": "Burst of light rays with controllable color, distortion, amount.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "PrismaticBurst.css",
			"target": "@components/PrismaticBurst.css",
			"content": ".prismatic-burst-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "PrismaticBurst.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\nimport './PrismaticBurst.css';\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n    vUv = uv;\n    gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2  uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int   uAnimType;\nuniform vec2  uMouse;\nuniform int   uColorCount;\nuniform float uDistort;\nuniform vec2  uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int   uRayCount;\nuniform float uLightMode;\n\nfloat hash21(vec2 p){\n    p = floor(p);\n    float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n    return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n    vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n    vec2 q = rot30() * p;\n    float n = 0.0;\n    n += 0.40 * hash21(q);\n    n += 0.25 * hash21(q * 2.0 + 17.0);\n    n += 0.20 * hash21(q * 4.0 + 47.0);\n    n += 0.10 * hash21(q * 8.0 + 113.0);\n    n += 0.05 * hash21(q * 16.0 + 191.0);\n    return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n    float focal = res.y * max(dist, 1e-3);\n    return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n    vec2 toC = frag - 0.5 * res - offset;\n    float r = length(toC) / (0.5 * min(res.x, res.y));\n    float x = clamp(r, 0.0, 1.0);\n    float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n    float s = q * 0.5;\n    s = pow(s, 1.5);\n    float tail = 1.0 - pow(1.0 - s, 2.0);\n    s = mix(s, tail, 0.2);\n    float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n    return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n    t = clamp(t, 0.0, 1.0);\n    return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n    float s = sin(a), c = cos(a);\n    return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n    float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n            + 0.7 * sin(q.y * 0.50 - t * 0.5)\n            + 0.6 * sin(q.z * 0.60 + t * 0.7);\n    return a;\n}\n\nvoid main(){\n    vec2 frag = gl_FragCoord.xy;\n    float t = uTime * uSpeed;\n    float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n    vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n    float marchT = 0.0;\n    vec3 col = vec3(0.0);\n    float n = layeredNoise(frag);\n    vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n    mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n    float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n    mat3 rot3dMat = mat3(1.0);\n    if(uAnimType == 1){\n      vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n      rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n    }\n    mat3 hoverMat = mat3(1.0);\n    if(uAnimType == 2){\n      vec2 m = uMouse * 2.0 - 1.0;\n      vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n      hoverMat = rotY(ang.y) * rotX(ang.x);\n    }\n\n    for (int i = 0; i < 44; ++i) {\n        vec3 P = marchT * dir;\n        P.z -= 2.0;\n        float rad = length(P);\n        vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n        if(uAnimType == 0){\n            Pl.xz *= M2;\n        } else if(uAnimType == 1){\n      Pl = rot3dMat * Pl;\n        } else {\n      Pl = hoverMat * Pl;\n        }\n\n        float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n        float grow = smoothstep(0.35, 3.0, marchT);\n        float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n        float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n        vec3 Pb = Pl;\n        Pb.xz = rot2(Pb.xz, a1);\n        Pb.xy = rot2(Pb.xy, a2);\n\n        float rayPattern = smoothstep(\n            0.5, 0.7,\n            sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n            sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n        );\n\n        if (uRayCount > 0) {\n            float ang = atan(Pb.y, Pb.x);\n            float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n            comb = pow(comb, 3.0);\n            rayPattern *= smoothstep(0.15, 0.95, comb);\n        }\n\n        vec3 spectralDefault = 1.0 + vec3(\n            cos(marchT * 3.0 + 0.0),\n            cos(marchT * 3.0 + 1.0),\n            cos(marchT * 3.0 + 2.0)\n        );\n\n        float saw = fract(marchT * 0.25);\n        float tRay = saw * saw * (3.0 - 2.0 * saw);\n        vec3 userGradient = 2.0 * sampleGradient(tRay);\n        vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n        vec3 base = (0.05 / (0.4 + stepLen))\n                  * smoothstep(5.0, 0.0, rad)\n                  * spectral;\n\n        col += base * rayPattern;\n        marchT += stepLen;\n    }\n\n    col *= edgeFade(frag, uResolution, uOffset);\n    col *= uIntensity;\n\n    col = clamp(col, 0.0, 1.0);\n    if (uLightMode > 0.5) {\n        float energy = max(max(col.r, col.g), col.b);\n        vec3 hue = col / max(energy, 0.0001);\n        float neutral = min(hue.r, min(hue.g, hue.b));\n        hue = max(hue - vec3(neutral * 0.68), vec3(0.0));\n        hue /= max(max(hue.r, max(hue.g, hue.b)), 0.0001);\n        vec3 pigment = mix(hue, hue * hue, 0.24) * 0.64;\n        float coverage = smoothstep(0.001, 0.32, energy);\n        coverage = pow(coverage, 0.72) * 0.92;\n        col = mix(vec3(1.0), pigment, coverage);\n    }\n    fragColor = vec4(col, 1.0);\n}`;\n\nconst hexToRgb01 = hex => {\n  let h = hex.trim();\n  if (h.startsWith('#')) h = h.slice(1);\n  if (h.length === 3) {\n    const r = h[0],\n      g = h[1],\n      b = h[2];\n    h = r + r + g + g + b + b;\n  }\n  const intVal = parseInt(h.slice(0, 6), 16);\n  if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n  const r = ((intVal >> 16) & 255) / 255;\n  const g = ((intVal >> 8) & 255) / 255;\n  const b = (intVal & 255) / 255;\n  return [r, g, b];\n};\n\nconst toPx = v => {\n  if (v == null) return 0;\n  if (typeof v === 'number') return v;\n  const s = String(v).trim();\n  const num = parseFloat(s.replace('px', ''));\n  return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n  intensity = 2,\n  speed = 0.5,\n  animationType = 'rotate3d',\n  colors,\n  distort = 0,\n  paused = false,\n  offset = { x: 0, y: 0 },\n  hoverDampness = 0,\n  rayCount,\n  mixBlendMode = 'lighten',\n  lightMode = false\n}) => {\n  const containerRef = useRef(null);\n  const programRef = useRef(null);\n  const rendererRef = useRef(null);\n  const mouseTargetRef = useRef([0.5, 0.5]);\n  const mouseSmoothRef = useRef([0.5, 0.5]);\n  const pausedRef = useRef(paused);\n  const gradTexRef = useRef(null);\n  const hoverDampRef = useRef(hoverDampness);\n  const isVisibleRef = useRef(true);\n  const meshRef = useRef(null);\n  const triRef = useRef(null);\n\n  useEffect(() => {\n    pausedRef.current = paused;\n  }, [paused]);\n  useEffect(() => {\n    hoverDampRef.current = hoverDampness;\n  }, [hoverDampness]);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const dpr = Math.min(window.devicePixelRatio || 1, 2);\n    const renderer = new Renderer({\n      dpr,\n      alpha: false,\n      antialias: false\n    });\n    rendererRef.current = renderer;\n\n    const gl = renderer.gl;\n    gl.canvas.style.position = 'absolute';\n    gl.canvas.style.inset = '0';\n    gl.canvas.style.width = '100%';\n    gl.canvas.style.height = '100%';\n    gl.canvas.style.mixBlendMode = lightMode ? 'normal' : mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n    container.appendChild(gl.canvas);\n\n    const white = new Uint8Array([255, 255, 255, 255]);\n    const gradientTex = new Texture(gl, {\n      image: white,\n      width: 1,\n      height: 1,\n      generateMipmaps: false,\n      flipY: false\n    });\n\n    gradientTex.minFilter = gl.LINEAR;\n    gradientTex.magFilter = gl.LINEAR;\n    gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n    gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n    gradTexRef.current = gradientTex;\n\n    const program = new Program(gl, {\n      vertex: vertexShader,\n      fragment: fragmentShader,\n      uniforms: {\n        uResolution: { value: [1, 1] },\n        uTime: { value: 0 },\n\n        uIntensity: { value: 1 },\n        uSpeed: { value: 1 },\n        uAnimType: { value: 0 },\n        uMouse: { value: [0.5, 0.5] },\n        uColorCount: { value: 0 },\n        uDistort: { value: 0 },\n        uOffset: { value: [0, 0] },\n        uGradient: { value: gradientTex },\n        uNoiseAmount: { value: 0.8 },\n        uRayCount: { value: 0 },\n        uLightMode: { value: lightMode ? 1 : 0 }\n      }\n    });\n\n    programRef.current = program;\n\n    const triangle = new Triangle(gl);\n    const mesh = new Mesh(gl, { geometry: triangle, program });\n    triRef.current = triangle;\n    meshRef.current = mesh;\n\n    const resize = () => {\n      const w = container.clientWidth || 1;\n      const h = container.clientHeight || 1;\n      renderer.setSize(w, h);\n      program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n    };\n\n    let ro = null;\n    if ('ResizeObserver' in window) {\n      ro = new ResizeObserver(resize);\n      ro.observe(container);\n    } else {\n      window.addEventListener('resize', resize);\n    }\n    resize();\n\n    const onPointer = e => {\n      const rect = container.getBoundingClientRect();\n      const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n      const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n      mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n    };\n    container.addEventListener('pointermove', onPointer, { passive: true });\n\n    let io = null;\n    if ('IntersectionObserver' in window) {\n      io = new IntersectionObserver(\n        entries => {\n          if (entries[0]) {\n            isVisibleRef.current = entries[0].isIntersecting;\n          }\n        },\n        { root: null, threshold: 0.01 }\n      );\n      io.observe(container);\n    }\n\n    const onVis = () => {};\n    document.addEventListener('visibilitychange', onVis);\n\n    let raf = 0;\n    let last = performance.now();\n    let accumTime = 0;\n\n    const update = now => {\n      const dt = Math.max(0, now - last) * 0.001;\n      last = now;\n      const visible = isVisibleRef.current && !document.hidden;\n      if (!pausedRef.current) accumTime += dt;\n\n      if (!visible) {\n        raf = requestAnimationFrame(update);\n        return;\n      }\n\n      const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n      const alpha = 1 - Math.exp(-dt / tau);\n      const tgt = mouseTargetRef.current;\n      const sm = mouseSmoothRef.current;\n      sm[0] += (tgt[0] - sm[0]) * alpha;\n      sm[1] += (tgt[1] - sm[1]) * alpha;\n\n      program.uniforms.uMouse.value = sm;\n      program.uniforms.uTime.value = accumTime;\n\n      renderer.render({ scene: meshRef.current });\n      raf = requestAnimationFrame(update);\n    };\n    raf = requestAnimationFrame(update);\n\n    return () => {\n      cancelAnimationFrame(raf);\n      container.removeEventListener('pointermove', onPointer);\n      ro?.disconnect();\n      if (!ro) window.removeEventListener('resize', resize);\n      io?.disconnect();\n      document.removeEventListener('visibilitychange', onVis);\n      try {\n        container.removeChild(gl.canvas);\n      } catch {\n        console.warn('Canvas already removed');\n      }\n      try {\n        meshRef.current?.remove?.();\n      } catch (e) {\n        /* ignore dispose errors */\n      }\n      try {\n        triRef.current?.remove?.();\n      } catch (e) {\n        /* ignore dispose errors */\n      }\n      try {\n        programRef.current?.remove?.();\n      } catch (e) {\n        /* ignore dispose errors */\n      }\n      try {\n        const glCtx = rendererRef.current?.gl;\n        if (glCtx && gradTexRef.current?.texture) {\n          glCtx.deleteTexture(gradTexRef.current.texture);\n        }\n      } catch (e) {\n        /* ignore texture delete errors */\n      }\n      programRef.current = null;\n      rendererRef.current = null;\n      gradTexRef.current = null;\n      meshRef.current = null;\n      triRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    const canvas = rendererRef.current?.gl?.canvas;\n\n    if (canvas) {\n      canvas.style.mixBlendMode = lightMode\n        ? 'normal'\n        : mixBlendMode && mixBlendMode !== 'none'\n          ? mixBlendMode\n          : '';\n    }\n  }, [mixBlendMode, lightMode]);\n\n  useEffect(() => {\n    const program = programRef.current;\n    const renderer = rendererRef.current;\n    const gradTex = gradTexRef.current;\n    if (!program || !renderer || !gradTex) return;\n\n    program.uniforms.uIntensity.value = intensity ?? 1;\n    program.uniforms.uSpeed.value = speed ?? 1;\n\n    const animTypeMap = {\n      rotate: 0,\n      rotate3d: 1,\n      hover: 2\n    };\n    program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n    program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n    const ox = toPx(offset?.x);\n    const oy = toPx(offset?.y);\n    program.uniforms.uOffset.value = [ox, oy];\n    program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n    program.uniforms.uLightMode.value = lightMode ? 1 : 0;\n\n    let count = 0;\n    if (Array.isArray(colors) && colors.length > 0) {\n      const gl = renderer.gl;\n      const capped = colors.slice(0, 64);\n      count = capped.length;\n      const data = new Uint8Array(count * 4);\n      for (let i = 0; i < count; i++) {\n        const [r, g, b] = hexToRgb01(capped[i]);\n        data[i * 4 + 0] = Math.round(r * 255);\n        data[i * 4 + 1] = Math.round(g * 255);\n        data[i * 4 + 2] = Math.round(b * 255);\n        data[i * 4 + 3] = 255;\n      }\n      gradTex.image = data;\n      gradTex.width = count;\n      gradTex.height = 1;\n      gradTex.minFilter = gl.LINEAR;\n      gradTex.magFilter = gl.LINEAR;\n      gradTex.wrapS = gl.CLAMP_TO_EDGE;\n      gradTex.wrapT = gl.CLAMP_TO_EDGE;\n      gradTex.flipY = false;\n      gradTex.generateMipmaps = false;\n      gradTex.format = gl.RGBA;\n      gradTex.type = gl.UNSIGNED_BYTE;\n      gradTex.needsUpdate = true;\n    } else {\n      count = 0;\n    }\n    program.uniforms.uColorCount.value = count;\n  }, [intensity, speed, animationType, colors, distort, offset, rayCount, lightMode]);\n\n  return <div className=\"prismatic-burst-container\" ref={containerRef} />;\n};\n\nexport default PrismaticBurst;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}