{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Scanner-JS-TW",
	"title": "Scanner",
	"description": "Calm interference bands sweeping across the screen like an oscilloscope.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Scanner/Scanner.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\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 directionToFloat = dir => (dir === 'horizontal' ? 1.0 : dir === 'diagonal' ? 2.0 : 0.0);\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 uSweepSpeed;\nuniform float uSweepWidth;\nuniform float uSweepFalloff;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uRipple;\nuniform float uBandDensity;\nuniform float uLineSharpness;\nuniform float uGlow;\nuniform float uColorSpread;\nuniform float uBrightness;\nuniform float uContrast;\nuniform float uSoftness;\nuniform float uVignette;\nuniform float uOpacity;\nuniform float uScanline;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform float uDirection;\nuniform vec2 uMouse;\nuniform float uMouseEnabled;\nuniform float uMouseRadius;\nuniform float uMouseStrength;\nuniform float uMouseActive;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nout vec4 fragColor;\n\nconst float TAU = 6.2831853;\n\nfloat signalField(vec2 p, float t) {\n  float w = sin(p.x * 1.3 + t * 0.7);\n  w += sin(p.y * 1.7 - t * 0.52) * 0.8;\n  w += sin((p.x + p.y) * 0.9 + t * 0.91) * 0.6;\n  w += sin((p.x - p.y) * 1.53 - t * 0.63) * 0.42;\n  return w * 0.35;\n}\n\nvec3 palette(float f) {\n  f = clamp(f, 0.0, 1.0);\n  f = pow(f, uContrast);\n  vec3 c = mix(uColor1, uColor2, smoothstep(0.08, 0.6, f));\n  return mix(c, uColor3, smoothstep(0.68, 1.0, f));\n}\n\nfloat scanBand(float x, float aa, float sharp) {\n  float v = mix(0.5, 0.5 + 0.5 * cos(x * TAU), aa);\n  return pow(v, sharp);\n}\n\nvoid main() {\n  float aspect = iResolution.x / iResolution.y;\n  vec2 uv0 = (gl_FragCoord.xy * 2.0 - iResolution.xy) / iResolution.y;\n  vec2 p = uv0 / max(uScale, 0.001);\n\n  float t = iTime * uSpeed;\n\n  float mouseBoost = 0.0;\n  if (uMouseEnabled > 0.5) {\n    vec2 mUv = vec2((uMouse.x * 2.0 - 1.0) * aspect, uMouse.y * 2.0 - 1.0);\n    vec2 md = uv0 - mUv;\n    float r = max(uMouseRadius, 0.001);\n    mouseBoost = exp(-dot(md, md) / (r * r)) * uMouseStrength * uMouseActive;\n  }\n\n  float axis;\n  if (uDirection < 0.5) axis = p.y;\n  else if (uDirection < 1.5) axis = p.x;\n  else axis = (p.x + p.y) * 0.70710678;\n\n  float sig = signalField(p * uFrequency, t);\n  float coord = axis + sig * uRipple;\n\n  float phase = coord / max(uSweepWidth, 0.05) - t * uSweepSpeed;\n  float sweep = pow(0.5 + 0.5 * cos(phase * TAU), max(uSweepFalloff, 0.1));\n\n  float lc = coord * uBandDensity;\n  float aa = 1.0 / (1.0 + uSoftness * fwidth(lc) * 3.0);\n  aa = clamp(aa * (1.0 + mouseBoost * 0.6), 0.0, 1.0);\n\n  float bodyBase = clamp(0.5 + 0.5 * sig, 0.0, 1.0);\n  float body = bodyBase * bodyBase * uGlow * sweep;\n\n  float sharp = max(uLineSharpness, 0.1);\n  float split = uColorSpread * 0.16;\n  float fr = clamp(scanBand(lc + split, aa, sharp) * sweep + body, 0.0, 1.0);\n  float fg = clamp(scanBand(lc, aa, sharp) * sweep + body, 0.0, 1.0);\n  float fb = clamp(scanBand(lc - split, aa, sharp) * sweep + body, 0.0, 1.0);\n\n  vec3 col = vec3(palette(fr).r, palette(fg).g, palette(fb).b);\n\n  float inten = (fr + fg + fb) * 0.3333333 * uBrightness;\n  inten *= 1.0 + mouseBoost * 0.9;\n\n  if (uScanline > 0.5) {\n    inten *= 1.0 - 0.18 * (0.5 + 0.5 * cos(gl_FragCoord.y * 1.7));\n  }\n\n  if (uGrain > 0.5) {\n    float g = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + iTime) * 43758.5453);\n    inten += (g - 0.5) * uGrainIntensity;\n  }\n\n  inten *= clamp(1.0 - uVignette * smoothstep(0.55, 1.65, length(uv0)), 0.0, 1.0);\n  inten = clamp(inten, 0.0, 1.0);\n\n  float a = clamp(inten * uOpacity, 0.0, 1.0);\n  fragColor = vec4(clamp(col, 0.0, 1.0) * a, a);\n}\n`;\n\nconst ctxMap = new WeakMap();\n\nconst Scanner = ({\n  color1 = '#5227FF',\n  color2 = '#FF9FFC',\n  color3 = '#FFFFFF',\n  speed = 0.5,\n  sweepSpeed = 0.25,\n  sweepWidth = 1.6,\n  sweepFalloff = 6,\n  scale = 1.5,\n  frequency = 2,\n  ripple = 0.22,\n  bandDensity = 11,\n  lineSharpness = 5.5,\n  glow = 0.22,\n  scanDirection = 'vertical',\n  colorSpread = 0.7,\n  brightness = 1.0,\n  contrast = 1.15,\n  softness = 1.4,\n  vignette = 0.45,\n  scanline = true,\n  grain = true,\n  grainIntensity = 0.05,\n  opacity = 1.0,\n  mouseInteraction = true,\n  mouseRadius = 0.5,\n  mouseStrength = 0.5,\n  className = ''\n}) => {\n  const containerRef = useRef(null);\n  const mouseEnabledRef = useRef(mouseInteraction);\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;\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.5 },\n        uSweepSpeed: { value: 0.25 },\n        uSweepWidth: { value: 1.6 },\n        uSweepFalloff: { value: 6 },\n        uScale: { value: 1.5 },\n        uFrequency: { value: 2 },\n        uRipple: { value: 0.22 },\n        uBandDensity: { value: 11 },\n        uLineSharpness: { value: 5.5 },\n        uGlow: { value: 0.22 },\n        uColorSpread: { value: 0.7 },\n        uBrightness: { value: 1.0 },\n        uContrast: { value: 1.15 },\n        uSoftness: { value: 1.4 },\n        uVignette: { value: 0.45 },\n        uOpacity: { value: 1.0 },\n        uScanline: { value: 1.0 },\n        uGrain: { value: 1.0 },\n        uGrainIntensity: { value: 0.05 },\n        uDirection: { value: 0.0 },\n        uMouse: { value: new Float32Array([0.5, 0.5]) },\n        uMouseEnabled: { value: 1.0 },\n        uMouseRadius: { value: 0.5 },\n        uMouseStrength: { value: 0.5 },\n        uMouseActive: { value: 0.0 },\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      }\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.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    let currentMouse = [0.5, 0.5];\n    let targetMouse = [0.5, 0.5];\n    let mouseActive = 0;\n    let targetMouseActive = 0;\n\n    const onMouseMove = e => {\n      const rect = canvas.getBoundingClientRect();\n      targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];\n      targetMouseActive = 1;\n    };\n    const onMouseLeave = () => {\n      targetMouseActive = 0;\n    };\n    canvas.addEventListener('mousemove', onMouseMove);\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 => {\n      program.uniforms.iTime.value = (t - t0) * 0.001;\n\n      if (!mouseEnabledRef.current) {\n        targetMouseActive = 0;\n      }\n      currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);\n      currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);\n      program.uniforms.uMouse.value[0] = currentMouse[0];\n      program.uniforms.uMouse.value[1] = currentMouse[1];\n      mouseActive += 0.05 * (targetMouseActive - mouseActive);\n      program.uniforms.uMouseActive.value = mouseActive;\n\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('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;\n\n    u.uSpeed.value = speed;\n    u.uSweepSpeed.value = sweepSpeed;\n    u.uSweepWidth.value = sweepWidth;\n    u.uSweepFalloff.value = sweepFalloff;\n    u.uScale.value = scale;\n    u.uFrequency.value = frequency;\n    u.uRipple.value = ripple;\n    u.uBandDensity.value = bandDensity;\n    u.uLineSharpness.value = lineSharpness;\n    u.uGlow.value = glow;\n    u.uColorSpread.value = colorSpread;\n    u.uBrightness.value = brightness;\n    u.uContrast.value = contrast;\n    u.uSoftness.value = softness;\n    u.uVignette.value = vignette;\n    u.uOpacity.value = opacity;\n    u.uScanline.value = scanline ? 1.0 : 0.0;\n    u.uGrain.value = grain ? 1.0 : 0.0;\n    u.uGrainIntensity.value = grainIntensity;\n    u.uDirection.value = directionToFloat(scanDirection);\n    u.uMouseEnabled.value = mouseInteraction ? 1.0 : 0.0;\n    u.uMouseRadius.value = mouseRadius;\n    u.uMouseStrength.value = mouseStrength;\n    const c1 = hexToRgb(color1);\n    const c2 = hexToRgb(color2);\n    const c3 = hexToRgb(color3);\n    u.uColor1.value[0] = c1[0];\n    u.uColor1.value[1] = c1[1];\n    u.uColor1.value[2] = c1[2];\n    u.uColor2.value[0] = c2[0];\n    u.uColor2.value[1] = c2[1];\n    u.uColor2.value[2] = c2[2];\n    u.uColor3.value[0] = c3[0];\n    u.uColor3.value[1] = c3[1];\n    u.uColor3.value[2] = c3[2];\n\n    mouseEnabledRef.current = mouseInteraction;\n  }, [\n    speed,\n    sweepSpeed,\n    sweepWidth,\n    sweepFalloff,\n    scale,\n    frequency,\n    ripple,\n    bandDensity,\n    lineSharpness,\n    glow,\n    colorSpread,\n    brightness,\n    contrast,\n    softness,\n    vignette,\n    opacity,\n    scanline,\n    grain,\n    grainIntensity,\n    scanDirection,\n    mouseInteraction,\n    mouseRadius,\n    mouseStrength,\n    color1,\n    color2,\n    color3\n  ]);\n\n  return <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className}`.trim()} />;\n};\n\nexport default Scanner;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}