{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scene-hero",
  "title": "Scene Hero",
  "description": "A lit three-dimensional object behind a headline, steered by the pointer.",
  "dependencies": [
    "three@^0.185.1"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.tinkererslabs.com/r/render-surface.json"
  ],
  "files": [
    {
      "path": "registry/default/scene-hero/scene-hero.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as THREE from \"three\"\nimport {\n  RenderSurface,\n  useThemeColors,\n  type SurfaceColor,\n} from \"@/registry/default/render-surface/render-surface\"\nimport { cn } from \"@/lib/utils\"\n\nexport type SceneHeroShape =\n  \"torus-knot\" | \"icosahedron\" | \"capsule\" | \"box\" | \"torus\"\n\nexport type SceneHeroProps = React.HTMLAttributes<HTMLDivElement> & {\n  shape?: SceneHeroShape\n  /** Colour of the object. A theme custom property or a CSS colour. */\n  color?: string\n  /** Colour of the light that rims it. */\n  rim?: string\n  metalness?: number\n  roughness?: number\n  /** Turns per second at rest. */\n  speed?: number\n  /** How far the object leans toward the pointer, in radians. */\n  sway?: number\n  paused?: boolean\n  surfaceClassName?: string\n}\n\ntype Scene = {\n  renderer: THREE.WebGLRenderer\n  scene: THREE.Scene\n  camera: THREE.PerspectiveCamera\n  mesh: THREE.Mesh\n  material: THREE.MeshStandardMaterial\n  key: THREE.DirectionalLight\n  fill: THREE.PointLight\n  width: number\n  height: number\n  dpr: number\n}\n\nfunction geometryFor(shape: SceneHeroShape): THREE.BufferGeometry {\n  switch (shape) {\n    case \"icosahedron\":\n      return new THREE.IcosahedronGeometry(1.15, 0)\n    case \"capsule\":\n      return new THREE.CapsuleGeometry(0.7, 1.1, 12, 32)\n    case \"box\":\n      return new THREE.BoxGeometry(1.5, 1.5, 1.5, 2, 2, 2)\n    case \"torus\":\n      return new THREE.TorusGeometry(1, 0.38, 24, 64)\n    default:\n      return new THREE.TorusKnotGeometry(0.9, 0.3, 160, 24)\n  }\n}\n\nfunction toThree([r, g, b]: SurfaceColor) {\n  return new THREE.Color(r, g, b)\n}\n\n/**\n * A lit object on a transparent background, sized to its box and steered by\n * the pointer. This is the one component that asks for three, because the\n * geometry, the material and the two lights are the point of it.\n */\nexport function SceneHero({\n  shape = \"torus-knot\",\n  color = \"--primary\",\n  rim = \"--foreground\",\n  metalness = 0.55,\n  roughness = 0.25,\n  speed = 0.12,\n  sway = 0.35,\n  paused,\n  className,\n  surfaceClassName,\n  children,\n  ...rootProps\n}: SceneHeroProps) {\n  const rootRef = React.useRef<HTMLDivElement>(null)\n  const pointer = React.useRef({ x: 0, y: 0 })\n\n  const tokens = React.useMemo(\n    () => [color, rim].filter((entry) => entry.startsWith(\"--\")),\n    [color, rim]\n  )\n\n  const resolved = useThemeColors(rootRef, tokens)\n  const bodyColor = color.startsWith(\"--\") ? resolved[color] : undefined\n  const rimColor = rim.startsWith(\"--\") ? resolved[rim] : undefined\n  const ready = bodyColor !== undefined && rimColor !== undefined\n\n  const setup = React.useCallback(\n    ({\n      canvas,\n      size,\n    }: {\n      canvas: HTMLCanvasElement\n      size: { width: number; height: number; dpr: number }\n    }): Scene => {\n      const renderer = new THREE.WebGLRenderer({\n        canvas,\n        alpha: true,\n        antialias: true,\n      })\n\n      renderer.setPixelRatio(size.dpr)\n      renderer.setSize(size.width, size.height, false)\n\n      const scene = new THREE.Scene()\n      const camera = new THREE.PerspectiveCamera(\n        42,\n        size.width / Math.max(size.height, 1),\n        0.1,\n        100\n      )\n      camera.position.set(0, 0, 4.6)\n\n      const material = new THREE.MeshStandardMaterial({\n        metalness,\n        roughness,\n      })\n\n      const mesh = new THREE.Mesh(geometryFor(shape), material)\n      scene.add(mesh)\n\n      const key = new THREE.DirectionalLight(0xffffff, 2.4)\n      key.position.set(2.5, 3, 4)\n      scene.add(key)\n\n      const fill = new THREE.PointLight(0xffffff, 18, 20)\n      fill.position.set(-3, -1.5, 2)\n      scene.add(fill)\n\n      scene.add(new THREE.AmbientLight(0xffffff, 0.5))\n\n      return {\n        renderer,\n        scene,\n        camera,\n        mesh,\n        material,\n        key,\n        fill,\n        width: size.width,\n        height: size.height,\n        dpr: size.dpr,\n      }\n    },\n    [metalness, roughness, shape]\n  )\n\n  const draw = React.useCallback(\n    ({\n      size,\n      state,\n      delta,\n    }: {\n      size: { width: number; height: number; dpr: number }\n      state: Scene\n      delta: number\n    }) => {\n      if (\n        size.width !== state.width ||\n        size.height !== state.height ||\n        size.dpr !== state.dpr\n      ) {\n        state.width = size.width\n        state.height = size.height\n        state.dpr = size.dpr\n        state.renderer.setPixelRatio(size.dpr)\n        state.renderer.setSize(size.width, size.height, false)\n        state.camera.aspect = size.width / Math.max(size.height, 1)\n        state.camera.updateProjectionMatrix()\n      }\n\n      if (bodyColor) state.material.color.copy(toThree(bodyColor))\n      if (rimColor) state.fill.color.copy(toThree(rimColor))\n\n      state.mesh.rotation.y += delta * speed * Math.PI * 2\n      state.mesh.rotation.x +=\n        (pointer.current.y * sway - state.mesh.rotation.x) *\n        Math.min(1, delta * 4)\n      state.mesh.position.x +=\n        (pointer.current.x * 0.3 - state.mesh.position.x) *\n        Math.min(1, delta * 4)\n\n      state.renderer.render(state.scene, state.camera)\n    },\n    [bodyColor, rimColor, speed, sway]\n  )\n\n  const teardown = React.useCallback((state: Scene) => {\n    state.mesh.geometry.dispose()\n    state.material.dispose()\n    state.renderer.dispose()\n  }, [])\n\n  return (\n    <div\n      ref={rootRef}\n      data-slot=\"scene-hero\"\n      data-shape={shape}\n      className={cn(\"relative isolate\", className)}\n      onPointerMove={(event) => {\n        const rect = event.currentTarget.getBoundingClientRect()\n        pointer.current.x = ((event.clientX - rect.left) / rect.width) * 2 - 1\n        pointer.current.y = ((event.clientY - rect.top) / rect.height) * 2 - 1\n      }}\n      onPointerLeave={() => {\n        pointer.current.x = 0\n        pointer.current.y = 0\n      }}\n      {...rootProps}\n    >\n      {ready && (\n        <RenderSurface<Scene, \"none\">\n          contextType=\"none\"\n          setup={setup}\n          draw={draw}\n          teardown={teardown}\n          rebuildOnResize={false}\n          paused={paused}\n          className={cn(\"absolute inset-0 -z-10\", surfaceClassName)}\n        />\n      )}\n      {children}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "scene",
    "hero",
    "3d",
    "webgl"
  ],
  "type": "registry:ui"
}