/* intel.jsx — RaDa Intelligence.
 * Institutional intelligence showcase (Overview / Science / Insights Lab /
 * Operations / Manage). External institutions + GSG staff log in here.
 *
 * Faithful port of Claude Design's RaDa Intelligence showcase design.
 * - Overview + Operations: LIVE from get_portfolio_intelligence (real M0-M5,
 *   counties, per-farm inventory). Falls back to the design's numbers only
 *   while the live fetch is in flight.
 * - Science + Insights Lab: the design's interactive cockpit. The data shown is
 *   an illustrative sample field (flagged) - the live per-farm cockpit is the
 *   Panel Workbench. P001: nothing synthetic is presented as a real reading.
 * - Auth: Firebase (same project as the county portal). Access = GSG staff
 *   Workspace domain OR a provisioned `intel_viewer` claim; the data API gates
 *   server-side on the same token. Aggregate, no PII.
 * Stack: in-browser React (UMD) + Babel, chemeli design tokens — same as the
 * county portal. Implements design handoff 2026-06-26. — Claudia (Code) */

const { useState, useEffect, useRef } = React;

const FB_CONFIG = { apiKey: "AIzaSyAFQ37zvZAD4vWIE5nwuCxzHUeCY8P-m5s", authDomain: "root-range-465417-m6.firebaseapp.com", projectId: "root-range-465417-m6", storageBucket: "root-range-465417-m6.firebasestorage.app", messagingSenderId: "6353794009", appId: "1:6353794009:web:09a6a1ffa464dfea445fc8" };
const PORTFOLIO_URL = "https://get-portfolio-intelligence-5bsfvjejza-ew.a.run.app";
const INTEL_FIELD_URL = "https://get-intel-field-5bsfvjejza-ew.a.run.app";
const REGISTER_FARM_URL = "https://register-farm-5bsfvjejza-ew.a.run.app";
const S2_TRUECOLOR_URL = "https://get-s2-truecolor-5bsfvjejza-ew.a.run.app";
const FEATURED_FIELD = "field_20260626151521";  // Arap_Samoei_Farm_05

/* ---- CSS-string -> React style object (keeps the design's exact inline CSS) */
function s(css) {
  const o = {};
  String(css).split(';').forEach(d => {
    const i = d.indexOf(':'); if (i < 0) return;
    const k = d.slice(0, i).trim(); if (!k) return;
    const v = d.slice(i + 1).trim();
    o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = v;
  });
  return o;
}
const merge = (css, extra) => Object.assign(s(css), extra || {});

/* ---- themes (verbatim from the design; cream == chemeli daylight) */
const THEMES = {
  dark: { bg: '#0B0D11', bg2: '#0E1217', panel: '#14181F', panel2: '#1A1F28', inset: '#0F1318', ink: '#E8EEF5', ink2: '#97A3B2', ink3: '#5F6A78', line: '#222A33', lineSoft: '#1A2129', accent: '#E0993F', accentDeep: '#B47432', accentSoft: 'rgba(224,153,63,.14)', good: '#5BB82E', goodSoft: 'rgba(91,184,46,.14)', warn: '#E3A53F', warnSoft: 'rgba(227,165,63,.14)', bad: '#E0573A', badSoft: 'rgba(224,87,58,.14)', note: '#5E94C4', noteSoft: 'rgba(94,148,196,.14)', grid: '#1C232C' },
  cream: { bg: '#F5EFE3', bg2: '#EFE8DA', panel: '#FCF7EC', panel2: '#FFFFFF', inset: '#EFE8DA', ink: '#16201E', ink2: '#4A5856', ink3: '#7A8682', line: '#E0D6C2', lineSoft: '#EBE2CF', accent: '#DE994C', accentDeep: '#B47432', accentSoft: '#FBE4C2', good: '#4A9E25', goodSoft: '#D8EBC9', warn: '#C77E1A', warnSoft: '#F8E4BD', bad: '#C0492B', badSoft: '#F4D6CB', note: '#3B6F8C', noteSoft: '#CEDFE7', grid: '#E0D6C2' }
};
const TV = { good: 'var(--good)', warn: 'var(--warn)', bad: 'var(--bad)', note: 'var(--note)', accent: 'var(--accent)', ink: 'var(--ink)', ink2: 'var(--ink2)', ink3: 'var(--ink3)' };
const tv = k => TV[k] || 'var(--ink2)';

const MODULES = [
  { id: 'overview', label: 'Overview', short: 'Overview', icon: 'monitoring', roles: ['CEO', 'Admin', 'Analyst', 'Agronomist'] },
  { id: 'operations', label: 'Operations', short: 'Ops', icon: 'travel_explore', roles: ['CEO', 'Admin', 'Analyst', 'Agronomist', 'Field Agent'] },
  { id: 'science', label: 'Science', short: 'Science', icon: 'science', roles: ['CEO', 'Admin', 'Analyst'] },
  { id: 'insights', label: 'Insights Lab', short: 'Insights', icon: 'experiment', roles: ['CEO', 'Admin', 'Analyst', 'Agronomist'] },
  { id: 'manage', label: 'Manage', short: 'Manage', icon: 'tune', roles: ['CEO', 'Admin'] }
];
const ROLES = ['CEO', 'Analyst', 'Agronomist', 'Field Agent', 'Admin'];
const TITLES = { overview: 'Overview', operations: 'Operations', science: 'Science', insights: 'Insights Lab', manage: 'Manage' };

/* ---- synthetic series for the illustrative Science cockpit (sample field) */
const SERIES = (function () {
  const out = []; const start = Date.UTC(2024, 6, 1); const total = 52;
  for (let i = 0; i < total; i++) {
    const d = new Date(start); d.setUTCDate(d.getUTCDate() + Math.round(i * (720 / total)));
    const phase = (i / total) * Math.PI * 4;
    let ndvi = 0.5 + 0.27 * Math.sin(phase - Math.PI / 2) + 0.04 * Math.sin(i * 1.7);
    ndvi = Math.max(0.13, Math.min(0.9, ndvi));
    const baseline = 0.5 + 0.21 * Math.sin(phase - Math.PI / 2);
    const rain = Math.max(0, Math.round(40 + 250 * Math.pow(Math.max(0, Math.sin(phase - 0.9)), 2)));
    out.push({ ndvi: +ndvi.toFixed(3), baseline: +baseline.toFixed(3), rain: rain, date: d });
  }
  return out;
})();
const CELLOFF = (function () { const a = []; for (let i = 0; i < 120; i++) { const f = Math.abs(Math.sin((i + 1) * 12.9898) * 43758.5453); a.push((f - Math.floor(f)) * 0.34 - 0.17); } return a; })();
const STAGES = [{ name: 'Bud break', emoji: '🌱', range: 'Aug–Sep' }, { name: 'Flowering', emoji: '🌼', range: 'Oct–Nov' }, { name: 'Fruit set', emoji: '🍈', range: 'Dec–Jan' }, { name: 'Fruit fill', emoji: '🥭', range: 'Feb–Mar' }, { name: 'Maturation', emoji: '🟠', range: 'Apr–May' }, { name: 'Harvest', emoji: '🧺', range: 'Jun–Jul' }];

const fmtDate = ms => { const d = new Date(ms); const p = n => String(n).padStart(2, '0'); return d.getUTCFullYear() + '-' + p(d.getUTCMonth() + 1) + '-' + p(d.getUTCDate()); };
const fmtMon = ms => { const M = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const d = new Date(ms); return M[d.getUTCMonth()] + ' ' + d.getUTCFullYear(); };
function ramp(v) { v = Math.max(0, Math.min(1, v)); const L = (a, b, t) => Math.round(a + (b - a) * t); let r, g, b; if (v < 0.5) { const t = v / 0.5; r = L(0xC0, 0xE3, t); g = L(0x49, 0xA5, t); b = L(0x2B, 0x3F, t); } else { const t = (v - 0.5) / 0.5; r = L(0xE3, 0x5B, t); g = L(0xA5, 0xB8, t); b = L(0x3F, 0x2E, t); } return 'rgb(' + r + ',' + g + ',' + b + ')'; }
const mc = m => m === 'M0' ? 'var(--ink3)' : m === 'M1' ? 'var(--note)' : m === 'M2' ? 'var(--note)' : m === 'M3' ? 'var(--warn)' : 'var(--good)';
const num = (v, d = 0) => (v == null || v !== v) ? '—' : Number(v).toLocaleString(undefined, { maximumFractionDigits: d });

/* eyebrow tag marking a sample / illustrative figure (P001 honesty) */
function SampleTag({ text }) {
  return <span style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.4px;text-transform:uppercase;color:var(--ink3);border:1px solid var(--line);border-radius:5px;padding:2px 6px")}>{text || 'sample'}</span>;
}

/* LOUD, non-dismissible P001 banner pinned to the top of synthetic tabs
   (Science / Insights Lab). Full-width, high-contrast, sticks while scrolling
   so a screenshot of any part of the tab still reads as illustrative. */
function SampleBanner() {
  return (
    <div style={s("position:sticky;top:0;z-index:8;display:flex;align-items:center;gap:12px;background:var(--warn);color:#0B0D11;border-radius:10px;padding:11px 16px;box-shadow:0 6px 20px rgba(0,0,0,.28)")}>
      <span className="ms" style={s("font-size:22px;color:#0B0D11")}>warning</span>
      <div style={s("display:flex;flex-direction:column;gap:1px;min-width:0")}>
        <span style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;font-weight:700")}>Sample · Illustrative</span>
        <span style={s("font-size:13px;line-height:1.35;font-weight:600")}>Demonstration field — <span style={{ textDecoration: 'underline' }}>not live farm data</span>. Live per-farm analysis runs in the Workbench.</span>
      </div>
    </div>
  );
}

/* LIVE banner shown once real per-field data has loaded. States the field + last
   acquisition and marks which sub-panels are still indicative (P001 honesty). */
function LiveFieldBanner({ fd, busy, note }) {
  const f = fd && fd.field;
  return (
    <div style={s("position:sticky;top:0;z-index:8;display:flex;align-items:center;gap:12px;background:var(--goodSoft);border:1px solid var(--good);border-radius:10px;padding:10px 15px")}>
      <span className="ms" style={s("font-size:20px;color:var(--good)")}>{busy ? 'sync' : 'satellite_alt'}</span>
      <div style={s("display:flex;flex-direction:column;gap:1px;min-width:0")}>
        <span style={s("font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:1.4px;text-transform:uppercase;color:var(--good);font-weight:700")}>Live · from the warehouse</span>
        <span style={s("font-size:12.5px;line-height:1.35;color:var(--ink)")}>{f ? (f.name + (f.crop ? ' · ' + f.crop : '') + ' · ' + (f.pixels || '?') + 'px · ' + (f.n_obs || '?') + ' acquisitions · latest ' + ((f.last_obs || '').slice(0, 10))) : 'loading field…'}{note ? <span style={{ color: 'var(--ink3)' }}> · {note}</span> : null}</span>
      </div>
    </div>
  );
}

/* Real Google Maps (hybrid satellite) + the field's live 10 m spots overlaid from the
   per-pixel cells, coloured by the chosen index (NDVI/NDMI/NDRE). True-colour toggle
   swaps the basemap; GT pins drop at capture locations; spots are clickable. Same
   engine as the /app demo (Map.jsx). */
function FieldMap({ cells, center, index, basemap, gtPoints, height, s2 }) {
  const ref = useRef(null), mapRef = useRef(null), rects = useRef([]), gtm = useRef([]);
  const [sel, setSel] = useState(null);
  function normIdx(raw, idx) { return idx === 'ndmi' ? Math.max(0, Math.min(1, (raw + 0.1) / 0.5)) : idx === 'ndre' ? Math.max(0, Math.min(1, raw / 0.5)) : idx === 'vv' ? Math.max(0, Math.min(1, (raw + 22) / 18)) : raw; }
  function paint() {
    if (!mapRef.current || !window.google || !window.google.maps) return;
    rects.current.forEach(r => r.setMap(null)); rects.current = [];
    gtm.current.forEach(m => m.setMap(null)); gtm.current = [];
    const px = cells || [];
    const d = 0.000055;  // ~10 m half-cell in degrees
    if (index && px.length) {
      px.forEach(p => {
        const raw = p[index]; if (raw == null) return;
        const rect = new google.maps.Rectangle({ map: mapRef.current,
          bounds: { north: p.lat + d, south: p.lat - d, east: p.lng + d, west: p.lng - d },
          fillColor: ramp(normIdx(raw, index)), fillOpacity: 0.72, strokeWeight: 0, clickable: true });
        rect.addListener('click', () => setSel({ lat: p.lat, lng: p.lng, ndvi: p.ndvi, ndmi: p.ndmi, ndre: p.ndre }));
        rects.current.push(rect);
      });
    }
    if (gtPoints && gtPoints.length) {
      gtPoints.forEach(g => gtm.current.push(new google.maps.Marker({ position: { lat: g.lat, lng: g.lng }, map: mapRef.current,
        icon: { path: google.maps.SymbolPath.CIRCLE, scale: 6, fillColor: '#5BB82E', fillOpacity: 1, strokeColor: '#fff', strokeWeight: 2 }, title: 'ground-truth capture (approx. field centre)' })));
    }
    if (px.length) { const b = new google.maps.LatLngBounds(); px.forEach(p => b.extend({ lat: p.lat, lng: p.lng })); mapRef.current.fitBounds(b); }
  }
  useEffect(() => {
    let n = 0, dead = false;
    const init = () => {
      if (dead) return;
      if (!window.google || !window.google.maps) { if (n++ < 120) setTimeout(init, 150); return; }
      if (mapRef.current || !ref.current) return;
      // data-viz map: native UI off (reference = /app PortfolioMap), keep zoom + fullscreen.
      // Native mapTypeControl/streetViewControl collided with the app's own chrome (2026-07-02 regression).
      mapRef.current = new google.maps.Map(ref.current, { center: center || { lat: 0.52, lng: 35.27 }, zoom: 17, mapTypeId: basemap || 'satellite', gestureHandling: 'greedy', tilt: 0, disableDefaultUI: true, zoomControl: true, fullscreenControl: true });
      paint();
    };
    init();
    return () => { dead = true; rects.current.forEach(r => r.setMap(null)); gtm.current.forEach(m => m.setMap(null)); if (s2ov.current) s2ov.current.setMap(null); };
  }, []);
  useEffect(() => { paint(); }, [cells, index, gtPoints]);
  useEffect(() => { if (mapRef.current && basemap && window.google) mapRef.current.setMapTypeId(basemap); }, [basemap]);
  // real Sentinel-2 RGB drape (true-colour): padded bounds from the CF keep it aligned
  const s2ov = useRef(null);
  useEffect(() => {
    if (!window.google || !mapRef.current) return;
    if (s2ov.current) { s2ov.current.setMap(null); s2ov.current = null; }
    if (s2 && s2.url && s2.bounds) {
      s2ov.current = new google.maps.GroundOverlay(s2.url, s2.bounds, { clickable: false });
      s2ov.current.setMap(mapRef.current);
    }
  }, [s2]);
  return React.createElement('div', { style: { position: 'relative' } },
    React.createElement('div', { ref, style: { width: '100%', height: height || 360, borderRadius: 10, overflow: 'hidden', background: '#0b1726' } }),
    sel ? React.createElement('div', { onClick: () => setSel(null), style: { position: 'absolute', bottom: 8, left: 8, background: 'rgba(11,13,17,.9)', color: '#fff', borderRadius: 8, padding: '8px 11px', fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, lineHeight: 1.55, cursor: 'pointer' } },
      React.createElement('div', { style: { color: '#97A3B2' } }, sel.lat.toFixed(5) + ', ' + sel.lng.toFixed(5)),
      React.createElement('div', null, 'NDVI ' + (sel.ndvi != null ? sel.ndvi.toFixed(3) : '—') + ' · NDMI ' + (sel.ndmi != null ? sel.ndmi.toFixed(3) : '—') + ' · NDRE ' + (sel.ndre != null ? sel.ndre.toFixed(3) : '—'))) : null);
}

/* Real Google map for the Add-a-farm flow - tap to drop polygon corners, real
   Street View pegman + fullscreen. Reports the corners up via onPolygon. */
function AddFarmMap({ height, onPolygon }) {
  const ref = useRef(null), mapRef = useRef(null), poly = useRef(null), pts = useRef([]), mks = useRef([]);
  const [count, setCount] = useState(0);
  useEffect(() => {
    let n = 0, dead = false;
    const init = () => {
      if (dead) return;
      if (!window.google || !window.google.maps) { if (n++ < 120) setTimeout(init, 150); return; }
      if (mapRef.current || !ref.current) return;
      mapRef.current = new google.maps.Map(ref.current, { center: { lat: 0.52, lng: 35.27 }, zoom: 15, mapTypeId: 'hybrid', tilt: 0, gestureHandling: 'greedy',
        zoomControl: true, mapTypeControl: true, streetViewControl: true, fullscreenControl: true, scaleControl: true });
      poly.current = new google.maps.Polygon({ map: mapRef.current, paths: [], strokeColor: '#DE994C', strokeWeight: 2, fillColor: '#DE994C', fillOpacity: 0.18, editable: false });
      mapRef.current.addListener('click', (e) => {
        pts.current.push({ lat: e.latLng.lat(), lng: e.latLng.lng() });
        poly.current.setPath(pts.current);
        mks.current.push(new google.maps.Marker({ position: e.latLng, map: mapRef.current, icon: { path: google.maps.SymbolPath.CIRCLE, scale: 5, fillColor: '#DE994C', fillOpacity: 1, strokeColor: '#fff', strokeWeight: 1.5 } }));
        setCount(pts.current.length);
        if (onPolygon) onPolygon(pts.current.slice());
      });
    };
    init();
    return () => { dead = true; };
  }, []);
  function clear() { pts.current = []; mks.current.forEach(m => m.setMap(null)); mks.current = []; if (poly.current) poly.current.setPath([]); setCount(0); if (onPolygon) onPolygon([]); }
  return React.createElement('div', { style: { position: 'relative', height: height || 560 } },
    React.createElement('div', { ref, style: { width: '100%', height: '100%', borderRadius: 12, overflow: 'hidden', background: '#0b1726' } }),
    React.createElement('div', { style: { position: 'absolute', top: 12, left: 12, background: 'rgba(11,13,17,.85)', color: '#fff', borderRadius: 8, padding: '6px 11px', fontSize: 12, fontFamily: "'JetBrains Mono',monospace", pointerEvents: 'none' } }, count ? (count + ' corners · drag the pegman for Street View') : 'Tap to drop boundary corners · drag the pegman for Street View'),
    count ? React.createElement('button', { onClick: clear, style: { position: 'absolute', top: 46, left: 12, background: 'rgba(11,13,17,.85)', color: '#fff', border: 'none', borderRadius: 8, padding: '6px 11px', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', zIndex: 5 } }, 'Clear') : null);
}

/* Real Google map of ALL farms - status pins, click to drill into a farm. Mirrors
   Map.jsx PortfolioMap. */
function PortfolioMap({ farms, onPick, height }) {
  const ref = useRef(null), mapRef = useRef(null), mks = useRef([]);
  const COL = { red: '#E0573A', amber: '#E3A53F', green: '#5BB82E', grey: '#7A8A86' };
  function draw() {
    if (!mapRef.current || !window.google || !window.google.maps) return;
    mks.current.forEach(m => m.setMap(null)); mks.current = [];
    const ll = (farms || []).filter(f => f.lat != null && f.lng != null);
    ll.forEach(f => {
      const m = new google.maps.Marker({ position: { lat: f.lat, lng: f.lng }, map: mapRef.current, title: f.farm_name || f.farm_id,
        icon: { path: google.maps.SymbolPath.CIRCLE, scale: 6.5, fillColor: COL[f.status] || COL.green, fillOpacity: 0.95, strokeColor: '#0B0D11', strokeWeight: 1.4 } });
      m.addListener('click', () => onPick && onPick(f.farm_id, f.farm_name));
      mks.current.push(m);
    });
    if (ll.length) { const b = new google.maps.LatLngBounds(); ll.forEach(f => b.extend({ lat: f.lat, lng: f.lng })); mapRef.current.fitBounds(b); }
  }
  useEffect(() => {
    let n = 0, dead = false;
    const init = () => {
      if (dead) return;
      if (!window.google || !window.google.maps) { if (n++ < 120) setTimeout(init, 150); return; }
      if (mapRef.current || !ref.current) return;
      mapRef.current = new google.maps.Map(ref.current, { center: { lat: 0.3, lng: 37.6 }, zoom: 6, mapTypeId: 'hybrid', gestureHandling: 'greedy', tilt: 0, zoomControl: true, mapTypeControl: true, streetViewControl: true, fullscreenControl: true, scaleControl: true });
      draw();
    };
    init();
    return () => { dead = true; mks.current.forEach(m => m.setMap(null)); mks.current = []; };
  }, []);
  useEffect(() => { draw(); }, [farms]);
  return React.createElement('div', { ref, style: { width: '100%', height: height || 300, borderRadius: 12, overflow: 'hidden', background: '#0b1726' } });
}

/* ========================== RADA INTELLIGENCE ========================== */
function Console({ live, user, onSignOut }) {
  const [st, setStRaw] = useState({
    theme: 'dark', role: 'CEO', active: 'overview',
    enabled: { overview: true, operations: true, science: true, insights: true, manage: true },
    frame: 40, playing: false, speed: 1, loop: true,
    overlays: { truecolor: false, ndvi: true, ndmi: false, ndre: false, radar: false, rain: true, gt: true },
    sciView: 'spectral', addFarm: false, compare: false, cmpVars: { ndmi: false, ndre: false, gdd: false },
    lab: 'zones', hypoBaseline: 'cohort', hypoIndex: 'wsi42',
    opStatus: 'all', opQ: '', anomThresh: 1.0,
    farmName: '', cropSel: 'Mango', visionJson: '', addPts: [], saveMsg: '', saveBusy: false, addSaved: 0,
    cmpVar: 'ndvi'
  });
  const set = p => setStRaw(s0 => Object.assign({}, s0, typeof p === 'function' ? p(s0) : p));
  const timer = useRef(null);

  /* ---- LIVE per-field science (Science + Insights) from get_intel_field ---- */
  const [fieldId, setFieldId] = useState(FEATURED_FIELD);
  const [fieldData, setFieldData] = useState(null);
  const [fieldBusy, setFieldBusy] = useState(false);
  useEffect(() => {
    if (!user) return;
    let alive = true;
    setFieldBusy(true);
    user.getIdToken()
      .then(tok => fetch(INTEL_FIELD_URL + '?field=' + encodeURIComponent(fieldId), { headers: { Authorization: 'Bearer ' + tok } }))
      .then(r => r.json().then(j => ({ ok: r.ok, j })))
      .then(({ ok, j }) => { if (alive && ok && j && j.series) { setFieldData(j); } if (alive) setFieldBusy(false); })
      .catch(() => { if (alive) setFieldBusy(false); });
    return () => { alive = false; };
  }, [fieldId, user]);
  /* ---- real Sentinel-2 true-colour drape (get_s2_truecolor). Fetched when the
     true-colour overlay is ON, for the acquisition date under the timeline cursor;
     debounced so scrubbing doesn't hammer Earth Engine. ---- */
  const [s2, setS2] = useState(null);
  const s2Key = useRef('');
  useEffect(() => {
    if (!user || !st.overlays.truecolor || !fieldData || !Array.isArray(fieldData.cells) || !fieldData.cells.length || !Array.isArray(fieldData.series) || !fieldData.series.length) { if (!st.overlays.truecolor) { setS2(null); s2Key.current = ''; } return; }
    const idx = Math.min(st.frame, fieldData.series.length - 1);
    const dateStr = String(fieldData.series[idx].date || '').slice(0, 10);
    const key = fieldId + '|' + dateStr;
    if (key === s2Key.current) return;
    const tmr = setTimeout(() => {
      s2Key.current = key;
      const las = fieldData.cells.map(c => c.lat), lns = fieldData.cells.map(c => c.lng);
      const qs = '?south=' + Math.min.apply(null, las) + '&west=' + Math.min.apply(null, lns) +
                 '&north=' + Math.max.apply(null, las) + '&east=' + Math.max.apply(null, lns) +
                 (dateStr ? '&date=' + dateStr : '');
      user.getIdToken()
        .then(tok => fetch(S2_TRUECOLOR_URL + qs, { headers: { Authorization: 'Bearer ' + tok } }))
        .then(r => r.ok ? r.json() : null)
        .then(j => { if (j && j.url && j.bounds && s2Key.current === key) setS2(j); })
        .catch(() => {});
    }, 600);
    return () => clearTimeout(tmr);
  }, [st.overlays.truecolor, st.frame, fieldData, fieldId, user]);

  // live spectral series mapped to the chart's {ndvi,ndmi,ndre,baseline,rain,date} shape (or null -> synthetic fallback)
  const liveArr = (fieldData && Array.isArray(fieldData.series) && fieldData.series.length)
    ? fieldData.series.map(p => ({ ndvi: p.ndvi, ndmi: p.ndmi, ndre: p.ndre, baseline: (p.baseline != null ? p.baseline : p.ndvi), rain: p.rain || 0, date: new Date(p.date) }))
    : null;
  const seriesLen = (liveArr || SERIES).length;

  // SC-6 second field to compare (real, live) - fetched on demand from the same CF
  const [cmpFieldId, setCmpFieldId] = useState(null);
  const [cmpData, setCmpData] = useState(null);
  useEffect(() => {
    if (!user || !cmpFieldId) { setCmpData(null); return; }
    let alive = true;
    user.getIdToken()
      .then(tok => fetch(INTEL_FIELD_URL + '?field=' + encodeURIComponent(cmpFieldId), { headers: { Authorization: 'Bearer ' + tok } }))
      .then(r => r.json().then(j => ({ ok: r.ok, j })))
      .then(({ ok, j }) => { if (alive && ok && j && j.series) setCmpData(j); })
      .catch(() => {});
    return () => { alive = false; };
  }, [cmpFieldId, user]);

  /* auto-play transport — effect owns the interval, keyed on play/speed/loop */
  useEffect(() => {
    if (!st.playing) { clearInterval(timer.current); return; }
    clearInterval(timer.current);
    const ms = Math.max(90, 460 / st.speed);
    timer.current = setInterval(() => {
      set(s0 => { let f = s0.frame + 1; if (f >= seriesLen) { if (s0.loop) return { frame: 0 }; clearInterval(timer.current); return { playing: false }; } return { frame: f }; });
    }, ms);
    return () => clearInterval(timer.current);
  }, [st.playing, st.speed, st.loop, seriesLen]);

  // handlers
  const toggleTheme = () => set(s0 => ({ theme: s0.theme === 'dark' ? 'cream' : 'dark' }));
  const setRole = r => set(s0 => { const cur = MODULES.find(m => m.id === s0.active); let active = s0.active; if (!cur || !cur.roles.includes(r)) { const f = MODULES.find(m => s0.enabled[m.id] && m.roles.includes(r)); active = f ? f.id : s0.active; } return { role: r, active }; });
  const go = id => { const m = MODULES.find(x => x.id === id); if (!m || !st.enabled[id] || !m.roles.includes(st.role)) return; set({ active: id }); };
  // drill: click a farm (Ops map/table/feed) -> load it into the live Science cockpit
  const openFarm = (fid) => { if (!fid) return; setFieldId(fid); set({ active: 'science', sciView: 'map', playing: false, frame: 9999 }); };
  // register a real farm from the Add-a-farm map -> register_farm CF (BQ identity + Firestore + pixelation)
  const saveFarm = () => {
    if (!st.farmName.trim()) { set({ saveMsg: 'Enter a farm name.' }); return; }
    const pts = st.addPts || [];
    if (pts.length < 3) { set({ saveMsg: 'Draw at least 3 boundary corners on the map.' }); return; }
    if (!user) { set({ saveMsg: 'Sign-in required.' }); return; }
    const ring = pts.map(p => [p.lng, p.lat]); ring.push(ring[0]);  // close the GeoJSON ring
    const body = {
      farm_name: st.farmName.trim(), crop_type: st.cropSel,
      owner_id: (user && user.email) || 'intel_showcase', source_app: 'intel_showcase', source_kind: 'SH',
      geometry: { type: 'Polygon', coordinates: [ring] },
      idempotency_key: 'intel_' + st.farmName.trim().replace(/\s+/g, '_').toLowerCase() + '_' + pts.length + '_' + Math.round(pts[0].lat * 1e4) + '_' + Math.round(pts[0].lng * 1e4)
    };
    set({ saveBusy: true, saveMsg: 'Registering ' + st.farmName.trim() + '…' });
    user.getIdToken()
      .then(tok => fetch(REGISTER_FARM_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + tok }, body: JSON.stringify(body) }))
      .then(r => r.json().then(j => ({ ok: r.ok, j })))
      .then(({ ok, j }) => {
        if (ok && j && j.farm_id) set({ saveBusy: false, saveMsg: '✓ Registered ' + (j.display_name || j.farm_id) + ' · ' + pts.length + ' corners. Pixelation + 24-month backfill queued.', farmName: '', addPts: [], addSaved: (st.addSaved || 0) + 1 });
        else set({ saveBusy: false, saveMsg: 'Registration failed: ' + ((j && (j.error || j.message)) || 'unexpected response') });
      })
      .catch(e => set({ saveBusy: false, saveMsg: 'Registration failed: ' + String(e) }));
  };
  const togglePlay = () => set(s0 => ({ playing: !s0.playing }));
  const cycleSpeed = () => { const sp = [0.5, 1, 2, 4]; set(s0 => ({ speed: sp[(sp.indexOf(s0.speed) + 1) % sp.length] })); };
  const toggleLoop = () => set(s0 => ({ loop: !s0.loop }));
  const onScrub = e => { const v = +e.target.value; set({ frame: v, playing: false }); };
  const toggleOverlay = k => set(s0 => ({ overlays: Object.assign({}, s0.overlays, { [k]: !s0.overlays[k] }) }));
  const setLab = id => set({ lab: id });
  const toggleModule = id => { if (id === 'overview' || id === 'manage') return; set(s0 => { const en = Object.assign({}, s0.enabled, { [id]: !s0.enabled[id] }); let active = s0.active; if (active === id && !en[id]) active = 'overview'; return { enabled: en, active }; }); };
  const setSciView = v => set({ sciView: v });
  const toggleCompare = () => set(s0 => ({ compare: !s0.compare }));
  const toggleCmpVar = k => set(s0 => ({ cmpVars: Object.assign({}, s0.cmpVars, { [k]: !s0.cmpVars[k] }) }));

  // ---- theme root
  const t = THEMES[st.theme] || THEMES.dark;
  const rootStyle = Object.assign(
    { '--bg': t.bg, '--bg2': t.bg2, '--panel': t.panel, '--panel2': t.panel2, '--inset': t.inset, '--ink': t.ink, '--ink2': t.ink2, '--ink3': t.ink3, '--line': t.line, '--lineSoft': t.lineSoft, '--accent': t.accent, '--accentDeep': t.accentDeep, '--accentSoft': t.accentSoft, '--good': t.good, '--goodSoft': t.goodSoft, '--warn': t.warn, '--warnSoft': t.warnSoft, '--bad': t.bad, '--badSoft': t.badSoft, '--note': t.note, '--noteSoft': t.noteSoft, '--grid': t.grid },
    { background: 'var(--bg)', color: 'var(--ink)', fontFamily: "'Familjen Grotesk',system-ui,sans-serif", height: '100vh', display: 'flex', overflow: 'hidden' }
  );

  const modules = MODULES.filter(m => st.enabled[m.id]).map(m => {
    const active = m.id === st.active; const allowed = m.roles.includes(st.role);
    return { id: m.id, short: m.short, icon: m.icon, active, lock: !allowed, onClick: () => go(m.id), iconColor: active ? 'var(--accent)' : (allowed ? 'var(--ink2)' : 'var(--ink3)'), labelColor: active ? 'var(--ink)' : (allowed ? 'var(--ink2)' : 'var(--ink3)'), bg: active ? 'var(--accentSoft)' : 'transparent', opacity: allowed ? '1' : '.4', cursor: allowed ? 'pointer' : 'not-allowed' };
  });

  const L = live;                                  // live API payload or null
  const tot = L && L.totals, ml = L && L.m_levels, cb = L && L.crop_conf_buckets;
  const NF = (tot && tot.farms) || 89;

  // ---- OVERVIEW (live where possible)
  const ladderMeta = [
    { id: 'M0', name: 'Registered', blurb: 'Polygon pixelated · 24-month backfill pulled', advance: 'needs crop-ID label', tone: 'ink3' },
    { id: 'M1', name: 'Crop identified', blurb: 'Crop confirmed by label or vision-AI', advance: 'needs seasonal baseline', tone: 'note' },
    { id: 'M2', name: 'Baselined', blurb: 'Indices + self seasonal baseline established', advance: 'needs harvest-window measure', tone: 'note' },
    { id: 'M3', name: 'Phenology', blurb: 'Harvest window & growth stage measured', advance: 'needs ground-truth calibration', tone: 'warn' },
    { id: 'M4', name: 'Calibrated', blurb: 'Walk/calibration captures linked to pixels', advance: 'needs model validation', tone: 'accent' },
    { id: 'M5', name: 'Validated', blurb: 'Model agrees with reality · loop closed', advance: 'trusted intelligence', tone: 'good' }
  ];
  const ladderCounts = ml ? [ml.M0, ml.M1, ml.M2, ml.M3, ml.M4, ml.M5] : [57, 0, 28, 4, 0, 0];
  const ladderMax = Math.max.apply(null, ladderCounts.concat([1]));
  const ladder = ladderMeta.map((r, i) => ({ ...r, count: ladderCounts[i] || 0, toneColor: tv(r.tone), barW: Math.round((ladderCounts[i] || 0) / ladderMax * 100) + '%' }));

  const m3plus = tot ? tot.m3plus : 4;
  const valColor = tone => tone === 'bad' ? 'var(--bad)' : tone === 'warn' ? 'var(--warn)' : tone === 'good' ? 'var(--good)' : 'var(--ink)';
  const kpisRaw = [
    { label: 'Farms onboarded', value: String(NF), sub: ((tot && tot.counties) || 7) + ' counties', icon: 'agriculture', tone: 'ink' },
    { label: 'Measured intel (M3+)', value: String(m3plus), sub: (Math.round(1000 * m3plus / NF) / 10) + '% of the portfolio', icon: 'trending_up', tone: 'warn' },
    { label: 'Crop-ID coverage', value: (tot ? tot.crop_id_pct : 36) + '%', sub: 'avg conf ' + (tot && tot.crop_id_conf != null ? tot.crop_id_conf : '0.57'), icon: 'label', tone: (tot ? tot.crop_id_pct : 36) >= 50 ? 'good' : 'warn' },
    { label: 'One label from M2', value: String(tot ? tot.unlock_ready : 56), sub: 'unlock-ready backlog', icon: 'lock_open', tone: 'note' }
  ];
  const kpis = kpisRaw.map(k => ({ ...k, iconColor: tv(k.tone), valColor: valColor(k.tone) }));

  const cbCounts = cb ? [cb.high, cb.med, cb.low, cb.unlabelled] : [9, 21, 12, 47];
  const confidence = [
    { label: 'High ≥80%', tone: 'good' }, { label: 'Medium 50–79%', tone: 'warn' },
    { label: 'Low <50%', tone: 'bad' }, { label: 'Unlabelled', tone: 'ink3' }
  ].map((c, i) => ({ ...c, count: cbCounts[i] || 0, toneColor: tv(c.tone), barW: Math.round((cbCounts[i] || 0) / NF * 100) + '%' }));

  const cntFallback = [
    { county: 'Makueni', n_farms: 24, dominant_crop: 'Mango', m2_coverage: .82, n_red: 0, n_amber: 4 },
    { county: 'Kilifi', n_farms: 16, dominant_crop: 'Cashew', m2_coverage: .1, n_red: 1, n_amber: 4 },
    { county: 'Muranga', n_farms: 12, dominant_crop: 'Macadamia', m2_coverage: .5, n_red: 0, n_amber: 1 },
    { county: 'UasinGishu', n_farms: 11, dominant_crop: 'Maize', m2_coverage: .15, n_red: 0, n_amber: 2 },
    { county: 'Machakos', n_farms: 9, dominant_crop: 'Mixed', m2_coverage: 0, n_red: 0, n_amber: 2 },
    { county: 'Kiambu', n_farms: 9, dominant_crop: 'Coffee', m2_coverage: 0, n_red: 0, n_amber: 1 },
    { county: 'Kirinyaga', n_farms: 8, dominant_crop: 'Tea', m2_coverage: 0, n_red: 0, n_amber: 1 }
  ];
  const cntRaw = (L && L.counties && L.counties.length) ? L.counties : cntFallback;
  const sc = v => v >= 0.25 ? 'var(--bad)' : v >= 0.15 ? 'var(--warn)' : 'var(--good)';
  const counties = cntRaw.map(c => {
    const stress = c.n_farms ? ((c.n_red || 0) + (c.n_amber || 0)) / c.n_farms : 0;
    const cov = Math.round((c.m2_coverage || 0) * 100);
    return { name: c.county, farms: c.n_farms, crop: c.dominant_crop || '—', stressW: Math.round(stress * 100) + '%', stressColor: sc(stress), m: cov + '% M2', mColor: cov >= 50 ? 'var(--good)' : cov > 0 ? 'var(--note)' : 'var(--ink3)' };
  });

  const activity = [
    { tone: 'good', icon: 'arrow_upward', text: 'Makueni-Mango-007 advanced M2 → M3', time: '2h' },
    { tone: 'note', icon: 'science', text: 'WSI42 swept — B11 carrier confirmed', time: '5h' },
    { tone: 'warn', icon: 'warning', text: '14 farms still reading the US plane (empty in app)', time: '1d' },
    { tone: 'accent', icon: 'photo_camera', text: '12 walk captures synced — 0 carried structured labels', time: '1d' },
    { tone: 'bad', icon: 'priority_high', text: 'Macadamia-001 flagged off-pattern (30% conf)', time: '2d' }
  ].map(a => ({ ...a, toneColor: tv(a.tone) }));
  const funnel = [{ label: 'flagged', v: 23, tone: 'bad' }, { label: 'queued', v: 11, tone: 'warn' }, { label: 'visited', v: 3, tone: 'note' }, { label: 'labelled', v: 2, tone: 'good' }].map(f => ({ ...f, toneColor: tv(f.tone), h: Math.round(f.v / 23 * 46) + 10 + 'px' }));

  // ---- SCIENCE geometry (LIVE series when loaded, else synthetic fallback)
  const arr = liveArr || SERIES, n = arr.length, W = 1000, H = 240, pL = 8, pR = 8, pT = 12, pB = 24;
  const isLiveSci = !!liveArr;
  const xAt = i => pL + (i / (n - 1)) * (W - pL - pR);
  const yAt = v => pT + (1 - v) * (H - pT - pB);
  const maxRain = Math.max.apply(null, arr.map(p => p.rain)) || 300;
  const ndviPoints = arr.map((p, i) => xAt(i).toFixed(1) + ',' + yAt(p.ndvi).toFixed(1)).join(' ');
  const baselinePoints = arr.map((p, i) => xAt(i).toFixed(1) + ',' + yAt(p.baseline).toFixed(1)).join(' ');
  // SC-3 NDMI series line (live only, when the NDMI overlay is on)
  const ndmiPoints = (isLiveSci && st.overlays.ndmi) ? arr.filter(p => p.ndmi != null).map((p) => { const i = arr.indexOf(p); return xAt(i).toFixed(1) + ',' + yAt(Math.max(0, Math.min(1, p.ndmi))).toFixed(1); }).join(' ') : '';
  // SC-1 overlay stack drives the FieldMap: index priority NDRE>NDMI>NDVI; true-colour swaps basemap; GT pins
  // True-colour ON = show the raw satellite imagery with NO index heatmap over it
  const sciIndex = st.overlays.truecolor ? null : (st.overlays.radar ? 'vv' : st.overlays.ndre ? 'ndre' : st.overlays.ndmi ? 'ndmi' : st.overlays.ndvi ? 'ndvi' : null);
  // always label-free imagery: 'hybrid' draws Google's POI/road labels over the spectral
  // cells (the "Chemist" collision) and styles arrays can't suppress labels on hybrid
  const sciBasemap = 'satellite';
  const sciGT = (st.overlays.gt && fieldData && fieldData.field && fieldData.field.lat != null && Array.isArray(fieldData.ground_truth) && fieldData.ground_truth.length) ? [{ lat: fieldData.field.lat, lng: fieldData.field.lng }] : null;
  const bw = (W - pL - pR) / n * 0.5;
  const rainBars = arr.map((p, i) => { const h = (p.rain / maxRain) * (H - pT - pB) * 0.5; return { x: (xAt(i) - bw / 2).toFixed(1), y: (H - pB - h).toFixed(1), w: bw.toFixed(1), h: h.toFixed(1) }; });
  const f = Math.min(st.frame, n - 1), cur = arr[f] || arr[n - 1] || { ndvi: 0.5, baseline: 0.5, rain: 0 }, cursorX = xAt(f).toFixed(1);
  const liveCells = (fieldData && Array.isArray(fieldData.cells) && fieldData.cells.length) ? fieldData.cells : null;
  const fieldCols = 12, fieldRows = 7, fieldCells = [];
  for (let i = 0; i < fieldCols * fieldRows; i++) {
    // real per-pixel NDVI sampled into the grid when live, else synthetic jitter around the frame mean
    const v = liveCells ? liveCells[i % liveCells.length].ndvi : Math.max(0.06, Math.min(0.96, cur.ndvi + CELLOFF[i]));
    fieldCells.push({ color: ramp(v) });
  }

  const overlayMeta = { truecolor: { label: 'True-colour', tone: 'ink2' }, ndvi: { label: 'NDVI — vigour', tone: 'good' }, ndmi: { label: 'NDMI — moisture', tone: 'note' }, ndre: { label: 'NDRE — nitrogen', tone: 'accent' }, radar: { label: 'Radar VV (S1)', tone: 'ink2' }, rain: { label: 'Rainfall (CHIRPS)', tone: 'note' }, gt: { label: 'Ground-truth pts', tone: 'good' } };
  const overlays = Object.keys(overlayMeta).map(k => { const on = !!st.overlays[k]; const dot = tv(overlayMeta[k].tone); return { key: k, label: overlayMeta[k].label, on, dot, check: on ? 'check' : '', boxColor: on ? dot : 'var(--line)', boxBg: on ? dot : 'transparent', onToggle: () => toggleOverlay(k) }; });

  const sv = st.sciView;
  const viewTabs = [{ id: 'map', label: 'Map' }, { id: 'spectral', label: 'Spectral' }, { id: 'gt', label: 'Ground truth' }].map(v => ({ label: v.label, onClick: () => setSciView(v.id), bg: sv === v.id ? 'var(--accent)' : 'transparent', color: sv === v.id ? '#0B0D11' : 'var(--ink2)' }));
  const mapOnly = sv === 'map', showSpectral = sv === 'spectral', showGT = sv === 'gt';
  const sciGrid = mapOnly ? '1fr' : '1fr 1fr';

  // NDVI-derived season position (per-field, non-static). The old strip was a fixed fruit
  // calendar keyed only on month-of-year - same for every field. Per-crop phenophase is
  // Mariia's parked model; here we place the field within its OWN NDVI cycle from live data.
  const PHASES = [{ n: 'Establishing', g: '🌱' }, { n: 'Greening', g: '🌿' }, { n: 'Peak canopy', g: '🟢' }, { n: 'Senescing', g: '🍂' }];
  let phaseIdx = 2, phaseRel = 0.5, phaseSlope = 0;
  if (isLiveSci && arr.length > 3) {
    const vals = arr.map(p => p.ndvi).filter(v => v != null);
    const mn = Math.min.apply(null, vals), mx = Math.max.apply(null, vals);
    phaseRel = (mx > mn) ? (cur.ndvi - mn) / (mx - mn) : 0.5;
    const prev = arr[Math.max(0, f - 2)];
    phaseSlope = (prev && prev !== cur && prev.ndvi != null) ? cur.ndvi - prev.ndvi : 0;
    if (phaseRel < 0.35 && phaseSlope >= -0.01) phaseIdx = 0;
    else if (phaseSlope > 0.02) phaseIdx = 1;
    else if (phaseRel >= 0.72) phaseIdx = 2;
    else if (phaseSlope < -0.02) phaseIdx = 3;
    else phaseIdx = phaseRel >= 0.6 ? 2 : 1;
  }
  const growthLineW = Math.round((phaseIdx / 3) * 82 + 6) + '%';
  const phaseRelPct = Math.round(phaseRel * 100);
  const stages = PHASES.map((sg, i) => { const stt = i < phaseIdx ? 'done' : (i === phaseIdx ? 'active' : 'todo'); return { name: sg.n, emoji: sg.g, range: '', nodeBg: stt === 'done' ? 'var(--good)' : (stt === 'active' ? 'var(--accent)' : 'var(--inset)'), nodeBorder: stt === 'active' ? 'var(--accent)' : (stt === 'done' ? 'var(--good)' : 'var(--line)'), nameColor: stt === 'todo' ? 'var(--ink3)' : 'var(--ink)', emojiOp: stt === 'todo' ? '0.4' : '1', pulse: stt === 'active' ? 'blink 2s infinite' : 'none' }; });
  // Structural readouts - real indices only. NO soil moisture (not measured, P001); NDMI is the moisture proxy.
  const lai = (cur.ndvi * 4.2).toFixed(1);
  const ndmiV = (cur.ndmi != null) ? cur.ndmi : null;
  const ndreV = (cur.ndre != null) ? cur.ndre : null;
  const norm01 = (v, lo, hi) => Math.max(0, Math.min(1, (v - lo) / (hi - lo)));
  const structural = [
    { label: 'Vigour · NDVI', val: cur.ndvi.toFixed(2), tone: cur.ndvi > 0.55 ? 'good' : cur.ndvi > 0.4 ? 'warn' : 'bad', pct: norm01(cur.ndvi, 0.2, 0.85) * 100, verdict: cur.ndvi > 0.55 ? 'healthy' : cur.ndvi > 0.4 ? 'watch' : 'low' },
    { label: 'Moisture · NDMI', val: ndmiV != null ? ndmiV.toFixed(2) : '—', tone: ndmiV == null ? 'ink3' : ndmiV > 0.30 ? 'good' : ndmiV > 0.15 ? 'warn' : 'bad', pct: ndmiV != null ? norm01(ndmiV, 0, 0.5) * 100 : 0, verdict: ndmiV == null ? '—' : ndmiV > 0.30 ? 'adequate' : ndmiV > 0.15 ? 'drying' : 'low' },
    { label: 'Nitrogen · NDRE', val: ndreV != null ? ndreV.toFixed(2) : '—', tone: ndreV == null ? 'ink3' : ndreV > 0.35 ? 'good' : ndreV > 0.20 ? 'warn' : 'note', pct: ndreV != null ? norm01(ndreV, 0, 0.5) * 100 : 0, verdict: ndreV == null ? '—' : ndreV > 0.35 ? 'good' : ndreV > 0.20 ? 'fair' : 'low' },
    { label: 'Canopy · LAI (est)', val: lai, tone: cur.ndvi > 0.55 ? 'good' : cur.ndvi > 0.4 ? 'warn' : 'bad', pct: norm01(+lai, 0, 5) * 100, verdict: 'est' }
  ].map(sg => ({ label: sg.label, val: sg.val, valColor: tv(sg.tone), pct: Math.round(sg.pct), verdict: sg.verdict }));

  // --- REAL derived signal vs the field's OWN seasonal baseline (live only). No cohort
  //     model, no per-band attribution - we only claim what the series supports. ---
  let devZ = null, ndmiDelta = null;
  if (isLiveSci && arr.length > 4) {
    const devs = arr.map(p => (p.ndvi != null && p.baseline != null) ? p.ndvi - p.baseline : null).filter(v => v != null);
    if (devs.length > 3) {
      const mean = devs.reduce((a, b) => a + b, 0) / devs.length;
      const sd = Math.sqrt(devs.reduce((a, b) => a + (b - mean) * (b - mean), 0) / devs.length) || 1e-6;
      const curDev = (cur.ndvi != null && cur.baseline != null) ? cur.ndvi - cur.baseline : 0;
      devZ = (curDev - mean) / sd;
    }
    const prev = arr[Math.max(0, f - 1)];
    if (cur.ndmi != null && prev && prev !== cur && prev.ndmi != null) ndmiDelta = cur.ndmi - prev.ndmi;
  }
  const devZlabel = devZ == null ? null : (devZ <= -1.5 ? 'below baseline' : (devZ >= 1.5 ? 'above baseline' : 'near baseline'));
  const devZtone = devZ == null ? 'ink3' : (devZ <= -1.5 ? 'bad' : (devZ >= 1.5 ? 'good' : 'warn'));

  // cropRead - NDVI-vs-own-baseline / phase fact only; NO fabricated SWIR/band causal claim (P001)
  let cropRead;
  if (isLiveSci && devZlabel) cropRead = 'NDVI is ' + devZlabel + ' for this field’s own seasonal norm (' + (devZ >= 0 ? '+' : '') + devZ.toFixed(1) + 'σ).';
  else if (cur.ndvi < 0.4) cropRead = 'Low canopy this acquisition — ' + PHASES[phaseIdx].n.toLowerCase() + '.';
  else cropRead = PHASES[phaseIdx].n + ' — NDVI ' + cur.ndvi.toFixed(2) + ' of this field’s own range.';

  // SC-6 compare = a real second field's NDVI trajectory (not a fabricated benchmark)
  const showCompare = !!(cmpData && Array.isArray(cmpData.series) && cmpData.series.length > 1);
  const cmpVar = st.cmpVar || 'ndvi';
  let comparePoints = '';
  if (showCompare) {
    const cs = cmpData.series.filter(p => p[cmpVar] != null);
    const m = cs.length;
    comparePoints = cs.map((p, j) => (pL + (m > 1 ? j / (m - 1) : 0) * (W - pL - pR)).toFixed(1) + ',' + yAt(Math.max(0, Math.min(1, p[cmpVar]))).toFixed(1)).join(' ');
  }
  const cmpName = cmpData && cmpData.field ? cmpData.field.name : null;
  const cmpFields = (fieldData && Array.isArray(fieldData.fields)) ? fieldData.fields.filter(o => o.id !== fieldId) : [];
  // single-select compare variable (drives the compare overlay line) - GDD dropped (no per-date GDD in the series)
  const cmpVars = [{ k: 'ndvi', label: 'NDVI' }, { k: 'ndmi', label: 'NDMI' }, { k: 'ndre', label: 'NDRE' }].map(v => { const on = cmpVar === v.k; return { label: v.label, onClick: () => set({ cmpVar: v.k }), bg: on ? 'var(--accent)' : 'transparent', col: on ? '#0B0D11' : 'var(--ink2)', bd: on ? 'var(--accent)' : 'var(--line)' }; });
  // GT cards - REAL captures for the field (walk + calibration). No fabricated severity/pixel -
  // those columns are NULL fleet-wide; we show only label + date + view/kind. Empty for most fields.
  const gtCards = (isLiveSci && Array.isArray(fieldData.ground_truth))
    ? fieldData.ground_truth.map(g => ({ label: String(g.label || 'capture').replace(/_/g, ' '), date: g.d, view: String(g.view || g.kind || '').replace(/_/g, ' '), toneColor: tv('note'), tint: 'rgba(120,140,160,.14)' }))
    : [];

  // ---- INSIGHTS
  const labTabs = [{ id: 'zones', label: 'Zones' }, { id: 'stress', label: 'Stress assessment' }, { id: 'hypo', label: 'Hypothesis tester' }, { id: 'anomalies', label: 'Anomalies' }, { id: 'drivers', label: 'Drivers' }, { id: 'dispatch', label: 'Dispatch queue' }].map(l => ({ label: l.label, onClick: () => setLab(l.id), color: st.lab === l.id ? 'var(--accent)' : 'var(--ink2)', under: st.lab === l.id ? 'var(--accent)' : 'transparent' }));
  // Zones - LIVE NDVI-tercile stats from get_intel_field when loaded, else synthetic.
  const zAction = { A: 'Hold — this block is tracking on vigour.', B: 'Watch — mid-vigour; check the NDMI trend before it shows in NDVI.', C: 'Investigate — under-performing block; confirm cause (water vs nutrient).' };
  const liveZones = (fieldData && Array.isArray(fieldData.zones) && fieldData.zones.length) ? fieldData.zones : null;
  const zones = (liveZones
    ? liveZones.map(z => ({ id: z.id, label: z.label, ndvi: (z.ndvi != null ? z.ndvi.toFixed(2) : '—'), area: (z.area_ha != null ? z.area_ha + ' ha' : '—'), stress: (z.stress_pct != null ? z.stress_pct + '%' : '—'), tone: z.tone, action: zAction[z.id] || '' }))
    : [
      { id: 'A', label: 'Zone A · vigorous', ndvi: '0.78', area: '1.1 ha', stress: '4%', tone: 'good', action: zAction.A },
      { id: 'B', label: 'Zone B · mid / watch', ndvi: '0.52', area: '0.6 ha', stress: '31%', tone: 'warn', action: zAction.B },
      { id: 'C', label: 'Zone C · under-performing', ndvi: '0.34', area: '0.3 ha', stress: '58%', tone: 'bad', action: zAction.C }
    ]
  ).map(z => ({ ...z, toneColor: tv(z.tone) }));
  const zoneCols = 12, zoneRows = 7, zoneCells = [];
  for (let i = 0; i < zoneCols * zoneRows; i++) {
    // real per-pixel NDVI pattern when live, else synthetic
    const v = liveCells ? liveCells[i % liveCells.length].ndvi : Math.max(0.06, Math.min(0.96, [0.78, 0.52, 0.34][i % 3] + CELLOFF[i] * 0.5));
    zoneCells.push({ color: ramp(v), ring: '1px', ringColor: 'rgba(0,0,0,.18)' });
  }

  // IN-1 Anomalies - rank the field's spots by NDVI deviation below the field mean (LIVE cells, client-side)
  let anomAll = [], anomMean = null, anomSd = null;
  if (liveCells) {
    const vs = liveCells.map(c => c.ndvi).filter(v => v != null);
    if (vs.length > 3) {
      anomMean = vs.reduce((a, b) => a + b, 0) / vs.length;
      anomSd = Math.sqrt(vs.reduce((a, b) => a + (b - anomMean) * (b - anomMean), 0) / vs.length) || 1e-6;
      anomAll = liveCells.filter(c => c.ndvi != null).map(c => ({ pixel_id: c.pixel_id, lat: c.lat, lng: c.lng, ndvi: c.ndvi, ndmi: c.ndmi, z: (c.ndvi - anomMean) / anomSd })).sort((a, b) => a.z - b.z);
    }
  }
  const anomThresh = st.anomThresh || 1.0;
  const anomalies = anomAll.filter(c => c.z <= -anomThresh);

  // IN-3 Drivers - fleet rule_v1 distribution (LIVE from payload)
  const DRV_LABEL = { healthy: 'Healthy', veg_drop: 'Vegetation drop', water_deficit: 'Water deficit', monitoring: 'Monitoring', insufficient_data: 'Awaiting data' };
  const DRV_TONE = { healthy: 'good', veg_drop: 'bad', water_deficit: 'warn', monitoring: 'warn', insufficient_data: 'ink3' };
  const driverDist = (L && Array.isArray(L.driver_dist) && L.driver_dist.length)
    ? L.driver_dist.map(d => ({ key: d.driver, label: DRV_LABEL[d.driver] || d.driver, n: d.n || 0, conf: d.avg_conf, tone: DRV_TONE[d.driver] || 'ink2' }))
    : null;
  const driverTotal = driverDist ? driverDist.reduce((a, d) => a + d.n, 0) : 0;

  // IN-4 this field's rule_v1 driver (LIVE)
  const fdrv = fieldData && fieldData.field_driver;

  const hb = st.hypoBaseline, hi = st.hypoIndex; let hypoVerdict, hypoTone, hypoSub;
  if (hi === 'wsi42' && hb === 'cohort') { hypoVerdict = 'STRESS · high'; hypoTone = 'var(--bad)'; hypoSub = '+2.0σ vs the crop cohort · carrier B11 (SWIR) leads NDVI.'; }
  else if (hi === 'wsi42' && hb === 'self') { hypoVerdict = 'STRESS · moderate'; hypoTone = 'var(--warn)'; hypoSub = '+1.3σ vs the field’s own seasonal prior for this day-of-year.'; }
  else if (hi === 'ndvi' && hb === 'cohort') { hypoVerdict = 'WATCH · low'; hypoTone = 'var(--warn)'; hypoSub = 'NDVI still sits inside the cohort band — vigour is masking the moisture drop.'; }
  else { hypoVerdict = 'NORMAL'; hypoTone = 'var(--good)'; hypoSub = 'NDVI is near the field’s own baseline for this day-of-year.'; }
  const segB = active => ({ border: active ? 'var(--accent)' : 'var(--line)', bg: active ? 'var(--accentSoft)' : 'var(--inset)' });
  const bSelf = segB(hb === 'self'), bCoh = segB(hb === 'cohort'), iN = segB(hi === 'ndvi'), iW = segB(hi === 'wsi42');
  const otherLab = { anomalies: { icon: 'scatter_plot', title: 'Anomalies', body: 'Ranks this field’s most-deviating spots (anomaly × low NDVI) into a visit list with coordinates — coming next.' }, drivers: { icon: 'category', title: 'Drivers (labelled)', body: 'Distribution of confirmed stressors — water / nutrient / pest / disease — from ground-truth labels. Builds as labels arrive.' }, dispatch: { icon: 'send', title: 'Dispatch queue', body: 'Most-deviating spots become a coordinate visit list to hand an agronomist. Wires onto the same spot-task backend.' } };
  const ol = otherLab[st.lab] || otherLab.anomalies;

  // ---- OPERATIONS rows (live)
  const opFallback = [
    { farm_name: 'Makueni-Mango-007', county: 'Makueni', crop: 'Mango', m_level: 3, cropid_conf: .85, m_bottleneck: 'needs ground-truth calibration' },
    { farm_name: 'EST_Kibwezi_017', county: 'Kibwezi', crop: 'Mango', m_level: 2, cropid_conf: .5, m_bottleneck: 'needs harvest-window measure' },
    { farm_name: 'Avc Saina farm', county: 'UasinGishu', crop: 'Avocado', m_level: 2, cropid_conf: .9, m_bottleneck: 'needs harvest-window measure' },
    { farm_name: 'Kilifi-Galana-01', county: 'Kilifi', crop: 'Cashew', m_level: 0, cropid_conf: .25, m_bottleneck: 'needs crop-ID label' }
  ];
  const opSrc = (L && L.farms && L.farms.length) ? L.farms : opFallback;
  const stDot = s0 => s0 === 'red' ? 'var(--bad)' : s0 === 'amber' ? 'var(--warn)' : s0 === 'green' ? 'var(--good)' : 'var(--ink3)';
  function opIssue(r) {
    if (!r.status || r.status === 'grey') return 'Awaiting clear observations';
    if (r.status === 'green') return 'On track (rule_v1)';
    const parts = [];
    if (r.driver) parts.push(DRV_LABEL[r.driver] || r.driver);
    if (r.ndvi_delta != null) parts.push('NDVI ' + (r.ndvi_delta >= 0 ? '+' : '') + (r.ndvi_delta * 100).toFixed(0) + '%');
    if (r.latest_ndmi != null) parts.push('NDMI ' + Number(r.latest_ndmi).toFixed(2));
    return parts.join(' · ') || (DRV_LABEL[r.driver] || 'Flagged');
  }
  const opAll = opSrc.map(r => {
    const lvl = 'M' + (r.m_level == null ? 0 : r.m_level);
    const conf = r.cropid_conf != null ? Math.round(r.cropid_conf * 100) + '%' : '—';
    const ct = r.cropid_conf == null ? 'ink3' : r.cropid_conf >= 0.8 ? 'good' : r.cropid_conf >= 0.5 ? 'warn' : 'bad';
    const status = r.status || null;
    return { farm_id: r.farm_id, farm: r.farm_name || '—', county: r.county || '—', crop: r.crop || '—', lat: r.lat, lng: r.lng, status, dot: stDot(status), issue: opIssue(r), asOf: r.driver_as_of, lvl, lvlColor: mc(lvl), conf, confColor: tv(ct), next: r.m_bottleneck || '—', rank: status === 'red' ? 0 : status === 'amber' ? 1 : status === 'green' ? 2 : 3 };
  });
  const opCounts = { red: opAll.filter(r => r.status === 'red').length, amber: opAll.filter(r => r.status === 'amber').length, green: opAll.filter(r => r.status === 'green').length };
  const opQ = (st.opQ || '').toLowerCase();
  const opRows = opAll.filter(r => (st.opStatus === 'all' || r.status === st.opStatus) && (!opQ || (r.farm + ' ' + r.county + ' ' + r.crop).toLowerCase().includes(opQ)));
  const opAttention = opAll.filter(r => r.status === 'red' || r.status === 'amber').sort((a, b) => a.rank - b.rank).slice(0, 6);
  const opStatusTabs = [['all', 'All', opAll.length], ['red', 'Urgent', opCounts.red], ['amber', 'Watch', opCounts.amber], ['green', 'Good', opCounts.green]].map(x => ({ id: x[0], label: x[1], n: x[2], on: st.opStatus === x[0], color: x[0] === 'red' ? 'var(--bad)' : x[0] === 'amber' ? 'var(--warn)' : x[0] === 'green' ? 'var(--good)' : 'var(--ink)' }));

  const manageModules = MODULES.map(m => { const fixed = (m.id === 'overview' || m.id === 'manage'); const en = !!st.enabled[m.id]; return { id: m.id, label: m.label, icon: m.icon, roles: m.roles.join(' · '), fixed, toggleable: !fixed, onToggle: () => toggleModule(m.id), swBg: en ? 'var(--accent)' : 'var(--line)', swX: en ? '23px' : '3px' }; });

  const isOverview = st.active === 'overview', isScience = st.active === 'science', isInsights = st.active === 'insights', isOperations = st.active === 'operations', isManage = st.active === 'manage';
  const liveTag = L ? ('live · as of ' + (L.as_of || '—')) : 'syncing live data…';

  return (
    <div style={rootStyle}>
      {/* ===== RAIL ===== */}
      <aside style={s("width:90px;flex:0 0 90px;background:var(--bg2);border-right:1px solid var(--line);display:flex;flex-direction:column;align-items:center;padding:16px 8px;gap:4px")}>
        <div style={s("display:flex;flex-direction:column;align-items:center;gap:7px;margin-bottom:12px")}>
          <svg width="30" height="28" viewBox="0 0 30 28" fill="none">
            <polygon points="7,3 19,3 15,9 3,9" fill="var(--ink)" />
            <polygon points="7,11 19,11 15,17 3,17" fill="var(--warn)" />
            <polygon points="7,19 19,19 15,25 3,25" fill="var(--accent)" />
            <rect x="23" y="3" width="4" height="22" rx="1" fill="var(--accent)" />
          </svg>
          <div style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:1.4px;color:var(--ink3);text-align:center;line-height:1.3")}>RADA<br />INTEL</div>
        </div>
        {modules.map((m, i) => (
          <button key={i} onClick={m.onClick} style={merge("width:100%;border:none;border-radius:12px;padding:10px 4px 9px;display:flex;flex-direction:column;align-items:center;gap:5px;position:relative;transition:background .15s", { background: m.bg, opacity: m.opacity, cursor: m.cursor })}>
            <span className="ms" style={merge("font-size:23px", { color: m.iconColor })}>{m.icon}</span>
            <span style={merge("font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:.3px;text-align:center;line-height:1.15", { color: m.labelColor })}>{m.short}</span>
            {m.lock && <span className="ms" style={s("position:absolute;top:7px;right:12px;font-size:11px;color:var(--ink3)")}>lock</span>}
            {m.active && <span style={s("position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:0 3px 3px 0;background:var(--accent)")} />}
          </button>
        ))}
        <div style={s("flex:1")} />
        <button onClick={toggleTheme} title="Toggle theme" style={s("width:46px;height:46px;border:1px solid var(--line);background:var(--panel);border-radius:13px;cursor:pointer;display:flex;align-items:center;justify-content:center")}>
          <span className="ms" style={s("font-size:20px;color:var(--ink2)")}>{st.theme === 'dark' ? 'light_mode' : 'dark_mode'}</span>
        </button>
        <button onClick={onSignOut} title="Sign out" style={s("width:40px;height:40px;border-radius:50%;background:var(--accentSoft);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;margin-top:6px;font-weight:700;color:var(--accent);font-size:14px;cursor:pointer")}>{(user && user.email ? user.email[0] : 'G').toUpperCase()}</button>
      </aside>

      {/* ===== MAIN COLUMN ===== */}
      <div style={s("flex:1;display:flex;flex-direction:column;min-width:0")}>
        <header style={s("height:66px;flex:0 0 66px;border-bottom:1px solid var(--line);background:var(--bg2);display:flex;align-items:center;padding:0 24px;gap:18px")}>
          <div style={s("display:flex;flex-direction:column;gap:2px;min-width:0")}>
            <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:1.4px;text-transform:uppercase;color:var(--ink3)")}>RADA INTELLIGENCE</div>
            <div style={s("font-size:18px;font-weight:600;letter-spacing:-.3px")}>{TITLES[st.active] || ''}</div>
          </div>
          <div style={s("flex:1")} />
          <div title={L ? 'data is live from the warehouse' : 'fetching the live portfolio…'} style={s("display:flex;align-items:center;gap:7px;font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--ink3)")}>
            <span style={merge("width:8px;height:8px;border-radius:50%", { background: L ? 'var(--good)' : 'var(--warn)', animation: L ? 'none' : 'blink 1.4s infinite' })} />{liveTag}
          </div>
          <div style={s("display:flex;align-items:center;gap:9px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:7px 8px 7px 12px")}>
            <span style={s("font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3)")}>Viewing as</span>
            <select value={st.role} onChange={e => setRole(e.target.value)} style={s("background:transparent;border:none;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer;outline:none")}>
              {ROLES.map((r, i) => <option key={i} value={r} style={{ color: '#16201E' }}>{r}</option>)}
            </select>
          </div>
        </header>

        <main style={s("flex:1;overflow:auto;min-height:0")}>

          {/* ============ OVERVIEW ============ */}
          {isOverview && (
            <div style={s("padding:26px 30px 70px;max-width:1340px;margin:0 auto;display:flex;flex-direction:column;gap:22px")}>
              <div>
                <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;color:var(--accent)")}>Where we are · intelligence level</div>
                <div style={s("font-size:27px;font-weight:600;letter-spacing:-.5px;margin-top:8px;max-width:840px;line-height:1.25;text-wrap:pretty")}>{NF} farms are seeing the sky. Only <span style={{ color: 'var(--warn)' }}>{m3plus}</span> have intelligence we can trust — and <span style={{ color: 'var(--bad)' }}>none</span> are calibrated against the ground yet.</div>
              </div>

              <div style={s("display:grid;grid-template-columns:repeat(4,1fr);gap:14px")}>
                {kpis.map((k, i) => (
                  <div key={i} style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px")}>
                    <div style={s("display:flex;align-items:flex-start;justify-content:space-between;gap:8px")}>
                      <span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.7px;text-transform:uppercase;color:var(--ink3);line-height:1.3")}>{k.label}</span>
                      <span className="ms" style={merge("font-size:18px", { color: k.iconColor })}>{k.icon}</span>
                    </div>
                    <div style={merge("font-size:31px;font-weight:700;letter-spacing:-.8px;margin-top:10px", { color: k.valColor })}>{k.value}</div>
                    <div style={s("font-size:12px;color:var(--ink2);margin-top:3px;line-height:1.3")}>{k.sub}</div>
                  </div>
                ))}
              </div>

              <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px 22px")}>
                <div style={s("display:flex;align-items:baseline;justify-content:space-between;flex-wrap:wrap;gap:8px")}>
                  <div>
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink3)")}>Intelligence maturity ladder</div>
                    <div style={s("font-size:17px;font-weight:600;margin-top:4px")}>From a polygon on a map to ground-proven intelligence</div>
                  </div>
                  <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink2)")}>each farm climbs M0 → M5</div>
                </div>
                <div style={s("display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:18px")}>
                  {ladder.map((r, i) => (
                    <div key={i} style={s("background:var(--inset);border:1px solid var(--line);border-radius:12px;padding:13px 13px 12px;display:flex;flex-direction:column;gap:8px;min-height:158px")}>
                      <div style={s("display:flex;align-items:center;justify-content:space-between")}>
                        <span style={merge("font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:600", { color: r.toneColor })}>{r.id}</span>
                        <span style={merge("width:8px;height:8px;border-radius:50%", { background: r.toneColor })} />
                      </div>
                      <div style={s("font-size:31px;font-weight:700;letter-spacing:-1px;color:var(--ink)")}>{r.count}</div>
                      <div style={s("height:4px;border-radius:3px;background:var(--line);overflow:hidden")}><div style={merge("height:100%", { width: r.barW, background: r.toneColor })} /></div>
                      <div style={s("font-size:12.5px;font-weight:600;color:var(--ink)")}>{r.name}</div>
                      <div style={s("font-size:10.5px;color:var(--ink3);line-height:1.35;flex:1")}>{r.blurb}</div>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.2px;text-transform:uppercase;color:var(--ink2);line-height:1.3")}>↳ {r.advance}</div>
                    </div>
                  ))}
                </div>
                <div style={s("margin-top:16px;display:flex;gap:11px;align-items:flex-start;background:var(--warnSoft);border:1px solid var(--line);border-radius:10px;padding:13px 15px")}>
                  <span className="ms" style={s("font-size:18px;color:var(--warn);margin-top:1px")}>lightbulb</span>
                  <div style={s("font-size:13px;color:var(--ink);line-height:1.45")}><b>What this means —</b> intelligence is <b>wide but shallow</b>. We can draw stress maps on {(tot ? tot.crop_id_pct : 36)}% of farms, but only {m3plus} have measured phenology and <b>zero are calibrated against ground truth</b>. <b>{tot ? tot.unlock_ready : 56} farms are one crop-ID label away from M2</b> — the whole portfolio is dammed up at labelling.</div>
                </div>
              </div>

              <div style={s("display:grid;grid-template-columns:1.35fr 1fr;gap:18px")}>
                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px 22px;display:flex;flex-direction:column;gap:15px")}>
                  <div style={s("display:flex;align-items:center;gap:10px")}>
                    <span className="ms" style={s("font-size:19px;color:var(--bad)")}>target</span>
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1.1px;text-transform:uppercase;color:var(--ink3)")}>Ground-truth coverage · the crown jewel</div>
                    <div style={s("flex:1")} />{L && L.ground_truth ? <span style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.4px;text-transform:uppercase;color:var(--good);border:1px solid var(--good);border-radius:5px;padding:2px 6px")}>live</span> : <SampleTag text="as audited" />}
                  </div>
                  <div style={s("display:flex;gap:30px;flex-wrap:wrap")}>
                    <div><div style={s("font-size:34px;font-weight:700;letter-spacing:-1px")}>{L && L.ground_truth ? L.ground_truth.photos : 196}</div><div style={s("font-size:11px;color:var(--ink2);margin-top:2px")}>photos captured</div></div>
                    <div><div style={s("font-size:34px;font-weight:700;letter-spacing:-1px;color:var(--bad)")}>{L && L.ground_truth ? L.ground_truth.linked : 0}</div><div style={s("font-size:11px;color:var(--ink2);margin-top:2px")}>linked to a pixel + label</div></div>
                    <div><div style={s("font-size:34px;font-weight:700;letter-spacing:-1px;color:var(--warn)")}>{L && L.ground_truth ? L.ground_truth.farms_with_gt : 4}<span style={s("font-size:15px;color:var(--ink3);font-weight:600")}>/{NF}</span></div><div style={s("font-size:11px;color:var(--ink2);margin-top:2px")}>farms with any ground truth</div></div>
                  </div>
                  <div style={s("height:9px;border-radius:5px;background:var(--inset);overflow:hidden;display:flex")}>
                    <div style={s("width:2%;background:var(--good)")} />
                    <div style={s("flex:1;background:repeating-linear-gradient(45deg,var(--badSoft),var(--badSoft) 6px,transparent 6px,transparent 13px)")} />
                  </div>
                  <div style={s("font-size:13px;color:var(--ink);line-height:1.5")}><b>What this means —</b> the satellite model is still <b>unproven against reality</b>. Every capture must answer <i>where · when · what · how-bad + photo</i> or it never enters training. Closing this gap is the single highest-leverage move on the board.</div>
                </div>

                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px 22px;display:flex;flex-direction:column;gap:14px")}>
                  <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1.1px;text-transform:uppercase;color:var(--ink3)")}>Crop-ID model confidence</div>
                  {confidence.map((c, i) => (
                    <div key={i} style={s("display:flex;flex-direction:column;gap:5px")}>
                      <div style={s("display:flex;justify-content:space-between;font-size:12.5px")}><span style={{ color: 'var(--ink)' }}>{c.label}</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink2)")}>{c.count}</span></div>
                      <div style={s("height:6px;border-radius:4px;background:var(--inset);overflow:hidden")}><div style={merge("height:100%", { width: c.barW, background: c.toneColor })} /></div>
                    </div>
                  ))}
                  <div style={s("font-size:12px;color:var(--ink2);line-height:1.45;margin-top:2px")}>Most farms are unlabelled or low-confidence — the model is <b style={{ color: 'var(--ink)' }}>guessing on the majority</b> until labels arrive.</div>
                </div>
              </div>

              <div style={s("display:grid;grid-template-columns:1.35fr 1fr;gap:18px")}>
                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px 22px")}>
                  <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:14px")}>
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1.1px;text-transform:uppercase;color:var(--ink3)")}>Coverage · {counties.length} counties</div>
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--accent)")}>Makueni leads</div>
                  </div>
                  {counties.map((c, i) => (
                    <div key={i} style={s("display:grid;grid-template-columns:118px 40px 1fr 64px;align-items:center;gap:12px;padding:8px 0;border-top:1px solid var(--lineSoft)")}>
                      <span style={s("font-size:13px;font-weight:600;color:var(--ink)")}>{c.name}</span>
                      <span style={s("font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--ink2)")}>{c.farms}</span>
                      <div style={s("display:flex;align-items:center;gap:8px")}>
                        <div style={s("flex:1;height:6px;border-radius:4px;background:var(--inset);overflow:hidden")}><div style={merge("height:100%", { width: c.stressW, background: c.stressColor })} /></div>
                        <span style={s("font-size:10.5px;color:var(--ink3);width:64px")}>{c.crop}</span>
                      </div>
                      <span style={merge("font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:600;text-align:right", { color: c.mColor })}>{c.m}</span>
                    </div>
                  ))}
                  <div style={s("font-size:11px;color:var(--ink3);margin-top:12px;font-family:'JetBrains Mono',monospace")}>bar = share of farms under stress · open the map in Operations to drill in</div>
                </div>

                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px 22px")}>
                  <div style={s("display:flex;align-items:center;gap:8px;margin-bottom:2px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1.1px;text-transform:uppercase;color:var(--ink3)")}>Intelligence activity</span><div style={s("flex:1")} /><SampleTag text="illustrative" /></div>
                  <div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);margin-bottom:6px")}>example events — the live activity log ships with the event bus</div>
                  {activity.map((a, i) => (
                    <div key={i} style={s("display:flex;gap:11px;align-items:flex-start;padding:10px 0;border-top:1px solid var(--lineSoft)")}>
                      <span className="ms" style={merge("font-size:16px;margin-top:1px", { color: a.toneColor })}>{a.icon}</span>
                      <div style={s("flex:1")}><div style={s("font-size:12.5px;color:var(--ink);line-height:1.4")}>{a.text}</div></div>
                      <span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--ink3)")}>{a.time}</span>
                    </div>
                  ))}
                </div>
              </div>

              <div style={s("display:grid;grid-template-columns:repeat(3,1fr);gap:18px")}>
                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:18px 20px")}>
                  <div style={s("display:flex;align-items:center;gap:8px;margin-bottom:12px")}><span className="ms" style={s("font-size:18px;color:var(--good)")}>database</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Pipeline health</span><div style={s("flex:1")} /><SampleTag text="snapshot 2026-06" /></div>
                  <div style={s("display:flex;flex-direction:column;gap:9px;font-size:12.5px")}>
                    <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>Canonical plane</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--good)")}>EU · gsg_*_eu</span></div>
                    <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>Last satellite pass</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink)")}>{L ? (L.as_of || '—') : '2 days ago'}</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:8px;color:var(--good)")}>live</span></div>
                    <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>Stranded on US plane</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink2)")}>~14 farms · snapshot</span></div>
                  </div>
                </div>
                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:18px 20px")}>
                  <div style={s("display:flex;align-items:center;gap:8px;margin-bottom:12px")}><span className="ms" style={s("font-size:18px;color:var(--note)")}>conversion_path</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Dispatch loop</span><div style={s("flex:1")} /><SampleTag text="illustrative" /></div>
                  <div style={s("display:flex;align-items:flex-end;gap:6px;height:60px")}>
                    {funnel.map((fn, i) => (
                      <div key={i} style={s("flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;justify-content:flex-end")}>
                        <span style={merge("font-size:15px;font-weight:700", { color: fn.toneColor })}>{fn.v}</span>
                        <div style={merge("width:100%;border-radius:4px 4px 0 0", { background: fn.toneColor, height: fn.h })} />
                        <span style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.3px;text-transform:uppercase;color:var(--ink3)")}>{fn.label}</span>
                      </div>
                    ))}
                  </div>
                  <div style={s("font-size:11px;color:var(--ink2);margin-top:10px;line-height:1.4")}>Stress found → visited → labelled. The loop barely closes — only 2 of 23 became training data.</div>
                </div>
                <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:18px 20px")}>
                  <div style={s("display:flex;align-items:center;gap:8px;margin-bottom:12px")}><span className="ms" style={s("font-size:18px;color:var(--good)")}>cyclone</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Hazard outlook · beta</span><div style={s("flex:1")} /><SampleTag text="illustrative" /></div>
                  <div style={s("display:flex;align-items:center;gap:10px")}><span style={s("width:11px;height:11px;border-radius:50%;background:var(--ink3)")} /><span style={s("font-size:18px;font-weight:600;color:var(--ink2)")}>All-clear <span style={s("font-size:11px;color:var(--ink3)")}>(illustrative)</span></span></div>
                  <div style={s("font-size:12px;color:var(--ink2);margin-top:10px;line-height:1.45")}>El Niño ~96% likely in the Oct–Dec window (NOAA CPC). Bands calm now. The live ward-level hazard nowcast runs in the hazard pipeline; this card is illustrative until it is wired here.</div>
                </div>
              </div>
            </div>
          )}

          {/* ============ SCIENCE ============ */}
          {isScience && (
            <div style={s("padding:18px 24px 26px;display:flex;flex-direction:column;gap:14px")}>
              {isLiveSci ? <LiveFieldBanner fd={fieldData} busy={fieldBusy} note="phenology + ground-truth panels are indicative" /> : <SampleBanner />}
              <div style={s("display:flex;align-items:center;gap:12px;flex-wrap:wrap")}>
                <div style={s("display:flex;align-items:center;gap:9px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:7px 12px")}>
                  <span className="ms" style={s("font-size:18px;color:var(--accent)")}>layers</span>
                  <select value={fieldId} onChange={e => { setFieldId(e.target.value); set({ frame: 9999, playing: false }); }} style={s("background:transparent;border:none;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer;outline:none;max-width:240px")}>
                    {((fieldData && fieldData.fields) || [{ id: fieldId, name: 'loading…', pixels: '' }]).map((o, i) => <option key={i} value={o.id} style={{ color: '#111' }}>{o.name}{o.pixels ? ' (' + o.pixels + 'px)' : ''}</option>)}
                  </select>
                </div>
                <div style={s("display:flex;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:3px")}>
                  {viewTabs.map((v, i) => <div key={i} onClick={v.onClick} style={merge("font-size:12px;font-weight:600;padding:6px 13px;border-radius:8px;cursor:pointer", { background: v.bg, color: v.color })}>{v.label}</div>)}
                </div>
                <div style={s("flex:1")} />
                <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink2)")}>{isLiveSci && fieldData.field ? (fieldData.field.n_obs + ' valid obs') : '43 valid obs · median'}</div>
              </div>

              <div style={s("display:grid;grid-template-columns:1fr 268px;gap:14px;align-items:start")}>
                <div style={s("display:flex;flex-direction:column;gap:12px")}>
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 20px;display:flex;flex-direction:column;gap:15px")}>
                    <div style={s("display:flex;align-items:center;gap:16px;flex-wrap:wrap")}>
                      <div style={s("display:flex;align-items:center;gap:12px")}>
                        <div style={s("width:46px;height:46px;border-radius:13px;background:var(--accentSoft);display:flex;align-items:center;justify-content:center;font-size:25px")}>{PHASES[phaseIdx].g}</div>
                        <div>
                          <div style={s("display:flex;align-items:center;gap:8px;flex-wrap:wrap")}><span style={s("font-size:15px;font-weight:700;letter-spacing:-.2px")}>{PHASES[phaseIdx].n} <span style={s("color:var(--ink3);font-weight:500;font-size:12px")}>· NDVI-derived</span></span><SampleTag text="proxy" />{isLiveSci ? <span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3)")}>{phaseRelPct}% of this field’s NDVI range{fieldData.phenology && fieldData.phenology.days_since_planting != null ? ' · day ' + fieldData.phenology.days_since_planting : ''}</span> : null}</div>
                          <div style={s("font-size:12px;color:var(--ink2);margin-top:1px")}>{cropRead}</div>
                        </div>
                      </div>
                      <div style={s("flex:1")} />
                      <div style={s("display:flex;gap:7px;flex-wrap:wrap")}>
                        {structural.map((sg, i) => (
                          <div key={i} style={s("background:var(--inset);border:1px solid var(--line);border-radius:9px;padding:7px 11px;min-width:94px")}>
                            <div style={s("font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.3px;text-transform:uppercase;color:var(--ink3)")}>{sg.label}</div>
                            <div style={s("display:flex;align-items:baseline;gap:6px")}><span style={merge("font-size:15px;font-weight:700", { color: sg.valColor })}>{sg.val}</span><span style={merge("font-size:9px", { color: sg.valColor })}>{sg.verdict}</span></div>
                            <div style={s("height:3px;border-radius:2px;background:var(--line);overflow:hidden;margin-top:4px")}><div style={merge("height:100%", { width: sg.pct + '%', background: sg.valColor })} /></div>
                          </div>
                        ))}
                      </div>
                    </div>
                    <div style={s("position:relative;padding-top:2px")}>
                      <div style={s("position:absolute;left:6%;right:6%;top:15px;height:3px;background:var(--inset);border-radius:2px")} />
                      <div style={merge("position:absolute;left:6%;top:15px;height:3px;background:var(--good);border-radius:2px", { width: growthLineW })} />
                      <div style={s("position:relative;display:flex")}>
                        {stages.map((g, i) => (
                          <div key={i} style={s("flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center")}>
                            <div style={merge("width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:15px", { background: g.nodeBg, border: '2px solid ' + g.nodeBorder, opacity: g.emojiOp, animation: g.pulse })}>{g.emoji}</div>
                            <div style={merge("font-size:11px;font-weight:600;line-height:1.1", { color: g.nameColor })}>{g.name}</div>
                            <div style={s("font-family:'JetBrains Mono',monospace;font-size:8px;color:var(--ink3)")}>{g.range}</div>
                          </div>
                        ))}
                      </div>
                    </div>
                  </div>

                  <div style={merge("display:grid;gap:12px", { gridTemplateColumns: sciGrid })}>
                    <div style={s("background:#070809;border:1px solid var(--line);border-radius:12px;padding:14px;position:relative;overflow:hidden")}>
                      <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:10px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3)")}>Map · 10 m spots{liveCells ? ' · ' + (sciIndex ? sciIndex.toUpperCase() : 'true-colour') : ''}</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--accent)")}>{st.overlays.truecolor && s2 ? 'Sentinel-2 · ' + s2.date + ' · ' + s2.cloud_pct + '% cloud' : fmtDate(cur.date)}</span></div>
                      {liveCells
                        ? <FieldMap cells={liveCells} center={fieldData.field && fieldData.field.lat != null ? { lat: fieldData.field.lat, lng: fieldData.field.lng } : null} index={sciIndex} basemap={sciBasemap} gtPoints={sciGT} height={mapOnly ? 640 : 380} s2={st.overlays.truecolor ? s2 : null} />
                        : <div style={merge("display:grid;gap:2px", { gridTemplateColumns: 'repeat(' + fieldCols + ',1fr)' })}>
                            {fieldCells.map((c, i) => <div key={i} style={merge("aspect-ratio:1;border-radius:1.5px", { background: c.color })} />)}
                          </div>}
                    </div>
                    {showSpectral && (
                      <div style={s("position:relative;background:#070809;border:1px solid var(--line);border-radius:12px;padding:14px 14px 6px")}>
                        {!isLiveSci && <span style={s("position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-8deg);font-family:'JetBrains Mono',monospace;font-size:15px;letter-spacing:2px;text-transform:uppercase;color:rgba(222,153,76,.30);font-weight:700;pointer-events:none;white-space:nowrap")}>synthetic demonstration data</span>}
                        <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:8px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3)")}>{isLiveSci ? 'NDVI × NDMI × rainfall · live' : 'NDVI × rainfall · 24 mo · synthetic'}</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--good)")}>NDVI {cur.ndvi.toFixed(3)}</span></div>
                        <svg viewBox="0 0 1000 240" preserveAspectRatio="none" style={s("width:100%;height:188px;display:block")}>
                          <line x1="0" y1="60" x2="1000" y2="60" stroke="var(--grid)" strokeWidth="1" />
                          <line x1="0" y1="120" x2="1000" y2="120" stroke="var(--grid)" strokeWidth="1" />
                          <line x1="0" y1="180" x2="1000" y2="180" stroke="var(--grid)" strokeWidth="1" />
                          {st.overlays.rain && rainBars.map((b, i) => <rect key={i} x={b.x} y={b.y} width={b.w} height={b.h} fill="var(--note)" opacity="0.32" />)}
                          <polyline points={baselinePoints} fill="none" stroke="var(--ink3)" strokeWidth="1.5" strokeDasharray="5,5" />
                          {showCompare && <polyline points={comparePoints} fill="none" stroke="var(--accent)" strokeWidth="2" strokeDasharray="2,5" />}
                          <polyline points={ndviPoints} fill="none" stroke="var(--good)" strokeWidth="2.4" />
                          {ndmiPoints && <polyline points={ndmiPoints} fill="none" stroke="var(--note)" strokeWidth="2" strokeDasharray="3,3" />}
                          <line x1={cursorX} y1="0" x2={cursorX} y2="240" stroke="var(--accent)" strokeWidth="2" />
                        </svg>
                        <div style={s("display:flex;justify-content:space-between;font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);padding:2px 2px 8px")}><span>{fmtMon(arr[0].date)}</span><span style={{ color: 'var(--ink2)' }}>▮ rain · — baseline</span><span>{fmtMon(arr[n - 1].date)}</span></div>
                      </div>
                    )}
                    {showGT && (
                      <div style={s("background:#070809;border:1px solid var(--line);border-radius:12px;padding:14px")}>
                        <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:10px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3)")}>Ground truth · captures for this field</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3)")}>{gtCards.length ? (gtCards.length + ' on file') : ''}</span></div>
                        {gtCards.length ? (
                          <div style={s("display:flex;flex-direction:column;gap:8px")}>
                            {gtCards.map((g, i) => (
                              <div key={i} style={s("display:flex;gap:10px;align-items:center;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px")}>
                                <div style={merge("width:46px;height:46px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:0 0 auto", { background: g.tint })}><span className="ms" style={merge("font-size:20px", { color: g.toneColor })}>photo_camera</span></div>
                                <div style={s("flex:1;min-width:0")}><div style={s("font-size:12.5px;font-weight:600;color:var(--ink);text-transform:capitalize")}>{g.label}</div><div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3)")}>{g.date}{g.view ? ' · ' + g.view : ''}</div></div>
                              </div>
                            ))}
                          </div>
                        ) : (
                          <div style={s("display:flex;flex-direction:column;align-items:center;gap:8px;padding:28px 16px;text-align:center")}>
                            <span className="ms" style={s("font-size:26px;color:var(--ink3)")}>photo_camera</span>
                            <div style={s("font-size:12.5px;color:var(--ink2)")}>No ground-truth captures linked to this field yet.</div>
                            <div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);line-height:1.4;max-width:300px")}>Farm-walk + calibration photos flow in from the Tu-RaDa app — 196 on file across 19 farms so far.</div>
                          </div>
                        )}
                        <div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);margin-top:10px;line-height:1.4")}>Captures don’t yet carry a structured pixel link or severity — that’s the calibration step (M4).</div>
                      </div>
                    )}
                  </div>

                  <div style={merge("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:15px 18px;display:flex;flex-direction:column;gap:12px", { borderColor: st.playing ? 'var(--accent)' : 'var(--line)' })}>
                    <div style={s("display:flex;align-items:center;gap:12px")}>
                      <button onClick={togglePlay} style={s("width:42px;height:42px;border-radius:50%;border:none;background:var(--accent);color:#0B0D11;cursor:pointer;display:flex;align-items:center;justify-content:center")}><span className="ms" style={s("font-size:24px")}>{st.playing ? 'pause' : 'play_arrow'}</span></button>
                      <button onClick={cycleSpeed} style={s("border:1px solid var(--line);background:var(--inset);color:var(--ink);border-radius:9px;padding:8px 12px;cursor:pointer;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:600;min-width:46px")}>{st.speed}×</button>
                      <button onClick={toggleLoop} title="Loop" style={s("width:38px;height:38px;border:1px solid var(--line);background:var(--inset);border-radius:9px;cursor:pointer;display:flex;align-items:center;justify-content:center")}><span className="ms" style={merge("font-size:19px", { color: st.loop ? 'var(--accent)' : 'var(--ink3)' })}>repeat</span></button>
                      <div style={s("flex:1")} />
                      <div style={s("text-align:right")}>
                        <div style={s("font-family:'JetBrains Mono',monospace;font-size:19px;font-weight:600;color:var(--accent);letter-spacing:.5px")}>{fmtDate(cur.date)}</div>
                        <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3)")}>acq {f + 1}/{n}{isLiveSci && fieldData.field ? ' · ' + (fieldData.field.pixels || '?') + ' px' : ''}</div>
                      </div>
                    </div>
                    <input type="range" min="0" max={n - 1} value={f} onInput={onScrub} onChange={onScrub} style={s("width:100%")} />
                    <div style={s("display:flex;justify-content:space-between;font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3)")}><span>← earlier</span><span style={{ color: 'var(--ink2)' }}>rain {cur.rain} mm · {fmtMon(cur.date)}</span><span>most recent →</span></div>
                  </div>
                </div>

                <div style={s("display:flex;flex-direction:column;gap:12px")}>
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:15px 16px")}>
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3);margin-bottom:6px")}>Overlay stack</div>
                    {overlays.map((o, i) => (
                      <button key={i} onClick={o.onToggle} style={s("display:flex;align-items:center;gap:10px;width:100%;background:transparent;border:none;cursor:pointer;padding:7px 2px;text-align:left")}>
                        <span style={merge("width:15px;height:15px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex:0 0 auto", { border: '1.5px solid ' + o.boxColor, background: o.boxBg })}><span className="ms" style={s("font-size:12px;color:#0B0D11")}>{o.check}</span></span>
                        <span style={merge("width:9px;height:9px;border-radius:2px;flex:0 0 auto", { background: o.dot })} />
                        <span style={s("font-size:12.5px;color:var(--ink);flex:1")}>{o.label}</span>
                      </button>
                    ))}
                  </div>
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:15px 16px")}>
                    <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:11px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Classification</span>{isLiveSci ? <span style={s("font-family:'JetBrains Mono',monospace;font-size:9px;background:var(--goodSoft);color:var(--good);padding:2px 6px;border-radius:5px")}>vs own baseline</span> : <SampleTag text="illustrative" />}</div>
                    {isLiveSci ? (
                      <React.Fragment>
                        <div style={merge("font-size:23px;font-weight:700;letter-spacing:-.4px;text-transform:capitalize", { color: tv(devZtone) })}>{devZlabel || '—'}</div>
                        <div style={s("display:flex;flex-direction:column;gap:7px;margin-top:11px;font-size:12px")}>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>NDVI vs own baseline</span><span style={merge("font-family:'JetBrains Mono',monospace", { color: tv(devZtone) })}>{devZ == null ? '—' : (devZ >= 0 ? '+' : '') + devZ.toFixed(1) + 'σ'}</span></div>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>NDMI vs prev</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink)")}>{ndmiDelta == null ? '—' : (ndmiDelta >= 0 ? '+' : '') + ndmiDelta.toFixed(2)}</span></div>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>method</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink3);font-size:10px")}>self seasonal σ</span></div>
                        </div>
                      </React.Fragment>
                    ) : (
                      <React.Fragment>
                        <div style={s("font-size:23px;font-weight:700;color:var(--bad);letter-spacing:-.4px")}>stress</div>
                        <div style={s("display:flex;flex-direction:column;gap:7px;margin-top:11px;font-size:12px")}>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>z vs cohort</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--bad)")}>+2.0σ</span></div>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>signal carrier</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink);background:var(--inset);padding:1px 7px;border-radius:5px")}>B11 · SWIR</span></div>
                          <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>confidence</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--good)")}>high</span></div>
                        </div>
                      </React.Fragment>
                    )}
                  </div>
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:15px 16px")}>
                    <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:9px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Compare</span><span className="ms" style={s("font-size:17px;color:var(--accent)")}>compare_arrows</span></div>
                    <div style={s("display:flex;align-items:center;gap:9px;padding:6px 0")}><span style={s("width:9px;height:9px;border-radius:2px;background:var(--good);flex:0 0 auto")} /><span style={s("font-size:12.5px;flex:1")}>{isLiveSci && fieldData.field ? fieldData.field.name : 'Sample field'} <span style={{ color: 'var(--ink3)' }}>· base</span></span></div>
                    {showCompare && (
                      <div style={s("display:flex;align-items:center;gap:9px;padding:6px 0;border-top:1px solid var(--lineSoft)")}><span style={s("width:9px;height:9px;border-radius:2px;background:var(--accent);flex:0 0 auto")} /><span style={s("font-size:12.5px;flex:1")}>{cmpName || 'loading…'} <span style={{ color: 'var(--ink3)' }}>· {cmpVar.toUpperCase()}</span></span><button onClick={() => setCmpFieldId(null)} style={s("background:transparent;border:none;cursor:pointer;color:var(--ink3);display:flex")}><span className="ms" style={s("font-size:16px")}>close</span></button></div>
                    )}
                    {!showCompare && (
                      <div style={s("display:flex;align-items:center;gap:8px;margin-top:6px;background:var(--inset);border:1px dashed var(--line);border-radius:9px;padding:7px 10px")}>
                        <span className="ms" style={s("font-size:16px;color:var(--accent)")}>add</span>
                        <select value="" onChange={e => { if (e.target.value) setCmpFieldId(e.target.value); }} style={s("flex:1;background:transparent;border:none;color:var(--ink2);font-size:12px;cursor:pointer;outline:none;font-family:inherit")}>
                          <option value="" style={{ color: '#111' }}>{cmpFields.length ? 'Add a farm to compare…' : 'Open a field to compare'}</option>
                          {cmpFields.map((o, i) => <option key={i} value={o.id} style={{ color: '#111' }}>{o.name}</option>)}
                        </select>
                      </div>
                    )}
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:.4px;text-transform:uppercase;color:var(--ink3);margin:13px 0 7px")}>Compare variable</div>
                    <div style={s("display:flex;flex-wrap:wrap;gap:6px")}>
                      {cmpVars.map((v, i) => <button key={i} onClick={v.onClick} style={merge("font-size:11px;font-weight:600;padding:5px 10px;border-radius:7px;cursor:pointer", { border: '1px solid ' + v.bd, background: v.bg, color: v.col })}>{v.label}</button>)}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          )}

          {/* ============ INSIGHTS LAB ============ */}
          {isInsights && (
            <div style={s("padding:24px 30px 60px;max-width:1320px;margin:0 auto;display:flex;flex-direction:column;gap:18px")}>
              <div>
                <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;color:var(--accent)")}>Insights Lab</div>
                <div style={s("font-size:22px;font-weight:600;letter-spacing:-.4px;margin-top:5px")}>Turn raw signals into decisions you can defend</div>
              </div>
              {liveZones ? <LiveFieldBanner fd={fieldData} busy={fieldBusy} note="Zones are live (NDVI terciles); Hypothesis / Anomalies / Drivers run in the Workbench" /> : <SampleBanner />}
              <div style={s("display:flex;gap:6px;flex-wrap:wrap;border-bottom:1px solid var(--line);padding-bottom:2px")}>
                {labTabs.map((tb, i) => <button key={i} onClick={tb.onClick} style={merge("background:transparent;border:none;font-size:13px;font-weight:600;padding:9px 13px;cursor:pointer;margin-bottom:-1px", { borderBottom: '2px solid ' + tb.under, color: tb.color })}>{tb.label}</button>)}
              </div>

              {st.lab === 'zones' && (
                <div style={s("display:flex;flex-direction:column;gap:16px")}>
                  <div style={s("display:flex;align-items:center;gap:8px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink3)")}>
                    <span style={{ color: 'var(--ink2)' }}>Kenya · {NF} farms</span><span className="ms" style={s("font-size:14px")}>chevron_right</span>{isLiveSci && fieldData.field ? <span style={{ color: 'var(--accent)' }}>{fieldData.field.name}{fieldData.field.crop ? ' · ' + fieldData.field.crop : ''}</span> : <React.Fragment><span style={{ color: 'var(--ink2)' }}>Makueni · 24</span><span className="ms" style={s("font-size:14px")}>chevron_right</span><span style={{ color: 'var(--accent)' }}>sample field</span><SampleTag text="sample field" /></React.Fragment>}
                  </div>
                  <div style={s("font-size:14px;color:var(--ink2);max-width:760px;line-height:1.5")}>Zones cluster a field's spots into a few <b style={{ color: 'var(--ink)' }}>management blocks</b> — so an agronomist acts on a block, not 84 separate pixels. Same idea scales up: counties cluster into regional zones for a bird's-eye view of Makueni.</div>
                  <div style={s("display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start")}>
                    <div style={s("background:#070809;border:1px solid var(--line);border-radius:14px;padding:16px")}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:10px")}>Clustered into 3 zones</div>
                      <div style={merge("display:grid;gap:2px", { gridTemplateColumns: 'repeat(' + zoneCols + ',1fr)' })}>
                        {zoneCells.map((z, i) => <div key={i} style={merge("aspect-ratio:1;border-radius:1.5px", { background: z.color, boxShadow: 'inset 0 0 0 ' + z.ring + ' ' + z.ringColor })} />)}
                      </div>
                    </div>
                    <div style={s("display:flex;flex-direction:column;gap:10px")}>
                      {zones.map((z, i) => (
                        <div key={i} style={merge("background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px", { borderLeft: '3px solid ' + z.toneColor })}>
                          <div style={s("display:flex;align-items:center;justify-content:space-between")}><span style={s("font-size:14px;font-weight:600;color:var(--ink)")}>{z.label}</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink2)")}>{z.area}</span></div>
                          <div style={s("display:flex;gap:18px;margin-top:9px;font-family:'JetBrains Mono',monospace;font-size:11px")}>
                            <span style={{ color: 'var(--ink2)' }}>NDVI <b style={{ color: z.toneColor }}>{z.ndvi}</b></span>
                            <span style={{ color: 'var(--ink2)' }}>stress <b style={{ color: z.toneColor }}>{z.stress}</b></span>
                          </div>
                          <div style={s("display:flex;gap:8px;align-items:flex-start;margin-top:10px;padding-top:10px;border-top:1px solid var(--lineSoft)")}><span className="ms" style={merge("font-size:15px", { color: z.toneColor })}>arrow_forward</span><span style={s("font-size:12px;color:var(--ink);line-height:1.4")}>{z.action}</span></div>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              )}

              {st.lab === 'stress' && (
                <div style={s("display:flex;flex-direction:column;gap:14px")}>
                  <div style={s("display:flex;align-items:flex-start;gap:10px")}><div style={s("font-size:14px;color:var(--ink2);max-width:720px;line-height:1.5")}>Stress assessment ranks the <b style={{ color: 'var(--ink)' }}>likely cause</b> against the field's own seasonal baseline. The Evidence below is live for the selected field; the cause attribution + verdict are a <b>worked example</b> — the live classifier runs in the Workbench.</div><SampleTag text="worked example" /></div>
                  <div style={s("display:grid;grid-template-columns:1fr 28px 1fr 28px 1fr;align-items:center;gap:6px")}>
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px")}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--note);margin-bottom:10px")}>1 · Evidence</div>
                      <div style={s("display:flex;flex-direction:column;gap:8px;font-size:12.5px")}>
                        <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>NDVI vs own baseline</span><span style={merge("font-family:'JetBrains Mono',monospace", { color: isLiveSci ? tv(devZtone) : 'var(--bad)' })}>{isLiveSci && devZ != null ? ((devZ >= 0 ? '+' : '') + devZ.toFixed(1) + 'σ') : '+2.0σ'}</span></div>
                        <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>NDMI Δ vs prev</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink)")}>{isLiveSci && ndmiDelta != null ? ((ndmiDelta >= 0 ? '+' : '') + ndmiDelta.toFixed(2)) : '−0.14'}</span></div>
                        <div style={s("display:flex;justify-content:space-between")}><span style={{ color: 'var(--ink2)' }}>latest NDMI</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink)")}>{isLiveSci && cur.ndmi != null ? cur.ndmi.toFixed(2) : '0.18'}</span></div>
                      </div>
                    </div>
                    <span className="ms" style={s("font-size:20px;color:var(--ink3);text-align:center")}>arrow_forward</span>
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px")}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--accent);margin-bottom:10px")}>2 · Method</div>
                      <div style={s("font-size:12.5px;color:var(--ink);line-height:1.5")}>Rank candidate drivers by how far each spectral signature deviates from this field's <b>self seasonal prior</b>, weighted by any ground-truth photo. Low-confidence high-deviation cases are flagged for an agronomist.</div>
                    </div>
                    <span className="ms" style={s("font-size:20px;color:var(--ink3);text-align:center")}>arrow_forward</span>
                    <div style={merge("border:1px solid var(--line);border-radius:12px;padding:16px", { background: fdrv ? (fdrv.status === 'red' ? 'var(--badSoft)' : fdrv.status === 'amber' ? 'var(--warnSoft)' : fdrv.status === 'green' ? 'var(--goodSoft)' : 'var(--inset)') : 'var(--badSoft)' })}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:10px")}>3 · Verdict</div>
                      {fdrv ? (
                        <React.Fragment>
                          <div style={merge("font-size:20px;font-weight:700", { color: tv(DRV_TONE[fdrv.driver] || 'ink2') })}>{DRV_LABEL[fdrv.driver] || fdrv.driver}</div>
                          <div style={s("font-size:11.5px;color:var(--ink2);margin-top:6px;line-height:1.45")}>rule_v1 · {fdrv.confidence != null ? ('confidence ' + fdrv.confidence) : 'confidence n/a'} · as of {(fdrv.last_week || '').slice(0, 10)}<br />satellite-derived, not GT-confirmed</div>
                        </React.Fragment>
                      ) : (
                        <React.Fragment>
                          <div style={s("font-size:20px;font-weight:700;color:var(--bad)")}>Water stress</div>
                          <div style={s("font-size:12px;color:var(--ink2);margin-top:6px")}>example conclusion · open a field for its live rule_v1 verdict</div>
                        </React.Fragment>
                      )}
                    </div>
                  </div>
                </div>
              )}

              {st.lab === 'hypo' && (
                <div style={s("display:flex;flex-direction:column;gap:16px")}>
                  <div style={s("font-size:14px;color:var(--ink2);max-width:760px;line-height:1.5")}>Change one assumption and watch the call move. This is how you <b style={{ color: 'var(--ink)' }}>pressure-test</b> a classification before trusting it — the answer depends on the baseline and the index you choose.</div>
                  <div style={s("display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start")}>
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px;display:flex;flex-direction:column;gap:16px")}>
                      <div>
                        <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:8px")}>Baseline</div>
                        <div style={s("display:flex;gap:8px")}>
                          <button onClick={() => set({ hypoBaseline: 'self' })} style={merge("flex:1;padding:10px;border-radius:9px;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer", { border: '1px solid ' + bSelf.border, background: bSelf.bg })}>Self prior (DOY)</button>
                          <button onClick={() => set({ hypoBaseline: 'cohort' })} style={merge("flex:1;padding:10px;border-radius:9px;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer", { border: '1px solid ' + bCoh.border, background: bCoh.bg })}>Crop cohort</button>
                        </div>
                      </div>
                      <div>
                        <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:8px")}>Index</div>
                        <div style={s("display:flex;gap:8px")}>
                          <button onClick={() => set({ hypoIndex: 'ndvi' })} style={merge("flex:1;padding:10px;border-radius:9px;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer", { border: '1px solid ' + iN.border, background: iN.bg })}>NDVI</button>
                          <button onClick={() => set({ hypoIndex: 'wsi42' })} style={merge("flex:1;padding:10px;border-radius:9px;color:var(--ink);font-size:13px;font-weight:600;cursor:pointer", { border: '1px solid ' + iW.border, background: iW.bg })}>WSI42 (SWIR)</button>
                        </div>
                      </div>
                      <div style={s("font-size:11.5px;color:var(--ink3);line-height:1.45;font-family:'JetBrains Mono',monospace")}>if baseline = {hb} and index = {hi} → classify</div>
                    </div>
                    <div style={merge("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:22px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:10px;min-height:190px", { borderColor: hypoTone })}>
                      <div style={s("display:flex;align-items:center;gap:8px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Resulting call</span><SampleTag text="illustrative" /></div>
                      <div style={merge("font-size:30px;font-weight:700;letter-spacing:-.6px", { color: hypoTone })}>{hypoVerdict}</div>
                      <div style={s("font-size:12.5px;color:var(--ink2);max-width:280px;line-height:1.45")}>{hypoSub}</div>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3);margin-top:6px;line-height:1.4;max-width:280px")}>Shows how the call moves with your assumptions — the σ values are illustrative; the live run is in the Workbench.</div>
                    </div>
                  </div>
                </div>
              )}

              {st.lab === 'anomalies' && (
                <div style={s("display:flex;flex-direction:column;gap:14px")}>
                  {isLiveSci ? (
                    <React.Fragment>
                      <div style={s("display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px")}>
                        <div style={s("font-size:14px;color:var(--ink2);max-width:600px;line-height:1.5")}>The field's most-deviating spots, ranked by NDVI vs the field's own mean. <span style={{ color: 'var(--ink3)' }}>{anomAll.length} spots · mean NDVI {anomMean != null ? anomMean.toFixed(2) : '—'}</span></div>
                        <div style={s("display:flex;align-items:center;gap:8px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink2)")}>≤ −{anomThresh.toFixed(1)}σ<input type="range" min="0.5" max="3" step="0.5" value={anomThresh} onChange={e => set({ anomThresh: +e.target.value })} style={s("width:120px")} /></div>
                      </div>
                      {anomalies.length ? (
                        <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden")}>
                          <div style={s("display:grid;grid-template-columns:60px 1fr 90px 70px;gap:10px;padding:10px 16px;border-bottom:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:9.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--ink3)")}><span>Rank</span><span>Coordinates</span><span>NDVI</span><span>z</span></div>
                          <div style={s("max-height:420px;overflow:auto")}>
                            {anomalies.slice(0, 60).map((c, i) => (
                              <div key={i} style={s("display:grid;grid-template-columns:60px 1fr 90px 70px;gap:10px;padding:9px 16px;border-top:1px solid var(--lineSoft);align-items:center;font-size:12px")}>
                                <span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink3)")}>#{i + 1}</span>
                                <span style={s("font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--ink2)")}>{c.lat.toFixed(5)}, {c.lng.toFixed(5)}</span>
                                <span style={merge("font-family:'JetBrains Mono',monospace", { color: c.ndvi < 0.4 ? 'var(--bad)' : c.ndvi < 0.55 ? 'var(--warn)' : 'var(--ink)' })}>{c.ndvi.toFixed(3)}</span>
                                <span style={s("font-family:'JetBrains Mono',monospace;color:var(--bad)")}>{c.z.toFixed(1)}σ</span>
                              </div>
                            ))}
                          </div>
                          <div style={s("padding:9px 16px;border-top:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3)")}>{anomalies.length} spots ≤ −{anomThresh.toFixed(1)}σ · live per-pixel NDVI, latest acquisition</div>
                        </div>
                      ) : <div style={s("background:var(--panel);border:1px dashed var(--line);border-radius:12px;padding:30px;text-align:center;color:var(--ink2);font-size:13px")}>No spots beyond −{anomThresh.toFixed(1)}σ — this field is uniform. Lower the threshold to inspect.</div>}
                    </React.Fragment>
                  ) : <div style={s("background:var(--panel);border:1px dashed var(--line);border-radius:12px;padding:30px;text-align:center;color:var(--ink2);font-size:13px")}>Open a field (Science tab) to rank its spots by deviation.</div>}
                </div>
              )}
              {st.lab === 'dispatch' && (
                <div style={s("display:flex;flex-direction:column;gap:14px")}>
                  <div style={s("font-size:14px;color:var(--ink2);max-width:640px;line-height:1.5")}>A coordinate visit list from the worst spots — hand to an agronomist. <SampleTag text="read-only" /> Assignment + status tracking need a dispatch backend (not built yet).</div>
                  {isLiveSci && anomalies.length ? (
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px")}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--ink3);margin-bottom:8px")}>{fieldData.field.name} · {anomalies.length} spots to visit (≤ −{anomThresh.toFixed(1)}σ)</div>
                      <pre style={s("font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink);white-space:pre-wrap;margin:0;line-height:1.7")}>{anomalies.slice(0, 30).map((c, i) => '#' + (i + 1) + '  ' + c.lat.toFixed(5) + ', ' + c.lng.toFixed(5) + '   NDVI ' + c.ndvi.toFixed(2) + '  (' + c.z.toFixed(1) + 'σ)').join('\n')}</pre>
                    </div>
                  ) : <div style={s("background:var(--panel);border:1px dashed var(--line);border-radius:12px;padding:30px;text-align:center;color:var(--ink2);font-size:13px")}>Open a field and flag spots in Anomalies to build a visit list.</div>}
                </div>
              )}
              {st.lab === 'drivers' && (
                <div style={s("display:flex;flex-direction:column;gap:14px")}>
                  <div style={s("font-size:14px;color:var(--ink2);max-width:680px;line-height:1.5")}>Fleet driver mix — rule_v1, <b style={{ color: 'var(--ink)' }}>satellite-derived, not ground-truth-confirmed</b>. Each farm's latest weekly classification.</div>
                  {driverDist ? (
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px")}>
                      {driverDist.map((d, i) => (
                        <div key={i} style={s("margin-bottom:12px")}>
                          <div style={s("display:flex;justify-content:space-between;font-size:12.5px;margin-bottom:5px")}><span style={{ color: 'var(--ink)' }}>{d.label}</span><span style={s("font-family:'JetBrains Mono',monospace;color:var(--ink2)")}>{d.n} · {driverTotal ? Math.round(d.n / driverTotal * 100) : 0}%{d.conf != null ? ' · conf ' + d.conf : ''}</span></div>
                          <div style={s("height:8px;border-radius:5px;background:var(--inset);overflow:hidden")}><div style={merge("height:100%", { width: (driverTotal ? d.n / driverTotal * 100 : 0) + '%', background: tv(d.tone) })} /></div>
                        </div>
                      ))}
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);margin-top:6px")}>{driverTotal} farms classified · rule_v1 (latest week per farm)</div>
                    </div>
                  ) : <div style={s("background:var(--panel);border:1px dashed var(--line);border-radius:12px;padding:30px;text-align:center;color:var(--ink2);font-size:13px")}>Driver distribution loads with the portfolio.</div>}
                  <div style={s("background:var(--panel);border:1px dashed var(--line);border-radius:12px;padding:16px;display:flex;gap:10px;align-items:flex-start")}><span className="ms" style={s("font-size:18px;color:var(--ink3)")}>photo_camera</span><div style={s("font-size:12.5px;color:var(--ink2);line-height:1.5")}><b style={{ color: 'var(--ink)' }}>GT-confirmed stressors</b> (water / nutrient / pest / disease from field photos): <b style={{ color: 'var(--ink)' }}>none linked yet</b> — captures carry no structured stressor label; that's the calibration step (M4).</div></div>
                </div>
              )}
            </div>
          )}

          {/* ============ OPERATIONS ============ */}
          {isOperations && (
            <div style={s("padding:24px 30px 60px;max-width:1340px;margin:0 auto;display:flex;flex-direction:column;gap:18px")}>
              {!st.addFarm && (
                <div style={s("display:flex;flex-direction:column;gap:18px")}>
                  <div style={s("display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:12px")}>
                    <div>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;color:var(--accent)")}>Operations</div>
                      <div style={s("font-size:22px;font-weight:600;letter-spacing:-.4px;margin-top:5px")}>Farm inventory & onboarding</div>
                    </div>
                    <div style={s("display:flex;gap:8px;align-items:center")}>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:9px 13px")}>{NF} farms</div>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:9px 13px")}>M0:{ladderCounts[0]} · M2:{ladderCounts[2]} · M3:{ladderCounts[3]}</div>
                      <button onClick={() => set({ addFarm: true })} style={s("display:flex;align-items:center;gap:6px;background:var(--accent);color:#0B0D11;border:none;border-radius:9px;padding:9px 14px;cursor:pointer;font-size:12px;font-weight:700")}><span className="ms" style={s("font-size:17px")}>add</span>Add Farm</button>
                    </div>
                  </div>
                  {/* real all-farms map - status pins, tap to drill */}
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:12px")}>
                    <div style={s("display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px")}>
                      <span style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3)")}>Fleet map · tap a farm to open it</span>
                      <div style={s("display:flex;align-items:center;gap:12px;font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--ink2)")}>
                        <span><span style={s("display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--bad);margin-right:5px")} />{opCounts.red} urgent</span>
                        <span><span style={s("display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--warn);margin-right:5px")} />{opCounts.amber} watch</span>
                        <span><span style={s("display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--good);margin-right:5px")} />{opCounts.green} good</span>
                      </div>
                    </div>
                    <PortfolioMap farms={opAll} onPick={openFarm} height={420} />
                    <div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3);margin-top:8px")}>Pin colour = rule_v1 status (satellite-derived, not ground-truth-confirmed). Tap a pin → the farm's live 10 m spots in Science.</div>
                  </div>

                  {/* attention feed - worst farms first */}
                  {opAttention.length > 0 && (
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px")}>
                      <div style={s("display:flex;align-items:center;gap:8px;margin-bottom:10px")}><span style={s("font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--ink3)")}>Needs attention</span><span style={s("font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3)")}>{opCounts.red + opCounts.amber} flagged · rule_v1</span></div>
                      <div style={s("display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px")}>
                        {opAttention.map((r, i) => (
                          <div key={i} onClick={() => openFarm(r.farm_id)} style={merge("display:flex;align-items:center;gap:10px;background:var(--inset);border:1px solid var(--line);border-radius:10px;padding:10px 12px;cursor:pointer", { borderLeft: '3px solid ' + r.dot })}>
                            <span style={merge("width:9px;height:9px;border-radius:50%;flex:0 0 auto", { background: r.dot })} />
                            <div style={s("flex:1;min-width:0")}><div style={s("font-size:12.5px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{r.farm}</div><div style={s("font-size:10.5px;color:var(--ink2)")}>{r.county} · {r.issue}</div></div>
                            <span className="ms" style={s("font-size:16px;color:var(--ink3)")}>chevron_right</span>
                          </div>
                        ))}
                      </div>
                    </div>
                  )}

                  {/* filterable, clickable inventory table */}
                  <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden")}>
                    <div style={s("display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 18px;border-bottom:1px solid var(--line);flex-wrap:wrap")}>
                      <div style={s("display:flex;gap:4px;background:var(--inset);border-radius:9px;padding:3px")}>
                        {opStatusTabs.map((tb, i) => <div key={i} onClick={() => set({ opStatus: tb.id })} style={merge("font-size:11.5px;font-weight:600;padding:6px 11px;border-radius:7px;cursor:pointer", { background: tb.on ? 'var(--accent)' : 'transparent', color: tb.on ? '#0B0D11' : 'var(--ink2)' })}>{tb.label} <span style={{ opacity: .7 }}>{tb.n}</span></div>)}
                      </div>
                      <input value={st.opQ} onChange={e => set({ opQ: e.target.value })} placeholder="Search farm / county / crop" style={s("background:var(--inset);border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:12.5px;color:var(--ink);outline:none;font-family:inherit;min-width:200px")} />
                    </div>
                    <div style={s("display:grid;grid-template-columns:16px 1.6fr .9fr .9fr 60px 1.5fr;gap:12px;padding:11px 18px;border-bottom:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.6px;text-transform:uppercase;color:var(--ink3)")}>
                      <span></span><span>Farm</span><span>County</span><span>Crop</span><span>Level</span><span>Status · issue</span>
                    </div>
                    <div style={s("max-height:460px;overflow:auto")}>
                      {opRows.map((r, i) => (
                        <div key={i} onClick={() => openFarm(r.farm_id)} style={s("display:grid;grid-template-columns:16px 1.6fr .9fr .9fr 60px 1.5fr;gap:12px;padding:11px 18px;border-top:1px solid var(--lineSoft);align-items:center;font-size:12.5px;cursor:pointer")}>
                          <span style={merge("width:9px;height:9px;border-radius:50%", { background: r.dot })} />
                          <span style={s("color:var(--ink);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{r.farm}</span>
                          <span style={{ color: 'var(--ink2)' }}>{r.county}</span>
                          <span style={{ color: 'var(--ink2)' }}>{r.crop}</span>
                          <span style={merge("font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:600", { color: r.lvlColor })}>{r.lvl}</span>
                          <span style={s("color:var(--ink2);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{r.issue}</span>
                        </div>
                      ))}
                    </div>
                    <div style={s("padding:9px 18px;border-top:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink3)")}>{opRows.length} of {opAll.length} farms · status is rule_v1 (satellite-derived), not ground-truth-confirmed</div>
                  </div>
                </div>
              )}
              {st.addFarm && (
                <div style={s("display:flex;flex-direction:column;gap:16px")}>
                  <div style={s("display:flex;align-items:center;gap:12px")}>
                    <button onClick={() => set({ addFarm: false })} style={s("display:flex;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:8px 12px;cursor:pointer;color:var(--ink)")}><span className="ms" style={s("font-size:16px")}>arrow_back</span><span style={s("font-size:12px;font-weight:600")}>Back</span></button>
                    <div>
                      <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;color:var(--accent)")}>Operations · onboarding</div>
                      <div style={s("font-size:22px;font-weight:600;letter-spacing:-.4px;margin-top:3px")}>Add a farm</div>
                    </div>
                  </div>
                  <div style={s("display:flex;align-items:center;gap:10px;background:var(--noteSoft);border:1px solid var(--line);border-radius:10px;padding:8px 13px")}>
                    <span className="ms" style={s("font-size:16px;color:var(--note)")}>info</span>
                    <span style={s("font-size:12px;color:var(--ink2);line-height:1.4")}>Tap the map to drop the farm boundary corners (drag the pegman for Street View). Save registers the farm live — writes to the identity layer + Firestore and queues pixelation + 24-month backfill.</span>
                  </div>
                  <div style={s("display:grid;grid-template-columns:1fr 320px;gap:16px;align-items:start")}>
                    <AddFarmMap key={st.addSaved} height={600} onPolygon={(pts) => set({ addPts: pts })} />
                    <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px;display:flex;flex-direction:column;gap:14px")}>
                      <div><div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:6px")}>Farm name</div><input value={st.farmName} onChange={e => set({ farmName: e.target.value })} placeholder="e.g. Makueni-Mango-007" style={s("width:100%;box-sizing:border-box;background:var(--inset);border:1px solid var(--line);border-radius:9px;padding:10px 12px;font-size:13px;color:var(--ink);outline:none;font-family:inherit")} /></div>
                      <div><div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:6px")}>Crop</div>
                        <select value={st.cropSel} onChange={e => set({ cropSel: e.target.value })} style={s("width:100%;box-sizing:border-box;background:var(--inset);border:1px solid var(--line);border-radius:9px;padding:10px 12px;font-size:13px;color:var(--ink);outline:none;font-family:inherit;cursor:pointer")}>
                          {['Mango', 'Avocado', 'Banana', 'Maize', 'Coffee', 'Tea', 'Cashew', 'Macadamia', 'Sorghum', 'Beans', 'Tomato', 'Sugarcane', 'Sweet potato', 'French beans', 'Other'].map((c, i) => <option key={i} value={c} style={{ color: '#111' }}>{c}</option>)}
                        </select>
                      </div>
                      <div><div style={s("font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin-bottom:6px")}>Paste vision-AI JSON · optional</div><textarea value={st.visionJson} onChange={e => set({ visionJson: e.target.value })} placeholder={'{"farm_name":"…","crop":"mango"}'} style={s("width:100%;box-sizing:border-box;background:var(--inset);border:1px solid var(--line);border-radius:9px;padding:10px 12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink);min-height:64px;outline:none;resize:vertical")} /></div>
                      <button onClick={() => { try { const j = JSON.parse(st.visionJson); set({ farmName: j.farm_name || j.name || st.farmName, cropSel: (j.crop ? (j.crop[0].toUpperCase() + j.crop.slice(1)) : st.cropSel), saveMsg: 'Applied from JSON.' }); } catch (e) { set({ saveMsg: 'Could not parse JSON.' }); } }} style={s("background:var(--inset);border:1px solid var(--line);border-radius:9px;padding:9px;font-size:12px;font-weight:600;color:var(--ink);cursor:pointer")}>Apply JSON</button>
                      <button onClick={saveFarm} disabled={st.saveBusy} style={merge("border:none;border-radius:9px;padding:12px;font-size:13px;font-weight:700;color:#0B0D11;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px", { background: st.saveBusy ? 'var(--ink3)' : 'var(--accent)' })}><span className="ms" style={s("font-size:18px")}>{st.saveBusy ? 'sync' : 'check'}</span>{st.saveBusy ? 'Registering…' : 'Save farm'}</button>
                      {st.saveMsg ? <div style={s("font-size:11.5px;color:var(--ink2);line-height:1.4;background:var(--inset);border:1px solid var(--line);border-radius:8px;padding:8px 10px")}>{st.saveMsg}</div> : null}
                    </div>
                  </div>
                </div>
              )}
            </div>
          )}

          {/* ============ MANAGE ============ */}
          {isManage && (
            <div style={s("padding:24px 30px 60px;max-width:920px;margin:0 auto;display:flex;flex-direction:column;gap:18px")}>
              <div>
                <div style={s("font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.6px;text-transform:uppercase;color:var(--accent)")}>Manage</div>
                <div style={s("font-size:22px;font-weight:600;letter-spacing:-.4px;margin-top:5px")}>Modules & access</div>
                <div style={s("font-size:13.5px;color:var(--ink2);margin-top:8px;max-width:620px;line-height:1.5")}>Turn modules on or off and see who can reach each one. This is the modular backbone — add a tool later, or restrict it to a role, without touching the rest. Try toggling one, then watch the rail.</div>
              </div>
              <div style={s("background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden")}>
                {manageModules.map((m, i) => (
                  <div key={i} style={s("display:flex;align-items:center;gap:14px;padding:15px 18px;border-top:1px solid var(--lineSoft)")}>
                    <span className="ms" style={s("font-size:22px;color:var(--accent)")}>{m.icon}</span>
                    <div style={s("flex:1")}><div style={s("font-size:14px;font-weight:600")}>{m.label}</div><div style={s("font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--ink3);margin-top:2px")}>visible to: {m.roles}</div></div>
                    {m.fixed && <span style={s("font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.6px;text-transform:uppercase;color:var(--ink3);background:var(--inset);padding:5px 9px;border-radius:6px")}>core</span>}
                    {m.toggleable && (
                      <button onClick={m.onToggle} style={merge("width:46px;height:26px;border-radius:13px;border:none;cursor:pointer;position:relative;transition:background .15s", { background: m.swBg })}><span style={merge("position:absolute;top:3px;width:20px;height:20px;border-radius:50%;background:#fff;transition:left .15s", { left: m.swX })} /></button>
                    )}
                  </div>
                ))}
              </div>
            </div>
          )}

        </main>
      </div>
    </div>
  );
}

/* ============================ AUTH GATE ============================ */
const loginCard = { width: 360, background: '#14181F', border: '1px solid #222A33', borderRadius: 16, padding: 26, textAlign: 'center', boxShadow: '0 24px 60px rgba(0,0,0,.45)' };
const loginInp = { width: '100%', boxSizing: 'border-box', padding: '11px 13px', margin: '6px 0', fontSize: 14, fontFamily: "'Familjen Grotesk',sans-serif", border: '1px solid #2A323C', borderRadius: 10, background: '#0F1318', color: '#E8EEF5', outline: 'none' };
const loginBtn = { width: '100%', marginTop: 10, padding: 12, border: 'none', borderRadius: 10, background: '#E0993F', color: '#0B0D11', fontWeight: 700, fontSize: 14, fontFamily: "'Familjen Grotesk',sans-serif", cursor: 'pointer' };

function Login({ onGoogle, onEmail, busy, err }) {
  const [email, setEmail] = useState('');
  const [pw, setPw] = useState('');
  return (
    <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 22, background: 'radial-gradient(120% 120% at 50% 0%, #11161D 0%, #0B0D11 60%)', padding: '32px 16px', fontFamily: "'Familjen Grotesk',sans-serif" }}>
      <div style={loginCard}>
        <svg width="38" height="36" viewBox="0 0 30 28" fill="none" style={{ marginBottom: 8 }}>
          <polygon points="7,3 19,3 15,9 3,9" fill="#E8EEF5" /><polygon points="7,11 19,11 15,17 3,17" fill="#E3A53F" /><polygon points="7,19 19,19 15,25 3,25" fill="#E0993F" /><rect x="23" y="3" width="4" height="22" rx="1" fill="#E0993F" />
        </svg>
        <div style={{ fontSize: 21, fontWeight: 600, color: '#E8EEF5' }}>RaDa Intelligence</div>
        <div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10, letterSpacing: 1.4, textTransform: 'uppercase', color: '#5F6A78', margin: '4px 0 20px' }}>Authorized access</div>
        <button onClick={onGoogle} disabled={busy} style={{ ...loginBtn, background: '#E8EEF5', color: '#16201E', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, marginTop: 0 }}>
          <svg width="16" height="16" viewBox="0 0 48 48"><path fill="#4285F4" d="M45 24c0-1.6-.1-2.8-.4-4H24v7.6h12c-.2 2-1.7 5-4.9 7l-.04.3 7 5.4.5.05C43 42.3 45 33.9 45 24z" /><path fill="#34A853" d="M24 46c5.9 0 10.9-1.9 14.5-5.3l-6.9-5.4c-1.9 1.3-4.4 2.2-7.6 2.2-5.8 0-10.7-3.9-12.5-9.2l-.3.02-7.2 5.6-.1.3C7.7 41.1 15.2 46 24 46z" /><path fill="#FBBC05" d="M11.5 28.3c-.5-1.4-.7-2.8-.7-4.3s.3-3 .7-4.3l-.01-.3-7.3-5.7-.2.1C2.6 16.8 2 20.3 2 24s.6 7.2 1.7 10.2l7.8-6z" /><path fill="#EA4335" d="M24 10.7c4.1 0 6.9 1.8 8.5 3.3l6.2-6C34.9 4.5 29.9 2 24 2 15.2 2 7.7 6.9 4 14.1l7.8 6c1.8-5.3 6.7-9.4 12.2-9.4z" /></svg>
          Sign in with Google (GSG staff)
        </button>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '16px 0 6px', color: '#5F6A78', fontSize: 11 }}>
          <div style={{ flex: 1, height: 1, background: '#222A33' }} /> or institution login <div style={{ flex: 1, height: 1, background: '#222A33' }} />
        </div>
        <input value={email} onChange={e => setEmail(e.target.value)} placeholder="Email" style={loginInp} />
        <input value={pw} onChange={e => setPw(e.target.value)} type="password" placeholder="Password" style={loginInp}
          onKeyDown={e => { if (e.key === 'Enter') onEmail(email, pw); }} />
        <button onClick={() => onEmail(email, pw)} disabled={busy} style={loginBtn}>{busy ? '…' : 'Sign in'}</button>
        {err && <div style={{ marginTop: 12, fontSize: 12.5, color: '#E0573A', lineHeight: 1.4 }}>{err}</div>}
        <div style={{ marginTop: 16, fontSize: 10.5, color: '#5F6A78', lineHeight: 1.5 }}>Aggregate portfolio intelligence · no farmer PII. Access is provisioned per institution.</div>
      </div>

      {/* Public company strip — the only logged-out surface of this console. Added
          2026-07-14 for the Google for Startups eligibility pass (team + Google
          Cloud stack + commercial model visible without sign-in, no overclaims). */}
      <div style={{ width: 360, maxWidth: '92vw', display: 'flex', flexDirection: 'column', gap: 12, textAlign: 'center' }}>
        <div style={{ fontSize: 12, color: '#97A3B2', lineHeight: 1.6 }}>
          RaDa Intelligence is the proprietary geospatial-AI platform of Chemeli V.8 — free for
          farmers; institutions, insurers, cooperatives and EU-compliance (EUDR) buyers subscribe to
          aggregate, farm-verified field intelligence.
        </div>
        <div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 9.5, letterSpacing: 1, textTransform: 'uppercase', color: '#5F6A78' }}>
          Built on Google Cloud · Earth Engine · BigQuery · Vertex AI · Cloud Functions
        </div>
        <div style={{ fontSize: 11, color: '#7A8595' }}>
          George Too — Founder &nbsp;·&nbsp; Eliza Chepkorir — COO&nbsp;
          <a href="mailto:eliza@radaintelligence.com" style={{ color: '#E0993F', textDecoration: 'none' }}>eliza@radaintelligence.com</a>
        </div>
        <a href="https://www.radaintelligence.com" style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10, color: '#7A8595', textDecoration: 'none' }}>
          www.radaintelligence.com →
        </a>
      </div>
    </div>
  );
}

function App() {
  const [user, setUser] = useState(undefined);
  const [data, setData] = useState(null);
  const [err, setErr] = useState('');
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    try { if (!firebase.apps.length) firebase.initializeApp(FB_CONFIG); } catch (e) {}
    return firebase.auth().onAuthStateChanged(u => {
      setUser(u || null); setData(null); setErr('');
      if (!u) return;
      u.getIdToken()
        .then(tok => fetch(PORTFOLIO_URL, { headers: { Authorization: 'Bearer ' + tok } }))
        .then(r => r.json().then(j => ({ ok: r.ok, j })))
        .then(({ ok, j }) => { if (ok) setData(j); else setErr((j && j.error) || 'access denied'); })
        .catch(e => setErr(String(e)));
    });
  }, []);

  const onGoogle = () => {
    setErr(''); setBusy(true);
    const p = new firebase.auth.GoogleAuthProvider();
    firebase.auth().signInWithPopup(p)
      .then(() => setBusy(false))
      .catch(e => { setBusy(false); setErr(e.code === 'auth/operation-not-allowed' ? 'Google sign-in is not enabled yet — use the institution login below.' : (e.code || e.message)); });
  };
  const onEmail = (email, pw) => {
    if (!email || !pw) { setErr('Enter email and password.'); return; }
    setErr(''); setBusy(true);
    firebase.auth().signInWithEmailAndPassword(email.trim(), pw)
      .then(() => setBusy(false))
      .catch(e => { setBusy(false); setErr(e.code || e.message || 'sign-in failed'); });
  };
  const signOut = () => firebase.auth().signOut();

  const wrap = msg => <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#97A3B2', background: '#0B0D11', textAlign: 'center', padding: 20, fontFamily: "'Familjen Grotesk',sans-serif" }}>{msg}</div>;
  if (user === undefined) return wrap('Loading…');
  if (!user) return <Login onGoogle={onGoogle} onEmail={onEmail} busy={busy} err={err} />;
  if (err) return wrap(
    <span>Signed in as {user.email || 'user'}, but access was denied ({err}). This view is for GSG staff or provisioned institutions.{' '}
      <span style={{ textDecoration: 'underline', cursor: 'pointer', color: '#E0993F' }} onClick={signOut}>Sign out</span>
    </span>
  );
  // data may still be loading — Console renders with the live indicator + design fallback until it arrives
  return <Console live={data} user={user} onSignOut={signOut} />;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
