{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "GridDistortion-JS-CSS",
	"title": "GridDistortion",
	"description": "Warped grid mesh distorts smoothly reacting to cursor.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "GridDistortion.css",
			"target": "@components/GridDistortion.css",
			"content": ".distortion-container {\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "GridDistortion.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './GridDistortion.css';\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n  vUv = uv;\n  vPosition = position;\n  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n  vec2 uv = vUv;\n  vec4 offset = texture2D(uDataTexture, vUv);\n  gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}`;\n\nconst GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {\n  const containerRef = useRef(null);\n  const sceneRef = useRef(null);\n  const rendererRef = useRef(null);\n  const cameraRef = useRef(null);\n  const planeRef = useRef(null);\n  const imageAspectRef = useRef(1);\n  const animationIdRef = useRef(null);\n  const resizeObserverRef = useRef(null);\n\n  useEffect(() => {\n    if (!containerRef.current) return;\n\n    const container = containerRef.current;\n\n    const scene = new THREE.Scene();\n    sceneRef.current = scene;\n\n    const renderer = new THREE.WebGLRenderer({\n      antialias: true,\n      alpha: true,\n      powerPreference: 'high-performance'\n    });\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n    renderer.setClearColor(0x000000, 0);\n    rendererRef.current = renderer;\n\n    container.innerHTML = '';\n    container.appendChild(renderer.domElement);\n\n    const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n    camera.position.z = 2;\n    cameraRef.current = camera;\n\n    const uniforms = {\n      time: { value: 0 },\n      resolution: { value: new THREE.Vector4() },\n      uTexture: { value: null },\n      uDataTexture: { value: null }\n    };\n\n    const textureLoader = new THREE.TextureLoader();\n    textureLoader.load(imageSrc, texture => {\n      texture.minFilter = THREE.LinearFilter;\n      texture.magFilter = THREE.LinearFilter;\n      texture.wrapS = THREE.ClampToEdgeWrapping;\n      texture.wrapT = THREE.ClampToEdgeWrapping;\n      imageAspectRef.current = texture.image.width / texture.image.height;\n      uniforms.uTexture.value = texture;\n      handleResize();\n    });\n\n    const size = grid;\n    const data = new Float32Array(4 * size * size);\n    for (let i = 0; i < size * size; i++) {\n      data[i * 4] = Math.random() * 255 - 125;\n      data[i * 4 + 1] = Math.random() * 255 - 125;\n    }\n\n    const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n    dataTexture.needsUpdate = true;\n    uniforms.uDataTexture.value = dataTexture;\n\n    const material = new THREE.ShaderMaterial({\n      side: THREE.DoubleSide,\n      uniforms,\n      vertexShader,\n      fragmentShader,\n      transparent: true\n    });\n\n    const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n    const plane = new THREE.Mesh(geometry, material);\n    planeRef.current = plane;\n    scene.add(plane);\n\n    const handleResize = () => {\n      if (!container || !renderer || !camera) return;\n\n      const rect = container.getBoundingClientRect();\n      const width = rect.width;\n      const height = rect.height;\n\n      if (width === 0 || height === 0) return;\n\n      const containerAspect = width / height;\n\n      renderer.setSize(width, height);\n\n      if (plane) {\n        plane.scale.set(containerAspect, 1, 1);\n      }\n\n      const frustumHeight = 1;\n      const frustumWidth = frustumHeight * containerAspect;\n      camera.left = -frustumWidth / 2;\n      camera.right = frustumWidth / 2;\n      camera.top = frustumHeight / 2;\n      camera.bottom = -frustumHeight / 2;\n      camera.updateProjectionMatrix();\n\n      uniforms.resolution.value.set(width, height, 1, 1);\n    };\n\n    if (window.ResizeObserver) {\n      const resizeObserver = new ResizeObserver(() => {\n        handleResize();\n      });\n      resizeObserver.observe(container);\n      resizeObserverRef.current = resizeObserver;\n    } else {\n      window.addEventListener('resize', handleResize);\n    }\n\n    const mouseState = {\n      x: 0,\n      y: 0,\n      prevX: 0,\n      prevY: 0,\n      vX: 0,\n      vY: 0\n    };\n\n    const handleMouseMove = e => {\n      const rect = container.getBoundingClientRect();\n      const x = (e.clientX - rect.left) / rect.width;\n      const y = 1 - (e.clientY - rect.top) / rect.height;\n      mouseState.vX = x - mouseState.prevX;\n      mouseState.vY = y - mouseState.prevY;\n      Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n    };\n\n    const handleMouseLeave = () => {\n      if (dataTexture) {\n        dataTexture.needsUpdate = true;\n      }\n      Object.assign(mouseState, {\n        x: 0,\n        y: 0,\n        prevX: 0,\n        prevY: 0,\n        vX: 0,\n        vY: 0\n      });\n    };\n\n    container.addEventListener('mousemove', handleMouseMove);\n    container.addEventListener('mouseleave', handleMouseLeave);\n\n    handleResize();\n\n    const animate = () => {\n      animationIdRef.current = requestAnimationFrame(animate);\n\n      if (!renderer || !scene || !camera) return;\n\n      uniforms.time.value += 0.05;\n\n      const data = dataTexture.image.data;\n      for (let i = 0; i < size * size; i++) {\n        data[i * 4] *= relaxation;\n        data[i * 4 + 1] *= relaxation;\n      }\n\n      const gridMouseX = size * mouseState.x;\n      const gridMouseY = size * mouseState.y;\n      const maxDist = size * mouse;\n\n      for (let i = 0; i < size; i++) {\n        for (let j = 0; j < size; j++) {\n          const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n          if (distSq < maxDist * maxDist) {\n            const index = 4 * (i + size * j);\n            const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n            data[index] += strength * 100 * mouseState.vX * power;\n            data[index + 1] -= strength * 100 * mouseState.vY * power;\n          }\n        }\n      }\n\n      dataTexture.needsUpdate = true;\n      renderer.render(scene, camera);\n    };\n\n    animate();\n\n    return () => {\n      if (animationIdRef.current) {\n        cancelAnimationFrame(animationIdRef.current);\n      }\n\n      if (resizeObserverRef.current) {\n        resizeObserverRef.current.disconnect();\n      } else {\n        window.removeEventListener('resize', handleResize);\n      }\n\n      container.removeEventListener('mousemove', handleMouseMove);\n      container.removeEventListener('mouseleave', handleMouseLeave);\n\n      if (renderer) {\n        renderer.dispose();\n        renderer.forceContextLoss();\n        if (container.contains(renderer.domElement)) {\n          container.removeChild(renderer.domElement);\n        }\n      }\n\n      if (geometry) geometry.dispose();\n      if (material) material.dispose();\n      if (dataTexture) dataTexture.dispose();\n      if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n      sceneRef.current = null;\n      rendererRef.current = null;\n      cameraRef.current = null;\n      planeRef.current = null;\n    };\n  }, [grid, mouse, strength, relaxation, imageSrc]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`distortion-container ${className}`}\n      style={{\n        width: '100%',\n        height: '100%',\n        minWidth: '0',\n        minHeight: '0'\n      }}\n    />\n  );\n};\n\nexport default GridDistortion;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}