{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "GradientBlinds-JS-TW",
	"title": "GradientBlinds",
	"description": "Layered gradient blinds with spotlight and noise distortion.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "GradientBlinds/GradientBlinds.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst MAX_COLORS = 8;\nconst hexToRGB = hex => {\n  const c = hex.replace('#', '').padEnd(6, '0');\n  const r = parseInt(c.slice(0, 2), 16) / 255;\n  const g = parseInt(c.slice(2, 4), 16) / 255;\n  const b = parseInt(c.slice(4, 6), 16) / 255;\n  return [r, g, b];\n};\nconst prepStops = stops => {\n  const base = (stops && stops.length ? stops : ['#FF9FFC', '#5227FF']).slice(0, MAX_COLORS);\n  if (base.length === 1) base.push(base[0]);\n  while (base.length < MAX_COLORS) base.push(base[base.length - 1]);\n  const arr = [];\n  for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[i]));\n  const count = Math.max(2, Math.min(MAX_COLORS, stops?.length ?? 2));\n  return { arr, count };\n};\n\nconst GradientBlinds = ({\n  className,\n  dpr,\n  paused = false,\n  gradientColors,\n  angle = 0,\n  noise = 0.3,\n  blindCount = 16,\n  blindMinWidth = 60,\n  mouseDampening = 0.15,\n  mirrorGradient = false,\n  spotlightRadius = 0.5,\n  spotlightSoftness = 1,\n  spotlightOpacity = 1,\n  distortAmount = 0,\n  shineDirection = 'left',\n  mixBlendMode = 'lighten',\n  lightMode = false\n}) => {\n  const containerRef = useRef(null);\n  const rafRef = useRef(null);\n  const programRef = useRef(null);\n  const meshRef = useRef(null);\n  const geometryRef = useRef(null);\n  const rendererRef = useRef(null);\n  const mouseTargetRef = useRef([0, 0]);\n  const lastTimeRef = useRef(0);\n  const firstResizeRef = useRef(true);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),\n      alpha: true,\n      antialias: true\n    });\n    rendererRef.current = renderer;\n    const gl = renderer.gl;\n    const canvas = gl.canvas;\n\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    container.appendChild(canvas);\n\n    const vertex = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\n\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\n    const fragment = `\n#ifdef GL_ES\nprecision mediump float;\n#endif\n\nuniform vec3  iResolution;\nuniform vec2  iMouse;\nuniform float iTime;\n\nuniform float uAngle;\nuniform float uNoise;\nuniform float uBlindCount;\nuniform float uSpotlightRadius;\nuniform float uSpotlightSoftness;\nuniform float uSpotlightOpacity;\nuniform float uMirror;\nuniform float uDistort;\nuniform float uShineFlip;\nuniform vec3  uColor0;\nuniform vec3  uColor1;\nuniform vec3  uColor2;\nuniform vec3  uColor3;\nuniform vec3  uColor4;\nuniform vec3  uColor5;\nuniform vec3  uColor6;\nuniform vec3  uColor7;\nuniform int   uColorCount;\nuniform float uLightMode;\n\nvarying vec2 vUv;\n\nfloat rand(vec2 co){\n  return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453);\n}\n\nvec2 rotate2D(vec2 p, float a){\n  float c = cos(a);\n  float s = sin(a);\n  return mat2(c, -s, s, c) * p;\n}\n\nvec3 getGradientColor(float t){\n  float tt = clamp(t, 0.0, 1.0);\n  int count = uColorCount;\n  if (count < 2) count = 2;\n  float scaled = tt * float(count - 1);\n  float seg = floor(scaled);\n  float f = fract(scaled);\n\n  if (seg < 1.0) return mix(uColor0, uColor1, f);\n  if (seg < 2.0 && count > 2) return mix(uColor1, uColor2, f);\n  if (seg < 3.0 && count > 3) return mix(uColor2, uColor3, f);\n  if (seg < 4.0 && count > 4) return mix(uColor3, uColor4, f);\n  if (seg < 5.0 && count > 5) return mix(uColor4, uColor5, f);\n  if (seg < 6.0 && count > 6) return mix(uColor5, uColor6, f);\n  if (seg < 7.0 && count > 7) return mix(uColor6, uColor7, f);\n  if (count > 7) return uColor7;\n  if (count > 6) return uColor6;\n  if (count > 5) return uColor5;\n  if (count > 4) return uColor4;\n  if (count > 3) return uColor3;\n  if (count > 2) return uColor2;\n  return uColor1;\n}\n\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n    vec2 uv0 = fragCoord.xy / iResolution.xy;\n\n    float aspect = iResolution.x / iResolution.y;\n    vec2 p = uv0 * 2.0 - 1.0;\n    p.x *= aspect;\n    vec2 pr = rotate2D(p, uAngle);\n    pr.x /= aspect;\n    vec2 uv = pr * 0.5 + 0.5;\n\n    vec2 uvMod = uv;\n    if (uDistort > 0.0) {\n      float a = uvMod.y * 6.0;\n      float b = uvMod.x * 6.0;\n      float w = 0.01 * uDistort;\n      uvMod.x += sin(a) * w;\n      uvMod.y += cos(b) * w;\n    }\n    float t = uvMod.x;\n    if (uMirror > 0.5) {\n      t = 1.0 - abs(1.0 - 2.0 * fract(t));\n    }\n    vec3 base = getGradientColor(t);\n\n    vec2 offset = vec2(iMouse.x/iResolution.x, iMouse.y/iResolution.y);\n  float d = length(uv0 - offset);\n  float r = max(uSpotlightRadius, 1e-4);\n  float dn = d / r;\n  float spot = (1.0 - 2.0 * pow(dn, uSpotlightSoftness)) * uSpotlightOpacity;\n  vec3 cir = vec3(spot);\n  float blindCount = max(uBlindCount, 1.0);\n  float stripePhase = uvMod.x * blindCount;\n  float stripe = fract(stripePhase);\n  float stripeAA = clamp(blindCount * 1.25 / min(iResolution.x, iResolution.y), 0.001, 0.12);\n  float edgeDistance = min(stripe, 1.0 - stripe);\n  float edgeBlend = 1.0 - smoothstep(0.0, stripeAA, edgeDistance);\n  stripe = mix(stripe, 0.5, edgeBlend);\n  if (uShineFlip > 0.5) stripe = 1.0 - stripe;\n    vec3 ran = vec3(stripe);\n    vec3 revealSignal = cir + base - ran;\n\n    vec3 col;\n    if (uLightMode > 0.5) {\n        float peak = max(base.r, max(base.g, base.b));\n        vec3 pigment = base / max(peak, 0.0001);\n        float neutral = min(pigment.r, min(pigment.g, pigment.b));\n        pigment = max(pigment - vec3(neutral * 0.72), vec3(0.0));\n        pigment /= max(max(pigment.r, max(pigment.g, pigment.b)), 0.0001);\n        pigment = mix(pigment, pigment * pigment, 0.12) * 0.72;\n        vec3 revealed = clamp(revealSignal, 0.0, 1.0);\n        float coverage = max(revealed.r, max(revealed.g, revealed.b));\n        col = mix(vec3(1.0), pigment, coverage);\n        float grain = max(rand(gl_FragCoord.xy + iTime) - 0.5, 0.0);\n        float grainAmount = grain * uNoise * mix(0.12, 0.18, coverage);\n        col = clamp(col - vec3(grainAmount), 0.0, 1.0);\n    } else {\n        col = revealSignal;\n        col += (rand(gl_FragCoord.xy + iTime) - 0.5) * uNoise;\n    }\n\n    fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n    vec4 color;\n    mainImage(color, vUv * iResolution.xy);\n    gl_FragColor = color;\n}\n`;\n\n    const { arr: colorArr, count: colorCount } = prepStops(gradientColors);\n    const uniforms = {\n      iResolution: {\n        value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1]\n      },\n      iMouse: { value: [0, 0] },\n      iTime: { value: 0 },\n      uAngle: { value: (angle * Math.PI) / 180 },\n      uNoise: { value: noise },\n      uBlindCount: { value: Math.max(1, blindCount) },\n      uSpotlightRadius: { value: spotlightRadius },\n      uSpotlightSoftness: { value: spotlightSoftness },\n      uSpotlightOpacity: { value: spotlightOpacity },\n      uMirror: { value: mirrorGradient ? 1 : 0 },\n      uDistort: { value: distortAmount },\n      uShineFlip: { value: shineDirection === 'right' ? 1 : 0 },\n      uColor0: { value: colorArr[0] },\n      uColor1: { value: colorArr[1] },\n      uColor2: { value: colorArr[2] },\n      uColor3: { value: colorArr[3] },\n      uColor4: { value: colorArr[4] },\n      uColor5: { value: colorArr[5] },\n      uColor6: { value: colorArr[6] },\n      uColor7: { value: colorArr[7] },\n      uColorCount: { value: colorCount },\n      uLightMode: { value: lightMode ? 1 : 0 }\n    };\n\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms\n    });\n    programRef.current = program;\n\n    const geometry = new Triangle(gl);\n    geometryRef.current = geometry;\n    const mesh = new Mesh(gl, { geometry, program });\n    meshRef.current = mesh;\n\n    const resize = () => {\n      const rect = container.getBoundingClientRect();\n      renderer.setSize(rect.width, rect.height);\n      uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];\n\n      if (blindMinWidth && blindMinWidth > 0) {\n        const maxByMinWidth = Math.max(1, Math.floor(rect.width / blindMinWidth));\n\n        const effective = blindCount ? Math.min(blindCount, maxByMinWidth) : maxByMinWidth;\n        uniforms.uBlindCount.value = Math.max(1, effective);\n      } else {\n        uniforms.uBlindCount.value = Math.max(1, blindCount);\n      }\n\n      if (firstResizeRef.current) {\n        firstResizeRef.current = false;\n        const cx = gl.drawingBufferWidth / 2;\n        const cy = gl.drawingBufferHeight / 2;\n        uniforms.iMouse.value = [cx, cy];\n        mouseTargetRef.current = [cx, cy];\n      }\n    };\n\n    resize();\n    const ro = new ResizeObserver(resize);\n    ro.observe(container);\n\n    const onPointerMove = e => {\n      const rect = canvas.getBoundingClientRect();\n      const scale = renderer.dpr || 1;\n      const x = (e.clientX - rect.left) * scale;\n      const y = (rect.height - (e.clientY - rect.top)) * scale;\n      mouseTargetRef.current = [x, y];\n      if (mouseDampening <= 0) {\n        uniforms.iMouse.value = [x, y];\n      }\n    };\n    canvas.addEventListener('pointermove', onPointerMove);\n\n    const loop = t => {\n      rafRef.current = requestAnimationFrame(loop);\n      uniforms.iTime.value = t * 0.001;\n      if (mouseDampening > 0) {\n        if (!lastTimeRef.current) lastTimeRef.current = t;\n        const dt = (t - lastTimeRef.current) / 1000;\n        lastTimeRef.current = t;\n        const tau = Math.max(1e-4, mouseDampening);\n        let factor = 1 - Math.exp(-dt / tau);\n        if (factor > 1) factor = 1;\n        const target = mouseTargetRef.current;\n        const cur = uniforms.iMouse.value;\n        cur[0] += (target[0] - cur[0]) * factor;\n        cur[1] += (target[1] - cur[1]) * factor;\n      } else {\n        lastTimeRef.current = t;\n      }\n      if (!paused && programRef.current && meshRef.current) {\n        try {\n          renderer.render({ scene: meshRef.current });\n        } catch (e) {\n          console.error(e);\n        }\n      }\n    };\n    rafRef.current = requestAnimationFrame(loop);\n\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      canvas.removeEventListener('pointermove', onPointerMove);\n      ro.disconnect();\n      if (canvas.parentElement === container) {\n        container.removeChild(canvas);\n      }\n      const callIfFn = (obj, key) => {\n        if (obj && typeof obj[key] === 'function') {\n          obj[key].call(obj);\n        }\n      };\n      callIfFn(programRef.current, 'remove');\n      callIfFn(geometryRef.current, 'remove');\n      callIfFn(meshRef.current, 'remove');\n      callIfFn(rendererRef.current, 'destroy');\n      programRef.current = null;\n      geometryRef.current = null;\n      meshRef.current = null;\n      rendererRef.current = null;\n    };\n  }, [\n    dpr,\n    paused,\n    gradientColors,\n    angle,\n    noise,\n    blindCount,\n    blindMinWidth,\n    mouseDampening,\n    mirrorGradient,\n    spotlightRadius,\n    spotlightSoftness,\n    spotlightOpacity,\n    distortAmount,\n    shineDirection,\n    lightMode\n  ]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`w-full h-full overflow-hidden relative ${className}`}\n      style={{\n        ...(!lightMode && mixBlendMode && {\n          mixBlendMode: mixBlendMode\n        })\n      }}\n    />\n  );\n};\n\nexport default GradientBlinds;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}