import { useEffect, useMemo, useState } from 'react' import { Canvas } from '@react-three/fiber' import { OrbitControls } from '@react-three/drei' import ThreeGlobe from 'three-globe' import { Color } from 'three' type Arc = { order: number startLat: number; startLng: number endLat: number; endLng: number arcAlt: number; color: string } const COLORS = ['#22ade0', '#3b82f6', '#CD9E53'] /* steel-trade arcs centred on Tehran/Shanghai */ const ARCS: Arc[] = [ { order: 1, startLat: 35.69, startLng: 51.39, endLat: 31.23, endLng: 121.47, arcAlt: 0.3, color: COLORS[0] }, { order: 1, startLat: 35.69, startLng: 51.39, endLat: 33.31, endLng: 44.36, arcAlt: 0.1, color: COLORS[2] }, { order: 2, startLat: 35.69, startLng: 51.39, endLat: 51.92, endLng: 4.47, arcAlt: 0.3, color: COLORS[1] }, { order: 2, startLat: 31.23, startLng: 121.47, endLat: 35.69, endLng: 51.39, arcAlt: 0.2, color: COLORS[0] }, { order: 3, startLat: 25.20, startLng: 55.27, endLat: 35.69, endLng: 51.39, arcAlt: 0.2, color: COLORS[2] }, { order: 3, startLat: 19.07, startLng: 72.88, endLat: 35.69, endLng: 51.39, arcAlt: 0.3, color: COLORS[1] }, { order: 4, startLat: 41.01, startLng: 28.97, endLat: 35.69, endLng: 51.39, arcAlt: 0.2, color: COLORS[0] }, { order: 4, startLat: -23.55, startLng: -46.63, endLat: 31.23, endLng: 121.47, arcAlt: 0.5, color: COLORS[2] }, { order: 5, startLat: 55.75, startLng: 37.62, endLat: 35.69, endLng: 51.39, arcAlt: 0.2, color: COLORS[1] }, { order: 5, startLat: 37.56, startLng: 126.97, endLat: 31.23, endLng: 121.47, arcAlt: 0.2, color: COLORS[0] }, ] function GlobeObject({ countries }: { countries: { features: object[] } }) { const globe = useMemo(() => { const g = new ThreeGlobe() .hexPolygonsData(countries.features) .hexPolygonResolution(3) .hexPolygonMargin(0.62) .hexPolygonUseDots(true) .hexPolygonColor(() => 'rgba(150,200,255,0.95)') .showAtmosphere(true) .atmosphereColor('#7cc4ff') .atmosphereAltitude(0.22) g.arcsData(ARCS) .arcStartLat((d: object) => (d as Arc).startLat) .arcStartLng((d: object) => (d as Arc).startLng) .arcEndLat((d: object) => (d as Arc).endLat) .arcEndLng((d: object) => (d as Arc).endLng) .arcColor((d: object) => (d as Arc).color) .arcAltitude((d: object) => (d as Arc).arcAlt) .arcStroke(0.5) .arcDashLength(0.9) .arcDashGap(4) .arcDashInitialGap((d: object) => (d as Arc).order) .arcDashAnimateTime(1400) const points = ARCS.flatMap((a) => [ { lat: a.startLat, lng: a.startLng, color: a.color }, { lat: a.endLat, lng: a.endLng, color: a.color }, ]) g.pointsData(points) .pointColor((d: object) => (d as { color: string }).color) .pointAltitude(0) .pointRadius(0.55) .pointsMerge(true) // material → lighter, distinguishable blue sphere const mat = g.globeMaterial() as unknown as { color: Color; emissive: Color; emissiveIntensity: number; shininess: number } mat.color = new Color('#1b386e') mat.emissive = new Color('#23508f') mat.emissiveIntensity = 0.45 mat.shininess = 0.6 return g }, [countries]) return } export function World() { const [countries, setCountries] = useState<{ features: object[] } | null>(null) useEffect(() => { let alive = true fetch('/globe-countries.geojson') .then((r) => r.json()) .then((geo) => { if (alive) setCountries(geo) }) .catch(() => {}) return () => { alive = false } }, []) return ( {countries && } ) } /* default wrapper sized to fit the section */ export default function Globe({ size = 600 }: { size?: number }) { return (
) }