
// ─── Project Gen Z — AAA Holographic Globe (Three.js) ─────────────────────
// Dot-matrix continents, organic infection spread, fresnel rim, mouse parallax

const { useRef, useEffect, useState, useCallback } = React;

const ACCENT     = 0xc8ff00;
const DANGER     = 0xff3b3b;
const INFECTION  = 0xff7700;
const ACCENT_CSS = '#c8ff00';
const DANGER_CSS = '#ff3b3b';

const EPICENTER = { lat: 37.84, lon: -84.27 };

const TARGET_CITIES = [
  { lat: 40.71,  lon: -74.01, name: 'Nova York',        delay: 5   },
  { lat: 51.51,  lon: -0.13,  name: 'Londres',          delay: 8   },
  { lat: 35.68,  lon: 139.65, name: 'Tóquio',           delay: 11  },
  { lat: -23.55, lon: -46.63, name: 'São Paulo',        delay: 13  },
  { lat: -33.87, lon: 151.21, name: 'Sydney',           delay: 15  },
  { lat: 55.76,  lon: 37.62,  name: 'Moscou',           delay: 17  },
  { lat: 19.08,  lon: 72.88,  name: 'Mumbai',           delay: 18.5},
  { lat: 30.04,  lon: 31.24,  name: 'Cairo',            delay: 20  },
  { lat: 52.52,  lon: 13.41,  name: 'Berlim',           delay: 21.5},
  { lat: 19.43,  lon: -99.13, name: 'Cidade do México', delay: 23  },
];

function ll2v(lat, lon, r) {
  r = r || 1;
  var phi   = (90 - lat) * Math.PI / 180;
  var theta = (lon + 180) * Math.PI / 180;
  return new THREE.Vector3(
    -r * Math.sin(phi) * Math.cos(theta),
     r * Math.cos(phi),
     r * Math.sin(phi) * Math.sin(theta)
  );
}

function haversine(lat1, lon1, lat2, lon2) {
  var R = 6371;
  var dLat = (lat2 - lat1) * Math.PI / 180;
  var dLon = (lon2 - lon1) * Math.PI / 180;
  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

function Globe3D(props) {
  var rotSpeed = props.rotSpeed || 0.00065;
  var size     = props.size || 620;

  var wrapRef     = useRef(null);
  var [labels, setLabels]     = useState([]);
  var [loading, setLoading]   = useState(true);
  var isDragging  = useRef(false);
  var manualRot   = useRef({ x: 0, y: 0 });
  var autoOffset  = useRef(4.8);
  var labelsRef   = useRef([]);

  labelsRef.current = labels;

  // Population counter — proportional to infection progress
  var POP_1993 = 5540000000; // world population 1993
  window._gzInfected = 0;
  window._gzInfPct = 0;
  window._gzLoaded = false;
  window._gzSatPos = { x: 0, y: 0, visible: false };

  // Main Three.js scene — runs ONCE
  useEffect(function() {
    if (!window.THREE || !wrapRef.current) return;
    var wrap = wrapRef.current;
    var w = wrap.clientWidth || window.innerWidth;
    var h = wrap.clientHeight || window.innerHeight;

    // ── Renderer ──────────────────────────────────────────────────────
    var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' });
    renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
    renderer.setSize(w, h);
    renderer.setClearColor(0x000000, 0);
    renderer.domElement.style.position = 'absolute';
    renderer.domElement.style.top = '0';
    renderer.domElement.style.left = '0';
    renderer.domElement.style.width = '100%';
    renderer.domElement.style.height = '100%';
    renderer.domElement.style.pointerEvents = 'none';
    wrap.appendChild(renderer.domElement);

    // ── Scene + Camera ────────────────────────────────────────────────
    var scene  = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(38, w / h, 0.1, 10);
    camera.position.set(-2.2, 0, 2.72);
    camera.lookAt(-0.6, 0, 0);

    // ── Backdrop sphere — opaque black for NormalBlending to work correctly
    scene.add(new THREE.Mesh(
      new THREE.SphereGeometry(1.98, 32, 32),
      new THREE.MeshBasicMaterial({ color: 0x222222, side: THREE.BackSide })
    ));

    var globe = new THREE.Group();
    globe.rotation.z = -0.28;
    scene.add(globe);

    // ── Ocean sphere — opaque
    globe.add(new THREE.Mesh(
      new THREE.SphereGeometry(0.998, 64, 64),
      new THREE.MeshBasicMaterial({ color: 0x222222 })
    ));

    // ── Atmosphere ────────────────────────────────────────────────────
    var atmoMats = [];
    [1.80, 1.50, 1.25, 1.10].forEach(function(r, i) {
      var op = [0.04, 0.10, 0.22, 0.18][i];
      var mat = new THREE.MeshBasicMaterial({
        color: ACCENT, side: THREE.BackSide,
        transparent: true, opacity: op,
        blending: THREE.AdditiveBlending, depthWrite: false,
      });
      scene.add(new THREE.Mesh(new THREE.SphereGeometry(r, 32, 32), mat));
      atmoMats.push(mat);
    });

    // ── Fresnel rim ──────────────────────────────────────────────────
    var fresnelColor = new THREE.Color(ACCENT);
    globe.add(new THREE.Mesh(
      new THREE.SphereGeometry(1.005, 64, 64),
      new THREE.ShaderMaterial({
        uniforms: { glowColor: { value: fresnelColor } },
        vertexShader: [
          'varying vec3 vNorm; varying vec3 vView;',
          'void main() {',
          '  vNorm = normalize(normalMatrix * normal);',
          '  vView = normalize(-(modelViewMatrix * vec4(position,1.0)).xyz);',
          '  gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);',
          '}'
        ].join('\n'),
        fragmentShader: [
          'uniform vec3 glowColor;',
          'varying vec3 vNorm; varying vec3 vView;',
          'void main() {',
          '  float rim = pow(1.0 - clamp(dot(vNorm, vView), 0.0, 1.0), 3.5) * 2.2;',
          '  gl_FragColor = vec4(glowColor * rim, rim * 0.95);',
          '}'
        ].join('\n'),
        transparent: true, blending: THREE.AdditiveBlending,
        depthWrite: false, side: THREE.FrontSide,
      })
    ));

    // ── Lat/lon grid ─────────────────────────────────────────────────
    var gridMat = new THREE.LineBasicMaterial({ color: ACCENT, transparent: true, opacity: 0.18, blending: THREE.AdditiveBlending, depthWrite: false });
    var equMat  = new THREE.LineBasicMaterial({ color: ACCENT, transparent: true, opacity: 0.45, blending: THREE.AdditiveBlending, depthWrite: false });

    for (var lat = -75; lat <= 75; lat += 15) {
      var pts = [];
      for (var i = 0; i < 97; i++) pts.push(ll2v(lat, -180 + i * 360 / 96));
      globe.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), lat === 0 ? equMat : gridMat));
    }
    for (var lon = 0; lon < 360; lon += 15) {
      var pts2 = [];
      for (var j = 0; j < 65; j++) pts2.push(ll2v(-90 + j * 180 / 64, lon - 180));
      globe.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts2), gridMat));
    }

    // ── Continent dots — SINGLE layer, color buffer updated per frame ─
    var dotColorArray = null;
    var dotMeta       = null;
    var dotCount      = 0;
    var colorAttr     = null;
    var NA_BOUNDS = { latMin: 10, latMax: 80, lonMin: -175, lonMax: -45 };

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://cdn.jsdelivr.net/npm/world-atlas@2/land-110m.json');
    xhr.onload = function() {
      try {
        var topo = JSON.parse(xhr.responseText);
        var land = window.topojson.feature(topo, topo.objects.land);

        var MW = 720, MH = 360;
        var mc = document.createElement('canvas'); mc.width = MW; mc.height = MH;
        var mx = mc.getContext('2d'); mx.fillStyle = '#fff';

        var polys = land.type === 'FeatureCollection' ? land.features.map(function(f){return f.geometry}) : [land.geometry];
        polys.forEach(function(geom) {
          var rings = geom.type === 'Polygon' ? [geom.coordinates] : geom.coordinates;
          rings.forEach(function(poly) {
            poly.forEach(function(ring) {
              mx.beginPath();
              var prevLn = ring[0][0];
              ring.forEach(function(pt, i) {
                var x = (pt[0] + 180) / 360 * MW;
                var y = (90 - pt[1]) / 180 * MH;
                if (i === 0 || Math.abs(pt[0] - prevLn) > 170) mx.moveTo(x, y);
                else mx.lineTo(x, y);
                prevLn = pt[0];
              });
              mx.closePath(); mx.fill();
            });
          });
        });

        var mobile = innerWidth < 768;
        var step = mobile ? 0.9 : 0.45;
        var positions = [], colors = [], meta = [];

        for (var la = -90; la <= 90; la += step) {
          for (var ln = -180; ln < 180; ln += step) {
            var px = Math.min(Math.round((ln + 180) / 360 * MW), MW - 1);
            var py = Math.min(Math.round((90 - la) / 180 * MH), MH - 1);
            if (mx.getImageData(px, py, 1, 1).data[0] > 128) {
              var jitter = 1 + (Math.random() - 0.5) * 0.002;
              var v = ll2v(la, ln, 1.004 * jitter);
              positions.push(v.x, v.y, v.z);
              var b = 0.5 + Math.random() * 0.5;
              colors.push(0.784 * b, b, 0);
              meta.push({ lat: la, lon: ln, brightness: b, infectedAt: 999, turned: false });
            }
          }
        }

        // ── Organic infection spread (BFS point-to-point contagion) ─────
        // Build spatial grid for fast neighbor lookup
        var CELL = 3; // degrees per grid cell
        var grid = {};
        for (var gi = 0; gi < meta.length; gi++) {
          var d = meta[gi];
          var key = Math.floor(d.lat / CELL) + '|' + Math.floor(d.lon / CELL);
          if (!grid[key]) grid[key] = [];
          grid[key].push(gi);
        }

        // Bucket queue (priority queue via time buckets)
        var BSIZE = 0.5;
        var MAX_T = 80;
        var buckets = [];
        for (var bi = 0; bi < Math.ceil(MAX_T / BSIZE); bi++) buckets.push([]);
        var infected = new Uint8Array(meta.length);

        // Seed: dots near Kentucky epicenter
        for (var si = 0; si < meta.length; si++) {
          var sd = meta[si];
          var sdist = haversine(EPICENTER.lat, EPICENTER.lon, sd.lat, sd.lon);
          if (sdist < 200) {
            var t0i = 0.5 + Math.random() * 1.5;
            sd.infectedAt = t0i;
            infected[si] = 1;
            var b0 = Math.min(Math.floor(t0i / BSIZE), buckets.length - 1);
            buckets[b0].push(si);
          }
        }

        // Also seed near each target city (air travel spread)
        TARGET_CITIES.forEach(function(city) {
          for (var ti = 0; ti < meta.length; ti++) {
            if (infected[ti]) continue;
            var td = meta[ti];
            var cdist = haversine(city.lat, city.lon, td.lat, td.lon);
            if (cdist < 100) {
              var ct0 = city.delay + Math.random() * 2;
              td.infectedAt = ct0;
              infected[ti] = 1;
              var cb = Math.min(Math.floor(ct0 / BSIZE), buckets.length - 1);
              if (cb >= 0) buckets[cb].push(ti);
            }
          }
        });

        // BFS: process buckets in time order
        var SPREAD_KM = 500;
        for (var bi = 0; bi < buckets.length; bi++) {
          var bucket = buckets[bi];
          for (var bj = 0; bj < bucket.length; bj++) {
            var ci = bucket[bj];
            var cd = meta[ci];
            var cx = Math.floor(cd.lat / CELL);
            var cy = Math.floor(cd.lon / CELL);

            // Check neighboring cells
            for (var dx = -2; dx <= 2; dx++) {
              for (var dy = -2; dy <= 2; dy++) {
                var nkey = (cx + dx) + '|' + (cy + dy);
                if (!grid[nkey]) continue;
                for (var nk = 0; nk < grid[nkey].length; nk++) {
                  var ni = grid[nkey][nk];
                  if (infected[ni]) continue;
                  var nd = meta[ni];
                  var ndist = haversine(cd.lat, cd.lon, nd.lat, nd.lon);
                  if (ndist > SPREAD_KM) continue;

                  infected[ni] = 1;
                  var delay = (ndist / SPREAD_KM) * 2.5 + Math.random() * 1.5;
                  nd.infectedAt = cd.infectedAt + delay;
                  var nb = Math.min(Math.floor(nd.infectedAt / BSIZE), buckets.length - 1);
                  if (nb >= 0 && nb < buckets.length) buckets[nb].push(ni);
                }
              }
            }
          }
        }

        // Fallback: any uninfected dots (remote islands)
        for (var ui = 0; ui < meta.length; ui++) {
          if (!infected[ui]) meta[ui].infectedAt = 40 + Math.random() * 10;
        }

        // Survivors: ~0.1% of dots stay green forever
        var survivorCount = Math.max(2, Math.floor(meta.length * 0.001));
        var survivorAttempts = 0;
        while (survivorCount > 0 && survivorAttempts < meta.length * 3) {
          var ri = Math.floor(Math.random() * meta.length);
          if (meta[ri].infectedAt < 999 && !meta[ri].isSurvivor) {
            meta[ri].infectedAt = 99999;
            meta[ri].isSurvivor = true;
            survivorCount--;
          }
          survivorAttempts++;
        }
        window._gzSurvivorCount = meta.filter(function(d){ return d.isSurvivor; }).length;

        console.log('[Globe] Organic infection computed:', meta.length, 'dots | Survivors:', window._gzSurvivorCount);
        window._gzLoaded = true;
        window.dispatchEvent(new Event('globe-loaded'));

        // Build geometry
        var posArr = new Float32Array(positions);
        var colArr = new Float32Array(colors);
        var geo = new THREE.BufferGeometry();
        geo.setAttribute('position', new THREE.BufferAttribute(posArr, 3));
        colorAttr = new THREE.BufferAttribute(colArr, 3);
        geo.setAttribute('color', colorAttr);

        globe.add(new THREE.Points(geo, new THREE.PointsMaterial({
          size: mobile ? 0.01 : 0.007,
          vertexColors: true,
          transparent: true, opacity: 0.92,
          blending: THREE.NormalBlending, depthWrite: false,
          sizeAttenuation: true,
        })));

        dotColorArray = colArr;
        dotMeta = meta;
        dotCount = meta.length;

        console.log('[Globe] Loaded', dotCount, 'dots | Sample infectedAt:', meta.slice(0, 5).map(function(d){ return d.infectedAt.toFixed(1) }));
        setLoading(false);
      } catch(err) {
        console.error('[Globe] Error:', err);
        setLoading(false);
      }
    };
    xhr.onerror = function() { setLoading(false); };
    xhr.send();

    // ── Kentucky epicenter ────────────────────────────────────────────
    var epicVec = ll2v(EPICENTER.lat, EPICENTER.lon, 1.022);
    var epicNormal = epicVec.clone().normalize();

    var epicCore = new THREE.Mesh(
      new THREE.SphereGeometry(0.018, 16, 16),
      new THREE.MeshBasicMaterial({ color: DANGER, blending: THREE.AdditiveBlending, depthWrite: false })
    );
    epicCore.position.copy(epicVec);
    globe.add(epicCore);

    var epicRings = [];
    for (var ri = 0; ri < 3; ri++) {
      (function(idx) {
        var mesh = new THREE.Mesh(
          new THREE.RingGeometry(0.016, 0.021, 48),
          new THREE.MeshBasicMaterial({ color: DANGER, side: THREE.DoubleSide, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false })
        );
        mesh.position.copy(epicVec);
        mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), epicNormal);
        globe.add(mesh);
        epicRings.push(mesh);
      })(ri);
    }

    // ── City markers ──────────────────────────────────────────────────
    var cityDots = [];
    var cityRings = [];
    TARGET_CITIES.forEach(function(city) {
      var dot = new THREE.Mesh(
        new THREE.SphereGeometry(0.013, 12, 12),
        new THREE.MeshBasicMaterial({ color: INFECTION, blending: THREE.AdditiveBlending, depthWrite: false })
      );
      dot.position.copy(ll2v(city.lat, city.lon, 1.018));
      dot.visible = false;
      globe.add(dot);
      cityDots.push(dot);

      var ring = new THREE.Mesh(
        new THREE.RingGeometry(0.008, 0.012, 32),
        new THREE.MeshBasicMaterial({ color: INFECTION, side: THREE.DoubleSide, transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false })
      );
      ring.position.copy(ll2v(city.lat, city.lon, 1.016));
      var n = ll2v(city.lat, city.lon, 1.016).clone().normalize();
      ring.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), n);
      ring.visible = false;
      globe.add(ring);
      cityRings.push(ring);
    });

    // ── Scanline ──────────────────────────────────────────────────────
    var scanMat = new THREE.MeshBasicMaterial({ color: ACCENT, transparent: true, opacity: 0.06, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
    var scanMesh = new THREE.Mesh(new THREE.PlaneGeometry(2.6, 0.005), scanMat);
    scene.add(scanMesh);

    // ── Satellite ────────────────────────────────────────────────────
    var satGroup = new THREE.Group();
    // Body
    satGroup.add(new THREE.Mesh(
      new THREE.BoxGeometry(0.018, 0.012, 0.012),
      new THREE.MeshBasicMaterial({ color: 0xcccccc })
    ));
    // Solar panels
    var panelMat = new THREE.MeshBasicMaterial({ color: 0x2244aa, side: THREE.DoubleSide });
    var panel1 = new THREE.Mesh(new THREE.BoxGeometry(0.035, 0.01, 0.002), panelMat);
    panel1.position.x = 0.028;
    satGroup.add(panel1);
    var panel2 = new THREE.Mesh(new THREE.BoxGeometry(0.035, 0.01, 0.002), panelMat);
    panel2.position.x = -0.028;
    satGroup.add(panel2);
    // Antenna
    satGroup.add(new THREE.Mesh(
      new THREE.CylinderGeometry(0.001, 0.001, 0.02, 4),
      new THREE.MeshBasicMaterial({ color: 0x999999 })
    )).position.y = 0.016;
    // Glow dot
    var satGlow = new THREE.Mesh(
      new THREE.SphereGeometry(0.003, 8, 8),
      new THREE.MeshBasicMaterial({ color: ACCENT, blending: THREE.AdditiveBlending, depthWrite: false })
    );
    satGlow.position.y = 0.026;
    satGroup.add(satGlow);
    scene.add(satGroup);

    // ── Mouse parallax ────────────────────────────────────────────────
    var mx2 = 0, my2 = 0, tmx = 0, tmy = 0, lastMove = 0;
    function onMouse(e) {
      if (isDragging.current) return;
      var rect = wrap.getBoundingClientRect();
      tmx = ((e.clientX - rect.left) / rect.width  - 0.5) * 2;
      tmy = ((e.clientY - rect.top)  / rect.height - 0.5) * 2;
      lastMove = Date.now();
    }
    window.addEventListener('mousemove', onMouse);

    // ── Drag inertia ─────────────────────────────────────────────────
    var velocityX = 0, velocityY = 0;
    var lastDragX = 0, lastDragY = 0;
    var lastDragTime = 0;

    function onGlobeDrag(e) {
      isDragging.current = true;
      var now = Date.now();
      var dt = Math.max(now - lastDragTime, 1);
      // Track velocity for inertia
      velocityY = e.detail.dx * 0.005 / dt * 16;
      velocityX = e.detail.dy * 0.003 / dt * 16;
      lastDragTime = now;
      manualRot.current.y += e.detail.dx * 0.005;
      manualRot.current.x += e.detail.dy * 0.003;
      manualRot.current.x = Math.max(-1.2, Math.min(1.2, manualRot.current.x));
    }
    function onGlobeDragEnd() {
      autoOffset.current = manualRot.current.y;
      setTimeout(function() { isDragging.current = false; }, 100);
    }
    window.addEventListener('globe-drag', onGlobeDrag);
    window.addEventListener('globe-drag-end', onGlobeDragEnd);

    // ── Animation loop — NO CYCLE, plays once then holds orange ──────
    var rafId, t0 = null;
    var shownCities = {};

    function frame(ts) {
      rafId = requestAnimationFrame(frame);
      if (!t0) t0 = ts;
      var elapsed = (ts - t0) / 1000;

      // Parallax
      if (!isDragging.current && Date.now() - lastMove > 3000) { tmx *= 0.92; tmy *= 0.92; }
      mx2 += (tmx - mx2) * 0.05;
      my2 += (tmy - my2) * 0.05;
      var parallaxY = isDragging.current ? 0 : mx2 * 0.38;
      var parallaxX = isDragging.current ? 0 : my2 * 0.22;

      // Inertia — apply decaying velocity when not dragging
      if (!isDragging.current) {
        if (Math.abs(velocityY) > 0.0001 || Math.abs(velocityX) > 0.0001) {
          manualRot.current.y += velocityY;
          autoOffset.current += velocityY;
          manualRot.current.x += velocityX;
          manualRot.current.x = Math.max(-1.2, Math.min(1.2, manualRot.current.x));
          velocityY *= 0.94;
          velocityX *= 0.94;
        }
      }

      // Globe rotation
      globe.rotation.y = elapsed * rotSpeed * 60 + autoOffset.current + manualRot.current.y + parallaxY;
      globe.rotation.x = manualRot.current.x + parallaxX;

      // Epicenter pulse
      epicCore.scale.setScalar(1 + Math.sin(elapsed * 5.5) * 0.3);
      for (var ri = 0; ri < epicRings.length; ri++) {
        var ph = (elapsed * 1.3 + ri / 3) % 1;
        epicRings[ri].scale.setScalar(1 + ph * 5);
        epicRings[ri].material.opacity = (1 - ph) * 0.75;
      }

      // Scanline
      scanMesh.position.y = Math.sin(elapsed * 0.32) * 1.1;

      // ── City activation ─────────────────────────────────────────────
      for (var ci = 0; ci < TARGET_CITIES.length; ci++) {
        var city = TARGET_CITIES[ci];
        if (elapsed >= city.delay && !shownCities[city.name]) {
          shownCities[city.name] = true;
          cityDots[ci].visible = true;
          cityRings[ci].visible = true;
          labelsRef.current = labelsRef.current.concat([city.name]).slice(-5);
          setLabels(labelsRef.current.slice());
          window._gzLabels = labelsRef.current.slice();
        }
      }

      // City pulse rings
      for (var cri = 0; cri < cityRings.length; cri++) {
        if (!cityRings[cri].visible) continue;
        var cage = elapsed - TARGET_CITIES[cri].delay;
        var cph = (cage * 0.8) % 1;
        cityRings[cri].scale.setScalar(1 + cph * 8);
        cityRings[cri].material.opacity = (1 - cph) * 0.6;
      }

      // ── Infection: green → red spread + global color shift ─────────
      var infectedCount = 0;
      if (dotColorArray && dotMeta) {
        var dirty = false;
        for (var di = 0; di < dotCount; di++) {
          var d = dotMeta[di];
          if (d.turned || d.infectedAt > elapsed) continue;
          d.turned = true;
          var i3 = di * 3;
          dotColorArray[i3]     = 1.0;
          dotColorArray[i3 + 1] = 0;
          dotColorArray[i3 + 2] = 0;
          dirty = true;
        }
        if (dirty && colorAttr) {
          colorAttr.needsUpdate = true;
        }
        for (var di = 0; di < dotCount; di++) {
          if (dotMeta[di].turned) infectedCount++;
        }
      }

      // ── Proportional population counter ────────────────────────────
      if (dotCount > 0) {
        var t = Math.min(infectedCount / dotCount, 1.0);
        window._gzInfPct = t;
        window._gzInfected = Math.floor(t * POP_1993);

        // Lerp: green (0.78, 1.0, 0) → red (1.0, 0, 0)
        var r = 0.78 + t * 0.22;
        var g = 1.0 - t;
        var b = 0;
        var lerpColor = new THREE.Color(r, g, b);

        for (var ai = 0; ai < atmoMats.length; ai++) {
          atmoMats[ai].color.copy(lerpColor);
        }
        fresnelColor.copy(lerpColor);
        gridMat.color.copy(lerpColor);
        equMat.color.copy(lerpColor);
        scanMat.color.copy(lerpColor);
        // Satellite glow follows infection color
        satGlow.material.color.copy(lerpColor);
      }

      // ── Satellite orbit ────────────────────────────────────────────
      var satAngle = elapsed * 0.25;
      var satRadius = 1.4;
      satGroup.position.set(
        Math.cos(satAngle) * satRadius,
        Math.sin(satAngle * 0.6) * 0.25,
        Math.sin(satAngle) * satRadius
      );
      satGroup.lookAt(0, 0, 0);
      satGlow.scale.setScalar(1 + Math.sin(elapsed * 4) * 0.4);

      // Project satellite to screen — hide when behind globe
      var behindGlobe = satGroup.position.dot(camera.position) < 0;
      var satVec = satGroup.position.clone().project(camera);
      window._gzSatPos = {
        x: (satVec.x * 0.5 + 0.5) * window.innerWidth,
        y: (-satVec.y * 0.5 + 0.5) * window.innerHeight,
        visible: satVec.z < 1 && !behindGlobe
      };

      renderer.render(scene, camera);
    }

    rafId = requestAnimationFrame(frame);

    // ── Resize ────────────────────────────────────────────────────────
    function onResize() {
      var nw = wrap.clientWidth || window.innerWidth;
      var nh = wrap.clientHeight || window.innerHeight;
      renderer.setSize(nw, nh);
      camera.aspect = nw / nh;
      camera.updateProjectionMatrix();
    }
    window.addEventListener('resize', onResize);

    return function() {
      cancelAnimationFrame(rafId);
      window.removeEventListener('mousemove', onMouse);
      window.removeEventListener('globe-drag', onGlobeDrag);
      window.removeEventListener('globe-drag-end', onGlobeDragEnd);
      window.removeEventListener('resize', onResize);
      renderer.dispose();
      if (wrap.contains(renderer.domElement)) wrap.removeChild(renderer.domElement);
    };
  }, []);

  // ── Render globe canvas only (no HUD) ──────────────────────────────
  return (
    <div ref={wrapRef} style={{
      position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh',
      overflow: 'hidden', zIndex: 0, pointerEvents: 'none', background: 'transparent',
    }} />
  );
}

// ── HUD Overlay — always on top, rendered separately ──────────────────
function GlobeHUD() {
  var [infected, setInfected] = useState(0);
  var [labels, setLabels]     = useState([]);
  var [pct, setPct]           = useState(0);
  var [satPos, setSatPos]     = useState({ x: 0, y: 0, visible: false });

  useEffect(function() {
    var id = setInterval(function() {
      setInfected(window._gzInfected || 0);
      setLabels((window._gzLabels || []).slice());
      setPct(window._gzInfPct || 0);
      if (window._gzSatPos) setSatPos(window._gzSatPos);
    }, 200);
    return function() { clearInterval(id); };
  }, []);

  var showDialog = pct >= 0.8 && satPos.visible;

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 50, pointerEvents: 'none',
      fontFamily: 'Share Tech Mono', color: '#fff',
    }}>
      <div style={{ position: 'absolute', top: 80, left: '3%', lineHeight: 1.6 }}>
        <div style={{ fontSize: 8, color: 'rgba(200,255,0,0.32)', letterSpacing: '0.14em' }}>ANALISE DE PROPAGACAO // AO VIVO</div>
        <div style={{ fontSize: 7, color: 'rgba(200,255,0,0.18)', letterSpacing: '0.1em', marginTop: 2 }}>GEN-Z BIOTRACK v4.2.1</div>
      </div>

      <div style={{ position: 'absolute', top: 80, right: '3%', textAlign: 'right', lineHeight: 1.7 }}>
        <div style={{ fontSize: 8, color: 'rgba(200,255,0,0.32)', letterSpacing: '0.1em' }}>SATELITE SL-PROJGZ-01</div>
        <div style={{ fontSize: 7, color: 'rgba(200,255,0,0.2)', letterSpacing: '0.08em' }}>LAT 37.8393 N / LON 84.2700 W</div>
      </div>

      <div style={{ position: 'absolute', bottom: '6%', left: '3%' }}>
        <div style={{ fontSize: 7, color: 'rgba(255,60,60,0.65)', letterSpacing: '0.14em', marginBottom: 4 }}>
          INFECTADOS CONFIRMADOS{(pct >= 1).toLocaleString ? '' : ''}
          {pct >= 1 && <span style={{ color: 'rgba(200,255,0,0.5)', marginLeft: 8 }}>CONTAMINACAO TOTAL</span>}
        </div>
        <div style={{ fontSize: 18, color: '#ff3b3b', textShadow: '0 0 16px rgba(255,60,60,0.7)' }}>
          {infected.toLocaleString('pt-BR')}
        </div>
      </div>

      <div style={{ position: 'absolute', bottom: '6%', right: '3%', textAlign: 'right' }}>
        {labels.slice(-5).map(function(l, i) {
          return (
            <div key={l + i} style={{
              fontSize: 8, color: 'rgba(255,150,50,0.65)', letterSpacing: '0.08em', marginBottom: 3,
              animation: 'fadeInLabel 0.4s ease',
            }}>
              {'\u25CF'} {l.toUpperCase()} — CONTAMINADA
            </div>
          );
        })}
      </div>

    </div>
  );
}

// ── Satellite dialog — same layer as globe (z-index 1) ────────────────
function SatDialog() {
  var [satPos, setSatPos] = useState({ x: 0, y: 0, visible: false });
  var [pct, setPct] = useState(0);

  useEffect(function() {
    var id = setInterval(function() {
      if (window._gzSatPos) setSatPos(window._gzSatPos);
      setPct(window._gzInfPct || 0);
    }, 200);
    return function() { clearInterval(id); };
  }, []);

  if (pct < 0.8 || !satPos.visible) return null;

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none',
    }}>
      <div style={{
        position: 'absolute',
        left: Math.min(satPos.x + 20, window.innerWidth - 220),
        top: Math.max(satPos.y - 60, 80),
        background: 'rgba(20,20,20,0.55)',
        backdropFilter: 'blur(16px)',
        WebkitBackdropFilter: 'blur(16px)',
        border: '1px solid rgba(255,60,60,0.4)',
        borderTop: '2px solid #ff3b3b',
        padding: '12px 16px',
        maxWidth: 200,
        animation: 'fadeIn 0.5s ease',
        fontFamily: 'Share Tech Mono',
      }}>
        <div style={{ fontSize: 8, color: 'rgba(255,60,60,0.6)', letterSpacing: '0.1em', marginBottom: 6 }}>
          SL-PROJGZ-01 // CANAL ABERTO
        </div>
        <div style={{ fontFamily: 'Rajdhani', fontWeight: 700, fontSize: 13, color: '#ff3b3b', letterSpacing: '0.04em', lineHeight: 1.4 }}>
          Satélite SL-PROJGZ-01:{' '}
          <span style={{ color: 'rgba(255,255,255,0.8)' }}>F*deu 🫠</span>
        </div>
        <div style={{
          position: 'absolute', left: -1, bottom: -1,
          width: 12, height: 12,
          borderLeft: '1px solid rgba(255,60,60,0.4)',
          borderBottom: '1px solid rgba(255,60,60,0.4)',
        }} />
      </div>
    </div>
  );
}

// ── Loading overlay — covers everything until globe is ready ──────────
function LoadingOverlay() {
  var [visible, setVisible] = useState(true);
  var [fading, setFading] = useState(false);

  useEffect(function() {
    function onLoaded() {
      setFading(true);
      setTimeout(function() { setVisible(false); }, 800);
    }
    if (window._gzLoaded) {
      onLoaded();
    } else {
      window.addEventListener('globe-loaded', onLoaded);
    }
    return function() { window.removeEventListener('globe-loaded', onLoaded); };
  }, []);

  if (!visible) return null;

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 9998,
      background: '#222222',
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      opacity: fading ? 0 : 1,
      transition: 'opacity 0.8s ease',
      pointerEvents: fading ? 'none' : 'all',
    }}>
      <img src="uploads/genz-logo.png" alt="Z"
        style={{ width: 168, height: 168, objectFit: 'contain', filter: 'drop-shadow(0 0 20px #c8ff00)' }} />
    </div>
  );
}

Object.assign(window, { Globe3D, GlobeHUD, SatDialog, LoadingOverlay });
