/* Brand New Edge — 3D spinning globe (canvas, no deps).
   Orthographic projection of a rotating unit sphere with graticule,
   great-circle arcs from BNE markets converging on São Paulo, and
   traveling pulses. Brand purple palette. Honors reduced-motion. */
const { useRef, useEffect } = React;

// lat/lon in degrees
const BNE_HUB = { lat:-23.55, lon:-46.63, name:'São Paulo' };
const BNE_NODES = [
  { lat:40.71, lon:-74.0 },   // New York (USA)
  { lat:37.77, lon:-122.42 }, // San Francisco (USA)
  { lat:43.65, lon:-79.38 },  // Toronto (Canada)
  { lat:51.51, lon:-0.13 },   // London (UK)
  { lat:38.72, lon:-9.14 },   // Lisbon (Portugal)
];

function llToVec(lat, lon) {
  const phi = lat * Math.PI/180, lam = lon * Math.PI/180;
  return [Math.cos(phi)*Math.sin(lam), Math.sin(phi), Math.cos(phi)*Math.cos(lam)];
}
function rotY([x,y,z], a){ const c=Math.cos(a),s=Math.sin(a); return [x*c+z*s, y, -x*s+z*c]; }
function rotX([x,y,z], a){ const c=Math.cos(a),s=Math.sin(a); return [x, y*c-z*s, y*s+z*c]; }
function norm([x,y,z]){ const m=Math.hypot(x,y,z)||1; return [x/m,y/m,z/m]; }
// spherical linear interpolation between two unit vectors
function slerp(a,b,t){
  let d=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; d=Math.max(-1,Math.min(1,d));
  const o=Math.acos(d); if(o<1e-4) return a;
  const s=Math.sin(o), w1=Math.sin((1-t)*o)/s, w2=Math.sin(t*o)/s;
  return [a[0]*w1+b[0]*w2, a[1]*w1+b[1]*w2, a[2]*w1+b[2]*w2];
}

// Continent outlines as [lon,lat] polygons — higher-detail, recognizable coasts.
const CONTINENTS = [
  // North America (mainland + Alaska)
  [[-166,66],[-168,68],[-163,71],[-156,71],[-148,70],[-140,70],[-132,69],[-124,70],[-114,69],[-104,68],[-95,69],[-88,70],[-82,73],[-78,68],[-80,62],[-78,57],[-92,57],[-79,52],[-64,60],[-56,53],[-53,47],[-60,46],[-66,44],[-70,43],[-70,41],[-74,40],[-75,37],[-76,35],[-81,31],[-80,27],[-80,25],[-82,28],[-84,30],[-88,30],[-90,29],[-94,29],[-97,26],[-97,22],[-95,18],[-105,22],[-110,24],[-113,27],[-115,30],[-118,33],[-121,35],[-122,38],[-124,40],[-124,43],[-124,46],[-123,48],[-128,51],[-132,55],[-138,58],[-146,60],[-152,59],[-158,57],[-162,59],[-166,66]],
  // Central America
  [[-95,18],[-93,15],[-90,14],[-86,12],[-83,10],[-79,8],[-76,7],[-78,10],[-81,12],[-85,15],[-89,16],[-92,17],[-95,18]],
  // Greenland
  [[-46,60],[-30,60],[-20,68],[-22,76],[-30,82],[-45,83],[-58,80],[-62,76],[-54,68],[-50,64],[-46,60]],
  // Iceland
  [[-24,64],[-21,66],[-15,66],[-14,64],[-19,63],[-24,64]],
  // South America
  [[-78,9],[-75,11],[-71,12],[-64,11],[-60,10],[-52,5],[-50,1],[-48,-1],[-44,-3],[-40,-3],[-35,-6],[-35,-9],[-38,-13],[-39,-16],[-41,-22],[-48,-25],[-48,-28],[-53,-34],[-58,-35],[-57,-39],[-62,-40],[-65,-45],[-69,-50],[-69,-53],[-74,-53],[-73,-48],[-74,-43],[-73,-37],[-71,-30],[-71,-24],[-70,-18],[-76,-14],[-78,-8],[-81,-6],[-81,-2],[-80,2],[-78,9]],
  // Africa
  [[-16,15],[-17,21],[-13,28],[-10,31],[-6,36],[0,36],[10,37],[11,33],[20,33],[25,32],[32,31],[34,28],[37,22],[39,18],[43,12],[44,11],[51,12],[51,7],[48,5],[44,2],[42,-2],[40,-5],[40,-10],[40,-15],[35,-20],[33,-26],[28,-32],[20,-35],[18,-34],[16,-29],[14,-23],[13,-17],[12,-10],[10,-2],[9,3],[5,5],[0,5],[-5,5],[-8,4],[-12,8],[-16,15]],
  // Madagascar
  [[43,-12],[47,-15],[50,-20],[49,-25],[45,-25],[43,-18],[43,-12]],
  // Europe (Iberia, France, Italy, Balkans, eastward)
  [[-9,37],[-9,41],[-9,43],[-2,44],[-2,48],[0,49],[4,51],[8,54],[8,57],[11,58],[10,54],[13,54],[19,54],[20,56],[24,57],[26,60],[30,60],[30,55],[28,50],[28,46],[30,46],[34,46],[38,47],[40,46],[42,43],[38,42],[34,42],[28,41],[26,40],[23,40],[20,42],[18,42],[16,42],[18,41],[16,38],[15,38],[13,42],[8,44],[7,44],[3,43],[-2,40],[-6,38],[-9,37]],
  // Scandinavia
  [[5,58],[6,62],[10,64],[15,67],[20,69],[25,71],[28,70],[26,66],[23,66],[20,64],[16,62],[12,60],[8,58],[5,58]],
  // Great Britain
  [[-5,50],[-6,52],[-5,55],[-3,58],[-2,57],[0,53],[-1,51],[-5,50]],
  // Ireland
  [[-10,52],[-10,54],[-7,55],[-6,53],[-9,51],[-10,52]],
  // Eurasia bridge (Russia / Central Asia — connects Europe to Asia)
  [[28,48],[32,55],[40,60],[50,63],[62,65],[74,67],[84,67],[90,62],[86,55],[78,51],[68,49],[58,48],[48,48],[40,48],[32,47],[28,48]],
  // Asia (Middle East to Siberia to China)
  [[40,40],[44,40],[48,44],[50,47],[55,52],[60,55],[68,55],[60,62],[68,68],[78,72],[90,74],[105,75],[120,73],[135,72],[150,70],[160,68],[170,67],[178,66],[170,60],[162,58],[156,52],[143,52],[140,48],[135,44],[131,43],[130,40],[127,40],[122,40],[121,37],[122,31],[121,28],[118,24],[110,21],[108,16],[106,10],[104,9],[100,13],[98,16],[94,16],[90,22],[88,22],[80,15],[77,8],[73,16],[68,24],[66,25],[62,25],[57,25],[56,27],[50,30],[48,30],[48,25],[44,29],[40,36],[36,36],[36,40],[40,40]],
  // Sumatra
  [[95,5],[98,3],[100,0],[102,-3],[105,-5],[103,-5],[100,-2],[97,2],[95,5]],
  // Java
  [[105,-6],[110,-7],[114,-8],[112,-8],[108,-7],[105,-6]],
  // Borneo
  [[109,2],[114,4],[118,3],[119,-1],[116,-3],[111,-3],[109,0],[109,2]],
  // Sulawesi
  [[120,1],[123,1],[125,-2],[123,-5],[120,-3],[119,0],[120,1]],
  // New Guinea
  [[131,-1],[138,-3],[144,-4],[150,-7],[146,-8],[140,-7],[134,-4],[131,-1]],
  // Philippines
  [[120,5],[122,8],[124,11],[126,14],[125,18],[121,18],[120,13],[120,5]],
  // Japan
  [[129,31],[130,34],[133,35],[136,37],[139,39],[142,42],[144,45],[146,44],[144,41],[142,38],[140,36],[137,34],[134,33],[131,32],[129,31]],
  // Australia
  [[114,-22],[114,-26],[116,-31],[118,-34],[123,-34],[129,-32],[131,-31],[134,-33],[138,-35],[141,-38],[144,-38],[147,-38],[150,-37],[153,-31],[153,-27],[151,-24],[146,-19],[142,-11],[137,-12],[136,-15],[130,-13],[126,-14],[122,-17],[114,-22]],
  // New Zealand
  [[166,-46],[168,-44],[171,-42],[173,-41],[175,-39],[177,-38],[175,-41],[172,-43],[170,-45],[167,-47],[166,-46]],
];
function pointInPoly(lon, lat, poly){
  let inside=false;
  for(let i=0,j=poly.length-1;i<poly.length;j=i++){
    const xi=poly[i][0], yi=poly[i][1], xj=poly[j][0], yj=poly[j][1];
    if(((yi>lat)!==(yj>lat)) && (lon < (xj-xi)*(lat-yi)/(yj-yi)+xi)) inside=!inside;
  }
  return inside;
}
function isLand(lon, lat){ for(const p of CONTINENTS) if(pointInPoly(lon,lat,p)) return true; return false; }

function Globe({ size = 520 }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current; if (!canvas) return;
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const ctx = canvas.getContext('2d');
    let dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = size, H = size;
    const setup = () => {
      W = canvas.clientWidth || size; H = W;
      canvas.width = W*dpr; canvas.height = H*dpr;
      ctx.setTransform(dpr,0,0,dpr,0,0);
    };
    setup();
    window.addEventListener('resize', setup);
    let ro; if (window.ResizeObserver){ ro = new ResizeObserver(() => setup()); ro.observe(canvas); }

    const baseLon = 0.9;           // bring Americas/Atlantic to front
    let theta = 0;                 // spin angle (horizontal)
    let tilt = 0;                  // no tilt — equator centered, neither up nor down
    let velTheta = 0;              // inertia after drag
    let dragging = false, lastX = 0, lastY = 0, idleSpin = true;
    const R = () => Math.min(W,H)*0.40;
    const C = () => [W/2, H/2];

    // pointer drag to spin
    const onDown = (e) => { dragging = true; idleSpin = false; velTheta = 0;
      lastX = e.clientX; lastY = e.clientY; canvas.style.cursor = 'grabbing';
      if (canvas.setPointerCapture && e.pointerId!=null) { try{canvas.setPointerCapture(e.pointerId);}catch(_){} } };
    const onMove = (e) => { if (!dragging) return;
      const dx = e.clientX - lastX, dy = e.clientY - lastY;
      lastX = e.clientX; lastY = e.clientY;
      theta += dx * 0.006; velTheta = dx * 0.006;
      tilt = Math.max(-1.15, Math.min(0.6, tilt - dy * 0.005));
      if (e.cancelable) e.preventDefault(); };
    const onUp = () => { if (!dragging) return; dragging = false; idleSpin = true; canvas.style.cursor = 'grab'; };
    canvas.style.cursor = 'grab';
    canvas.addEventListener('pointerdown', onDown);
    window.addEventListener('pointermove', onMove, { passive:false });
    window.addEventListener('pointerup', onUp);

    // precompute graticule polylines (lat/lon grids)
    const grat = [];
    for (let lon=-180; lon<180; lon+=30){ const line=[]; for(let lat=-80;lat<=80;lat+=4) line.push(llToVec(lat,lon)); grat.push(line); }
    for (let lat=-60; lat<=60; lat+=30){ const line=[]; for(let lon=-180;lon<=180;lon+=4) line.push(llToVec(lat,lon)); grat.push(line); }

    // precompute land dots (continents) — dense dotted-globe look
    const landDots = [];
    for (let lat=-80; lat<=82; lat+=2){
      const rowStep = 2 / Math.max(0.15, Math.cos(lat*Math.PI/180));
      for (let lon=-180; lon<180; lon+=rowStep){
        if (isLand(lon, lat)) landDots.push(llToVec(lat, lon));
      }
    }

    // arcs: market node -> hub, lifted off surface
    const hubV = llToVec(BNE_HUB.lat, BNE_HUB.lon);
    const arcs = BNE_NODES.map((n,i) => {
      const a = llToVec(n.lat, n.lon); const pts=[]; const N=64;
      for(let k=0;k<=N;k++){ const t=k/N; const v=norm(slerp(a,hubV,t)); const lift=1+0.18*Math.sin(Math.PI*t); pts.push([v[0]*lift,v[1]*lift,v[2]*lift]); }
      return { pts, a, phase: i/BNE_NODES.length };
    });

    const project = (v) => {
      let p = rotY(v, theta + baseLon);
      p = rotX(p, tilt);
      const [cx,cy] = C(); const r = R();
      return { x: cx + p[0]*r, y: cy - p[1]*r, z: p[2] };
    };

    let raf, t0 = performance.now();
    const draw = (now) => {
      const dt = now - t0; t0 = now;
      if (dragging) {
        // rotation controlled by pointer
      } else if (Math.abs(velTheta) > 0.0002) {
        theta += velTheta; velTheta *= 0.94;        // inertia after release
        if (Math.abs(velTheta) <= 0.0002) idleSpin = true;
      } else if (idleSpin && !reduce) {
        theta += dt * 0.0001;                       // slow auto-spin
      }
      const [cx,cy] = C(); const r = R();
      ctx.clearRect(0,0,W,H);

      // glassy white sphere (transparent)
      let sg = ctx.createRadialGradient(cx-r*0.36, cy-r*0.40, r*0.1, cx, cy, r*1.05);
      sg.addColorStop(0,'rgba(255,255,255,0.17)');
      sg.addColorStop(0.6,'rgba(255,255,255,0.06)');
      sg.addColorStop(1,'rgba(255,255,255,0.02)');
      ctx.fillStyle = sg; ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.fill();

      // limb ring
      ctx.lineWidth = 1.1; ctx.strokeStyle = 'rgba(255,255,255,0.45)';
      ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.stroke();

      // graticule (front-facing segments, subtle)
      ctx.lineWidth = 1;
      for (const line of grat){
        for (let k=0;k<line.length-1;k++){
          const p1=project(line[k]), p2=project(line[k+1]);
          if (p1.z<=0 || p2.z<=0) continue;
          const a = Math.min(p1.z,p2.z);
          ctx.strokeStyle = `rgba(255,255,255,${0.05+0.13*a})`;
          ctx.beginPath(); ctx.moveTo(p1.x,p1.y); ctx.lineTo(p2.x,p2.y); ctx.stroke();
        }
      }

      // land dots (continents) — front solid, back translucent (see-through globe)
      const dotR = Math.max(1.3, r*0.0095);
      for (const v of landDots){
        const p = project(v);
        if (p.z > 0.02){
          ctx.fillStyle = `rgba(255,255,255,${0.4 + 0.6*p.z})`;
          ctx.beginPath(); ctx.arc(p.x, p.y, dotR, 0, Math.PI*2); ctx.fill();
        } else {
          // far side — faint, so you can see it through the globe while it spins
          ctx.fillStyle = `rgba(214,196,240,${0.07 + 0.08*(p.z+1)})`;
          ctx.beginPath(); ctx.arc(p.x, p.y, dotR*0.8, 0, Math.PI*2); ctx.fill();
        }
      }

      // arcs + traveling pulses
      for (const arc of arcs){
        // arc line
        ctx.lineWidth = 1.6;
        for (let k=0;k<arc.pts.length-1;k++){
          const p1=project(arc.pts[k]), p2=project(arc.pts[k+1]);
          if (p1.z<=-0.05 || p2.z<=-0.05) continue;
          const a = Math.max(0, Math.min(1,(Math.min(p1.z,p2.z)+0.2)));
          ctx.strokeStyle = `rgba(255,255,255,${0.2+0.5*a})`;
          ctx.beginPath(); ctx.moveTo(p1.x,p1.y); ctx.lineTo(p2.x,p2.y); ctx.stroke();
        }
        // node at origin (market)
        const np = project(arc.a);
        if (np.z>0){
          ctx.fillStyle = `rgba(255,255,255,${0.4+0.5*np.z})`;
          ctx.beginPath(); ctx.arc(np.x,np.y, 2.6, 0, Math.PI*2); ctx.fill();
        }
        // traveling pulse — market → Brazil
        const speed = reduce ? 0 : 0.00016;
        const tt = ((now*speed)+arc.phase) % 1;
        const idx = Math.floor(tt*(arc.pts.length-1));
        const pp = project(arc.pts[idx]);
        if (pp.z>0){
          ctx.shadowColor = 'rgba(123,47,190,0.9)'; ctx.shadowBlur = 10;
          ctx.fillStyle = '#9B55D6';
          ctx.beginPath(); ctx.arc(pp.x,pp.y, 2.8, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 0;
        }
        // traveling pulse — Brazil → market (reverse, staggered)
        const tt2 = ((now*speed)+arc.phase+0.5) % 1;
        const idx2 = Math.floor((1-tt2)*(arc.pts.length-1));
        const pp2 = project(arc.pts[idx2]);
        if (pp2.z>0){
          ctx.shadowColor = 'rgba(198,163,236,0.9)'; ctx.shadowBlur = 9;
          ctx.fillStyle = '#E4D2F7';
          ctx.beginPath(); ctx.arc(pp2.x,pp2.y, 2.4, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 0;
        }
      }

      // hub (São Paulo) — pulsing
      const hp = project(hubV);
      if (hp.z>0){
        const pulse = reduce ? 0.5 : (0.5+0.5*Math.sin(now*0.004));
        ctx.strokeStyle = `rgba(155,85,214,${0.5*(1-pulse)+0.1})`;
        ctx.lineWidth = 1.4;
        ctx.beginPath(); ctx.arc(hp.x,hp.y, 6+pulse*9, 0, Math.PI*2); ctx.stroke();
        ctx.shadowColor = 'rgba(155,85,214,0.95)'; ctx.shadowBlur = 16;
        ctx.fillStyle = '#9B55D6';
        ctx.beginPath(); ctx.arc(hp.x,hp.y, 4.6, 0, Math.PI*2); ctx.fill();
        ctx.shadowBlur = 0;
        ctx.fillStyle = '#fff';
        ctx.beginPath(); ctx.arc(hp.x,hp.y, 1.8, 0, Math.PI*2); ctx.fill();
      }

      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', setup);
      if (ro) ro.disconnect();
      canvas.removeEventListener('pointerdown', onDown);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp); };
  }, [size]);

  return <canvas ref={ref} style={{ display:'block', width:'100%', maxWidth:size, aspectRatio:'1/1', margin:'0 auto', touchAction:'none', userSelect:'none' }} aria-hidden="true"></canvas>;
}

Object.assign(window, { Globe });

/* live timezone helpers */
function zoneOffsetHours(zone){
  const now = new Date();
  const inv = new Date(now.toLocaleString('en-US', { timeZone: zone }));
  return (inv - now) / 3600000;
}
function fmtTime(zone){
  try { return new Date().toLocaleTimeString('en-US', { timeZone: zone, hour:'numeric', minute:'2-digit', hour12:true }); }
  catch(e){ return '--:--'; }
}
function diffLabel(tz, hubTz){
  const d = Math.round(zoneOffsetHours(tz) - zoneOffsetHours(hubTz));
  if (d === 0) return '±0h';
  return (d > 0 ? '+' : '') + d + 'h';
}

function RouteRow({ rt, hubTz }){
  const [, tick] = useState(0);
  useEffect(() => { const id = setInterval(() => tick(x => x+1), 20000); return () => clearInterval(id); }, []);
  const zones = rt.zones || [];
  const multi = zones.length > 1;
  const sub = multi
    ? zones.map(z => `${z.label} ${fmtTime(z.tz)}`).join('   ·   ')
    : `${zones[0].label ? zones[0].label + ' ' : ''}${fmtTime(zones[0].tz)}${zones[0].label ? '' : ' local'}`;
  return (
    <div style={{ display:'flex', alignItems:'center', gap:13, padding:'12px 16px', borderRadius:'var(--r-card)',
      background:'rgba(255,255,255,.05)', border:'1px solid rgba(255,255,255,.1)' }}>
      <span style={{ display:'inline-flex' }}>{rt.flags.map((f,j)=><span key={f} style={{ marginLeft:j>0?-7:0 }}><FlagIcon code={f} size={22}/></span>)}</span>
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ fontFamily:'var(--font-body)', fontWeight:600, fontSize:15, color:'#fff' }}>{rt.label}</div>
        <div style={{ fontFamily:'var(--font-mono)', fontSize:11.5, color:'rgba(255,255,255,.5)', letterSpacing:'.02em', marginTop:1 }}>{sub}</div>
      </div>
      <div style={{ display:'flex', flexDirection:'row', gap:6, alignItems:'center', flexWrap:'wrap', justifyContent:'flex-end' }}>
        {zones.map((z,i) => (
          <span key={i} style={{ fontFamily:'var(--font-mono)', fontSize:11.5, fontWeight:500, color:'#C6A3EC', padding:'3px 8px', borderRadius:'var(--r-pill)', background:'rgba(123,47,190,.2)', border:'1px solid rgba(123,47,190,.12)', whiteSpace:'nowrap' }}>
            {multi ? `${z.label} ` : ''}{diffLabel(z.tz, hubTz)}
          </span>
        ))}
      </div>
      <Icon name="arrow" size={15} style={{ color:'var(--accent)', flexShrink:0 }}/>
      <span style={{ display:'inline-flex' }}><FlagIcon code="br" size={22}/></span>
    </div>
  );
}

/* Section: text + routes (left, constrained) · large cropped globe bleeding off the right. */
function GlobeSection({ t }) {
  const g = t.globe;
  return (
    <Section bg="navy" label="Global network" style={{ overflow:'hidden', position:'relative', background:'radial-gradient(120% 100% at 85% 50%, #0C1835 0%, #0A1530 46%, #060D1F 100%)' }}>
      {/* large globe, cropped off the right edge — stays behind content, keeps spinning */}
      <div className="bne-globe-float">
        <div aria-hidden="true" style={{ position:'absolute', inset:'-6%', borderRadius:'50%', pointerEvents:'none',
          background:'radial-gradient(circle, rgba(155,85,214,.4) 0%, rgba(123,47,190,.16) 44%, transparent 66%)' }}></div>
        <div style={{ position:'relative', zIndex:1 }}><Globe size={850} /></div>
      </div>
      <Container style={{ ...window.PAD, position:'relative', zIndex:2, pointerEvents:'none' }}>
        <div style={{ maxWidth:540, pointerEvents:'auto' }}>
          <Reveal><Eyebrow style={{ marginBottom:18 }}>{g.eyebrow}</Eyebrow></Reveal>
          <Reveal delay={60}>
            <h2 className="t-h2" style={{ margin:0, color:'#fff', textWrap:'balance' }}>
              {g.title.split('\n').map((l,i)=><React.Fragment key={i}>{i>0&&<br/>}{l}</React.Fragment>)}
            </h2>
          </Reveal>
          <Reveal delay={120}><p className="t-lead" style={{ color:'rgba(255,255,255,.78)', marginTop:18, maxWidth:440 }}>{g.sub}</p></Reveal>
          <Reveal delay={180}>
            <div style={{ marginTop:28 }}>
              <div className="t-caption" style={{ fontFamily:'var(--font-mono)', fontSize:11, letterSpacing:'.08em', textTransform:'uppercase', color:'rgba(255,255,255,.42)', marginBottom:12 }}>{g.localCap}</div>
              <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
                {g.routes.map((rt, i) => <RouteRow key={i} rt={rt} hubTz={g.hubTz} />)}
              </div>
            </div>
          </Reveal>
          <Reveal delay={240}>
            <div style={{ display:'inline-flex', alignItems:'center', gap:9, marginTop:22, fontFamily:'var(--font-mono)', fontSize:12.5, color:'rgba(255,255,255,.66)', letterSpacing:'.03em' }}>
              <span style={{ width:8, height:8, borderRadius:'50%', background:'#9B55D6', boxShadow:'0 0 10px rgba(155,85,214,.9)' }}></span>
              Hub: {g.hub} · <HubClock hubTz={g.hubTz} />
            </div>
          </Reveal>
        </div>
      </Container>
    </Section>
  );
}

function HubClock({ hubTz }){
  const [, tick] = useState(0);
  useEffect(() => { const id = setInterval(() => tick(x => x+1), 20000); return () => clearInterval(id); }, []);
  return <span style={{ color:'#fff', fontWeight:500 }}>{fmtTime(hubTz)}</span>;
}

Object.assign(window, { GlobeSection });

