{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "globe",
  "title": "Globe",
  "description": "A draggable, auto-rotating WebGL globe with glowing location markers.",
  "dependencies": ["cobe"],
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/globe/globe.tsx",
      "content": "\"use client\";\n\nimport createGlobe, { type COBEOptions } from \"cobe\";\nimport * as React from \"react\";\n\nexport type GlobeProps = React.HTMLAttributes<HTMLDivElement> & {\n  /** Override any cobe option (markers, colors, glow, etc.). */\n  config?: Partial<COBEOptions>;\n};\n\nconst DEFAULT_CONFIG: COBEOptions = {\n  width: 800,\n  height: 800,\n  devicePixelRatio: 2,\n  phi: 0,\n  theta: 0.3,\n  dark: 0,\n  diffuse: 0.4,\n  mapSamples: 16000,\n  mapBrightness: 1.2,\n  baseColor: [1, 1, 1],\n  markerColor: [0.23, 0.51, 0.96],\n  glowColor: [1, 1, 1],\n  markers: [\n    { location: [37.7595, -122.4367], size: 0.05 },\n    { location: [40.7128, -74.006], size: 0.08 },\n    { location: [51.5074, -0.1278], size: 0.06 },\n    { location: [35.6762, 139.6503], size: 0.07 },\n    { location: [-23.5505, -46.6333], size: 0.06 },\n    { location: [1.3521, 103.8198], size: 0.05 },\n  ],\n};\n\nconst Globe = React.forwardRef<HTMLDivElement, GlobeProps>(\n  ({ config, className, style, ...props }, forwardedRef) => {\n    const wrapRef = React.useRef<HTMLDivElement>(null);\n    React.useImperativeHandle(\n      forwardedRef,\n      () => wrapRef.current as HTMLDivElement,\n    );\n    const canvasRef = React.useRef<HTMLCanvasElement>(null);\n\n    const phiRef = React.useRef(0);\n    const widthRef = React.useRef(0);\n    const pointerInteracting = React.useRef<number | null>(null);\n    const pointerMovement = React.useRef(0);\n\n    React.useEffect(() => {\n      const canvas = canvasRef.current;\n      const wrap = wrapRef.current;\n      if (!canvas || !wrap) return;\n\n      const onResize = () => {\n        widthRef.current = wrap.offsetWidth;\n      };\n      window.addEventListener(\"resize\", onResize);\n      onResize();\n\n      const merged: COBEOptions = {\n        ...DEFAULT_CONFIG,\n        ...config,\n        width: widthRef.current * 2,\n        height: widthRef.current * 2,\n      };\n\n      const globe = createGlobe(canvas, merged);\n      let raf = 0;\n      // Honor reduced motion — hold the globe still instead of auto-rotating.\n      const reduceMotion = window.matchMedia(\n        \"(prefers-reduced-motion: reduce)\",\n      ).matches;\n      // Auto-rotate while idle; hand control to the pointer on drag.\n      const tick = () => {\n        if (pointerInteracting.current === null && !reduceMotion)\n          phiRef.current += 0.005;\n        globe.update({\n          phi: phiRef.current + pointerMovement.current / 200,\n          width: widthRef.current * 2,\n          height: widthRef.current * 2,\n        });\n        raf = requestAnimationFrame(tick);\n      };\n      raf = requestAnimationFrame(tick);\n      const reveal = requestAnimationFrame(() => {\n        canvas.style.opacity = \"1\";\n      });\n\n      return () => {\n        cancelAnimationFrame(raf);\n        cancelAnimationFrame(reveal);\n        globe.destroy();\n        window.removeEventListener(\"resize\", onResize);\n      };\n    }, [config]);\n\n    const updateMovement = (clientX: number) => {\n      if (pointerInteracting.current !== null) {\n        pointerMovement.current = clientX - pointerInteracting.current;\n      }\n    };\n\n    return (\n      <div\n        ref={wrapRef}\n        data-slot=\"globe\"\n        className={`relative aspect-square w-full max-w-[600px] ${className ?? \"\"}`}\n        style={style}\n        {...props}\n      >\n        <canvas\n          ref={canvasRef}\n          className=\"size-full opacity-0 [contain:layout_paint_size] [transition:opacity_1s_ease]\"\n          onPointerDown={(e) => {\n            pointerInteracting.current = e.clientX - pointerMovement.current;\n            e.currentTarget.style.cursor = \"grabbing\";\n          }}\n          onPointerUp={(e) => {\n            pointerInteracting.current = null;\n            e.currentTarget.style.cursor = \"grab\";\n          }}\n          onPointerOut={(e) => {\n            pointerInteracting.current = null;\n            e.currentTarget.style.cursor = \"grab\";\n          }}\n          onPointerMove={(e) => updateMovement(e.clientX)}\n          style={{ cursor: \"grab\" }}\n        />\n      </div>\n    );\n  },\n);\nGlobe.displayName = \"Globe\";\n\nexport { Globe };\n",
      "type": "registry:ui",
      "target": "components/godui/globe.tsx"
    }
  ],
  "type": "registry:ui"
}
