{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-dive",
  "type": "registry:component",
  "title": "ScrollDive",
  "description": "Scroll into the page, not down it — a sticky canvas where scroll flies the camera forward through a tunnel of glowing rings. Reuses your Lenis-smoothed scroll via one scrubbed ScrollTrigger.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/postprocessing",
    "gsap"
  ],
  "registryDependencies": [
    "https://prism.icglabs.co/r/canvas-perf.json"
  ],
  "tier": "free",
  "note": "Pairs with a Lenis + gsap.ticker smooth-scroll setup (see SmoothScroll). Render via next/dynamic ssr:false.",
  "files": [
    {
      "path": "components/prism/ScrollDive.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useRef, useState, useLayoutEffect } from \"react\";\nimport { useDeviceTier } from \"@/lib/useDeviceTier\";\nimport { AutoDpr, PauseWhenOffscreen } from \"@/components/showcase/CanvasPerf\";\nimport { Canvas, useFrame, useThree } from \"@react-three/fiber\";\nimport { EffectComposer, Bloom } from \"@react-three/postprocessing\";\nimport * as THREE from \"three\";\nimport gsap from \"gsap\";\nimport { ScrollTrigger } from \"gsap/ScrollTrigger\";\n\ngsap.registerPlugin(ScrollTrigger);\n\n/**\n * ScrollDive — scroll *into* the page, not down it. A sticky canvas where scroll\n * flies the camera FORWARD through a tunnel of glowing rings. Driven by one\n * scrubbed ScrollTrigger reading the same Lenis-smoothed scroll the site uses.\n * Honors prefers-reduced-motion (static composed shot).\n */\nconst PALETTE = [\"#8b5cff\", \"#5b7cff\", \"#1fd4e6\", \"#ff3d81\", \"#ffb24d\", \"#9bf25a\"];\nconst RING_COUNT = 30;\nconst SPACING = 7;\nconst DEPTH = RING_COUNT * SPACING;\n\nfunction Rings() {\n  const rings = useMemo(\n    () =>\n      Array.from({ length: RING_COUNT }, (_, i) => ({\n        z: -i * SPACING - 6,\n        x: (Math.random() - 0.5) * 2.6,\n        y: (Math.random() - 0.5) * 2.6,\n        rot: Math.random() * Math.PI,\n        color: PALETTE[i % PALETTE.length],\n        r: 2.3 + Math.random() * 1.5,\n      })),\n    []\n  );\n  return (\n    <>\n      {rings.map((rg, i) => (\n        <mesh key={i} position={[rg.x, rg.y, rg.z]} rotation={[0, 0, rg.rot]}>\n          <torusGeometry args={[rg.r, 0.06, 16, 80]} />\n          <meshStandardMaterial\n            color={rg.color}\n            emissive={rg.color}\n            emissiveIntensity={1.3}\n            roughness={0.4}\n            toneMapped={false}\n          />\n        </mesh>\n      ))}\n    </>\n  );\n}\n\nfunction Diver({ progress, reduce }: { progress: React.RefObject<number>; reduce: boolean }) {\n  const camera = useThree((s) => s.camera);\n  useFrame(() => {\n    const p = reduce ? 0.02 : THREE.MathUtils.clamp(progress.current, 0, 1);\n    const z = 2 - p * (DEPTH - 12);\n    // gentle banking sway so the flight feels piloted, not on rails\n    const sx = Math.sin(p * 6.0) * 0.7;\n    const sy = Math.cos(p * 5.0) * 0.5;\n    camera.position.set(sx, sy, z);\n    camera.lookAt(Math.sin((p + 0.04) * 6.0) * 0.7, Math.cos((p + 0.04) * 5.0) * 0.5, z - 10);\n  });\n  return null;\n}\n\nexport function ScrollDive({ acts = 4 }: { acts?: number }) {\n  const wrap = useRef<HTMLDivElement>(null);\n  const progress = useRef(0);\n  const reduceRef = useRef(false);\n  const q = useDeviceTier();\n  const [low, setLow] = useState(false);\n  const bloomOn = q.bloom && !low;\n\n  useLayoutEffect(() => {\n    const el = wrap.current;\n    if (!el) return;\n    reduceRef.current = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    const st = ScrollTrigger.create({\n      trigger: el,\n      start: \"top top\",\n      end: \"bottom bottom\",\n      scrub: reduceRef.current ? false : 0.6,\n      invalidateOnRefresh: true,\n      onUpdate: (self) => {\n        progress.current = self.progress;\n      },\n    });\n    return () => st.kill();\n  }, []);\n\n  return (\n    <div ref={wrap} style={{ height: `${acts * 100}vh`, position: \"relative\" }}>\n      <div style={{ position: \"sticky\", top: 0, height: \"100vh\", width: \"100%\" }}>\n        <Canvas\n          dpr={q.dpr}\n          gl={{ antialias: false, powerPreference: \"high-performance\" }}\n          camera={{ position: [0, 0, 2], fov: 62, near: 0.1, far: 240 }}\n        >\n          <color attach=\"background\" args={[\"#07070c\"]} />\n          <fog attach=\"fog\" args={[\"#07070c\", 6, 70]} />\n          <ambientLight intensity={0.4} />\n          <Rings />\n          <Diver progress={progress} reduce={reduceRef.current} />\n          <AutoDpr onLow={() => setLow(true)} />\n          <PauseWhenOffscreen />\n          {bloomOn && (\n            <EffectComposer>\n              <Bloom intensity={1.1} luminanceThreshold={0.2} luminanceSmoothing={0.3} mipmapBlur radius={0.7} />\n            </EffectComposer>\n          )}\n        </Canvas>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/prism/ScrollDive.tsx"
    }
  ]
}