"use client" import { useEffect, useRef } from "react" import createGlobe, { type COBEOptions } from "cobe" import { useMotionValue, useSpring } from "motion/react" import { cn } from "@/lib/utils" const MOVEMENT_DAMPING = 1400 const GLOBE_CONFIG: Omit = { phi: 0, theta: 0.3, dark: 0, diffuse: 0.4, mapSamples: 16000, mapBrightness: 1.2, devicePixelRatio: 2, baseColor: [1, 1, 1] as [number, number, number], markerColor: [251 / 255, 100 / 255, 21 / 255] as [number, number, number], glowColor: [1, 1, 1] as [number, number, number], markers: [ { location: [14.5995, 120.9842], size: 0.03 }, { location: [19.076, 72.8777], size: 0.1 }, { location: [23.8103, 90.4125], size: 0.05 }, { location: [30.0444, 31.2357], size: 0.07 }, { location: [39.9042, 116.4074], size: 0.08 }, { location: [-23.5505, -46.6333], size: 0.1 }, { location: [19.4326, -99.1332], size: 0.1 }, { location: [40.7128, -74.006], size: 0.1 }, { location: [34.6937, 135.5022], size: 0.05 }, { location: [41.0082, 28.9784], size: 0.06 }, ], } export function Globe({ className, config = GLOBE_CONFIG, }: { className?: string config?: Omit }) { const canvasRef = useRef(null) const phiRef = useRef(0) const widthRef = useRef(0) const pointerInteracting = useRef(null) const pointerInteractionMovement = useRef(0) const globeRef = useRef | null>(null) const r = useMotionValue(0) const rs = useSpring(r, { mass: 1, damping: 30, stiffness: 100, }) const updatePointerInteraction = (value: number | null) => { pointerInteracting.current = value if (canvasRef.current) { canvasRef.current.style.cursor = value !== null ? "grabbing" : "grab" } } const updateMovement = (clientX: number) => { if (pointerInteracting.current !== null) { const delta = clientX - pointerInteracting.current pointerInteractionMovement.current = delta r.set(r.get() + delta / MOVEMENT_DAMPING) } } useEffect(() => { const onResize = () => { if (canvasRef.current) { widthRef.current = canvasRef.current.offsetWidth globeRef.current?.update({ width: widthRef.current * 2, height: widthRef.current * 2, }) } } window.addEventListener("resize", onResize) onResize() let animFrame: number const render = () => { if (!pointerInteracting.current) phiRef.current += 0.005 globeRef.current?.update({ phi: phiRef.current + rs.get(), }) animFrame = requestAnimationFrame(render) } globeRef.current = createGlobe(canvasRef.current!, { ...config, width: widthRef.current * 2, height: widthRef.current * 2, }) animFrame = requestAnimationFrame(render) setTimeout(() => (canvasRef.current!.style.opacity = "1"), 0) return () => { cancelAnimationFrame(animFrame) globeRef.current?.destroy() window.removeEventListener("resize", onResize) } }, [rs, config]) return (
{ pointerInteracting.current = e.clientX updatePointerInteraction(e.clientX) }} onPointerUp={() => updatePointerInteraction(null)} onPointerOut={() => updatePointerInteraction(null)} onMouseMove={(e) => updateMovement(e.clientX)} onTouchMove={(e) => e.touches[0] && updateMovement(e.touches[0].clientX) } />
) }