{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "CRTWarp-JS-TW",
	"title": "CRTWarp",
	"description": "Full-canvas CRT plasma with curved distortion, scanlines, bloom and pointer interaction.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "CRTWarp/CRTWarp.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\n\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nvarying vec2 vUv;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform vec3 uColor;\nuniform vec3 uBackgroundColor;\nuniform float uCurvature;\nuniform float uScanlineStrength;\nuniform float uScanlineFrequency;\nuniform float uWaveAmplitude;\nuniform float uWaveFrequency;\nuniform float uBloom;\nuniform float uBloomRadius;\nuniform float uNoise;\nuniform float uVignette;\nuniform float uBrightness;\nuniform float uPixelation;\nuniform float uRgbShift;\nuniform vec2 uPointer;\nuniform float uMouseStrength;\nuniform float uMouseReact;\n\nfloat hash21(vec2 p) {\n  p = fract(p * vec2(123.34, 456.21));\n  p += dot(p, p + 45.32);\n  return fract(p.x * p.y);\n}\n\nvec2 crtCurve(vec2 uv, float radius) {\n  vec2 p = (uv - 0.5) * 2.0;\n  float safeRadius = max(radius, 1.415);\n  float cornerScale = safeRadius / sqrt(max(safeRadius * safeRadius - 2.0, 0.001));\n  p = safeRadius * p / sqrt(max(safeRadius * safeRadius - dot(p, p), 0.001));\n  p /= cornerScale;\n  return p * 0.5 + 0.5;\n}\n\nfloat referencePlasma(vec2 uv, float t) {\n  float frequencyScale = max(uWaveFrequency / 2.2, 0.001);\n  uv = (uv - 0.5) * frequencyScale + 0.5;\n\n  float scanline = 0.5 - 0.5 * cos(uv.y * 3.14159265 * uScanlineFrequency);\n  scanline = mix(1.0, scanline, uScanlineStrength);\n\n  uv *= vec2(80.0, 24.0);\n  uv = ceil(uv);\n  uv /= vec2(80.0, 24.0);\n\n  float amplitude = uWaveAmplitude / 0.28;\n  float field = 0.0;\n  field += 0.7 * sin(0.5 * uv.x + t / 5.0);\n  field += 3.0 * sin(1.6 * uv.y + t / 5.0);\n  field += sin(10.0 * (uv.y * sin(t / 2.0) + uv.x * cos(t / 5.0)) + t / 2.0);\n\n  float cx = uv.x + 0.5 * sin(t / 2.0);\n  float cy = uv.y + 0.5 * cos(t / 4.0);\n  field += 0.4 * sin(sqrt(100.0 * cx * cx + 100.0 * cy * cy + 1.0) + t);\n  field += 0.9 * sin(sqrt(75.0 * cx * cx + 25.0 * cy * cy + 1.0) + t);\n  field -= 1.4 * sin(sqrt(256.0 * cx * cx + 25.0 * cy * cy + 1.0) + t);\n  field += 0.3 * sin(0.5 * uv.y + uv.x + sin(t));\n\n  return scanline * floor(3.0 * (0.5 + 0.499 * sin(field * amplitude))) / 3.0;\n}\n\nvoid main() {\n  vec2 uv = vUv;\n  if (uPixelation > 1.001) {\n    vec2 cells = max(uResolution / uPixelation, vec2(1.0));\n    uv = (floor(uv * cells) + 0.5) / cells;\n  }\n\n  float curveRadius = 1.1 + 0.42 / max(uCurvature, 0.001);\n  if (uMouseReact > 0.5) {\n    curveRadius *= exp(-uPointer.y * uMouseStrength * 0.4);\n  }\n  vec2 curvedUv = crtCurve(uv, curveRadius);\n  if (uMouseReact > 0.5) {\n    curvedUv.x -= uPointer.x * uMouseStrength * 0.035;\n  }\n\n  float signal = referencePlasma(curvedUv, uTime);\n  float radius = 0.01 * uBloomRadius;\n  float glow = signal * 0.2;\n  glow += referencePlasma(curvedUv + vec2(radius, 0.0), uTime) * 0.12;\n  glow += referencePlasma(curvedUv - vec2(radius, 0.0), uTime) * 0.12;\n  glow += referencePlasma(curvedUv + vec2(0.0, radius), uTime) * 0.12;\n  glow += referencePlasma(curvedUv - vec2(0.0, radius), uTime) * 0.12;\n  glow += referencePlasma(curvedUv + vec2(radius), uTime) * 0.08;\n  glow += referencePlasma(curvedUv - vec2(radius), uTime) * 0.08;\n  glow += referencePlasma(curvedUv + vec2(radius, -radius), uTime) * 0.08;\n  glow += referencePlasma(curvedUv + vec2(-radius, radius), uTime) * 0.08;\n\n  float redSignal = referencePlasma(curvedUv + vec2(uRgbShift, 0.0), uTime);\n  float blueSignal = referencePlasma(curvedUv - vec2(uRgbShift, 0.0), uTime);\n  vec3 channelSignal = vec3(redSignal, signal, blueSignal);\n  vec3 waveColor = uColor * (0.3 + signal * 0.7 + glow * uBloom * 0.65);\n  waveColor += (channelSignal - signal) * 0.42;\n\n  float edge = clamp(1.0 - dot(vUv - 0.5, vUv - 0.5) * 2.0, 0.0, 1.0);\n  float edgeFade = mix(1.0, smoothstep(0.0, 1.0, edge), uVignette);\n  float waveMask = clamp(signal * 0.82 + glow * 0.52, 0.0, 1.0) * edgeFade;\n\n  float grain = hash21(gl_FragCoord.xy + vec2(fract(uTime) * 173.0));\n  waveColor = max(waveColor * uBrightness, vec3(0.0));\n  vec3 color = mix(uBackgroundColor, waveColor, waveMask);\n  color += (grain - 0.5) * uNoise;\n  gl_FragColor = vec4(max(color, vec3(0.0)), 1.0);\n}\n`;\n\nexport default function CRTWarp({\n  color = '#c755f7',\n  backgroundColor = '#05010a',\n  speed = 0.5,\n  curvature = 0.25,\n  scanlineStrength = 0.25,\n  scanlineFrequency = 200,\n  waveAmplitude = 0.3,\n  waveFrequency = 2.5,\n  bloom = 1.5,\n  bloomRadius = 1,\n  noise = 0.1,\n  vignette = 0,\n  brightness = 1.25,\n  pixelation = 1,\n  rgbShift = 0.015,\n  mouseReact = true,\n  mouseStrength = 0.5,\n  dpr = 1,\n  fps = 30,\n  paused = false,\n  className,\n  style\n}) {\n  const containerRef = useRef(null);\n  const materialRef = useRef(null);\n  const rendererRef = useRef(null);\n  const frameRef = useRef(null);\n  const pausedRef = useRef(paused);\n  const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n  const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n  const visibleRef = useRef(true);\n  const fpsRef = useRef(fps);\n  const lastFrameRef = useRef(0);\n\n  useEffect(() => {\n    pausedRef.current = paused;\n  }, [paused]);\n\n  useEffect(() => {\n    fpsRef.current = Math.max(1, fps);\n  }, [fps]);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return undefined;\n\n    const scene = new THREE.Scene();\n    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n    const geometry = new THREE.PlaneGeometry(2, 2);\n    const material = new THREE.ShaderMaterial({\n      vertexShader,\n      fragmentShader,\n      uniforms: {\n        uResolution: { value: new THREE.Vector2(1, 1) },\n        uTime: { value: 0 },\n        uSpeed: { value: 0.5 },\n        uColor: { value: new THREE.Color('#c755f7') },\n        uBackgroundColor: { value: new THREE.Color('#05010a') },\n        uCurvature: { value: 0.25 },\n        uScanlineStrength: { value: 0.25 },\n        uScanlineFrequency: { value: 200 },\n        uWaveAmplitude: { value: 0.3 },\n        uWaveFrequency: { value: 2.5 },\n        uBloom: { value: 1.5 },\n        uBloomRadius: { value: 1 },\n        uNoise: { value: 0.1 },\n        uVignette: { value: 0 },\n        uBrightness: { value: 1.25 },\n        uPixelation: { value: 1 },\n        uRgbShift: { value: 0.015 },\n        uPointer: { value: new THREE.Vector2(0, 0) },\n        uMouseStrength: { value: 0.5 },\n        uMouseReact: { value: 1 }\n      }\n    });\n    materialRef.current = material;\n\n    const mesh = new THREE.Mesh(geometry, material);\n    scene.add(mesh);\n\n    const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: false, powerPreference: 'low-power' });\n    rendererRef.current = renderer;\n    renderer.outputColorSpace = THREE.SRGBColorSpace;\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1));\n    renderer.domElement.style.width = '100%';\n    renderer.domElement.style.height = '100%';\n    renderer.domElement.style.display = 'block';\n    container.appendChild(renderer.domElement);\n\n    const resize = () => {\n      const width = Math.max(container.clientWidth, 1);\n      const height = Math.max(container.clientHeight, 1);\n      renderer.setSize(width, height, false);\n      material.uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n    };\n\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    resize();\n\n    const clock = new THREE.Clock();\n    const visibilityObserver = new IntersectionObserver(([entry]) => {\n      visibleRef.current = entry.isIntersecting;\n    });\n    visibilityObserver.observe(container);\n\n    const render = now => {\n      frameRef.current = requestAnimationFrame(render);\n      if (!visibleRef.current || document.hidden) return;\n      const interval = 1000 / fpsRef.current;\n      if (now - lastFrameRef.current < interval) return;\n      lastFrameRef.current = now - ((now - lastFrameRef.current) % interval);\n      const delta = Math.min(clock.getDelta(), 0.1);\n      if (!pausedRef.current) material.uniforms.uTime.value += delta * material.uniforms.uSpeed.value;\n      pointerCurrentRef.current.lerp(pointerTargetRef.current, 0.08);\n      material.uniforms.uPointer.value.copy(pointerCurrentRef.current);\n      renderer.render(scene, camera);\n    };\n\n    render(0);\n\n    const onPointerMove = event => {\n      const rect = container.getBoundingClientRect();\n      pointerTargetRef.current.set(\n        ((event.clientX - rect.left) / Math.max(rect.width, 1)) * 2 - 1,\n        -(((event.clientY - rect.top) / Math.max(rect.height, 1)) * 2 - 1)\n      );\n    };\n    const onPointerLeave = () => pointerTargetRef.current.set(0, 0);\n    container.addEventListener('pointermove', onPointerMove, { passive: true });\n    container.addEventListener('pointerleave', onPointerLeave);\n\n    return () => {\n      cancelAnimationFrame(frameRef.current);\n      resizeObserver.disconnect();\n      visibilityObserver.disconnect();\n      container.removeEventListener('pointermove', onPointerMove);\n      container.removeEventListener('pointerleave', onPointerLeave);\n      geometry.dispose();\n      material.dispose();\n      renderer.dispose();\n      renderer.domElement.remove();\n      materialRef.current = null;\n      rendererRef.current = null;\n    };\n  }, []);\n\n  useEffect(() => {\n    const material = materialRef.current;\n    const renderer = rendererRef.current;\n    if (!material || !renderer) return;\n    const uniforms = material.uniforms;\n    uniforms.uColor.value.set(color);\n    uniforms.uBackgroundColor.value.set(backgroundColor);\n    uniforms.uSpeed.value = speed;\n    uniforms.uCurvature.value = curvature;\n    uniforms.uScanlineStrength.value = scanlineStrength;\n    uniforms.uScanlineFrequency.value = scanlineFrequency;\n    uniforms.uWaveAmplitude.value = waveAmplitude;\n    uniforms.uWaveFrequency.value = waveFrequency;\n    uniforms.uBloom.value = bloom;\n    uniforms.uBloomRadius.value = bloomRadius;\n    uniforms.uNoise.value = noise;\n    uniforms.uVignette.value = vignette;\n    uniforms.uBrightness.value = brightness;\n    uniforms.uPixelation.value = pixelation;\n    uniforms.uRgbShift.value = rgbShift;\n    uniforms.uMouseReact.value = mouseReact ? 1 : 0;\n    uniforms.uMouseStrength.value = mouseStrength;\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, dpr));\n    const container = containerRef.current;\n    if (container) {\n      renderer.setSize(Math.max(container.clientWidth, 1), Math.max(container.clientHeight, 1), false);\n      uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n    }\n  }, [\n    backgroundColor,\n    bloom,\n    bloomRadius,\n    brightness,\n    color,\n    curvature,\n    dpr,\n    mouseReact,\n    mouseStrength,\n    noise,\n    pixelation,\n    rgbShift,\n    scanlineFrequency,\n    scanlineStrength,\n    speed,\n    fps,\n    vignette,\n    waveAmplitude,\n    waveFrequency\n  ]);\n\n  return (\n    <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className || ''}`} style={style} />\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}