{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Threads-JS-TW",
	"title": "Threads",
	"description": "Animated pattern of lines forming a fabric-like motion.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Threads/Threads.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Color } from 'ogl';\n\nconst vertexShader = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec3 uColor;\nuniform float uAmplitude;\nuniform float uDistance;\nuniform vec2 uMouse;\n\n#define PI 3.1415926538\n\nconst int u_line_count = 40;\nconst float u_line_width = 7.0;\nconst float u_line_blur = 10.0;\n\nfloat Perlin2D(vec2 P) {\n    vec2 Pi = floor(P);\n    vec4 Pf_Pfmin1 = P.xyxy - vec4(Pi, Pi + 1.0);\n    vec4 Pt = vec4(Pi.xy, Pi.xy + 1.0);\n    Pt = Pt - floor(Pt * (1.0 / 71.0)) * 71.0;\n    Pt += vec2(26.0, 161.0).xyxy;\n    Pt *= Pt;\n    Pt = Pt.xzxz * Pt.yyww;\n    vec4 hash_x = fract(Pt * (1.0 / 951.135664));\n    vec4 hash_y = fract(Pt * (1.0 / 642.949883));\n    vec4 grad_x = hash_x - 0.49999;\n    vec4 grad_y = hash_y - 0.49999;\n    vec4 grad_results = inversesqrt(grad_x * grad_x + grad_y * grad_y)\n        * (grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww);\n    grad_results *= 1.4142135623730950;\n    vec2 blend = Pf_Pfmin1.xy * Pf_Pfmin1.xy * Pf_Pfmin1.xy\n               * (Pf_Pfmin1.xy * (Pf_Pfmin1.xy * 6.0 - 15.0) + 10.0);\n    vec4 blend2 = vec4(blend, vec2(1.0 - blend));\n    return dot(grad_results, blend2.zxzx * blend2.wwyy);\n}\n\nfloat pixel(float count, vec2 resolution) {\n    return (1.0 / max(resolution.x, resolution.y)) * count;\n}\n\nfloat lineFn(vec2 st, float width, float perc, float offset, vec2 mouse, float time, float amplitude, float distance) {\n    float split_offset = (perc * 0.4);\n    float split_point = 0.1 + split_offset;\n\n    float amplitude_normal = smoothstep(split_point, 0.7, st.x);\n    float amplitude_strength = 0.5;\n    float finalAmplitude = amplitude_normal * amplitude_strength\n                           * amplitude * (1.0 + (mouse.y - 0.5) * 0.2);\n\n    float time_scaled = time / 10.0 + (mouse.x - 0.5) * 1.0;\n    float blur = smoothstep(split_point, split_point + 0.05, st.x) * perc;\n\n    float xnoise = mix(\n        Perlin2D(vec2(time_scaled, st.x + perc) * 2.5),\n        Perlin2D(vec2(time_scaled, st.x + time_scaled) * 3.5) / 1.5,\n        st.x * 0.3\n    );\n\n    float y = 0.5 + (perc - 0.5) * distance + xnoise / 2.0 * finalAmplitude;\n\n    float line_start = smoothstep(\n        y + (width / 2.0) + (u_line_blur * pixel(1.0, iResolution.xy) * blur),\n        y,\n        st.y\n    );\n\n    float line_end = smoothstep(\n        y,\n        y - (width / 2.0) - (u_line_blur * pixel(1.0, iResolution.xy) * blur),\n        st.y\n    );\n\n    return clamp(\n        (line_start - line_end) * (1.0 - smoothstep(0.0, 1.0, pow(perc, 0.3))),\n        0.0,\n        1.0\n    );\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n    vec2 uv = fragCoord / iResolution.xy;\n\n    float line_strength = 1.0;\n    for (int i = 0; i < u_line_count; i++) {\n        float p = float(i) / float(u_line_count);\n        line_strength *= (1.0 - lineFn(\n            uv,\n            u_line_width * pixel(1.0, iResolution.xy) * (1.0 - p),\n            p,\n            (PI * 1.0) * p,\n            uMouse,\n            iTime,\n            uAmplitude,\n            uDistance\n        ));\n    }\n\n    float colorVal = 1.0 - line_strength;\n    fragColor = vec4(uColor * colorVal, colorVal);\n}\n\nvoid main() {\n    mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`;\n\nconst Threads = ({ color = [1, 1, 1], amplitude = 1, distance = 0, enableMouseInteraction = false, ...rest }) => {\n  const containerRef = useRef(null);\n  const animationFrameId = useRef(0);\n\n  // Keep the latest props in a ref so updating them mutates the live shader\n  // uniforms instead of tearing down and rebuilding the whole WebGL context.\n  const propsRef = useRef({ color, amplitude, distance, enableMouseInteraction });\n  propsRef.current = { color, amplitude, distance, enableMouseInteraction };\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({ alpha: true });\n    const gl = renderer.gl;\n    gl.clearColor(0, 0, 0, 0);\n    gl.enable(gl.BLEND);\n    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n    container.appendChild(gl.canvas);\n\n    const geometry = new Triangle(gl);\n    const program = new Program(gl, {\n      vertex: vertexShader,\n      fragment: fragmentShader,\n      uniforms: {\n        iTime: { value: 0 },\n        iResolution: {\n          value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n        },\n        uColor: { value: new Color(...propsRef.current.color) },\n        uAmplitude: { value: propsRef.current.amplitude },\n        uDistance: { value: propsRef.current.distance },\n        uMouse: { value: new Float32Array([0.5, 0.5]) }\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n\n    // The fragment shader is heavy (per-pixel Perlin noise across many lines), so\n    // its cost scales with the number of rendered pixels. Cap the internal render\n    // resolution to keep large / high-DPI screens smooth; the effect is soft\n    // enough that the downscale is imperceptible.\n    const MAX_RENDER_DIM = 1920;\n    function resize() {\n      const { clientWidth, clientHeight } = container;\n      const baseDpr = Math.min(window.devicePixelRatio || 1, 2);\n      const longestSide = Math.max(clientWidth, clientHeight) * baseDpr;\n      const dpr = longestSide > MAX_RENDER_DIM ? (baseDpr * MAX_RENDER_DIM) / longestSide : baseDpr;\n      renderer.dpr = dpr;\n      renderer.setSize(clientWidth, clientHeight);\n      program.uniforms.iResolution.value.r = gl.canvas.width;\n      program.uniforms.iResolution.value.g = gl.canvas.height;\n      program.uniforms.iResolution.value.b = gl.canvas.width / gl.canvas.height;\n    }\n\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    window.addEventListener('resize', resize);\n    resize();\n\n    const currentMouse = [0.5, 0.5];\n    let targetMouse = [0.5, 0.5];\n\n    function handleMouseMove(e) {\n      const rect = container.getBoundingClientRect();\n      const x = (e.clientX - rect.left) / rect.width;\n      const y = 1.0 - (e.clientY - rect.top) / rect.height;\n      targetMouse = [x, y];\n    }\n    function handleMouseLeave() {\n      targetMouse = [0.5, 0.5];\n    }\n    container.addEventListener('mousemove', handleMouseMove);\n    container.addEventListener('mouseleave', handleMouseLeave);\n\n    // Only animate while the canvas is on screen and the tab is visible, so the\n    // shader never burns GPU/CPU for something the user can't see.\n    let isVisible = true;\n    const intersectionObserver = new IntersectionObserver(\n      entries => {\n        isVisible = entries[0].isIntersecting;\n      },\n      { threshold: 0 }\n    );\n    intersectionObserver.observe(container);\n\n    function update(t) {\n      animationFrameId.current = requestAnimationFrame(update);\n      if (!isVisible || document.hidden) return;\n\n      const { color, amplitude, distance, enableMouseInteraction } = propsRef.current;\n\n      program.uniforms.uColor.value.set(...color);\n      program.uniforms.uAmplitude.value = amplitude;\n      program.uniforms.uDistance.value = distance;\n\n      if (enableMouseInteraction) {\n        const smoothing = 0.05;\n        currentMouse[0] += smoothing * (targetMouse[0] - currentMouse[0]);\n        currentMouse[1] += smoothing * (targetMouse[1] - currentMouse[1]);\n        program.uniforms.uMouse.value[0] = currentMouse[0];\n        program.uniforms.uMouse.value[1] = currentMouse[1];\n      } else {\n        program.uniforms.uMouse.value[0] = 0.5;\n        program.uniforms.uMouse.value[1] = 0.5;\n      }\n      program.uniforms.iTime.value = t * 0.001;\n\n      renderer.render({ scene: mesh });\n    }\n    animationFrameId.current = requestAnimationFrame(update);\n\n    return () => {\n      if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current);\n      resizeObserver.disconnect();\n      intersectionObserver.disconnect();\n      window.removeEventListener('resize', resize);\n      container.removeEventListener('mousemove', handleMouseMove);\n      container.removeEventListener('mouseleave', handleMouseLeave);\n      if (container.contains(gl.canvas)) container.removeChild(gl.canvas);\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n  }, []);\n\n  return <div ref={containerRef} className=\"w-full h-full relative\" {...rest} />;\n};\n\nexport default Threads;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}