{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "voxel-wall",
  "title": "Voxel Wall",
  "description": "A dark voxel tunnel with volumetric light rays, drifting cubes, and mouse-driven camera parallax.",
  "dependencies": [
    "react",
    "three",
    "@react-three/fiber",
    "@react-three/postprocessing",
    "postprocessing"
  ],
  "devDependencies": [
    "@types/three"
  ],
  "files": [
    {
      "path": "registry/gammaui/voxel-wall/scene.tsx",
      "content": "\"use client\"\n\nimport { Suspense, useEffect, useLayoutEffect, useRef, useState } from \"react\"\nimport { Canvas } from \"@react-three/fiber\"\nimport * as THREE from \"three\"\n\nimport CameraRig from \"./camera-rig\"\nimport { DustMotes, LightEffects, LightSource, SceneLights } from \"./light-rays\"\nimport VoxelWall from \"./wall\"\n\ninterface VoxelWallSceneProps {\n  className?: string\n  height?: string | number\n}\n\nfunction SceneContents() {\n  const sunRef = useRef<THREE.Mesh>(null!)\n  const [sun, setSun] = useState<THREE.Mesh | null>(null)\n\n  useLayoutEffect(() => {\n    if (sunRef.current) setSun(sunRef.current)\n  }, [])\n\n  return (\n    <>\n      <SceneLights />\n      <VoxelWall />\n      <LightSource ref={sunRef} />\n      <DustMotes />\n      <CameraRig />\n      {sun && <LightEffects sun={sun} />}\n    </>\n  )\n}\n\nexport default function VoxelWallScene({\n  className,\n  height = \"100%\",\n}: VoxelWallSceneProps) {\n  const [mounted, setMounted] = useState(false)\n\n  useEffect(() => {\n    setMounted(true)\n  }, [])\n\n  return (\n    <div\n      className={className}\n      style={{ width: \"100%\", height, background: \"#000000\" }}\n    >\n      {mounted ? (\n        <Canvas\n          dpr={[1, 2]}\n          shadows\n          camera={{ position: [0.15, -2.35, 4.2], fov: 52, near: 0.1, far: 40 }}\n          gl={{\n            antialias: false,\n            alpha: false,\n            toneMapping: THREE.ACESFilmicToneMapping,\n            toneMappingExposure: 1.05,\n          }}\n          style={{ width: \"100%\", height: \"100%\" }}\n        >\n          <color attach=\"background\" args={[\"#000000\"]} />\n          <fog attach=\"fog\" args={[\"#000000\", 6, 22]} />\n          <Suspense fallback={null}>\n            <SceneContents />\n          </Suspense>\n        </Canvas>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/gammaui/voxel-wall/wall.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useMemo, useRef } from \"react\"\nimport { useFrame } from \"@react-three/fiber\"\nimport * as THREE from \"three\"\n\nconst SPACING = 0.44\nconst CUBE = 0.38\nconst ROOM_W = 9.2\nconst ROOM_H = 6.4\nconst ROOM_D = 10.5\n\nconst HOLE_CX = 0.55\nconst HOLE_CY = 0.85\nconst HOLE_W = 3.4\nconst HOLE_H = 2.55\n\ninterface CubeInstance {\n  x: number\n  y: number\n  z: number\n  rotX: number\n  rotY: number\n  rotZ: number\n  scale: number\n  jitter: boolean\n  phase: number\n  driftSpeed: number\n  maxDrift: number\n}\n\nconst LIGHT_TARGET = {\n  x: HOLE_CX,\n  y: HOLE_CY,\n  z: -ROOM_D - 0.35,\n}\n\nfunction mulberry32(seed: number) {\n  return () => {\n    seed |= 0\n    seed = (seed + 0x6d2b79f5) | 0\n    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed)\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n  }\n}\n\nfunction isInHole(\n  x: number,\n  y: number,\n  z: number,\n  rand: () => number\n): false | true | \"debris\" {\n  const backZ = -ROOM_D\n  if (Math.abs(z - backZ) > SPACING * 0.6) return false\n\n  const dx = Math.abs(x - HOLE_CX)\n  const dy = Math.abs(y - HOLE_CY)\n\n  const inside = dx < HOLE_W / 2 && dy < HOLE_H / 2\n  if (!inside) return false\n\n  const edgeDist = Math.min(HOLE_W / 2 - dx, HOLE_H / 2 - dy)\n\n  if (edgeDist < SPACING * 0.85) {\n    if (rand() < 0.42) return true\n    if (rand() < 0.72) return \"debris\"\n    return false\n  }\n\n  if (edgeDist < SPACING * 1.6 && rand() < 0.38) return \"debris\"\n\n  return true\n}\n\nfunction pushCube(\n  cubes: CubeInstance[],\n  x: number,\n  y: number,\n  z: number,\n  rand: () => number,\n  edge = false,\n  debris = false\n) {\n  const spread = edge || debris ? 0.22 : 0.06\n  const bx = x + (rand() - 0.5) * spread\n  const by = y + (rand() - 0.5) * spread\n  const bz = z + (rand() - 0.5) * (debris ? 0.5 : spread)\n\n  const dx = LIGHT_TARGET.x - bx\n  const dy = LIGHT_TARGET.y - by\n  const dz = LIGHT_TARGET.z - bz\n  const dist = Math.hypot(dx, dy, dz)\n\n  cubes.push({\n    x: bx,\n    y: by,\n    z: bz,\n    rotX: edge || debris ? (rand() - 0.5) * 0.7 : 0,\n    rotY: edge || debris ? (rand() - 0.5) * 0.9 : 0,\n    rotZ: edge || debris ? (rand() - 0.5) * 0.55 : 0,\n    scale:\n      CUBE *\n      (debris\n        ? 0.65 + rand() * 0.45\n        : edge\n          ? 0.78 + rand() * 0.35\n          : 0.92 + rand() * 0.12),\n    jitter: edge || debris,\n    phase: rand() * Math.PI * 2,\n    driftSpeed: debris\n      ? 0.18 + rand() * 0.14\n      : edge\n        ? 0.1 + rand() * 0.07\n        : 0.03 + rand() * 0.025,\n    maxDrift: debris ? dist * 0.98 : edge ? dist * 0.55 : dist * 0.18,\n  })\n}\n\nfunction generateTunnel(): CubeInstance[] {\n  const rand = mulberry32(481516)\n  const cubes: CubeInstance[] = []\n\n  const halfW = ROOM_W / 2\n  const halfH = ROOM_H / 2\n  const backZ = -ROOM_D\n\n  for (let z = 0; z >= backZ; z -= SPACING) {\n    for (let x = -halfW; x <= halfW; x += SPACING) {\n      const hole = isInHole(x, -halfH, z, rand)\n      if (hole === true) continue\n      if (hole === \"debris\") {\n        pushCube(cubes, x, -halfH, z + rand() * 0.4, rand, true, true)\n        continue\n      }\n      pushCube(cubes, x, -halfH, z, rand, z < backZ + SPACING * 2)\n    }\n  }\n\n  for (let z = 0; z >= backZ; z -= SPACING) {\n    for (let x = -halfW; x <= halfW; x += SPACING) {\n      const hole = isInHole(x, halfH, z, rand)\n      if (hole === true) continue\n      if (hole === \"debris\") {\n        pushCube(cubes, x, halfH, z + rand() * 0.3, rand, true, true)\n        continue\n      }\n      pushCube(cubes, x, halfH, z, rand)\n    }\n  }\n\n  for (let z = 0; z >= backZ; z -= SPACING) {\n    for (let y = -halfH; y <= halfH; y += SPACING) {\n      const hole = isInHole(-halfW, y, z, rand)\n      if (hole === true) continue\n      if (hole === \"debris\") {\n        pushCube(cubes, -halfW, y, z + rand() * 0.35, rand, true, true)\n        continue\n      }\n      pushCube(cubes, -halfW, y, z, rand, z < backZ + SPACING * 3)\n    }\n  }\n\n  for (let z = 0; z >= backZ; z -= SPACING) {\n    for (let y = -halfH; y <= halfH; y += SPACING) {\n      if (Math.abs(y) < 0.2 && z > backZ + SPACING * 4 && rand() < 0.18)\n        continue\n      const hole = isInHole(halfW, y, z, rand)\n      if (hole === true) continue\n      if (hole === \"debris\") {\n        pushCube(cubes, halfW, y, z + rand() * 0.35, rand, true, true)\n        continue\n      }\n      pushCube(cubes, halfW, y, z, rand, z < backZ + SPACING * 2)\n    }\n  }\n\n  for (let y = -halfH; y <= halfH; y += SPACING) {\n    for (let x = -halfW; x <= halfW; x += SPACING) {\n      const hole = isInHole(x, y, backZ, rand)\n      if (hole === true) continue\n      if (hole === \"debris\") {\n        pushCube(\n          cubes,\n          x + (rand() - 0.5) * 0.35,\n          y + (rand() - 0.5) * 0.35,\n          backZ + rand() * 0.8,\n          rand,\n          true,\n          true\n        )\n        continue\n      }\n\n      const nearHole =\n        Math.abs(x - HOLE_CX) < HOLE_W / 2 + SPACING * 1.5 &&\n        Math.abs(y - HOLE_CY) < HOLE_H / 2 + SPACING * 1.5\n      pushCube(cubes, x, y, backZ, rand, nearHole)\n    }\n  }\n\n  for (let i = 0; i < 28; i++) {\n    pushCube(\n      cubes,\n      HOLE_CX + (rand() - 0.5) * HOLE_W * 1.1,\n      HOLE_CY + (rand() - 0.5) * HOLE_H * 1.1,\n      backZ + 0.4 + rand() * 2.2,\n      rand,\n      true,\n      true\n    )\n  }\n\n  return cubes\n}\n\nexport default function VoxelWall() {\n  const meshRef = useRef<THREE.InstancedMesh>(null)\n  const instances = useMemo(() => generateTunnel(), [])\n  const dummy = useMemo(() => new THREE.Object3D(), [])\n\n  useEffect(() => {\n    const mesh = meshRef.current\n    if (!mesh) return\n\n    instances.forEach((cube, i) => {\n      dummy.position.set(cube.x, cube.y, cube.z)\n      dummy.rotation.set(cube.rotX, cube.rotY, cube.rotZ)\n      dummy.scale.setScalar(cube.scale)\n      dummy.updateMatrix()\n      mesh.setMatrixAt(i, dummy.matrix)\n    })\n    mesh.instanceMatrix.needsUpdate = true\n  }, [instances, dummy])\n\n  useFrame(({ clock }) => {\n    const mesh = meshRef.current\n    if (!mesh) return\n\n    const t = clock.elapsedTime\n\n    instances.forEach((cube, i) => {\n      const dx = LIGHT_TARGET.x - cube.x\n      const dy = LIGHT_TARGET.y - cube.y\n      const dz = LIGHT_TARGET.z - cube.z\n      const dist = Math.hypot(dx, dy, dz) || 1\n\n      const cycle = (t * cube.driftSpeed + cube.phase) % 1\n      const eased = cycle * cycle * (3 - 2 * cycle)\n      const suck = eased * eased\n      const pull = suck * cube.maxDrift\n\n      const px = cube.x + (dx / dist) * pull\n      const py = cube.y + (dy / dist) * pull\n      const pz = cube.z + (dz / dist) * pull\n\n      const wobble = cube.jitter\n        ? Math.sin(t * 1.4 + cube.phase) * 0.012 * (1 - suck * 0.6)\n        : Math.sin(t * 0.6 + cube.phase) * 0.004 * (1 - suck * 0.4)\n\n      dummy.position.set(\n        px + Math.sin(t * 0.7 + cube.phase) * 0.003 * (1 - suck),\n        py + wobble,\n        pz\n      )\n      dummy.rotation.set(\n        cube.rotX + suck * 0.5 + Math.sin(t + cube.phase) * 0.01,\n        cube.rotY + suck * 0.8,\n        cube.rotZ + suck * 0.4 + Math.cos(t * 1.1 + cube.phase) * 0.01\n      )\n      dummy.scale.setScalar(\n        cube.scale * (1 - suck * (cube.jitter ? 0.55 : 0.3))\n      )\n      dummy.updateMatrix()\n      mesh.setMatrixAt(i, dummy.matrix)\n    })\n\n    mesh.instanceMatrix.needsUpdate = true\n  })\n\n  return (\n    <instancedMesh\n      ref={meshRef}\n      args={[undefined, undefined, instances.length]}\n      castShadow\n      receiveShadow\n    >\n      <boxGeometry args={[1, 1, 1]} />\n      <meshStandardMaterial color=\"#050505\" roughness={0.98} metalness={0.02} />\n    </instancedMesh>\n  )\n}\n\nexport const HOLE_CENTER = new THREE.Vector3(HOLE_CX, HOLE_CY, -ROOM_D - 0.35)\n\nexport const HOLE_SIZE = {\n  width: HOLE_W * 0.95,\n  height: HOLE_H * 0.95,\n}\n\nexport const LIGHT_ORIGIN = new THREE.Vector3(\n  HOLE_CX + 0.9,\n  HOLE_CY + 1.4,\n  -ROOM_D - 2.2\n)\n\nexport const ROOM_DEPTH = ROOM_D\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/gammaui/voxel-wall/light-rays.tsx",
      "content": "\"use client\"\n\nimport { forwardRef, useMemo, useRef } from \"react\"\nimport { useFrame } from \"@react-three/fiber\"\nimport {\n  Bloom,\n  EffectComposer,\n  GodRays,\n  Noise,\n  Vignette,\n} from \"@react-three/postprocessing\"\nimport { BlendFunction } from \"postprocessing\"\nimport * as THREE from \"three\"\n\nimport { HOLE_CENTER, HOLE_SIZE, LIGHT_ORIGIN, ROOM_DEPTH } from \"./wall\"\n\ninterface LightSourceProps {\n  position?: [number, number, number]\n}\n\nexport const LightSource = forwardRef<THREE.Mesh, LightSourceProps>(\n  function LightSource({ position }, ref) {\n    useFrame(({ clock }) => {\n      if (!ref || typeof ref === \"function\" || !ref.current) return\n\n      const pulse = 0.96 + Math.sin(clock.elapsedTime * 1.2) * 0.04\n      const mat = ref.current.material as THREE.MeshBasicMaterial\n      mat.opacity = pulse\n    })\n\n    return (\n      <mesh\n        ref={ref}\n        position={position ?? [HOLE_CENTER.x, HOLE_CENTER.y, HOLE_CENTER.z]}\n        rotation={[0, 0, -0.08]}\n      >\n        <planeGeometry\n          args={[HOLE_SIZE.width * 1.15, HOLE_SIZE.height * 1.2]}\n        />\n        <meshBasicMaterial\n          color=\"#ffffff\"\n          transparent\n          opacity={1}\n          depthWrite={false}\n          toneMapped={false}\n        />\n      </mesh>\n    )\n  }\n)\n\nexport function DustMotes() {\n  const pointsRef = useRef<THREE.Points>(null)\n\n  const positions = useMemo(() => {\n    const count = 220\n    const arr = new Float32Array(count * 3)\n    for (let i = 0; i < count; i++) {\n      arr[i * 3] = HOLE_CENTER.x + (Math.random() - 0.5) * HOLE_SIZE.width * 1.6\n      arr[i * 3 + 1] =\n        HOLE_CENTER.y + (Math.random() - 0.5) * HOLE_SIZE.height * 1.5\n      arr[i * 3 + 2] = -ROOM_DEPTH + Math.random() * (ROOM_DEPTH + 2)\n    }\n    return arr\n  }, [])\n\n  const geometry = useMemo(() => {\n    const geo = new THREE.BufferGeometry()\n    geo.setAttribute(\"position\", new THREE.BufferAttribute(positions, 3))\n    return geo\n  }, [positions])\n\n  useFrame(({ clock }) => {\n    const points = pointsRef.current\n    if (!points) return\n    points.rotation.z = Math.sin(clock.elapsedTime * 0.06) * 0.015\n  })\n\n  return (\n    <points ref={pointsRef} geometry={geometry}>\n      <pointsMaterial\n        size={0.022}\n        color=\"#ffffff\"\n        transparent\n        opacity={0.14}\n        depthWrite={false}\n        blending={THREE.AdditiveBlending}\n        sizeAttenuation\n      />\n    </points>\n  )\n}\n\ninterface LightEffectsProps {\n  sun: THREE.Mesh\n}\n\nexport function LightEffects({ sun }: LightEffectsProps) {\n  return (\n    <EffectComposer multisampling={0}>\n      <GodRays\n        sun={sun}\n        blendFunction={BlendFunction.SCREEN}\n        samples={80}\n        density={0.96}\n        decay={0.92}\n        weight={0.5}\n        exposure={0.42}\n        clampMax={1}\n        blur\n      />\n      <Bloom\n        luminanceThreshold={0.55}\n        luminanceSmoothing={0.35}\n        intensity={1.15}\n        mipmapBlur\n      />\n      <Noise opacity={0.07} blendFunction={BlendFunction.OVERLAY} />\n      <Vignette eskil={false} offset={0.18} darkness={1.15} />\n    </EffectComposer>\n  )\n}\n\nexport function SceneLights() {\n  return (\n    <>\n      <ambientLight intensity={0.008} color=\"#0a0a0a\" />\n      <pointLight\n        position={[LIGHT_ORIGIN.x, LIGHT_ORIGIN.y, LIGHT_ORIGIN.z]}\n        intensity={340}\n        color=\"#ffffff\"\n        distance={26}\n        decay={2}\n      />\n    </>\n  )\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/gammaui/voxel-wall/camera-rig.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport { useFrame, useThree } from \"@react-three/fiber\"\nimport * as THREE from \"three\"\n\nimport { HOLE_CENTER } from \"./wall\"\n\nconst BASE_POSITION = new THREE.Vector3(0.15, -2.35, 4.2)\nconst LOOK_TARGET = new THREE.Vector3(\n  HOLE_CENTER.x,\n  HOLE_CENTER.y - 0.1,\n  HOLE_CENTER.z + 1.5\n)\n\nexport default function CameraRig() {\n  const mouse = useRef({ x: 0, y: 0 })\n  const { camera } = useThree()\n\n  useEffect(() => {\n    const onPointerMove = (event: PointerEvent) => {\n      mouse.current.x = (event.clientX / window.innerWidth) * 2 - 1\n      mouse.current.y = -(event.clientY / window.innerHeight) * 2 + 1\n    }\n\n    window.addEventListener(\"pointermove\", onPointerMove, { passive: true })\n    return () => window.removeEventListener(\"pointermove\", onPointerMove)\n  }, [])\n\n  useFrame(({ clock }) => {\n    const t = clock.elapsedTime\n\n    const swayX = Math.sin(t * 0.18) * 0.05 + Math.cos(t * 0.14) * 0.03\n    const swayY = Math.cos(t * 0.16) * 0.025 + Math.sin(t * 0.11) * 0.018\n    const parallaxX = mouse.current.x * 0.18\n    const parallaxY = mouse.current.y * 0.1\n\n    camera.position.x = THREE.MathUtils.lerp(\n      camera.position.x,\n      BASE_POSITION.x + swayX + parallaxX,\n      0.05\n    )\n    camera.position.y = THREE.MathUtils.lerp(\n      camera.position.y,\n      BASE_POSITION.y + swayY + parallaxY * 0.35,\n      0.05\n    )\n    camera.position.z = THREE.MathUtils.lerp(\n      camera.position.z,\n      BASE_POSITION.z + Math.sin(t * 0.12) * 0.06,\n      0.04\n    )\n\n    const lookAt = LOOK_TARGET.clone()\n    lookAt.x += parallaxX * 0.12\n    lookAt.y += parallaxY * 0.08\n    camera.lookAt(lookAt)\n  })\n\n  return null\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}