{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Strands-TS-TW",
	"title": "Strands",
	"description": "Glowing ribbon-like strands that ripple and weave across a transparent canvas.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Strands/Strands.tsx",
			"content": "import { Renderer, Program, Mesh, Color, Triangle, RenderTarget } from 'ogl';\nimport { useEffect, useRef, type CSSProperties } from 'react';\n\nconst MAX_STRANDS = 12;\nconst MAX_COLORS = 8;\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform vec3 uColors[${MAX_COLORS}];\nuniform int uColorCount;\nuniform int uStrandCount;\nuniform float uSpeed;\nuniform float uAmplitude;\nuniform float uWaviness;\nuniform float uThickness;\nuniform float uGlow;\nuniform float uTaper;\nuniform float uSpread;\nuniform float uHueShift;\nuniform float uIntensity;\nuniform float uOpacity;\nuniform float uScale;\nuniform float uSaturation;\n\nout vec4 fragColor;\n\nconst float PI = 3.14159265;\n\nvec3 spectrum(float t) {\n  return 0.5 + 0.5 * cos(2.0 * PI * (t + vec3(0.00, 0.33, 0.67)));\n}\n\nvec3 samplePalette(float t) {\n  t = fract(t);\n  float scaled = t * float(uColorCount);\n  int idx = int(floor(scaled));\n  float blend = fract(scaled);\n  int nextIdx = idx + 1;\n  if (nextIdx >= uColorCount) nextIdx = 0;\n  return mix(uColors[idx], uColors[nextIdx], blend);\n}\n\nvec3 strandColor(float t) {\n  if (uColorCount > 0) return samplePalette(t);\n  return spectrum(t);\n}\n\nvoid main() {\n  vec2 uv = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;\n  uv /= max(uScale, 0.0001);\n\n  float e = 0.06 + uIntensity * 0.94;\n  float env = pow(max(cos(uv.x * PI * 1.3), 0.0), uTaper);\n\n  vec3 col = vec3(0.0);\n\n  for (int i = 0; i < ${MAX_STRANDS}; i++) {\n    if (i >= uStrandCount) break;\n\n    float fi = float(i);\n    float ph = fi * 1.7 * uSpread;\n    float freq = (2.0 + fi * 0.35) * uWaviness;\n    float spd = 1.4 + fi * 1.2;\n\n    float tt = uTime * uSpeed;\n    float w = sin(uv.x * freq + tt * spd + ph) * 0.60\n            + sin(uv.x * freq * 1.1 - tt * spd * 0.7 + ph * 1.7) * 0.40;\n\n    float amp = (0.1 + 0.02 * e) * env * uAmplitude;\n    float y = w * amp;\n\n    float d = abs(uv.y - y);\n    float thick = (0.001 + 0.05 * e) * (0.35 + env) * uThickness;\n    float g = thick / (d + thick * 0.45);\n    g = g * g;\n\n    float h = fi / float(uStrandCount) + uv.x * 0.30 + uTime * 0.04 + uHueShift;\n    col += strandColor(h) * g * env;\n  }\n\n  col *= 0.45 + 0.7 * e;\n  col = 1.0 - exp(-col * uGlow);\n\n  float gray = dot(col, vec3(0.2126, 0.7152, 0.0722));\n  col = max(mix(vec3(gray), col, uSaturation), 0.0);\n\n  float lum = max(max(col.r, col.g), col.b);\n  float alpha = clamp(lum, 0.0, 1.0) * uOpacity;\n\n  fragColor = vec4(col * uOpacity, alpha);\n}\n`;\n\nconst GLASS_FRAG = `#version 300 es\nprecision highp float;\n\nuniform sampler2D uScene;\nuniform vec2 uResolution;\nuniform float uRadius;\nuniform float uRefraction;\nuniform float uDispersion;\n\nout vec4 fragColor;\n\nvec2 toUv(vec2 p) {\n  return p * (uResolution.y / uResolution) + 0.5;\n}\n\nvoid main() {\n  vec2 p = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;\n  float d = length(p);\n  float r = uRadius;\n\n  float edge = fwidth(d) * 1.5;\n  float mask = 1.0 - smoothstep(r - edge, r + edge, d);\n  if (mask <= 0.0) {\n    fragColor = vec4(0.0);\n    return;\n  }\n\n  // sphere height: 0 at the rim, 1 at the center\n  float z = sqrt(max(r * r - d * d, 0.0)) / r;\n  float nd = d / r; // 0 at the center, 1 at the rim\n\n  // refraction is confined to a narrow band near the rim; the rest stays undistorted\n  vec2 dir = d > 0.0 ? p / d : vec2(0.0);\n  float lens = smoothstep(0.85, 1.0, nd) * pow(nd, 6.0);\n  vec2 offset = -dir * lens * uRefraction * 0.15;\n  vec2 disp = -dir * lens * uDispersion * 0.012;\n\n  vec3 light;\n  light.r = texture(uScene, toUv(p + offset - disp)).r;\n  light.g = texture(uScene, toUv(p + offset)).g;\n  light.b = texture(uScene, toUv(p + offset + disp)).b;\n\n  // neutral fresnel rim (no color tint so the glass stays clear)\n  float fres = pow(1.0 - z, 3.0);\n  vec3 rim = vec3(1.0) * fres * 0.18;\n\n  // specular highlight from the upper-left\n  vec2 lightDir = normalize(vec2(-0.55, 0.6));\n  float spec = pow(max(dot(p / max(r, 1e-4), lightDir), 0.0), 6.0);\n  spec *= smoothstep(r, r * 0.55, d);\n\n  vec3 emissive = light + rim + vec3(spec) * 0.4;\n  float emissiveA = clamp(max(max(emissive.r, emissive.g), emissive.b), 0.0, 1.0);\n\n  // almost clear glass body: only a faint neutral darkening, mostly near the rim\n  float bodyA = 0.05 + fres * 0.05;\n\n  // composite emissive light over the clear body (premultiplied)\n  float outA = emissiveA + bodyA * (1.0 - emissiveA);\n  vec3 outRGB = emissive;\n\n  outRGB *= mask;\n  outA *= mask;\n\n  fragColor = vec4(outRGB, outA);\n}\n`;\n\nexport interface StrandsProps {\n  colors?: string[];\n  count?: number;\n  speed?: number;\n  amplitude?: number;\n  waviness?: number;\n  thickness?: number;\n  glow?: number;\n  taper?: number;\n  spread?: number;\n  hueShift?: number;\n  intensity?: number;\n  saturation?: number;\n  opacity?: number;\n  scale?: number;\n  glass?: boolean;\n  refraction?: number;\n  dispersion?: number;\n  glassSize?: number;\n  className?: string;\n  style?: CSSProperties;\n}\n\nconst buildPalette = (colors: string[]): number[][] => {\n  const filled = colors && colors.length ? colors : ['#ffffff'];\n  const padded: number[][] = [];\n  for (let i = 0; i < MAX_COLORS; i++) {\n    const hex = filled[i] ?? filled[filled.length - 1];\n    const c = new Color(hex);\n    padded.push([c.r, c.g, c.b]);\n  }\n  return padded;\n};\n\nexport default function Strands({\n  colors = ['#FF4242', '#7C3AED', '#06B6D4', '#EAB308'],\n  count = 3,\n  speed = 0.5,\n  amplitude = 1,\n  waviness = 1,\n  thickness = 0.7,\n  glow = 2.6,\n  taper = 3,\n  spread = 1,\n  hueShift = 0,\n  intensity = 0.6,\n  saturation = 1.5,\n  opacity = 1,\n  scale = 1.5,\n  glass = false,\n  refraction = 1,\n  dispersion = 1,\n  glassSize = 1,\n  className = '',\n  style\n}: StrandsProps) {\n  const propsRef = useRef<Required<Omit<StrandsProps, 'className' | 'style'>>>({\n    colors,\n    count,\n    speed,\n    amplitude,\n    waviness,\n    thickness,\n    glow,\n    taper,\n    spread,\n    hueShift,\n    intensity,\n    saturation,\n    opacity,\n    scale,\n    glass,\n    refraction,\n    dispersion,\n    glassSize\n  });\n  propsRef.current = {\n    colors,\n    count,\n    speed,\n    amplitude,\n    waviness,\n    thickness,\n    glow,\n    taper,\n    spread,\n    hueShift,\n    intensity,\n    saturation,\n    opacity,\n    scale,\n    glass,\n    refraction,\n    dispersion,\n    glassSize\n  };\n\n  const ctnDom = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const ctn = ctnDom.current;\n    if (!ctn) return;\n\n    const renderer = new Renderer({\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: true\n    });\n    const gl = renderer.gl;\n    gl.clearColor(0, 0, 0, 0);\n    gl.enable(gl.BLEND);\n    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n    gl.canvas.style.backgroundColor = 'transparent';\n\n    const geometry = new Triangle(gl);\n    if (geometry.attributes.uv) {\n      delete geometry.attributes.uv;\n    }\n\n    const program = new Program(gl, {\n      vertex: VERT,\n      fragment: FRAG,\n      uniforms: {\n        uTime: { value: 0 },\n        uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n        uColors: { value: buildPalette(propsRef.current.colors) },\n        uColorCount: { value: Math.min(propsRef.current.colors.length, MAX_COLORS) },\n        uStrandCount: { value: Math.min(propsRef.current.count, MAX_STRANDS) },\n        uSpeed: { value: speed },\n        uAmplitude: { value: amplitude },\n        uWaviness: { value: waviness },\n        uThickness: { value: thickness },\n        uGlow: { value: glow },\n        uTaper: { value: taper },\n        uSpread: { value: spread },\n        uHueShift: { value: hueShift },\n        uIntensity: { value: intensity },\n        uOpacity: { value: opacity },\n        uScale: { value: scale },\n        uSaturation: { value: saturation }\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n\n    const renderTarget = new RenderTarget(gl, {\n      width: ctn.offsetWidth,\n      height: ctn.offsetHeight\n    });\n\n    const glassProgram = new Program(gl, {\n      vertex: VERT,\n      fragment: GLASS_FRAG,\n      uniforms: {\n        uScene: { value: renderTarget.texture },\n        uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n        uRadius: { value: 0.46 * glassSize },\n        uRefraction: { value: refraction },\n        uDispersion: { value: dispersion }\n      }\n    });\n    const glassMesh = new Mesh(gl, { geometry, program: glassProgram });\n\n    ctn.appendChild(gl.canvas);\n\n    function resize() {\n      if (!ctn) return;\n      const width = ctn.offsetWidth;\n      const height = ctn.offsetHeight;\n      renderer.setSize(width, height);\n      program.uniforms.uResolution.value = [width, height];\n      renderTarget.setSize(width, height);\n      glassProgram.uniforms.uResolution.value = [width, height];\n    }\n    window.addEventListener('resize', resize);\n    resize();\n\n    let animateId = 0;\n    const update = (t: number) => {\n      animateId = requestAnimationFrame(update);\n      const current = propsRef.current;\n      program.uniforms.uTime.value = t * 0.001;\n      program.uniforms.uColors.value = buildPalette(current.colors);\n      program.uniforms.uColorCount.value = Math.min(current.colors.length, MAX_COLORS);\n      program.uniforms.uStrandCount.value = Math.min(Math.max(Math.round(current.count), 1), MAX_STRANDS);\n      program.uniforms.uSpeed.value = current.speed;\n      program.uniforms.uAmplitude.value = current.amplitude;\n      program.uniforms.uWaviness.value = current.waviness;\n      program.uniforms.uThickness.value = current.thickness;\n      program.uniforms.uGlow.value = current.glow;\n      program.uniforms.uTaper.value = current.taper;\n      program.uniforms.uSpread.value = current.spread;\n      program.uniforms.uHueShift.value = current.hueShift;\n      program.uniforms.uIntensity.value = current.intensity;\n      program.uniforms.uOpacity.value = current.opacity;\n      program.uniforms.uScale.value = current.scale;\n      program.uniforms.uSaturation.value = current.saturation;\n\n      if (current.glass) {\n        renderer.render({ scene: mesh, target: renderTarget });\n        glassProgram.uniforms.uScene.value = renderTarget.texture;\n        glassProgram.uniforms.uRefraction.value = current.refraction;\n        glassProgram.uniforms.uDispersion.value = current.dispersion;\n        glassProgram.uniforms.uRadius.value = 0.46 * current.glassSize;\n        renderer.render({ scene: glassMesh });\n      } else {\n        renderer.render({ scene: mesh });\n      }\n    };\n    animateId = requestAnimationFrame(update);\n\n    return () => {\n      cancelAnimationFrame(animateId);\n      window.removeEventListener('resize', resize);\n      if (ctn && gl.canvas.parentNode === ctn) {\n        ctn.removeChild(gl.canvas);\n      }\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return <div ref={ctnDom} className={`relative w-full h-full bg-transparent ${className}`} style={style} />;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}