/* Senzary / IoTLogIQ — network topology animation.
   Continuous composition: one element tree, all choreography keyed to T + CUES. */

const C = {
  bg: '#14132E', panel: '#1B1A52', indigo: '#3D3B83', indigo3: '#8C8AC0',
  indigo100: '#E8E7F4', pink: '#CE489A', pink3: '#E58CC0', ok: '#2BAE6A',
  warn: '#F2A93B', white: '#FFFFFF',
};
const FD = '"Saira", system-ui, sans-serif';
const FB = '"Manrope", system-ui, sans-serif';
const FM = '"JetBrains Mono", ui-monospace, monospace';

const WW = 5600, WH = 1340, G = 1000;

const MOTION = {
  cam: (u) => window.Easing.easeInOutCubic(u),
  enter: (u) => window.Easing.easeOutCubic(u),
  pop: (u) => window.Easing.easeOutBack(u),
};

const cl = (v, a, b) => Math.max(a, Math.min(b, v));

function kf(list, T, ease) {
  if (T <= list[0].t) return list[0];
  for (let i = 0; i < list.length - 1; i++) {
    const a = list[i], b = list[i + 1];
    if (T <= b.t) {
      const u = ease(cl((T - a.t) / (b.t - a.t || 1), 0, 1));
      const o = {};
      for (const k in a) if (k !== 't') o[k] = a[k] + (b[k] - a[k]) * u;
      return o;
    }
  }
  return list[list.length - 1];
}

function focus(T, s, e, fade) {
  fade = (fade || 0.5) * (window.__nvP || 1);
  if (T <= s - fade || T >= e + fade) return 0;
  return Math.min(cl((T - (s - fade)) / fade, 0, 1), cl((e + fade - T) / fade, 0, 1));
}

const ctrl = (a, b, sag) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 - sag });
const qpath = (a, b, sag) => { const c = ctrl(a, b, sag); return `M ${a.x} ${a.y} Q ${c.x} ${c.y} ${b.x} ${b.y}`; };
function qpt(a, b, sag, t) {
  const c = ctrl(a, b, sag), u = 1 - t;
  return { x: u * u * a.x + 2 * u * t * c.x + t * t * b.x, y: u * u * a.y + 2 * u * t * c.y + t * t * b.y };
}

/* d = depth: 1 = further back (drawn higher + smaller) so the six read as a ring, not a row */
const GWS = [
  { x: 2010, h: 130, d: 0 }, { x: 2180, h: 150, d: 1 }, { x: 2292, h: 118, d: 0 },
  { x: 2700, h: 148, d: 0 }, { x: 2842, h: 150, d: 1 }, { x: 2984, h: 132, d: 0 },
];
const gwBase = (g) => G - g.d * 104;
const gwTop = (g) => gwBase(g) - g.h * (1 - g.d * 0.2);

const SPOT_ORDER = [0, 1, 2, 4, 5, 6, 7, 3];
const SENSORS = [
  { x: 3150, kind: 'animal', name: 'COW / BULL TRACKER', name2: 'RASTREO DE GANADO', val: 'ear tag · 1 uplink/15 min', gw: 5, km: '1.8 km' },
  { x: 3460, kind: 'soil', name: 'SOIL CONDITIONS', name2: 'CONDICIONES DE SUELO', val: '28.4 % vwc', gw: 5, km: '2.1 km' },
  { x: 3760, kind: 'soil', name: 'FERTILIZER', name2: 'FERTILIZANTE', val: 'tank 62 %', gw: 5, km: '2.6 km' },
  { x: 4060, kind: 'gate', name: 'GATE / DOOR', name2: 'PORTÓN / PUERTA', val: 'OPEN 4 min', gw: 4, km: '3.1 km' },
  { x: 4380, kind: 'energy', name: 'POWER / ENERGY', name2: 'ENERGÍA', val: '41.6 kW', gw: 4, km: '3.5 km' },
  { x: 4700, kind: 'vehicle', name: 'VEHICLE TRACKING', name2: 'RASTREO DE VEHÍCULOS', val: 'moving · 34 km/h', gw: 3, km: '4.2 km' },
  { x: 5010, kind: 'water', name: 'TANK LEVEL', name2: 'NIVEL DE TANQUE', val: '2.14 m', gw: 3, km: '4.7 km' },
  { x: 5320, kind: 'water', name: 'WATER QUALITY', name2: 'CALIDAD DEL AGUA', val: 'pH 7.2 · 320 ppm', gw: 3, km: '5.9 km' },
];

const FGW = { x: 4235, top: 372 };
const AP = { x: 2400, top: 740 };
const TW = { x: 760, top: 578 };
const NET = { x: 300, y: 220 };
const PER = { x: 2560 };
const PER2 = { x: 2210 };

const VERTICALS = [
  { id: 'v-farm', en: 'Farms', es: 'Fincas' },
  { id: 'v-city', en: 'Cities & campuses', es: 'Ciudades y campus' },
  { id: 'v-port', en: 'Ports & warehousing', es: 'Puertos y almacenes' },
  { id: 'v-stadium', en: 'Stadiums & parks', es: 'Estadios y parques' },
  { id: 'v-oil', en: 'Remote oil & gas', es: 'Petróleo y gas remoto' },
];

/* ---------------- plan (top-down) view ---------------- */

const PW = 7000, PH = 5600;
const MI = 300;                                     /* world px per mile — the plan's scale */
const M = MI / 1609;                                /* world px per metre */
const PRING = 250 * M * 9;                          /* one large WiFi / CBRS coverage area */
const PAPS = [{ x: 3730, y: 2680 }];
const PAP = PAPS[0];
/* one LoRaWAN gateway, standing beside the access point */
const PGWS = [{ i: 0, ap: 0, x: 3730 + 150, y: 2680 - 96 }];
/* three sector fans of LoRaWAN sensors out at the edges */
const PSECTORS = [
  { deg: -60, label: 'Field', spread: 44, lr: 1230 },
  { deg: 16, label: 'Field', spread: 44, lr: 1230 },
  { deg: 96, label: 'Field', spread: 40, lr: 900 },
];
const PHUB = { x: 3730, y: 2680 };
const RINGS = [
  { mi: 5, lb: '5 miles', deg: 152 },
  { mi: 10, lb: '10 miles · LoRaWAN edge', deg: 128, hero: true },
];
const PCELL = { x: 2410, y: 1970 };
const PNET = { x: 1960, y: 1700 };

function rng(seed) {
  let a = seed;
  return function () {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

const PSENSORS = (function () {
  const r = rng(20260822);
  const kinds = ['animal', 'soil', 'person', 'vehicle', 'gate', 'animal', 'water', 'person', 'energy', 'soil', 'animal', 'vehicle'];
  const out = [];
  PSECTORS.forEach((sec, si) => {
    for (let i = 0; i < 7; i++) {
      const ang = (sec.deg + (r() - 0.5) * sec.spread * 2) * Math.PI / 180;
      const rad = MI * (1.4 + r() * 7.8);
      const x = PHUB.x + rad * Math.cos(ang) * 1.06;
      const y = PHUB.y + rad * Math.sin(ang) * 0.92;
      let gi = 0, best = Infinity;
      PGWS.forEach((g) => {
        const d = (g.x - x) * (g.x - x) + (g.y - y) * (g.y - y);
        if (d < best) { best = d; gi = g.i; }
      });
      out.push({ x, y, sec: si, kind: kinds[(si * 7 + i) % kinds.length], gw: gi, ph: r() });
    }
  });
  return out;
})();

const HEROES = [
  { kind: 'animal', mi: 1.6, deg: -10, en: 'One cow', es: 'Una vaca' },
  { kind: 'vehicle', mi: 2.9, deg: 8, en: 'One truck', es: 'Un camión' },
  { kind: 'water', mi: 4.1, deg: -7, en: 'One tank', es: 'Un tanque' },
  { kind: 'gate', mi: 5.3, deg: 9, en: 'One door', es: 'Una puerta' },
  { kind: 'person', mi: 2.2, deg: 26, en: 'One worker', es: 'Un trabajador' },
].map((h) => {
  const a = h.deg * Math.PI / 180;
  const x = PHUB.x + h.mi * MI * Math.cos(a) * 1.06;
  const y = PHUB.y + h.mi * MI * Math.sin(a) * 0.92;
  let gi = 0, best = Infinity;
  PGWS.forEach((g) => {
    const d = (g.x - x) * (g.x - x) + (g.y - y) * (g.y - y);
    if (d < best) { best = d; gi = g.i; }
  });
  return { ...h, x, y, gw: gi };
});

const SENSOR_Y = { soil: 946, energy: 902, water: 848, animal: 930, gate: 894, vehicle: 902, person: 920 };
const SENSOR_COL = { soil: '#2BAE6A', energy: '#F2A93B', water: '#2E7AD1', animal: '#CE489A', gate: '#E8E7F4', vehicle: '#5B58B5', person: '#7FB2E8' };
const KIND_LABEL = [
  { k: 'soil', en: 'Soil moisture', es: 'Humedad de suelo' },
  { k: 'water', en: 'Water level', es: 'Nivel de agua' },
  { k: 'energy', en: 'Energy metering', es: 'Medición de energía' },
  { k: 'animal', en: 'Livestock trackers', es: 'Rastreo de ganado' },
  { k: 'gate', en: 'Gate / door open', es: 'Portón / puerta' },
  { k: 'vehicle', en: 'Vehicle trackers', es: 'Rastreo de vehículos' },
  { k: 'person', en: 'People on site', es: 'Personal en sitio' },
];

/* pole heights, in world units — kept low so the site reads as ground-level */
const SB = { x: PHUB.x - 6.6 * MI, y: PHUB.y + 1.1 * MI };
const H = { ap: 150, gw: 82, cell: 168, net: 178 };
const gwH = (i) => H.gw + (i % 3) * 12;

/* replicate the CSS transform chain so links can be drawn between antenna tops */
function mkProj(cam) {
  const z = cam.z, d = 2400;
  const OX = 960, OY = 1080 * 0.54, PX = 960, PY = 1080 * 0.46;
  const tr = cam.tilt * Math.PI / 180, yr = cam.yaw * Math.PI / 180;
  const ct = Math.cos(tr), st = Math.sin(tr), cy = Math.cos(yr), sy = Math.sin(yr);
  return function (wx, wy, h) {
    const u = 960 - cam.cx * z + wx * z, v = 540 - cam.cy * z + wy * z;
    const ax = u - OX, ay = v - OY, az = (h || 0) * z;
    const rx = ax * cy - ay * sy, ry = ax * sy + ay * cy;
    const y2 = ry * ct - az * st, z2 = ry * st + az * ct;
    const k = d / Math.max(200, d - z2);
    return { x: PX + (OX + rx - PX) * k, y: PY + (OY + y2 - PY) * k };
  };
}

function Bill({ x, y, z, yaw, tilt, children }) {
  return (
    <div style={{
      position: 'absolute', left: x, top: y, transformStyle: 'preserve-3d',
      transform: `rotateZ(${-yaw}deg) rotateX(${-tilt}deg) scale(${1 / z})`,
    }}>
      <div style={{ position: 'absolute', left: 0, top: 0, transform: 'translate(-50%, -100%)', whiteSpace: 'nowrap' }}>
        {children}
      </div>
    </div>
  );
}

function Pole({ x, y, h, rise, color, w, tilt, cap }) {
  const hh = Math.max(0, h * rise);
  return (
    <div style={{
      position: 'absolute', left: x, top: y, transformStyle: 'preserve-3d',
      transform: 'rotateX(-90deg)', transformOrigin: '0 0',
    }}>
      <div style={{
        position: 'absolute', left: -(w || 6) / 2, top: -hh, width: w || 6, height: hh,
        background: `linear-gradient(180deg, ${color} 0%, rgba(140,138,192,0.35) 100%)`,
      }} />
      {cap ? (
        <div style={{
          position: 'absolute', left: 0, top: -hh, transformStyle: 'preserve-3d',
          transform: `rotateX(${90 - tilt}deg)`, opacity: rise > 0.35 ? 1 : 0,
        }}>{cap}</div>
      ) : null}
    </div>
  );
}

function ApCap() {
  return (
    <div style={{
      position: 'absolute', left: 0, top: 0, transform: 'translate(-50%,-100%)',
      display: 'flex', flexDirection: 'column', alignItems: 'center',
    }}>
      <div style={{ display: 'flex', gap: 5, marginBottom: 3 }}>
        {[0, 1, 2].map((i) => (
          <div key={i} style={{ width: 8, height: 26, borderRadius: 4, background: C.pink }} />
        ))}
      </div>
      <div style={{
        width: 74, height: 34, borderRadius: 10, background: C.pink,
        border: `4px solid ${C.pink3}`, boxShadow: '0 0 44px rgba(206,72,154,0.65)',
      }} />
    </div>
  );
}
function GwCap() {
  return (
    <div style={{
      position: 'absolute', left: 0, top: 0, transform: 'translate(-50%,-100%)',
      display: 'flex', flexDirection: 'column', alignItems: 'center',
    }}>
      <div style={{ width: 4, height: 16, background: C.ok }} />
      <div style={{
        width: 30, height: 30, background: C.ok, border: `3px solid #7FE0AE`,
        boxShadow: '0 0 26px rgba(43,174,106,0.6)',
      }} />
    </div>
  );
}
function CellCap({ tilt, yaw }) {
  const legs = [];
  for (let i = 0; i < 7; i++) {
    const t0 = i / 7, t1 = (i + 1) / 7;
    const y0 = 150 - 96 * t0, y1 = 150 - 96 * t1;
    const w0 = 34 - 22 * t0, w1 = 34 - 22 * t1;
    legs.push(<path key={i} d={`M ${70 - w0} ${y0} L ${70 + w1} ${y1} M ${70 + w0} ${y0} L ${70 - w1} ${y1}`}
      stroke={C.indigo3} strokeWidth="3.5" fill="none" opacity="0.85" />);
    legs.push(<line key={'h' + i} x1={70 - w1} y1={y1} x2={70 + w1} y2={y1} stroke={C.indigo3} strokeWidth="3" opacity="0.6" />);
  }
  return (
    <div style={{ transformStyle: 'preserve-3d', transform: `rotateZ(${-(yaw || 0)}deg) rotateX(${-(tilt || 0)}deg)` }}>
      <div style={{ position: 'absolute', left: 0, top: 0, transform: 'translate(-50%,-100%)' }}>
        <svg width="120" height="156" viewBox="0 0 120 156" style={{ display: 'block', overflow: 'visible' }}>
          <line x1="26" y1="152" x2="48" y2="54" stroke={C.indigo100} strokeWidth="5" />
          <line x1="94" y1="152" x2="72" y2="54" stroke={C.indigo100} strokeWidth="5" />
          {legs}
          <g>
            <rect x="36" y="8" width="11" height="42" rx="5" fill={C.pink} />
            <rect x="54" y="2" width="11" height="48" rx="5" fill={C.pink} />
            <rect x="72" y="8" width="11" height="42" rx="5" fill={C.pink} />
          </g>
        </svg>
      </div>
    </div>
  );
}
function NetCap({ tilt, yaw }) {
  return (
    <div style={{
      transformStyle: 'preserve-3d',
      transform: `rotateZ(${-(yaw || 0)}deg) rotateX(${-(tilt || 0)}deg)`,
    }}>
      <div style={{ position: 'absolute', left: 0, top: 0, transform: 'translate(-50%,-100%)' }}>
        <svg width="230" height="150" viewBox="0 0 230 150" style={{ display: 'block', overflow: 'visible' }}>
          <path d="M 44 118 a 34 34 0 0 1 6 -66 a 46 46 0 0 1 84 -14 a 38 38 0 0 1 52 24 a 30 30 0 0 1 -6 56 z"
            fill="rgba(27,26,82,0.94)" stroke={C.indigo3} strokeWidth="6" strokeLinejoin="round" />
          <text x="118" y="100" textAnchor="middle"
            style={{ font: `600 20px ${FD}`, letterSpacing: '0.16em', fill: C.indigo100 }}>CLOUD</text>
        </svg>
      </div>
    </div>
  );
}

function PlanView({ T, o, start, P }) {
  if (o <= 0.001) return null;
  const cam = kf([
    { t: start, cx: 3730, cy: 2680, z: 0.165, tilt: 0, yaw: 0 },
    { t: start + 3.0 * P, cx: 3730, cy: 2680, z: 0.172, tilt: 0, yaw: 0 },
    { t: start + 6.0 * P, cx: 3700, cy: 2660, z: 0.210, tilt: 0, yaw: 0 },
    { t: start + 9.0 * P, cx: 3660, cy: 2640, z: 0.320, tilt: 0, yaw: 0 },
    { t: start + 11.5 * P, cx: 3620, cy: 2960, z: 0.375, tilt: 46, yaw: -6 },
    { t: start + 14.0 * P, cx: 3560, cy: 3060, z: 0.395, tilt: 52, yaw: -10 },
    { t: start + 16.0 * P, cx: 3500, cy: 3120, z: 0.405, tilt: 54, yaw: -12 },
  ], T, MOTION.cam);
  const z = cam.z, tilt = cam.tilt, yaw = cam.yaw;
  const lit = (i) => cl((T - (start + 1.2 * P + i * 0.045 * P)) / (0.8 * P), 0, 1);
  const KK = KIND_LABEL.map((k) => k.k);
  const cyc = 2.6 * P;
  const kPhase = ((T - start) / cyc) % KK.length;
  const activeKind = KK[Math.floor(cl(kPhase, 0, KK.length - 0.0001))];
  const kOn = (kind) => (kind === activeKind ? 1 : 0.24);
  const rise = MOTION.enter(cl((T - (start + 8.6 * P)) / (2.6 * P), 0, 1));
  const gwOn = cl((T - (start + 5.6 * P)) / (1.1 * P), 0, 1);
  const apOn = cl((T - (start + 9.0 * P)) / (1.1 * P), 0, 1);
  const step = T < start + 5.6 * P ? 0 : T < start + 9.0 * P ? 1 : 2;
  const uplinkPhase = cl((T - (start + 11.6 * P)) / (1.0 * P), 0, 1);
  const sitePhase = 1 - uplinkPhase;

  return (
    <div style={{
      position: 'absolute', inset: 0, opacity: o, perspective: '2400px',
      perspectiveOrigin: '50% 40%', overflow: 'hidden',
    }}>
      <div style={{
        position: 'absolute', inset: 0, transformStyle: 'preserve-3d',
        transform: `rotateX(${tilt}deg) rotateZ(${yaw}deg)`, transformOrigin: '50% 54%',
      }}>
        <div style={{
          position: 'absolute', left: 0, top: 0, width: PW, height: PH, transformOrigin: '0 0',
          transformStyle: 'preserve-3d',
          transform: `translate(${960 - cam.cx * z}px, ${540 - cam.cy * z}px) scale(${z})`,
        }}>
          <svg width={PW} height={PH} style={{ position: 'absolute', left: 0, top: 0, overflow: 'visible' }}>
            <g opacity="0.5">
              {Array.from({ length: 22 }).map((_, i) => {
                const r2 = rng(900 + i);
                const w = 220 + r2() * 300, h = 150 + r2() * 220;
                const x = 260 + r2() * (PW - 700), y = 220 + r2() * (PH - 620);
                return <rect key={i} x={x} y={y} width={w} height={h} rx="10" fill="none"
                  stroke="#8C8AC0" strokeWidth="3" opacity="0.28" />;
              })}
              <path d={`M 0 1780 C 700 1660 1500 1980 2300 1820 S 3200 1600 ${PW} 1700`}
                fill="none" stroke="#2E7AD1" strokeWidth="26" opacity="0.25" />
            </g>

            {PAPS.map((ap, i) => (
              <g key={i}>
                <circle cx={ap.x} cy={ap.y} r={PRING} fill="rgba(206,72,154,0.06)" stroke={C.pink3}
                  strokeWidth="4" strokeDasharray="16 14" opacity="0.85" />
              </g>
            ))}
            {RINGS.map((r4, i) => (
              <circle key={i} cx={PHUB.x} cy={PHUB.y} r={r4.mi * MI} fill="none" stroke={C.ok}
                strokeWidth={r4.hero ? 7 : 4} strokeDasharray={r4.hero ? '46 26' : '22 22'}
                opacity={(r4.hero ? 0.8 : 0.4) * cl((T - (start + 0.6 * P + i * 0.5 * P)) / (0.9 * P), 0, 1)} />
            ))}

            <g opacity={0.6 * cl((T - (start + 0.6 * P)) / (0.9 * P), 0, 1)}>
              <line x1={SB.x} y1={SB.y} x2={SB.x + MI} y2={SB.y} stroke={C.indigo100} strokeWidth="8" />
              <line x1={SB.x} y1={SB.y - 34} x2={SB.x} y2={SB.y + 34} stroke={C.indigo100} strokeWidth="8" />
              <line x1={SB.x + MI} y1={SB.y - 34} x2={SB.x + MI} y2={SB.y + 34} stroke={C.indigo100} strokeWidth="8" />
            </g>
            <line x1={PAP.x} y1={PAP.y} x2={PAP.x + PRING} y2={PAP.y}
              stroke={C.pink3} strokeWidth="6" opacity={0.8 * gwOn} />



            {(function () {
              const op = cl((T - (start + 1.0 * P)) / (1.2 * P), 0, 1);
              const ly = PHUB.y + 0.55 * MI;
              return (
                <g opacity={0.7 * op} stroke={C.ok} fill="none">
                  <line x1={PHUB.x} y1={ly} x2={PHUB.x + 6 * MI * op} y2={ly} strokeWidth="6" />
                  {[0, 2, 4, 6].map((mi) => (
                    <line key={mi} x1={PHUB.x + mi * MI} y1={ly - 26} x2={PHUB.x + mi * MI} y2={ly + 26}
                      strokeWidth="6" opacity={op > mi / 6 ? 1 : 0} />
                  ))}
                </g>
              );
            })()}

            {HEROES.map((h, k) => {
              const g = PGWS[h.gw];
              const op = (0.4 + 0.6 * kOn(h.kind)) * cl((T - (start + 1.4 * P + k * 0.5 * P)) / (1.0 * P), 0, 1);
              if (op <= 0.02) return null;
              return <line key={'hl' + k} x1={h.x} y1={h.y} x2={g.x} y2={g.y}
                stroke={SENSOR_COL[h.kind]} strokeWidth="4" strokeDasharray="16 14" opacity={0.55 * op} />;
            })}

            {PSENSORS.map((s0, i) => {
              const L = lit(i);
              if (L <= 0.01) return null;
              const drift = s0.kind === 'animal' ? 26 : s0.kind === 'vehicle' ? 90 : 0;
              const s = drift
                ? { ...s0, x: s0.x + Math.sin(T * 0.35 + s0.ph * 6.28) * drift, y: s0.y + Math.cos(T * 0.28 + s0.ph * 6.28) * drift * 0.6 }
                : s0;
              const rp = (T * 0.7 + s0.ph) % 1;
              return (
                <g key={i}>
                  <circle cx={s.x} cy={s.y} r={17} fill={SENSOR_COL[s0.kind]} opacity={0.9 * L * (0.42 + 0.58 * kOn(s0.kind))} />
                  <circle cx={s.x} cy={s.y} r={17 + 34 * rp} fill="none" stroke={SENSOR_COL[s0.kind]}
                    strokeWidth="6" opacity={L * 0.32 * (1 - rp) * kOn(s0.kind)} />
                </g>
              );
            })}

            {PGWS.map((g, i) => (
              <rect key={i} x={g.x - 18} y={g.y - 18} width="36" height="36" fill={C.ok} opacity={0.9} />
            ))}

            {/* one large unit per sensor class, out along the distance line */}
            {HEROES.map((h, k) => {
              const op = (0.45 + 0.55 * kOn(h.kind)) * cl((T - (start + 1.0 * P + k * 0.5 * P)) / (1.0 * P), 0, 1);
              if (op <= 0.02) return null;
              const wob = h.kind === 'animal' ? Math.sin(T * 0.7 + k) * 26
                : h.kind === 'vehicle' ? ((T * 46 + k * 120) % 900) - 450 : 0;
              const col = SENSOR_COL[h.kind];
              return (
                <g key={'h' + k} transform={`translate(${h.x + wob} ${h.y}) scale(3.6)`} opacity={op}
                  stroke={col} fill="none" strokeWidth="4" strokeLinecap="round">
                  {h.kind === 'animal' ? (
                    <g><rect x="-26" y="-14" width="52" height="24" rx="11" />
                      <path d="M 26 -8 l 17 -8 l 6 13 l -13 7 z" />
                      <path d="M -18 10 l 0 13 M -6 10 l 0 13 M 8 10 l 0 13 M 20 10 l 0 13" /></g>
                  ) : h.kind === 'vehicle' ? (
                    <g><rect x="-32" y="-16" width="44" height="26" rx="5" />
                      <path d="M 12 10 l 0 -20 l 16 0 l 11 13 l 0 7 z" />
                      <circle cx="-18" cy="14" r="9" /><circle cx="24" cy="14" r="9" /></g>
                  ) : h.kind === 'person' ? (
                    <g><circle cx="0" cy="-22" r="9" />
                      <path d="M 0 -13 l 0 20" />
                      <path d="M 0 7 l -11 18 M 0 7 l 11 18" />
                      <path d="M 0 -6 l -13 9 M 0 -6 l 13 -4" /></g>
                  ) : h.kind === 'gate' ? (
                    <g><path d="M -26 18 l 0 -34 M 26 18 l 0 -34" />
                      <g transform={`rotate(${-22 - 18 * (0.5 + 0.5 * Math.sin(T * 0.45 + k))} -26 10)`}>
                        <rect x="-26" y="-10" width="48" height="26" rx="4" /></g></g>
                  ) : (
                    <g><rect x="-22" y="-26" width="44" height="52" rx="6" />
                      <path d="M -22 -2 l 44 0" opacity="0.85" />
                      <path d="M -22 10 l 44 0 M -22 20 l 44 0" opacity="0.4" /></g>
                  )}
                </g>
              );
            })}

            {PAPS.map((ap, i) => (
              <circle key={i} cx={ap.x} cy={ap.y} r={30 + 130 * ((T * 0.5 + i * 0.3) % 1)} fill="none"
                stroke={C.pink3} strokeWidth="4" opacity={0.4 * (1 - ((T * 0.5 + i * 0.3) % 1))} />
            ))}
          </svg>

          {/* standing 3D infrastructure */}
          {PAPS.map((ap, i) => (
            <Pole key={i} x={ap.x} y={ap.y} h={H.ap} rise={rise} color={C.indigo100} w={9} tilt={tilt} cap={<ApCap />} />
          ))}
          {PGWS.map((g, i) => (
            <Pole key={i} x={g.x} y={g.y} h={gwH(i)} rise={rise} color={C.indigo100} w={6} tilt={tilt} cap={<GwCap />} />
          ))}
          <Pole x={PCELL.x} y={PCELL.y} h={H.cell} rise={rise} color={C.indigo100} w={11} tilt={tilt} cap={<CellCap tilt={tilt} yaw={yaw} />} />
          <Pole x={PNET.x} y={PNET.y} h={H.net} rise={rise * apOn} color={C.indigo3} w={5} tilt={tilt} cap={<NetCap tilt={tilt} yaw={yaw} />} />

        </div>
      </div>

      {(function () {
        const pj = mkProj(cam);
        const gh = (i) => gwH(i) * rise, ah = H.ap * rise, ch = H.cell * rise, nh = H.net * rise * apOn;
        const seg = (A, B, col, w, dash, prog, flow, key, dots) => {
          const x2 = A.x + (B.x - A.x) * prog, y2 = A.y + (B.y - A.y) * prog;
          return (
            <g key={key}>
              <line x1={A.x} y1={A.y} x2={x2} y2={y2} stroke={col} strokeWidth={w}
                strokeDasharray={dash} opacity={(dash === '9 11' ? 0.4 : 0.72) * prog} strokeLinecap="round" />
              {prog > 0.96 ? Array.from({ length: dots || 2 }).map((_, k) => {
                const p = ((T * flow + k / (dots || 2)) % 1 + 1) % 1;
                return <circle key={k} cx={A.x + (B.x - A.x) * p} cy={A.y + (B.y - A.y) * p}
                  r={Math.min(w + 1, 4.5)} fill={col} opacity={0.3 + 0.55 * Math.sin(Math.PI * p)} />;
              }) : null}
            </g>
          );
        };
        return (
          <svg width="1920" height="1080" style={{
            position: 'absolute', left: 0, top: 0, pointerEvents: 'none', overflow: 'visible',
          }}>
            {PSENSORS.map((s0, i) => {
              const L = lit(i);
              if (L <= 0.02) return null;
              const g = PGWS[s0.gw];
              const drift = s0.kind === 'animal' ? 26 : s0.kind === 'vehicle' ? 90 : 0;
              const sx = s0.x + (drift ? Math.sin(T * 0.35 + s0.ph * 6.28) * drift : 0);
              const sy = s0.y + (drift ? Math.cos(T * 0.28 + s0.ph * 6.28) * drift * 0.6 : 0);
              return seg(pj(sx, sy, 0), pj(g.x, g.y, gh(g.i)), SENSOR_COL[s0.kind], 1.6, '9 11', L * kOn(s0.kind), 0.28, 's' + i, 1);
            })}
            {PGWS.map((g, i) => seg(pj(g.x, g.y, gh(i)), pj(PAPS[g.ap].x, PAPS[g.ap].y, ah), C.indigo3, 2.6, '10 9', gwOn, 0.5, 'g' + i, 2))}
            {PAPS.map((ap, i) => seg(pj(ap.x, ap.y, ah), pj(PCELL.x, PCELL.y, ch), C.pink, 3.4, '18 13', apOn, 0.32, 'a' + i, 3))}
            {seg(pj(PCELL.x, PCELL.y, ch), pj(PNET.x, PNET.y, nh), C.indigo3, 3.4, null, apOn, 0.3, 'net', 3)}
          </svg>
        );
      })()}

      {(function () {
        const pj = mkProj(cam);
        const placed = [];
        const tag = (p, eyebrow, title, sub, accent, op, dy, dx) => {
          let ty = p.y - 26 + (dy || 0);
          if (op <= 0.02 || p.x < 130 || p.x > 1790 || ty < 200) return null;
          const tw = 26 + Math.max((eyebrow || '').length * 8.4, (title || '').length * 12.6, (sub || '').length * 7.6);
          const th = sub ? 88 : 62;
          const tcx = p.x + (dx || 0);
          for (let guard = 0; guard < 10; guard++) {
            const cy = ty - th / 2;
            const hit = placed.find((qb) => Math.abs(qb.x - tcx) < (qb.w + tw) / 2 && Math.abs(qb.y - cy) < ((qb.h || 27) + th) / 2 + 10);
            if (!hit) break;
            ty = hit.y + (hit.h || 27) / 2 + th + 14;
          }
          if (ty > 1000) return null;
          placed.push({ x: tcx, y: ty - th / 2, w: tw, h: th });
          return (
            <div key={title} style={{
              position: 'absolute', left: p.x + (dx || 0), top: ty, opacity: op,
              transform: 'translate(-50%, -100%)', whiteSpace: 'nowrap', textAlign: 'center',
              pointerEvents: 'none', padding: '8px 16px', borderRadius: 12,
              background: 'rgba(20,19,46,0.9)', border: '1px solid rgba(140,138,192,0.35)',
            }}>
              <div style={{ font: `600 13px ${FD}`, letterSpacing: '0.18em', textTransform: 'uppercase', color: accent }}>{eyebrow}</div>
              <div style={{ font: `600 21px ${FD}`, color: C.white, marginTop: 2 }}>{title}</div>
              {sub ? <div style={{ font: `500 14px ${FB}`, color: 'rgba(232,231,244,0.82)', marginTop: 2 }}>{sub}</div> : null}
            </div>
          );
        };
        const pill = (p, text, color, op, dy) => {
          if (op <= 0.02) return null;
          let tx = cl(p.x, 150, 1700);
          let ty = p.y + (dy || 0);
          if (ty > 1000) return null;
          if (tx > 560 && ty < 330) ty = 330;
          if (ty < 215) return null;
          const w = 16 + text.length * 8.2;
          for (let guard = 0; guard < 10; guard++) {
            const hit = placed.find((q) => Math.abs(q.x - tx) < (q.w + w) / 2 && Math.abs(q.y - ty) < ((q.h || 27) + 27) / 2 + 8);
            if (!hit) break;
            ty = hit.y + (hit.h || 27) / 2 + 24;
          }
          if (ty > 1000) return null;
          placed.push({ x: tx, y: ty, w });
          return (
            <div key={text} style={{
              position: 'absolute', left: tx, top: ty, opacity: op,
              transform: 'translate(-50%, -50%)', whiteSpace: 'nowrap', pointerEvents: 'none',
              padding: '4px 11px', borderRadius: 999, background: 'rgba(20,19,46,0.9)',
              border: `1px solid ${color}`, font: `600 11px ${FD}`, letterSpacing: '0.14em',
              textTransform: 'uppercase', color: color,
            }}>{text}</div>
          );
        };
        const ringPt = (r4) => pj(
          PHUB.x + r4.mi * MI * Math.cos(r4.deg * Math.PI / 180),
          PHUB.y + r4.mi * MI * Math.sin(r4.deg * Math.PI / 180), 0);
        return (
          <React.Fragment>
            {RINGS.map((r4, i) => pill(ringPt(r4), r4.lb, C.ok,
              0.95 * cl((T - (start + 0.8 * P + i * 0.5 * P)) / (0.9 * P), 0, 1), 0))}

            {HEROES.map((h, k) => {
              const wob = h.kind === 'animal' ? Math.sin(T * 0.7 + k) * 26
                : h.kind === 'vehicle' ? ((T * 46 + k * 120) % 900) - 450 : 0;
              return pill(pj(h.x + wob, h.y, 0), `${h.en} · ${Math.round(h.mi)} mi`, SENSOR_COL[h.kind],
                0.95 * cl((T - (start + 1.6 * P + k * 0.5 * P)) / (1.0 * P), 0, 1), -100);
            })}
            {pill(pj(PHUB.x + 3 * MI, PHUB.y + 0.55 * MI, 0), 'distance from the gateway · 0 – 6 miles', C.ok,
              0.95 * cl((T - (start + 2.0 * P)) / (1.0 * P), 0, 1), 46)}
            {pill(pj(SB.x + MI / 2, SB.y, 0), '1 mile', C.indigo100,
              0.9 * cl((T - (start + 0.6 * P)) / (0.9 * P), 0, 1), -22)}
            {pill(pj(PAP.x + PRING, PAP.y, 0), 'WiFi / CBRS area', C.pink3, 0.95 * gwOn, -20)}
            {tag(pj(PCELL.x, PCELL.y, H.cell * rise), 'Baicells', '5G CBRS CELL', '1 – 1.5 miles out', C.pink3, uplinkPhase * (0.35 + 0.65 * apOn), 16)}
            {tag(pj(PNET.x, PNET.y, H.net * rise * apOn), 'Uplink', 'INTERNET · IoTLogIQ', null, C.indigo3, uplinkPhase * apOn, -96)}
            {tag(pj(PAP.x, PAP.y, H.ap * rise), 'MultiTech', 'WiFi / CBRS AP', 'one coverage area', C.pink3, sitePhase * (0.35 + 0.65 * gwOn), -14, -175)}
            {tag(pj(PGWS[0].x, PGWS[0].y, gwH(0) * rise), 'Kerlink', 'iFEMTOCELL GATEWAY', 'every sensor reports here', C.ok, sitePhase * gwOn, 54)}
          </React.Fragment>
        );
      })()}

      <div style={{
        position: 'absolute', left: 56, top: 300, width: 430,
        background: 'rgba(20,19,46,0.92)', border: '1px solid rgba(140,138,192,0.4)',
        borderRadius: 16, padding: 14, boxShadow: '0 20px 60px rgba(20,19,46,0.6)',
      }}>
        <div style={{
          font: `600 12px ${FD}`, letterSpacing: '0.18em', textTransform: 'uppercase',
          color: 'rgba(232,231,244,0.6)', marginBottom: 10,
        }}>Zoom · on the ground</div>
        <svg width="402" height="152" viewBox="0 0 402 152" style={{ display: 'block', overflow: 'visible' }}>
          <line x1="0" y1="140" x2="402" y2="140" stroke={C.indigo3} strokeWidth="3" opacity="0.5" />
          {[{ x: 104, col: C.pink }, { x: 286, col: C.warn }].map((p, i) => (
            <g key={i} stroke={C.white} fill="none" strokeWidth="4" strokeLinecap="round">
              <circle cx={p.x} cy="72" r="13" />
              <path d={`M ${p.x} 85 l 0 32 M ${p.x} 117 l -13 23 M ${p.x} 117 l 13 23 M ${p.x} 94 l -16 14 M ${p.x} 94 l 17 -12`} />
              <rect x={p.x + 15} y="76" width="13" height="21" rx="3" fill={p.col} stroke={p.col} />
              {[13, 22, 31].map((r, k) => (
                <path key={k} d={`M ${p.x - r * 0.7} ${58 - r * 0.4} Q ${p.x} ${58 - r * 1.1} ${p.x + r * 0.7} ${58 - r * 0.4}`}
                  stroke={p.col} strokeWidth={3 - k * 0.4}
                  opacity={0.45 + 0.55 * Math.abs(Math.sin(T * 1.6 + k * 0.6 + i))} />
              ))}
            </g>
          ))}
        </svg>
        <div style={{ display: 'flex', gap: 10, marginTop: 12 }}>
          {[{ t: 'WiFi access', s: 'via the on-site AP', c: C.pink },
            { t: 'SIM card', s: 'direct to the 5G cell', c: C.warn }].map((c, i) => (
            <div key={i} style={{
              flex: 1, borderRadius: 10, padding: '8px 10px',
              background: 'rgba(27,26,82,0.85)', borderLeft: `3px solid ${c.c}`,
            }}>
              <div style={{ font: `600 12px ${FD}`, letterSpacing: '0.14em', textTransform: 'uppercase', color: c.c }}>{c.t}</div>
              <div style={{ font: `500 12px ${FB}`, color: 'rgba(232,231,244,0.8)', marginTop: 2 }}>{c.s}</div>
            </div>
          ))}
        </div>
      </div>

      <div style={{
        position: 'absolute', left: 0, right: 0, bottom: 232, display: 'flex',
        flexWrap: 'wrap', justifyContent: 'center', gap: '14px 34px', padding: '0 120px',
      }}>
        {KIND_LABEL.map((k) => (
          <div key={k.k} style={{
            display: 'flex', alignItems: 'center', gap: 10, whiteSpace: 'nowrap',
            opacity: k.k === activeKind ? 1 : 0.34,
          }}>
            <div style={{ width: 14, height: 14, borderRadius: 999, background: SENSOR_COL[k.k], flex: '0 0 auto' }} />
            <div style={{ font: `600 14px ${FD}`, letterSpacing: '0.1em', textTransform: 'uppercase', color: C.white }}>{k.en}</div>
          </div>
        ))}
      </div>

      <div style={{
        position: 'absolute', left: 0, right: 0, top: 30, display: 'flex',
        justifyContent: 'center', alignItems: 'stretch', gap: 12, padding: '0 300px',
      }}>
        {[
          { t: 'Sensors', d: 'LoRaWAN endpoints', at: 0 },
          { t: 'LoRaWAN gateways', d: '1 – 10 miles', at: 0 },
          { t: 'WiFi access points', d: '200 – 300 meters', at: 1 },
          { t: '5G cell', d: '1 – 1.5 miles', at: 2 },
        ].map((c, i) => (
          <React.Fragment key={i}>
            {i ? <div style={{
              alignSelf: 'center', font: `600 18px ${FD}`,
              color: step >= c.at ? C.pink : 'rgba(140,138,192,0.5)',
            }}>→</div> : null}
            <div style={{
              padding: '10px 20px', borderRadius: 12, whiteSpace: 'nowrap',
              background: step >= c.at ? 'rgba(20,19,46,0.9)' : 'rgba(20,19,46,0.55)',
              border: `2px solid ${step >= c.at ? C.pink3 : 'rgba(140,138,192,0.35)'}`,
              opacity: step >= c.at ? 1 : 0.55,
            }}>
              <div style={{
                font: `600 15px ${FD}`, letterSpacing: '0.1em', textTransform: 'uppercase',
                color: step >= c.at ? C.white : 'rgba(232,231,244,0.7)',
              }}>{c.t}</div>
              <div style={{
                font: `500 13px ${FB}`, marginTop: 3,
                color: step >= c.at ? C.pink3 : 'rgba(140,138,192,0.8)',
              }}>{c.d}</div>
              <div style={{
                height: 3, marginTop: 8, borderRadius: 2,
                background: step === c.at ? C.pink : 'transparent',
                opacity: step === c.at ? 0.9 : 0,
              }} />
            </div>
          </React.Fragment>
        ))}
      </div>

    </div>
  );
}

/* ---------------- small parts ---------------- */

function Rings({ x, y, n, R, speed, phase, color, o }) {
  const T = window.__nvT;
  const out = [];
  for (let k = 0; k < n; k++) {
    const p = ((T * speed + phase + k / n) % 1 + 1) % 1;
    out.push(
      <circle key={k} cx={x} cy={y} r={8 + p * R} fill="none" stroke={color}
        strokeWidth={3} opacity={(1 - p) * 0.55 * o} />
    );
  }
  return <g>{out}</g>;
}

function Arcs({ x, y, color, o, flip }) {
  const d = flip ? -1 : 1;
  return (
    <g opacity={o} stroke={color} fill="none" strokeLinecap="round">
      {[16, 28, 40].map((r, i) => (
        <path key={i} strokeWidth={4 - i * 0.5}
          d={`M ${x - r * 0.72} ${y - d * r * 0.5} Q ${x} ${y - d * r * 1.25} ${x + r * 0.72} ${y - d * r * 0.5}`} />
      ))}
    </g>
  );
}

function Lbl({ x, y, z, eyebrow, title, sub, accent, o, align, clampX }) {
  if (o <= 0.001) return null;
  if (clampX) {
    const sx = clampX.sx + (x - clampX.cx) * z;
    if (sx < clampX.min || sx > clampX.max) return null;
  }
  return (
    <div style={{
      position: 'absolute', left: x, top: y, opacity: o,
      transform: `translate(-50%, -100%) scale(${1 / z})`,
      transformOrigin: '50% 100%', textAlign: align || 'center', whiteSpace: 'nowrap',
      textShadow: '0 2px 14px rgba(20,19,46,0.9)',
    }}>
      {eyebrow ? <div style={{
        font: `600 14px ${FD}`, letterSpacing: '0.16em', color: accent || C.pink3,
        textTransform: 'uppercase', marginBottom: 5,
      }}>{eyebrow}</div> : null}
      <div style={{ font: `600 20px ${FD}`, letterSpacing: '-0.01em', color: C.white, lineHeight: 1.1 }}>{title}</div>
      {sub ? <div style={{ font: `500 13px ${FB}`, color: 'rgba(232,231,244,0.82)', marginTop: 5 }}>{sub}</div> : null}
    </div>
  );
}

function Chip({ x, y, z, text, color, o, mono }) {
  if (o <= 0.001) return null;
  return (
    <div style={{
      position: 'absolute', left: x, top: y, opacity: o,
      transform: `translate(-50%, -50%) scale(${1 / z})`, transformOrigin: '50% 50%',
      padding: '8px 18px', borderRadius: 999, whiteSpace: 'nowrap',
      background: 'rgba(20,19,46,0.9)', border: `2px solid ${color}`,
      font: mono ? `500 20px ${FM}` : `600 20px ${FD}`,
      letterSpacing: mono ? '0.02em' : '0.14em', color: color,
      textTransform: mono ? 'none' : 'uppercase',
    }}>{text}</div>
  );
}

function Dim({ x1, x2, y, z, label, o, color }) {
  if (o <= 0.001) return null;
  const c = color || C.indigo3;
  return (
    <div style={{ position: 'absolute', left: x1, top: y, width: x2 - x1, opacity: o }}>
      <div style={{ position: 'absolute', left: 0, right: 0, top: 0, height: 2, background: c, opacity: 0.55 }} />
      <div style={{ position: 'absolute', left: 0, top: -9, width: 2, height: 20, background: c, opacity: 0.55 }} />
      <div style={{ position: 'absolute', right: 0, top: -9, width: 2, height: 20, background: c, opacity: 0.55 }} />
      <div style={{
        position: 'absolute', left: '50%', top: 0,
        transform: `translate(-50%, -50%) scale(${1 / z})`, transformOrigin: '50% 50%',
        background: C.bg, padding: '6px 18px', whiteSpace: 'nowrap',
        font: `600 17px ${FD}`, letterSpacing: '0.12em', textTransform: 'uppercase', color: c,
      }}>{label}</div>
    </div>
  );
}

function Cap(en, es) {
  return (
    <div>
      <div style={{ font: `500 21px ${FB}`, color: '#F5F3FC' }}>{en}</div>
      <div style={{ font: `500 15px ${FB}`, color: 'rgba(229,140,192,0.95)', marginTop: 6 }}>{es}</div>
    </div>
  );
}

const RES = (k, path) => (window.__resources && window.__resources[k]) || path;
const PHOTOS = {
  'dev-multitech': { src: () => RES('devMultitech', 'assets/dev-multitech.webp') },
  'dev-kerlink': { src: () => RES('devKerlink', 'assets/dev-kerlink.png') },
  'dev-baicells': { src: () => RES('devBaicells', 'assets/dev-baicells.webp'), small: true },
  'dev-sensor': { src: () => RES('devBob', 'assets/dev-bob.jpg') },
  'dev-tracker': { src: () => RES('devTracker', 'assets/dev-tracker.png') },
  'dev-asystom': { src: () => RES('devAsystom', 'assets/dev-asystom.png') },
  'dev-console': { src: () => RES('devConsole', 'assets/console-wateriq.png'), cover: true },
};

function DevPhoto({ id, label, sub, accent, o, placeholder }) {
  if (o <= 0.001) return null;
  return (
    <div style={{
      width: 330, opacity: o, background: 'rgba(27,26,82,0.94)', border: `2px solid ${accent}`,
      borderRadius: 16, padding: 12, boxShadow: '0 18px 50px rgba(20,19,46,0.6)',
      transform: `translateX(${(1 - o) * 26}px)`,
    }}>
      <div style={{
        height: 178, borderRadius: 10, overflow: 'hidden', background: 'rgba(255,255,255,0.94)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        {PHOTOS[id] ? (
          <img src={PHOTOS[id].src()} alt={label} style={
            PHOTOS[id].cover ? { width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top left', display: 'block' }
            : PHOTOS[id].small ? { width: 75, height: 75, imageRendering: 'auto', display: 'block' }
            : { maxWidth: '92%', maxHeight: '92%', objectFit: 'contain', display: 'block' }
          } />
        ) : (
          <image-slot id={id} shape="rounded" radius="10" fit="contain" placeholder={placeholder}></image-slot>
        )}
      </div>
      <div style={{
        font: `600 14px ${FD}`, letterSpacing: '0.14em', textTransform: 'uppercase',
        color: accent, marginTop: 12,
      }}>{label}</div>
      <div style={{ font: `500 13px ${FB}`, color: 'rgba(232,231,244,0.8)', marginTop: 4, marginBottom: 2 }}>{sub}</div>
    </div>
  );
}

function Roof({ w }) {
  return (
    <div style={{ width: w, marginTop: -1 }}>
      <div style={{
        height: 0, borderLeft: `${w * 0.14}px solid transparent`,
        borderRight: `${w * 0.14}px solid transparent`,
        borderBottom: `26px solid ${C.indigo3}`, opacity: 0.55,
      }} />
      <div style={{
        height: 54, background: 'rgba(61,59,131,0.55)', borderLeft: `1px solid ${C.indigo3}`,
        borderRight: `1px solid ${C.indigo3}`, display: 'grid',
        gridTemplateColumns: 'repeat(4, 1fr)', gap: 8, padding: '10px 14px',
      }}>
        {[0, 1, 2, 3].map((i) => (
          <div key={i} style={{ background: 'rgba(232,231,244,0.22)', borderRadius: 2 }} />
        ))}
      </div>
    </div>
  );
}

function PairPanel({ T, o, showPhotos }) {
  if (o <= 0.001) return null;
  const dots = 11;
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, top: '50%', opacity: o,
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14,
      transform: `translateY(calc(-50% + ${(1 - o) * -18}px))`,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{
          font: `600 15px ${FD}`, letterSpacing: '0.16em', textTransform: 'uppercase', color: C.pink3,
          whiteSpace: 'nowrap',
        }}>5G cell bandwidth / speed</div>
        <div style={{
          font: `500 15px ${FM}`, color: C.white, background: 'rgba(20,19,46,0.88)',
          border: `2px solid ${C.pink}`, borderRadius: 999, padding: '6px 18px', whiteSpace: 'nowrap',
        }}>300 Mbps ↓ · 60 Mbps ↑ · 18 ms</div>
      </div>

      <div style={{ position: 'relative', display: 'flex', alignItems: 'flex-start', gap: 200 }}>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
          {showPhotos ? (
            <DevPhoto id="dev-baicells" accent={C.pink3} label="Baicells" sub="5G CBRS small cell · rooftop"
              o={1} placeholder="Baicells radio photo" />
          ) : null}
          <Roof w={330} />
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
          {showPhotos ? (
            <DevPhoto id="dev-multitech" accent={C.pink3} label="MultiTech" sub="WiFi / CBRS access point · on site"
              o={1} placeholder="MultiTech AP photo" />
          ) : null}
          <Roof w={330} />
        </div>

        <svg width="200" height="300" style={{ position: 'absolute', left: 330, top: 60, overflow: 'visible' }}>
          <line x1="6" y1="60" x2="194" y2="60" stroke={C.pink} strokeWidth="4" strokeDasharray="12 10" opacity="0.85" />
          <line x1="6" y1="118" x2="194" y2="118" stroke={C.warn} strokeWidth="4" strokeDasharray="12 10" opacity="0.7" />
          {[0, 1, 2].map((k) => {
            const p = ((T * 0.5 + k / 3) % 1 + 1) % 1;
            return <circle key={'a' + k} cx={6 + 188 * p} cy="60" r="8" fill={C.pink}
              opacity={0.35 + 0.65 * Math.sin(Math.PI * p)} />;
          })}
          {[0, 1, 2].map((k) => {
            const p = ((T * 0.5 + k / 3) % 1 + 1) % 1;
            return <circle key={'b' + k} cx={194 - 188 * p} cy="118" r="8" fill={C.warn}
              opacity={0.35 + 0.65 * Math.sin(Math.PI * p)} />;
          })}
          <text x="100" y="42" fill={C.pink} fontFamily="Saira, sans-serif" fontSize="13"
            fontWeight="600" textAnchor="middle" letterSpacing="2">DOWNLINK</text>
          <text x="100" y="152" fill={C.warn} fontFamily="Saira, sans-serif" fontSize="13"
            fontWeight="600" textAnchor="middle" letterSpacing="2">UPLINK</text>
        </svg>
      </div>
    </div>
  );
}

function TrioPanel({ T, o, showPhotos }) {
  if (o <= 0.001) return null;
  const link = (leftCol, rightCol, label) => (
    <svg width="210" height="300" style={{ position: 'absolute', top: 0, overflow: 'hidden' }}>
      <line x1="26" y1="104" x2="184" y2="104" stroke={leftCol} strokeWidth="4" strokeDasharray="12 10" opacity="0.85" />
      <line x1="26" y1="142" x2="184" y2="142" stroke={rightCol} strokeWidth="4" strokeDasharray="12 10" opacity="0.7" />
      {[0, 1, 2].map((k) => {
        const p = ((T * 0.5 + k / 3) % 1 + 1) % 1;
        return <circle key={'d' + k} cx={26 + 158 * p} cy="104" r="6" fill={leftCol}
          opacity={0.35 + 0.65 * Math.sin(Math.PI * p)} />;
      })}
      {[0, 1, 2].map((k) => {
        const p = ((T * 0.5 + k / 3) % 1 + 1) % 1;
        return <circle key={'u' + k} cx={184 - 158 * p} cy="142" r="6" fill={rightCol}
          opacity={0.35 + 0.65 * Math.sin(Math.PI * p)} />;
      })}
    </svg>
  );
  const col = (id, label, sub, accent, ph) => (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
      {showPhotos ? <DevPhoto id={id} accent={accent} label={label} sub={sub} o={1} placeholder={ph} /> : null}
      <Roof w={330} />
    </div>
  );
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, top: '50%', opacity: o,
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14,
      transform: `translateY(calc(-50% + ${(1 - o) * -18}px))`,
    }}>
      <div style={{
        font: `600 15px ${FD}`, letterSpacing: '0.16em', textTransform: 'uppercase', color: C.pink3,
        whiteSpace: 'nowrap',
      }}>One network · three radios talking to each other</div>

      <div style={{ position: 'relative', display: 'flex', alignItems: 'flex-start', gap: 210 }}>
        {col('dev-baicells', 'Baicells', '5G CBRS small cell · rooftop', C.pink3, 'Baicells radio photo')}
        {col('dev-multitech', 'MultiTech', 'WiFi / CBRS access point', C.pink3, 'MultiTech AP photo')}
        {col('dev-kerlink', 'Kerlink', 'iFemtoCell LoRaWAN gateway', C.ok, 'Kerlink iFemtoCell photo')}
        <div style={{ position: 'absolute', left: 330, top: 0 }}>{link(C.pink, C.warn, 'CBRS · 3.5 GHz')}</div>
        <div style={{ position: 'absolute', left: 870, top: 0 }}>{link(C.indigo3, C.ok, 'Ethernet / LoRaWAN')}</div>
      </div>
    </div>
  );
}

function Narrator({ T, beats, o }) {
  if (o <= 0.001) return null;
  let idx = 0;
  beats.forEach((b, i) => { if (T >= b.at) idx = i; });
  const b = beats[idx];
  const inU = MOTION.enter(cl((T - b.at) / 0.6, 0, 1));
  return (
    <div style={{
      position: 'absolute', right: 88, top: 150, width: 420, opacity: o,
      background: 'rgba(20,19,46,0.86)', border: '1px solid rgba(140,138,192,0.45)',
      borderRadius: 16, padding: '20px 24px 19px', backdropFilter: 'blur(3px)',
    }}>
      <div style={{ display: 'flex', gap: 6, marginBottom: 14 }}>
        {beats.map((_, i) => (
          <div key={i} style={{
            height: 4, flex: 1, borderRadius: 2,
            background: i <= idx ? C.pink : 'rgba(140,138,192,0.3)',
          }} />
        ))}
      </div>
      <div style={{
        font: `600 11px ${FD}`, letterSpacing: '0.18em', textTransform: 'uppercase', color: C.pink3,
      }}>{String(idx + 1).padStart(2, '0')} · {b.tag}</div>
      <div style={{
        font: `600 18px ${FD}`, color: C.white, marginTop: 9, lineHeight: 1.2,
        transform: `translateY(${(1 - inU) * 10}px)`, opacity: 0.25 + 0.75 * inU,
      }}>{b.en}</div>
      <div style={{ font: `500 13px ${FB}`, color: 'rgba(229,140,192,0.95)', marginTop: 8, lineHeight: 1.32 }}>{b.es}</div>
    </div>
  );
}

const TICKER = [
  { c: '#CE489A', en: 'Cattle tracking', es: 'Rastreo de ganado' },
  { c: '#F2A93B', en: 'Pump status', es: 'Estado de bombas' },
  { c: '#2E7AD1', en: 'Pipe flow', es: 'Flujo en tubería' },
  { c: '#E8E7F4', en: 'Valve open / close', es: 'Válvulas abrir / cerrar' },
  { c: '#2BAE6A', en: 'Fertilizer tank level', es: 'Nivel de tanque de fertilizante' },
  { c: '#F2A93B', en: 'Corn worm traps', es: 'Trampas de gusano' },
  { c: '#2E7AD1', en: 'Temperature + humidity', es: 'Temperatura y humedad' },
  { c: '#2BAE6A', en: 'Soil conditions', es: 'Condiciones del suelo' },
  { c: '#E8E7F4', en: 'Door open / close', es: 'Puertas abrir / cerrar' },
  { c: '#CE489A', en: 'Animal counting', es: 'Conteo de animales' },
  { c: '#5B58B5', en: 'Tractor tracking', es: 'Rastreo de tractores' },
];

function Ticker({ T, o }) {
  if (o <= 0.001) return null;
  const items = TICKER.concat(TICKER);
  const rowRef = React.useRef(null);
  const [span, setSpan] = React.useState(3674);
  React.useLayoutEffect(() => {
    const el = rowRef.current;
    if (!el) return;
    const half = el.scrollWidth / 2;
    if (half > 100 && Math.abs(half - span) > 1) setSpan(half);
  });
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, bottom: 0, height: 62, opacity: o,
      overflow: 'hidden', background: 'rgba(20,19,46,0.92)',
      borderTop: '1px solid rgba(140,138,192,0.35)', pointerEvents: 'none',
    }}>
      <div ref={rowRef} style={{
        position: 'absolute', left: 0, top: 0, height: '100%', display: 'flex',
        alignItems: 'center', gap: 34, whiteSpace: 'nowrap', willChange: 'transform',
        transform: `translateX(${-((T * 88) % span)}px)`,
      }}>
        {items.map((it, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 11, flex: '0 0 auto' }}>
            <div style={{ width: 8, height: 8, borderRadius: 999, background: it.c, flex: '0 0 auto' }} />
            <div style={{ font: `600 13px ${FD}`, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.white }}>{it.en}</div>
            <div style={{ font: `500 12px ${FB}`, color: 'rgba(229,140,192,0.9)' }}>{it.es}</div>
            <div style={{ width: 1, height: 20, background: 'rgba(140,138,192,0.3)', marginLeft: 22 }} />
          </div>
        ))}
      </div>
    </div>
  );
}

/* ---------------- main ---------------- */

function NetworkVideo(props) {
  const showCaptions = props.showCaptions !== false && props.showCaptions !== 'false';
  const showLabels = props.showLabels !== false && props.showLabels !== 'false';
  const showPhotos = props.showPhotos !== false && props.showPhotos !== 'false';
  const P = +props.pace || 1;
  window.__nvP = P;
  const { T, CUES } = window.useComposition();
  window.__nvT = T;

  const HS = CUES.Handset, BH = CUES.Backhaul, SN = CUES.Sensors;
  const GW = CUES.Gateways, WD = CUES.Wide, AER = CUES.Aerial, CLS = CUES.Close, END = CLS + 2.5 * P;

  const cam = kf([
    { t: 0, cx: 2740, cy: 700, z: 0.325 },
    { t: HS - 1.0 * P, cx: 2520, cy: 712, z: 0.42 },
    { t: HS + 1.2 * P, cx: 2506, cy: 845, z: 1.85 },
    { t: BH, cx: 2486, cy: 838, z: 1.95 },
    { t: BH + 2.2 * P, cx: 1640, cy: 790, z: 0.60 },
    { t: SN - 0.6 * P, cx: 640, cy: 520, z: 0.86 },
    { t: SN + 2.4 * P, cx: FGW.x, cy: 690, z: 0.50 },
    { t: GW, cx: 3760, cy: 700, z: 0.455 },
    { t: GW + 1.6 * P, cx: 3240, cy: 690, z: 0.400 },
    { t: WD, cx: 3000, cy: 690, z: 0.365 },
    { t: WD + 2.0 * P, cx: 2740, cy: 700, z: 0.325 },
    { t: AER, cx: 2740, cy: 700, z: 0.34 },
    { t: CLS, cx: 2742, cy: 700, z: 0.328 },
    { t: END, cx: 2740, cy: 700, z: 0.325 },
  ], T, MOTION.cam);
  const z = cam.z;

  {
    const s0 = SN + 0.6 * P, s1 = GW - 1.0 * P;
    if (T > s0 - 0.4 * P && T < s1 + 0.6 * P) {
      const n = SENSORS.length;
      const u = cl((T - s0) / ((s1 - s0) || 1), 0, 0.9999) * n;
      const k = Math.floor(u);
      const a = SENSORS[SPOT_ORDER[k]].x;
      const b = SENSORS[SPOT_ORDER[Math.min(k + 1, n - 1)]].x;
      const target = a + (b - a) * MOTION.cam(cl((u - k - 0.62) / 0.38, 0, 1)) - 210;
      const blend = Math.min(cl((T - (s0 - 0.4 * P)) / (0.9 * P), 0, 1), cl((s1 + 0.6 * P - T) / (0.7 * P), 0, 1));
      cam.cx = cam.cx + (target - cam.cx) * blend;
    }
  }

  const aerial = focus(T, AER + 0.2 * P, AER + 16.4 * P, 0.35 * P);
  const wide = focus(T, WD, AER + 0.4 * P, 0.6);
  const fUEonly = focus(T, HS, BH + 1.4 * P);
  const fUE = Math.max(fUEonly, wide);
  const fBHonly = focus(T, BH + 0.3 * P, SN - 0.2 * P);
  const fNETonly = focus(T, BH + 2.0 * P, SN - 0.2 * P);
  const fBH = Math.max(fBHonly, wide);
  const fNET = Math.max(fNETonly, wide);
  const q = 1 - wide;
  const fLORAonly = focus(T, SN, GW - 0.5 * P, 0.4 * P);
  const spot = SPOT_ORDER[Math.floor(cl((T - SN - 0.6 * P) / ((GW - 1.0 * P - SN - 0.6 * P) || 1), 0, 0.9999) * SENSORS.length)];
  const fLORA = Math.max(fLORAonly, wide);
  const fGWonly = focus(T, GW, WD + 0.8 * P);
  const fGW = Math.max(fGWonly, wide);

  const closeO = cl((T - (CLS + 0.1 * P)) / (0.6 * P), 0, 1); // holds to the last frame
  const pairDim = focus(T, BH + 0.9 * P, SN - 0.8 * P, 0.7 * P);
  const trioO = focus(T, WD + 2.2 * P, WD + 5.4 * P, 0.7 * P) * cl(1 - aerial * 3, 0, 1);
  const twX = 960 + (TW.x - cam.cx) * z;
  const twVis = cl((twX - 40) / 220, 0, 1) * cl((1880 - twX) / 220, 0, 1);
  const simF = fUE * twVis;
  const sideO = cl(1 - aerial * 2.2, 0, 1) * (1 - closeO) * (1 - 0.82 * Math.max(pairDim, trioO));
  const pairO = focus(T, BH + 0.6 * P, SN - 0.5 * P, 0.6 * P);

  const links = [
    { a: { x: 2586, y: 900 }, b: { x: AP.x, y: AP.top - 30 }, sag: 70, col: C.pink, dash: '16 12', w: 6, sp: 0.34, n: 3, f: fUE },
    { a: { x: PER2.x - 26, y: 884 }, b: { x: TW.x, y: TW.top + 24 }, sag: 150, col: C.warn, dash: null, w: 7, sp: 0.22, n: 4, f: simF },
    { a: { x: AP.x, y: AP.top - 40 }, b: { x: TW.x + 12, y: TW.top + 24 }, sag: 240, col: C.pink, dash: '20 14', w: 7, sp: 0.12, n: 5, f: fBH },
    { a: { x: TW.x - 8, y: TW.top + 14 }, b: { x: NET.x + 108, y: NET.y + 54 }, sag: 70, col: C.indigo3, dash: null, w: 6, sp: 0.28, n: 3, f: fNET },
  ];
  SENSORS.forEach((s, i) => {
    links.push({
      a: { x: s.x, y: SENSOR_Y[s.kind] },
      b: { x: FGW.x, y: FGW.top + 26 },
      sag: 60 + Math.abs(FGW.x - s.x) * 0.06, col: C.ok, dash: '12 16', w: 5, sp: 0.10 + i * 0.012, n: 3,
      f: fLORA * (i === spot ? 1 : 0.22),
    });
  });
  links.push({
    a: { x: FGW.x, y: FGW.top + 10 }, b: { x: AP.x, y: AP.top - 26 },
    sag: 210, col: C.indigo3, dash: '10 12', w: 5, sp: 0.34, n: 3, f: Math.max(fLORA * 0.5, fGW),
  });
  GWS.forEach((g) => {
    links.push({
      a: { x: g.x, y: gwTop(g) - 34 * (1 - g.d * 0.24) }, b: { x: AP.x, y: AP.top - 26 },
      sag: 44, col: C.indigo3, dash: '8 10', w: 4, sp: 0.5, n: 2, f: fGW,
    });
  });

  const worldTf = {
    position: 'absolute', left: 0, top: 0, width: WW, height: WH, opacity: sideO,
    visibility: sideO < 0.02 ? 'hidden' : 'visible',
    transformOrigin: '0 0',
    transform: `translate(${960 - cam.cx * z}px, ${540 - cam.cy * z}px) scale(${z})`,
  };

  const titleO = focus(T, 0.4, HS - 0.7 * P, 0.5);
  const clearForPanels = Math.max(titleO, closeO, pairO, trioO) > 0.03 ? 0 : 1;

  return (
    <div data-screen-label={`t=${T.toFixed(0)}s`} style={{
      position: 'absolute', inset: 0, overflow: 'hidden',
      background: 'radial-gradient(120% 90% at 12% 0%, #2A2870 0%, #1B1A52 48%, #14132E 100%)',
      fontFamily: FB,
    }}>
      {/* ---------- world ---------- */}
      <div style={worldTf}>
        {/* real-photo backdrop the user fills in */}
        {showPhotos ? (
          <div style={{
            position: 'absolute', left: -2200, top: 120, width: WW + 4400, height: 880,
            opacity: 0.16, overflow: 'hidden',
          }} />
        ) : null}
        <div style={{
          position: 'absolute', left: -2200, top: 120, width: WW + 4400, height: 880, pointerEvents: 'none',
          background: 'linear-gradient(180deg, rgba(20,19,46,0.55) 0%, rgba(20,19,46,0.25) 45%, rgba(20,19,46,0.85) 100%)',
        }} />

        <svg width={WW} height={WH} style={{ position: 'absolute', left: 0, top: 0, overflow: 'visible' }}>
          <defs>
            <pattern id="dots" width="60" height="60" patternUnits="userSpaceOnUse">
              <circle cx="2" cy="2" r="2" fill="#8C8AC0" opacity="0.16" />
            </pattern>
          </defs>
          <rect x={-2200} y="0" width={WW + 4400} height={G} fill="url(#dots)" />
          <rect x={-2200} y={G} width={WW + 4400} height={WH - G} fill="rgba(61,59,131,0.42)" />
          <line x1={-2200} y1={G} x2={WW + 2200} y2={G} stroke={C.indigo3} strokeWidth="4" opacity="0.7" />

          {links.map((L, i) => {
            const o = (L.col === C.warn ? 0 : 0.14) + 0.86 * L.f;
            return (
              <g key={i}>
                <path d={qpath(L.a, L.b, L.sag)} fill="none" stroke={L.col} strokeWidth={L.w}
                  strokeDasharray={L.dash || undefined} strokeLinecap="round" opacity={o * 0.55} />
                {Array.from({ length: L.n }).map((_, j) => {
                  const p = ((T * L.sp + j / L.n + i * 0.11) % 1 + 1) % 1;
                  const pt = qpt(L.a, L.b, L.sag, p);
                  return <circle key={j} cx={pt.x} cy={pt.y} r={9} fill={L.col}
                    opacity={L.f * (0.35 + 0.65 * Math.sin(Math.PI * p))} />;
                })}
              </g>
            );
          })}

          {/* internet / cloud */}
          <g>
            <rect x={NET.x - 160} y={NET.y - 70} width={320} height={132} rx={20}
              fill="rgba(27,26,82,0.9)" stroke={C.indigo3} strokeWidth={4} />
            <path d={`M ${NET.x - 92} ${NET.y} a 34 34 0 0 1 62 -20 a 40 40 0 0 1 74 14 a 28 28 0 0 1 -4 56 h -120 a 26 26 0 0 1 -12 -50 z`}
              fill="none" stroke={C.indigo100} strokeWidth={5} opacity={0.85} />
          </g>

          {/* Baicells 5G CBRS tower */}
          <g>
            <line x1={TW.x - 44} y1={G} x2={TW.x - 8} y2={TW.top + 20} stroke={C.indigo100} strokeWidth={7} />
            <line x1={TW.x + 44} y1={G} x2={TW.x + 8} y2={TW.top + 20} stroke={C.indigo100} strokeWidth={7} />
            {Array.from({ length: 7 }).map((_, i) => {
              const t0 = i / 7, t1 = (i + 1) / 7;
              const yA = G + (TW.top + 20 - G) * t0, yB = G + (TW.top + 20 - G) * t1;
              const xA = 44 * (1 - t0) + 8 * t0, xB = 44 * (1 - t1) + 8 * t1;
              return <g key={i} stroke={C.indigo3} strokeWidth={4} opacity={0.8}>
                <line x1={TW.x - xA} y1={yA} x2={TW.x + xB} y2={yB} />
                <line x1={TW.x + xA} y1={yA} x2={TW.x - xB} y2={yB} />
              </g>;
            })}
            <rect x={TW.x - 46} y={TW.top - 4} width={22} height={62} rx={8} fill={C.pink} opacity={0.9} />
            <rect x={TW.x - 11} y={TW.top - 16} width={22} height={62} rx={8} fill={C.pink} opacity={0.9} />
            <rect x={TW.x + 24} y={TW.top - 4} width={22} height={62} rx={8} fill={C.pink} opacity={0.9} />
            <Rings x={TW.x} y={TW.top + 20} n={3} R={210} speed={0.32} phase={0.1} color={C.pink} o={0.5 + 0.5 * fBH} />
            <circle cx={TW.x} cy={TW.top + 24} r={20} fill="none" stroke={C.warn} strokeWidth={5} opacity={0.9 * simF} />
            <circle cx={TW.x} cy={TW.top + 24} r={8} fill={C.warn} opacity={0.9 * simF} />
          </g>

          {/* on-site WiFi / CBRS coverage bubble */}
          <g opacity={0.35 + 0.65 * Math.max(fUEonly, fGWonly)}>
            <ellipse cx={2500} cy={G - 40} rx={690} ry={352} fill="rgba(206,72,154,0.07)"
              stroke={C.pink3} strokeWidth={4} strokeDasharray="20 16" opacity={0.75} />
            <ellipse cx={2500} cy={G - 40} rx={640} ry={310} fill="rgba(206,72,154,0.05)" stroke="none" />
          </g>

          {/* field LoRaWAN gateway — centre of the sensor run */}
          <g opacity={0.3 + 0.7 * Math.max(fLORA, fGW)}>
            <line x1={FGW.x} y1={G} x2={FGW.x} y2={FGW.top + 42} stroke={C.indigo100} strokeWidth={9} />
            {Array.from({ length: 6 }).map((_, i) => {
              const t0 = i / 6, t1 = (i + 1) / 6;
              const yA = G + (FGW.top + 42 - G) * t0, yB = G + (FGW.top + 42 - G) * t1;
              return <g key={i} stroke={C.indigo3} strokeWidth={4} opacity={0.75}>
                <line x1={FGW.x - 26 * (1 - t0)} y1={yA} x2={FGW.x + 26 * (1 - t1)} y2={yB} />
                <line x1={FGW.x + 26 * (1 - t0)} y1={yA} x2={FGW.x - 26 * (1 - t1)} y2={yB} />
              </g>;
            })}
            <rect x={FGW.x - 22} y={FGW.top + 4} width={44} height={48} rx={10} fill={C.panel} stroke={C.ok} strokeWidth={5} />
            <line x1={FGW.x} y1={FGW.top + 4} x2={FGW.x} y2={FGW.top - 34} stroke={C.ok} strokeWidth={5} />
            <circle cx={FGW.x} cy={FGW.top + 26} r={6} fill={C.ok} opacity={0.45 + 0.55 * Math.abs(Math.sin(T * 2))} />
            <Rings x={FGW.x} y={FGW.top - 34} n={3} R={180} speed={0.4} phase={0.2} color={C.ok} o={0.35 + 0.65 * fLORA} />
          </g>

          {/* MultiTech access point */}
          <g>
            <line x1={AP.x} y1={G} x2={AP.x} y2={AP.top} stroke={C.indigo100} strokeWidth={8} />
            <rect x={AP.x - 26} y={AP.top - 34} width={52} height={44} rx={10} fill={C.panel} stroke={C.pink3} strokeWidth={4} />
            <line x1={AP.x} y1={AP.top - 34} x2={AP.x} y2={AP.top - 60} stroke={C.pink3} strokeWidth={5} />
            <Arcs x={AP.x} y={AP.top - 62} color={C.pink} o={0.55 + 0.45 * Math.max(fUE, fGW)} />
            <Rings x={AP.x} y={AP.top - 12} n={3} R={150} speed={0.45} phase={0.4} color={C.pink3} o={0.4 + 0.6 * fUE} />
          </g>

          {/* Kerlink iFemtoCell gateways */}
          {GWS.map((g, i) => {
            const top = gwTop(g), k = 1 - g.d * 0.24, op = 1 - g.d * 0.28;
            return (
              <g key={i} opacity={op}>
                <line x1={g.x} y1={gwBase(g)} x2={g.x} y2={top} stroke={C.indigo100} strokeWidth={6 * k} opacity={0.9} />
                <rect x={g.x - 17 * k} y={top - 30 * k} width={34 * k} height={38 * k} rx={8} fill={C.panel} stroke={C.ok} strokeWidth={4 * k} />
                <line x1={g.x} y1={top - 30 * k} x2={g.x} y2={top - 52 * k} stroke={C.ok} strokeWidth={4 * k} />
                <circle cx={g.x} cy={top - 12 * k} r={5 * k} fill={C.ok} opacity={0.4 + 0.6 * Math.abs(Math.sin(T * 2 + i))} />
                <Rings x={g.x} y={top - 52 * k} n={2} R={110 * k} speed={0.4} phase={i * 0.17} color={C.ok} o={(0.3 + 0.7 * fLORA) * op} />
              </g>
            );
          })}

          {/* worker + handset */}
          <g>
            <circle cx={PER.x} cy={898} r={17} fill="none" stroke={C.white} strokeWidth={5} />
            <path d={`M ${PER.x} 915 L ${PER.x} 958`} stroke={C.white} strokeWidth={6} strokeLinecap="round" />
            <path d={`M ${PER.x} 958 L ${PER.x - 16} 1000 M ${PER.x} 958 L ${PER.x + 16} 1000`} stroke={C.white} strokeWidth={6} strokeLinecap="round" fill="none" />
            <path d={`M ${PER.x} 926 L ${PER.x - 20} 946`} stroke={C.white} strokeWidth={5} strokeLinecap="round" />
            <path d={`M ${PER.x} 926 L ${PER.x + 22} 908`} stroke={C.white} strokeWidth={5} strokeLinecap="round" />
            <rect x={PER.x + 18} y={888} width={16} height={26} rx={4} fill={C.pink} stroke={C.white} strokeWidth={2} />
            <Arcs x={PER.x + 26} y={884} color={C.pink} o={0.5 + 0.5 * fUE} />
          </g>

          {/* second worker — SIM card straight to the 5G cell */}
          <g>
            <circle cx={PER2.x} cy={898} r={17} fill="none" stroke={C.white} strokeWidth={5} />
            <path d={`M ${PER2.x} 915 L ${PER2.x} 958`} stroke={C.white} strokeWidth={6} strokeLinecap="round" />
            <path d={`M ${PER2.x} 958 L ${PER2.x - 17} 1000 M ${PER2.x} 958 L ${PER2.x + 15} 1000`} stroke={C.white} strokeWidth={6} strokeLinecap="round" fill="none" />
            <path d={`M ${PER2.x} 926 L ${PER2.x + 20} 946`} stroke={C.white} strokeWidth={5} strokeLinecap="round" />
            <path d={`M ${PER2.x} 926 L ${PER2.x - 22} 908`} stroke={C.white} strokeWidth={5} strokeLinecap="round" />
            <rect x={PER2.x - 34} y={888} width={16} height={26} rx={4} fill={C.warn} stroke={C.white} strokeWidth={2} />
            <Arcs x={PER2.x - 26} y={884} color={C.warn} o={0.5 + 0.5 * fUE} flip={false} />
          </g>

          {/* sensors */}
          {SENSORS.map((s, i) => {
            const glow = 0.3 + 0.7 * fLORA;
            if (s.kind === 'soil') {
              return (
                <g key={i}>
                  <path d={`M ${s.x} 986 L ${s.x} 1086 M ${s.x - 14} 1030 L ${s.x} 1046 M ${s.x + 14} 1030 L ${s.x} 1046`}
                    stroke={C.ok} strokeWidth={5} fill="none" strokeLinecap="round" opacity={0.9} />
                  <rect x={s.x - 20} y={946} width={40} height={40} rx={9} fill={C.panel} stroke={C.ok} strokeWidth={4} />
                  <Rings x={s.x} y={956} n={2} R={90} speed={0.5} phase={i * 0.3} color={C.ok} o={glow} />
                </g>
              );
            }
            if (s.kind === 'energy') {
              return (
                <g key={i}>
                  <rect x={s.x - 34} y={902} width={68} height={98} rx={10} fill={C.panel} stroke={C.warn} strokeWidth={4} />
                  <path d={`M ${s.x + 6} 928 L ${s.x - 10} 958 L ${s.x + 2} 958 L ${s.x - 6} 982`}
                    stroke={C.warn} strokeWidth={5} fill="none" strokeLinecap="round" />
                  <Rings x={s.x} y={906} n={2} R={90} speed={0.5} phase={i * 0.3} color={C.ok} o={glow} />
                </g>
              );
            }
            if (s.kind === 'animal') {
              const sway = Math.sin(T * 0.9 + i) * 8;
              return (
                <g key={i} transform={`translate(${sway} 0)`}>
                  <rect x={s.x - 46} y={938} width={92} height={40} rx={18} fill="none" stroke={C.indigo100} strokeWidth={5} />
                  <path d={`M ${s.x + 44} 946 l 30 -14 l 10 22 l -22 12 z`} fill="none" stroke={C.indigo100} strokeWidth={5} />
                  <path d={`M ${s.x - 32} 978 l 0 22 M ${s.x - 10} 978 l 0 22 M ${s.x + 14} 978 l 0 22 M ${s.x + 36} 978 l 0 22`}
                    stroke={C.indigo100} strokeWidth={5} strokeLinecap="round" />
                  <rect x={s.x + 58} y={916} width={22} height={22} rx={6} fill={C.panel} stroke="#CE489A" strokeWidth={4} />
                  <Rings x={s.x + 69} y={922} n={2} R={80} speed={0.5} phase={i * 0.3} color="#CE489A" o={glow} />
                </g>
              );
            }
            if (s.kind === 'gate') {
              const swing = 18 + 26 * (0.5 + 0.5 * Math.sin(T * 0.55 + i));
              return (
                <g key={i}>
                  <line x1={s.x - 60} y1={G} x2={s.x - 60} y2={886} stroke={C.indigo100} strokeWidth={6} />
                  <line x1={s.x + 60} y1={G} x2={s.x + 60} y2={886} stroke={C.indigo100} strokeWidth={6} />
                  <g transform={`rotate(${-swing} ${s.x - 60} 940)`}>
                    <rect x={s.x - 58} y={912} width={112} height={56} rx={6} fill="none" stroke="#E8E7F4" strokeWidth={5} />
                    <line x1={s.x - 58} y1={940} x2={s.x + 54} y2={940} stroke="#E8E7F4" strokeWidth={4} opacity={0.7} />
                  </g>
                  <rect x={s.x + 46} y={860} width={30} height={30} rx={8} fill={C.panel} stroke="#E8E7F4" strokeWidth={4} />
                  <Rings x={s.x + 61} y={866} n={2} R={90} speed={0.5} phase={i * 0.3} color="#E8E7F4" o={glow} />
                </g>
              );
            }
            if (s.kind === 'vehicle') {
              const roll = ((T * 26) % 220) - 110;
              return (
                <g key={i} transform={`translate(${roll} 0)`}>
                  <rect x={s.x - 70} y={912} width={100} height={62} rx={8} fill="none" stroke="#5B58B5" strokeWidth={5} />
                  <path d={`M ${s.x + 30} 974 l 0 -40 l 34 0 l 22 26 l 0 14 z`} fill="none" stroke="#5B58B5" strokeWidth={5} />
                  <circle cx={s.x - 38} cy={982} r={17} fill="none" stroke={C.indigo100} strokeWidth={5} />
                  <circle cx={s.x + 50} cy={982} r={17} fill="none" stroke={C.indigo100} strokeWidth={5} />
                  <rect x={s.x - 34} y={886} width={28} height={24} rx={7} fill={C.panel} stroke="#5B58B5" strokeWidth={4} />
                  <Rings x={s.x - 20} y={892} n={2} R={90} speed={0.5} phase={i * 0.3} color="#5B58B5" o={glow} />
                </g>
              );
            }
            return (
              <g key={i}>
                <rect x={s.x - 58} y={856} width={116} height={144} rx={12} fill="none" stroke={C.indigo100} strokeWidth={5} />
                <rect x={s.x - 52} y={856 + 144 - 78} width={104} height={72} rx={6} fill="#2E7AD1" opacity={0.45} />
                <line x1={s.x - 52} y1={856 + 144 - 78} x2={s.x + 52} y2={856 + 144 - 78} stroke="#7FB2E8" strokeWidth={5} />
                <rect x={s.x - 18} y={824} width={36} height={36} rx={8} fill={C.panel} stroke={C.ok} strokeWidth={4} />
                <Rings x={s.x} y={834} n={2} R={90} speed={0.5} phase={i * 0.3} color={C.ok} o={glow} />
              </g>
            );
          })}
        </svg>

        {showLabels && clearForPanels ? (<React.Fragment>
          <Lbl clampX={{ sx: 960, cx: cam.cx, min: 240, max: 1180 }} x={NET.x} y={NET.y - 90} z={z} eyebrow="Uplink" title="INTERNET · CLOUD" sub="IoTLogIQ middleware · AWS / Azure" accent={C.indigo3} o={q * (0.35 + 0.65 * fNETonly) * (1 - 0.85 * pairO)} />
          <Lbl clampX={{ sx: 960, cx: cam.cx, min: 240, max: 1180 }} x={TW.x} y={TW.top - 90} z={z} eyebrow="Baicells" title="5G CBRS CELL" sub="Band 48 · 3.55–3.70 GHz" accent={C.pink3} o={q * (0.35 + 0.65 * fBHonly) * (1 - 0.85 * pairO)} />
          <Lbl clampX={{ sx: 960, cx: cam.cx, min: 240, max: 1180 }} x={FGW.x} y={FGW.top - 96} z={z} eyebrow="Kerlink" title="iFEMTOCELL GATEWAY" sub="Every sensor reports here" accent={C.ok} o={q * (0.35 + 0.65 * Math.max(fLORAonly, fGWonly))} />
          <Lbl clampX={{ sx: 960, cx: cam.cx, min: 240, max: 1180 }} x={AP.x} y={AP.top - 120} z={z} eyebrow="MultiTech" title="WiFi / CBRS ACCESS POINT" sub="" accent={C.pink3} o={q * (0.35 + 0.65 * Math.max(fUEonly, fGWonly))} />

          {SENSORS.map((s, i) => (
            <Lbl key={i} clampX={{ sx: 960, cx: cam.cx, min: 300, max: 1400 }} x={s.x + (s.kind === 'animal' ? 60 : 0)} y={SENSOR_Y[s.kind] - 110} z={z}
              eyebrow={s.km} title={s.name} sub={`${s.val} · ${s.name2}`} accent={C.ok}
              o={i === spot ? q * fLORAonly : 0} />
          ))}

          <Dim x1={TW.x + 60} x2={AP.x - 60} y={1120} z={z} label="1 – 1.5 miles · 1.6 – 2.4 km" o={q * (0.25 + 0.75 * fBHonly) * cl((960 + ((TW.x + AP.x) / 2 - cam.cx) * z - 300) / 200, 0, 1)} color={C.pink3} />
          <Dim x1={GWS[5].x + 60} x2={5320} y={1150} z={z} label="LoRaWAN range · up to 10 miles" o={q * (0.25 + 0.75 * fLORAonly) * cl((1420 - (960 + ((GWS[5].x + 5380) / 2 - cam.cx) * z)) / 200, 0, 1)} color={C.ok} />
          <Dim x1={GWS[0].x} x2={GWS[5].x} y={1046} z={z} label="6 gateways within 200 m of the AP" o={q * (0.3 + 0.7 * fGWonly) * cl((960 + ((GWS[0].x + GWS[5].x) / 2 - cam.cx) * z - 300) / 200, 0, 1)} color={C.indigo3} />

          <Chip x={(AP.x + TW.x) / 2} y={520} z={z} text="CBRS backhaul · 3.5 GHz" color={C.pink3} o={focus(T, BH + 0.9 * P, SN - 0.3 * P)} />
          <Chip x={(TW.x + NET.x) / 2 + 60} y={330} z={z} text="fibre / IP transit" color={C.indigo3} o={focus(T, BH + 2.3 * P, SN - 0.3 * P) * (1 - 0.9 * pairO)} />
        </React.Fragment>) : null}

      </div>

      <PlanView T={T} o={aerial} start={AER} P={P} />

      <Narrator T={T} o={clearForPanels} beats={[
        { at: HS - 0.3 * P, tag: 'On site', en: 'A handset joins the MultiTech WiFi / CBRS access point.', es: 'Un teléfono se conecta al punto de acceso MultiTech.' },
        { at: BH + 0.3 * P, tag: 'Backhaul', en: 'That AP reaches a Baicells 5G cell 1 to 1.5 miles away.', es: 'Ese AP alcanza una celda Baicells 5G a 1.6–2.4 km.' },
        { at: BH + 2.4 * P, tag: 'Uplink', en: 'From the cell: the internet, and IoTLogIQ.', es: 'Desde la celda: el internet, y IoTLogIQ.' },
        { at: SN, tag: 'Sensing', en: 'Out in the field, battery sensors report over LoRaWAN — for miles.', es: 'En campo, sensores a batería reportan por LoRaWAN — a kilómetros.' },
        { at: GW, tag: 'Collection', en: 'The field gateway hands everything to the on-site AP.', es: 'El gateway de campo entrega todo al AP en sitio.' },
        { at: WD, tag: 'One path', en: 'Data and sensing share the same route to the cloud.', es: 'Datos y sensado comparten la misma ruta a la nube.' },
        { at: AER + 1.2 * P, tag: 'From above', en: 'Seen from above: sensors, assets, people and animals across one site.', es: 'Desde arriba: sensores, activos, personas y animales en un sitio.' },
        { at: AER + 5.6 * P, tag: 'Collection', en: 'Every one of them reports to a single LoRaWAN gateway.', es: 'Todos reportan a un solo gateway LoRaWAN.' },
        { at: AER + 9.0 * P, tag: 'Handoff', en: 'The gateway hands off to the WiFi / CBRS access point.', es: 'El gateway entrega al punto de acceso WiFi / CBRS.' },
        { at: AER + 12.6 * P, tag: 'Uplink', en: 'The 5G cell carries the whole site to the cloud.', es: 'La celda 5G lleva todo el sitio a la nube.' },
      ]} />

      {/* ---------- screen-space overlays ---------- */}
      <div style={{
        position: 'absolute', left: 56, top: 44, display: 'flex', alignItems: 'center', gap: 14,
        font: `600 15px ${FD}`, letterSpacing: '0.16em', textTransform: 'uppercase',
        color: 'rgba(232,231,244,0.68)', whiteSpace: 'nowrap',
      }}>
        <div style={{ width: 10, height: 10, borderRadius: 999, background: C.pink }} />
        SENZARY · IoTLogIQ
      </div>

      {(function () {
        if (!showPhotos) return null;
        const cards = [
          { o: focus(T, HS + 0.4 * P, BH - 0.4 * P), id: 'dev-multitech', accent: C.pink3, label: 'MultiTech', sub: 'WiFi / CBRS access point', ph: 'MultiTech AP photo', wx: AP.x, wy: AP.top - 12 },
          (function () {
            const k = SENSORS[spot] ? SENSORS[spot].kind : 'soil';
            const isTracker = k === 'animal' || k === 'vehicle';
            const isMachine = k === 'energy';
            return {
              o: fLORAonly,
              id: isTracker ? 'dev-tracker' : isMachine ? 'dev-asystom' : 'dev-sensor',
              accent: C.ok,
              label: isTracker ? 'Abeeway tracker' : isMachine ? 'Asystom sentinel' : 'nke Watteco BoB',
              sub: isTracker ? 'Livestock and vehicle tracking' : isMachine ? 'Vibration · acoustic · temperature' : 'Soil · water · level · gate',
              ph: 'Sensor photo', wx: SENSORS[4].x, wy: SENSOR_Y.energy,
            };
          })(),
          { o: fGWonly, id: 'dev-kerlink', accent: C.ok, label: 'Kerlink', sub: 'iFemtoCell gateway', ph: 'Kerlink iFemtoCell photo', wx: GWS[3].x, wy: gwTop(GWS[3]) - 12 },
          { o: fNETonly, id: 'dev-console', accent: C.indigo3, label: 'IoTLogIQ', sub: 'WaterIQ console · live site', ph: 'Console screenshot', wx: NET.x, wy: NET.y },
        ];
        const top = cards.reduce((m, c) => (c.o > m.o ? c : m), cards[0]);
        const dim = Math.max(pairDim, trioO, pairO, wide, 1 - clearForPanels);
        if (top.o * (1 - dim) <= 0.02) return null;
        top.o = top.o * (1 - dim);
        const cardX = 1920 - 48 - 330, cardY = 120 + 300;
        const tx = top.wx != null ? 960 + (top.wx - cam.cx) * z : null;
        const ty = top.wy != null ? 540 + (top.wy - cam.cy) * z : null;
        const onScreen = tx != null && tx > 40 && tx < 1880 && ty > 40 && ty < 1000;
        return (
          <React.Fragment>

            <div style={{
              position: 'absolute', left: 56, top: 130, width: 330,
              display: 'flex', flexDirection: 'column', alignItems: 'stretch', gap: 10,
            }}>
              <DevPhoto id={top.id} accent={top.accent} label={top.label} sub={top.sub}
                o={0.97 * top.o} placeholder={top.ph} />
            </div>
          </React.Fragment>
        );
      })()}

      {(function () {
        const o = fUEonly * clearForPanels;
        if (o <= 0.02) return null;
        const sx = (wx) => cl(960 + (wx - cam.cx) * z, 200, 1720);
        const card = (accent, k, t, d, wx, dy) => (
          <div style={{
            position: 'absolute', left: sx(wx), top: 540 + (884 - cam.cy) * z - dy,
            transform: 'translate(-50%, -100%)',
            padding: '10px 14px', borderRadius: 12, whiteSpace: 'nowrap', textAlign: 'center',
            background: 'rgba(20,19,46,0.92)', border: `2px solid ${accent}`, opacity: o,
          }}>
            <div style={{
              font: `600 12px ${FD}`, letterSpacing: '0.16em', textTransform: 'uppercase', color: accent,
            }}>{k}</div>
            <div style={{ font: `600 18px ${FD}`, color: C.white, marginTop: 3 }}>{t}</div>
            <div style={{ font: `500 13px ${FB}`, color: 'rgba(232,231,244,0.82)', marginTop: 3 }}>{d}</div>
          </div>
        );
        return (
          <React.Fragment>
            {card(C.warn, 'SIM card', 'HIGH-SPEED DATA', 'Direct to the 5G cell', PER2.x, 120)}
            {card(C.pink3, 'WiFi access', 'VIA WiFi ROUTER', 'On-site AP → CBRS backhaul', PER.x, 120)}
          </React.Fragment>
        );
      })()}

      <PairPanel T={T} o={pairO} showPhotos={showPhotos} />
      <TrioPanel T={T} o={trioO} showPhotos={showPhotos} />

      {/* wide-shot flow strip */}
      {wide > 0.001 ? (
        <div style={{
          position: 'absolute', left: 0, right: 610, top: 150, display: 'flex',
          justifyContent: 'center', gap: 20, opacity: wide * (titleO > 0.02 ? 0 : 1) * (1 - trioO) * cl(1 - aerial * 3, 0, 1), whiteSpace: 'nowrap',
          font: `600 15px ${FD}`, letterSpacing: '0.2em', textTransform: 'uppercase',
          color: 'rgba(232,231,244,0.9)', alignItems: 'center',
        }}>
          {['Sensor', 'Kerlink gateway', 'MultiTech AP', 'Baicells 5G CBRS', 'Cloud'].map((w, i) => (
            <React.Fragment key={i}>
              {i ? <span style={{ color: C.pink, fontSize: 20 }}>→</span> : null}
              <span>{w}</span>
            </React.Fragment>
          ))}
        </div>
      ) : null}

      {/* opening title */}
      {titleO > 0.001 ? (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', opacity: titleO,
          background: 'radial-gradient(60% 60% at 50% 50%, rgba(20,19,46,0.86) 0%, rgba(20,19,46,0.35) 70%, rgba(20,19,46,0) 100%)',
        }}>
          <div style={{
            font: `600 14px ${FD}`, letterSpacing: '0.28em', color: C.pink3,
            textTransform: 'uppercase', width: '90%', textAlign: 'center',
          }}>Data + sensing on one network</div>
          <div style={{
            font: `700 67px ${FD}`, letterSpacing: '-0.02em', color: C.white, marginTop: 18,
            width: '92%', textAlign: 'center',
            transform: `translateY(${(1 - MOTION.enter(cl((T - 0.4) / (1.1 * P), 0, 1))) * 26}px)`,
          }}>ONE NETWORK, END TO END</div>
          <div style={{
            font: `500 20px ${FB}`, color: 'rgba(232,231,244,0.85)', marginTop: 16,
            width: '90%', textAlign: 'center',
          }}>UNA SOLA RED, DE PUNTA A PUNTA</div>
          <div style={{
            font: `500 15px ${FB}`, color: 'rgba(229,140,192,0.95)', marginTop: 22,
            width: '90%', textAlign: 'center',
          }}>MultiTech · Baicells 5G CBRS · Kerlink iFemtoCell</div>
        </div>
      ) : null}

      {/* close: logo + verticals */}
      {closeO > 0.001 ? (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', gap: 34,
          opacity: closeO, background: C.bg,
        }}>
          <div style={{
            background: C.white, borderRadius: 20, padding: '20px 40px', display: 'flex',
            alignItems: 'center', gap: 26, boxShadow: '0 24px 70px rgba(20,19,46,0.55)',
            transform: `scale(${0.96 + 0.04 * MOTION.enter(cl((T - CLS - 0.1 * P) / (0.8 * P), 0, 1))})`,
          }}>
            <img src={(window.__resources && window.__resources.senzaryLogo) || "assets/logo-iotlogiq-senzary.png"} alt="IoTLogIQ by Senzary" style={{ width: 118, display: 'block' }} />
            <div style={{ textAlign: 'left', width: 520 }}>
              <div style={{ font: `700 17px ${FD}`, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.indigo, lineHeight: 1.25 }}>
                Sensing and delivering actionable insights
              </div>
              <div style={{ font: `500 12px ${FB}`, color: C.pink, marginTop: 8 }}>
                Sensando y entregando información accionable
              </div>
              <div style={{ display: 'flex', gap: 10, marginTop: 14, flexWrap: 'nowrap' }}>
                <div style={{
                  font: `600 11px ${FD}`, letterSpacing: '0.06em', color: C.white,
                  background: C.pink, borderRadius: 999, padding: '8px 18px', whiteSpace: 'nowrap',
                }}>sales@senzary.com</div>
                <div style={{
                  font: `600 11px ${FD}`, letterSpacing: '0.06em', color: C.indigo,
                  border: `2px solid ${C.indigo}`, borderRadius: 999, padding: '6px 18px', whiteSpace: 'nowrap',
                }}>1833-Senzary</div>
              </div>
            </div>
          </div>

          <div style={{ display: 'flex', alignItems: 'center', gap: 28, justifyContent: 'center' }}>
            <div style={{
              width: 880, borderRadius: 16, overflow: 'hidden',
              border: '1px solid rgba(232,231,244,0.18)', boxShadow: '0 24px 70px rgba(20,19,46,0.6)',
            }}>
              <img src={RES('devConsole', 'assets/console-wateriq.png')} alt="IoTLogIQ console"
                style={{ width: '100%', display: 'block' }} />
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10, textAlign: 'left' }}>
              <div style={{
                font: `600 11px ${FD}`, letterSpacing: '0.2em', textTransform: 'uppercase', color: C.pink3,
              }}>Deployed on</div>
              {VERTICALS.map((v) => (
                <div key={v.id}>
                  <div style={{
                    font: `600 15px ${FD}`, letterSpacing: '0.12em', textTransform: 'uppercase', color: C.white,
                  }}>{v.en}</div>
                  <div style={{ font: `500 12px ${FB}`, color: 'rgba(229,140,192,0.9)' }}>{v.es}</div>
                </div>
              ))}
            </div>
          </div>
        </div>
      ) : null}

      <Ticker T={T} o={0.96 * focus(T, 1.2 * P, END - 0.6 * P, 0.8 * P)} />

      {showCaptions ? <window.Captions
        items={[
          { at: 0.6, until: HS - 0.6 * P, text: Cap('One network carrying data and sensor traffic at the same time.', 'Una sola red que transporta datos y tráfico de sensores a la vez.') },
          { at: WD + 2.8 * P, until: WD + 6.0 * P, text: Cap('One network, three radios.', 'Una red, tres radios.') },
        ]}
        style={{ bottom: '10.5%', textShadow: '0 2px 18px rgba(20,19,46,0.9)' }}
      /> : null}
    </div>
  );
}

window.NetworkVideo = NetworkVideo;
