{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LightTunnel-JS-TW",
	"title": "LightTunnel",
	"description": "A radial fibre-optic tunnel with light pulses racing into depth.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "LightTunnel/LightTunnel.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  if (!result) return [1, 1, 1];\n  return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uSpeed;\nuniform float uFlowDir;\nuniform float uPulseSpeed;\nuniform float uPulseLength;\nuniform float uPulseBlend;\nuniform float uPulseWidth;\nuniform float uCableCount;\nuniform float uThickness;\nuniform float uRimWidth;\nuniform float uWaviness;\nuniform float uSway;\nuniform float uSize;\nuniform vec2 uCenter;\nuniform vec2 uMouseOffset;\nuniform float uGlow;\nuniform float uFadeNear;\nuniform float uFadeFar;\nuniform float uBrightness;\nuniform float uColorVariance;\nuniform float uOpacity;\nuniform vec3 uCableColor;\nuniform vec3 uPulseColor;\nuniform vec3 uTunnelColor;\nuniform float uTunnelOpacity;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform float uLightMode;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, in vec2 fragCoord) {\n  float size = uSize * 2.0;\n  float flowDir = uFlowDir;\n  float speedBase = uSpeed * 4.0 * flowDir;\n  float waviness = uWaviness * 0.15;\n  float rotationOsc = uSway * 0.5;\n  float baseThick = uThickness * 0.35 + 0.05;\n  float borderWeight = uRimWidth * 0.15 + 0.01;\n  float cablesCount = floor(uCableCount);\n\n  vec2 res = iResolution.xy;\n  vec2 uv = (fragCoord - 0.5 * res) / min(res.y, res.x);\n  uv -= (uCenter + uMouseOffset);\n  uv /= (size + 0.0001);\n\n  float r = length(uv);\n  float angle = atan(uv.y, uv.x);\n  float depth = -log(r + 0.0001);\n\n  float swing = sin(iTime * (uSpeed * 0.5 + 0.1)) * rotationOsc;\n  float waveOffset = sin(depth * 1.2 + iTime * speedBase * 0.25) * waviness;\n\n  float angleNormalized = (angle / 6.2831853) + 0.5;\n  float finalAngle = fract(angleNormalized + waveOffset + swing);\n\n  float cableID = floor(finalAngle * cablesCount);\n  float gvX = (fract(finalAngle * cablesCount) - 0.5);\n\n  float rand = fract(sin(cableID * 12.9898) * 43758.5453);\n  float randSpeed = (0.4 + rand * 0.6) * speedBase * uPulseSpeed;\n  float cableThick = baseThick * (0.6 + rand * 0.4);\n\n  vec3 cableCol = uCableColor;\n  cableCol *= 1.0 + (rand - 0.5) * 0.4 * uColorVariance;\n  cableCol = mix(cableCol, uPulseColor, rand * 0.25 * uColorVariance);\n\n  float scroll = depth + (iTime * randSpeed);\n  float pulseFact = fract(scroll);\n\n  float distToCore = abs(gvX);\n  float wireMask = smoothstep(cableThick, cableThick - 0.05, distToCore);\n  float rimGlow = smoothstep(borderWeight, 0.0, abs(distToCore - cableThick));\n\n  float pulseThick = cableThick * uPulseWidth;\n  float pulseMask = smoothstep(pulseThick, pulseThick - 0.05 * uPulseWidth, distToCore);\n\n  float pulseDist = abs(pulseFact - 0.5);\n  float pulseTotal = uPulseLength;\n  float pulseCore = pulseTotal * (1.0 - uPulseBlend);\n  float pulseLo = min(pulseCore, pulseTotal - max(fwidth(scroll), 1e-4));\n  float dataPulse = 1.0 - smoothstep(pulseLo, pulseTotal, pulseDist);\n\n  float aBody = wireMask * uTunnelOpacity;\n  float aRim = rimGlow;\n  float aPulse = clamp(dataPulse * pulseMask, 0.0, 1.0);\n\n  vec3 fiberCol = uTunnelColor * aBody\n    + cableCol * aRim * 1.3 * uGlow\n    + uPulseColor * dataPulse * 3.0 * pulseMask;\n\n  float distFade = smoothstep(0.0, uFadeNear, r) * smoothstep(uFadeFar, uFadeFar - 0.9, r);\n  float inten = clamp(aBody + aRim + aPulse, 0.0, 1.0) * distFade;\n\n  vec3 finalCol = fiberCol * uBrightness;\n  float alpha = clamp(inten, 0.0, 1.0) * uOpacity;\n  vec3 outRgb = finalCol * alpha;\n\n  if (uGrain > 0.5) {\n    float gv = (fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + iTime) * 43758.5453) - 0.5) * uGrainIntensity;\n    outRgb = clamp(outRgb + gv, 0.0, 1.0);\n    alpha = clamp(alpha + gv, 0.0, 1.0);\n  }\n\n  o = vec4(outRgb, alpha);\n}\n\nvoid main() {\n  vec4 o = vec4(0.0);\n  mainImage(o, gl_FragCoord.xy);\n  if (uLightMode > 0.5) {\n    float peak = max(o.r, max(o.g, o.b));\n    vec3 chroma = pow(clamp(o.rgb / max(peak, 0.0001), 0.0, 1.0), vec3(1.16));\n    fragColor = vec4(mix(vec3(1.0), chroma, o.a * 0.95), 1.0);\n  } else {\n    fragColor = o;\n  }\n}\n`;\n\nconst ctxMap = new WeakMap();\n\nconst LightTunnel = ({\n  cableColor = '#A855F7',\n  pulseColor = '#A855F7',\n  tunnelColor = '#5227FF',\n  tunnelOpacity = 0,\n  speed = 0.1,\n  flowDirection = 'outward',\n  pulseSpeed = 2,\n  pulseLength = 0.28,\n  pulseBlend = 1,\n  pulseWidth = 1,\n  cableCount = 20,\n  thickness = 0.35,\n  rimWidth = 0.15,\n  waviness = 0.3,\n  sway = 0.5,\n  size = 1.0,\n  centerX = 0.0,\n  centerY = 0.0,\n  glow = 1.0,\n  fadeNear = 0.5,\n  fadeFar = 2,\n  brightness = 1.0,\n  colorVariance = true,\n  grain = true,\n  grainIntensity = 0.05,\n  opacity = 1.0,\n  mouseInteraction = true,\n  mouseStrength = 0.1,\n  lightMode = false,\n  className = ''\n}) => {\n  const containerRef = useRef(null);\n  const mouseEnabledRef = useRef(mouseInteraction);\n  const mouseStrengthRef = useRef(mouseStrength);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      webgl: 2,\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: false,\n      dpr: Math.min(window.devicePixelRatio || 1, 2)\n    });\n\n    const gl = renderer.gl;\n    gl.clearColor(0, 0, 0, 0);\n    const canvas = gl.canvas;\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    container.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms: {\n        iTime: { value: 0 },\n        iResolution: { value: new Float32Array([1, 1]) },\n        uSpeed: { value: 0.1 },\n        uFlowDir: { value: -1.0 },\n        uPulseSpeed: { value: 2.0 },\n        uPulseLength: { value: 0.28 },\n        uPulseBlend: { value: 1.0 },\n        uPulseWidth: { value: 1.0 },\n        uCableCount: { value: 20 },\n        uThickness: { value: 0.35 },\n        uRimWidth: { value: 0.15 },\n        uWaviness: { value: 0.3 },\n        uSway: { value: 0.5 },\n        uSize: { value: 1.0 },\n        uCenter: { value: new Float32Array([0, 0]) },\n        uMouseOffset: { value: new Float32Array([0, 0]) },\n        uGlow: { value: 1.0 },\n        uFadeNear: { value: 0.5 },\n        uFadeFar: { value: 2.0 },\n        uBrightness: { value: 1.0 },\n        uColorVariance: { value: 1.0 },\n        uOpacity: { value: 1.0 },\n        uCableColor: { value: new Float32Array([0.65882353, 0.33333333, 0.96862745]) },\n        uPulseColor: { value: new Float32Array([0.65882353, 0.33333333, 0.96862745]) },\n        uTunnelColor: { value: new Float32Array([0.32156863, 0.15294118, 1]) },\n        uTunnelOpacity: { value: 0.0 },\n        uGrain: { value: 1.0 },\n        uGrainIntensity: { value: 0.05 },\n        uLightMode: { value: 0.0 }\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n    ctxMap.set(container, { renderer, program, mesh });\n\n    const setSize = () => {\n      const rect = container.getBoundingClientRect();\n      const w = Math.max(1, Math.floor(rect.width));\n      const h = Math.max(1, Math.floor(rect.height));\n      renderer.setSize(w, h);\n      const res = program.uniforms.iResolution.value;\n      res[0] = gl.drawingBufferWidth;\n      res[1] = gl.drawingBufferHeight;\n      renderer.render({ scene: mesh });\n    };\n\n    const ro = new ResizeObserver(setSize);\n    ro.observe(container);\n    setSize();\n\n    let currentMouse = [0.5, 0.5];\n    let targetMouse = [0.5, 0.5];\n\n    const handleMouseMove = e => {\n      const rect = canvas.getBoundingClientRect();\n      targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];\n    };\n    const handleMouseLeave = () => {\n      targetMouse = [0.5, 0.5];\n    };\n    canvas.addEventListener('mousemove', handleMouseMove);\n    canvas.addEventListener('mouseleave', handleMouseLeave);\n\n    let raf = 0;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const t0 = performance.now();\n\n    const loop = t => {\n      program.uniforms.iTime.value = (t - t0) * 0.001;\n\n      if (mouseEnabledRef.current) {\n        currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);\n        currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);\n      } else {\n        currentMouse[0] += 0.05 * (0.5 - currentMouse[0]);\n        currentMouse[1] += 0.05 * (0.5 - currentMouse[1]);\n      }\n      const off = program.uniforms.uMouseOffset.value;\n      off[0] = (currentMouse[0] - 0.5) * mouseStrengthRef.current;\n      off[1] = (currentMouse[1] - 0.5) * mouseStrengthRef.current;\n\n      renderer.render({ scene: mesh });\n      raf = requestAnimationFrame(loop);\n    };\n\n    const tryStart = () => {\n      if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);\n    };\n    const tryStop = () => {\n      if (raf !== 0) {\n        cancelAnimationFrame(raf);\n        raf = 0;\n      }\n    };\n\n    const io = new IntersectionObserver(\n      ([entry]) => {\n        isVisible = entry.isIntersecting;\n        isVisible ? tryStart() : tryStop();\n      },\n      { threshold: 0 }\n    );\n    io.observe(container);\n\n    const onVisibility = () => {\n      isPageVisible = !document.hidden;\n      isPageVisible ? tryStart() : tryStop();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n\n    tryStart();\n\n    return () => {\n      tryStop();\n      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener('visibilitychange', onVisibility);\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      canvas.removeEventListener('mouseleave', handleMouseLeave);\n      ctxMap.delete(container);\n      try {\n        container.removeChild(canvas);\n      } catch {}\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n  }, []);\n\n  useEffect(() => {\n    mouseEnabledRef.current = mouseInteraction;\n    mouseStrengthRef.current = mouseStrength;\n\n    const container = containerRef.current;\n    if (!container) return;\n    const ctx = ctxMap.get(container);\n    if (!ctx) return;\n    const { program } = ctx;\n    const u = program.uniforms;\n\n    u.uSpeed.value = speed;\n    u.uFlowDir.value = flowDirection === 'outward' ? -1.0 : 1.0;\n    u.uPulseSpeed.value = pulseSpeed;\n    u.uPulseLength.value = pulseLength;\n    u.uPulseBlend.value = pulseBlend;\n    u.uPulseWidth.value = pulseWidth;\n    u.uCableCount.value = cableCount;\n    u.uThickness.value = thickness;\n    u.uRimWidth.value = rimWidth;\n    u.uWaviness.value = waviness;\n    u.uSway.value = sway;\n    u.uSize.value = size;\n    const center = u.uCenter.value;\n    center[0] = centerX;\n    center[1] = centerY;\n    u.uGlow.value = glow;\n    u.uFadeNear.value = fadeNear;\n    u.uFadeFar.value = fadeFar;\n    u.uBrightness.value = brightness;\n    u.uColorVariance.value = colorVariance ? 1.0 : 0.0;\n    u.uGrain.value = grain ? 1.0 : 0.0;\n    u.uGrainIntensity.value = grainIntensity;\n    u.uOpacity.value = opacity;\n    u.uLightMode.value = lightMode ? 1.0 : 0.0;\n    const cable = hexToRgb(cableColor);\n    const cableU = u.uCableColor.value;\n    cableU[0] = cable[0];\n    cableU[1] = cable[1];\n    cableU[2] = cable[2];\n    const pulse = hexToRgb(pulseColor);\n    const pulseU = u.uPulseColor.value;\n    pulseU[0] = pulse[0];\n    pulseU[1] = pulse[1];\n    pulseU[2] = pulse[2];\n    const tunnel = hexToRgb(tunnelColor);\n    const tunnelU = u.uTunnelColor.value;\n    tunnelU[0] = tunnel[0];\n    tunnelU[1] = tunnel[1];\n    tunnelU[2] = tunnel[2];\n    u.uTunnelOpacity.value = tunnelOpacity;\n  }, [\n    cableColor,\n    pulseColor,\n    tunnelColor,\n    tunnelOpacity,\n    speed,\n    flowDirection,\n    pulseSpeed,\n    pulseLength,\n    pulseBlend,\n    pulseWidth,\n    cableCount,\n    thickness,\n    rimWidth,\n    waviness,\n    sway,\n    size,\n    centerX,\n    centerY,\n    glow,\n    fadeNear,\n    fadeFar,\n    brightness,\n    colorVariance,\n    grain,\n    grainIntensity,\n    opacity,\n    mouseInteraction,\n    mouseStrength,\n    lightMode\n  ]);\n\n  return <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className}`.trim()} />;\n};\n\nexport default LightTunnel;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}