{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "WebThreads-TS-CSS",
	"title": "WebThreads",
	"description": "Glowing sine threads woven through a luminous convergence point.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "WebThreads.css",
			"target": "@components/WebThreads.css",
			"content": ".web-threads-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "WebThreads.tsx",
			"content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './WebThreads.css';\n\nexport type FanMode = 'center' | 'left' | 'right';\n\nexport interface WebThreadsProps {\n  color1?: string;\n  color2?: string;\n  color3?: string;\n  speed?: number;\n  threadCount?: number;\n  frequency?: number;\n  spread?: number;\n  taper?: number;\n  position?: number;\n  fanMode?: FanMode;\n  glow?: number;\n  falloff?: number;\n  thickness?: number;\n  brightness?: number;\n  opacity?: number;\n  mirror?: boolean;\n  shimmer?: boolean;\n  grain?: boolean;\n  grainIntensity?: number;\n  mouseInteraction?: boolean;\n  mouseStrength?: number;\n  backgroundColor?: string;\n  lightMode?: boolean;\n  className?: string;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\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 FAN_MODE: Record<FanMode, number> = { center: 0, left: 1, right: 2 };\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 uThreadCount;\nuniform float uFrequency;\nuniform float uSpread;\nuniform float uTaper;\nuniform float uPosition;\nuniform float uFanMode;\nuniform float uGlow;\nuniform float uFalloff;\nuniform float uThickness;\nuniform float uBrightness;\nuniform float uOpacity;\nuniform float uMirror;\nuniform float uShimmer;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform vec3 uBackgroundColor;\nuniform bool uLightMode;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uEnableMouse;\nuniform float uMouseActive;\nout vec4 fragColor;\n\n#define TAU 6.28318530718\n#define MAX_THREADS 10\n\nfloat glow(float x, float str, float dist) {\n  return dist / pow(max(x, 1e-4), str);\n}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / iResolution.xy;\n  float n = max(uThreadCount, 1.0);\n\n  float pinchX = uFanMode < 0.5 ? 0.5 : (uFanMode < 1.5 ? 0.0 : 1.0);\n  if (uEnableMouse > 0.5) {\n    pinchX = mix(pinchX, uMouse.x, clamp(uMouseStrength, 0.0, 1.0) * uMouseActive);\n  }\n\n  float spreadDx = uSpread * abs(uv.x - pinchX);\n  float baseT = iTime * uSpeed;\n  float tauOverN = TAU / n;\n  float mirror = uMirror > 0.5 ? sign(pinchX - uv.x) : 1.0;\n  bool doShimmer = uShimmer > 0.5;\n  float shimmerT = iTime * 1.7;\n  float invThickness = 1.0 / max(uThickness, 0.01);\n  float xFreq = uv.x * uFrequency;\n  float yOff = uv.y - uPosition;\n  float ciScale = n > 1.0 ? 1.0 / (n - 1.0) : 0.0;\n\n  vec3 col = vec3(0.0);\n  float gsum = 0.0;\n\n  for (int idx = 0; idx < MAX_THREADS; idx++) {\n    float i = float(idx);\n    if (i >= n) break;\n\n    float amplitude = spreadDx * (1.0 + i * uTaper);\n    float shimmer = doShimmer ? sin(shimmerT + i * 1.3) * 0.35 : 0.0;\n    float phase = (baseT + i * tauOverN) * mirror + shimmer;\n\n    float sdf = abs(yOff + sin(xFreq + phase) * amplitude) * invThickness;\n\n    float g = glow(sdf, uFalloff, uGlow);\n    float ci = i * ciScale;\n    vec3 threadCol = mix(uColor1, uColor2, ci);\n\n    col += g * threadCol;\n    gsum += g;\n  }\n\n  float coreAmt = smoothstep(0.5, 2.2, gsum);\n  col = mix(col, uColor3 * gsum, coreAmt * 0.5);\n\n  float bright = uBrightness;\n  if (uEnableMouse > 0.5) {\n    vec2 md = uv - uMouse;\n    float d2 = dot(md, md);\n    bright += clamp(uMouseStrength, 0.0, 1.0) * uMouseActive * exp(-d2 * 6.0) * 0.6;\n  }\n  col *= bright;\n\n  float alpha = clamp(gsum, 0.0, 1.0) * uOpacity;\n\n  vec3 outRgb = col * 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  if (uLightMode) {\n    vec3 mapped = vec3(1.0) - exp(-max(col, vec3(0.0)) * 1.3);\n    float rawEnergy = clamp(max(mapped.r, max(mapped.g, mapped.b)) * uOpacity, 0.0, 1.0);\n    float coverage = smoothstep(0.18, 0.72, rawEnergy);\n    coverage *= coverage;\n    vec3 hue = mapped / max(max(mapped.r, max(mapped.g, mapped.b)), 1e-4);\n    vec3 chroma = pow(clamp(hue, 0.0, 1.0), vec3(0.78));\n    vec3 pigment = mix(chroma, vec3(0.08), 0.12);\n    vec3 ink = mix(vec3(0.9), pigment, 0.82 + coverage * 0.18);\n    fragColor = vec4(mix(uBackgroundColor, ink, coverage), 1.0);\n  } else {\n    fragColor = vec4(outRgb, alpha);\n  }\n}\n`;\n\ntype WebThreadsCtx = {\n  renderer: InstanceType<typeof Renderer>;\n  program: InstanceType<typeof Program>;\n  mesh: InstanceType<typeof Mesh>;\n};\nconst ctxMap = new WeakMap<HTMLDivElement, WebThreadsCtx>();\n\nconst WebThreads: React.FC<WebThreadsProps> = ({\n  color1 = '#5227FF',\n  color2 = '#FF9FFC',\n  color3 = '#FFFFFF',\n  speed = 0.2,\n  threadCount = 6,\n  frequency = 5.0,\n  spread = 0.18,\n  taper = 1.0,\n  position = 0.5,\n  fanMode = 'center',\n  glow = 0.02,\n  falloff = 0.6,\n  thickness = 1.1,\n  brightness = 0.6,\n  opacity = 1.0,\n  mirror = true,\n  shimmer = false,\n  grain = true,\n  grainIntensity = 0.05,\n  mouseInteraction = true,\n  mouseStrength = 0.3,\n  backgroundColor = '#FFFFFF',\n  lightMode = false,\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const mouseRef = useRef<{ enabled: boolean; strength: number }>({ enabled: true, strength: 0.3 });\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 as HTMLCanvasElement;\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.2 },\n        uThreadCount: { value: 6 },\n        uFrequency: { value: 5.0 },\n        uSpread: { value: 0.18 },\n        uTaper: { value: 1.0 },\n        uPosition: { value: 0.5 },\n        uFanMode: { value: 0 },\n        uGlow: { value: 0.02 },\n        uFalloff: { value: 0.6 },\n        uThickness: { value: 1.1 },\n        uBrightness: { value: 0.6 },\n        uOpacity: { value: 1.0 },\n        uMirror: { value: 1.0 },\n        uShimmer: { value: 0.0 },\n        uGrain: { value: 1.0 },\n        uGrainIntensity: { value: 0.05 },\n        uColor1: { value: new Float32Array([1, 1, 1]) },\n        uColor2: { value: new Float32Array([1, 1, 1]) },\n        uColor3: { value: new Float32Array([1, 1, 1]) },\n        uBackgroundColor: { value: new Float32Array([1, 1, 1]) },\n        uLightMode: { value: false },\n        uMouse: { value: new Float32Array([0.5, 0.5]) },\n        uMouseStrength: { value: 0.3 },\n        uEnableMouse: { value: 1.0 },\n        uMouseActive: { value: 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 as { value: Float32Array }).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    const currentMouse: [number, number] = [0.5, 0.5];\n    const targetMouse: [number, number] = [0.5, 0.5];\n    let currentActive = 0;\n    let targetActive = 0;\n\n    const onMouseMove = (e: MouseEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      targetMouse[0] = (e.clientX - rect.left) / rect.width;\n      targetMouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;\n      targetActive = 1;\n    };\n    const onMouseEnter = () => {\n      targetActive = 1;\n    };\n    const onMouseLeave = () => {\n      targetActive = 0;\n    };\n    canvas.addEventListener('mousemove', onMouseMove);\n    canvas.addEventListener('mouseenter', onMouseEnter);\n    canvas.addEventListener('mouseleave', onMouseLeave);\n\n    let raf = 0;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const t0 = performance.now();\n\n    const loop = (t: number) => {\n      (program.uniforms.iTime as { value: number }).value = (t - t0) * 0.001;\n      currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);\n      currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);\n      currentActive += 0.05 * (targetActive - currentActive);\n      const mouse = (program.uniforms.uMouse as { value: Float32Array }).value;\n      mouse[0] = currentMouse[0];\n      mouse[1] = currentMouse[1];\n      (program.uniforms.uMouseActive as { value: number }).value = currentActive;\n      (program.uniforms.uEnableMouse as { value: number }).value = mouseRef.current.enabled ? 1.0 : 0.0;\n      (program.uniforms.uMouseStrength as { value: number }).value = mouseRef.current.strength;\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', onMouseMove);\n      canvas.removeEventListener('mouseenter', onMouseEnter);\n      canvas.removeEventListener('mouseleave', onMouseLeave);\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    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 as Record<string, { value: any }>;\n\n    u.uSpeed.value = speed;\n    u.uThreadCount.value = Math.round(threadCount);\n    u.uFrequency.value = frequency;\n    u.uSpread.value = spread;\n    u.uTaper.value = taper;\n    u.uPosition.value = position;\n    u.uFanMode.value = FAN_MODE[fanMode] ?? 0;\n    u.uGlow.value = glow;\n    u.uFalloff.value = falloff;\n    u.uThickness.value = thickness;\n    u.uBrightness.value = brightness;\n    u.uOpacity.value = opacity;\n    u.uMirror.value = mirror ? 1.0 : 0.0;\n    u.uShimmer.value = shimmer ? 1.0 : 0.0;\n    u.uGrain.value = grain ? 1.0 : 0.0;\n    u.uGrainIntensity.value = grainIntensity;\n    const c1 = u.uColor1.value as Float32Array;\n    const rgb1 = hexToRgb(color1);\n    c1[0] = rgb1[0];\n    c1[1] = rgb1[1];\n    c1[2] = rgb1[2];\n    const c2 = u.uColor2.value as Float32Array;\n    const rgb2 = hexToRgb(color2);\n    c2[0] = rgb2[0];\n    c2[1] = rgb2[1];\n    c2[2] = rgb2[2];\n    const c3 = u.uColor3.value as Float32Array;\n    const rgb3 = hexToRgb(color3);\n    c3[0] = rgb3[0];\n    c3[1] = rgb3[1];\n    c3[2] = rgb3[2];\n    const bg = hexToRgb(backgroundColor);\n    u.uBackgroundColor.value[0] = bg[0];\n    u.uBackgroundColor.value[1] = bg[1];\n    u.uBackgroundColor.value[2] = bg[2];\n    u.uLightMode.value = lightMode;\n    u.uMouseStrength.value = mouseStrength;\n    u.uEnableMouse.value = mouseInteraction ? 1.0 : 0.0;\n    mouseRef.current.enabled = mouseInteraction;\n    mouseRef.current.strength = mouseStrength;\n  }, [\n    color1,\n    color2,\n    color3,\n    speed,\n    threadCount,\n    frequency,\n    spread,\n    taper,\n    position,\n    fanMode,\n    glow,\n    falloff,\n    thickness,\n    brightness,\n    opacity,\n    mirror,\n    shimmer,\n    grain,\n    grainIntensity,\n    mouseInteraction,\n    mouseStrength,\n    backgroundColor,\n    lightMode\n  ]);\n\n  return <div ref={containerRef} className={`web-threads-container ${className}`.trim()} />;\n};\n\nexport default WebThreads;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}