1- import { useRef , useEffect , useCallback } from "react" ;
1+ import { useRef , useEffect } from "react" ;
22import { CONTINENTS } from "./worlddata" ;
33
4- const CITIES : [ number , number , string ] [ ] = [
5- [ 40.71 , - 74.01 , "New York" ] , [ 37.77 , - 122.42 , "San Francisco" ] ,
6- [ 51.51 , - 0.13 , "London" ] , [ 48.86 , 2.35 , "Paris" ] ,
7- [ 35.68 , 139.69 , "Tokyo" ] , [ 1.35 , 103.82 , "Singapore" ] ,
8- [ - 33.87 , 151.21 , "Sydney" ] , [ 55.76 , 37.62 , "Moscow" ] ,
9- [ - 23.55 , - 46.63 , "Sao Paulo" ] , [ 19.43 , - 99.13 , "Mexico City" ] ,
10- [ 28.61 , 77.21 , "Delhi" ] , [ 31.23 , 121.47 , "Shanghai" ] ,
11- [ 37.57 , 126.98 , "Seoul" ] , [ 52.52 , 13.41 , "Berlin" ] ,
12- [ 25.20 , 55.27 , "Dubai" ] , [ 22.32 , 114.17 , "Hong Kong" ] ,
13- [ - 1.29 , 36.82 , "Nairobi" ] , [ 6.52 , 3.38 , "Lagos" ] ,
14- [ 43.65 , - 79.38 , "Toronto" ] , [ 34.05 , - 118.24 , "Los Angeles" ] ,
15- ] ;
16-
17- type Arc = { from : [ number , number ] ; to : [ number , number ] ; birth : number ; duration : number } ;
18-
194function latLngToXYZ ( lat : number , lng : number , r : number ) : [ number , number , number ] {
205 const phi = ( 90 - lat ) * ( Math . PI / 180 ) ;
216 const theta = ( lng + 180 ) * ( Math . PI / 180 ) ;
@@ -39,45 +24,17 @@ function transform(lat: number, lng: number, R: number, rY: number, rX: number):
3924 return [ x , y , z ] ;
4025}
4126
42- function slerp ( lat1 : number , lng1 : number , lat2 : number , lng2 : number , t : number , alt : number ) : [ number , number , number ] {
43- const p1 = latLngToXYZ ( lat1 , lng1 , 1 ) , p2 = latLngToXYZ ( lat2 , lng2 , 1 ) ;
44- const dot = p1 [ 0 ] * p2 [ 0 ] + p1 [ 1 ] * p2 [ 1 ] + p1 [ 2 ] * p2 [ 2 ] ;
45- const omega = Math . acos ( Math . max ( - 1 , Math . min ( 1 , dot ) ) ) ;
46- let x : number , y : number , z : number ;
47- if ( omega < 0.001 ) {
48- x = p1 [ 0 ] * ( 1 - t ) + p2 [ 0 ] * t ; y = p1 [ 1 ] * ( 1 - t ) + p2 [ 1 ] * t ; z = p1 [ 2 ] * ( 1 - t ) + p2 [ 2 ] * t ;
49- } else {
50- const sinO = Math . sin ( omega ) , a = Math . sin ( ( 1 - t ) * omega ) / sinO , b = Math . sin ( t * omega ) / sinO ;
51- x = a * p1 [ 0 ] + b * p2 [ 0 ] ; y = a * p1 [ 1 ] + b * p2 [ 1 ] ; z = a * p1 [ 2 ] + b * p2 [ 2 ] ;
52- }
53- const len = Math . sqrt ( x * x + y * y + z * z ) ;
54- const lift = 1 + alt * Math . sin ( t * Math . PI ) ;
55- return [ ( x / len ) * lift , ( y / len ) * lift , ( z / len ) * lift ] ;
56- }
57-
5827export function Globe ( ) {
5928 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
60- const arcsRef = useRef < Arc [ ] > ( [ ] ) ;
6129 const rotRef = useRef ( 0 ) ;
6230 const frameRef = useRef ( 0 ) ;
6331
64- const spawnArc = useCallback ( ( ) => {
65- const from = CITIES [ Math . floor ( Math . random ( ) * CITIES . length ) ] ;
66- let to = from ;
67- while ( to === from ) to = CITIES [ Math . floor ( Math . random ( ) * CITIES . length ) ] ;
68- arcsRef . current . push ( { from : [ from [ 0 ] , from [ 1 ] ] , to : [ to [ 0 ] , to [ 1 ] ] , birth : Date . now ( ) , duration : 1800 + Math . random ( ) * 1200 } ) ;
69- if ( arcsRef . current . length > 20 ) arcsRef . current . shift ( ) ;
70- } , [ ] ) ;
71-
7232 useEffect ( ( ) => {
7333 const canvas = canvasRef . current ;
7434 if ( ! canvas ) return ;
7535 const ctx = canvas . getContext ( "2d" ) ! ;
7636 let running = true ;
7737
78- const spawner = setInterval ( ( ) => { spawnArc ( ) ; if ( Math . random ( ) < 0.4 ) spawnArc ( ) ; } , 600 ) ;
79- for ( let i = 0 ; i < 5 ; i ++ ) spawnArc ( ) ;
80-
8138 function resize ( ) {
8239 const rect = canvas ! . getBoundingClientRect ( ) ;
8340 const dpr = window . devicePixelRatio || 1 ;
@@ -94,7 +51,6 @@ export function Globe() {
9451 const w = rect . width , h = rect . height ;
9552 const cx = w / 2 , cy = h / 2 ;
9653 const R = Math . min ( w , h ) * 0.40 ;
97- const now = Date . now ( ) ;
9854 const rY = rotRef . current , rX = - 0.35 ;
9955
10056 ctx . clearRect ( 0 , 0 , w , h ) ;
@@ -122,86 +78,21 @@ export function Globe() {
12278 ctx . strokeStyle = "#2a2a2a" ;
12379 ctx . lineWidth = 1 ;
12480 ctx . stroke ( ) ;
125- // Subtle fill
12681 ctx . fillStyle = "rgba(255,255,255,0.02)" ;
12782 ctx . fill ( ) ;
12883 }
12984
130- // City dots
131- for ( const [ lat , lng ] of CITIES ) {
132- const [ x , y , z ] = transform ( lat , lng , R , rY , rX ) ;
133- if ( z < 0 ) continue ;
134- ctx . beginPath ( ) ;
135- ctx . arc ( cx + x , cy - y , 2 , 0 , Math . PI * 2 ) ;
136- ctx . fillStyle = "#555" ;
137- ctx . fill ( ) ;
138- }
139-
140- // Arcs
141- const alive : Arc [ ] = [ ] ;
142- for ( const arc of arcsRef . current ) {
143- const elapsed = now - arc . birth ;
144- if ( elapsed > arc . duration + 600 ) continue ;
145- alive . push ( arc ) ;
146-
147- const progress = Math . min ( elapsed / arc . duration , 1 ) ;
148- const fade = elapsed > arc . duration ? 1 - ( elapsed - arc . duration ) / 600 : 1 ;
149- const steps = 40 ;
150- const headT = progress , tailT = Math . max ( 0 , progress - 0.35 ) ;
151-
152- // Trail
153- ctx . beginPath ( ) ;
154- let started = false ;
155- for ( let i = 0 ; i <= steps ; i ++ ) {
156- const t = tailT + ( headT - tailT ) * ( i / steps ) ;
157- const [ sx , sy , sz ] = slerp ( arc . from [ 0 ] , arc . from [ 1 ] , arc . to [ 0 ] , arc . to [ 1 ] , t , 0.15 ) ;
158- let [ x , y , z ] = [ sx * R , sy * R , sz * R ] ;
159- [ x , y , z ] = rotateY ( x , y , z , rY ) ;
160- [ x , y , z ] = rotateX ( x , y , z , rX ) ;
161- if ( z < 0 ) { started = false ; continue ; }
162- if ( ! started ) { ctx . moveTo ( cx + x , cy - y ) ; started = true ; }
163- else ctx . lineTo ( cx + x , cy - y ) ;
164- }
165- ctx . strokeStyle = `rgba(200,200,200,${ fade * 0.7 } )` ;
166- ctx . lineWidth = 1.5 ;
167- ctx . stroke ( ) ;
168-
169- // Head
170- const [ hx , hy , hz ] = slerp ( arc . from [ 0 ] , arc . from [ 1 ] , arc . to [ 0 ] , arc . to [ 1 ] , headT , 0.15 ) ;
171- let [ rx2 , ry2 , rz2 ] = [ hx * R , hy * R , hz * R ] ;
172- [ rx2 , ry2 , rz2 ] = rotateY ( rx2 , ry2 , rz2 , rY ) ;
173- [ rx2 , ry2 , rz2 ] = rotateX ( rx2 , ry2 , rz2 , rX ) ;
174- if ( rz2 >= 0 && progress < 1 ) {
175- const px = cx + rx2 , py = cy - ry2 ;
176- ctx . beginPath ( ) ; ctx . arc ( px , py , 3 , 0 , Math . PI * 2 ) ;
177- ctx . fillStyle = `rgba(255,255,255,${ fade * 0.9 } )` ; ctx . fill ( ) ;
178- ctx . beginPath ( ) ; ctx . arc ( px , py , 7 , 0 , Math . PI * 2 ) ;
179- ctx . fillStyle = `rgba(255,255,255,${ fade * 0.12 } )` ; ctx . fill ( ) ;
180- }
181-
182- // Arrival flash
183- if ( progress >= 1 && fade > 0.3 ) {
184- const [ dx , dy , dz ] = transform ( arc . to [ 0 ] , arc . to [ 1 ] , R , rY , rX ) ;
185- if ( dz >= 0 ) {
186- ctx . beginPath ( ) ; ctx . arc ( cx + dx , cy - dy , 8 , 0 , Math . PI * 2 ) ;
187- ctx . fillStyle = `rgba(255,255,255,${ fade * 0.3 } )` ; ctx . fill ( ) ;
188- }
189- }
190- }
191- arcsRef . current = alive ;
192-
19385 rotRef . current += 0.0015 ;
19486 frameRef . current = requestAnimationFrame ( draw ) ;
19587 }
19688
19789 frameRef . current = requestAnimationFrame ( draw ) ;
198- return ( ) => { running = false ; cancelAnimationFrame ( frameRef . current ) ; clearInterval ( spawner ) ; window . removeEventListener ( "resize" , resize ) ; } ;
199- } , [ spawnArc ] ) ;
90+ return ( ) => { running = false ; cancelAnimationFrame ( frameRef . current ) ; window . removeEventListener ( "resize" , resize ) ; } ;
91+ } , [ ] ) ;
20092
20193 return (
20294 < div className = "globe-wrap" >
20395 < canvas ref = { canvasRef } className = "globe-canvas" />
204- < div className = "globe-label" > Live Transaction Routing</ div >
20596 </ div >
20697 ) ;
20798}
0 commit comments