{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gl-stage",
  "title": "GL Stage",
  "description": "Creates a WebGL canvas near the viewport and falls back to a description if WebGL fails.",
  "dependencies": [
    "three",
    "@react-three/fiber"
  ],
  "devDependencies": [
    "@types/three"
  ],
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json",
    "https://spotcheckui.com/r/use-in-view.json",
    "https://spotcheckui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/gl-stage.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Canvas, useThree, type CanvasProps } from \"@react-three/fiber\"\n\nimport { useInView } from \"@/registry/spotcheck/hooks/use-in-view\"\nimport { useReducedMotion } from \"@/registry/spotcheck/hooks/use-reduced-motion\"\nimport { cn } from \"@/lib/utils\"\n\n// The gap between the two stops a canvas being rebuilt by small scrolls near one edge.\nconst NEAR_MARGIN = \"300px\"\nconst FAR_MARGIN = \"900px\"\n\nlet webGlAvailable: boolean | undefined\n\n/** Checks once per page, then gives the context back because browsers cap how many can be open. */\nexport function supportsWebGl() {\n  if (webGlAvailable === undefined) {\n    const context = document.createElement(\"canvas\").getContext(\"webgl2\")\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext()\n    webGlAvailable = Boolean(context)\n  }\n  return webGlAvailable\n}\n\nclass GlBoundary extends React.Component<{ onFail: () => void; children: React.ReactNode }, { failed: boolean }> {\n  state = { failed: false }\n\n  static getDerivedStateFromError() {\n    return { failed: true }\n  }\n\n  componentDidCatch() {\n    this.props.onFail()\n  }\n\n  render() {\n    return this.state.failed ? null : this.props.children\n  }\n}\n\nfunction ContextWatch({ onLost }: { onLost: () => void }) {\n  const canvas = useThree((state) => state.gl.domElement)\n\n  React.useEffect(() => {\n    canvas.addEventListener(\"webglcontextlost\", onLost)\n    return () => canvas.removeEventListener(\"webglcontextlost\", onLost)\n  }, [canvas, onLost])\n\n  return null\n}\n\ntype GlStageProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Describes the scene. Screen readers get it, and it replaces the canvas if WebGL fails. */\n  label: string\n  /** Renders every frame while on screen. Turn it off for scenes that only change on input. */\n  animate?: boolean\n  /** The starting camera. */\n  camera?: CanvasProps[\"camera\"]\n  /** The point the camera faces. */\n  lookAt?: [x: number, y: number, z: number]\n  /** Runs when a click lands on nothing in the scene. */\n  onPointerMissed?: CanvasProps[\"onPointerMissed\"]\n  /** The scene, built from three.js elements. */\n  children: React.ReactNode\n}\n\nexport function GlStage({\n  label,\n  animate = true,\n  camera,\n  lookAt,\n  onPointerMissed,\n  ref,\n  className,\n  children,\n  ...props\n}: GlStageProps) {\n  const host = React.useRef<HTMLDivElement>(null)\n  React.useImperativeHandle(ref, () => host.current as HTMLDivElement)\n\n  const onScreen = useInView(host)\n  const near = useInView(host, NEAR_MARGIN)\n  const far = useInView(host, FAR_MARGIN)\n  const reduced = useReducedMotion()\n  const [live, setLive] = React.useState(false)\n  const [failed, setFailed] = React.useState(false)\n  const fail = React.useCallback(() => setFailed(true), [])\n\n  if (near && !live) setLive(true)\n  if (!near && !far && live) {\n    setLive(false)\n    // A dropped context can come back, so the next approach gets a fresh try.\n    setFailed(false)\n  }\n\n  const broken = failed || (live && !supportsWebGl())\n\n  return (\n    <div\n      ref={host}\n      role={broken ? undefined : \"img\"}\n      aria-label={broken ? undefined : label}\n      className={cn(\"relative min-w-0 touch-pan-y overflow-hidden font-(family-name:--f-body)\", className)}\n      {...props}\n    >\n      {broken ? (\n        <p className=\"text-mute grid size-full place-items-center p-4 text-center font-(family-name:--f-mono) text-xs\">\n          {label}\n        </p>\n      ) : (\n        live && (\n          <GlBoundary onFail={fail}>\n            <Canvas\n              flat\n              dpr={[1, 2]}\n              frameloop={onScreen && animate && !reduced ? \"always\" : \"demand\"}\n              gl={{ antialias: true, alpha: true }}\n              camera={camera}\n              onCreated={(state) => {\n                if (lookAt) state.camera.lookAt(...lookAt)\n              }}\n              onPointerMissed={onPointerMissed}\n            >\n              <ContextWatch onLost={fail} />\n              {children}\n            </Canvas>\n          </GlBoundary>\n        )\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:item"
}