"use client"; import createGlobe, { COBEOptions } from "cobe"; import { useMotionValue, useSpring } from "motion/react"; import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; const MOVEMENT_DAMPING = 1400; const GLOBE_CONFIG: COBEOptions = { width: 800, height: 800, onRender: () => {}, devicePixelRatio: 2, phi: 0, theta: 0.3, dark: 0, diffuse: 0.25, // softer shading for premium look mapSamples: 16000, mapBrightness: 1.1, baseColor: [0.8, 0.8, 0.8], // sleek dark gray globe markerColor: [0.3, 0.6, 1], // soft, elegant blue glowColor: [0.8, 0.8, 0.85], // subtle glow markers: [ // --- Core Global Markers --- { location: [14.5995, 120.9842], size: 0.03 }, // Manila { location: [19.076, 72.8777], size: 0.1 }, // Mumbai { location: [23.8103, 90.4125], size: 0.05 }, // Dhaka { location: [30.0444, 31.2357], size: 0.07 }, // Cairo { location: [39.9042, 116.4074], size: 0.08 }, // Beijing { location: [-23.5505, -46.6333], size: 0.1 }, // São Paulo { location: [19.4326, -99.1332], size: 0.1 }, // Mexico City { location: [40.7128, -74.006], size: 0.1 }, // New York { location: [34.6937, 135.5022], size: 0.05 }, // Osaka { location: [41.0082, 28.9784], size: 0.06 }, // Istanbul { location: [48.8566, 2.3522], size: 0.08 }, // Paris { location: [51.5072, -0.1276], size: 0.08 }, // London { location: [52.52, 13.405], size: 0.07 }, // Berlin { location: [35.6895, 139.6917], size: 0.06 }, // Tokyo { location: [-33.8688, 151.2093], size: 0.06 }, // Sydney { location: [-1.2921, 36.8219], size: 0.05 }, // Nairobi { location: [-34.6037, -58.3816], size: 0.07 }, // Buenos Aires { location: [37.7749, -122.4194], size: 0.08 }, // San Francisco { location: [1.3521, 103.8198], size: 0.06 }, // Singapore { location: [28.6139, 77.2090], size: 0.08 }, // New Delhi { location: [13.7563, 100.5018], size: 0.06 }, // Bangkok { location: [59.9343, 30.3351], size: 0.05 }, // St. Petersburg { location: [33.6844, 73.0479], size: 0.05 }, // Islamabad { location: [25.276987, 55.296249], size: 0.07 }, // Dubai { location: [60.1699, 24.9384], size: 0.05 }, // Helsinki { location: [43.6532, -79.3832], size: 0.07 }, // Toronto { location: [6.5244, 3.3792], size: 0.08 }, // Lagos { location: [50.1109, 8.6821], size: 0.06 }, // Frankfurt // --- 12 New US + European Cities --- { location: [34.0522, -118.2437], size: 0.08 }, // Los Angeles { location: [41.8781, -87.6298], size: 0.07 }, // Chicago { location: [29.7604, -95.3698], size: 0.07 }, // Houston { location: [25.7617, -80.1918], size: 0.07 }, // Miami { location: [45.5017, -73.5673], size: 0.06 }, // Montreal { location: [47.6062, -122.3321], size: 0.06 }, // Seattle { location: [40.4406, -79.9959], size: 0.05 }, // Pittsburgh { location: [41.3851, 2.1734], size: 0.06 }, // Barcelona { location: [45.4642, 9.19], size: 0.06 }, // Milan { location: [52.3676, 4.9041], size: 0.06 }, // Amsterdam { location: [38.7169, -9.139], size: 0.05 }, // Lisbon { location: [59.3293, 18.0686], size: 0.05 }, // Stockholmx ], }; export function Globe({ className, config = GLOBE_CONFIG, }: { className?: string; config?: COBEOptions; }) { let phi = 0; let width = 0; const canvasRef = useRef(null); const pointerInteracting = useRef(null); const r = useMotionValue(0); const rs = useSpring(r, { mass: 1, damping: 35, // slightly smoother motion 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; r.set(r.get() + delta / MOVEMENT_DAMPING); } }; useEffect(() => { const onResize = () => { if (canvasRef.current) width = canvasRef.current.offsetWidth; }; window.addEventListener("resize", onResize); onResize(); const globe = createGlobe(canvasRef.current!, { ...config, width: width * 2, height: width * 2, onRender: (state) => { if (!pointerInteracting.current) phi += 0.004; // slightly slower rotation for elegance state.phi = phi + rs.get(); state.width = width * 2; state.height = width * 2; }, }); setTimeout(() => (canvasRef.current!.style.opacity = "1"), 0); return () => { globe.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) } />
); }