{
  "name": "globe",
  "type": "registry:ui",
  "dependencies": [
    "three",
    "three-globe",
    "@react-three/fiber@alpha",
    "@react-three/drei"
  ],
  "devDependencies": [
    "@types/three"
  ],
  "files": [
    {
      "path": "components/ui/globe.tsx",
      "content": "\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { Color, Scene, Fog, PerspectiveCamera, Vector3 } from \"three\";\nimport ThreeGlobe from \"three-globe\";\nimport { useThree, Canvas, extend } from \"@react-three/fiber\";\nimport { OrbitControls } from \"@react-three/drei\";\nimport countries from \"@/data/globe.json\";\ndeclare module \"@react-three/fiber\" {\n  interface ThreeElements {\n    threeGlobe: ThreeElements[\"mesh\"] & {\n      new (): ThreeGlobe;\n    };\n  }\n}\n\nextend({ ThreeGlobe: ThreeGlobe });\n\nconst RING_PROPAGATION_SPEED = 3;\nconst aspect = 1.2;\nconst cameraZ = 300;\n\ntype Position = {\n  order: number;\n  startLat: number;\n  startLng: number;\n  endLat: number;\n  endLng: number;\n  arcAlt: number;\n  color: string;\n};\n\nexport type GlobeConfig = {\n  pointSize?: number;\n  globeColor?: string;\n  showAtmosphere?: boolean;\n  atmosphereColor?: string;\n  atmosphereAltitude?: number;\n  emissive?: string;\n  emissiveIntensity?: number;\n  shininess?: number;\n  polygonColor?: string;\n  ambientLight?: string;\n  directionalLeftLight?: string;\n  directionalTopLight?: string;\n  pointLight?: string;\n  arcTime?: number;\n  arcLength?: number;\n  rings?: number;\n  maxRings?: number;\n  initialPosition?: {\n    lat: number;\n    lng: number;\n  };\n  autoRotate?: boolean;\n  autoRotateSpeed?: number;\n};\n\ninterface WorldProps {\n  globeConfig: GlobeConfig;\n  data: Position[];\n}\n\nlet numbersOfRings = [0];\n\nexport function Globe({ globeConfig, data }: WorldProps) {\n  const globeRef = useRef<ThreeGlobe | null>(null);\n  const groupRef = useRef();\n  const [isInitialized, setIsInitialized] = useState(false);\n\n  const defaultProps = {\n    pointSize: 1,\n    atmosphereColor: \"#ffffff\",\n    showAtmosphere: true,\n    atmosphereAltitude: 0.1,\n    polygonColor: \"rgba(255,255,255,0.7)\",\n    globeColor: \"#1d072e\",\n    emissive: \"#000000\",\n    emissiveIntensity: 0.1,\n    shininess: 0.9,\n    arcTime: 2000,\n    arcLength: 0.9,\n    rings: 1,\n    maxRings: 3,\n    ...globeConfig,\n  };\n\n  // Initialize globe only once\n  useEffect(() => {\n    if (!globeRef.current && groupRef.current) {\n      globeRef.current = new ThreeGlobe();\n      (groupRef.current as any).add(globeRef.current);\n      setIsInitialized(true);\n    }\n  }, []);\n\n  // Build material when globe is initialized or when relevant props change\n  useEffect(() => {\n    if (!globeRef.current || !isInitialized) return;\n\n    const globeMaterial = globeRef.current.globeMaterial() as unknown as {\n      color: Color;\n      emissive: Color;\n      emissiveIntensity: number;\n      shininess: number;\n    };\n    globeMaterial.color = new Color(globeConfig.globeColor);\n    globeMaterial.emissive = new Color(globeConfig.emissive);\n    globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;\n    globeMaterial.shininess = globeConfig.shininess || 0.9;\n  }, [\n    isInitialized,\n    globeConfig.globeColor,\n    globeConfig.emissive,\n    globeConfig.emissiveIntensity,\n    globeConfig.shininess,\n  ]);\n\n  // Build data when globe is initialized or when data changes\n  useEffect(() => {\n    if (!globeRef.current || !isInitialized || !data) return;\n\n    const arcs = data;\n    let points = [];\n    for (let i = 0; i < arcs.length; i++) {\n      const arc = arcs[i];\n      const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };\n      points.push({\n        size: defaultProps.pointSize,\n        order: arc.order,\n        color: arc.color,\n        lat: arc.startLat,\n        lng: arc.startLng,\n      });\n      points.push({\n        size: defaultProps.pointSize,\n        order: arc.order,\n        color: arc.color,\n        lat: arc.endLat,\n        lng: arc.endLng,\n      });\n    }\n\n    // remove duplicates for same lat and lng\n    const filteredPoints = points.filter(\n      (v, i, a) =>\n        a.findIndex((v2) =>\n          [\"lat\", \"lng\"].every(\n            (k) => v2[k as \"lat\" | \"lng\"] === v[k as \"lat\" | \"lng\"],\n          ),\n        ) === i,\n    );\n\n    globeRef.current\n      .hexPolygonsData(countries.features)\n      .hexPolygonResolution(3)\n      .hexPolygonMargin(0.7)\n      .showAtmosphere(defaultProps.showAtmosphere)\n      .atmosphereColor(defaultProps.atmosphereColor)\n      .atmosphereAltitude(defaultProps.atmosphereAltitude)\n      .hexPolygonColor(() => defaultProps.polygonColor);\n\n    globeRef.current\n      .arcsData(data)\n      .arcStartLat((d) => (d as { startLat: number }).startLat * 1)\n      .arcStartLng((d) => (d as { startLng: number }).startLng * 1)\n      .arcEndLat((d) => (d as { endLat: number }).endLat * 1)\n      .arcEndLng((d) => (d as { endLng: number }).endLng * 1)\n      .arcColor((e: any) => (e as { color: string }).color)\n      .arcAltitude((e) => (e as { arcAlt: number }).arcAlt * 1)\n      .arcStroke(() => [0.32, 0.28, 0.3][Math.round(Math.random() * 2)])\n      .arcDashLength(defaultProps.arcLength)\n      .arcDashInitialGap((e) => (e as { order: number }).order * 1)\n      .arcDashGap(15)\n      .arcDashAnimateTime(() => defaultProps.arcTime);\n\n    globeRef.current\n      .pointsData(filteredPoints)\n      .pointColor((e) => (e as { color: string }).color)\n      .pointsMerge(true)\n      .pointAltitude(0.0)\n      .pointRadius(2);\n\n    globeRef.current\n      .ringsData([])\n      .ringColor(() => defaultProps.polygonColor)\n      .ringMaxRadius(defaultProps.maxRings)\n      .ringPropagationSpeed(RING_PROPAGATION_SPEED)\n      .ringRepeatPeriod(\n        (defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings,\n      );\n  }, [\n    isInitialized,\n    data,\n    defaultProps.pointSize,\n    defaultProps.showAtmosphere,\n    defaultProps.atmosphereColor,\n    defaultProps.atmosphereAltitude,\n    defaultProps.polygonColor,\n    defaultProps.arcLength,\n    defaultProps.arcTime,\n    defaultProps.rings,\n    defaultProps.maxRings,\n  ]);\n\n  // Handle rings animation with cleanup\n  useEffect(() => {\n    if (!globeRef.current || !isInitialized || !data) return;\n\n    const interval = setInterval(() => {\n      if (!globeRef.current) return;\n\n      const newNumbersOfRings = genRandomNumbers(\n        0,\n        data.length,\n        Math.floor((data.length * 4) / 5),\n      );\n\n      const ringsData = data\n        .filter((d, i) => newNumbersOfRings.includes(i))\n        .map((d) => ({\n          lat: d.startLat,\n          lng: d.startLng,\n          color: d.color,\n        }));\n\n      globeRef.current.ringsData(ringsData);\n    }, 2000);\n\n    return () => {\n      clearInterval(interval);\n    };\n  }, [isInitialized, data]);\n\n  return <group ref={groupRef} />;\n}\n\nexport function WebGLRendererConfig() {\n  const { gl, size } = useThree();\n\n  useEffect(() => {\n    gl.setPixelRatio(window.devicePixelRatio);\n    gl.setSize(size.width, size.height);\n    gl.setClearColor(0xffaaff, 0);\n  }, []);\n\n  return null;\n}\n\nexport function World(props: WorldProps) {\n  const { globeConfig } = props;\n  const scene = new Scene();\n  scene.fog = new Fog(0xffffff, 400, 2000);\n  return (\n    <Canvas scene={scene} camera={new PerspectiveCamera(50, aspect, 180, 1800)}>\n      <WebGLRendererConfig />\n      <ambientLight color={globeConfig.ambientLight} intensity={0.6} />\n      <directionalLight\n        color={globeConfig.directionalLeftLight}\n        position={new Vector3(-400, 100, 400)}\n      />\n      <directionalLight\n        color={globeConfig.directionalTopLight}\n        position={new Vector3(-200, 500, 200)}\n      />\n      <pointLight\n        color={globeConfig.pointLight}\n        position={new Vector3(-200, 500, 200)}\n        intensity={0.8}\n      />\n      <Globe {...props} />\n      <OrbitControls\n        enablePan={false}\n        enableZoom={false}\n        minDistance={cameraZ}\n        maxDistance={cameraZ}\n        autoRotateSpeed={1}\n        autoRotate={true}\n        minPolarAngle={Math.PI / 3.5}\n        maxPolarAngle={Math.PI - Math.PI / 3}\n      />\n    </Canvas>\n  );\n}\n\nexport function hexToRgb(hex: string) {\n  var shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\n  hex = hex.replace(shorthandRegex, function (m, r, g, b) {\n    return r + r + g + g + b + b;\n  });\n\n  var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  return result\n    ? {\n        r: parseInt(result[1], 16),\n        g: parseInt(result[2], 16),\n        b: parseInt(result[3], 16),\n      }\n    : null;\n}\n\nexport function genRandomNumbers(min: number, max: number, count: number) {\n  const arr = [];\n  while (arr.length < count) {\n    const r = Math.floor(Math.random() * (max - min)) + min;\n    if (arr.indexOf(r) === -1) arr.push(r);\n  }\n\n  return arr;\n}\n",
      "type": "registry:ui",
      "target": "components/ui/globe.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Globe"
}