{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "GhostFibers-JS-TW",
	"title": "GhostFibers",
	"description": "A deep-blue recursive fiber field with luminous bands, radial twisting and soft atmospheric glow.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "GhostFibers/GhostFibers.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Mesh, Program, Renderer, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n  const value = hex.trim().replace(/^#/, '');\n  const normalized = value.length === 3 ? value.replace(/./g, channel => channel + channel) : value;\n  const match = /^([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(normalized);\n  if (!match) return [1, 1, 1];\n  return [parseInt(match[1], 16) / 255, parseInt(match[2], 16) / 255, parseInt(match[3], 16) / 255];\n};\n\nconst setColor = (uniform, hex) => {\n  const color = hexToRgb(hex);\n  uniform.value[0] = color[0];\n  uniform.value[1] = color[1];\n  uniform.value[2] = color[2];\n};\n\nconst vertex = `#version 300 es\nin vec2 position;\n\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\n\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uSpeed;\nuniform float uScale;\nuniform float uRotation;\nuniform float uLayers;\nuniform float uWaveAmplitude;\nuniform float uWaveFrequency;\nuniform float uWaveSpeed;\nuniform float uLayerSpeed;\nuniform float uTwist;\nuniform float uTwistFrequency;\nuniform float uTwistSpeed;\nuniform float uLineFrequency;\nuniform float uLineSpacing;\nuniform float uLineSharpness;\nuniform float uGlowFalloff;\nuniform float uGlowIntensity;\nuniform float uBrightness;\nuniform float uBlueBoost;\nuniform float uVignette;\nuniform float uGrain;\nuniform float uRotationSpeed;\nuniform float uLightMode;\nuniform vec3 uLineColor;\nuniform vec3 uGlowColor;\n\nout vec4 fragColor;\n\n#define MAX_LAYERS 10\n\nmat2 rotate2d(float angle) {\n  float sine = sin(angle);\n  float cosine = cos(angle);\n  return mat2(cosine, -sine, sine, cosine);\n}\n\nfloat grainHash(vec2 point) {\n  point = floor(point);\n  float hash = 52.9829189 * fract(dot(point, vec2(0.065, 0.005)));\n  return fract(hash);\n}\n\nfloat layeredGrain(vec2 fragmentPixel) {\n  vec2 point = mod(fragmentPixel + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n  vec2 rotated = mat2(0.8, -0.5, 0.5, 0.8) * point;\n  float grain = 0.0;\n  grain += 0.40 * grainHash(rotated);\n  grain += 0.25 * grainHash(rotated * 2.0 + 17.0);\n  grain += 0.20 * grainHash(rotated * 4.0 + 47.0);\n  grain += 0.10 * grainHash(rotated * 8.0 + 113.0);\n  grain += 0.05 * grainHash(rotated * 16.0 + 191.0);\n  return grain;\n}\n\nvoid main() {\n  vec2 resolution = max(uResolution, vec2(1.0));\n  vec2 uv = (2.0 * gl_FragCoord.xy - resolution) / resolution.y;\n  float time = uTime * uSpeed;\n  vec3 backdrop = mix(vec3(0.070588, 0.058824, 0.090196), vec3(1.0), step(0.5, uLightMode));\n  vec3 centerTone = max(uLineColor * 0.85567 - uGlowColor * 0.06186, vec3(0.0));\n  vec3 cloudTone = uLineColor * 0.19588 + uGlowColor * 0.2268;\n  vec2 p = uv;\n  p /= max(uScale, 0.05);\n  p = rotate2d(radians(uRotation) + time * uRotationSpeed) * p;\n  vec3 color = vec3(0.0);\n  float fiberField = 0.0;\n\n  for (int index = 0; index < MAX_LAYERS; index++) {\n    float fi = float(index) + 1.0;\n    if (fi > uLayers) break;\n\n    p += uWaveAmplitude * sin(p.yx * fi * uWaveFrequency + time * (uWaveSpeed + fi * uLayerSpeed));\n\n    float radius = length(p);\n    float polarAngle = atan(p.y, p.x);\n    polarAngle += sin(radius * uTwistFrequency - time * uTwistSpeed + fi) * uTwist;\n    p = vec2(cos(polarAngle), sin(polarAngle)) * radius;\n\n    float lines = abs(sin(p.x * (uLineFrequency + fi * uLineSpacing) + sin(p.y * 3.0 + time)));\n    lines = pow(max(0.0, 1.0 - lines), uLineSharpness);\n    fiberField += lines / fi;\n    color += uLineColor * lines / fi;\n\n    float glow = exp(-uGlowFalloff * abs(sin(p.x * 3.0 + time + fi)));\n    color += uGlowColor * glow * uGlowIntensity / (fi * 2.0);\n  }\n\n  float center = exp(-2.2 * dot(uv, uv));\n  color += centerTone * center;\n\n  float cloud = exp(-1.5 * length(uv + vec2(sin(time * 0.3) * 0.25, cos(time * 0.25) * 0.18)));\n  color += cloudTone * cloud;\n\n  float vignette = 1.0 - smoothstep(0.35, 1.45, length(uv));\n  color *= mix(1.0 - uVignette, 1.0, vignette);\n  color = 1.0 - exp(-color * uBrightness);\n  color.b *= uBlueBoost;\n\n  vec3 outputColor;\n  if (uLightMode > 0.5) {\n    float edgeFade = mix(1.0 - uVignette, 1.0, vignette);\n    float fibers = pow(smoothstep(0.12, 1.05, fiberField) * edgeFade, 1.5);\n    float atmosphere = (center * 0.025 + cloud * 0.015) * edgeFade;\n    vec3 fiberInk = mix(backdrop, uLineColor, 0.52);\n    vec3 airColor = mix(backdrop, uGlowColor, 0.16);\n\n    outputColor = mix(backdrop, airColor, atmosphere);\n    outputColor = mix(outputColor, fiberInk, fibers * 0.3);\n  } else {\n    outputColor = backdrop + color;\n  }\n\n  float noise = (layeredGrain(gl_FragCoord.xy) - 0.5) * uGrain;\n  outputColor = clamp(outputColor + noise, 0.0, 1.0);\n  fragColor = vec4(outputColor, 1.0);\n}\n`;\n\nconst contexts = new WeakMap();\n\nconst GhostFibers = ({\n  lineColor = '#140E35',\n  glowColor = '#3437A0',\n  speed = 0.2,\n  scale = 2,\n  rotation = 0,\n  rotationSpeed = 0.25,\n  layers = 4,\n  waveAmplitude = 0.015,\n  waveFrequency = 3,\n  waveSpeed = 0.15,\n  layerSpeed = 0.08,\n  twist = 0.1,\n  twistFrequency = 5,\n  twistSpeed = 1.2,\n  lineFrequency = 5,\n  lineSpacing = 2,\n  lineSharpness = 16,\n  glowFalloff = 10,\n  glowIntensity = 1.6,\n  brightness = 2,\n  blueBoost = 1.25,\n  vignette = 0.8,\n  grain = 0.05,\n  lightMode = false,\n  dpr = 1,\n  fps = 60,\n  paused = false,\n  className = ''\n}) => {\n  const containerRef = useRef(null);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      webgl: 2,\n      alpha: false,\n      antialias: false,\n      dpr: Math.min(Math.max(dpr, 0.5), 2)\n    });\n    const gl = renderer.gl;\n    const canvas = gl.canvas;\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    canvas.setAttribute('aria-hidden', 'true');\n    container.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms: {\n        uResolution: { value: new Float32Array([1, 1]) },\n        uTime: { value: 0 },\n        uSpeed: { value: 0.2 },\n        uScale: { value: 2 },\n        uRotation: { value: 0 },\n        uRotationSpeed: { value: 0.25 },\n        uLayers: { value: 4 },\n        uWaveAmplitude: { value: 0.015 },\n        uWaveFrequency: { value: 3 },\n        uWaveSpeed: { value: 0.15 },\n        uLayerSpeed: { value: 0.08 },\n        uTwist: { value: 0.1 },\n        uTwistFrequency: { value: 5 },\n        uTwistSpeed: { value: 1.2 },\n        uLineFrequency: { value: 5 },\n        uLineSpacing: { value: 2 },\n        uLineSharpness: { value: 16 },\n        uGlowFalloff: { value: 10 },\n        uGlowIntensity: { value: 1.6 },\n        uBrightness: { value: 2 },\n        uBlueBoost: { value: 1.25 },\n        uVignette: { value: 0.8 },\n        uGrain: { value: 0.05 },\n        uLightMode: { value: 0 },\n        uLineColor: { value: new Float32Array(hexToRgb('#140E35')) },\n        uGlowColor: { value: new Float32Array(hexToRgb('#3437A0')) }\n      }\n    });\n    const mesh = new Mesh(gl, { geometry, program });\n\n    let frameId = 0;\n    let elapsed = 0;\n    let previousTime = performance.now();\n    let lastRenderTime = 0;\n    let frameRate = 60;\n    let isPaused = false;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');\n\n    const render = () => renderer.render({ scene: mesh });\n    const stop = () => {\n      if (frameId !== 0) cancelAnimationFrame(frameId);\n      frameId = 0;\n    };\n    const canAnimate = () => isVisible && isPageVisible && !isPaused && !reducedMotion.matches;\n\n    const loop = now => {\n      frameId = 0;\n      if (!canAnimate()) return;\n\n      const delta = Math.min((now - previousTime) / 1000, 0.1);\n      previousTime = now;\n      elapsed += delta;\n\n      if (now - lastRenderTime >= 1000 / frameRate - 0.5) {\n        program.uniforms.uTime.value = elapsed;\n        render();\n        lastRenderTime = now;\n      }\n\n      frameId = requestAnimationFrame(loop);\n    };\n\n    const start = () => {\n      if (!canAnimate() || frameId !== 0) return;\n      previousTime = performance.now();\n      frameId = requestAnimationFrame(loop);\n    };\n\n    const setSize = () => {\n      const rect = container.getBoundingClientRect();\n      renderer.setSize(Math.max(1, Math.floor(rect.width)), Math.max(1, Math.floor(rect.height)));\n      program.uniforms.uResolution.value[0] = gl.drawingBufferWidth;\n      program.uniforms.uResolution.value[1] = gl.drawingBufferHeight;\n      render();\n    };\n\n    const handleVisibility = () => {\n      isPageVisible = !document.hidden;\n      if (canAnimate()) start();\n      else stop();\n    };\n    const handleReducedMotion = () => {\n      if (canAnimate()) start();\n      else {\n        stop();\n        render();\n      }\n    };\n\n    const resizeObserver = new ResizeObserver(setSize);\n    resizeObserver.observe(container);\n    const intersectionObserver = new IntersectionObserver(\n      ([entry]) => {\n        isVisible = entry.isIntersecting;\n        if (canAnimate()) start();\n        else stop();\n      },\n      { threshold: 0 }\n    );\n    intersectionObserver.observe(container);\n    document.addEventListener('visibilitychange', handleVisibility);\n    reducedMotion.addEventListener('change', handleReducedMotion);\n\n    contexts.set(container, {\n      renderer,\n      program,\n      mesh,\n      render,\n      setPaused(value) {\n        isPaused = value;\n        if (canAnimate()) start();\n        else {\n          stop();\n          render();\n        }\n      },\n      setFps(value) {\n        frameRate = Math.min(Math.max(value, 1), 120);\n      }\n    });\n\n    setSize();\n    start();\n\n    return () => {\n      stop();\n      resizeObserver.disconnect();\n      intersectionObserver.disconnect();\n      document.removeEventListener('visibilitychange', handleVisibility);\n      reducedMotion.removeEventListener('change', handleReducedMotion);\n      contexts.delete(container);\n      if (canvas.parentNode === container) container.removeChild(canvas);\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n  }, [dpr]);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const context = contexts.get(container);\n    if (!context) return;\n\n    const uniforms = context.program.uniforms;\n    setColor(uniforms.uLineColor, lineColor);\n    setColor(uniforms.uGlowColor, glowColor);\n    uniforms.uSpeed.value = speed;\n    uniforms.uScale.value = scale;\n    uniforms.uRotation.value = rotation;\n    uniforms.uRotationSpeed.value = rotationSpeed;\n    uniforms.uLayers.value = Math.min(Math.max(Math.round(layers), 1), 10);\n    uniforms.uWaveAmplitude.value = waveAmplitude;\n    uniforms.uWaveFrequency.value = waveFrequency;\n    uniforms.uWaveSpeed.value = waveSpeed;\n    uniforms.uLayerSpeed.value = layerSpeed;\n    uniforms.uTwist.value = twist;\n    uniforms.uTwistFrequency.value = twistFrequency;\n    uniforms.uTwistSpeed.value = twistSpeed;\n    uniforms.uLineFrequency.value = lineFrequency;\n    uniforms.uLineSpacing.value = lineSpacing;\n    uniforms.uLineSharpness.value = lineSharpness;\n    uniforms.uGlowFalloff.value = glowFalloff;\n    uniforms.uGlowIntensity.value = glowIntensity;\n    uniforms.uBrightness.value = brightness;\n    uniforms.uBlueBoost.value = blueBoost;\n    uniforms.uVignette.value = vignette;\n    uniforms.uGrain.value = grain;\n    uniforms.uLightMode.value = lightMode ? 1 : 0;\n    context.setFps(fps);\n    context.setPaused(paused);\n    context.render();\n  }, [\n    lineColor,\n    glowColor,\n    speed,\n    scale,\n    rotation,\n    rotationSpeed,\n    layers,\n    waveAmplitude,\n    waveFrequency,\n    waveSpeed,\n    layerSpeed,\n    twist,\n    twistFrequency,\n    twistSpeed,\n    lineFrequency,\n    lineSpacing,\n    lineSharpness,\n    glowFalloff,\n    glowIntensity,\n    brightness,\n    blueBoost,\n    vignette,\n    grain,\n    lightMode,\n    fps,\n    paused,\n    dpr\n  ]);\n\n  return <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className}`.trim()} />;\n};\n\nexport default GhostFibers;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}