{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "blob-sphere",
  "description": "A sphere displaced by 3D noise in the vertex shader — drag to spin.",
  "dependencies": [
    "@react-three/drei",
    "@react-three/fiber",
    "lil-gui",
    "three"
  ],
  "files": [
    {
      "path": "registry/new-york/shaders/blob-sphere.tsx",
      "content": "\"use client\";\n\n/**\n * A sphere displaced by 3D noise in the vertex shader — drag to spin.\n */\n\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { OrbitControls } from \"@react-three/drei\";\nimport type { Mesh, ShaderMaterial } from \"three\";\nimport GUI from \"lil-gui\";\n\nconst vertexShader = /* glsl */ `\n  uniform float uTime;\n  uniform float uFrequency;\n  uniform float uAmplitude;\n  uniform float uSpeed;\n  varying float vDisplacement;\n\n  // 3D simplex noise (Ashima / Ian McEwan), returns ~[-1, 1]\n  vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\n  vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\n  vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }\n  vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }\n\n  float snoise(vec3 v) {\n    const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);\n    const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n    vec3 i  = floor(v + dot(v, C.yyy));\n    vec3 x0 = v - i + dot(i, C.xxx);\n    vec3 g = step(x0.yzx, x0.xyz);\n    vec3 l = 1.0 - g;\n    vec3 i1 = min(g.xyz, l.zxy);\n    vec3 i2 = max(g.xyz, l.zxy);\n    vec3 x1 = x0 - i1 + C.xxx;\n    vec3 x2 = x0 - i2 + C.yyy;\n    vec3 x3 = x0 - D.yyy;\n    i = mod289(i);\n    vec4 p = permute(permute(permute(\n              i.z + vec4(0.0, i1.z, i2.z, 1.0))\n            + i.y + vec4(0.0, i1.y, i2.y, 1.0))\n            + i.x + vec4(0.0, i1.x, i2.x, 1.0));\n    float n_ = 0.142857142857;\n    vec3 ns = n_ * D.wyz - D.xzx;\n    vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n    vec4 x_ = floor(j * ns.z);\n    vec4 y_ = floor(j - 7.0 * x_);\n    vec4 x = x_ * ns.x + ns.yyyy;\n    vec4 y = y_ * ns.x + ns.yyyy;\n    vec4 h = 1.0 - abs(x) - abs(y);\n    vec4 b0 = vec4(x.xy, y.xy);\n    vec4 b1 = vec4(x.zw, y.zw);\n    vec4 s0 = floor(b0) * 2.0 + 1.0;\n    vec4 s1 = floor(b1) * 2.0 + 1.0;\n    vec4 sh = -step(h, vec4(0.0));\n    vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n    vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n    vec3 p0 = vec3(a0.xy, h.x);\n    vec3 p1 = vec3(a0.zw, h.y);\n    vec3 p2 = vec3(a1.xy, h.z);\n    vec3 p3 = vec3(a1.zw, h.w);\n    vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1),\n                                   dot(p2, p2), dot(p3, p3)));\n    p0 *= norm.x;\n    p1 *= norm.y;\n    p2 *= norm.z;\n    p3 *= norm.w;\n    vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1),\n                            dot(x2, x2), dot(x3, x3)), 0.0);\n    m = m * m;\n    return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1),\n                                  dot(p2, x2), dot(p3, x3)));\n  }\n\n  void main() {\n    // sample noise in the sphere's own space, animated over time\n    float noise = snoise(position * uFrequency + uTime * uSpeed);\n    float displacement = noise * uAmplitude;\n    vDisplacement = displacement;\n\n    // push the vertex out along its normal by the noise amount\n    vec3 newPosition = position + normal * displacement;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);\n  }\n`;\n\nconst fragmentShader = /* glsl */ `\n  varying float vDisplacement;\n  void main() {\n    vec3 colorA = vec3(0.1, 0.2, 0.9);\n    vec3 colorB = vec3(0.9, 0.3, 0.6);\n    vec3 color = mix(colorA, colorB, vDisplacement * 2.0 + 0.5);\n    gl_FragColor = vec4(color, 1.0);\n  }\n`;\n\nconst Blob = ({ interactive }: { interactive: boolean }) => {\n  const meshRef = useRef<Mesh>(null);\n  const materialRef = useRef<ShaderMaterial>(null);\n  const uniforms = useMemo(\n    () => ({\n      uTime: { value: 0 },\n      uFrequency: { value: 1.25 },\n      uAmplitude: { value: 1 },\n      uSpeed: { value: 0.23 },\n    }),\n    []\n  );\n\n  useFrame(({ clock }) => {\n    const t = clock.getElapsedTime();\n    if (materialRef.current) {\n      materialRef.current.uniforms.uTime.value = t;\n    }\n    if (!interactive && meshRef.current) {\n      meshRef.current.rotation.y = t * 0.3;\n    }\n  });\n\n  useEffect(() => {\n    if (!interactive) return;\n    const material = materialRef.current;\n    if (!material) return;\n    const gui = new GUI();\n    gui.domElement.style.zIndex = \"10000\"; // above the z-[9999] modal\n    gui.add(material.uniforms.uFrequency, \"value\", 0, 5).name(\"frequency\");\n    gui.add(material.uniforms.uAmplitude, \"value\", 0, 1).name(\"amplitude\");\n    gui.add(material.uniforms.uSpeed, \"value\", 0, 2).name(\"speed\");\n    return () => gui.destroy();\n  }, [interactive]);\n\n  return (\n    <mesh ref={meshRef}>\n      <sphereGeometry args={[1, 128, 128]} />\n      <shaderMaterial\n        ref={materialRef}\n        vertexShader={vertexShader}\n        fragmentShader={fragmentShader}\n        uniforms={uniforms}\n      />\n    </mesh>\n  );\n};\n\nconst BlobSphere = ({\n  className,\n  dpr = 2,\n  interactive = false,\n}: {\n  className?: string;\n  dpr?: number;\n  interactive?: boolean;\n}) => {\n  return (\n    <Canvas className={className} dpr={dpr} camera={{ position: [0, 0, 3] }}>\n      <color attach=\"background\" args={[0, 0, 0]} />\n      <Blob interactive={interactive} />\n      {interactive && <OrbitControls enablePan={false} enableZoom={false} />}\n    </Canvas>\n  );\n};\n\nexport default BlobSphere;\n",
      "type": "registry:component",
      "target": "components/pixel-perfect/blob-sphere.tsx"
    }
  ],
  "type": "registry:block"
}