// Sign-in + Account pages — magic-link auth via Supabase, no passwords.
//
// Flow:
//   1. User enters email on /#/signin → sendMagicLink → email shows up.
//   2. User clicks link in email → Supabase verifies → redirects back to
//      our origin with #access_token=... in URL hash.
//   3. supabase-client.js's captureAuthFromHash() catches that on script
//      load, stores the session in localStorage, and rewrites the URL
//      to /#/account so this page renders for them as a logged-in user.
//   4. Sign-out clears localStorage and revokes the token server-side.

const { useState: useStateAuth, useEffect: useEffectAuth, useRef: useRefAuth, useMemo: useMemoAuth } = React;

// =============================================================================
// MiniMap — lightweight reusable Leaflet map for dashboard breakouts.
// Used by the wildfire breakout, the California CHP breakout, and any other
// "show me where these points are" moment. Takes a flat array of points,
// renders divIcon dot markers, sized + colored per point. Auto-fits bounds
// when points are present; falls back to a default center/zoom otherwise.
// =============================================================================
function MiniMap({ points, height, defaultCenter, defaultZoom, attribution, dark, fitBounds }) {
  const mapRef = useRefAuth(null);
  const elRef = useRefAuth(null);
  const layerRef = useRefAuth(null);

  // Mount once.
  useEffectAuth(() => {
    if (!elRef.current || !window.L || mapRef.current) return;
    const map = window.L.map(elRef.current, {
      zoomControl: true, scrollWheelZoom: false,
      attributionControl: !!attribution,
      worldCopyJump: false,
      minZoom: 3, maxZoom: 12,
      fadeAnimation: false, zoomAnimation: false, markerZoomAnimation: false,
    }).setView(defaultCenter || [39.5, -98.5], defaultZoom || 4);
    const tileUrl = dark
      ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
      : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
    window.L.tileLayer(tileUrl, {
      attribution: attribution || "© OpenStreetMap · © CARTO",
      subdomains: "abcd", maxZoom: 12, noWrap: true,
    }).addTo(map);
    mapRef.current = map;
    setTimeout(() => map.invalidateSize(), 80);
    return () => { map.remove(); mapRef.current = null; };
  }, []);

  // Re-build the layer whenever the points change.
  useEffectAuth(() => {
    const map = mapRef.current;
    if (!map || !window.L) return;
    if (layerRef.current && map.hasLayer(layerRef.current)) {
      map.removeLayer(layerRef.current);
    }
    const valid = (points || []).filter(p => p.lat != null && p.lng != null);
    const markers = valid.map(p => {
      const size = p.size || 9;
      const color = p.color || "oklch(0.50 0.16 250)";
      const html = `<span style="display:block;width:${size}px;height:${size}px;border-radius:50%;background:${color};border:1.5px solid rgba(255,255,255,0.9);box-shadow:0 0 4px rgba(0,0,0,0.4)"></span>`;
      const icon = window.L.divIcon({
        className: "minimap-dot", html,
        iconSize: [size + 4, size + 4],
        iconAnchor: [(size + 4) / 2, (size + 4) / 2],
      });
      const m = window.L.marker([p.lat, p.lng], { icon, riseOnHover: true });
      if (p.popupHtml) m.bindPopup(p.popupHtml);
      return m;
    });
    const lg = window.L.layerGroup(markers);
    lg.addTo(map);
    layerRef.current = lg;
    if (fitBounds && valid.length >= 2) {
      try {
        map.fitBounds(window.L.latLngBounds(valid.map(v => [v.lat, v.lng])).pad(0.15));
      } catch {}
    }
  }, [points, fitBounds]);

  return (
    <div
      ref={elRef}
      style={{
        height: height || 320,
        width: "100%",
        borderRadius: 6,
        border: "1px solid var(--rule)",
        overflow: "hidden",
        background: dark ? "#0a0d12" : "var(--paper-2)",
      }}
    />
  );
}

function SignInPage({ onNav }) {
  const [email, setEmail] = useStateAuth("");
  const [busy, setBusy] = useStateAuth(false);
  const [sent, setSent] = useStateAuth(false);
  const [error, setError] = useStateAuth(null);

  async function submit(e) {
    e.preventDefault();
    if (!email.trim() || !email.includes("@")) { setError("Please enter a valid email address."); return; }
    setBusy(true); setError(null);
    try {
      await window.SI_DB.auth.sendMagicLink(email.trim());
      setSent(true);
    } catch (err) {
      setError(String(err.message || err));
    }
    setBusy(false);
  }

  return (
    <div className="page-insights-v2 article-detail">
      <section className="article-detail-hero">
        <div className="article-detail-inner" style={{ maxWidth: 480 }}>
          <button className="article-back" onClick={() => onNav("home")}>← Back home</button>
          <div className="article-meta-row">
            <span className="insight-tag">Sign in</span>
          </div>
          <h1 className="article-title-big" style={{ fontSize: 36 }}>
            Sign in with your email.
          </h1>
          <p className="article-dek">
            No passwords. We send a one-time link; click it and you're in.
            New to the site? You'll be signed up automatically.
          </p>

          {sent ? (
            <div className="auth-sent">
              <div className="auth-sent-h">✓ Check your inbox.</div>
              <p>We sent a sign-in link to <strong>{email}</strong>. Click the link and you'll land back here, signed in.</p>
              <p style={{ fontSize: 13, color: "var(--ink-soft)" }}>
                The link expires in 1 hour. Didn't arrive? Check spam, or
                <button className="link-button" onClick={() => { setSent(false); }}> try again</button>.
              </p>
            </div>
          ) : (
            <form onSubmit={submit} className="auth-form">
              <label className="auth-label">Email</label>
              <input
                type="email"
                required
                className="auth-input"
                placeholder="you@company.com"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                autoFocus
              />
              {error && <div className="auth-error">{error}</div>}
              <button type="submit" className="btn-primary auth-submit" disabled={busy}>
                {busy ? "Sending…" : "Send sign-in link →"}
              </button>
              <p className="auth-fineprint">
                We'll never share your email or send marketing without your consent.
                See the <a href="#/privacy" onClick={(e) => { e.preventDefault(); onNav("privacy"); }}>privacy policy</a>.
              </p>
            </form>
          )}
        </div>
      </section>
    </div>
  );
}

// =============================================================
// Helpers used by the dashboard tiles + weather strip
// =============================================================
function weatherIconFor(short) {
  const s = (short || "").toLowerCase();
  if (s.includes("snow") || s.includes("blizzard")) return "❄️";
  if (s.includes("thunder") || s.includes("storm")) return "⛈";
  if (s.includes("rain") || s.includes("shower")) return "🌧";
  if (s.includes("drizzle")) return "🌦";
  if (s.includes("fog") || s.includes("haze") || s.includes("mist")) return "🌫";
  if (s.includes("wind"))  return "💨";
  if (s.includes("partly") || s.includes("mostly cloudy")) return "⛅";
  if (s.includes("cloud") || s.includes("overcast")) return "☁️";
  if (s.includes("clear") || s.includes("sunny") || s.includes("fair")) return "☀️";
  return "🌡";
}

// Bucket rows into N daily counts ending today (UTC). Used for tile sparklines.
function dailyBuckets(rows, days, predicate) {
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  const out = new Array(days).fill(0);
  rows.forEach((r) => {
    if (predicate && !predicate(r)) return;
    const t = new Date(r.posted_at || r.created_at);
    if (isNaN(t.getTime())) return;
    const dayIdx = days - 1 - Math.floor((today.getTime() - t.getTime()) / 86400000);
    if (dayIdx >= 0 && dayIdx < days) out[dayIdx] += 1;
  });
  return out;
}

// Per-tile dashboard stat — accent strip on top, big number, optional
// delta vs prior period, optional 7-day sparkline.
function StatTile({ label, value, sub, accent = "blue", spark, delta, onClick }) {
  const w = 88, h = 22;
  const max = spark && spark.length ? Math.max(1, ...spark) : 0;
  const bars = spark && spark.length
    ? spark.map((v, i) => {
        const bw = w / spark.length - 2;
        const bh = max > 0 ? (v / max) * h : 0;
        const x = i * (w / spark.length) + 1;
        const y = h - bh;
        return <rect key={i} x={x.toFixed(1)} y={y.toFixed(1)} width={bw.toFixed(1)} height={Math.max(1, bh).toFixed(1)} rx="1" />;
      })
    : null;
  const dPct = delta && delta.prev > 0 ? Math.round(((delta.curr - delta.prev) / delta.prev) * 100) : null;
  const dDir = dPct == null ? null : dPct > 0 ? "▲" : dPct < 0 ? "▼" : "—";
  const dColor = dPct == null ? "var(--ink-soft)"
    : dPct > 0 ? "oklch(0.55 0.18 25)"
    : dPct < 0 ? "oklch(0.50 0.14 145)"
    : "var(--ink-soft)";
  return (
    <button className="dash-stat" data-accent={accent} onClick={onClick}>
      <div className="dash-stat-strip" />
      <div className="dash-stat-row">
        <div>
          <div className="dash-stat-num" data-accent={accent}>{value}</div>
          <div className="dash-stat-label">{label}</div>
        </div>
        {spark && spark.length > 0 && (
          <svg className="dash-stat-spark" data-accent={accent} width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
            {bars}
          </svg>
        )}
      </div>
      <div className="dash-stat-foot">
        {dPct != null && (
          <span className="dash-stat-delta" style={{ color: dColor }}>
            {dDir} {Math.abs(dPct)}% <span className="dash-stat-delta-l">vs prior</span>
          </span>
        )}
        {sub && <span className="dash-stat-sub">{sub}</span>}
      </div>
    </button>
  );
}

// Slim horizontal weather strip — one cell per featured metro, today's
// high temp + condition icon. Sits directly below the command bar.
function DashWeatherStrip({ forecast, cities, subscribedIds, activeCityId, onCityClick }) {
  if (!cities || !cities.length) return null;
  // Prefer subscribed metros at the front, then the rest of the featured 12.
  const subSet = new Set(subscribedIds || []);
  const ordered = [
    ...cities.filter(c => subSet.has(c.id)),
    ...cities.filter(c => !subSet.has(c.id)),
  ].slice(0, 12);

  // For each metro, find the first daytime forecast period (temp_high present),
  // falling back to the first row at all if none.
  const byCity = {};
  (forecast || []).forEach(f => {
    if (!f.city_id) return;
    if (!byCity[f.city_id]) byCity[f.city_id] = [];
    byCity[f.city_id].push(f);
  });
  Object.values(byCity).forEach(arr => {
    arr.sort((a, b) => new Date(a.effective_at || 0) - new Date(b.effective_at || 0));
  });

  function dayHigh(cityId) {
    const arr = byCity[cityId] || [];
    const day = arr.find(f => f.temp_high != null) || arr[0];
    return day || null;
  }

  return (
    <section className="dash-weather-strip">
      <div className="dash-weather-strip-inner">
        <div className="dash-weather-strip-label">
          <span className="dws-label-eyebrow">Now across the network</span>
          <span className="dws-label-sub">12 metros · live</span>
        </div>
        <div className="dash-weather-strip-cells">
          {ordered.map((c) => {
            const f = dayHigh(c.id);
            const isSub = subSet.has(c.id);
            const isActive = activeCityId === c.id;
            const high = f && f.temp_high != null ? `${f.temp_high}°` : "—";
            const cond = (f && (f.short_forecast || "")) || "";
            const cls = `dws-cell${isSub ? " dws-cell-sub" : ""}${isActive ? " dws-cell-active" : ""}`;
            return (
              <div
                key={c.id}
                className={cls}
                title={cond}
                role="button"
                tabIndex={0}
                onClick={() => onCityClick && onCityClick(isActive ? null : c.id)}
                onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onCityClick && onCityClick(isActive ? null : c.id); } }}
              >
                <span className="dws-icon">{weatherIconFor(cond)}</span>
                <span className="dws-city">{c.name}</span>
                <span className="dws-temp">{high}</span>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

// =============================================================
// SMALL-PACKAGE HUB MAP — Leaflet pins for the major small-package
// distribution hubs, colored by recent signal severity at the hub's
// metro. Shows shippers/ops which corner of the country to watch.
// Hub list is hand-curated — every major hub a parcel passes
// through on its way through the network.
// =============================================================
const SMALL_PKG_HUBS = [
  // UPS network — global + major US sorts
  { id: "ups-sdf",  name: "UPS Worldport",            carrier: "ups",   carrierLabel: "UPS",   cityId: "louisville",   city: "Louisville, KY",  lat: 38.1740, lng: -85.7390 },
  { id: "ups-ord",  name: "UPS CACH (Hodgkins)",      carrier: "ups",   carrierLabel: "UPS",   cityId: "chicago",      city: "Chicago, IL",     lat: 41.7686, lng: -87.8467 },
  { id: "ups-mhv",  name: "UPS Mid-Hudson",           carrier: "ups",   carrierLabel: "UPS",   cityId: "new-york",     city: "Newburgh, NY",    lat: 41.5113, lng: -74.1051 },
  { id: "ups-atl",  name: "UPS Pleasantdale Hub",     carrier: "ups",   carrierLabel: "UPS",   cityId: "atlanta",      city: "Atlanta, GA",     lat: 33.8400, lng: -84.4830 },
  { id: "ups-mes",  name: "UPS Mesquite Sort",        carrier: "ups",   carrierLabel: "UPS",   cityId: "dallas",       city: "Mesquite, TX",    lat: 32.7480, lng: -96.6240 },
  { id: "ups-phx",  name: "UPS Phoenix Hub",          carrier: "ups",   carrierLabel: "UPS",   cityId: "phoenix",      city: "Phoenix, AZ",     lat: 33.4360, lng: -112.0080 },
  { id: "ups-clt",  name: "UPS Charlotte Hub",        carrier: "ups",   carrierLabel: "UPS",   cityId: "atlanta",      city: "Charlotte, NC",   lat: 35.2150, lng: -80.9430 },
  { id: "ups-phl",  name: "UPS Philadelphia Hub",     carrier: "ups",   carrierLabel: "UPS",   cityId: "philly",       city: "Philadelphia, PA",lat: 39.8770, lng: -75.2380 },
  { id: "ups-mia",  name: "UPS Miami Gateway",        carrier: "ups",   carrierLabel: "UPS",   cityId: "miami",        city: "Miami, FL",       lat: 25.7919, lng: -80.2870 },
  // FedEx Express + Ground
  { id: "fdx-mem",  name: "FedEx World Hub",          carrier: "fedex", carrierLabel: "FedEx", cityId: "memphis",      city: "Memphis, TN",     lat: 35.0467, lng: -89.9787 },
  { id: "fdx-ind",  name: "FedEx Indianapolis Hub",   carrier: "fedex", carrierLabel: "FedEx", cityId: "indianapolis", city: "Indianapolis, IN",lat: 39.7173, lng: -86.2944 },
  { id: "fdx-oak",  name: "FedEx Oakland Hub",        carrier: "fedex", carrierLabel: "FedEx", cityId: "oakland",      city: "Oakland, CA",     lat: 37.7212, lng: -122.2207 },
  { id: "fdx-ewr",  name: "FedEx Newark Hub",         carrier: "fedex", carrierLabel: "FedEx", cityId: "new-york",     city: "Newark, NJ",      lat: 40.6925, lng: -74.1687 },
  { id: "fdx-afw",  name: "FedEx Alliance (Ft Worth)",carrier: "fedex", carrierLabel: "FedEx", cityId: "dallas",       city: "Fort Worth, TX",  lat: 32.9870, lng: -97.3187 },
  { id: "fdxg-olv", name: "FedEx Ground Olive Branch",carrier: "fedex", carrierLabel: "FedEx", cityId: "memphis",      city: "Olive Branch, MS",lat: 34.9675, lng: -89.8295 },
  { id: "fdx-grb",  name: "FedEx Ground Greensboro",  carrier: "fedex", carrierLabel: "FedEx", cityId: "atlanta",      city: "Greensboro, NC",  lat: 36.1090, lng: -79.9370 },
  { id: "fdx-stl",  name: "FedEx Express STL Hub",    carrier: "fedex", carrierLabel: "FedEx", cityId: "indianapolis", city: "St. Louis, MO",   lat: 38.7480, lng: -90.3600 },
  { id: "fdx-msp",  name: "FedEx Express MSP",        carrier: "fedex", carrierLabel: "FedEx", cityId: "chicago",      city: "Minneapolis, MN", lat: 44.8870, lng: -93.2230 },
  { id: "fdx-mia",  name: "FedEx Miami Gateway",      carrier: "fedex", carrierLabel: "FedEx", cityId: "miami",        city: "Miami, FL",       lat: 25.7790, lng: -80.2950 },
  // Amazon Air — operates 75+ hubs; flagging the top throughput nodes
  { id: "amzn-cvg", name: "Amazon Air CVG (Prime Air HQ)", carrier: "amazon",carrierLabel: "Amazon",cityId: "cincinnati",   city: "Cincinnati, KY",  lat: 39.0533, lng: -84.6611 },
  { id: "amzn-sdf", name: "Amazon Air SDF",           carrier: "amazon",carrierLabel: "Amazon",cityId: "louisville",   city: "Louisville, KY",  lat: 38.1742, lng: -85.7390 },
  { id: "amzn-ind", name: "Amazon Air IND",           carrier: "amazon",carrierLabel: "Amazon",cityId: "indianapolis", city: "Indianapolis, IN",lat: 39.7173, lng: -86.2944 },
  { id: "amzn-rfd", name: "Amazon Air RFD (Rockford)",carrier: "amazon",carrierLabel: "Amazon",cityId: "chicago",      city: "Rockford, IL",    lat: 42.1947, lng: -89.0972 },
  { id: "amzn-lex", name: "Amazon Air LEX",           carrier: "amazon",carrierLabel: "Amazon",cityId: "louisville",   city: "Lexington, KY",   lat: 38.0365, lng: -84.6059 },
  { id: "amzn-mia", name: "Amazon Air MIA",           carrier: "amazon",carrierLabel: "Amazon",cityId: "miami",        city: "Miami, FL",       lat: 25.7959, lng: -80.2870 },
  // DHL — North America hub + secondary nodes
  { id: "dhl-cvg",  name: "DHL Americas Hub (CVG)",   carrier: "dhl",   carrierLabel: "DHL",   cityId: "cincinnati",   city: "Cincinnati, KY",  lat: 39.0533, lng: -84.6611 },
  { id: "dhl-jfk",  name: "DHL JFK Gateway",          carrier: "dhl",   carrierLabel: "DHL",   cityId: "new-york",     city: "JFK / Queens, NY",lat: 40.6500, lng: -73.7820 },
  { id: "dhl-lax",  name: "DHL LAX Gateway",          carrier: "dhl",   carrierLabel: "DHL",   cityId: "los-angeles",  city: "Los Angeles, CA", lat: 33.9426, lng: -118.4081 },
  { id: "dhl-mia",  name: "DHL Miami Gateway",        carrier: "dhl",   carrierLabel: "DHL",   cityId: "miami",        city: "Miami, FL",       lat: 25.7960, lng: -80.2700 },
  // OnTrac (post-LaserShip merger) — west + new east coverage
  { id: "ontrac-anh", name: "OnTrac Anaheim",         carrier: "ontrac",carrierLabel: "OnTrac",cityId: "los-angeles",  city: "Anaheim, CA",     lat: 33.8350, lng: -117.9140 },
  { id: "ontrac-sac", name: "OnTrac Sacramento",      carrier: "ontrac",carrierLabel: "OnTrac",cityId: "los-angeles",  city: "Sacramento, CA",  lat: 38.5300, lng: -121.4360 },
  { id: "ontrac-phx", name: "OnTrac Phoenix",         carrier: "ontrac",carrierLabel: "OnTrac",cityId: "phoenix",      city: "Phoenix, AZ",     lat: 33.4250, lng: -112.0710 },
  { id: "ontrac-sea", name: "OnTrac Seattle (Auburn)",carrier: "ontrac",carrierLabel: "OnTrac",cityId: "seattle",      city: "Auburn, WA",      lat: 47.2980, lng: -122.2270 },
  { id: "ontrac-den", name: "OnTrac Denver",          carrier: "ontrac",carrierLabel: "OnTrac",cityId: "denver",       city: "Aurora, CO",      lat: 39.7530, lng: -104.7950 },
  { id: "ontrac-edi", name: "OnTrac Edison NJ",       carrier: "ontrac",carrierLabel: "OnTrac",cityId: "new-york",     city: "Edison, NJ",      lat: 40.5260, lng: -74.4070 },
  { id: "ontrac-atl", name: "OnTrac Atlanta",         carrier: "ontrac",carrierLabel: "OnTrac",cityId: "atlanta",      city: "Atlanta, GA",     lat: 33.6580, lng: -84.4220 },
  { id: "ontrac-orl", name: "OnTrac Orlando",         carrier: "ontrac",carrierLabel: "OnTrac",cityId: "miami",        city: "Orlando, FL",     lat: 28.4910, lng: -81.4170 },
  // LSO (Texas/Southeast regional)
  { id: "lso-aus",  name: "LSO Austin",               carrier: "lso",   carrierLabel: "LSO",   cityId: "houston",      city: "Austin, TX",      lat: 30.2940, lng: -97.7370 },
  { id: "lso-dal",  name: "LSO Dallas Hub",           carrier: "lso",   carrierLabel: "LSO",   cityId: "dallas",       city: "Dallas, TX",      lat: 32.7920, lng: -96.7770 },
  { id: "lso-hou",  name: "LSO Houston Hub",          carrier: "lso",   carrierLabel: "LSO",   cityId: "houston",      city: "Houston, TX",     lat: 29.7700, lng: -95.3800 },
  // GLS US (was GSO) — west + central
  { id: "gls-fre",  name: "GLS Fresno (Hub)",         carrier: "gls",   carrierLabel: "GLS US",cityId: "los-angeles",  city: "Fresno, CA",      lat: 36.7393, lng: -119.7846 },
  { id: "gls-las",  name: "GLS Las Vegas",            carrier: "gls",   carrierLabel: "GLS US",cityId: "phoenix",      city: "Las Vegas, NV",   lat: 36.0840, lng: -115.1530 },
  // Spee-Dee Delivery (Upper Midwest regional)
  { id: "spee-stp", name: "Spee-Dee St. Cloud",       carrier: "speedee",carrierLabel:"Spee-Dee",cityId:"chicago",     city: "St. Cloud, MN",   lat: 45.5580, lng: -94.1620 },
  // USPS — primary network distribution centers
  { id: "usps-mem", name: "USPS Memphis NDC",         carrier: "usps",  carrierLabel: "USPS",  cityId: "memphis",      city: "Memphis, TN",     lat: 35.1142, lng: -89.9908 },
  { id: "usps-jfk", name: "USPS NY Distribution",     carrier: "usps",  carrierLabel: "USPS",  cityId: "new-york",     city: "New York, NY",    lat: 40.6520, lng: -73.7920 },
  { id: "usps-ord", name: "USPS Chicago NDC",         carrier: "usps",  carrierLabel: "USPS",  cityId: "chicago",      city: "Chicago, IL",     lat: 41.9750, lng: -87.9080 },
  { id: "usps-atl", name: "USPS Atlanta NDC",         carrier: "usps",  carrierLabel: "USPS",  cityId: "atlanta",      city: "Atlanta, GA",     lat: 33.6500, lng: -84.4060 },
  { id: "usps-dal", name: "USPS Dallas NDC",          carrier: "usps",  carrierLabel: "USPS",  cityId: "dallas",       city: "Coppell, TX",     lat: 32.9700, lng: -96.9510 },
  { id: "usps-lax", name: "USPS Los Angeles NDC",     carrier: "usps",  carrierLabel: "USPS",  cityId: "los-angeles",  city: "Bell, CA",        lat: 33.9700, lng: -118.1860 },
];

const HUB_CARRIER_COLOR = {
  ups:    "#7B5731",   // UPS brown
  fedex:  "#4D148C",   // FedEx purple
  amazon: "#FF9900",   // Amazon orange
  dhl:    "#FFCC00",   // DHL yellow
  usps:   "#004B87",   // USPS blue
  ontrac: "#FF4F00",   // OnTrac orange (post-LaserShip rebrand)
  lso:    "#0A4F8B",   // LSO blue
  gls:    "#004A8F",   // GLS blue
  speedee:"#D71920",   // Spee-Dee red
};

function HubStatusMap({ allIntel }) {
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);

  // Compute per-hub status: count high+critical signals tagged to this
  // hub's carrier + metro in the last 7 days.
  const now = Date.now();
  const day = 24 * 3600 * 1000;
  const hubStatus = SMALL_PKG_HUBS.map(h => {
    const matches = (allIntel || []).filter(r => {
      if (r.status !== "verified" || r.visibility !== "public") return false;
      if (now - new Date(r.created_at).getTime() > 7 * day) return false;
      const carrierMatch = (r.parsed_carrier || "").toLowerCase() === h.carrier;
      const cityMatch    = r.parsed_city === h.cityId;
      return carrierMatch && cityMatch;
    });
    const hi = matches.filter(r => r.severity === "high" || r.severity === "critical").length;
    const total = matches.length;
    let status = "clean";
    if (hi >= 2 || matches.some(r => r.severity === "critical")) status = "issue";
    else if (hi === 1 || total >= 3) status = "watch";
    return { ...h, total, hi, status, recent: matches.slice(0, 3) };
  });

  React.useEffect(() => {
    if (!ref.current || !window.L) return;
    if (!mapRef.current) {
      mapRef.current = window.L.map(ref.current, {
        zoomControl: true, scrollWheelZoom: false, attributionControl: true,
      }).setView([39.5, -95], 4);
      window.L.tileLayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        { attribution: "© OpenStreetMap", maxZoom: 18 }
      ).addTo(mapRef.current);
    }
    mapRef.current.eachLayer(l => { if ((l.options && l.options.pane === "markerPane") || l._icon) mapRef.current.removeLayer(l); });
    hubStatus.forEach(h => {
      const ringColor =
        h.status === "issue" ? "#dc2626" :
        h.status === "watch" ? "#d97706" :
        "#10b981";
      const fill = HUB_CARRIER_COLOR[h.carrier] || "#444";
      const icon = window.L.divIcon({
        className: "hub-pin",
        html: `<span class="hub-pin-dot" style="background:${fill};border:3px solid ${ringColor};box-shadow:0 0 0 2px rgba(255,255,255,0.85)"></span>`,
        iconSize: [22, 22], iconAnchor: [11, 11],
      });
      const m = window.L.marker([h.lat, h.lng], { icon }).addTo(mapRef.current);
      const recentHtml = h.recent.map(r =>
        `<div class="hub-popup-sig" style="font-size:11px;color:#555;margin-top:4px">
           <strong>${(r.severity||"low").toUpperCase()}</strong> · ${(r.parsed_intel_type||"").replace(/_/g," ")} · ${(r.raw_text||"").slice(0,140).replace(/'/g,"&#39;")}
         </div>`).join("");
      m.bindPopup(
        `<div class="hub-popup" style="min-width:240px;max-width:320px">
           <div style="font-family:var(--font-serif);font-size:16px;font-weight:600;letter-spacing:-0.01em;margin-bottom:2px">${h.name}</div>
           <div style="font-family:var(--font-mono);font-size:11px;letter-spacing:0.1em;text-transform:uppercase;color:#666">${h.carrierLabel} · ${h.city}</div>
           <div style="margin-top:8px;font-size:13px">
             <strong style="color:${ringColor}">${h.status === "issue" ? "Active issues" : h.status === "watch" ? "Watching" : "Clean"}</strong>
             · ${h.hi} high+critical / ${h.total} signals · 7d
           </div>
           ${recentHtml}
         </div>`
      );
    });
    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 50);
  }, [allIntel]);

  React.useEffect(() => () => {
    if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
  }, []);

  return (
    <>
      <style>{`
        .hub-map { width: 100%; height: 420px; border-radius: 8px; overflow: hidden;
          border: 1px solid var(--rule); }
        .hub-pin-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; }
        .hub-legend { display: flex; gap: 18px; flex-wrap: wrap; font-family: var(--font-mono);
          font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
          color: var(--ink-soft); margin-top: 12px; }
        .hub-legend-item { display: inline-flex; align-items: center; gap: 6px; }
        .hub-legend-dot { width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--ink); }
        .hub-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
          margin-top: 14px; }
        @media (max-width: 700px) { .hub-summary { grid-template-columns: 1fr; } }
        .hub-summary-card { background: #fff; border: 1px solid var(--rule); border-radius: 6px;
          padding: 12px 14px; }
        .hub-summary-num { font-family: var(--font-serif); font-size: 22px; line-height: 1.1;
          letter-spacing: -0.02em; }
        .hub-summary-label { font-family: var(--font-mono); font-size: 10px;
          letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft); margin-top: 4px; }
      `}</style>
      <div ref={ref} className="hub-map" />
      <div className="hub-legend">
        <span className="hub-legend-item"><span className="hub-legend-dot" style={{ borderColor: "#dc2626" }} /> Active issues</span>
        <span className="hub-legend-item"><span className="hub-legend-dot" style={{ borderColor: "#d97706" }} /> Watching</span>
        <span className="hub-legend-item"><span className="hub-legend-dot" style={{ borderColor: "#10b981" }} /> Clean</span>
        <span className="hub-legend-item">Center color = carrier brand</span>
      </div>
      <div className="hub-summary">
        <div className="hub-summary-card">
          <div className="hub-summary-num" style={{ color: "#dc2626" }}>
            {hubStatus.filter(h => h.status === "issue").length}
          </div>
          <div className="hub-summary-label">Hubs with active issues</div>
        </div>
        <div className="hub-summary-card">
          <div className="hub-summary-num" style={{ color: "#d97706" }}>
            {hubStatus.filter(h => h.status === "watch").length}
          </div>
          <div className="hub-summary-label">Watching</div>
        </div>
        <div className="hub-summary-card">
          <div className="hub-summary-num" style={{ color: "#10b981" }}>
            {hubStatus.filter(h => h.status === "clean").length}
          </div>
          <div className="hub-summary-label">Clean</div>
        </div>
      </div>
    </>
  );
}

// =============================================================
// CRUDE PRODUCTION MAP — US states colored by monthly crude output
// (EIA 914 survey). Permian dominance — TX + NM = 56% of total US.
// Click a state → exact kbpd value.
// =============================================================
const STATE_NAME_TO_CODE_PROD = {
  "Alabama":"AL","Alaska":"AK","Arizona":"AZ","Arkansas":"AR","California":"CA",
  "Colorado":"CO","Connecticut":"CT","Delaware":"DE","Florida":"FL","Georgia":"GA",
  "Hawaii":"HI","Idaho":"ID","Illinois":"IL","Indiana":"IN","Iowa":"IA",
  "Kansas":"KS","Kentucky":"KY","Louisiana":"LA","Maine":"ME","Maryland":"MD",
  "Massachusetts":"MA","Michigan":"MI","Minnesota":"MN","Mississippi":"MS",
  "Missouri":"MO","Montana":"MT","Nebraska":"NE","Nevada":"NV","New Hampshire":"NH",
  "New Jersey":"NJ","New Mexico":"NM","New York":"NY","North Carolina":"NC",
  "North Dakota":"ND","Ohio":"OH","Oklahoma":"OK","Oregon":"OR","Pennsylvania":"PA",
  "Rhode Island":"RI","South Carolina":"SC","South Dakota":"SD","Tennessee":"TN",
  "Texas":"TX","Utah":"UT","Vermont":"VT","Virginia":"VA","Washington":"WA",
  "West Virginia":"WV","Wisconsin":"WI","Wyoming":"WY","District of Columbia":"DC",
};

function CrudeProductionMap({ fuel }) {
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);
  const layerRef = React.useRef(null);
  const [geo, setGeo] = React.useState(null);

  // Build state→kbpd from fuel_signals where series='crude_production_by_state'
  const byState = React.useMemo(() => {
    const out = {};
    (fuel || []).forEach(f => {
      if (f.series === "crude_production_by_state") {
        out[f.region] = parseFloat(f.value_num) || 0;
      }
    });
    return out;
  }, [fuel]);

  const totalProd = React.useMemo(() =>
    Object.values(byState).reduce((s, v) => s + v, 0)
  , [byState]);

  React.useEffect(() => {
    let alive = true;
    fetch(US_STATES_GEOJSON_URL)
      .then(r => r.ok ? r.json() : null)
      .then(g => { if (alive) setGeo(g); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  React.useEffect(() => {
    if (!ref.current || !window.L || !geo) return;
    if (!mapRef.current) {
      mapRef.current = window.L.map(ref.current, {
        zoomControl: true, scrollWheelZoom: false, attributionControl: true,
      }).setView([39.5, -97], 4);
      window.L.tileLayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        { attribution: "© OpenStreetMap", maxZoom: 18, opacity: 0.45 }
      ).addTo(mapRef.current);
    }
    if (layerRef.current) {
      mapRef.current.removeLayer(layerRef.current);
      layerRef.current = null;
    }

    const max = Math.max(1, ...Object.values(byState));
    function styleFn(feature) {
      const code = STATE_NAME_TO_CODE_PROD[feature.properties.name];
      const v = byState[code] || 0;
      // Log-style scale for production (TX dwarfs everything; we want visible
      // gradient even for small producers).
      const t = v <= 0 ? 0 : 0.15 + 0.70 * Math.pow(v / max, 0.55);
      return {
        fillColor:  "#0E5440",          // crude green — refining/pipeline traditionally
        fillOpacity:t,
        weight:     1,
        color:      "#fff",
        opacity:    1,
      };
    }
    layerRef.current = window.L.geoJSON(geo, {
      style: styleFn,
      onEachFeature: (feature, layer) => {
        const code = STATE_NAME_TO_CODE_PROD[feature.properties.name];
        const v = byState[code] || 0;
        const pct = totalProd > 0 ? (v / totalProd) * 100 : 0;
        const lbl = v > 0
          ? `<strong>${feature.properties.name}</strong><br/><span style="font-family:monospace;font-size:11px">${v.toLocaleString()} kbpd · ${pct.toFixed(1)}% of US</span>`
          : `<strong>${feature.properties.name}</strong><br/><span style="font-family:monospace;font-size:11px;color:#888">no production reported</span>`;
        layer.bindTooltip(lbl, { sticky: true });
      },
    }).addTo(mapRef.current);
    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 50);
  }, [geo, byState, totalProd]);

  React.useEffect(() => () => {
    if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
  }, []);

  // Top-5 producing states for the legend
  const top5 = Object.entries(byState).sort((a, b) => b[1] - a[1]).slice(0, 5);

  return (
    <div>
      <div ref={ref} style={{ width: "100%", height: 420, borderRadius: 8, overflow: "hidden", border: "1px solid var(--rule)" }} />
      <div style={{ marginTop: 12, display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 8 }}>
        {top5.map(([code, v], i) => (
          <div key={code} style={{ background: "#fff", border: "1px solid var(--rule)", borderRadius: 6, padding: "10px 12px" }}>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em", color: "var(--ink-soft)", textTransform: "uppercase" }}>
              #{i + 1} · {code}
            </div>
            <div style={{ fontFamily: "var(--font-serif)", fontSize: 22, letterSpacing: "-0.02em" }}>
              {v.toLocaleString()}
            </div>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-soft)", marginTop: 2 }}>
              kbpd · {totalProd > 0 ? ((v / totalProd) * 100).toFixed(1) : "0"}% US
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}


// =============================================================
// CARRIER GRAVITY MAP — state-by-state density per carrier.
// The intelligence the freight industry wonders how we got. Two modes:
//   • Single carrier — color states by how much that carrier shows up
//                       in our last-90-day operator-network signal
//   • Dominant per state — every state gets its top-by-volume carrier,
//                       colored by that carrier's brand
// Click a state → drill into the signal list.
// =============================================================

// Cached GeoJSON so we don't re-fetch when the picker toggles.
let _usStatesGeoJson = null;

async function loadUSStates() {
  if (_usStatesGeoJson) return _usStatesGeoJson;
  try {
    const res = await fetch("https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json");
    if (!res.ok) throw new Error("geo fetch failed");
    const topo = await res.json();
    // us-atlas uses TopoJSON — convert to GeoJSON via the topojson-client lib
    // we'll load on-demand. Faster path: use a pre-baked GeoJSON.
    return topo;
  } catch {
    return null;
  }
}

// Standalone GeoJSON of US states (state codes as id) — falls back to this
// if the topojson conversion path isn't available. Hosted on jsdelivr.
const US_STATES_GEOJSON_URL =
  "https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json";

const CARRIER_BRAND_COLOR = {
  ups:                 "#7B5731",
  fedex:               "#4D148C",
  amazon:              "#FF9900",
  dhl:                 "#FFCC00",
  usps:                "#004B87",
  "old-dominion":      "#C8102E",
  saia:                "#003E7E",
  xpo:                 "#E31837",
  "abf-freight":       "#003366",
  "knight-swift":      "#F36F21",
  "jb-hunt":           "#FFD200",
  schneider:           "#F58220",
  werner:              "#0073C7",
  "hub-group":         "#003B5C",
  "yellow-corp":       "#FFCC00",
  "marten-transport":  "#005DAA",
  "heartland-express": "#0098D4",
  "ch-robinson":       "#003B71",
  gxo:                 "#9DBE2C",
  rxo:                 "#0066B3",
  "forward-air":       "#1F4E8C",
  "covenant-logistics":"#003B71",
  "pam-transportation":"#1F4E8C",
  ontrac:              "#FF4F00",
};

const BIG_CARRIERS = [
  { slug: "ups",          label: "UPS"          },
  { slug: "fedex",        label: "FedEx"        },
  { slug: "amazon",       label: "Amazon"       },
  { slug: "usps",         label: "USPS"         },
  { slug: "old-dominion", label: "Old Dominion" },
  { slug: "saia",         label: "Saia"         },
  { slug: "xpo",          label: "XPO"          },
  { slug: "knight-swift", label: "Knight-Swift" },
  { slug: "jb-hunt",      label: "J.B. Hunt"    },
  { slug: "schneider",    label: "Schneider"    },
  { slug: "werner",       label: "Werner"       },
  { slug: "abf-freight",  label: "ABF / ArcBest"},
  { slug: "hub-group",    label: "Hub Group"    },
  { slug: "dhl",          label: "DHL"          },
];

function CarrierGravityMap({ allIntel, cities, carrierLanes }) {
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);
  const layerRef = React.useRef(null);
  const [mode, setMode] = React.useState("single");        // "single" | "dominant"
  const [carrier, setCarrier] = React.useState("ups");
  const [geo, setGeo] = React.useState(null);
  const [clickedState, setClickedState] = React.useState(null);

  // Build city→state lookup from cities table
  const cityState = React.useMemo(() => {
    const m = {};
    (cities || []).forEach(c => { m[c.id] = c.state; });
    return m;
  }, [cities]);

  // Build state-density per carrier from intel_submissions client-side.
  // Mirrors the materialized view in policies-carrier-lanes.sql so it
  // works even before the SQL is applied / view is refreshed.
  const stateDensity = React.useMemo(() => {
    const now = Date.now();
    const day = 24 * 3600 * 1000;
    const cutoff = now - 90 * day;
    const out = {};   // out[carrier_slug][STATE] = { count, hi }
    (allIntel || []).forEach(r => {
      if (r.status !== "verified" || r.visibility !== "public") return;
      if (new Date(r.created_at).getTime() < cutoff) return;
      const c = (r.parsed_carrier || "").toLowerCase();
      const st = cityState[r.parsed_city];
      if (!c || !st) return;
      if (!out[c]) out[c] = {};
      if (!out[c][st]) out[c][st] = { count: 0, hi: 0 };
      out[c][st].count++;
      if (r.severity === "high" || r.severity === "critical") out[c][st].hi++;
    });
    return out;
  }, [allIntel, cityState]);

  // Compute dominant carrier per state for "dominant" mode.
  const dominantPerState = React.useMemo(() => {
    const out = {};   // out[STATE] = { carrier_slug, count }
    Object.entries(stateDensity).forEach(([slug, byState]) => {
      Object.entries(byState).forEach(([st, { count }]) => {
        if (!out[st] || count > out[st].count) out[st] = { carrier_slug: slug, count };
      });
    });
    return out;
  }, [stateDensity]);

  // For curated-lane overlay: which states are in the selected carrier's
  // dominant/strong corridors
  const curatedStates = React.useMemo(() => {
    const out = { dominant: new Set(), strong: new Set(), growing: new Set() };
    (carrierLanes || [])
      .filter(l => l.carrier_slug === carrier)
      .forEach(l => {
        (l.states || []).forEach(s => {
          const tier = l.strength_tier || "operating";
          if (out[tier]) out[tier].add(s);
        });
      });
    return out;
  }, [carrierLanes, carrier]);

  // Load GeoJSON once
  React.useEffect(() => {
    let alive = true;
    fetch(US_STATES_GEOJSON_URL)
      .then(r => r.ok ? r.json() : null)
      .then(g => { if (alive) setGeo(g); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  // Init Leaflet map once
  React.useEffect(() => {
    if (!ref.current || !window.L || !geo) return;
    if (!mapRef.current) {
      mapRef.current = window.L.map(ref.current, {
        zoomControl: true, scrollWheelZoom: false, attributionControl: true,
      }).setView([39.5, -97], 4);
      window.L.tileLayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        { attribution: "© OpenStreetMap", maxZoom: 18, opacity: 0.45 }
      ).addTo(mapRef.current);
    }

    // Remove existing layer
    if (layerRef.current) {
      mapRef.current.removeLayer(layerRef.current);
      layerRef.current = null;
    }

    // Style function — depends on mode
    function styleSingle(feature) {
      const code = stateAbbr(feature.properties.name);
      const data = (stateDensity[carrier] || {})[code];
      const count = data ? data.count : 0;
      // Density tier: 0 light, 1-2 mid, 3-5 high, 6+ peak
      const baseColor = CARRIER_BRAND_COLOR[carrier] || "#444";
      let opacity = 0.05;
      if (count >= 6)      opacity = 0.85;
      else if (count >= 3) opacity = 0.60;
      else if (count >= 1) opacity = 0.35;
      // Curated overlay: bump opacity for dominant/strong corridors
      if (curatedStates.dominant.has(code) && opacity < 0.50) opacity = 0.50;
      if (curatedStates.strong.has(code)   && opacity < 0.30) opacity = 0.30;
      return {
        fillColor:  baseColor,
        fillOpacity:opacity,
        weight:     1,
        color:      "#fff",
        opacity:    1,
      };
    }
    function styleDominant(feature) {
      const code = stateAbbr(feature.properties.name);
      const dom = dominantPerState[code];
      if (!dom) {
        return { fillColor: "#e8e8e8", fillOpacity: 0.30, weight: 1, color: "#fff", opacity: 1 };
      }
      const c = CARRIER_BRAND_COLOR[dom.carrier_slug] || "#444";
      return { fillColor: c, fillOpacity: 0.75, weight: 1, color: "#fff", opacity: 1 };
    }
    const styleFn = mode === "dominant" ? styleDominant : styleSingle;

    layerRef.current = window.L.geoJSON(geo, {
      style: styleFn,
      onEachFeature: (feature, layer) => {
        const stateName = feature.properties.name;
        const code = stateAbbr(stateName);
        layer.on("click", () => setClickedState(code));
        if (mode === "dominant") {
          const dom = dominantPerState[code];
          const lbl = dom
            ? `<strong>${stateName}</strong><br/><span style="font-family:monospace;font-size:11px">${dom.carrier_slug.toUpperCase()} — ${dom.count} signals (90d)</span>`
            : `<strong>${stateName}</strong><br/><span style="font-family:monospace;font-size:11px;color:#888">no carrier-tagged signal yet</span>`;
          layer.bindTooltip(lbl, { sticky: true });
        } else {
          const d = (stateDensity[carrier] || {})[code];
          const tier =
            curatedStates.dominant.has(code) ? "DOMINANT corridor"
          : curatedStates.strong.has(code)   ? "STRONG corridor"
          : curatedStates.growing.has(code)  ? "GROWING corridor"
          : "operating";
          const lbl = `<strong>${stateName}</strong><br/><span style="font-family:monospace;font-size:11px">${(d?.count || 0)} signals · ${tier}</span>`;
          layer.bindTooltip(lbl, { sticky: true });
        }
      },
    }).addTo(mapRef.current);

    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 50);
  }, [geo, mode, carrier, stateDensity, dominantPerState, curatedStates]);

  // Cleanup
  React.useEffect(() => () => {
    if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
  }, []);

  // Drill: signals for clicked state + selected carrier (single mode only)
  const drill = React.useMemo(() => {
    if (!clickedState || mode !== "single") return [];
    const now = Date.now();
    const day = 24 * 3600 * 1000;
    return (allIntel || []).filter(r =>
      r.status === "verified"
      && r.visibility === "public"
      && (r.parsed_carrier || "").toLowerCase() === carrier
      && cityState[r.parsed_city] === clickedState
      && (now - new Date(r.created_at).getTime()) < 90 * day
    ).slice(0, 10);
  }, [clickedState, mode, carrier, allIntel, cityState]);

  // Top-5 metros for selected carrier (single mode)
  const top5 = React.useMemo(() => {
    if (mode !== "single") return [];
    const buckets = {};
    (allIntel || []).forEach(r => {
      if (r.status !== "verified" || r.visibility !== "public") return;
      if ((r.parsed_carrier || "").toLowerCase() !== carrier) return;
      if (!r.parsed_city) return;
      buckets[r.parsed_city] = (buckets[r.parsed_city] || 0) + 1;
    });
    const cityName = (id) => {
      const c = (cities || []).find(x => x.id === id);
      return c ? `${c.name}, ${c.state}` : id;
    };
    return Object.entries(buckets).sort((a,b)=>b[1]-a[1]).slice(0, 5)
      .map(([cid, n]) => ({ id: cid, name: cityName(cid), count: n }));
  }, [carrier, allIntel, cities, mode]);

  return (
    <div>
      <style>{`
        .cgm-controls { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
        .cgm-mode { display: inline-flex; border: 1px solid var(--rule); border-radius: 6px; overflow: hidden; }
        .cgm-mode button { padding: 6px 12px; font-family: inherit; font-size: 13px;
          background: transparent; border: none; cursor: pointer; color: var(--ink-soft); }
        .cgm-mode button.active { background: var(--ink); color: #fff; }
        .cgm-carriers { display: flex; gap: 6px; flex-wrap: wrap; }
        .cgm-carrier { padding: 5px 11px; border: 1px solid var(--rule); border-radius: 999px;
          font-size: 12px; background: #fff; cursor: pointer; transition: all 0.12s; }
        .cgm-carrier.active { background: var(--ink); color: #fff; border-color: var(--ink); }
        .cgm-map { height: 480px; border-radius: 8px; overflow: hidden; border: 1px solid var(--rule); }
        .cgm-legend { display: flex; gap: 18px; flex-wrap: wrap; font-size: 11px; color: var(--ink-soft);
          margin-top: 10px; align-items: center; font-family: var(--font-mono); letter-spacing: 0.1em;
          text-transform: uppercase; }
        .cgm-legend-swatch { display: inline-block; width: 16px; height: 10px; border-radius: 2px; margin-right: 6px; vertical-align: middle; }
        .cgm-drill-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 18px; }
        @media (max-width: 760px) { .cgm-drill-grid { grid-template-columns: 1fr; } }
        .cgm-card { background: #fff; border: 1px solid var(--rule); border-radius: 8px; padding: 16px; }
        .cgm-card-h { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em;
          text-transform: uppercase; color: var(--ink-soft); margin-bottom: 10px; }
      `}</style>

      <div className="cgm-controls">
        <div className="cgm-mode">
          <button className={mode === "single" ? "active" : ""} onClick={() => setMode("single")}>Single carrier</button>
          <button className={mode === "dominant" ? "active" : ""} onClick={() => setMode("dominant")}>Dominant per state</button>
        </div>
        {mode === "single" && (
          <div className="cgm-carriers">
            {BIG_CARRIERS.map(c => (
              <button key={c.slug}
                      className={`cgm-carrier ${c.slug === carrier ? "active" : ""}`}
                      onClick={() => { setCarrier(c.slug); setClickedState(null); }}>
                {c.label}
              </button>
            ))}
          </div>
        )}
      </div>

      <div ref={ref} className="cgm-map" />

      <div className="cgm-legend">
        {mode === "single" ? (
          <>
            <span><span className="cgm-legend-swatch" style={{ background: CARRIER_BRAND_COLOR[carrier] || "#444", opacity: 0.85 }} /> high signal density</span>
            <span><span className="cgm-legend-swatch" style={{ background: CARRIER_BRAND_COLOR[carrier] || "#444", opacity: 0.50 }} /> dominant corridor (curated)</span>
            <span><span className="cgm-legend-swatch" style={{ background: CARRIER_BRAND_COLOR[carrier] || "#444", opacity: 0.35 }} /> active</span>
            <span><span className="cgm-legend-swatch" style={{ background: CARRIER_BRAND_COLOR[carrier] || "#444", opacity: 0.05 }} /> minimal / no signal</span>
          </>
        ) : (
          <>Each state colored by its top-volume carrier in the operator-network signal · last 90 days</>
        )}
      </div>

      {mode === "single" && (
        <div className="cgm-drill-grid">
          <div className="cgm-card">
            <div className="cgm-card-h">Top metros · {BIG_CARRIERS.find(c => c.slug === carrier)?.label || carrier}</div>
            {top5.length === 0 ? (
              <div style={{ color: "var(--ink-soft)" }}>No carrier-tagged signal in the last 90 days.</div>
            ) : (
              <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                {top5.map((m, i) => (
                  <li key={m.id} style={{ display: "flex", justifyContent: "space-between",
                                          padding: "8px 0", borderBottom: i < top5.length - 1 ? "1px solid var(--rule)" : "none" }}>
                    <span>{m.name}</span>
                    <span style={{ fontFamily: "var(--font-mono)", color: "var(--ink-soft)" }}>{m.count}</span>
                  </li>
                ))}
              </ul>
            )}
          </div>
          <div className="cgm-card">
            <div className="cgm-card-h">
              {clickedState ? `${clickedState} · ${BIG_CARRIERS.find(c => c.slug === carrier)?.label || carrier}` : "Click a state to drill in"}
            </div>
            {clickedState && drill.length === 0 && (
              <div style={{ color: "var(--ink-soft)" }}>No carrier-tagged signal in {clickedState} in the last 90 days.</div>
            )}
            {drill.length > 0 && (
              <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                {drill.map((r, i) => (
                  <li key={r.id} style={{ padding: "10px 0",
                                          borderBottom: i < drill.length - 1 ? "1px solid var(--rule)" : "none",
                                          fontSize: 13 }}>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.1em",
                                   textTransform: "uppercase", color: "var(--ink-soft)" }}>
                      {(r.severity || "low").toUpperCase()} · {(r.parsed_intel_type || "").replace(/_/g," ")}
                    </span>
                    <div>{(r.raw_text || "").slice(0, 200)}</div>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// US state name → 2-letter code (Leaflet GeoJSON has state names, not codes)
const US_STATE_NAME_TO_CODE = {
  "Alabama":"AL","Alaska":"AK","Arizona":"AZ","Arkansas":"AR","California":"CA",
  "Colorado":"CO","Connecticut":"CT","Delaware":"DE","Florida":"FL","Georgia":"GA",
  "Hawaii":"HI","Idaho":"ID","Illinois":"IL","Indiana":"IN","Iowa":"IA",
  "Kansas":"KS","Kentucky":"KY","Louisiana":"LA","Maine":"ME","Maryland":"MD",
  "Massachusetts":"MA","Michigan":"MI","Minnesota":"MN","Mississippi":"MS",
  "Missouri":"MO","Montana":"MT","Nebraska":"NE","Nevada":"NV","New Hampshire":"NH",
  "New Jersey":"NJ","New Mexico":"NM","New York":"NY","North Carolina":"NC",
  "North Dakota":"ND","Ohio":"OH","Oklahoma":"OK","Oregon":"OR","Pennsylvania":"PA",
  "Rhode Island":"RI","South Carolina":"SC","South Dakota":"SD","Tennessee":"TN",
  "Texas":"TX","Utah":"UT","Vermont":"VT","Virginia":"VA","Washington":"WA",
  "West Virginia":"WV","Wisconsin":"WI","Wyoming":"WY","District of Columbia":"DC",
  "Puerto Rico":"PR",
};
function stateAbbr(name) { return US_STATE_NAME_TO_CODE[name] || name; }

// =============================================================
// REFINERIES MAP — pin every major US refinery, sized by capacity,
// colored by operator. Click pin → popup with capacity + products
// + operator. The fuel-supply layer of the freight intelligence:
// every gallon of diesel a fleet burns came from one of these dots.
// =============================================================
const REFINERY_OPERATOR_COLOR = {
  "Marathon Petroleum":      "#1F4E8C",
  "ExxonMobil":              "#E31837",
  "Saudi Aramco / Motiva":   "#0E5440",
  "Valero":                  "#0064A8",
  "PDVSA / Citgo":           "#C8102E",
  "Phillips 66":             "#FF6E1B",
  "Phillips 66 / Cenovus":   "#FF6E1B",
  "Phillips 66 / Macquarie": "#FF6E1B",
  "BP":                      "#009A44",
  "PBF Energy":              "#1A2A6C",
  "Chevron":                 "#0058A0",
  "Monroe Energy (Delta)":   "#9B1B30",
  "HF Sinclair":             "#0E7C66",
};

function RefineriesMap({ refineries, paddUtil }) {
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);

  React.useEffect(() => {
    if (!ref.current || !window.L || !refineries || refineries.length === 0) return;
    if (!mapRef.current) {
      mapRef.current = window.L.map(ref.current, {
        zoomControl: true, scrollWheelZoom: false, attributionControl: true,
      }).setView([37.5, -97], 4);
      window.L.tileLayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        { attribution: "© OpenStreetMap", maxZoom: 18 }
      ).addTo(mapRef.current);
    }
    mapRef.current.eachLayer(l => { if ((l.options && l.options.pane === "markerPane") || l._icon) mapRef.current.removeLayer(l); });
    refineries.forEach(r => {
      // Pin size scales with capacity tier:
      //   <200k bpd  → 10px
      //   200-400k   → 14px
      //   400-600k   → 18px
      //   >600k      → 22px
      const cap = r.capacity_kbpd || 0;
      const size = cap > 600 ? 22 : cap > 400 ? 18 : cap > 200 ? 14 : 10;
      const color = REFINERY_OPERATOR_COLOR[r.operator] || "#444";
      // PADD-level utilization → outer ring color. EIA weekly utilization_pct
      // is the same number for every refinery in that PADD; the ring just
      // surfaces the regional state on each pin so the map reads at a glance.
      //   ≥92% running hot (amber) · 85-92% normal (no extra ring) ·
      //   <85% slack/throttled (red — product tightness ahead)
      const util = paddUtil ? paddUtil[`PADD-${r.padd}`] : null;
      let ring = null;
      if (util != null) {
        if (util >= 92)      ring = { color: "#d97706", label: "running hot" };       // amber
        else if (util < 85)  ring = { color: "#dc2626", label: "throttled / slack" }; // red
        else                 ring = { color: "#16a34a", label: "normal" };            // green
      }
      const ringSize = size + 8;
      const ringHtml = ring
        ? `<span style="position:absolute;top:0;left:0;width:${ringSize}px;height:${ringSize}px;border-radius:50%;border:2px solid ${ring.color};box-shadow:0 0 0 1px rgba(255,255,255,0.7)"></span>`
        : "";
      const dotOffset = ring ? Math.round((ringSize - size) / 2) : 0;
      const icon = window.L.divIcon({
        className: "ref-pin",
        html: `<span style="position:relative;display:inline-block;width:${ringSize}px;height:${ringSize}px">
          ${ringHtml}
          <span style="position:absolute;top:${dotOffset}px;left:${dotOffset}px;display:block;width:${size}px;height:${size}px;border-radius:50%;background:${color};border:2px solid #fff;box-shadow:0 0 0 1.5px rgba(0,0,0,0.25)"></span>
        </span>`,
        iconSize: [ringSize, ringSize], iconAnchor: [ringSize / 2, ringSize / 2],
      });
      const m = window.L.marker([r.lat, r.lng], { icon }).addTo(mapRef.current);
      const products = (r.products || []).join(" · ");
      const utilLine = util != null
        ? `<div style="margin-top:6px;font-size:12px"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${ring.color};margin-right:6px;vertical-align:middle"></span>PADD ${r.padd} utilization: <strong>${util.toFixed(1)}%</strong> · ${ring.label}</div>`
        : "";
      m.bindPopup(
        `<div style="min-width:240px;max-width:300px">
           <div style="font-family:var(--font-serif);font-size:16px;font-weight:600;letter-spacing:-0.01em;margin-bottom:2px">${r.name.replace(/'/g, "&#39;")}</div>
           <div style="font-family:var(--font-mono);font-size:11px;letter-spacing:0.1em;text-transform:uppercase;color:#666">${r.operator} · ${r.city || ""}, ${r.state} · PADD ${r.padd}</div>
           <div style="margin-top:8px;font-size:14px"><strong>${cap}</strong> kbpd capacity</div>
           ${utilLine}
           ${products ? `<div style="margin-top:6px;font-size:12px;color:#444">${products}</div>` : ""}
         </div>`
      );
    });
    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 50);
  }, [refineries, paddUtil]);

  React.useEffect(() => () => {
    if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
  }, []);

  return <div ref={ref} style={{ width: "100%", height: 480, borderRadius: 8, overflow: "hidden", border: "1px solid var(--rule)" }} />;
}

// =============================================================
// WHAT WE'RE WATCHING — second-order synthesis. Pulls intel_alerts
// (status=published) — these are cross-source patterns the analyst
// layer surfaced from the underlying signal pipeline. Top-of-page
// so freight planners read the thesis before the raw feed.
// =============================================================
function AlertsStrip({ alerts, cities }) {
  const [expanded, setExpanded] = React.useState(null);
  if (!alerts || alerts.length === 0) return null;
  const cityName = (id) => {
    if (!id) return null;
    const c = (cities || []).find(x => x.id === id);
    return c ? `${c.name}, ${c.state}` : id;
  };
  // Match the active-warning palette (hue 25 deep red) so all severity
  // colors across the dashboard read as one system.
  const sevColor = {
    critical: "oklch(0.50 0.22 25)",
    high:     "oklch(0.55 0.20 25)",
    moderate: "oklch(0.55 0.16 75)",
    low:      "oklch(0.55 0.05 250)",
  };
  return (
    <section className="dash-section dash-alerts-section" data-accent="violet" style={{ marginBottom: 24 }}>
      <style>{`
        .dash-alerts-section { background: linear-gradient(180deg, #fff 0%, oklch(0.985 0.01 280) 100%);
          border: 1px solid oklch(0.92 0.03 280); border-radius: 10px; padding: 20px 24px;
          position: relative; overflow: hidden; }
        .dash-alerts-section::before { content: ""; position: absolute; top: 0; left: 0; right: 0;
          height: 3px; background: linear-gradient(90deg, oklch(0.55 0.18 280), oklch(0.55 0.16 25)); }
        .dash-alerts-h { display: flex; align-items: center; gap: 10px;
          font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em;
          text-transform: uppercase; color: var(--ink-soft); margin-bottom: 16px; }
        .dash-alerts-h .dot { width: 8px; height: 8px; border-radius: 50%;
          background: oklch(0.55 0.18 280); animation: pulse-dot 2s infinite; }
        @keyframes pulse-dot { 0%,100%{opacity:1} 50%{opacity:0.4} }
        .dash-alerts-list { display: flex; flex-direction: column; gap: 10px; }
        .dash-alert { display: grid; grid-template-columns: 8px 1fr auto;
          gap: 12px; padding: 14px 14px 14px 18px;
          background: #fff; border: 1px solid var(--rule); border-radius: 8px;
          cursor: pointer; align-items: start; transition: border-color 0.15s; }
        .dash-alert:hover { border-color: var(--ink-soft); }
        .dash-alert-bar { width: 4px; align-self: stretch; border-radius: 2px;
          margin-left: -4px; margin-right: 0; }
        .dash-alert-meta { display: flex; gap: 10px; font-family: var(--font-mono);
          font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
          color: var(--ink-soft); margin-bottom: 6px; flex-wrap: wrap; }
        .dash-alert-pill { padding: 2px 8px; border-radius: 4px; font-weight: 700; color: #fff; }
        .dash-alert-topic { font-family: var(--font-serif); font-size: 18px;
          letter-spacing: -0.01em; line-height: 1.3; margin: 0; }
        .dash-alert-narr { font-size: 14px; line-height: 1.55; color: var(--ink);
          margin: 12px 0 4px; }
        .dash-alert-narr p { margin: 0 0 10px; }
        .dash-alert-support { font-family: var(--font-mono); font-size: 11px;
          color: var(--ink-soft); margin-top: 10px; padding-top: 10px;
          border-top: 1px solid var(--rule); }
        .dash-alert-chev { font-size: 14px; color: var(--ink-soft); }
      `}</style>
      <div className="dash-alerts-h">
        <span className="dot" />
        🔍 What we're watching · {alerts.length} pattern{alerts.length === 1 ? "" : "s"}
      </div>
      <div className="dash-alerts-list">
        {alerts.map((a, i) => {
          const isOpen = expanded === a.id;
          const cn = cityName(a.city_id);
          const paragraphs = (a.narrative || "").split("\n\n").filter(Boolean);
          return (
            <div key={a.id} className="dash-alert" onClick={() => setExpanded(isOpen ? null : a.id)}>
              <div className="dash-alert-bar" style={{ background: sevColor[a.severity] || sevColor.moderate }} />
              <div>
                <div className="dash-alert-meta">
                  <span className="dash-alert-pill" style={{ background: sevColor[a.severity] || sevColor.moderate }}>
                    {(a.severity || "moderate").toUpperCase()}
                  </span>
                  {cn && <span>· {cn}</span>}
                  {a.carrier_slug && <span>· {a.carrier_slug}</span>}
                  {a.evidence_count > 0 && <span>· {a.evidence_count} signal{a.evidence_count === 1 ? "" : "s"}</span>}
                </div>
                <h4 className="dash-alert-topic">{a.topic}</h4>
                {isOpen && (
                  <div className="dash-alert-narr">
                    {paragraphs.map((p, j) => <p key={j}>{p}</p>)}
                    {a.support_summary && (
                      <div className="dash-alert-support">
                        <strong>Evidence:</strong> {a.support_summary}
                      </div>
                    )}
                  </div>
                )}
              </div>
              <span className="dash-alert-chev">{isOpen ? "▴" : "▾"}</span>
            </div>
          );
        })}
      </div>
    </section>
  );
}

// =============================================================
// ACTIVE WARNINGS — promote SEVERE/EXTREME NWS weather alerts to
// metro-level warnings with their effective time window. Sits near
// the top of the dashboard so freight planners see them immediately.
// =============================================================
function ActiveWarningsBanner({ weather, cities }) {
  // Persistent per-warning dismiss state. Keyed by source_id (NWS alert id)
  // so a dismissed warning stays dismissed across reloads until it expires.
  const STORE_KEY = "sc.dismissedWarnings.v1";
  const [dismissed, setDismissed] = React.useState(() => {
    try { return new Set(JSON.parse(localStorage.getItem(STORE_KEY) || "[]")); }
    catch { return new Set(); }
  });
  function dismissOne(id) {
    if (!id) return;
    setDismissed(prev => {
      const next = new Set(prev); next.add(id);
      try { localStorage.setItem(STORE_KEY, JSON.stringify([...next])); } catch {}
      return next;
    });
  }
  function dismissAll(ids) {
    setDismissed(prev => {
      const next = new Set(prev); ids.forEach(id => id && next.add(id));
      try { localStorage.setItem(STORE_KEY, JSON.stringify([...next])); } catch {}
      return next;
    });
  }

  if (!weather || !weather.length) return null;
  // Severity ranks: extreme > severe > moderate > minor
  const SEV_RANK = { extreme: 4, severe: 3, moderate: 2, minor: 1 };
  const now = Date.now();
  const keyFor = (w) => w.source_id || w.id || `${w.event}|${w.city_id}|${w.effective_at}`;
  const items = weather
    .filter(w => SEV_RANK[(w.severity || "").toLowerCase()] >= 3)  // SEVERE+ only
    .filter(w => !w.expires_at || new Date(w.expires_at).getTime() > now)
    .filter(w => !dismissed.has(keyFor(w)))
    .sort((a, b) => (SEV_RANK[b.severity?.toLowerCase()] || 0) - (SEV_RANK[a.severity?.toLowerCase()] || 0));

  if (items.length === 0) return null;
  function fmtTime(iso) {
    if (!iso) return null;
    const d = new Date(iso);
    return d.toLocaleString("en-US", {
      month: "short", day: "numeric", hour: "numeric", minute: "2-digit",
      hour12: true, timeZoneName: "short",
    });
  }
  function cityName(id) {
    const c = (cities || []).find(x => x.id === id);
    return c ? `${c.name}, ${c.state}` : (id || "—");
  }
  return (
    <section className="dash-warnings">
      <div className="dash-warnings-inner">
        <div className="dash-warnings-head">
          <span className="dash-warnings-badge">⚠ ACTIVE WARNINGS</span>
          <span className="dash-warnings-count">{items.length} severe-or-higher · {new Set(items.map(i => i.city_id)).size} metro{items.length === 1 ? "" : "s"}</span>
          {items.length > 1 && (
            <button
              className="dash-warnings-dismiss-all"
              onClick={() => dismissAll(items.map(keyFor))}
              title="Dismiss all visible warnings until they re-issue or expire"
            >Dismiss all ×</button>
          )}
        </div>
        <ul className="dash-warnings-list">
          {items.slice(0, 6).map((w, i) => {
            const sev = (w.severity || "").toLowerCase();
            const k = keyFor(w);
            return (
              <li key={k || i} className={`dash-warning-row dash-warning-${sev}`}>
                <span className={`dash-warning-pill dash-warning-pill-${sev}`}>{(w.severity || "").toUpperCase()}</span>
                <div className="dash-warning-body">
                  <div className="dash-warning-headline">
                    <strong>{w.event || "Weather alert"}</strong>
                    <span className="dash-warning-city">· {cityName(w.city_id)}</span>
                  </div>
                  <div className="dash-warning-window">
                    {w.effective_at && fmtTime(w.effective_at)}
                    {w.expires_at && (<> → <strong>{fmtTime(w.expires_at)}</strong></>)}
                    {!w.effective_at && !w.expires_at && (w.headline || "Active now")}
                  </div>
                  {w.headline && w.event && w.headline !== w.event && (
                    <div className="dash-warning-detail">{w.headline}</div>
                  )}
                </div>
                <button
                  className="dash-warning-close"
                  onClick={() => dismissOne(k)}
                  aria-label="Dismiss this warning"
                  title="Dismiss this warning"
                >×</button>
              </li>
            );
          })}
        </ul>
        {items.length > 6 && (
          <div className="dash-warnings-more">+ {items.length - 6} more · scroll to weather section for full list</div>
        )}
      </div>
    </section>
  );
}

// =============================================================
// TRUCK-STOP MAP — Leaflet pin map for one metro's local stops.
// Renders inside the expanded metro detail card.
// =============================================================
function TruckStopMap({ stops, chainColor }) {
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);

  React.useEffect(() => {
    if (!ref.current || !window.L || !stops || stops.length === 0) return;
    // Initialize once
    if (!mapRef.current) {
      const center = [stops[0].lat, stops[0].lng];
      mapRef.current = window.L.map(ref.current, {
        zoomControl: true, scrollWheelZoom: false, attributionControl: true,
      }).setView(center, 9);
      window.L.tileLayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        { attribution: "© OpenStreetMap", maxZoom: 18 }
      ).addTo(mapRef.current);
    }
    // Clear existing layers (markers) before re-rendering
    mapRef.current.eachLayer(l => { if (l.options && l.options.pane === "markerPane" || (l._icon)) mapRef.current.removeLayer(l); });
    const bounds = [];
    stops.forEach(s => {
      const c = chainColor[s.chain] || "#444";
      const icon = window.L.divIcon({
        className: "ts-pin",
        html: `<span class="ts-pin-dot" style="background:${c}"></span>`,
        iconSize: [16, 16], iconAnchor: [8, 8],
      });
      const marker = window.L.marker([s.lat, s.lng], { icon }).addTo(mapRef.current);
      marker.bindPopup(
        `<div class="ts-popup">
           <div class="ts-popup-name">${s.name.replace(/'/g, "&#39;")}</div>
           <div class="ts-popup-meta">${[s.city, s.state].filter(Boolean).join(", ") || ""}</div>
           <a class="ts-popup-link" target="_blank" rel="noopener noreferrer"
              href="https://www.google.com/maps/dir/?api=1&destination=${s.lat},${s.lng}&travelmode=driving">
              Directions ↗
           </a>
         </div>`
      );
      bounds.push([s.lat, s.lng]);
    });
    if (bounds.length > 1) mapRef.current.fitBounds(bounds, { padding: [24, 24] });
    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 50);
    return () => { /* keep map; metro toggling reuses */ };
  }, [stops]);

  React.useEffect(() => () => {
    if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
  }, []);

  return <div ref={ref} className="ts-map" />;
}

// =============================================================
// LIVE OPS MAP — single Leaflet showing every freight-disruption
// signal with a position right now: CHP collisions/hazards (CA),
// active wildfires (≥500 ac OR metro-adjacent), degraded-METAR
// airport hubs, freight-tagged earthquakes (last 7d). Overlay
// answers "is anything on fire near my freight" at a glance.
// =============================================================
// Airport ICAO → [lat, lng]. Same set as ingest-airport-weather.py.
// Inline so the map doesn't need a separate DB roundtrip for coordinates.
const AIRPORT_LATLNG = {
  KMEM: [35.0424, -89.9767], KSDF: [38.1740, -85.7361], KCVG: [39.0489, -84.6678],
  KIND: [39.7173, -86.2944], KANC: [61.1744, -149.9962], KORD: [41.9742, -87.9073],
  KLAX: [33.9416, -118.4085], KJFK: [40.6413, -73.7781], KEWR: [40.6925, -74.1687],
  KDFW: [32.8998, -97.0403], KATL: [33.6407, -84.4277], KMIA: [25.7959, -80.2870],
  KOAK: [37.7126, -122.2197], KSEA: [47.4502, -122.3088], KPHX: [33.4373, -112.0078],
  KBOS: [42.3656, -71.0096], KIAH: [29.9844, -95.3414], KPHL: [39.8744, -75.2424],
};

function LiveOpsMap({ chpIncidents, wildfires, airportWx, earthquakes }) {
  const mapRef = React.useRef(null);
  const containerRef = React.useRef(null);
  const layersRef = React.useRef({});
  const layerControlRef = React.useRef(null);

  // Counts displayed in the section header. Computed inline from props
  // so we don't trigger the layer-update effect via state.
  const chpFiltered = React.useMemo(() => (chpIncidents || []).filter(c =>
    (c.category === "collision" || c.category === "hazard" || c.category === "closure")
    && c.lat != null && c.lng != null
  ), [chpIncidents]);
  const fireFiltered = React.useMemo(() => (wildfires || []).filter(f =>
    f.lat != null && f.lng != null
  ), [wildfires]);
  const metarFiltered = React.useMemo(() => (airportWx || []).filter(a =>
    AIRPORT_LATLNG[a.icao_id]
  ), [airportWx]);
  const quakeFiltered = React.useMemo(() => (earthquakes || []).filter(q =>
    q.lat != null && q.lng != null
  ), [earthquakes]);

  // Mount-only effect: create the map exactly once. setView fires here
  // and never again, so the user's pan/zoom is preserved on data updates.
  React.useEffect(() => {
    if (!containerRef.current || !window.L || mapRef.current) return;
    mapRef.current = window.L.map(containerRef.current, {
      zoomControl: true, scrollWheelZoom: true, attributionControl: true,
      // Prevent the map from auto-moving when markers re-render.
      fadeAnimation: false, zoomAnimation: false, markerZoomAnimation: false,
    }).setView([39.5, -98.5], 4);
    window.L.tileLayer(
      "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
      { attribution: "© OpenStreetMap", maxZoom: 11 }
    ).addTo(mapRef.current);
    setTimeout(() => mapRef.current && mapRef.current.invalidateSize(), 80);
    return () => {
      if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
    };
  }, []);

  // Data-update effect: only swap layers when the upstream data changes.
  // No setView, no setState, no layer-control rebuild on every render.
  React.useEffect(() => {
    if (!mapRef.current || !window.L) return;

    // Clear previous overlay layers (keep base tile layer).
    // Capture old layers before clearing so we can remove them from the
    // layer control too (otherwise entries duplicate on every refresh).
    const oldLayers = Object.values(layersRef.current);
    oldLayers.forEach(lg => {
      if (lg && mapRef.current.hasLayer(lg)) mapRef.current.removeLayer(lg);
    });
    layersRef.current = {};

    const COLORS = {
      collision: "oklch(0.55 0.20 25)",
      hazard:    "oklch(0.55 0.18 50)",
      closure:   "oklch(0.45 0.22 25)",
      fire:      "oklch(0.55 0.18 30)",
      LIFR:      "oklch(0.45 0.22 305)",
      IFR:       "oklch(0.55 0.20 25)",
      MVFR:      "oklch(0.55 0.16 95)",
      quake:     "oklch(0.45 0.18 305)",
    };
    function pin(latLng, color, size, popupHtml, opacity = 0.9) {
      const html = `<span style="display:inline-block;width:${size}px;height:${size}px;
        border-radius:50%;background:${color};border:2px solid #fff;
        box-shadow:0 0 0 1px rgba(0,0,0,0.25);opacity:${opacity};"></span>`;
      const icon = window.L.divIcon({
        className: "ops-pin", html,
        iconSize: [size + 4, size + 4], iconAnchor: [(size + 4) / 2, (size + 4) / 2],
      });
      const m = window.L.marker(latLng, { icon });
      if (popupHtml) m.bindPopup(popupHtml);
      return m;
    }

    // CHP — collisions + hazards only (skip "other" to keep map clean).
    const chpLayer = window.L.layerGroup(chpFiltered.map(c => {
      const tag = c.category || "other";
      const popup = `
        <div style="min-width:220px;font-family:var(--font-sans, system-ui)">
          <div style="font-family:var(--font-mono);font-size:10px;letter-spacing:0.12em;
                      text-transform:uppercase;color:#666">${tag}${c.freeway ? " · " + c.freeway : ""}</div>
          <div style="font-family:var(--font-serif);font-size:14px;margin-top:4px">
            ${(c.incident_type || "Incident").replace(/^[0-9]+-/, "")}</div>
          <div style="font-size:11px;color:#666;margin-top:3px">
            ${[c.area, c.location_desc, c.metro_city_id].filter(Boolean).join(" · ")}</div>
        </div>`;
      return pin([c.lat, c.lng], COLORS[tag] || COLORS.collision,
                 tag === "closure" ? 11 : 9, popup, 0.85);
    }));

    // Wildfires — ≥500 ac or metro-adjacent (already filtered upstream).
    const fireLayer = window.L.layerGroup(fireFiltered.map(f => {
      const ac = Number(f.acres) || 0;
      // Size by acres: <1K = 8, 1K-10K = 12, ≥10K = 16
      const size = ac >= 10000 ? 16 : ac >= 1000 ? 12 : 8;
      const acStr = ac >= 1000 ? `${(ac/1000).toFixed(ac >= 10000 ? 0 : 1)}K` : Math.round(ac).toLocaleString();
      const popup = `
        <div style="min-width:220px;font-family:var(--font-sans, system-ui)">
          <div style="font-family:var(--font-mono);font-size:10px;letter-spacing:0.12em;
                      text-transform:uppercase;color:#666">🔥 wildfire · ${(f.state || "").replace(/^US-/, "")}</div>
          <div style="font-family:var(--font-serif);font-size:14px;margin-top:4px">${f.name || "Unnamed fire"}</div>
          <div style="font-size:11px;color:#666;margin-top:3px">
            ${acStr} ac${f.pct_contained != null ? " · " + f.pct_contained + "% contained" : ""}
            ${f.county ? " · " + f.county + " Co." : ""}
          </div>
        </div>`;
      return pin([f.lat, f.lng], COLORS.fire, size, popup, 0.85);
    }));

    // Airports — show all hubs with a position. Color by flight cat;
    // VFR pins are smaller + faded so degraded ones pop.
    const metarLayer = window.L.layerGroup(metarFiltered.map(a => {
      const ll = AIRPORT_LATLNG[a.icao_id];
      const cat = a.flight_cat || "VFR";
      const degraded = cat !== "VFR";
      const popup = `
        <div style="min-width:240px;font-family:var(--font-sans, system-ui)">
          <div style="font-family:var(--font-mono);font-size:10px;letter-spacing:0.12em;
                      text-transform:uppercase;color:#666">✈️ ${a.icao_id} · ${cat}</div>
          <div style="font-family:var(--font-serif);font-size:14px;margin-top:4px">${(a.station_name || "").split("·")[0].trim()}</div>
          <div style="font-size:11px;color:#666;margin-top:3px">
            ${a.wind_dir_deg ?? "—"}°/${a.wind_speed_kt ?? "?"}kt${a.wind_gust_kt ? "G" + a.wind_gust_kt : ""}
            · vis ${a.visibility ?? "?"}mi
            ${a.ceiling_ft ? " · ceiling " + a.ceiling_ft + "ft" : ""}
          </div>
          ${a.raw_obs ? `<div style="font-family:var(--font-mono);font-size:10px;color:#999;margin-top:6px;word-break:break-all">${a.raw_obs}</div>` : ""}
        </div>`;
      return pin(ll, degraded ? COLORS[cat] : "oklch(0.55 0.10 145)",
                 degraded ? 12 : 7, popup, degraded ? 0.95 : 0.55);
    }));

    // Earthquakes — freight-tagged (already filtered by query).
    const quakeLayer = window.L.layerGroup(quakeFiltered.map(q => {
      const m = Number(q.magnitude) || 0;
      const size = m >= 6 ? 16 : m >= 5 ? 13 : 10;
      const popup = `
        <div style="min-width:220px;font-family:var(--font-sans, system-ui)">
          <div style="font-family:var(--font-mono);font-size:10px;letter-spacing:0.12em;
                      text-transform:uppercase;color:#666">🌋 M${m.toFixed(1)}${q.tsunami ? " · TSUNAMI" : ""}</div>
          <div style="font-family:var(--font-serif);font-size:14px;margin-top:4px">${q.place || "Earthquake"}</div>
          <div style="font-size:11px;color:#666;margin-top:3px">
            ${q.depth_km ? Number(q.depth_km).toFixed(0) + "km depth · " : ""}
            ${q.near_metro_id ? "near " + q.near_metro_id + " (" + Number(q.near_metro_mi).toFixed(0) + "mi)" : ""}
          </div>
        </div>`;
      return pin([q.lat, q.lng], COLORS.quake, size, popup, 0.85);
    }));

    chpLayer.addTo(mapRef.current);
    fireLayer.addTo(mapRef.current);
    metarLayer.addTo(mapRef.current);
    quakeLayer.addTo(mapRef.current);
    layersRef.current = { chp: chpLayer, fire: fireLayer, metar: metarLayer, quake: quakeLayer };

    // Build layer control once. On subsequent updates, just refresh the
    // labels on the same control so its viewport position doesn't reset
    // (the previous remove+add was visually flickering on every data tick).
    if (!layerControlRef.current) {
      layerControlRef.current = window.L.control.layers(null, {}, {
        collapsed: false, position: "topright",
      }).addTo(mapRef.current);
    } else {
      oldLayers.forEach(lg => layerControlRef.current.removeLayer(lg));
    }
    layerControlRef.current.addOverlay(chpLayer,   `🛣 California (${chpFiltered.length})`);
    layerControlRef.current.addOverlay(fireLayer,  `🔥 Wildfires (${fireFiltered.length})`);
    layerControlRef.current.addOverlay(metarLayer, `✈️ Airports (${metarFiltered.length})`);
    layerControlRef.current.addOverlay(quakeLayer, `🌋 Quakes (${quakeFiltered.length})`);
  }, [chpFiltered, fireFiltered, metarFiltered, quakeFiltered]);

  const total = chpFiltered.length + fireFiltered.length + metarFiltered.length + quakeFiltered.length;

  return (
    <section className="dash-section" data-accent="red">
      <style>{`
        .ops-map { width: 100%; height: 480px; border-radius: 8px;
          overflow: hidden; border: 1px solid var(--rule); }
        .ops-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px;
          font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.05em;
          color: var(--ink-soft); }
        .ops-legend .swatch { display: inline-block; width: 8px; height: 8px;
          border-radius: 50%; vertical-align: middle; margin-right: 6px;
          border: 1.5px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.25); }
      `}</style>
      <div className="dash-section-head">
        <h3 className="dash-section-h">🗺 Live operations map</h3>
        <span className="dash-card-sub">
          {total} signal{total === 1 ? "" : "s"} on the map ·
          {" "}{chpFiltered.length} CHP · {fireFiltered.length} fires ·
          {" "}{metarFiltered.length} hubs · {quakeFiltered.length} quakes ·
          {" "}toggle layers in the top-right
        </span>
      </div>
      <div ref={containerRef} className="ops-map" />
      <div className="ops-legend">
        <span><span className="swatch" style={{ background: "oklch(0.55 0.20 25)" }} />CHP collision</span>
        <span><span className="swatch" style={{ background: "oklch(0.55 0.18 50)" }} />CHP hazard</span>
        <span><span className="swatch" style={{ background: "oklch(0.55 0.18 30)" }} />Wildfire (size = acres)</span>
        <span><span className="swatch" style={{ background: "oklch(0.45 0.22 305)" }} />Airport LIFR / quake (size = magnitude)</span>
        <span><span className="swatch" style={{ background: "oklch(0.55 0.20 25)" }} />Airport IFR</span>
        <span><span className="swatch" style={{ background: "oklch(0.55 0.16 95)" }} />Airport MVFR</span>
        <span><span className="swatch" style={{ background: "oklch(0.55 0.10 145)" }} />Airport VFR (clear)</span>
      </div>
    </section>
  );
}

// =============================================================
// TRUCK STOPS — clickable metro list; click to expand local truck-
// accessible diesel locations with directions + amenities + regional
// retail-diesel baseline.
// =============================================================
function TruckStopSection({ truckStops, cities, pulseSubs, fuel }) {
  const [openMetro, setOpenMetro] = React.useState(null);
  const CHAIN_LABEL = { pilot: "Pilot Flying J", ta: "TA Travel Centers", loves: "Love's", sapp: "Sapp Bros" };
  const CHAIN_COLOR = { pilot: "#E31837", ta: "#0C4DA2", loves: "#E51E25", sapp: "#0F4DA8" };

  // Group stops by metro, capture chain breakdown + amenity stats
  const byMetro = {};
  (truckStops || []).forEach(t => {
    if (!t.metro_city_id) return;
    if (!byMetro[t.metro_city_id]) byMetro[t.metro_city_id] = { stops: [], chains: {} };
    byMetro[t.metro_city_id].stops.push(t);
    byMetro[t.metro_city_id].chains[t.chain] = (byMetro[t.metro_city_id].chains[t.chain] || 0) + 1;
  });

  // Order: subscribed metros first, then any metro with stops
  const subSet = new Set((pulseSubs || []).map(s => s.city_id));
  const allMetros = [
    ...(cities || []).filter(c => subSet.has(c.id)),
    ...(cities || []).filter(c => !subSet.has(c.id)),
  ];

  // Regional retail diesel baseline (EIA)
  const regional = (fuel || []).find(f => f.series === "diesel_retail_us");
  const regionalPrice = regional ? `$${parseFloat(regional.value_num).toFixed(3)}/gal` : null;

  return (
    <section className="dash-section" data-accent="orange">
      <div className="dash-section-head">
        <h3 className="dash-section-h">🛻 Truck stops</h3>
        <span className="dash-card-sub">
          Click a metro to see local truck-accessible diesel locations
          {regionalPrice && (<> · regional retail diesel baseline <strong>{regionalPrice}</strong></>)}
        </span>
      </div>

      <div className="dash-tsmetro-grid">
        {allMetros.map(c => {
          const data = byMetro[c.id];
          const count = data ? data.stops.length : 0;
          const chains = data ? data.chains : {};
          const isOpen = openMetro === c.id;
          const isSub = subSet.has(c.id);
          return (
            <div key={c.id} className={`dash-tsmetro-wrap${isOpen ? " dash-tsmetro-open" : ""}`}>
              <button
                className={`dash-tsmetro-btn${isSub ? " dash-tsmetro-btn-sub" : ""}`}
                onClick={() => setOpenMetro(isOpen ? null : c.id)}
                disabled={count === 0}
              >
                <div className="dash-tsmetro-btn-l">
                  <div className="dash-tsmetro-name">
                    {c.name}, {c.state}
                    {isSub && <span className="dash-tsmetro-sub-pill">subscribed</span>}
                  </div>
                  <div className="dash-tsmetro-count">
                    {count > 0 ? <><strong>{count}</strong> truck stops nearby</> : "No mapped stops within 60 mi"}
                  </div>
                  {count > 0 && (
                    <div className="dash-tsmetro-chains">
                      {Object.entries(chains).map(([ch, n]) => (
                        <span key={ch} className="dash-tsmetro-chain-mini"
                              style={{ background: CHAIN_COLOR[ch], color: "#fff" }}>
                          {CHAIN_LABEL[ch] || ch} <strong>{n}</strong>
                        </span>
                      ))}
                    </div>
                  )}
                </div>
                {count > 0 && <span className="dash-tsmetro-chevron">{isOpen ? "▴" : "▾"}</span>}
              </button>
              {isOpen && data && (
                <div className="dash-tsmetro-detail">
                  <div className="dash-tsmetro-detail-grid">
                    <TruckStopMap stops={data.stops} chainColor={CHAIN_COLOR} />
                    <div>
                  <ul className="dash-truckstop-list">
                    {data.stops.slice(0, 12).map(t => {
                      // Display name: our chain label is the source of truth
                      // (OSM names get noisy — TA locations OSM-tagged "Conoco",
                      // Pilot tagged "Flying J", etc.). Show chain label as
                      // primary and the OSM name only when it adds info.
                      const chainLabel = CHAIN_LABEL[t.chain] || t.chain;
                      const osmExtra = t.name && t.name.toLowerCase() !== t.chain
                        && !chainLabel.toLowerCase().includes((t.name || "").toLowerCase())
                        ? t.name : null;
                      // Address fallback: prefer street/city/state; else show
                      // coords; never an em-dash.
                      const addrLine = [t.address, t.city, t.state].filter(Boolean).join(", ");
                      const locLine = addrLine
                        || `${t.lat.toFixed(3)}°, ${t.lng.toFixed(3)}°`;
                      return (
                        <li key={t.id} className="dash-truckstop-row">
                          <span className="dash-truckstop-dot" style={{ background: CHAIN_COLOR[t.chain] }} />
                          <div className="dash-truckstop-body">
                            <div className="dash-truckstop-name">
                              {chainLabel}
                              {osmExtra && <span style={{ color: "var(--ink-soft)", fontWeight: 400 }}> · {osmExtra}</span>}
                            </div>
                            <div className="dash-truckstop-meta">
                              {locLine}
                              {t.amenities && t.amenities.showers && " · 🚿"}
                              {t.amenities && t.amenities.scale && " · ⚖️"}
                              {t.amenities && t.amenities.restaurant && " · 🍔"}
                              {t.amenities && t.amenities.wifi && " · 📶"}
                            </div>
                          </div>
                          <a className="dash-truckstop-link"
                             href={`https://www.google.com/maps/dir/?api=1&destination=${t.lat},${t.lng}&travelmode=driving`}
                             target="_blank" rel="noopener noreferrer"
                             title="Google Maps · passenger-car routing">
                             🗺 Directions ↗
                          </a>
                        </li>
                      );
                    })}
                  </ul>
                  {data.stops.length > 12 && (
                    <div className="dash-tsmetro-more">+ {data.stops.length - 12} more nearby</div>
                  )}
                  <div style={{
                    marginTop: 12, padding: "10px 12px", background: "oklch(0.97 0.01 80)",
                    border: "1px solid oklch(0.90 0.04 80)", borderRadius: 6,
                    fontSize: 11, color: "oklch(0.40 0.10 80)", lineHeight: 1.5
                  }}>
                    <strong>Data note:</strong> addresses on the network are sparse for ~half of stops.
                    Per-station diesel pricing rolls in on the next pulse cycle.
                  </div>

                    </div>
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>
      <p className="dash-footer-note" style={{ textAlign: "left", marginTop: 14 }}>
        <strong>How to use:</strong> map shows truck-accessible chain locations within 60 miles of each metro.
        Regional retail diesel is the carrier-grade baseline; actual pump prices vary by ±$0.20/gal.
        Tap "Directions" to open truck-legal routing.
      </p>
    </section>
  );
}

// Dashboard tabs — each gets its own deep-linkable hash route at
// /#/account/<slug>. The page is restructured into focused views
// so the value telegraphs itself instead of getting lost in 20+
// scroll-deep sections.
const DASH_TABS = [
  { id: "now",       label: "Now",       sub: "Live, last 4h" },
  { id: "carriers",  label: "Carriers",  sub: "Fleet · FMCSA · gravity" },
  { id: "corridors", label: "Corridors", sub: "Metros · ports · cold" },
  { id: "markets",   label: "Markets",   sub: "Fuel · refineries · oil" },
  { id: "watchlist", label: "Watchlist", sub: "Your alerts" },
];

function DashTabNav({ tab, onSelect }) {
  return (
    <nav className="dash-tabnav">
      <style>{`
        /* File-folder style tabs. The container has a bottom rule running
           the full width; the active tab interrupts it (looks like the tab
           is sitting on top of the content surface, the rest are tucked
           behind). */
        /* Sticks below the dash command bar (.dash-bar is sticky top:0 z:50) */
        .dash-tabnav { position: sticky; top: 56px; z-index: 40;
          background: oklch(0.96 0.006 250); padding: 14px 0 0;
          margin: 0 0 28px; border-bottom: 1px solid var(--rule); }
        .dash-tabnav-inner { max-width: 1300px; margin: 0 auto; padding: 0 24px;
          display: flex; gap: 6px; overflow-x: auto; align-items: flex-end; }
        .dash-tab { position: relative; background: oklch(0.93 0.008 250);
          border: 1px solid var(--rule); border-bottom: 0;
          border-radius: 9px 9px 0 0;
          padding: 10px 22px 12px; font-family: inherit; cursor: pointer;
          text-align: left; flex-shrink: 0; transition: all 0.12s;
          margin-bottom: -1px; min-width: 124px;
          box-shadow: inset 0 -8px 12px -10px rgba(0,0,0,0.08); }
        .dash-tab:hover { background: oklch(0.95 0.005 250); }
        .dash-tab[data-active="true"] {
          background: #fff; box-shadow: none;
          border-color: var(--rule); z-index: 1; padding-top: 12px;
        }
        /* Erase the container's bottom rule under the active tab. */
        .dash-tab[data-active="true"]::after {
          content: ""; position: absolute; left: -1px; right: -1px;
          bottom: -1px; height: 2px; background: #fff;
        }
        /* Top accent stripe on the active tab, color-coded per category. */
        .dash-tab[data-active="true"]::before {
          content: ""; position: absolute; left: 12px; right: 12px;
          top: 0; height: 2px; border-radius: 1px; background: var(--accent, var(--ink));
        }
        .dash-tab-l { font-family: var(--font-serif); font-size: 17px;
          letter-spacing: -0.015em; line-height: 1.1;
          color: var(--ink-soft); }
        .dash-tab[data-active="true"] .dash-tab-l { color: var(--ink); }
        .dash-tab-s { font-family: var(--font-mono); font-size: 10px;
          letter-spacing: 0.12em; text-transform: uppercase;
          color: var(--ink-soft); margin-top: 3px; opacity: 0.75; }
        .dash-tab[data-active="true"] .dash-tab-s { opacity: 1; }
        /* Color accents per tab — top stripe color when active. */
        .dash-tab[data-tab="now"]       { --accent: oklch(0.55 0.20 25); }
        .dash-tab[data-tab="carriers"]  { --accent: oklch(0.50 0.18 300); }
        .dash-tab[data-tab="corridors"] { --accent: oklch(0.50 0.18 250); }
        .dash-tab[data-tab="markets"]   { --accent: oklch(0.50 0.14 145); }
        .dash-tab[data-tab="watchlist"] { --accent: oklch(0.55 0.18 50); }
      `}</style>
      <div className="dash-tabnav-inner">
        {DASH_TABS.map(t => (
          <button key={t.id} className="dash-tab"
                  data-tab={t.id}
                  data-active={tab === t.id}
                  onClick={() => onSelect(t.id)}>
            <div className="dash-tab-l">{t.label}</div>
            <div className="dash-tab-s">{t.sub}</div>
          </button>
        ))}
      </div>
    </nav>
  );
}

function AccountPage({ onNav, tab = "now" }) {
  // Tab gate — `inTab` returns true if this section should render in the
  // active tab. Sections are bucketed at render time below.
  const validTab = DASH_TABS.find(t => t.id === tab) ? tab : "now";
  const inTab = (...names) => names.includes(validTab);
  const setTab = (next) => {
    window.location.hash = `#/account/${next}`;
  };

  const [user, setUser] = useStateAuth(null);
  const [pulseSubs, setPulseSubs] = useStateAuth([]);
  const [allIntelRaw, setAllIntel] = useStateAuth([]);
  // Server-side counts for the Intel Report headline tiles. PostgREST default
  // response cap is 1000 rows — taking .length of a fetched window misreports
  // every "all-time" tile (the 500/500/500 we kept seeing). count=exact via
  // Range:0-0 lets us get true totals without scanning rows back.
  const [intelCounts, setIntelCounts] = useStateAuth(null);
  const [carriers, setCarriers] = useStateAuth([]);
  const [cities, setCities] = useStateAuth([]);
  const [weather, setWeather] = useStateAuth([]);
  const [alerts, setAlerts] = useStateAuth([]);
  const [forecast, setForecast] = useStateAuth([]);
  const [ports, setPorts] = useStateAuth([]);
  const [portSignals, setPortSignals] = useStateAuth([]);
  const [coldStorage, setColdStorage] = useStateAuth([]);
  const [fuel, setFuel] = useStateAuth([]);
  const [refineries, setRefineries] = useStateAuth([]);
  const [refinerySignals, setRefinerySignals] = useStateAuth([]);
  const [carrierLanes, setCarrierLanes] = useStateAuth([]);
  const [roads, setRoads] = useStateAuth([]);
  const [tropicalStorms, setTropicalStorms] = useStateAuth([]);
  const [tropicalOutlook, setTropicalOutlook] = useStateAuth([]);
  const [chpIncidents, setChpIncidents] = useStateAuth([]);
  const [airportWx, setAirportWx] = useStateAuth([]);
  const [airportEvents, setAirportEvents] = useStateAuth([]);
  const [earthquakes, setEarthquakes] = useStateAuth([]);
  const [wildfires, setWildfires] = useStateAuth([]);
  const [loading, setLoading] = useStateAuth(true);
  const [weatherCity, setWeatherCity] = useStateAuth(null);
  const [truckStops, setTruckStops] = useStateAuth([]);
  const [now, setNow] = useStateAuth(Date.now());
  // Drill-down filter — clicking carrier/metro/severity/type narrows the feed.
  const [filter, setFilter] = useStateAuth({ carrier: null, city: null, severity: null, type: null });
  // Source filter — set of source identifiers the user has *excluded* from
  // the dashboard's numbers and feed. Persisted in localStorage so the
  // choice survives reloads.
  const [excludedSources, setExcludedSources] = useStateAuth(() => {
    try { return new Set(JSON.parse(localStorage.getItem("sc.excludedSources.v1") || "[]")); }
    catch { return new Set(); }
  });
  function toggleSource(src) {
    setExcludedSources(prev => {
      const next = new Set(prev);
      if (next.has(src)) next.delete(src);
      else next.add(src);
      try { localStorage.setItem("sc.excludedSources.v1", JSON.stringify([...next])); } catch {}
      return next;
    });
  }
  // Identify source from source_url string. Used to gate rows out of
  // dashboard numbers when the user toggles a source off.
  function rowSource(r) {
    const u = (r && r.source_url) || "";
    if (u.includes("reddit.com")) return "reddit";
    if (u.includes("browncafe.com") || (u.includes("web.archive.org/web/") && u.includes("browncafe"))) return "browncafe";
    if (u.includes("news.google.com")) return "google_news";
    if (u) return "other";
    return "unattributed";  // no source_url — older classifier output, no link back yet
  }

  useEffectAuth(() => {
    let cancelled = false;
    (async () => {
      const u = await window.SI_DB.auth.getCurrentUser();
      if (cancelled) return;
      setUser(u);
      if (!u || !u.email) { setLoading(false); return; }

      // Profile-completion gate — if this user has no row in user_profiles,
      // redirect to /#/welcome to complete it. Skip the check silently on
      // any failure (don't trap users out of their account).
      try {
        const session = window.SI_DB.auth.readSession();
        if (session && session.access_token) {
          const r = await fetch(`/api/profile-complete`, {
            headers: { Authorization: `Bearer ${session.access_token}` },
          });
          if (r.ok) {
            const j = await r.json();
            if (!cancelled && (!j || !j.profile)) {
              window.location.hash = "#/welcome";
              return;
            }
          }
        }
      } catch (_) { /* don't block account on profile probe error */ }

      // Pulse subs (authenticated)
      const subs = await window.SI_DB.raw.selectAuthed(
        "pulse_subscriptions",
        `select=email,city_id,status,created_at,last_sent_at,send_count&email=eq.${encodeURIComponent(u.email)}&order=created_at.desc`
      );
      if (cancelled) return;
      setPulseSubs(Array.isArray(subs) ? subs : []);

      // ALL recent intel — global view, anyone signed in sees the same. Pulls
      // both verified-public AND sealed counts (anon can't read sealed text,
      // but we want totals for charts). For the feed we only show verified.
      // Bumped to 2000 (PostgREST max=1000 per page; we range with 0-1999 on
      // the server side) so the in-memory 14-day uniques + the 30-day stacked
      // chart actually have all the rows they need. Headline tiles still get
      // their numbers from server-side counts below — never .length on this.
      const intel = await window.SI_DB.raw.select(
        "intel_submissions",
        `select=id,parsed_carrier,parsed_intel_type,severity,raw_text,created_at,parsed_city,visibility,status,source_url,notes&order=created_at.desc&limit=2000`
      );
      if (cancelled) return;
      setAllIntel(Array.isArray(intel) ? intel : []);

      // Server-side counts for the Intel Report headline tiles. Fire in
      // parallel; count=exact is cheap (no row scan back). Each call returns
      // the integer total or null on failure.
      const since24h = new Date(Date.now() - 24 * 3600 * 1000).toISOString();
      const since48h = new Date(Date.now() - 48 * 3600 * 1000).toISOString();
      const since7d  = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
      const since14d = new Date(Date.now() - 14 * 24 * 3600 * 1000).toISOString();
      const cQ = (q) => window.SI_DB.raw.count("intel_submissions", q).catch(() => null);
      Promise.all([
        cQ(""),
        cQ("status=eq.verified"),
        cQ("severity=eq.critical"),
        cQ(`created_at=gte.${since7d}`),
        cQ(`created_at=gte.${since14d}&created_at=lt.${since7d}`),
        cQ(`created_at=gte.${since24h}`),
        cQ(`created_at=gte.${since48h}&created_at=lt.${since24h}`),
        cQ(`created_at=gte.${since24h}&severity=in.(high,critical)`),
        cQ(`created_at=gte.${since48h}&created_at=lt.${since24h}&severity=in.(high,critical)`),
        cQ(`created_at=gte.${since14d}`),
      ]).then(([total, verified, critical, d7, prevD7, d24, prevD24, high24, prevHigh24, d14]) => {
        if (cancelled) return;
        setIntelCounts({ total, verified, critical, d7, prevD7, d24, prevD24, high24, prevHigh24, d14 });
      });

      const [cs, ct, w, fc, pts, ps, cz, fl, rd, ts, al, rfs, rsg, lns, trs, tro, chp, awx, aev, eqs, wfs] = await Promise.all([
        window.SI_DB.carriers(),
        window.SI_DB.cities(),
        window.SI_DB.raw.select("active_weather_signals", "select=*&kind=eq.alert&order=severity.desc&limit=20"),
        window.SI_DB.raw.select("active_weather_signals", "select=*&kind=eq.forecast&order=effective_at.asc&limit=200"),
        window.SI_DB.raw.select("ports", "select=*&is_featured=eq.true&order=name.asc").catch(() => []),
        window.SI_DB.raw.select("port_signals", "select=*&order=retrieved_at.desc&limit=200").catch(() => []),
        window.SI_DB.raw.select("cold_storage_signals", "select=*&order=period.desc&limit=200").catch(() => []),
        window.SI_DB.raw.select("fuel_signals", "select=*&order=period.desc&limit=400").catch(() => []),
        window.SI_DB.raw.select("active_road_signals", "select=*&order=severity.desc&limit=50").catch(() => []),
        // Truck stops total = ~2,254 (HANDOFF). Limit at 3000 so the per-metro
        // counts aren't quietly clipped when new stops are ingested.
        window.SI_DB.raw.select("truck_stops", "select=id,chain,name,lat,lng,city,state,metro_city_id,amenities&metro_city_id=not.is.null&limit=3000").catch(() => []),
        window.SI_DB.raw.select("intel_alerts", "select=*&status=eq.published&order=published_at.desc&limit=8").catch(() => []),
        window.SI_DB.raw.select("refineries", "select=id,name,operator,city,state,padd,lat,lng,capacity_kbpd,products,metro_city_id&order=capacity_kbpd.desc").catch(() => []),
        // Refinery utilization signals — EIA weekly utilization_pct by scope
        // (US + PADD-1..5). Latest period per scope wins. Powers the colored
        // ring around each refinery pin.
        window.SI_DB.raw.select(
          "refinery_signals",
          "select=scope,signal_type,value_num,period&signal_type=eq.utilization_pct&order=period.desc&limit=200"
        ).catch(() => []),
        window.SI_DB.raw.select("carrier_strength_lanes", "select=*&order=carrier_slug.asc").catch(() => []),
        window.SI_DB.raw.select("tropical_storms", "select=*&order=advisory_at.desc&limit=20").catch(() => []),
        window.SI_DB.raw.select("tropical_outlook", "select=*&order=issued_at.desc&limit=20").catch(() => []),
        // Pull last-24h CHP incidents that we have a position for. The
        // bare select against last_seen_at avoids ingesting stale rows
        // when the cron has been off for a while.
        window.SI_DB.raw.select(
          "chp_incidents",
          `select=id,incident_type,category,location,location_desc,area,freeway,lat,lng,metro_city_id,log_time,last_seen_at` +
          `&last_seen_at=gte.${new Date(Date.now() - 24*3600*1000).toISOString()}` +
          `&order=last_seen_at.desc&limit=300`
        ).catch(() => []),
        window.SI_DB.raw.select("airport_weather", "select=*&order=observed_at.desc").catch(() => []),
        window.SI_DB.raw.select("airport_events", "select=*").catch(() => []),
        // M4.5+ in last 7d. Filter to anything we tagged as freight-relevant
        // (within 150mi of a tracked metro, refinery, or port).
        window.SI_DB.raw.select(
          "earthquakes",
          "select=*&or=(near_metro_id.not.is.null,near_refinery_id.not.is.null,near_port_id.not.is.null)" +
          "&order=occurred_at.desc&limit=20"
        ).catch(() => []),
        // Active fires worth surfacing: status not 'Out', AND either ≥500 acres
        // or within 60mi of a tracked metro. Sorted by acres desc.
        window.SI_DB.raw.select(
          "wildfires",
          "select=*&or=(acres.gte.500,near_metro_id.not.is.null)" +
          "&status=neq.Out&order=acres.desc&limit=20"
        ).catch(() => []),
      ]);
      if (cancelled) return;
      setCarriers(Array.isArray(cs) ? cs : []);
      setCities(Array.isArray(ct) ? ct : []);
      setWeather(Array.isArray(w) ? w : []);
      setForecast(Array.isArray(fc) ? fc : []);
      setPorts(Array.isArray(pts) ? pts : []);
      setPortSignals(Array.isArray(ps) ? ps : []);
      setColdStorage(Array.isArray(cz) ? cz : []);
      setFuel(Array.isArray(fl) ? fl : []);
      setRoads(Array.isArray(rd) ? rd : []);
      setTruckStops(Array.isArray(ts) ? ts : []);
      setAlerts(Array.isArray(al) ? al : []);
      setRefineries(Array.isArray(rfs) ? rfs : []);
      setRefinerySignals(Array.isArray(rsg) ? rsg : []);
      setCarrierLanes(Array.isArray(lns) ? lns : []);
      setTropicalStorms(Array.isArray(trs) ? trs : []);
      setTropicalOutlook(Array.isArray(tro) ? tro : []);
      setChpIncidents(Array.isArray(chp) ? chp : []);
      setAirportWx(Array.isArray(awx) ? awx : []);
      setAirportEvents(Array.isArray(aev) ? aev : []);
      setEarthquakes(Array.isArray(eqs) ? eqs : []);
      setWildfires(Array.isArray(wfs) ? wfs : []);

      setLoading(false);
    })();
    // refresh "now" every minute so timestamps stay live
    const tick = setInterval(() => setNow(Date.now()), 60000);
    return () => { cancelled = true; clearInterval(tick); };
  }, []);

  // Scroll-on-arrival: home-page tiles can request a specific dashboard section
  // via sessionStorage('sc.scrollAnchor'). Read once after data loads and the
  // section has rendered, then clear so a regular dashboard visit doesn't
  // re-fire the scroll. Tries the anchor a couple of times because some sections
  // mount lazily (Leaflet maps, gravity map, etc.).
  useEffectAuth(() => {
    if (loading) return;
    let anchor;
    try { anchor = sessionStorage.getItem("sc.scrollAnchor"); } catch {}
    if (!anchor) return;
    try { sessionStorage.removeItem("sc.scrollAnchor"); } catch {}
    let tries = 0;
    const t = setInterval(() => {
      tries++;
      const el = document.getElementById(anchor);
      if (el) {
        el.scrollIntoView({ behavior: "smooth", block: "start" });
        clearInterval(t);
      } else if (tries > 20) {
        clearInterval(t);
      }
    }, 150);
    return () => clearInterval(t);
  }, [loading]);

  async function doSignOut() {
    await window.SI_DB.auth.signOut();
    onNav("home");
  }

  if (loading) {
    return (
      <div style={{ padding: 96, textAlign: "center", color: "var(--ink-soft)" }}>
        Loading dashboard…
      </div>
    );
  }
  if (!user) {
    return (
      <div className="page-insights-v2 article-detail">
        <section className="article-detail-hero">
          <div className="article-detail-inner" style={{ maxWidth: 480 }}>
            <h1 className="article-title-big" style={{ fontSize: 32 }}>You're signed out.</h1>
            <p className="article-dek">Sign in to view your dashboard.</p>
            <button className="btn-primary" onClick={() => onNav("signin")}>Sign in →</button>
          </div>
        </section>
      </div>
    );
  }

  function carrierName(slug) {
    if (!slug) return "Unknown";
    const c = carriers.find(x => x.slug === slug);
    return c ? c.name : slug.toUpperCase();
  }
  function prettyCity(id) {
    if (!id) return "Other";
    const c = cities.find(x => x.id === id);
    if (c) return `${c.name}, ${c.state}`;
    return id.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
  }

  // Apply the source exclusion BEFORE computing any derived metrics so the
  // numbers, feed, pattern radar, and tiles all respect the toggle.
  const allIntel = excludedSources.size > 0
    ? allIntelRaw.filter(r => !excludedSources.has(rowSource(r)))
    : allIntelRaw;
  // Tally what's coming from where, even when filtered, so the chip UI can
  // show counts (and let the user toggle off the dominant source).
  const sourceCounts = {};
  allIntelRaw.forEach(r => {
    const s = rowSource(r);
    sourceCounts[s] = (sourceCounts[s] || 0) + 1;
  });

  // Derived metrics — recompute on every render, cheap.
  const day = 24 * 3600 * 1000;
  const week = 7 * day;
  const recent24h = allIntel.filter(r => now - new Date(r.created_at).getTime() < day);
  const recent7d = allIntel.filter(r => now - new Date(r.created_at).getTime() < week);
  const highSev24h = recent24h.filter(r => r.severity === "high" || r.severity === "critical");
  // Full critical-rows list — used for in-memory length fallbacks. The
  // displayed top-5 list (criticalDisplay below) is a slice of this; never
  // use the slice for a "how many critical?" count.
  const criticalRows = allIntel.filter(r => r.severity === "critical");
  const criticalAll = criticalRows.slice(0, 5);

  // By-carrier counts (last 7 days)
  const byCarrier = {};
  recent7d.forEach(r => {
    const k = r.parsed_carrier || "unknown";
    byCarrier[k] = (byCarrier[k] || 0) + 1;
  });
  // Total distinct carriers with chatter (the headline number in the tile).
  // topCarriers below is the *displayed rank* (top 8) — its .length is at
  // most 8 and must NOT be used for the "how many carriers" tile.
  const carriersWithChatterCount = Object.keys(byCarrier).filter(k => k !== "unknown").length;
  const topCarriers = Object.entries(byCarrier)
    .filter(([k]) => k !== "unknown")
    .sort((a, b) => b[1] - a[1])
    .slice(0, 8);
  const maxCount = topCarriers.length ? topCarriers[0][1] : 1;

  // By-intel-type
  const byType = {};
  recent7d.forEach(r => {
    const k = r.parsed_intel_type || "other";
    byType[k] = (byType[k] || 0) + 1;
  });
  const typeRank = Object.entries(byType).sort((a, b) => b[1] - a[1]).slice(0, 6);

  // Per-metro for subscribed metros (count of recent intel + last_sent)
  const subbedCityIds = pulseSubs.map(s => s.city_id);
  const intelByMetro = {};
  recent7d.forEach(r => {
    if (!r.parsed_city) return;
    intelByMetro[r.parsed_city] = (intelByMetro[r.parsed_city] || 0) + 1;
  });

  function timeAgo(iso) {
    const ms = now - new Date(iso).getTime();
    const m = Math.floor(ms / 60000);
    if (m < 1) return "just now";
    if (m < 60) return `${m}m ago`;
    const h = Math.floor(m / 60);
    if (h < 24) return `${h}h ago`;
    const d = Math.floor(h / 24);
    return `${d}d ago`;
  }

  return (
    <div className="dash">
      {/* COMMAND BAR */}
      <header className="dash-bar">
        <div className="dash-bar-inner">
          <div className="dash-bar-left">
            <div className="dash-live">
              <span className="dash-live-dot" />
              LIVE
            </div>
            <div className="dash-bar-title">
              <span style={{ color: "var(--ink-soft)" }}>Welcome,</span>{" "}
              <strong>{user.email}</strong>
            </div>
          </div>
          <div className="dash-bar-right">
            <button className="btn-ghost" onClick={() => onNav("home")}>+ Subscribe</button>
            <button className="btn-ghost" onClick={() => onNav("admin")}>Editor</button>
            <button className="btn-ghost" onClick={doSignOut}>Sign out</button>
          </div>
        </div>
        {(() => {
          // Combined crawler: severe weather warnings AND high-severity intel
          // events scroll across the top. Weather goes first because freight
          // planners need that the moment it hits.
          const SEV = { extreme: 4, severe: 3 };
          const wxAlerts = (weather || [])
            .filter(w => SEV[(w.severity || "").toLowerCase()] >= 3)
            .filter(w => !w.expires_at || new Date(w.expires_at).getTime() > Date.now());
          const totalAlerts = wxAlerts.length + highSev24h.length;
          if (totalAlerts === 0) return null;
          function fmtTime(iso) {
            if (!iso) return "";
            const d = new Date(iso);
            return d.toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true });
          }
          return (
            <div className="dash-ticker">
              <span className="dash-ticker-label">
                {wxAlerts.length > 0 && <>⚠ {wxAlerts.length} weather warning{wxAlerts.length > 1 ? "s" : ""} · </>}
                {highSev24h.length} high-severity event{highSev24h.length === 1 ? "" : "s"} · 24h
              </span>
              <div className="dash-ticker-feed">
                {wxAlerts.slice(0, 6).map((w, i) => (
                  <span key={`wx-${i}`} className="dash-ticker-item dash-ticker-wx">
                    <span className="dash-ticker-pill">{(w.severity || "").toUpperCase()}</span>
                    <strong>{w.event || "Alert"}</strong> · {prettyCity(w.city_id)}
                    {w.expires_at && <> · until {fmtTime(w.expires_at)}</>}
                  </span>
                ))}
                {highSev24h.slice(0, 5).map((it, i) => (
                  <span key={`int-${i}`} className="dash-ticker-item">
                    <strong>{carrierName(it.parsed_carrier)}</strong> · {prettyCity(it.parsed_city)} · {(it.raw_text || "").slice(0, 90)}…
                  </span>
                ))}
              </div>
            </div>
          );
        })()}
      </header>

      {/* TAB NAV — splits the dashboard into 5 focused views, each
          deep-linkable at /#/account/<tab>. */}
      <DashTabNav tab={validTab} onSelect={setTab} />

      {/* WEATHER STRIP — 12 metros at a glance · CORRIDORS */}
      {inTab("corridors") && <DashWeatherStrip
        forecast={forecast}
        cities={(window.SI_DATA && window.SI_DATA.FEATURED_CITIES) || []}
        subscribedIds={pulseSubs.map(s => s.city_id)}
        activeCityId={weatherCity}
        onCityClick={setWeatherCity}
      />}
      {inTab("corridors") && weatherCity && (() => {
        const fc = (window.SI_DATA && window.SI_DATA.FEATURED_CITIES) || [];
        const c = fc.find(x => x.id === weatherCity);
        const name = c ? `${c.name}, ${c.state}` : weatherCity;
        return (
          <div className="dash-weather-expanded">
            <div className="dash-weather-expanded-head">
              <h3 className="dash-weather-expanded-h">7-day forecast · {name}</h3>
              <button className="dash-weather-expanded-close" onClick={() => setWeatherCity(null)} aria-label="Close">×</button>
            </div>
            <ForecastStrip cityId={weatherCity} cityName={name} forecast={forecast} />
          </div>
        );
      })()}

      {/* WHAT WE'RE WATCHING — published intel_alerts (synthesizer output) · NOW */}
      {inTab("now") && <AlertsStrip alerts={alerts} cities={cities} />}

      {/* ACTIVE WARNINGS — severe/extreme NWS alerts auto-flagged with time window · NOW */}
      {inTab("now") && <ActiveWarningsBanner weather={weather} cities={cities} />}

      {/* Source filter intentionally hidden from public surface — internal only. */}

      {/* TOP-LINE METRICS — clickable tiles, each drills into its detail section · NOW */}
      {inTab("now") && <section className="dash-grid dash-stats">
        {(() => {
          const sigSpark   = dailyBuckets(allIntel, 7);
          const sigPrev24h = allIntel.filter(r => {
            const ms = now - new Date(r.created_at).getTime();
            return ms >= day && ms < 2 * day;
          }).length;
          const highSpark   = dailyBuckets(allIntel, 7, r => r.severity === "high" || r.severity === "critical");
          const highPrev24h = allIntel.filter(r => {
            if (!(r.severity === "high" || r.severity === "critical")) return false;
            const ms = now - new Date(r.created_at).getTime();
            return ms >= day && ms < 2 * day;
          }).length;
          // Carriers w/ chatter sparkline = unique carriers each day for the last 7 days
          const carrSpark = (() => {
            const today = new Date(); today.setUTCHours(0, 0, 0, 0);
            const sets = new Array(7).fill(null).map(() => new Set());
            allIntel.forEach(r => {
              if (!r.parsed_carrier) return;
              const t = new Date(r.posted_at || r.created_at);
              const idx = 6 - Math.floor((today.getTime() - t.getTime()) / 86400000);
              if (idx >= 0 && idx < 7) sets[idx].add(r.parsed_carrier);
            });
            return sets.map(s => s.size);
          })();
          return (
            <>
              <StatTile
                label="Signals · 24h" accent="blue"
                value={(intelCounts && intelCounts.d24 != null) ? intelCounts.d24 : recent24h.length}
                sub={`${(intelCounts && intelCounts.d7 != null) ? intelCounts.d7 : recent7d.length} in 7d · click to view feed →`}
                spark={sigSpark}
                delta={{
                  curr: (intelCounts && intelCounts.d24 != null) ? intelCounts.d24 : recent24h.length,
                  prev: (intelCounts && intelCounts.prevD24 != null) ? intelCounts.prevD24 : sigPrev24h,
                }}
                onClick={() => { setFilter({ ...filter, severity: null }); document.getElementById("dash-feed")?.scrollIntoView({ behavior: "smooth" }); }}
              />
              <StatTile
                label="High severity · 24h" accent="red"
                value={(intelCounts && intelCounts.high24 != null) ? intelCounts.high24 : highSev24h.length}
                sub={`${(intelCounts && intelCounts.critical != null) ? intelCounts.critical : criticalRows.length} critical · filter feed →`}
                spark={highSpark}
                delta={{
                  curr: (intelCounts && intelCounts.high24 != null) ? intelCounts.high24 : highSev24h.length,
                  prev: (intelCounts && intelCounts.prevHigh24 != null) ? intelCounts.prevHigh24 : highPrev24h,
                }}
                onClick={() => { setFilter({ ...filter, severity: "high" }); document.getElementById("dash-feed")?.scrollIntoView({ behavior: "smooth" }); }}
              />
              <StatTile
                label="Carriers w/ chatter" accent="violet"
                value={carriersWithChatterCount}
                sub={topCarriers[0] ? `${carrierName(topCarriers[0][0])} #1 of ${carriersWithChatterCount} →` : "—"}
                spark={carrSpark}
                onClick={() => document.getElementById("dash-carrier-pulse")?.scrollIntoView({ behavior: "smooth" })}
              />
              <StatTile
                label="Your subscriptions" accent="teal"
                value={pulseSubs.length}
                sub={`${Object.keys(intelByMetro).length} metros active →`}
                onClick={() => document.getElementById("dash-your-metros")?.scrollIntoView({ behavior: "smooth" })}
              />
              <StatTile
                label="Weather alerts" accent="amber"
                value={weather.length}
                sub={`${forecast.length} forecast pts →`}
                onClick={() => document.getElementById("dash-roads")?.scrollIntoView({ behavior: "smooth" })}
              />
              <StatTile
                label="Ports tracked" accent="cyan"
                value={ports.length}
                sub={`${portSignals.length} live signals →`}
                onClick={() => document.getElementById("dash-port-pulse")?.scrollIntoView({ behavior: "smooth" })}
              />
              <StatTile
                label="Cold storage" accent="ice"
                value={coldStorage.length}
                sub="commodities tracked →"
                onClick={() => document.getElementById("dash-cold-storage")?.scrollIntoView({ behavior: "smooth" })}
              />
              <StatTile
                label="Market signals" accent="green"
                value={fuel.length}
                sub="fuel + commodities →"
                onClick={() => document.getElementById("dash-markets")?.scrollIntoView({ behavior: "smooth" })}
              />
            </>
          );
        })()}
      </section>}

      {/* LIVE OPS MAP — single Leaflet showing every freight-disruption
          signal with a position right now. Sits between the 8 stat tiles
          and the hero grid so it's the visual anchor of the NOW tab. */}
      {inTab("now") && <LiveOpsMap
        chpIncidents={chpIncidents}
        wildfires={wildfires}
        airportWx={airportWx}
        earthquakes={earthquakes}
      />}

      {/* INTEL REPORT — Tableau-style · CARRIERS */}
      {inTab("carriers") && <div style={{ maxWidth: 1400, margin: "0 auto", padding: "0 24px" }}>
        <IntelReport
          allIntel={allIntel}
          carriers={carriers}
          cities={cities}
          weather={weather}
          forecast={forecast}
          pulseSubs={pulseSubs}
          filter={filter}
          setFilter={setFilter}
          counts={intelCounts}
        />
      </div>}

      {/* MAIN GRID hero (Small Package Pulse · Intel Types · Your Metros · Critical) · NOW */}
      {inTab("now") && <section className="dash-main">
        {/* CARRIER PULSE */}
        <div className="dash-card" data-accent="violet" id="dash-carrier-pulse">
          <div className="dash-card-head">
            <h3 className="dash-card-h">Small package pulse</h3>
            <span className="dash-card-sub">Last 7 days · sorted by volume</span>
          </div>
          <ul className="dash-bars">
            {topCarriers.map(([slug, cnt]) => {
              const cc = window.carrierColor ? window.carrierColor(slug) : "var(--ink)";
              return (
                <li key={slug} className="dash-bar-row dash-bar-clickable" onClick={() => setFilter({ ...filter, carrier: filter.carrier === slug ? null : slug })}>
                  <span className="dash-bar-label" style={{ color: cc, fontWeight: 600 }}>
                    <span className="dash-carrier-dot" style={{ background: cc }} />
                    {carrierName(slug)}
                  </span>
                  <div className="dash-bar-track">
                    <div className="dash-bar-fill" style={{ width: `${(cnt / maxCount) * 100}%`, background: cc }} />
                  </div>
                  <span className="dash-bar-num">{cnt}</span>
                </li>
              );
            })}
            {topCarriers.length === 0 && <li className="dash-empty">No chatter yet — pipeline still warming up.</li>}
          </ul>
        </div>

        {/* INTEL TYPES */}
        <div className="dash-card" data-accent="blue">
          <div className="dash-card-head">
            <h3 className="dash-card-h">What kind of trouble</h3>
            <span className="dash-card-sub">Last 7 days · operational signal types</span>
          </div>
          <ul className="dash-bars">
            {typeRank.map(([t, cnt]) => (
              <li key={t} className="dash-bar-row">
                <span className="dash-bar-label">{t.replace(/_/g, " ")}</span>
                <div className="dash-bar-track">
                  <div className="dash-bar-fill" style={{ width: `${(cnt / (typeRank[0][1] || 1)) * 100}%`, background: typeColor(t) }} />
                </div>
                <span className="dash-bar-num">{cnt}</span>
              </li>
            ))}
            {typeRank.length === 0 && <li className="dash-empty">No types yet.</li>}
          </ul>
        </div>

        {/* YOUR METROS */}
        <div className="dash-card" data-accent="teal" id="dash-your-metros">
          <div className="dash-card-head">
            <h3 className="dash-card-h">Your metros</h3>
            <span className="dash-card-sub">{pulseSubs.length} subscription{pulseSubs.length === 1 ? "" : "s"}</span>
          </div>
          {pulseSubs.length === 0 ? (
            <div className="dash-empty">
              You're not subscribed to any metros.{" "}
              <a href="#" onClick={(e) => { e.preventDefault(); onNav("home"); }}>Pick one from the homepage →</a>
            </div>
          ) : (
            <ul className="dash-metro-list">
              {pulseSubs.map((s) => {
                // Per-metro top carriers from intel last 7d
                const metroIntel = recent7d.filter(r => r.parsed_city === s.city_id);
                const carrierCounts = {};
                metroIntel.forEach(r => {
                  if (!r.parsed_carrier) return;
                  carrierCounts[r.parsed_carrier] = (carrierCounts[r.parsed_carrier] || 0) + 1;
                });
                const topMetroCarriers = Object.entries(carrierCounts)
                  .sort((a, b) => b[1] - a[1])
                  .slice(0, 5);
                return (
                  <li key={s.city_id} className="dash-metro-row dash-metro-row-detail">
                    <div className="dash-metro-name">{prettyCity(s.city_id)}</div>
                    <div className="dash-metro-stats">
                      <span><strong>{intelByMetro[s.city_id] || 0}</strong> signals · 7d</span>
                      <span>·</span>
                      <span>{s.send_count || 0} digests sent</span>
                    </div>
                    {topMetroCarriers.length > 0 ? (
                      <div className="dash-metro-carriers">
                        <span className="dash-metro-carriers-label">Top carriers:</span>
                        {topMetroCarriers.map(([slug, cnt]) => {
                          const cc = window.carrierColor ? window.carrierColor(slug) : "var(--ink)";
                          return (
                            <a key={slug} href={`#/carrier/${slug}`}
                                    className="dash-metro-carrier-chip"
                                    style={{ borderColor: cc, color: cc }}
                                    onClick={(e) => e.stopPropagation()}
                                    title={`Open ${carrierName(slug)} profile`}>
                              <span className="dash-carrier-dot" style={{ background: cc }} />
                              {carrierName(slug)} <strong>{cnt}</strong>
                            </a>
                          );
                        })}
                      </div>
                    ) : (
                      <div className="dash-metro-carriers" style={{ color: "var(--ink-soft)", fontSize: 12 }}>
                        No carrier-specific signal yet for this metro.
                      </div>
                    )}
                  </li>
                );
              })}
            </ul>
          )}
        </div>

        {/* CRITICAL FEED (4th column in hero grid) */}
        <div className="dash-card" data-accent="red">
          <div className="dash-card-head">
            <h3 className="dash-card-h">🚨 Critical right now</h3>
            <span className="dash-card-sub">Top {Math.min(criticalAll.length, 4)} highest-severity</span>
          </div>
          {criticalAll.length === 0 ? (
            <div className="dash-empty">No critical events. Pipeline running clean.</div>
          ) : (
            <ul className="dash-mini-feed">
              {criticalAll.slice(0, 4).map((it, i) => (
                <li key={i} className="dash-mini-row">
                  <div className="dash-mini-meta">
                    <span className="admin-tag" data-sev="critical">CRIT</span>
                    <strong>{carrierName(it.parsed_carrier)}</strong>
                    <span className="dash-mini-time">{timeAgo(it.created_at)}</span>
                  </div>
                  <div className="dash-mini-text">{(it.raw_text || "").slice(0, 110)}…</div>
                </li>
              ))}
            </ul>
          )}
        </div>
      </section>}

      {/* AIR CARGO HUBS · CARRIERS */}
      {inTab("carriers") && <section className="dash-grid" id="dash-air-cargo">
        <div className="dash-card" data-accent="violet">
          <div className="dash-card-head">
            <h3 className="dash-card-h">✈️ Air cargo hubs</h3>
            <span className="dash-card-sub">Major US airfreight nodes · cargo throughput, weather, signal volume</span>
          </div>
          {(() => {
            // Hand-curated. Tonnage is annual cargo handled (metric tons),
            // sourced from each operator's most recent published figures.
            // tsaTypMin/tsaPeakMin = typical / peak-hour security wait minutes
            // for the same passenger airport, sourced from TSA's published
            // CY 2024 wait-time dataset (tsa.gov/contact-center/airport-wait-times).
            // Cargo crews + handlers + non-revenue freight reposition through
            // the same checkpoints — when the line is 30+ minutes, time-critical
            // air-cargo handoffs slip too. Replace with live ingest later.
            const HUBS = [
              { iata: "MEM", cityId: "memphis",      city: "Memphis, TN",      carrier: "FedEx Express",  role: "Global SuperHub",        tons: 4_700_000, flights: 400, tsaTypMin:  6, tsaPeakMin: 12 },
              { iata: "SDF", cityId: "louisville",   city: "Louisville, KY",   carrier: "UPS Worldport",  role: "Global hub",             tons: 3_300_000, flights: 300, tsaTypMin:  5, tsaPeakMin: 11 },
              { iata: "ANC", cityId: "anchorage",    city: "Anchorage, AK",    carrier: "Multi-carrier",  role: "Asia–US transit pivot",  tons: 3_500_000, flights: 550, tsaTypMin:  5, tsaPeakMin: 12 },
              { iata: "CVG", cityId: "cincinnati",   city: "Cincinnati, OH",   carrier: "DHL + Amazon Air", role: "Americas hub",         tons: 1_300_000, flights: 200, tsaTypMin:  6, tsaPeakMin: 14 },
              { iata: "IND", cityId: "indianapolis", city: "Indianapolis, IN", carrier: "FedEx + Amazon Air", role: "Secondary hub",      tons: 1_400_000, flights: 180, tsaTypMin:  7, tsaPeakMin: 15 },
              { iata: "ATL", cityId: "atlanta",      city: "Atlanta, GA",      carrier: "Multi-carrier",  role: "Pax + cargo",            tons:   650_000, flights: 100, tsaTypMin: 14, tsaPeakMin: 28 },
              { iata: "OAK", cityId: null,           city: "Oakland, CA",      carrier: "FedEx + Amazon Air", role: "West Coast hub",     tons:   650_000, flights:  90, tsaTypMin:  9, tsaPeakMin: 19 },
              { iata: "MIA", cityId: "miami",        city: "Miami, FL",        carrier: "Multi-carrier",  role: "Latin America gateway",  tons: 2_300_000, flights: 250, tsaTypMin: 16, tsaPeakMin: 31 },
            ];
            function fmtTons(n) {
              if (n >= 1_000_000) return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M";
              if (n >= 1_000)     return (n / 1_000).toFixed(0) + "K";
              return String(n);
            }
            // METAR-keyed lookup so we can drop a flight-category badge on
            // each hub card. Maps IATA → airport_weather row by ICAO.
            const FLIGHT_CAT_COLOR = {
              VFR:  "oklch(0.50 0.14 145)",  // green
              MVFR: "oklch(0.55 0.16 95)",   // amber
              IFR:  "oklch(0.55 0.20 25)",   // red
              LIFR: "oklch(0.45 0.22 305)",  // magenta — worst tier
            };
            const wxByIata = {};
            (airportWx || []).forEach(a => {
              if (!a.icao_id) return;
              const iata = a.icao_id.replace(/^K/, "");
              if (!wxByIata[iata]) wxByIata[iata] = a;
            });
            // FAA NAS Traffic Management Initiatives keyed by 3-letter IATA
            // (FAA omits the K prefix) — ground stops, delays, closures.
            const nasByIata = {};
            (airportEvents || []).forEach(e => {
              if (e.airport_id) nasByIata[e.airport_id] = e;
            });
            return (
              <div className="dash-air-grid">
                {HUBS.map((h) => {
                  const wxAlerts = h.cityId
                    ? weather.filter(w => w.city_id === h.cityId).length : 0;
                  const f = forecast.find(x => x.city_id === h.cityId && x.temp_high != null);
                  const cond = f && f.short_forecast ? f.short_forecast : "";
                  const high = f && f.temp_high != null ? `${f.temp_high}°` : null;
                  const cityIntel7d = h.cityId
                    ? recent7d.filter(r => r.parsed_city === h.cityId).length : 0;
                  const metar = wxByIata[h.iata];
                  const flightCat = metar && metar.flight_cat;
                  const degraded = flightCat && flightCat !== "VFR";
                  const nas = nasByIata[h.iata];
                  // Ranked TMI severity: closure > ground stop > ground delay > arrival/dep delay
                  const tmi = nas && (
                    nas.airport_closure ? { kind: "CLOSED",  tone: "crit" } :
                    nas.ground_stop     ? { kind: "GS",      tone: "crit" } :
                    nas.ground_delay    ? { kind: "GDP",     tone: "high" } :
                    nas.arrival_delay   ? { kind: "ARR DLY", tone: "high" } :
                    nas.departure_delay ? { kind: "DEP DLY", tone: "high" } :
                    nas.deicing         ? { kind: "DEICE",   tone: "mod"  } :
                    null
                  );
                  // TSA peak ≥30 min counts as a card alert because that's when
                  // crew/handler reposition slips affect the cargo handoff window.
                  const tsaPeakAlert = h.tsaPeakMin != null && h.tsaPeakMin >= 30;
                  const cardAlert = wxAlerts > 0 || degraded || tsaPeakAlert || (tmi && tmi.tone !== "mod");
                  return (
                    <div key={h.iata} className={`dash-air-card${cardAlert ? " dash-air-card-alert" : ""}`}>
                      <div className="dash-air-head">
                        <span className="dash-air-iata">{h.iata}</span>
                        <div>
                          <div className="dash-air-city">{h.city}</div>
                          <div className="dash-air-role">{h.role}</div>
                        </div>
                      </div>
                      <div className="dash-air-carrier">{h.carrier}</div>
                      <div className="dash-air-stats">
                        <div className="dash-air-stat">
                          <div className="dash-air-stat-num">{fmtTons(h.tons)}</div>
                          <div className="dash-air-stat-l">tons / yr</div>
                        </div>
                        <div className="dash-air-stat">
                          <div className="dash-air-stat-num">{h.flights}</div>
                          <div className="dash-air-stat-l">daily flights</div>
                        </div>
                      </div>
                      {(high || wxAlerts > 0 || cityIntel7d > 0 || flightCat || tmi || h.tsaTypMin != null) && (
                        <div className="dash-air-foot">
                          {tmi && (
                            <span title={JSON.stringify({
                              groundStop: nas.ground_stop, groundDelay: nas.ground_delay,
                              closure: nas.airport_closure, arr: nas.arrival_delay,
                              dep: nas.departure_delay, deicing: nas.deicing,
                            }, null, 2)}
                                  style={{
                                    fontFamily: "var(--font-mono)", fontSize: 10,
                                    letterSpacing: "0.08em", padding: "2px 7px",
                                    borderRadius: 3, fontWeight: 600,
                                    background: tmi.tone === "crit" ? "oklch(0.94 0.05 25)"
                                              : tmi.tone === "high" ? "oklch(0.94 0.05 50)"
                                              : "oklch(0.95 0 0)",
                                    color: tmi.tone === "crit" ? "oklch(0.40 0.18 25)"
                                         : tmi.tone === "high" ? "oklch(0.42 0.16 50)"
                                         : "var(--ink-soft)",
                                  }}>
                              FAA · {tmi.kind}
                            </span>
                          )}
                          {h.tsaTypMin != null && (
                            <span title={`TSA security checkpoint · typical ${h.tsaTypMin}min · peak ${h.tsaPeakMin}min · sourced from TSA published CY data, not real-time`}
                                  style={{
                                    fontFamily: "var(--font-mono)", fontSize: 10,
                                    letterSpacing: "0.08em", padding: "2px 6px",
                                    borderRadius: 3, fontWeight: 600,
                                    background: tsaPeakAlert ? "oklch(0.94 0.05 50)" : "rgba(0,0,0,0.04)",
                                    color: tsaPeakAlert ? "oklch(0.42 0.16 50)" : "var(--ink-soft)",
                                  }}>
                              🛂 TSA · {h.tsaTypMin}m / {h.tsaPeakMin}m peak
                            </span>
                          )}
                          {flightCat && (
                            <span title={metar.raw_obs}
                                  style={{
                                    fontFamily: "var(--font-mono)", fontSize: 10,
                                    letterSpacing: "0.08em", padding: "2px 6px",
                                    borderRadius: 3, background: "rgba(0,0,0,0.04)",
                                    color: FLIGHT_CAT_COLOR[flightCat] || "var(--ink)",
                                  }}>
                              {flightCat}
                              {metar.wind_speed_kt != null && (
                                <> · {metar.wind_dir_deg ?? "—"}°/{metar.wind_speed_kt}kt
                                {metar.wind_gust_kt ? `G${metar.wind_gust_kt}` : ""}</>
                              )}
                              {metar.visibility != null && <> · {metar.visibility}mi</>}
                            </span>
                          )}
                          {high && (
                            <span title={cond}>
                              {weatherIconFor(cond)} {high}
                            </span>
                          )}
                          {wxAlerts > 0 && (
                            <span className="dash-air-flag">⚠ {wxAlerts} alert{wxAlerts === 1 ? "" : "s"}</span>
                          )}
                          {cityIntel7d > 0 && (
                            <span><strong>{cityIntel7d}</strong> 7d signals</span>
                          )}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            );
          })()}
          <p style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 14,
                      lineHeight: 1.5, fontStyle: "italic", fontFamily: "var(--font-mono)",
                      letterSpacing: "0.02em" }}>
            🛂 TSA wait values are typical / peak-hour averages from TSA's published
            CY 2024 dataset. Live realtime ingest from MyTSA is on the post-launch list.
            METAR + FAA NAS data above is real-time.
          </p>
        </div>
      </section>}

      {/* SMALL-PACKAGE HUB MAP · CARRIERS */}
      {inTab("carriers") && <section className="dash-grid">
        <div className="dash-card" data-accent="violet">
          <div className="dash-card-head">
            <h3 className="dash-card-h">📦 Small-package hub map</h3>
            <span className="dash-card-sub">{SMALL_PKG_HUBS.length} major hubs across UPS / FedEx / Amazon / DHL / OnTrac / LSO / GLS / Spee-Dee / USPS · pin color = carrier · ring = active issue / watching / clean (last 7 days)</span>
          </div>
          <HubStatusMap allIntel={allIntel} />
        </div>
      </section>}

      {/* CARRIER GRAVITY MAP · CARRIERS */}
      {inTab("carriers") && <section className="dash-grid">
        <div className="dash-card" data-accent="violet">
          <div className="dash-card-head">
            <h3 className="dash-card-h">🗺 Carrier gravity map · where they make their money</h3>
            <span className="dash-card-sub">
              State-by-state network density per carrier · synthesized from operator-network signal,
              hub footprints, and curated dominant corridors from public filings ·
              {carrierLanes.length > 0 && <> {carrierLanes.length} curated corridor strengths across {BIG_CARRIERS.length} big carriers</>}
            </span>
          </div>
          <CarrierGravityMap
            allIntel={allIntel}
            cities={cities}
            carrierLanes={carrierLanes}
          />
          <p style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 14,
                      lineHeight: 1.5, fontStyle: "italic" }}>
            <strong>How to read:</strong> single-carrier mode shows that carrier's intel-signal
            density across the country layered with curated corridor-strength claims (DOMINANT
            tier from 10-Ks and earnings calls). Dominant-per-state mode colors every state by
            whichever carrier has the most operator-network signal there in the last 90 days —
            the network gravity nobody else publishes.
          </p>
        </div>
      </section>}

      {/* ACROSS THE NETWORK · CORRIDORS */}
      {inTab("corridors") && <section className="dash-grid">
        <div className="dash-card" data-accent="cyan">
          <div className="dash-card-head">
            <h3 className="dash-card-h">Across the network</h3>
            <span className="dash-card-sub">All {((window.SI_DATA && window.SI_DATA.FEATURED_CITIES) || []).length} featured metros · top carriers from last 7 days · click a chip to filter</span>
          </div>
          {(() => {
            const allCities = (window.SI_DATA && window.SI_DATA.FEATURED_CITIES) || [];
            const subSet = new Set(pulseSubs.map(s => s.city_id));
            // Show subscribed first, then the rest
            const ordered = [
              ...allCities.filter(c => subSet.has(c.id)),
              ...allCities.filter(c => !subSet.has(c.id)),
            ];
            return (
              <div className="dash-metro-grid">
                {ordered.map((c) => {
                  const metroIntel = recent7d.filter(r => r.parsed_city === c.id);
                  const carrierCounts = {};
                  metroIntel.forEach(r => {
                    if (!r.parsed_carrier) return;
                    carrierCounts[r.parsed_carrier] = (carrierCounts[r.parsed_carrier] || 0) + 1;
                  });
                  const topMetroCarriers = Object.entries(carrierCounts)
                    .sort((a, b) => b[1] - a[1])
                    .slice(0, 4);
                  const isSub = subSet.has(c.id);
                  return (
                    <div key={c.id} className={`dash-metro-cell${isSub ? " dash-metro-cell-sub" : ""}`}>
                      <div className="dash-metro-cell-head">
                        <span className="dash-metro-cell-name">{c.name}, {c.state}</span>
                        {isSub && <span className="dash-metro-cell-pill">subscribed</span>}
                      </div>
                      <div className="dash-metro-cell-meta">
                        <strong>{metroIntel.length}</strong> signals · 7d
                      </div>
                      {topMetroCarriers.length > 0 ? (
                        <div className="dash-metro-cell-carriers">
                          {topMetroCarriers.map(([slug, cnt]) => {
                            const cc = window.carrierColor ? window.carrierColor(slug) : "var(--ink)";
                            return (
                              <button key={slug} className="dash-metro-carrier-chip"
                                      style={{ borderColor: cc, color: cc }}
                                      onClick={(e) => { e.stopPropagation(); setFilter({ ...filter, carrier: slug, city: c.id }); }}
                                      title="Click to filter the recent feed">
                                <span className="dash-carrier-dot" style={{ background: cc }} />
                                {carrierName(slug)} <strong>{cnt}</strong>
                              </button>
                            );
                          })}
                        </div>
                      ) : (
                        <div className="dash-metro-cell-empty">No carrier signal yet</div>
                      )}
                    </div>
                  );
                })}
              </div>
            );
          })()}
        </div>
      </section>}

      {/* NATIONAL & REGULATORY PULSE · NOW */}
      {inTab("now") && <section className="dash-grid">
        <div className="dash-card" data-accent="red">
          <div className="dash-card-head">
            <h3 className="dash-card-h">🌐 National &amp; regulatory pulse</h3>
            <span className="dash-card-sub">Intel that affects the network at large · last 30 days</span>
          </div>
          {(() => {
            const day30 = 30 * 24 * 3600 * 1000;
            const nowMs = Date.now();
            const SEV_RANK = { critical: 4, high: 3, moderate: 2, low: 1 };
            const tts = (r) => new Date(r.posted_at || r.created_at).getTime();
            const nat = allIntel
              .filter(r => !r.parsed_city)
              .filter(r => r.status === "verified")
              .filter(r => nowMs - tts(r) <= day30)
              .sort((a, b) => {
                const sd = (SEV_RANK[b.severity] || 0) - (SEV_RANK[a.severity] || 0);
                if (sd !== 0) return sd;
                return tts(b) - tts(a);
              })
              .slice(0, 12);
            if (nat.length === 0) {
              return <div className="dash-empty">No national-tier intel in the last 30 days.</div>;
            }
            return (
              <ul className="dash-event-list" style={{ marginTop: 0 }}>
                {nat.map((it) => (
                  <li key={it.id} className="dash-event">
                    <span className="admin-tag" data-sev={it.severity || "low"}>{(it.severity || "—").toUpperCase()}</span>
                    <div className="dash-event-body">
                      <div className="dash-event-meta">
                        {it.parsed_carrier ? (
                          <a href={`#/carrier/${it.parsed_carrier}`} className="carrier-name-link"><strong>{carrierName(it.parsed_carrier)}</strong></a>
                        ) : (
                          <strong>National</strong>
                        )}
                        {" · "}{it.parsed_intel_type || "—"}
                        {" · "}{timeAgo(it.created_at)}
                        {(() => {
                          const m = (it.notes || "").match(/cluster:(\d+)/);
                          return m ? <span className="dash-event-cluster" title="Reported by multiple independent sources">📰 {m[1]} sources</span> : null;
                        })()}
                      </div>
                      <div className="dash-event-text">{it.raw_text}</div>
                    </div>
                  </li>
                ))}
              </ul>
            );
          })()}
        </div>
      </section>}

      {/* FUEL & OIL MARKETS · MARKETS */}
      <span id="dash-markets"></span>
      {inTab("markets") && fuel.length > 0 && (() => {
        const latest = (s) => fuel.find(f => f.series === s && f.region !== "futures-month-ago");
        const monthAgo = (s) => fuel.find(f => f.series === s && f.region === "futures-month-ago");
        function MoMTag({ series }) {
          const cur = latest(series);
          const old = monthAgo(series);
          if (!cur || !old) return null;
          const a = parseFloat(cur.value_num);
          const b = parseFloat(old.value_num);
          if (!a || !b) return null;
          const pct = ((a - b) / b) * 100;
          const dir = pct > 0.5 ? "up" : pct < -0.5 ? "down" : "flat";
          const arrow = dir === "up" ? "▲" : dir === "down" ? "▼" : "—";
          return (
            <span className={`mom-tag mom-${dir}`} title={`Month-ago: $${b.toFixed(2)}`}>
              {arrow} {Math.abs(pct).toFixed(1)}% MoM
            </span>
          );
        }
        const wti = latest("wti_crude_futures");
        const brent = latest("brent_crude_futures");
        const ulsd = latest("ulsd_diesel_futures");
        const rbob = latest("rbob_gas_futures");
        const retailUS = latest("diesel_retail_us");
        const distCrack = latest("distillate_crack_spread");
        const gasCrack  = latest("gasoline_crack_spread");
        const crack321  = latest("crack_321_spread");
        const regions = [
          ["west", "West Coast"], ["east", "East Coast"], ["gulf", "Gulf Coast"],
          ["midwest", "Midwest"], ["rocky", "Rocky Mtn"]
        ].map(([r, name]) => ({ ...latest(`diesel_retail_${r}`), name }));

        // Implied retail diesel from ULSD futures: rough rule of thumb
        // retail = futures wholesale + ~$1.10/gal (taxes + dist + retail margin)
        const implied = ulsd ? parseFloat(ulsd.value_num) + 1.10 : null;
        const actualUS = retailUS ? parseFloat(retailUS.value_num) : null;
        const gap = (implied != null && actualUS != null) ? actualUS - implied : null;

        return (
          <section className="dash-section" data-accent="green">
            <div className="dash-section-head">
              <h3 className="dash-section-h">⛽ Fuel & oil markets</h3>
              <span className="dash-card-sub">Futures vs pump · the gap predicts fuel-surcharge moves</span>
            </div>
            <div className="dash-fuel-grid">
              {wti && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">WTI crude · futures</div>
                  <div className="dash-fuel-num">${parseFloat(wti.value_num).toFixed(2)}</div>
                  <div className="dash-fuel-unit">/ barrel · CL=F</div>
                  <MoMTag series="wti_crude_futures" />
                </div>
              )}
              {brent && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">Brent crude · futures</div>
                  <div className="dash-fuel-num">${parseFloat(brent.value_num).toFixed(2)}</div>
                  <div className="dash-fuel-unit">/ barrel · BZ=F · East-Coast benchmark</div>
                  <MoMTag series="brent_crude_futures" />
                </div>
              )}
              {wti && brent && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">Brent–WTI spread</div>
                  <div className="dash-fuel-num"
                       style={{ color: (parseFloat(brent.value_num) - parseFloat(wti.value_num)) > 5 ? "oklch(0.55 0.18 25)" : "var(--ink)" }}>
                    ${(parseFloat(brent.value_num) - parseFloat(wti.value_num)).toFixed(2)}
                  </div>
                  <div className="dash-fuel-unit">
                    {(parseFloat(brent.value_num) - parseFloat(wti.value_num)) > 5
                      ? "wide — East Coast refining cost rising"
                      : "tight — domestic crude advantage"}
                  </div>
                </div>
              )}
              {ulsd && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">ULSD diesel · futures</div>
                  <div className="dash-fuel-num">${parseFloat(ulsd.value_num).toFixed(3)}</div>
                  <div className="dash-fuel-unit">/ gallon · HO=F</div>
                  <MoMTag series="ulsd_diesel_futures" />
                </div>
              )}
              {rbob && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">RBOB gasoline · futures</div>
                  <div className="dash-fuel-num">${parseFloat(rbob.value_num).toFixed(3)}</div>
                  <div className="dash-fuel-unit">/ gallon · RB=F</div>
                  <MoMTag series="rbob_gas_futures" />
                </div>
              )}
              {retailUS && (
                <div className="dash-fuel-card">
                  <div className="dash-fuel-label">Diesel retail · US average</div>
                  <div className="dash-fuel-num">${parseFloat(retailUS.value_num).toFixed(3)}</div>
                  <div className="dash-fuel-unit">/ gallon · DOE weekly · {retailUS.period}</div>
                </div>
              )}
              {gap != null && (
                <div className="dash-fuel-card dash-fuel-divergence">
                  <div className="dash-fuel-label">Futures-to-pump gap</div>
                  <div className="dash-fuel-num" style={{ color: Math.abs(gap) > 0.30 ? "oklch(0.55 0.18 25)" : "oklch(0.50 0.14 145)" }}>
                    {gap > 0 ? "+" : ""}${gap.toFixed(2)}
                  </div>
                  <div className="dash-fuel-unit">
                    actual vs implied · {gap > 0.20 ? "pump above expectation" : gap < -0.20 ? "pump below expectation" : "in line"}
                  </div>
                </div>
              )}
            </div>
            <div className="dash-fuel-regions">
              <div className="dash-fuel-regions-label">Regional retail diesel:</div>
              {regions.filter(r => r.value_num).map(r => (
                <div key={r.name} className="dash-fuel-region">
                  <span className="dash-fuel-region-name">{r.name}</span>
                  <span className="dash-fuel-region-num">${parseFloat(r.value_num).toFixed(3)}</span>
                </div>
              ))}
            </div>

            {/* Stocks + production — EIA weekly. The inventory layer below
                price + crack: when distillate stocks compress, surcharges
                follow regardless of futures direction. */}
            {(() => {
              const stockSeries = [
                { key: "crude_stocks_commercial", label: "Commercial crude",     unit: "kbbl", color: "ink",   note: "US ex-SPR" },
                { key: "crude_stocks_cushing",    label: "Cushing OK",            unit: "kbbl", color: "ink",   note: "WTI pricing point" },
                { key: "crude_stocks_spr",        label: "Strategic Reserve",     unit: "kbbl", color: "ink",   note: "SPR" },
                { key: "distillate_stocks",       label: "Distillate stocks",     unit: "kbbl", color: "red",   note: "Diesel inventory" },
                { key: "gasoline_stocks",         label: "Gasoline stocks",       unit: "kbbl", color: "ink",   note: "Motor gasoline" },
                { key: "crude_production",        label: "US crude production",   unit: "kbpd", color: "ink",   note: "Weekly output" },
              ];
              const visible = stockSeries
                .map(s => ({ ...s, row: latest(s.key) }))
                .filter(s => s.row);
              if (visible.length === 0) return null;
              const fmt = (n) => Number(n).toLocaleString("en-US", { maximumFractionDigits: 0 });
              return (
                <div style={{ marginTop: 18, padding: "14px 16px",
                              background: "oklch(0.98 0.01 250)",
                              border: "1px solid oklch(0.90 0.04 250)",
                              borderRadius: 6 }}>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 11,
                                letterSpacing: "0.16em", textTransform: "uppercase",
                                color: "oklch(0.40 0.10 250)", marginBottom: 10 }}>
                    🛢 Crude + product stocks · weekly EIA
                  </div>
                  <div style={{ display: "grid",
                                gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))",
                                gap: 12 }}>
                    {visible.map(s => (
                      <div key={s.key}>
                        <div style={{ fontFamily: "var(--font-serif)", fontSize: 22,
                                      letterSpacing: "-0.02em",
                                      color: s.color === "red" ? "oklch(0.55 0.20 25)" : "var(--ink)" }}>
                          {fmt(s.row.value_num)}
                        </div>
                        <div style={{ fontSize: 11, color: "var(--ink-soft)",
                                      fontFamily: "var(--font-mono)",
                                      letterSpacing: "0.1em", textTransform: "uppercase",
                                      marginTop: 2 }}>
                          {s.label} · {s.unit}
                        </div>
                        <div style={{ fontSize: 10, color: "var(--ink-soft)",
                                      marginTop: 2, fontStyle: "italic" }}>
                          {s.note}
                        </div>
                      </div>
                    ))}
                  </div>
                  <p style={{ fontSize: 11, color: "var(--ink-soft)", lineHeight: 1.5,
                              margin: "12px 0 0" }}>
                    <strong>How to read:</strong> when distillate stocks compress sharply (5%+ in
                    4 weeks), expect diesel surcharge pressure within 2-4 weeks regardless of
                    futures direction. Cushing draws signal tightness at the WTI delivery point.
                    SPR direction shows the political-supply lever. Production above 13M bpd is
                    historical highs.
                  </p>
                </div>
              );
            })()}

            {(distCrack || gasCrack || crack321) && (
              <div style={{ marginTop: 18, padding: "14px 16px",
                            background: "oklch(0.97 0.015 145)",
                            border: "1px solid oklch(0.88 0.06 145)",
                            borderRadius: 6 }}>
                <div style={{ fontFamily: "var(--font-mono)", fontSize: 11,
                              letterSpacing: "0.16em", textTransform: "uppercase",
                              color: "oklch(0.40 0.10 145)", marginBottom: 10 }}>
                  Refining margins · the leading indicator
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
                  {distCrack && (
                    <div>
                      <div style={{ fontFamily: "var(--font-serif)", fontSize: 24, letterSpacing: "-0.02em" }}>
                        ${parseFloat(distCrack.value_num).toFixed(2)}
                      </div>
                      <div style={{ fontSize: 11, color: "var(--ink-soft)", fontFamily: "var(--font-mono)",
                                    letterSpacing: "0.1em", textTransform: "uppercase", marginTop: 2 }}>
                        Distillate crack · per bbl
                      </div>
                    </div>
                  )}
                  {gasCrack && (
                    <div>
                      <div style={{ fontFamily: "var(--font-serif)", fontSize: 24, letterSpacing: "-0.02em" }}>
                        ${parseFloat(gasCrack.value_num).toFixed(2)}
                      </div>
                      <div style={{ fontSize: 11, color: "var(--ink-soft)", fontFamily: "var(--font-mono)",
                                    letterSpacing: "0.1em", textTransform: "uppercase", marginTop: 2 }}>
                        Gasoline crack · per bbl
                      </div>
                    </div>
                  )}
                  {crack321 && (
                    <div>
                      <div style={{ fontFamily: "var(--font-serif)", fontSize: 24, letterSpacing: "-0.02em" }}>
                        ${parseFloat(crack321.value_num).toFixed(2)}
                      </div>
                      <div style={{ fontSize: 11, color: "var(--ink-soft)", fontFamily: "var(--font-mono)",
                                    letterSpacing: "0.1em", textTransform: "uppercase", marginTop: 2 }}>
                        3-2-1 crack · per bbl
                      </div>
                    </div>
                  )}
                </div>
                <p style={{ fontSize: 11, color: "var(--ink-soft)", lineHeight: 1.5, margin: "12px 0 0" }}>
                  <strong>How to read:</strong> the spread between refined-product futures and crude. When cracks
                  drop sharply, refineries throttle runs and product tightness follows in 4-8 weeks. When cracks
                  expand, refineries run hard — supply ahead.
                </p>
              </div>
            )}

            <p className="dash-footer-note" style={{ textAlign: "left", marginTop: 14 }}>
              <strong>Pump gap:</strong> when the futures-to-pump gap widens beyond ~$0.30, fuel surcharges
              under-recover for carriers and over-charge for shippers (lag effect). Watch the gap close, then
              renegotiate.
            </p>
          </section>
        );
      })()}

      {/* US REFINERIES · MARKETS */}
      {inTab("markets") && refineries.length > 0 && (
        <section className="dash-section" data-accent="green">
          <div className="dash-section-head">
            <h3 className="dash-section-h">🛢 US Refineries</h3>
            <span className="dash-card-sub">
              {refineries.length} major refineries · {(refineries.reduce((s,r) => s + (r.capacity_kbpd || 0), 0) / 1000).toFixed(2)}M bpd combined capacity
              · pin size = capacity tier · color = operator
            </span>
          </div>
          {(() => {
            // Latest utilization_pct per scope. Rows arrive ordered by period
            // desc, so first hit per scope wins.
            const paddUtil = {};
            (refinerySignals || []).forEach(s => {
              if (s.signal_type !== "utilization_pct") return;
              if (!(s.scope in paddUtil)) paddUtil[s.scope] = Number(s.value_num);
            });
            return <RefineriesMap refineries={refineries} paddUtil={paddUtil} />;
          })()}
          {refinerySignals.length > 0 && (
            <div style={{
              display: "flex", flexWrap: "wrap", gap: 16, marginTop: 10,
              fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-soft)",
              letterSpacing: "0.04em",
            }}>
              <span><span style={{ display: "inline-block", width: 14, height: 14, borderRadius: "50%", border: "2px solid #d97706", background: "transparent", marginRight: 5, verticalAlign: "middle" }} />ring · running hot ≥92%</span>
              <span><span style={{ display: "inline-block", width: 14, height: 14, borderRadius: "50%", border: "2px solid #16a34a", background: "transparent", marginRight: 5, verticalAlign: "middle" }} />ring · normal 85–92%</span>
              <span><span style={{ display: "inline-block", width: 14, height: 14, borderRadius: "50%", border: "2px solid #dc2626", background: "transparent", marginRight: 5, verticalAlign: "middle" }} />ring · throttled &lt;85%</span>
              <span style={{ marginLeft: "auto", fontStyle: "italic" }}>EIA weekly · PADD-level utilization</span>
            </div>
          )}
          <div style={{ marginTop: 16, display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 12 }}>
            {[
              ["I",  "East Coast"],
              ["II", "Midwest"],
              ["III","Gulf Coast"],
              ["IV", "Rocky Mtn"],
              ["V",  "West Coast"],
            ].map(([padd, label]) => {
              const inPadd = refineries.filter(r => r.padd === padd);
              const cap = inPadd.reduce((s, r) => s + (r.capacity_kbpd || 0), 0);
              // Latest utilization for this PADD (rows arrive period.desc)
              let util = null;
              for (const s of (refinerySignals || [])) {
                if (s.signal_type === "utilization_pct" && s.scope === `PADD-${padd}`) {
                  util = Number(s.value_num); break;
                }
              }
              const utilColor = util == null ? "var(--ink-soft)"
                              : util >= 92 ? "#d97706"
                              : util < 85  ? "#dc2626"
                              : "#16a34a";
              return (
                <div key={padd} style={{
                  background: "#fff", border: "1px solid var(--rule)",
                  borderRadius: 6, padding: "12px 14px"
                }}>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 10,
                                letterSpacing: "0.14em", textTransform: "uppercase",
                                color: "var(--ink-soft)" }}>
                    PADD {padd} · {label}
                  </div>
                  <div style={{ fontFamily: "var(--font-serif)", fontSize: 22,
                                letterSpacing: "-0.02em", marginTop: 4 }}>
                    {cap > 0 ? `${(cap / 1000).toFixed(2)}M` : "—"}
                  </div>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 10,
                                letterSpacing: "0.14em", color: "var(--ink-soft)",
                                marginTop: 2 }}>
                    bpd · {inPadd.length} refineries
                  </div>
                  {util != null && (
                    <div style={{
                      marginTop: 8, paddingTop: 8, borderTop: "1px solid var(--rule)",
                      display: "flex", alignItems: "center", gap: 6,
                      fontFamily: "var(--font-mono)", fontSize: 11,
                    }}>
                      <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: utilColor }} />
                      <span style={{ fontWeight: 700, color: utilColor }}>{util.toFixed(1)}%</span>
                      <span style={{ color: "var(--ink-soft)", letterSpacing: "0.04em" }}>utilization</span>
                    </div>
                  )}
                </div>
              );
            })}
          </div>
          <p style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 14,
                      lineHeight: 1.5, fontStyle: "italic" }}>
            Pin rings show PADD-level utilization from EIA's weekly series — running hot ≥92% (amber),
            normal 85–92% (green), throttled &lt;85% (red). Per-refinery outage flagging needs PADD
            outage reports (Reuters/Argus) — that's the next layer.
          </p>
        </section>
      )}

      {/* US CRUDE PRODUCTION BY STATE · MARKETS */}
      {inTab("markets") && (fuel || []).some(f => f.series === "crude_production_by_state") && (
        <section className="dash-section" data-accent="green">
          <div className="dash-section-head">
            <h3 className="dash-section-h">🛢 US crude production by state</h3>
            <span className="dash-card-sub">
              EIA 914 monthly survey · who actually pumps the oil ·
              hover any state for output share
            </span>
          </div>
          <CrudeProductionMap fuel={fuel} />
          <p style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 14,
                      lineHeight: 1.5, fontStyle: "italic" }}>
            <strong>Permian dominance:</strong> Texas + New Mexico account for roughly 56% of
            total US crude production by themselves. North Dakota (Bakken) is third. Together
            those three states drive most of US energy independence math.
          </p>
        </section>
      )}

      {/* TRUCK STOPS · MARKETS */}
      {inTab("markets") && <TruckStopSection
        truckStops={truckStops}
        cities={cities}
        pulseSubs={pulseSubs}
        fuel={fuel}
      />}

      {/* COMMODITIES · MARKETS */}
      {inTab("markets") && fuel.length > 0 && (() => {
        const latest = (s) => fuel.find(f => f.series === s && f.region !== "futures-month-ago");
        const monthAgo = (s) => fuel.find(f => f.series === s && f.region === "futures-month-ago");
        function MoMTag({ series }) {
          const cur = latest(series);
          const old = monthAgo(series);
          if (!cur || !old) return null;
          const a = parseFloat(cur.value_num);
          const b = parseFloat(old.value_num);
          if (!a || !b) return null;
          const pct = ((a - b) / b) * 100;
          const dir = pct > 0.5 ? "up" : pct < -0.5 ? "down" : "flat";
          const arrow = dir === "up" ? "▲" : dir === "down" ? "▼" : "—";
          return (
            <span className={`mom-tag mom-${dir}`} title={`Month-ago: $${b.toFixed(2)}`}>
              {arrow} {Math.abs(pct).toFixed(1)}% MoM
            </span>
          );
        }
        const commodities = [
          { series: "copper_futures",     name: "Copper",       why: "Wiring · motors · EV chargers" },
          { series: "aluminum_futures",   name: "Aluminum",     why: "Trailers · EV battery cases" },
          { series: "steel_hrc_futures",  name: "Steel (HRC)",  why: "Trailer bodies · rails" },
          { series: "lumber_futures",     name: "Lumber",       why: "Pallets · packaging" },
          { series: "natural_gas_futures",name: "Natural gas",  why: "CNG/LNG fleets · warehouse heat" },
          { series: "corn_futures",       name: "Corn",         why: "Ag freight demand · ethanol" },
          { series: "soybean_futures",    name: "Soybeans",     why: "Ag freight demand · biodiesel" },
          { series: "wheat_futures",      name: "Wheat",        why: "Ag freight demand" },
        ].map(c => ({ ...c, data: latest(c.series) })).filter(c => c.data);

        if (commodities.length === 0) return null;

        return (
          <section className="dash-section" data-accent="amber">
            <div className="dash-section-head">
              <h3 className="dash-section-h">📊 Commodities · transportation inputs</h3>
              <span className="dash-card-sub">
                Fleet OpEx and freight-demand drivers · delayed market data
              </span>
            </div>
            <div className="dash-commodity-grid">
              {commodities.map(c => (
                <div key={c.series} className="dash-commodity-card">
                  <div className="dash-commodity-name">{c.name}</div>
                  <div className="dash-commodity-num">${parseFloat(c.data.value_num).toFixed(2)}</div>
                  <div className="dash-commodity-unit">{(c.data.unit || "").replace(/_/g, " ").replace("usd per ", "/ ")}</div>
                  <MoMTag series={c.series} />
                  <div className="dash-commodity-why">{c.why}</div>
                </div>
              ))}
            </div>
            <p className="dash-footer-note" style={{ textAlign: "left", marginTop: 14 }}>
              <strong>Why this matters:</strong> tire prices follow rubber + steel; trailer build cost follows aluminum + steel;
              EV charging buildout follows copper; agricultural shipping follows corn/soy/wheat.
              Fleets that watch these inputs see OpEx changes 4-12 weeks early.
            </p>
          </section>
        );
      })()}

      {inTab("corridors") && <div className="dash-paired">
      {/* COLD STORAGE PULSE — USDA leading indicator for refrigerated freight */}
      <span id="dash-cold-storage"></span>
      {coldStorage.length > 0 && (
        <section className="dash-section" data-accent="ice">
          <div className="dash-section-head">
            <h3 className="dash-section-h">❄️ Cold storage pulse</h3>
            <span className="dash-card-sub">Monthly · leading indicator for reefer truckload demand</span>
          </div>
          <div className="dash-cold-grid">
            {coldStorage.slice(0, 8).map(s => {
              const lbs = parseFloat(s.value_lbs || 0);
              const display = lbs >= 1e9 ? `${(lbs/1e9).toFixed(2)}B`
                            : lbs >= 1e6 ? `${(lbs/1e6).toFixed(0)}M`
                            : lbs.toLocaleString();
              const yoy = parseFloat(s.yoy_pct || 0);
              const mom = parseFloat(s.mom_pct || 0);
              return (
                <div key={s.commodity} className="dash-cold-card">
                  <div className="dash-cold-cat">{s.category || "—"}</div>
                  <div className="dash-cold-name">{s.commodity.replace(/_/g, " ")}</div>
                  <div className="dash-cold-num">{display}</div>
                  <div className="dash-cold-unit">lbs · {s.period}</div>
                  <div className="dash-cold-deltas">
                    <span style={{ color: mom > 0 ? "oklch(0.55 0.18 25)" : mom < 0 ? "oklch(0.50 0.14 145)" : "var(--ink-soft)" }}>
                      MoM {mom > 0 ? "▲" : mom < 0 ? "▼" : "—"} {Math.abs(mom).toFixed(1)}%
                    </span>
                    <span style={{ color: yoy > 0 ? "oklch(0.55 0.18 25)" : yoy < 0 ? "oklch(0.50 0.14 145)" : "var(--ink-soft)" }}>
                      YoY {yoy > 0 ? "▲" : yoy < 0 ? "▼" : "—"} {Math.abs(yoy).toFixed(1)}%
                    </span>
                  </div>
                </div>
              );
            })}
          </div>
          <p className="dash-footer-note" style={{ textAlign: "left", marginTop: 14 }}>
            <strong>Why this matters:</strong> high holdings → expect outbound surge in 30-60 days (more reefer demand);
            low holdings → tight reefer capacity at restock.
          </p>
        </section>
      )}

      </div>}{/* /dash-paired */}

      {/* PORT PULSE · CORRIDORS */}
      <span id="dash-port-pulse"></span>
      {inTab("corridors") && ports.length > 0 && (
        <section className="dash-section" data-accent="cyan">
          <div className="dash-section-head">
            <h3 className="dash-section-h">⚓ Port pulse</h3>
            <span className="dash-card-sub">{ports.length} US ports monitored · live operational signal</span>
          </div>
          {/* Map of every monitored port. Pin size scales with TEU throughput
              (large coastal gateways like LA/LB/NY/NJ stand out at a glance);
              ring color reflects status — live (vessel signal received),
              active (intel signals on the parent metro this week), or
              monitoring (idle but watched). Quick way to see "where is the
              container traffic, and where is anything happening". */}
          {(() => {
            const TEU_FOR_MAP = {
              "usa-lax": 900_000, "usa-lgb": 830_000, "usa-nyc": 770_000,
              "usa-sav": 470_000, "usa-hou": 330_000, "usa-nfk": 290_000,
              "usa-sea": 180_000, "usa-tac": 160_000, "usa-chs": 220_000,
              "usa-oak": 190_000, "usa-balt": 95_000, "usa-mia": 95_000,
            };
            const portPoints = ports
              .filter(p => p.lat != null && p.lng != null)
              .map(p => {
                const teu = TEU_FOR_MAP[p.id] || 0;
                const size = teu >= 700_000 ? 18
                           : teu >= 300_000 ? 14
                           : teu >= 100_000 ? 11
                           : 8;
                const sig = portSignals.filter(s => s.port_id === p.id);
                const liveShips = sig.some(s => s.signal_type === "ships_at_anchor" || s.signal_type === "ships_at_berth");
                const cityId = p.city_id;
                const cityIntel = cityId
                  ? recent7d.filter(r => r.parsed_city === cityId).length
                  : 0;
                const status = liveShips ? "live" : cityIntel > 0 ? "active" : "monitoring";
                const color = status === "live"      ? "oklch(0.50 0.16 250)"
                            : status === "active"    ? "oklch(0.55 0.18 50)"
                            :                          "oklch(0.65 0.05 60)";
                const teuLine = teu > 0 ? `<div style="margin-top:6px;font-size:13px"><strong>${(teu/1000).toFixed(0)}K</strong> TEU/mo</div>` : "";
                const popupHtml = `
                  <div style="font-family:system-ui;font-size:12px;line-height:1.4;min-width:180px">
                    <div style="font-family:Helvetica;font-weight:700;text-transform:uppercase;
                                letter-spacing:0.05em;font-size:10px;color:${color};margin-bottom:4px">
                      ${status.toUpperCase()} · ${p.region ? p.region.replace("-", " ") : ""}
                    </div>
                    <div style="font-family:Helvetica;font-weight:600;font-size:14px;margin-bottom:2px">${p.name.replace(/'/g, "&#39;")}</div>
                    ${p.unlocode ? `<div style="font-family:monospace;font-size:11px;color:#666">${p.unlocode}</div>` : ""}
                    ${teuLine}
                    ${cityIntel > 0 ? `<div style="font-size:11px;color:#666;margin-top:4px"><b>${cityIntel}</b> signals · 7d</div>` : ""}
                  </div>`;
                return { lat: p.lat, lng: p.lng, size, color, popupHtml };
              });
            return (
              <div style={{ marginBottom: 16 }}>
                <MiniMap
                  points={portPoints}
                  height={340}
                  fitBounds={portPoints.length >= 2}
                  defaultCenter={[37.5, -97]}
                  defaultZoom={4}
                  attribution="© OpenStreetMap · © CARTO"
                />
                <div style={{
                  display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8,
                  fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-soft)",
                  letterSpacing: "0.04em",
                }}>
                  <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.50 0.16 250)", marginRight: 5, verticalAlign: "middle" }} />Live · vessel signal</span>
                  <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.55 0.18 50)", marginRight: 5, verticalAlign: "middle" }} />Active · intel on metro this week</span>
                  <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.65 0.05 60)", marginRight: 5, verticalAlign: "middle" }} />Monitoring</span>
                  <span style={{ marginLeft: "auto" }}>pin size = TEU/mo throughput tier</span>
                </div>
              </div>
            );
          })()}
          {(() => {
            // Hand-curated monthly container throughput — sourced from each
            // port authority's most recently published statistics page.
            // Updated quarterly. Numbers are rough monthly TEU averages
            // (twenty-foot equivalent units) over the last 12 months.
            const TEU_MONTHLY = {
              "usa-lax":   900_000,
              "usa-lgb":   830_000,
              "usa-nyc":   770_000,
              "usa-sav":   470_000,
              "usa-hou":   330_000,
              "usa-nfk":   290_000,
              "usa-sea":   180_000,  // NWSA Seattle terminals
              "usa-tac":   160_000,  // NWSA Tacoma terminals
              "usa-chs":   220_000,
              "usa-oak":   190_000,
              "usa-balt":   95_000,
              "usa-mia":    95_000,
            };
            // Port → metro fallback for ports whose own city_id isn't seeded
            const PORT_CITY = {
              "usa-sav":  null,        // Savannah not in 12 metros yet
              "usa-chs":  null,        // Charleston ditto
              "usa-oak":  null,        // Oakland not in 12 metros yet
              "usa-balt": null,
              "usa-nfk":  null,
            };
            function fmtTeu(n) {
              if (!n) return "—";
              if (n >= 1_000_000) return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M";
              if (n >= 1_000)     return (n / 1_000).toFixed(0) + "K";
              return String(n);
            }
            return (
              <div className="dash-port-grid-compact">
                {ports.map(p => {
                  const cityId = p.city_id || PORT_CITY[p.id] || null;
                  const teu = TEU_MONTHLY[p.id] || null;
                  const cityIntel7d = cityId
                    ? recent7d.filter(r => r.parsed_city === cityId).length
                    : 0;
                  const cityHighSev7d = cityId
                    ? recent7d.filter(r => r.parsed_city === cityId
                      && (r.severity === "high" || r.severity === "critical")).length
                    : 0;
                  const cityWeather = cityId
                    ? weather.filter(w => w.city_id === cityId).length
                    : 0;
                  const sig = portSignals.filter(s => s.port_id === p.id);
                  const anchor = sig.find(s => s.signal_type === "ships_at_anchor");
                  const berth  = sig.find(s => s.signal_type === "ships_at_berth");
                  const liveShips = !!(anchor || berth);
                  const liveIntel = cityIntel7d > 0;
                  const status = liveShips ? "live"
                                : liveIntel ? "active"
                                : "monitoring";
                  return (
                    <div key={p.id} className="dash-port-mini">
                      <div className="dash-port-mini-head">
                        <span className="dash-port-mini-name">{p.name.replace(/^Port of /, "")}</span>
                        <span className={`dash-port-mini-pill dpm-${status}`}>{status}</span>
                      </div>
                      <div className="dash-port-mini-meta">
                        {(p.region || "").replace("-", " ")} · {p.unlocode}
                      </div>
                      <div className="dash-port-mini-stats">
                        <span title="Monthly container throughput (TEU)">
                          <strong>{fmtTeu(teu)}</strong> TEU/mo
                        </span>
                        {liveShips && anchor != null && (
                          <span title="Vessels at anchor"><strong>{anchor.value_num}</strong> anchor</span>
                        )}
                        {liveShips && berth != null && (
                          <span title="Vessels at berth"><strong>{berth.value_num}</strong> berth</span>
                        )}
                      </div>
                      {cityId && (
                        <div className="dash-port-mini-stats dash-port-mini-stats-soft">
                          <span title="Operator-network signals tied to this port's metro, last 7 days">
                            <strong>{cityIntel7d}</strong> 7d signals
                          </span>
                          {cityHighSev7d > 0 && (
                            <span className="dash-port-mini-flag" title="High or critical severity">
                              ⚠ <strong>{cityHighSev7d}</strong> high
                            </span>
                          )}
                          {cityWeather > 0 && (
                            <span title="Active weather alerts at this metro">
                              🌩 <strong>{cityWeather}</strong>
                            </span>
                          )}
                        </div>
                      )}
                      {p.authority_url && (
                        <a className="dash-port-mini-link" href={p.authority_url} target="_blank" rel="noopener noreferrer">
                          Authority ↗
                        </a>
                      )}
                    </div>
                  );
                })}
              </div>
            );
          })()}
        </section>
      )}

      {/* Critical events now live in the hero grid as a compact card. */}

      {/* TROPICAL WATCH · NOW */}
      {inTab("now") && (tropicalStorms.length > 0 || tropicalOutlook.length > 0) && (() => {
        const CLASS_LABEL = {
          TD: "Tropical Depression", TS: "Tropical Storm",
          HU: "Hurricane", MH: "Major Hurricane",
          PT: "Post-Tropical", EX: "Extratropical",
          LO: "Remnant Low", DB: "Disturbance",
          WV: "Tropical Wave", SD: "Subtropical Depression",
          SS: "Subtropical Storm",
        };
        const accent = (s) => {
          if (!s) return "amber";
          const c = s.classification;
          if (c === "MH" || c === "HU") return "red";
          if (c === "TS" || c === "SS") return "amber";
          return "amber";
        };
        const sevHue = (s) => {
          const c = s.classification;
          if (c === "MH") return "oklch(0.50 0.22 25)";
          if (c === "HU") return "oklch(0.55 0.20 25)";
          if (c === "TS" || c === "SS") return "oklch(0.55 0.18 50)";
          return "oklch(0.55 0.14 95)";
        };
        const fmtBasin = (b) => ({
          atlantic: "Atlantic", eastpac: "East Pacific", centpac: "Central Pacific"
        }[b] || b);
        const sortedStorms = [...tropicalStorms].sort((a,b) => {
          const order = { MH: 0, HU: 1, TS: 2, SS: 3, TD: 4, SD: 5 };
          return (order[a.classification] || 9) - (order[b.classification] || 9);
        });
        const activeOutlook = tropicalOutlook.filter(o => (o.formation_2d || 0) > 0 || (o.formation_7d || 0) > 0);
        return (
          <section className="dash-section" data-accent="red">
            <style>{`
              .trop-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
                gap: 14px; }
              .trop-card { background: #fff; border: 1px solid var(--rule); border-radius: 8px;
                padding: 16px 18px; position: relative; overflow: hidden; }
              .trop-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0;
                width: 4px; }
              .trop-name { font-family: var(--font-serif); font-size: 22px; letter-spacing: -0.02em;
                margin: 0 0 4px; }
              .trop-meta { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em;
                text-transform: uppercase; color: var(--ink-soft); }
              .trop-row { font-size: 13px; margin-top: 8px; line-height: 1.4; }
              .trop-row strong { font-family: var(--font-serif); }
              .trop-out-card { background: oklch(0.97 0.005 95); }
              .trop-pct { display: inline-block; font-family: var(--font-mono); font-size: 11px;
                padding: 2px 7px; border-radius: 3px; letter-spacing: 0.05em; }
              .trop-pct-low { background: oklch(0.94 0.02 95); color: oklch(0.45 0.10 95); }
              .trop-pct-medium { background: oklch(0.94 0.05 50); color: oklch(0.42 0.16 50); }
              .trop-pct-high { background: oklch(0.94 0.05 25); color: oklch(0.40 0.18 25); }
            `}</style>
            <div className="dash-section-head">
              <h3 className="dash-section-h">🌀 Tropical watch</h3>
              <span className="dash-card-sub">
                {sortedStorms.length > 0
                  ? `${sortedStorms.length} active system${sortedStorms.length === 1 ? "" : "s"}`
                  : `${activeOutlook.length} area${activeOutlook.length === 1 ? "" : "s"} of disturbed weather`}
                {" · "}NHC advisories
              </span>
            </div>
            <div className="trop-grid">
              {sortedStorms.map(s => (
                <div key={s.id} className="trop-card" style={{ "--accent": sevHue(s) }}>
                  <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 4,
                                background: sevHue(s) }} />
                  <div className="trop-meta">
                    {fmtBasin(s.basin)} · {CLASS_LABEL[s.classification] || s.classification || "—"}
                  </div>
                  <h4 className="trop-name">{s.name || s.id?.toUpperCase()}</h4>
                  <div className="trop-row">
                    <strong>{s.intensity_kt || "—"} kt</strong> max winds
                    {s.pressure_mb && <> · <strong>{s.pressure_mb} mb</strong> central pressure</>}
                  </div>
                  {(s.lat != null && s.lng != null) && (
                    <div className="trop-row">
                      <strong>{parseFloat(s.lat).toFixed(1)}°, {parseFloat(s.lng).toFixed(1)}°</strong>
                      {s.movement_dir && <> · moving {s.movement_dir} at {s.movement_speed || "?"} kt</>}
                    </div>
                  )}
                  {s.advisory_at && (
                    <div className="trop-row" style={{ color: "var(--ink-soft)", fontSize: 11 }}>
                      Advisory: {new Date(s.advisory_at).toUTCString().replace(":00 GMT", "Z")}
                    </div>
                  )}
                  {s.public_advisory_url && (
                    <a href={s.public_advisory_url} target="_blank" rel="noopener noreferrer"
                       style={{ display: "inline-block", marginTop: 8, fontFamily: "var(--font-mono)",
                                fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase",
                                color: "var(--ink-soft)", borderBottom: "1px dotted var(--ink-soft)" }}>
                      NHC public advisory ↗
                    </a>
                  )}
                </div>
              ))}
              {activeOutlook.map(o => (
                <div key={o.id} className="trop-card trop-out-card">
                  <div className="trop-meta">{fmtBasin(o.basin)} · area {o.area_label}</div>
                  <h4 className="trop-name">Disturbed weather</h4>
                  <div className="trop-row">
                    <span className={`trop-pct trop-pct-${o.category || "low"}`}>
                      2d: {o.formation_2d ?? 0}% · 7d: {o.formation_7d ?? 0}%
                    </span>
                  </div>
                  {o.summary && (
                    <div className="trop-row" style={{ color: "var(--ink-soft)", fontSize: 12,
                                                       maxHeight: 80, overflow: "hidden" }}>
                      {o.summary}
                    </div>
                  )}
                </div>
              ))}
            </div>
            <p className="dash-footer-note" style={{ marginTop: 14, textAlign: "left" }}>
              <strong>Why it's here:</strong> a major hurricane crossing the Gulf or East Coast freight
              corridors triggers fuel-surcharge spikes, port closures, evacuation reroutes, and dock
              backlogs. Atlantic season runs June 1 – November 30. Eastern Pacific: May 15 – November 30.
            </p>
          </section>
        );
      })()}

      {/* SEISMIC · NOW */}
      {inTab("now") && earthquakes.length > 0 && (() => {
        const sorted = [...earthquakes].sort((a, b) => (b.magnitude || 0) - (a.magnitude || 0));
        const ALERT_TONE = {
          red: { bg: "oklch(0.94 0.05 25)", fg: "oklch(0.40 0.18 25)", label: "RED" },
          orange: { bg: "oklch(0.94 0.05 50)", fg: "oklch(0.42 0.16 50)", label: "ORANGE" },
          yellow: { bg: "oklch(0.94 0.04 95)", fg: "oklch(0.42 0.14 95)", label: "YELLOW" },
          green: { bg: "oklch(0.94 0.04 145)", fg: "oklch(0.40 0.12 145)", label: "GREEN" },
        };
        return (
          <section className="dash-section" data-accent="amber" id="dash-earthquakes">
            <div className="dash-section-head">
              <h3 className="dash-section-h">🌋 Seismic activity near freight infrastructure</h3>
              <span className="dash-card-sub">
                {earthquakes.length} M4.5+ event{earthquakes.length === 1 ? "" : "s"} within 150mi
                {" "}of a tracked metro, refinery, or port · USGS · last 7d
              </span>
            </div>
            {/* Map of every freight-tagged quake. Pin diameter scales with
                magnitude (M5.5+ stands out clearly). Color uses USGS PAGER
                alert level when present, falls back to a magnitude-based ramp.
                Auto-fits to the active set so a single Aleutian quake doesn't
                pull the view off the continental map. */}
            {(() => {
              const ALERT_RING = {
                red:    "oklch(0.45 0.22 25)",
                orange: "oklch(0.55 0.18 50)",
                yellow: "oklch(0.55 0.16 75)",
                green:  "oklch(0.50 0.14 145)",
              };
              const quakePoints = earthquakes
                .filter(q => q.lat != null && q.lng != null)
                .map(q => {
                  const m = Number(q.magnitude) || 0;
                  const size = m >= 6.0 ? 22
                             : m >= 5.5 ? 16
                             : m >= 5.0 ? 12
                             : 9;
                  const color = ALERT_RING[q.alert_level]
                              || (m >= 5.5 ? "oklch(0.50 0.20 25)"
                              :   m >= 5.0 ? "oklch(0.55 0.18 50)"
                              :              "oklch(0.55 0.14 75)");
                  const ago = q.occurred_at
                    ? (() => {
                        const ms = Date.now() - new Date(q.occurred_at).getTime();
                        if (ms < 3600000) return `${Math.floor(ms/60000)}m ago`;
                        if (ms < 86400000) return `${Math.floor(ms/3600000)}h ago`;
                        return `${Math.floor(ms/86400000)}d ago`;
                      })()
                    : "";
                  const popupHtml = `
                    <div style="font-family:system-ui;font-size:12px;line-height:1.4;min-width:200px">
                      <div style="font-family:Helvetica;font-weight:700;font-size:18px;color:${color};letter-spacing:-0.01em">M${m.toFixed(1)}</div>
                      <div style="font-size:13px;margin-top:4px">${(q.place || "—").replace(/'/g, "&#39;")}</div>
                      ${q.depth_km != null ? `<div style="font-family:monospace;font-size:11px;color:#666;margin-top:4px">${Number(q.depth_km).toFixed(0)} km depth · ${ago}</div>` : ""}
                      ${q.tsunami ? `<div style="font-family:Helvetica;font-weight:700;color:#dc2626;font-size:11px;margin-top:6px;letter-spacing:0.08em">⚠ TSUNAMI</div>` : ""}
                      ${q.near_metro_id ? `<div style="font-size:11px;color:#666;margin-top:4px">nearest metro: <b>${q.near_metro_id}</b> (${Number(q.near_metro_mi).toFixed(0)}mi)</div>` : ""}
                      ${q.near_refinery_id ? `<div style="font-size:11px;color:#666">refinery: <b>${q.near_refinery_id}</b> (${Number(q.near_refinery_mi).toFixed(0)}mi)</div>` : ""}
                      ${q.near_port_id ? `<div style="font-size:11px;color:#666">port: <b>${q.near_port_id}</b> (${Number(q.near_port_mi).toFixed(0)}mi)</div>` : ""}
                    </div>`;
                  return { lat: q.lat, lng: q.lng, size, color, popupHtml };
                });
              return (
                <div style={{ marginBottom: 16 }}>
                  <MiniMap
                    points={quakePoints}
                    height={320}
                    fitBounds={quakePoints.length >= 2}
                    defaultCenter={[37.5, -119.5]}
                    defaultZoom={5}
                    attribution="USGS · © OpenStreetMap · © CARTO"
                  />
                  <div style={{
                    display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8,
                    fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-soft)",
                    letterSpacing: "0.04em",
                  }}>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.50 0.20 25)", marginRight: 5, verticalAlign: "middle" }} />M5.5+</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.55 0.18 50)", marginRight: 5, verticalAlign: "middle" }} />M5.0–5.5</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.55 0.14 75)", marginRight: 5, verticalAlign: "middle" }} />M4.5–5.0</span>
                    <span style={{ marginLeft: "auto" }}>pin size = magnitude · color = PAGER alert when set</span>
                  </div>
                </div>
              );
            })()}
            <div className="chp-rows">
              {sorted.slice(0, 8).map(q => {
                const tone = ALERT_TONE[q.alert_level] || null;
                const t = q.occurred_at ? new Date(q.occurred_at) : null;
                const ago = t ? Math.max(1, Math.floor((Date.now() - t.getTime()) / 60000)) : null;
                const bigMag = (q.magnitude || 0) >= 5.5;
                return (
                  <div key={q.id} className="chp-row" style={{
                    borderLeft: bigMag ? "3px solid oklch(0.55 0.20 25)" : "3px solid transparent",
                    paddingLeft: bigMag ? 13 : 16,
                  }}>
                    <div className="chp-row-meta">
                      <span style={{
                        fontFamily: "var(--font-serif)", fontSize: 16, marginRight: 8,
                        fontWeight: 600, letterSpacing: "-0.01em", textTransform: "none",
                        color: bigMag ? "oklch(0.45 0.20 25)" : "var(--ink)",
                      }}>
                        M{q.magnitude != null ? Number(q.magnitude).toFixed(1) : "—"}
                      </span>
                      {q.tsunami && (
                        <span className="chp-tag" style={{
                          background: "oklch(0.94 0.05 25)", color: "oklch(0.40 0.18 25)"
                        }}>TSUNAMI</span>
                      )}
                      {tone && (
                        <span className="chp-tag" style={{ background: tone.bg, color: tone.fg }}>
                          PAGER {tone.label}
                        </span>
                      )}
                      {ago != null && ` · ${ago < 60 ? `${ago}m ago` : ago < 1440 ? `${Math.floor(ago/60)}h ago` : `${Math.floor(ago/1440)}d ago`}`}
                    </div>
                    <div className="chp-row-body">
                      {q.place || "—"}
                    </div>
                    <div style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 4,
                                  fontFamily: "var(--font-mono)", letterSpacing: "0.02em" }}>
                      {q.depth_km != null && <>{Number(q.depth_km).toFixed(0)}km depth · </>}
                      {q.near_metro_id && <>nearest metro: <strong>{q.near_metro_id}</strong> ({Number(q.near_metro_mi).toFixed(0)}mi)</>}
                      {q.near_refinery_id && <> · refinery: {q.near_refinery_id} ({Number(q.near_refinery_mi).toFixed(0)}mi)</>}
                      {q.near_port_id && <> · port: {q.near_port_id} ({Number(q.near_port_mi).toFixed(0)}mi)</>}
                      {q.url && (
                        <> · <a href={q.url} target="_blank" rel="noopener noreferrer"
                                 style={{ color: "var(--ink-soft)", borderBottom: "1px dotted var(--ink-soft)" }}>USGS event ↗</a></>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
            <p className="dash-footer-note" style={{ marginTop: 14, textAlign: "left" }}>
              <strong>Why it's here:</strong> M5+ near the Bay Area can close the Bay Bridge.
              M5+ near LA refineries forces unit shutdowns for inspection. M6+ on a port
              fault closes container ops for hours-to-days. We tag at 150mi so the dashboard
              flags every one within shaking-felt range of freight infrastructure we track.
            </p>
          </section>
        );
      })()}

      {/* WILDFIRES · NOW */}
      {inTab("now") && wildfires.length > 0 && (() => {
        const fmtAcres = (a) => {
          if (a == null) return "—";
          const n = Number(a);
          if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}K`;
          return Math.round(n).toLocaleString();
        };
        const totalAcres = wildfires.reduce((s, f) => s + (Number(f.acres) || 0), 0);
        return (
          <section className="dash-section" data-accent="red" id="dash-wildfires">
            <div className="dash-section-head">
              <h3 className="dash-section-h">🔥 Wildfire watch</h3>
              <span className="dash-card-sub">
                {wildfires.length} active fire{wildfires.length === 1 ? "" : "s"} ≥500 acres or near
                a tracked metro · {fmtAcres(totalAcres)} acres total burning · NIFC
              </span>
            </div>
            {/* Map of every active fire we ingest. Pins sized by acreage tier
                so the eye finds the megafires immediately; color shifts toward
                deep red as containment drops. Click a pin for the popup. */}
            {(() => {
              const firePoints = wildfires
                .filter(f => f.lat != null && f.lng != null)
                .map(f => {
                  const ac = Number(f.acres) || 0;
                  const pct = f.pct_contained != null ? Number(f.pct_contained) : null;
                  const size = ac >= 50000 ? 18 : ac >= 10000 ? 14 : ac >= 1000 ? 10 : 7;
                  const color = pct != null && pct >= 75 ? "oklch(0.55 0.14 145)"
                              : pct != null && pct >= 30 ? "oklch(0.60 0.16 75)"
                              : "oklch(0.55 0.20 25)";
                  const popupHtml = `
                    <div style="font-family:system-ui;font-size:12px;line-height:1.4;min-width:180px">
                      <div style="font-family:Helvetica;font-weight:600;text-transform:uppercase;
                                  letter-spacing:0.05em;font-size:10px;color:#666;margin-bottom:4px">
                        🔥 ${(f.state || "").replace(/^US-/, "")}
                        ${pct != null ? ` · ${pct}% contained` : ""}
                      </div>
                      <div style="font-weight:600;font-size:14px;margin-bottom:4px">${f.name || "Unnamed fire"}</div>
                      <div style="font-size:12px"><b>${fmtAcres(f.acres)}</b> acres
                        ${f.county ? ` · ${f.county} County` : ""}
                      </div>
                      ${f.near_metro_id ? `<div style="font-size:11px;color:#666;margin-top:4px">
                        nearest metro: <b>${f.near_metro_id}</b>${f.near_metro_mi != null ? ` (${Number(f.near_metro_mi).toFixed(0)}mi)` : ""}
                      </div>` : ""}
                    </div>`;
                  return { lat: f.lat, lng: f.lng, size, color, popupHtml };
                });
              return (
                <div style={{ marginBottom: 16 }}>
                  <MiniMap
                    points={firePoints}
                    height={360}
                    fitBounds={true}
                    defaultCenter={[39.5, -98.5]}
                    defaultZoom={4}
                    attribution="NIFC · © OpenStreetMap · © CARTO"
                  />
                  <div style={{
                    display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8,
                    fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-soft)",
                    letterSpacing: "0.04em",
                  }}>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.55 0.20 25)", marginRight: 5, verticalAlign: "middle" }} />&lt;30% contained</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.60 0.16 75)", marginRight: 5, verticalAlign: "middle" }} />30–75%</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "oklch(0.55 0.14 145)", marginRight: 5, verticalAlign: "middle" }} />≥75%</span>
                    <span style={{ marginLeft: "auto" }}>pin size = acreage tier (≥50K · ≥10K · ≥1K · &lt;1K)</span>
                  </div>
                </div>
              );
            })()}
            <div className="chp-rows">
              {wildfires.slice(0, 8).map(fr => {
                const acres = Number(fr.acres) || 0;
                const sev = acres >= 10000 ? "crit" : acres >= 1000 ? "high" : "mod";
                const pct = fr.pct_contained != null ? Number(fr.pct_contained) : null;
                const t = fr.discovered_at ? new Date(fr.discovered_at) : null;
                const days = t ? Math.floor((Date.now() - t.getTime()) / 86400000) : null;
                return (
                  <div key={fr.id} className="chp-row" style={{
                    borderLeft: sev === "crit" ? "3px solid oklch(0.55 0.20 25)"
                              : sev === "high" ? "3px solid oklch(0.55 0.18 50)"
                              : "3px solid transparent",
                    paddingLeft: sev === "mod" ? 16 : 13,
                  }}>
                    <div className="chp-row-meta">
                      <span style={{
                        fontFamily: "var(--font-serif)", fontSize: 16, marginRight: 8,
                        fontWeight: 600, letterSpacing: "-0.01em", textTransform: "none",
                        color: sev === "crit" ? "oklch(0.45 0.20 25)"
                             : sev === "high" ? "oklch(0.45 0.16 50)" : "var(--ink)",
                      }}>
                        {fmtAcres(fr.acres)} ac
                      </span>
                      {pct != null && (
                        <span className="chp-tag" style={{
                          background: pct >= 75 ? "oklch(0.94 0.04 145)"
                                    : pct >= 30 ? "oklch(0.94 0.04 95)"
                                    : "oklch(0.94 0.05 25)",
                          color: pct >= 75 ? "oklch(0.40 0.12 145)"
                               : pct >= 30 ? "oklch(0.42 0.14 95)"
                               : "oklch(0.40 0.18 25)",
                        }}>{pct}% CONT</span>
                      )}
                      {fr.state && ` · ${fr.state.replace(/^US-/, "")}`}
                      {days != null && days >= 0 && ` · ${days === 0 ? "today" : days === 1 ? "1 day in" : `day ${days+1}`}`}
                    </div>
                    <div className="chp-row-body">
                      <strong>{fr.name || "Unnamed fire"}</strong>
                      {fr.county && <> · {fr.county} County</>}
                      {fr.cause && fr.cause !== "Undetermined" && <> · {fr.cause}</>}
                    </div>
                    {fr.near_metro_id && (
                      <div style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 4,
                                    fontFamily: "var(--font-mono)", letterSpacing: "0.02em" }}>
                        nearest metro: <strong>{fr.near_metro_id}</strong> ({Number(fr.near_metro_mi).toFixed(0)}mi)
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
            <p className="dash-footer-note" style={{ marginTop: 14, textAlign: "left" }}>
              <strong>Why it's here:</strong> a 5K-acre fire near I-5 in Northern California shut
              the corridor for 36 hours in Aug 2024. Smoke alone drops vis at hub airports —
              KOAK and KSMF have logged sub-2-mi visibility from valley smoke. Fires &lt;30%
              contained near metros are the ones to watch.
            </p>
          </section>
        );
      })()}

      {/* WEATHER ALERTS · NOW */}
      {inTab("now") && weather.length > 0 && (
        <section className="dash-section" data-accent="amber">
          <div className="dash-section-head">
            <h3 className="dash-section-h">🌩 Active weather</h3>
            <span className="dash-card-sub">{weather.length} alert{weather.length === 1 ? "" : "s"} affecting freight metros</span>
          </div>
          <ul className="dash-event-list">
            {weather.slice(0, 6).map((w, i) => (
              <li key={i} className="dash-event">
                <span className="admin-tag" data-sev={w.severity === "extreme" ? "critical" : w.severity === "severe" ? "high" : "moderate"}>
                  {(w.severity || "—").toUpperCase()}
                </span>
                <div className="dash-event-body">
                  <div className="dash-event-meta">
                    <strong>{w.event || "Alert"}</strong>
                    {" · "}{prettyCity(w.city_id)}
                  </div>
                  <div className="dash-event-text">{w.headline || w.description || "—"}</div>
                </div>
              </li>
            ))}
          </ul>
        </section>
      )}

      {/* ROAD CONDITIONS + CONSTRUCTION · NOW */}
      {inTab("now") && <section className="dash-section" data-accent="orange" id="dash-roads">
        <div className="dash-section-head">
          <h3 className="dash-section-h">🛣 Road conditions & construction</h3>
          <span className="dash-card-sub">
            {roads.length} active across freight metros · weather + 511 + driver-reported
          </span>
        </div>
        {roads.length === 0 ? (
          <div className="dash-event-list" style={{ padding: 20 }}>
            <div className="dash-empty">
              No road incidents in the database yet. We're cross-referencing weather alerts
              and freight-corridor lane disruptions from driver chatter as a fallback. State
              511 ingest (Caltrans, 511NY, Illinois 511, Georgia 511, COtrip) is queued for
              the next pipeline iteration.
            </div>
            {/* Fallback: surface lane-disruption intel and weather as "watch list" */}
            {(() => {
              const laneIntel = allIntel
                .filter(r => r.parsed_intel_type === "lane_disruption" && r.status === "verified")
                .slice(0, 6);
              const wxRoad = (weather || []).filter(w =>
                /winter|wind|storm|flood|tornado|ice|snow|fog/i.test(`${w.event || ""} ${w.headline || ""}`)
              ).slice(0, 4);
              if (laneIntel.length === 0 && wxRoad.length === 0) return null;
              return (
                <div style={{ marginTop: 16 }}>
                  <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8, color: "var(--ink-soft)", textTransform: "uppercase", letterSpacing: "0.05em" }}>
                    Watch list — weather impacts + lane chatter
                  </div>
                  <ul className="dash-event-list" style={{ marginTop: 0 }}>
                    {wxRoad.map((w, i) => (
                      <li key={`wx${i}`} className="dash-event">
                        <span className="admin-tag" data-sev={w.severity === "extreme" ? "critical" : w.severity === "severe" ? "high" : "moderate"}>
                          {(w.severity || "—").toUpperCase()}
                        </span>
                        <div className="dash-event-body">
                          <div className="dash-event-meta">
                            <strong>{w.event || "Weather"}</strong> · {prettyCity(w.city_id)}
                          </div>
                          <div className="dash-event-text">{w.headline || w.description || "—"}</div>
                        </div>
                      </li>
                    ))}
                    {laneIntel.map((it, i) => (
                      <li key={`ln${i}`} className="dash-event">
                        <span className="admin-tag" data-sev={it.severity || "low"}>{(it.severity || "—").toUpperCase()}</span>
                        <div className="dash-event-body">
                          <div className="dash-event-meta">
                            <strong>Lane disruption</strong>
                            {it.parsed_carrier && ` · ${carrierName(it.parsed_carrier)}`}
                            {it.parsed_city && ` · ${prettyCity(it.parsed_city)}`}
                            {" · "}{timeAgo(it.created_at)}
                          </div>
                          <div className="dash-event-text">{it.raw_text}</div>
                        </div>
                      </li>
                    ))}
                  </ul>
                </div>
              );
            })()}
          </div>
        ) : (
          <ul className="dash-event-list">
            {roads.map((r, i) => (
              <li key={i} className="dash-event">
                <span className="admin-tag" data-sev={r.severity === "major" ? "high" : r.severity === "moderate" ? "moderate" : "low"}>
                  {(r.severity || "—").toUpperCase()}
                </span>
                <div className="dash-event-body">
                  <div className="dash-event-meta">
                    <strong>{r.road_name || r.category || "Incident"}</strong>
                    {r.direction && ` · ${r.direction}`}
                    {r.cross_street && ` near ${r.cross_street}`}
                    {r.city_id && ` · ${prettyCity(r.city_id)}`}
                  </div>
                  <div className="dash-event-text">{r.description || "—"}</div>
                </div>
              </li>
            ))}
          </ul>
        )}
      </section>}

      {/* CHP REAL-TIME · NOW */}
      {inTab("now") && chpIncidents.length > 0 && (() => {
        const METRO_LABEL = {
          "los-angeles": "Los Angeles", "san-francisco": "Bay Area",
          "san-jose": "San Jose", "sacramento": "Sacramento",
          "san-diego": "San Diego", "stockton": "Stockton",
          "fresno": "Fresno", "bakersfield": "Bakersfield",
        };
        const byMetro = {};
        const byFreeway = {};
        const byCat = { collision: 0, hazard: 0, closure: 0, other: 0 };
        chpIncidents.forEach(c => {
          if (c.metro_city_id) byMetro[c.metro_city_id] = (byMetro[c.metro_city_id] || 0) + 1;
          if (c.freeway) byFreeway[c.freeway] = (byFreeway[c.freeway] || 0) + 1;
          byCat[c.category || "other"] = (byCat[c.category || "other"] || 0) + 1;
        });
        const metros = Object.entries(byMetro).sort((a,b) => b[1] - a[1]).slice(0, 8);
        const freeways = Object.entries(byFreeway).sort((a,b) => b[1] - a[1]).slice(0, 8);
        const recent = chpIncidents
          .filter(c => c.category === "collision" || c.category === "hazard")
          .sort((a,b) => new Date(b.log_time || 0) - new Date(a.log_time || 0))
          .slice(0, 8);
        return (
          <section className="dash-section" data-accent="orange">
            <style>{`
              .chp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
              @media (max-width: 760px) { .chp-grid { grid-template-columns: 1fr; } }
              .chp-pillrow { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
              .chp-pill { background: #fff; border: 1px solid var(--rule); border-radius: 5px;
                padding: 8px 12px; font-family: var(--font-mono); font-size: 11px;
                letter-spacing: 0.05em; }
              .chp-pill .v { font-family: var(--font-serif); font-size: 16px; margin-left: 8px;
                letter-spacing: -0.01em; }
              .chp-rows { background: #fff; border: 1px solid var(--rule); border-radius: 6px;
                overflow: hidden; }
              .chp-row { padding: 12px 16px; border-bottom: 1px solid var(--rule); }
              .chp-row:last-child { border-bottom: none; }
              .chp-row-meta { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em;
                color: var(--ink-soft); text-transform: uppercase; }
              .chp-row-body { font-size: 13px; margin-top: 4px; }
              .chp-tag { display: inline-block; padding: 1px 6px; border-radius: 3px;
                font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.05em;
                margin-right: 8px; }
              .chp-tag.collision { background: oklch(0.94 0.05 25); color: oklch(0.40 0.18 25); }
              .chp-tag.hazard    { background: oklch(0.94 0.05 50); color: oklch(0.42 0.16 50); }
              .chp-tag.closure   { background: oklch(0.94 0.05 25); color: oklch(0.40 0.18 25); }
              .chp-tag.other     { background: oklch(0.95 0 0); color: var(--ink-soft); }
            `}</style>
            <div className="dash-section-head" id="dash-chp-california">
              <h3 className="dash-section-h">🚨 California live · CHP</h3>
              <span className="dash-card-sub">
                {chpIncidents.length} active · {byCat.collision} collisions ·
                {" "}{byCat.hazard} hazards{byCat.closure ? ` · ${byCat.closure} closures` : ""}
                {" · "}refreshed every cron tick
              </span>
            </div>

            {/* Where the trouble is, in one glance. Pins colored by category
                (collision / hazard / closure). Auto-fits to the active set so
                the map zooms tight on a quiet day and pulls back when CHP is
                lit up. Tooltip carries freeway + log code + minutes-ago. */}
            {(() => {
              const CAT_COLOR = {
                collision: "oklch(0.55 0.20 25)",
                closure:   "oklch(0.42 0.22 25)",
                hazard:    "oklch(0.60 0.18 50)",
                other:     "oklch(0.65 0.05 60)",
              };
              const chpPoints = chpIncidents
                .filter(c => c.lat != null && c.lng != null)
                .map(c => {
                  const cat = c.category || "other";
                  const color = CAT_COLOR[cat] || CAT_COLOR.other;
                  const size = cat === "closure" ? 12 : cat === "collision" ? 10 : 8;
                  const t = c.log_time ? new Date(c.log_time) : null;
                  const ago = t ? Math.max(1, Math.floor((Date.now() - t.getTime()) / 60000)) : null;
                  const agoStr = ago != null ? (ago < 60 ? `${ago}m ago` : `${Math.floor(ago/60)}h ago`) : "";
                  const incType = c.incident_type ? c.incident_type.replace(/^[0-9]+-/, "") : "Incident";
                  const popupHtml = `
                    <div style="font-family:system-ui;font-size:12px;line-height:1.4;min-width:180px">
                      <div style="font-family:Helvetica;font-weight:700;text-transform:uppercase;
                                  letter-spacing:0.05em;font-size:10px;color:${color};margin-bottom:4px">
                        ${cat.toUpperCase()} ${c.freeway ? "· " + c.freeway : ""}
                      </div>
                      <div style="font-weight:600;font-size:13px;margin-bottom:4px">${incType}</div>
                      ${c.location_desc ? `<div style="font-size:12px">${c.location_desc}</div>` : ""}
                      ${c.area ? `<div style="font-size:11px;color:#666;margin-top:4px">${c.area}</div>` : ""}
                      ${agoStr ? `<div style="font-size:11px;color:#666;margin-top:4px;font-family:Helvetica">${agoStr}</div>` : ""}
                    </div>`;
                  return { lat: c.lat, lng: c.lng, size, color, popupHtml };
                });
              return (
                <div style={{ marginBottom: 16 }}>
                  <MiniMap
                    points={chpPoints}
                    height={360}
                    fitBounds={true}
                    defaultCenter={[37.0, -119.5]}
                    defaultZoom={6}
                    attribution="CHP · © OpenStreetMap · © CARTO"
                  />
                  <div style={{
                    display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8,
                    fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-soft)",
                    letterSpacing: "0.04em",
                  }}>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: CAT_COLOR.collision, marginRight: 5, verticalAlign: "middle" }} />Collision</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: CAT_COLOR.hazard, marginRight: 5, verticalAlign: "middle" }} />Hazard</span>
                    <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: CAT_COLOR.closure, marginRight: 5, verticalAlign: "middle" }} />Closure</span>
                    <span style={{ marginLeft: "auto" }}>{chpPoints.length.toLocaleString()} pins · last 24h</span>
                  </div>
                </div>
              );
            })()}

            <div className="chp-pillrow">
              {metros.map(([m, n]) => (
                <span key={m} className="chp-pill">{METRO_LABEL[m] || m} <span className="v">{n}</span></span>
              ))}
            </div>
            <div className="chp-pillrow">
              {freeways.map(([f, n]) => (
                <span key={f} className="chp-pill">{f} <span className="v">{n}</span></span>
              ))}
            </div>

            {recent.length > 0 && (
              <div className="chp-rows">
                {recent.map(c => {
                  const t = c.log_time ? new Date(c.log_time) : null;
                  const ago = t ? Math.max(1, Math.floor((Date.now() - t.getTime()) / 60000)) : null;
                  return (
                    <div key={c.id} className="chp-row">
                      <div className="chp-row-meta">
                        <span className={`chp-tag ${c.category || "other"}`}>{(c.category || "other").toUpperCase()}</span>
                        {c.freeway && <strong>{c.freeway}</strong>}
                        {c.area && ` · ${c.area}`}
                        {ago != null && ` · ${ago < 60 ? `${ago}m ago` : `${Math.floor(ago/60)}h ago`}`}
                        {c.metro_city_id && ` · ${METRO_LABEL[c.metro_city_id] || c.metro_city_id}`}
                      </div>
                      <div className="chp-row-body">
                        {c.incident_type ? c.incident_type.replace(/^[0-9]+-/, "") : "Incident"}
                        {c.location_desc && ` · ${c.location_desc}`}
                      </div>
                    </div>
                  );
                })}
              </div>
            )}
            <p className="dash-footer-note" style={{ marginTop: 14, textAlign: "left" }}>
              <strong>Source:</strong> California Highway Patrol live incident feed. Public, real-time,
              statewide. Codes: 1179/1180/1181/1183 = collision flavors; 1125 = hazard / blocking;
              1144 = fire. NY · IL · GA · CO 511 ingest is the next add — same shape, different state.
            </p>
          </section>
        );
      })()}

      {/* RECENT FEED · NOW + CARRIERS (filtered for either) */}
      {(inTab("now") || inTab("carriers")) && <section className="dash-section" data-accent="blue" id="dash-feed">
        <div className="dash-section-head">
          <h3 className="dash-section-h">Recent operational signal</h3>
          <span className="dash-card-sub">
            {filter.carrier || filter.city || filter.severity || filter.type
              ? `Filtered: ${[filter.carrier, filter.city, filter.severity, filter.type].filter(Boolean).join(" · ")}`
              : "Last 50 verified items · all metros"}
          </span>
        </div>
        <ul className="dash-event-list">
          {(() => {
            // Sort:
            //   1. Critical/High severity within last 4 hours → top, severity desc, then newest
            //   2. Everything else → newest first
            const SEV_RANK = { critical: 4, high: 3, moderate: 2, low: 1 };
            const FOUR_H = 4 * 3600 * 1000;
            const nowMs = Date.now();
            const ts = (r) => new Date(r.posted_at || r.created_at).getTime();
            const isHotRecent = (r) => (SEV_RANK[r.severity] || 0) >= 3 && (nowMs - ts(r)) <= FOUR_H;
            return allIntel.filter(r => r.status === "verified")
              .filter(r => !filter.carrier || r.parsed_carrier === filter.carrier)
              .filter(r => !filter.city    || r.parsed_city    === filter.city)
              .filter(r => !filter.severity|| r.severity       === filter.severity)
              .filter(r => !filter.type    || r.parsed_intel_type === filter.type)
              .sort((a, b) => {
                const ah = isHotRecent(a), bh = isHotRecent(b);
                if (ah !== bh) return ah ? -1 : 1;
                if (ah && bh) {
                  // Both hot: severity desc, then newest first
                  const sd = (SEV_RANK[b.severity] || 0) - (SEV_RANK[a.severity] || 0);
                  if (sd !== 0) return sd;
                }
                return ts(b) - ts(a);
              })
              .slice(0, 50);
          })().map((it) => (
            <li key={it.id} className="dash-event">
              <span className="admin-tag" data-sev={it.severity || "low"}>{(it.severity || "—").toUpperCase()}</span>
              <div className="dash-event-body">
                <div className="dash-event-meta">
                  {it.parsed_carrier ? (
                    <a href={`#/carrier/${it.parsed_carrier}`} className="carrier-name-link"><strong>{carrierName(it.parsed_carrier)}</strong></a>
                  ) : (
                    <strong>{carrierName(it.parsed_carrier)}</strong>
                  )}
                  {" · "}{prettyCity(it.parsed_city)}
                  {" · "}{it.parsed_intel_type || "—"}
                  {" · "}{timeAgo(it.created_at)}
                  {(() => {
                    const m = (it.notes || "").match(/cluster:(\d+)/);
                    if (!m) return null;
                    return <span className="dash-event-cluster" title="This story was reported by multiple independent sources — pattern signal">📰 {m[1]} sources</span>;
                  })()}
                </div>
                <div className="dash-event-text">{it.raw_text}</div>
                {it.notes && (() => {
                  // Strip auto-accept + cluster machine tags before showing notes
                  const clean = (it.notes || "")
                    .replace(/^auto-accept[^·]*·\s*/, "")
                    .replace(/(?:^|·\s*)cluster:\d+\|[^·]*/g, "")
                    .replace(/^·\s*/, "").trim();
                  return clean ? <div className="dash-event-notes">{clean}</div> : null;
                })()}
              </div>
            </li>
          ))}
          {allIntel.length === 0 && <li className="dash-empty">No verified intel yet — check back after the editor processes today's batch.</li>}
        </ul>
        <p className="dash-footer-note">
          Drivers stay anonymous. Only aggregated counts and verified text surface here. Sealed tips never republished verbatim. <a href="#/methodology" onClick={(e) => { e.preventDefault(); onNav("methodology"); }}>Read the trust contract →</a>
        </p>
      </section>}

      {/* WATCHLIST · WATCHLIST */}
      {inTab("watchlist") && (
        <section className="dash-section" data-accent="blue">
          <div className="dash-section-head">
            <h3 className="dash-section-h">Your watchlist</h3>
            <span className="dash-card-sub">
              Pulse subscriptions, saved carriers, custom alerts. Free for everyone signed in.
            </span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 14 }}>
            <div style={{ background: "#fff", border: "1px solid var(--rule)", borderRadius: 8, padding: "18px 20px" }}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em",
                            textTransform: "uppercase", color: "var(--ink-soft)" }}>Pulse subscriptions</div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 28, letterSpacing: "-0.02em",
                            margin: "6px 0 4px" }}>{pulseSubs.length}</div>
              <div style={{ fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.5 }}>
                {pulseSubs.length > 0
                  ? `Daily digest delivering to ${user.email} for ${pulseSubs.map(s => prettyCity(s.city_id)).join(", ")}.`
                  : "Subscribe to a metro from any /#/report page to start receiving the daily Pulse digest."}
              </div>
            </div>
            <div style={{ background: "#fff", border: "1px solid var(--rule)", borderRadius: 8, padding: "18px 20px" }}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em",
                            textTransform: "uppercase", color: "var(--ink-soft)" }}>Saved carriers</div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 28, letterSpacing: "-0.02em",
                            margin: "6px 0 4px", color: "var(--ink-soft)" }}>—</div>
              <div style={{ fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.5 }}>
                Bookmark carriers from any /#/carrier page. Coming soon: per-carrier alert thresholds
                (OOS rate spikes, financial filings, fleet changes).
              </div>
            </div>
            <div style={{ background: "#fff", border: "1px solid var(--rule)", borderRadius: 8, padding: "18px 20px" }}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em",
                            textTransform: "uppercase", color: "var(--ink-soft)" }}>Saved lanes</div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 28, letterSpacing: "-0.02em",
                            margin: "6px 0 4px", color: "var(--ink-soft)" }}>—</div>
              <div style={{ fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.5 }}>
                Pin origin/destination metro pairs you ship. Get notified when CHP, weather, fires,
                or carrier-network signal flares up on those lanes.
              </div>
            </div>
            <div style={{ background: "#fff", border: "1px solid var(--rule)", borderRadius: 8, padding: "18px 20px" }}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em",
                            textTransform: "uppercase", color: "var(--ink-soft)" }}>Custom alerts</div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 28, letterSpacing: "-0.02em",
                            margin: "6px 0 4px", color: "var(--ink-soft)" }}>—</div>
              <div style={{ fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.5 }}>
                "Tell me when ULSD futures move &gt;5% in a week" or "ping me when any tracked
                carrier files a Form 8-K Item 4.02." Coming next.
              </div>
            </div>
          </div>
          <p className="dash-footer-note" style={{ marginTop: 14, textAlign: "left" }}>
            Watchlist is part of the free signed-in tier — for drivers, observers, and any user
            who just wants the digest narrowed to what they care about. Always free.
          </p>
        </section>
      )}
    </div>
  );
}

// Color-by-type for the chart bars
function typeColor(t) {
  if (t.includes("safety")) return "oklch(0.55 0.18 25)"; // red
  if (t.includes("delay") || t.includes("disruption")) return "oklch(0.65 0.16 50)"; // amber
  if (t.includes("service")) return "oklch(0.55 0.16 250)"; // blue
  if (t.includes("pricing")) return "oklch(0.55 0.16 300)"; // violet
  if (t.includes("capacity")) return "oklch(0.50 0.14 145)"; // green
  return "oklch(0.45 0 0)"; // neutral
}

// =============================================================
// Tableau-style intel report — KPI deltas, time-series area chart,
// carrier × intel-type heatmap. Pure SVG, no chart library.
// =============================================================

function severityColor(sev) {
  if (sev === "critical") return "oklch(0.45 0.20 25)";
  if (sev === "high")     return "oklch(0.58 0.20 30)";
  if (sev === "moderate") return "oklch(0.70 0.16 75)";
  if (sev === "low")      return "oklch(0.55 0.10 145)";
  return "oklch(0.65 0 0)";
}

function KPITile({ label, value, prevValue, sparklineDays, accent = "blue" }) {
  const delta = prevValue > 0 ? ((value - prevValue) / prevValue) * 100 : null;
  const direction = delta == null ? "" : delta > 0 ? "▲" : delta < 0 ? "▼" : "—";
  const deltaColor = delta == null ? "var(--ink-soft)"
    : delta > 0 ? "oklch(0.55 0.18 25)" : delta < 0 ? "oklch(0.50 0.14 145)" : "var(--ink-soft)";
  const max = Math.max(1, ...sparklineDays);
  const w = 80, h = 24, n = sparklineDays.length || 1;
  const points = sparklineDays.map((v, i) => {
    const x = (i / Math.max(1, n - 1)) * w;
    const y = h - (v / max) * h;
    return `${x.toFixed(1)},${y.toFixed(1)}`;
  }).join(" ");
  return (
    <div className="kpi-tile" data-accent={accent}>
      <div className="kpi-label">{label}</div>
      <div className="kpi-row">
        <div className="kpi-num" data-accent={accent}>{value}</div>
        {delta != null && (
          <div className="kpi-delta" style={{ color: deltaColor }}>
            {direction} {Math.abs(delta).toFixed(0)}%
          </div>
        )}
      </div>
      <svg className="kpi-spark" data-accent={accent} width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
        <polyline fill="none" strokeWidth="1.6" points={points} />
      </svg>
    </div>
  );
}

function StackedAreaChart({ rows, days = 30 }) {
  // rows: array of intel { created_at, severity, posted_at? }
  // bucket per day, stack by severity (critical/high on top)
  // Prefer posted_at (original source post date) over created_at (when we ingested),
  // so historical operator-network data spreads across the timeline naturally.
  const buckets = [];
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  for (let i = days - 1; i >= 0; i--) {
    const d = new Date(today.getTime() - i * 86400000);
    buckets.push({ date: d, critical: 0, high: 0, moderate: 0, low: 0 });
  }
  rows.forEach(r => {
    const t = new Date(r.posted_at || r.created_at);
    t.setUTCHours(0, 0, 0, 0);
    const idx = buckets.findIndex(b => b.date.getTime() === t.getTime());
    if (idx === -1) return;
    const sev = r.severity || "low";
    if (buckets[idx][sev] != null) buckets[idx][sev] += 1;
  });

  const W = 720, H = 220, pad = { l: 32, r: 12, t: 12, b: 26 };
  const innerW = W - pad.l - pad.r, innerH = H - pad.t - pad.b;
  const max = Math.max(1, ...buckets.map(b => b.critical + b.high + b.moderate + b.low));
  const x = (i) => pad.l + (i / Math.max(1, days - 1)) * innerW;
  const y = (v) => pad.t + innerH - (v / max) * innerH;

  function buildArea(seriesKey, baselineKey) {
    const top = buckets.map((b, i) => `${x(i).toFixed(1)},${y(b._stack[seriesKey]).toFixed(1)}`);
    const bot = buckets.map((b, i) => `${x(i).toFixed(1)},${y(b._stack[baselineKey]).toFixed(1)}`).reverse();
    return `M ${top.join(" L ")} L ${bot.join(" L ")} Z`;
  }
  // Compute cumulative stacks from bottom (low → moderate → high → critical)
  buckets.forEach(b => {
    b._stack = {
      base: 0,
      low: b.low,
      moderate: b.low + b.moderate,
      high: b.low + b.moderate + b.high,
      critical: b.low + b.moderate + b.high + b.critical,
    };
  });

  // Y-axis ticks
  const ticks = [0, Math.round(max / 2), max];

  return (
    <div className="report-chart">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{ width: "100%", height: 240 }}>
        {/* gridlines */}
        {ticks.map((t, i) => (
          <g key={i}>
            <line x1={pad.l} x2={W - pad.r} y1={y(t)} y2={y(t)} stroke="var(--rule-soft)" strokeWidth="0.5" />
            <text x={pad.l - 6} y={y(t) + 3} textAnchor="end" fontSize="10" fill="var(--ink-soft)">{t}</text>
          </g>
        ))}
        {/* areas, bottom to top so taller stacks paint over shorter ones */}
        <path d={buildArea("low", "base")}      fill={severityColor("low")}      opacity="0.85" />
        <path d={buildArea("moderate", "low")}  fill={severityColor("moderate")} opacity="0.85" />
        <path d={buildArea("high", "moderate")} fill={severityColor("high")}     opacity="0.85" />
        <path d={buildArea("critical", "high")} fill={severityColor("critical")} opacity="0.95" />
        {/* x-axis date labels — first, middle, last only to avoid clutter */}
        {[0, Math.floor(days / 2), days - 1].map(i => (
          <text key={i} x={x(i)} y={H - 8} textAnchor="middle" fontSize="10" fill="var(--ink-soft)">
            {buckets[i].date.toLocaleDateString("en-US", { month: "short", day: "numeric" })}
          </text>
        ))}
      </svg>
    </div>
  );
}

function CarrierHeatmap({ rows, carriers, onCell }) {
  // Matrix: carriers (rows) × intel_types (cols), cell = count
  const carrierSet = new Set();
  const typeSet = new Set();
  const counts = {};
  rows.forEach(r => {
    if (!r.parsed_carrier || !r.parsed_intel_type) return;
    carrierSet.add(r.parsed_carrier);
    typeSet.add(r.parsed_intel_type);
    const k = `${r.parsed_carrier}|${r.parsed_intel_type}`;
    counts[k] = (counts[k] || 0) + 1;
  });
  const carrierList = [...carrierSet].sort((a, b) => {
    const ta = Object.keys(counts).filter(k => k.startsWith(a + "|")).reduce((s, k) => s + counts[k], 0);
    const tb = Object.keys(counts).filter(k => k.startsWith(b + "|")).reduce((s, k) => s + counts[k], 0);
    return tb - ta;
  }).slice(0, 10);
  const typeList = [...typeSet].sort();
  const max = Math.max(1, ...Object.values(counts));

  function cellColor(v) {
    if (!v) return "transparent";
    const intensity = Math.min(1, v / max);
    return `oklch(${0.95 - intensity * 0.45} ${0.05 + intensity * 0.16} 25)`;
  }
  function carrierLabel(slug) {
    const c = (carriers || []).find(x => x.slug === slug);
    return c ? c.name : slug.toUpperCase();
  }

  if (!carrierList.length || !typeList.length) {
    return <div className="dash-empty">Not enough classified data yet to build a heatmap.</div>;
  }

  return (
    <div className="report-heatmap-wrap">
      <table className="report-heatmap">
        <thead>
          <tr>
            <th></th>
            {typeList.map(t => <th key={t}>{t.replace(/_/g, " ")}</th>)}
          </tr>
        </thead>
        <tbody>
          {carrierList.map(c => (
            <tr key={c}>
              <th>{carrierLabel(c)}</th>
              {typeList.map(t => {
                const v = counts[`${c}|${t}`] || 0;
                return (
                  <td key={t}
                      style={{ background: cellColor(v), color: v > max * 0.6 ? "white" : "var(--ink)", cursor: v > 0 ? "pointer" : "default" }}
                      title={`${carrierLabel(c)} · ${t}: ${v}${v > 0 ? "  · click to filter" : ""}`}
                      onClick={() => v > 0 && onCell && onCell(c, t)}>
                    {v || ""}
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// =============================================================
// PATTERN RADAR — recurring (carrier × intel-type) and (city × intel-type)
// pairs from the last 30 days. Surfaces "this is not a one-off" signal so
// shippers see chronic problems instead of just feed noise.
// Clicking a pattern expands the underlying intel rows inline.
// =============================================================
function PatternRadar({ rows, carriers, cities, onPattern }) {
  const [expanded, setExpanded] = React.useState(null);
  const [search, setSearch] = React.useState("");
  const day = 86400000;
  const now = Date.now();
  const recent30 = rows.filter(r => now - new Date(r.posted_at || r.created_at).getTime() < 30 * day);

  // Severity weights for ranking — high+critical lift the pattern
  const sevWeight = { critical: 4, high: 3, moderate: 1.5, low: 1 };

  // Aggregate by (carrier, intel_type)
  const carrPatterns = {};
  recent30.forEach(r => {
    if (!r.parsed_carrier || !r.parsed_intel_type) return;
    const k = `${r.parsed_carrier}|${r.parsed_intel_type}`;
    if (!carrPatterns[k]) carrPatterns[k] = {
      key: k, kind: "carrier", carrier: r.parsed_carrier, type: r.parsed_intel_type,
      count: 0, score: 0, sev: { critical:0, high:0, moderate:0, low:0 },
      cities: new Set(), latest: 0,
    };
    const p = carrPatterns[k];
    p.count += 1;
    p.score += sevWeight[r.severity] || 1;
    if (p.sev[r.severity] != null) p.sev[r.severity] += 1;
    if (r.parsed_city) p.cities.add(r.parsed_city);
    const t = new Date(r.posted_at || r.created_at).getTime();
    if (t > p.latest) p.latest = t;
  });

  // Aggregate by (city, intel_type) — facility-level signal
  const cityPatterns = {};
  recent30.forEach(r => {
    if (!r.parsed_city || !r.parsed_intel_type) return;
    const k = `${r.parsed_city}|${r.parsed_intel_type}`;
    if (!cityPatterns[k]) cityPatterns[k] = {
      key: k, kind: "city", city: r.parsed_city, type: r.parsed_intel_type,
      count: 0, score: 0, sev: { critical:0, high:0, moderate:0, low:0 },
      carriers: new Set(), latest: 0,
    };
    const p = cityPatterns[k];
    p.count += 1;
    p.score += sevWeight[r.severity] || 1;
    if (p.sev[r.severity] != null) p.sev[r.severity] += 1;
    if (r.parsed_carrier) p.carriers.add(r.parsed_carrier);
    const t = new Date(r.posted_at || r.created_at).getTime();
    if (t > p.latest) p.latest = t;
  });

  // Threshold: 3+ events to qualify as a pattern. Drops one-offs.
  const baseline = [...Object.values(carrPatterns), ...Object.values(cityPatterns)]
    .filter(p => p.count >= 3)
    .sort((a, b) => b.score - a.score);

  // Apply search across subject (carrier or city), intel type, and raw text
  // of any of the underlying rows.
  const q = search.trim().toLowerCase();
  function carrierLookup(slug) {
    const c = (carriers || []).find(x => x.slug === slug);
    return c ? c.name : (slug || "").replace(/-/g, " ");
  }
  function cityLookup(id) {
    const c = (cities || []).find(x => x.id === id);
    return c ? `${c.name} ${c.state}` : (id || "").replace(/-/g, " ");
  }
  function patternMatchingRows(p) {
    return rows.filter(r => {
      if (now - new Date(r.posted_at || r.created_at).getTime() >= 30 * day) return false;
      if (p.kind === "carrier") return r.parsed_carrier === p.carrier && r.parsed_intel_type === p.type;
      return r.parsed_city === p.city && r.parsed_intel_type === p.type;
    });
  }
  function patternMatches(p) {
    if (!q) return true;
    const subjectStr = (p.kind === "carrier" ? carrierLookup(p.carrier) : cityLookup(p.city)).toLowerCase();
    const typeStr = (p.type || "").toLowerCase();
    if (subjectStr.includes(q) || typeStr.includes(q)) return true;
    // Search inside the matching intel rows' text too
    const matching = patternMatchingRows(p);
    return matching.some(r => (r.raw_text || "").toLowerCase().includes(q));
  }
  const all = baseline.filter(patternMatches).slice(0, q ? 30 : 12);

  function carrierName(slug) {
    if (!slug) return "—";
    const c = (carriers || []).find(x => x.slug === slug);
    return c ? c.name : slug.toUpperCase();
  }
  function cityName(id) {
    if (!id) return "—";
    const c = (cities || []).find(x => x.id === id);
    return c ? `${c.name}, ${c.state}` : id;
  }
  function fmtType(t) { return (t || "").replace(/_/g, " "); }
  function fmtAge(ms) {
    const d = Math.floor((now - ms) / day);
    if (d <= 0) return "today";
    if (d === 1) return "1d ago";
    return `${d}d ago`;
  }

  return (
    <div>
      <div className="pattern-search-row">
        <input
          type="search"
          className="pattern-search"
          placeholder="Search patterns by carrier, metro, type, or text…"
          value={search}
          onChange={(e) => { setSearch(e.target.value); setExpanded(null); }}
        />
        {search && (
          <button className="pattern-search-clear" onClick={() => setSearch("")}>Clear ×</button>
        )}
        <span className="pattern-search-count">
          {all.length} {all.length === 1 ? "pattern" : "patterns"}{search ? ` matching "${search}"` : ""}
        </span>
      </div>

      {all.length === 0 ? (
        <div className="pattern-empty">
          {search
            ? `No patterns match "${search}". Try a carrier name, metro, or intel type.`
            : "Not enough signal volume yet to surface chronic patterns. As the network fills in, repeat-offender carriers and metros will land here automatically."}
        </div>
      ) : (
        <div className="pattern-list">
          {all.map((p) => {
            const sev = p.sev.critical > 0 ? "critical"
                      : p.sev.high > 0 ? "high"
                      : p.sev.moderate > 0 ? "moderate" : "low";
            const subjectLabel = p.kind === "carrier" ? carrierName(p.carrier) : cityName(p.city);
            const subjectKind  = p.kind === "carrier" ? "Carrier" : "Metro";
            const breadth = p.kind === "carrier" ? p.cities.size : p.carriers.size;
            const breadthL = p.kind === "carrier" ? "metros" : "carriers";
            const isOpen = expanded === p.key;
            const matching = isOpen ? patternMatchingRows(p)
                                       .sort((a, b) => new Date(b.posted_at || b.created_at) - new Date(a.posted_at || a.created_at))
                                       .slice(0, 20)
                                    : [];
            const accentColor = p.kind === "carrier" && window.carrierColor
              ? window.carrierColor(p.carrier)
              : null;
            return (
              <div key={p.key} className={`pattern-row-wrap ${isOpen ? "pattern-open" : ""}`}>
                <button
                  className={`pattern-row pattern-row-${sev}`}
                  onClick={() => setExpanded(isOpen ? null : p.key)}
                  style={accentColor ? { borderLeftColor: accentColor, borderLeftWidth: "5px" } : undefined}
                >
                  <span className={`pattern-sev-dot pattern-sev-${sev}`} />
                  <div className="pattern-body">
                    <div className="pattern-headline">
                      <span
                        className="pattern-subject"
                        style={accentColor ? { color: accentColor } : undefined}
                      >{subjectLabel}</span>
                      <span className="pattern-arrow">→</span>
                      <span className="pattern-type">{fmtType(p.type)}</span>
                    </div>
                    <div className="pattern-meta">
                      {subjectKind} · {p.count} reports · {breadth} {breadthL} · last {fmtAge(p.latest)}
                      {p.sev.critical + p.sev.high > 0 && (
                        <span className="pattern-sev-bar"> · <strong>{p.sev.critical + p.sev.high}</strong> high+critical</span>
                      )}
                    </div>
                  </div>
                  <span className="pattern-count">{p.count}</span>
                  <span className="pattern-chevron">{isOpen ? "▴" : "▾"}</span>
                </button>
                {isOpen && (
                  <div className="pattern-detail">
                    <div className="pattern-detail-head">
                      Showing {matching.length} of {p.count} reports · most recent first
                      {onPattern && (
                        <button
                          className="pattern-detail-filter"
                          onClick={(e) => { e.stopPropagation(); onPattern(p); }}
                          title="Apply this pattern as a filter to the recent feed below"
                        >Filter feed by this →</button>
                      )}
                    </div>
                    <ul className="pattern-detail-list">
                      {matching.map((r, i) => (
                        <li key={i} className="pattern-detail-row">
                          <span className={`pattern-sev-dot pattern-sev-${r.severity || "low"}`} />
                          <div className="pattern-detail-body">
                            <div className="pattern-detail-meta">
                              <strong>{carrierName(r.parsed_carrier)}</strong>
                              {r.parsed_city && (<>{" · "}{cityName(r.parsed_city)}</>)}
                              {" · "}{fmtAge(new Date(r.posted_at || r.created_at).getTime())}
                            </div>
                            <div className="pattern-detail-text">{(r.raw_text || "").slice(0, 220)}{(r.raw_text || "").length > 220 ? "…" : ""}</div>
                          </div>
                        </li>
                      ))}
                    </ul>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function StateOfCityChart({ rows, cities, weather, onCity }) {
  // For each city, count last-7d signals split by severity, and weather alerts.
  const day = 86400000;
  const now = Date.now();
  const recent7 = rows.filter(r => now - new Date(r.created_at).getTime() < 7 * day);
  const byCity = {};
  recent7.forEach(r => {
    const c = r.parsed_city || "unknown";
    if (!byCity[c]) byCity[c] = { critical: 0, high: 0, moderate: 0, low: 0 };
    const s = r.severity || "low";
    if (byCity[c][s] != null) byCity[c][s] += 1;
  });
  const wxByCity = {};
  (weather || []).forEach(w => {
    if (!w.city_id) return;
    wxByCity[w.city_id] = (wxByCity[w.city_id] || 0) + 1;
  });

  // Show featured cities only, ranked by total signal volume desc
  const list = (cities || [])
    .filter(c => c.is_featured)
    .map(c => {
      const sev = byCity[c.id] || { critical: 0, high: 0, moderate: 0, low: 0 };
      const total = sev.critical + sev.high + sev.moderate + sev.low;
      return { ...c, sev, total, weather: wxByCity[c.id] || 0 };
    })
    .sort((a, b) => b.total - a.total);

  const max = Math.max(1, ...list.map(c => c.total));

  return (
    <div className="state-city">
      {list.map(c => {
        const pct = (v) => max ? (v / max) * 100 : 0;
        return (
          <div key={c.id} className="state-row" onClick={() => onCity && onCity(c.id)}>
            <div className="state-name">{c.name}<span className="state-state">{c.state}</span></div>
            <div className="state-bar">
              <div className="state-seg" style={{ width: `${pct(c.sev.low)}%`,      background: severityColor("low") }}      title={`Low: ${c.sev.low}`} />
              <div className="state-seg" style={{ width: `${pct(c.sev.moderate)}%`, background: severityColor("moderate") }} title={`Moderate: ${c.sev.moderate}`} />
              <div className="state-seg" style={{ width: `${pct(c.sev.high)}%`,     background: severityColor("high") }}     title={`High: ${c.sev.high}`} />
              <div className="state-seg" style={{ width: `${pct(c.sev.critical)}%`, background: severityColor("critical") }} title={`Critical: ${c.sev.critical}`} />
            </div>
            <div className="state-total">{c.total}</div>
            {c.weather > 0 && <div className="state-wx" title={`${c.weather} active weather alert(s)`}>🌩 {c.weather}</div>}
          </div>
        );
      })}
    </div>
  );
}

function ForecastStrip({ cityId, cityName, forecast }) {
  // Filter forecast rows to this city, take next 7 distinct period_labels.
  const seen = new Set();
  const items = (forecast || [])
    .filter(f => f.city_id === cityId)
    .filter(f => {
      const k = f.period_label || f.source_id;
      if (seen.has(k)) return false;
      seen.add(k);
      return true;
    })
    .slice(0, 7);

  if (items.length === 0) {
    return <div className="dash-empty">No forecast data yet for {cityName}.</div>;
  }

  function tempLabel(it) {
    if (it.temp_high != null) return `${it.temp_high}°`;
    if (it.temp_low != null) return `${it.temp_low}°`;
    return "—";
  }

  return (
    <div className="forecast-strip">
      {items.map((it, i) => (
        <div key={i} className="forecast-day">
          <div className="forecast-label" title={it.period_label || ""}>{it.period_label || ""}</div>
          <div className="forecast-temp-row">
            <span className="forecast-icon" aria-hidden="true">{weatherIconFor(it.short_forecast)}</span>
            <span className="forecast-temp">{tempLabel(it)}</span>
          </div>
          <div className="forecast-cond">{it.short_forecast || ""}</div>
          {it.precip_pct != null && it.precip_pct > 0 && (
            <div className="forecast-precip">💧 {it.precip_pct}%</div>
          )}
        </div>
      ))}
    </div>
  );
}

function IntelReport({ allIntel, carriers, cities, weather, forecast, pulseSubs, filter, setFilter, counts }) {
  // Resolver: prefer the server-side count, fall back to in-memory length only
  // when the count fetch hasn't returned yet. Anything labelled "all-time" or
  // a multi-day window MUST go through this — array .length lies once the
  // 2000-row window is full.
  const c = (key, fallback) => (counts && counts[key] != null) ? counts[key] : fallback;
  const now = Date.now();
  const day = 86400000, week = 7 * day;
  const recent24 = allIntel.filter(r => now - new Date(r.created_at).getTime() < day);
  const recent7d = allIntel.filter(r => now - new Date(r.created_at).getTime() < week);
  const prev24 = allIntel.filter(r => {
    const t = now - new Date(r.created_at).getTime();
    return t >= day && t < 2 * day;
  });
  const prev7d = allIntel.filter(r => {
    const t = now - new Date(r.created_at).getTime();
    return t >= week && t < 2 * week;
  });
  const high24 = recent24.filter(r => r.severity === "high" || r.severity === "critical");
  const prevHigh24 = prev24.filter(r => r.severity === "high" || r.severity === "critical");

  // Sparkline: last 14 days, daily count
  function dailyCounts(rows) {
    const out = Array(14).fill(0);
    const today = new Date();
    today.setUTCHours(0, 0, 0, 0);
    rows.forEach(r => {
      const t = new Date(r.created_at);
      t.setUTCHours(0, 0, 0, 0);
      const days = Math.floor((today.getTime() - t.getTime()) / day);
      if (days >= 0 && days < 14) out[13 - days] += 1;
    });
    return out;
  }
  const last14 = dailyCounts(allIntel.filter(r => now - new Date(r.created_at).getTime() < 14 * day));
  const carriersUnique14 = [...new Set(allIntel.filter(r => now - new Date(r.created_at).getTime() < 14 * day).map(r => r.parsed_carrier))].length;
  const carrierSpark = Array(14).fill(carriersUnique14);

  return (
    <section className="report-section">
      <div className="report-head">
        <h2 className="report-title">Intel Report</h2>
        <span className="report-sub">Last 7 days vs prior 7 days · all carriers, all metros</span>
      </div>

      <div className="kpi-row-grid">
        <KPITile accent="blue"   label="Signals · 7d" value={c("d7", recent7d.length)} prevValue={c("prevD7", prev7d.length)} sparklineDays={last14} />
        <KPITile accent="blue"   label="Signals · 24h" value={c("d24", recent24.length)} prevValue={c("prevD24", prev24.length)} sparklineDays={last14} />
        <KPITile accent="red"    label="High severity · 24h" value={c("high24", high24.length)} prevValue={c("prevHigh24", prevHigh24.length)} sparklineDays={last14} />
        <KPITile accent="red"    label="Critical · all-time" value={c("critical", allIntel.filter(r => r.severity === "critical").length)} prevValue={null} sparklineDays={last14} />
        <KPITile accent="violet" label="Active carriers · 14d" value={carriersUnique14} prevValue={null} sparklineDays={carrierSpark} />
        <KPITile accent="teal"   label="Active metros · 14d" value={[...new Set(allIntel.filter(r => now - new Date(r.created_at).getTime() < 14 * day).map(r => r.parsed_city).filter(Boolean))].length} prevValue={null} sparklineDays={carrierSpark} />
        <KPITile accent="green"  label="Verified intel" value={c("verified", allIntel.filter(r => r.status === "verified").length)} prevValue={null} sparklineDays={last14} />
        <KPITile accent="cyan"   label="All intel · all-time" value={c("total", allIntel.length)} prevValue={null} sparklineDays={last14} />
      </div>

      {filter && (filter.carrier || filter.city || filter.severity || filter.type) && (
        <div className="report-filter-row">
          <span className="report-filter-label">Filtering by:</span>
          {filter.carrier && <span className="report-filter-chip">carrier: <strong>{filter.carrier}</strong></span>}
          {filter.city && <span className="report-filter-chip">metro: <strong>{filter.city}</strong></span>}
          {filter.severity && <span className="report-filter-chip">severity: <strong>{filter.severity}</strong></span>}
          {filter.type && <span className="report-filter-chip">type: <strong>{filter.type}</strong></span>}
          <button className="report-filter-clear" onClick={() => setFilter && setFilter({ carrier: null, city: null, severity: null, type: null })}>Clear ×</button>
        </div>
      )}

      <div className="report-charts">
        <div className="report-chart-card" data-accent="blue">
          <div className="report-chart-head">
            <h3 className="report-chart-h">Signal volume · 30 days</h3>
            <span className="report-sub">Stacked by severity · click a level to filter</span>
          </div>
          <StackedAreaChart rows={allIntel} days={30} />
          <div className="report-legend report-legend-clickable">
            {["critical", "high", "moderate", "low"].map(s => (
              <button key={s} className="report-legend-item report-legend-btn" onClick={() => setFilter && setFilter({ ...filter, severity: filter && filter.severity === s ? null : s })}>
                <span className="report-legend-dot" style={{ background: severityColor(s) }} />
                {s}
              </button>
            ))}
          </div>
        </div>

        <div className="report-chart-card" data-accent="violet">
          <div className="report-chart-head">
            <h3 className="report-chart-h">Where the trouble concentrates</h3>
            <span className="report-sub">Top 10 carriers × intel type · click a cell to drill in</span>
          </div>
          <CarrierHeatmap
            rows={allIntel.filter(r => now - new Date(r.created_at).getTime() < 30 * day)}
            carriers={carriers}
            onCell={(carrier, type) => setFilter && setFilter({ ...filter, carrier, type })}
          />
        </div>
      </div>

      <div className="report-chart-card" data-accent="red" style={{ marginTop: 16 }}>
        <div className="report-chart-head">
          <h3 className="report-chart-h">🔁 Pattern radar · chronic issues</h3>
          <span className="report-sub">Last 30 days · 3+ reports = pattern · click to filter the feed</span>
        </div>
        <PatternRadar
          rows={allIntel}
          carriers={carriers}
          cities={cities}
          onPattern={(p) => {
            if (!setFilter) return;
            if (p.kind === "carrier") setFilter({ ...filter, carrier: p.carrier, type: p.type });
            else                       setFilter({ ...filter, city: p.city, type: p.type });
          }}
        />
      </div>

      <div className="report-chart-card" data-accent="teal" style={{ marginTop: 16 }}>
        <div className="report-chart-head">
          <h3 className="report-chart-h">State of the cities</h3>
          <span className="report-sub">Last 7 days · all featured metros · click a city to drill in</span>
        </div>
        <StateOfCityChart
          rows={allIntel}
          cities={cities}
          weather={weather}
          onCity={(cityId) => setFilter && setFilter({ ...filter, city: cityId })}
        />
      </div>

      {pulseSubs && pulseSubs.length > 0 && (
        <div className="report-chart-card" data-accent="amber" style={{ marginTop: 16 }}>
          <div className="report-chart-head">
            <h3 className="report-chart-h">7-day weather outlook · your metros</h3>
            <span className="report-sub">7-day weather outlook</span>
          </div>
          {pulseSubs.map(s => {
            const c = (cities || []).find(x => x.id === s.city_id);
            const cityName = c ? `${c.name}, ${c.state}` : s.city_id;
            return (
              <div key={s.city_id} style={{ marginTop: 18 }}>
                <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 6 }}>{cityName}</div>
                <ForecastStrip cityId={s.city_id} cityName={cityName} forecast={forecast} />
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

window.SignInPage = SignInPage;
window.AccountPage = AccountPage;
