// ─── Freight Map View — restyled to match Infomatics reference ───────
const { useState, useMemo, useEffect, useRef, useCallback } = React;

/* ─── Geofence (red zone) constants & helpers ─────────────────────── */
const DEFAULT_RZ_RADIUS_M = 250;       // Samsara-style default: 250m around geocoded point
const MIN_RZ_RADIUS_M = 50;
const MAX_RZ_RADIUS_M = 5000;
const INITIAL_POLYGON_VERTICES = 12;   // when user converts circle → polygon
const MAX_POLYGON_VERTICES = 40;       // hard cap per geofence

// Parse "lat, lng" coordinate strings. Returns [lat, lng] or null.
const parseCoords = (str) => {
  const m = (str || '').trim().match(/^(-?\d{1,2}(?:\.\d+)?)\s*[,\s]\s*(-?\d{1,3}(?:\.\d+)?)$/);
  if (!m) return null;
  const lat = parseFloat(m[1]);
  const lng = parseFloat(m[2]);
  if (Math.abs(lat) > 90 || Math.abs(lng) > 180) return null;
  return [lat, lng];
};

// Convert a circle (center + radius in meters) to a polygon of N vertices.
const circleToPolygon = (center, radius_m, count) => {
  const lat0 = center[0] * Math.PI / 180;
  const earth = 6378137;
  const dLat = (radius_m / earth) * (180 / Math.PI);
  const dLng = (radius_m / (earth * Math.cos(lat0))) * (180 / Math.PI);
  const out = [];
  for (let i = 0; i < count; i++) {
    const a = (i / count) * 2 * Math.PI - Math.PI / 2;
    out.push([
      center[0] + Math.sin(a) * dLat,
      center[1] + Math.cos(a) * dLng
    ]);
  }
  return out;
};

// East-edge point of a circle, used as the radius drag handle's position.
const circleEastPoint = (center, radius_m) => {
  const lat0 = center[0] * Math.PI / 180;
  const earth = 6378137;
  const dLng = (radius_m / (earth * Math.cos(lat0))) * (180 / Math.PI);
  return [center[0], center[1] + dLng];
};

// Polygon centroid (simple arithmetic mean — fine for visual handle anchoring).
const polygonCentroid = (verts) => {
  if (!verts || !verts.length) return [0, 0];
  let y = 0, x = 0;
  verts.forEach((p) => { y += p[0]; x += p[1]; });
  return [y / verts.length, x / verts.length];
};

// Format a radius value for the toolbar.
const formatRadius = (m) => {
  if (m == null) return '—';
  if (m >= 1000) return (m / 1000).toFixed(m >= 10000 ? 0 : 2).replace(/\.00$/, '') + ' km';
  return Math.round(m) + ' m';
};

/* ─── City coordinates ───────────────────────────────────────────── */
const CITY = {
  'Rancho Cucamonga, CA': [34.106, -117.593],
  'Carson, CA': [33.831, -118.282],
  'Ontario, CA': [34.064, -117.651],
  'Los Angeles, CA': [34.052, -118.244],
  'Long Beach, CA': [33.770, -118.190],
  'Pittsburgh, PA': [40.441, -79.996],
  'Mechanicsburg, PA': [40.213, -77.005],
  'Columbus, OH': [39.961, -82.998],
  'Fayetteville, NC': [35.052, -78.878],
  'Killingly, CT': [41.846, -71.866],
  'Dallas, TX': [32.776, -96.797],
  'Houston, TX': [29.760, -95.370],
  'San Antonio, TX': [29.424, -98.494],
  'Chicago, IL': [41.878, -87.629],
  'Memphis, TN': [35.149, -90.049],
  'Portland, OR': [45.515, -122.678],
  'Las Vegas, NV': [36.169, -115.139],
  'Atlanta, GA': [33.749, -84.388],
  'Charlotte, NC': [35.227, -80.843],
  'Phoenix, AZ': [33.448, -112.074],
  'Seattle, WA': [47.606, -122.332],
  'Salt Lake City, UT': [40.760, -111.891],
  'Louisville, KY': [38.252, -85.758],
  'Nashville, TN': [36.162, -86.781],
  'Denver, CO': [39.739, -104.990],
  'Kansas City, MO': [39.099, -94.578],
  'Minneapolis, MN': [44.978, -93.265],
  'Indianapolis, IN': [39.768, -86.158],
  'Richmond, VA': [37.541, -77.434],
  'Miami, FL': [25.762, -80.192],
  'Tampa, FL': [27.951, -82.457],
  'Boston, MA': [42.361, -71.057]
};

/* ─── Status map ─────────────────────────────────────────────────── */
const STATUS_MAP = {
  order_created: { label: 'Order Created' },
  pickup: { label: 'Pickup' },
  in_transit: { label: 'In Transit' },
  out_for_delivery: { label: 'Out for Delivery' },
  delivered: { label: 'Delivered' },
  billing: { label: 'Billing' },
  invoiced: { label: 'Invoiced' }
};

/* ─── Shipment data (unchanged) ──────────────────────────────────── */
const SHIPMENTS = [
{ id: 1, status: 'pickup', bol: 'SHOWCASE-006', origin: 'Rancho Cucamonga, CA', dest: 'Pittsburgh, PA', refs: ['21250176697'], eta: '2026-05-26T14:00:00Z', pct: 22, mabd: '2026-05-27T00:00:00Z', appt: '2026-05-26T10:00:00Z', pallets: 4, cartons: 12, wt: 1840, shipper: 'LogiWest Distribution Center', consignee: 'Rocky Mountain Logistics', carriers: [{ name: 'XPO Logistics', active: true }] },
{ id: 2, status: 'in_transit', bol: 'SHOWCASE-LTL-007', origin: 'Carson, CA', dest: 'Pittsburgh, PA', refs: ['32250000646', 'PO-44821'], eta: '2026-05-26T18:00:00Z', pct: 55, mabd: '2026-05-27T00:00:00Z', appt: '2026-05-27T08:00:00Z', pallets: 2, cartons: 8, wt: 960, shipper: 'ASUS-SBG c/o Prime Freight', consignee: 'Pittsburgh Gateway Hub', carriers: [{ name: 'Old Dominion', active: true }, { name: 'XPO Logistics', active: false }] },
{ id: 3, status: 'order_created', bol: 'NC00000634', origin: 'Carson, CA', dest: 'Columbus, OH', refs: ['21260076426', '21260076427', '21260076428'], eta: null, pct: 0, mabd: '2026-05-29T00:00:00Z', appt: '2026-05-29T09:00:00Z', pallets: 6, cartons: 24, wt: 3200, shipper: 'Carson Fulfillment Co.', consignee: 'STAPLES-US-C-DC895', carriers: [{ name: 'Estes Express', active: true }] },
{ id: 4, status: 'order_created', bol: 'NC00000635', origin: 'Carson, CA', dest: 'Mechanicsburg, PA', refs: ['21260076415'], eta: null, pct: 0, mabd: '2026-05-24T20:00:00Z', appt: '2026-05-29T10:00:00Z', pallets: 3, cartons: 15, wt: 1450, shipper: 'Carson Fulfillment Co.', consignee: 'STAPLES-US-C-DC994', carriers: [{ name: 'R+L Carriers', active: true }] },
{ id: 5, status: 'in_transit', bol: 'NC00000636', redZone: true, origin: 'Carson, CA', dest: 'Fayetteville, NC', refs: ['21260076411', 'SO-77021'], eta: '2026-05-27T14:00:00Z', pct: 62, mabd: '2026-05-28T00:00:00Z', appt: '2026-05-27T14:00:00Z', pallets: 5, cartons: 18, wt: 2100, shipper: 'Carson Fulfillment Co.', consignee: 'STAPLES-US-C-DC799', carriers: [{ name: 'ABF Freight', active: true }, { name: 'Saia LTL', active: false }] },
{ id: 6, status: 'billing', bol: 'NC00000637', podMissing: true, origin: 'Carson, CA', dest: 'Killingly, CT', refs: ['21260076416', 'PO-33021', 'SO-77120', 'REF-91044', '21260077000'], eta: null, pct: 100, mabd: '2026-05-20T00:00:00Z', appt: '2026-05-20T08:00:00Z', pallets: 8, cartons: 32, wt: 4800, shipper: 'Carson Fulfillment Co.', consignee: 'STAPLES-US-C-DC580', carriers: [{ name: 'XPO Logistics', active: true }] },
{ id: 7, status: 'invoiced', bol: 'INVLTL-2201', podMissing: true, origin: 'Ontario, CA', dest: 'Dallas, TX', refs: ['PO-88821-C'], eta: null, pct: 100, mabd: '2026-05-15T00:00:00Z', appt: '2026-05-15T11:00:00Z', pallets: 10, cartons: 42, wt: 5900, shipper: 'Pacific Coast Distributors', consignee: 'Target DC – Dallas', carriers: [{ name: 'Saia LTL', active: true }] },
{ id: 8, status: 'out_for_delivery', bol: 'BL-44892', redZone: true, origin: 'Memphis, TN', dest: 'Chicago, IL', refs: ['PO-77612', 'REF-20901'], eta: '2026-05-25T16:00:00Z', pct: 94, mabd: '2026-05-25T00:00:00Z', appt: '2026-05-25T15:00:00Z', pallets: 3, cartons: 9, wt: 870, shipper: 'Mid-South Logistics', consignee: 'Amazon FBA – ORD4', carriers: [{ name: 'FedEx Freight', active: true }] },
{ id: 9, status: 'pickup', bol: 'LTL-99021', origin: 'Portland, OR', dest: 'Las Vegas, NV', refs: ['SO-12045'], eta: '2026-05-27T09:00:00Z', pct: 18, mabd: '2026-05-28T00:00:00Z', appt: '2026-05-27T08:00:00Z', pallets: 2, cartons: 6, wt: 530, shipper: 'Northwest Freight Partners', consignee: 'Costco – Las Vegas DC', carriers: [{ name: 'Old Dominion', active: true }] },
{ id: 10, status: 'out_for_delivery', bol: 'RL-20044', arrivingEarly: true, origin: 'Atlanta, GA', dest: 'Charlotte, NC', refs: ['REF-45892'], eta: '2026-05-25T11:00:00Z', pct: 97, isEarly: true, mabd: '2026-05-26T00:00:00Z', appt: '2026-05-26T08:00:00Z', pallets: 1, cartons: 4, wt: 320, shipper: 'Southeast Express LLC', consignee: 'Home Depot – Charlotte', carriers: [{ name: 'R+L Carriers', active: true }] },
{ id: 11, status: 'billing', bol: 'BL-55001', podMissing: true, origin: 'Houston, TX', dest: 'Phoenix, AZ', refs: ['PO-90044', 'PO-90045', 'PO-90046', 'SO-11200'], eta: null, pct: 100, mabd: '2026-05-18T00:00:00Z', appt: '2026-05-18T10:00:00Z', pallets: 7, cartons: 28, wt: 3800, shipper: 'Gulf Coast Shipping LLC', consignee: 'Walmart DC – Phoenix', carriers: [{ name: 'TForce Freight', active: true }, { name: 'XPO Logistics', active: false }] },
{ id: 12, status: 'invoiced', bol: 'INV-88001', origin: 'Seattle, WA', dest: 'Salt Lake City, UT', refs: ['REF-31200'], eta: null, pct: 100, mabd: '2026-05-10T00:00:00Z', appt: '2026-05-10T09:00:00Z', pallets: 4, cartons: 16, wt: 2200, shipper: 'Pacific NW Logistics', consignee: "Smith's Food Distribution", carriers: [{ name: 'Estes Express', active: true }] },
{ id: 13, status: 'in_transit', bol: 'LTL-44120', arrivingEarly: true, origin: 'Louisville, KY', dest: 'Nashville, TN', refs: ['PO-55612'], eta: '2026-05-25T20:00:00Z', pct: 80, mabd: '2026-05-26T00:00:00Z', appt: '2026-05-25T22:00:00Z', pallets: 5, cartons: 20, wt: 2600, shipper: 'Bluegrass Distribution', consignee: 'Dollar General DC', carriers: [{ name: 'ABF Freight', active: true }] },
{ id: 14, status: 'in_transit', bol: 'NC-88500', redZone: true, origin: 'Denver, CO', dest: 'Kansas City, MO', refs: ['21260099100', 'REF-20021', 'PO-60014', 'SO-44892', '21260099101', '21260099102'], eta: '2026-05-27T15:00:00Z', pct: 40, isDelayed: true, mabd: '2026-05-26T00:00:00Z', appt: '2026-05-26T09:00:00Z', pallets: 9, cartons: 36, wt: 5100, shipper: 'Rocky Mtn. Supply Chain', consignee: 'CVS Distribution', carriers: [{ name: 'Old Dominion', active: true }, { name: 'J.B. Hunt', active: false }] },
{ id: 15, status: 'in_transit', bol: 'FT-22891', origin: 'Phoenix, AZ', dest: 'San Antonio, TX', refs: ['PO-44102', 'SO-88310'], eta: '2026-05-28T12:00:00Z', pct: 28, isDelayed: true, mabd: '2026-05-27T00:00:00Z', appt: '2026-05-27T10:00:00Z', pallets: 6, cartons: 22, wt: 3100, shipper: 'Desert SW Shipping Inc.', consignee: 'H-E-B DC – San Antonio', carriers: [{ name: 'Werner Enterprises', active: true }] },
{ id: 16, status: 'billing', bol: 'BILL-99401', podMissing: true, origin: 'Minneapolis, MN', dest: 'Indianapolis, IN', refs: ['PO-33021'], eta: null, pct: 100, mabd: '2026-05-19T00:00:00Z', appt: '2026-05-19T13:00:00Z', pallets: 4, cartons: 18, wt: 2400, shipper: 'Northstar Freight LLC', consignee: 'Target DC – Indianapolis', carriers: [{ name: 'Schneider', active: true }] },
{ id: 17, status: 'invoiced', bol: 'INV-77002', podMissing: true, origin: 'Columbus, OH', dest: 'Richmond, VA', refs: ['REF-10044', 'REF-10045', 'REF-10046'], eta: null, pct: 100, mabd: '2026-05-12T00:00:00Z', appt: '2026-05-12T09:00:00Z', pallets: 3, cartons: 10, wt: 1100, shipper: 'Great Lakes Logistics', consignee: "Lowe's DC – Richmond", carriers: [{ name: 'XPO Logistics', active: true }] },
{ id: 18, status: 'in_transit', bol: 'SPOT-40012', redZone: true, origin: 'Chicago, IL', dest: 'Miami, FL', refs: ['SO-88203'], eta: '2026-05-28T10:00:00Z', pct: 44, mabd: null, appt: '2026-05-28T09:00:00Z', pallets: 3, cartons: 11, wt: 1320, shipper: 'Midwest Spot Freight Inc.', consignee: 'Port Miami Distribution', carriers: [{ name: 'J.B. Hunt', active: true }] },
{ id: 19, status: 'pickup', bol: 'SPOT-40013', origin: 'Kansas City, MO', dest: 'Tampa, FL', refs: ['SO-88204'], eta: '2026-05-29T08:00:00Z', pct: 12, mabd: null, appt: '2026-05-29T08:00:00Z', pallets: 2, cartons: 7, wt: 690, shipper: 'Heartland Express Co.', consignee: 'Suncoast Wholesale LLC', carriers: [{ name: 'Schneider', active: true }] },
{ id: 20, status: 'order_created', bol: 'NC00000701', origin: 'Los Angeles, CA', dest: 'Boston, MA', refs: ['PO-12241'], eta: null, pct: 0, mabd: null, appt: '2026-06-02T10:00:00Z', apptEnd: '2026-06-02T10:30:00Z', pallets: 5, cartons: 21, wt: 2750, shipper: 'Pacific Rim Logistics', consignee: 'New England DC', carriers: [{ name: 'Old Dominion', active: true }, { name: 'XPO Logistics', active: false }, { name: 'Estes Express', active: false }] },
{ id: 21, status: 'delivered', bol: 'NC00000720', origin: 'Carson, CA', dest: 'Phoenix, AZ', refs: ['21260076500', 'PO-44120'], eta: '2026-05-23T15:30:00Z', pct: 100, mabd: '2026-05-24T00:00:00Z', appt: '2026-05-23T14:00:00Z', pallets: 4, cartons: 16, wt: 2080, shipper: 'Carson Fulfillment Co.', consignee: 'STAPLES-US-C-DC410', carriers: [{ name: 'Old Dominion', active: true }] },
{ id: 22, status: 'delivered', bol: 'RL-20088', arrivingEarly: true, origin: 'Atlanta, GA', dest: 'Nashville, TN', refs: ['REF-45920'], eta: '2026-05-24T09:15:00Z', pct: 100, isEarly: true, mabd: '2026-05-24T00:00:00Z', appt: '2026-05-24T10:00:00Z', pallets: 2, cartons: 8, wt: 640, shipper: 'Southeast Express LLC', consignee: 'Dollar General DC', carriers: [{ name: 'R+L Carriers', active: true }] },
{ id: 23, status: 'delivered', bol: 'BL-44910', origin: 'Memphis, TN', dest: 'Indianapolis, IN', refs: ['PO-77700', 'REF-21044'], eta: '2026-05-22T17:45:00Z', pct: 100, mabd: '2026-05-23T00:00:00Z', appt: '2026-05-22T16:00:00Z', pallets: 6, cartons: 24, wt: 3120, shipper: 'Mid-South Logistics', consignee: 'Target DC – Indianapolis', carriers: [{ name: 'FedEx Freight', active: true }] }];

/* ── Atlanta hub demo cluster ─────────────────────────────────────
   22 in-transit shipments freshly departed the Atlanta DC, all sitting
   at virtually the same pixel on the map. Used to showcase the
   cluster-breakdown popover + spider-fan UI for large stacked clusters. */
const HUB_DESTS = [
  ['Miami, FL', 'Coastal Distribution Inc.'],
  ['Tampa, FL', 'Suncoast Wholesale LLC'],
  ['Charlotte, NC', 'Home Depot – Charlotte'],
  ['Nashville, TN', 'Dollar General DC'],
  ['Richmond, VA', "Lowe's DC – Richmond"],
  ['Memphis, TN', 'Mid-South Logistics'],
  ['Louisville, KY', 'Bluegrass Distribution']];
const HUB_CARRIERS = ['XPO Logistics', 'Old Dominion', 'ABF Freight', 'Estes Express', 'R+L Carriers', 'FedEx Freight', 'Saia LTL'];
const HUB_DEMO = Array.from({ length: 22 }, (_, i) => {
  const [dest, consignee] = HUB_DESTS[i % HUB_DESTS.length];
  const carrier = HUB_CARRIERS[i % HUB_CARRIERS.length];
  const isDelayed = i === 3 || i === 9 || i === 17;
  const podMissing = false;
  const arrivingEarly = i === 6;
  const redZone = i === 12 || i === 19;
  return {
    id: 100 + i,
    status: 'in_transit',
    bol: `ATL-HUB-${String(8200 + i).padStart(4, '0')}`,
    origin: 'Atlanta, GA',
    dest,
    refs: [`PO-${48000 + i}`],
    eta: `2026-05-27T${String(8 + i % 12).padStart(2, '0')}:00:00Z`,
    pct: 3 + i % 4,
    isDelayed,
    arrivingEarly,
    redZone,
    mabd: '2026-05-27T00:00:00Z',
    appt: `2026-05-27T${String(9 + i % 10).padStart(2, '0')}:00:00Z`,
    pallets: 2 + i % 6,
    cartons: 8 + i % 14,
    wt: 600 + i * 75,
    shipper: 'Southeast Express LLC',
    consignee,
    carriers: [{ name: carrier, active: true }]
  };
});
SHIPMENTS.push(...HUB_DEMO);

/* ── Phoenix DC delivered cluster ────────────────────────────────
   28 shipments recently delivered to the Phoenix consignee — sits on top
   of the same destination point so the green delivered pin rolls up into
   a counted cluster, matching the truck-cluster UX. */
const DELIV_ORIGINS = [
  ['Houston, TX', 'Gulf Coast Shipping LLC'],
  ['Dallas, TX', 'Pacific Coast Distributors'],
  ['Las Vegas, NV', 'Northwest Freight Partners'],
  ['Salt Lake City, UT', 'Pacific NW Logistics'],
  ['Los Angeles, CA', 'Pacific Rim Logistics'],
  ['Carson, CA', 'Carson Fulfillment Co.'],
  ['Denver, CO', 'Rocky Mtn. Supply Chain']];
const DELIV_CARRIERS = ['XPO Logistics', 'Old Dominion', 'ABF Freight', 'Estes Express', 'R+L Carriers', 'FedEx Freight', 'TForce Freight'];
const DELIV_DEMO = Array.from({ length: 28 }, (_, i) => {
  const [origin, shipper] = DELIV_ORIGINS[i % DELIV_ORIGINS.length];
  const carrier = DELIV_CARRIERS[i % DELIV_CARRIERS.length];
  const day = 18 + i % 6; // delivered between May 18-23
  return {
    id: 200 + i,
    status: 'delivered',
    bol: `PHX-DLV-${String(7300 + i).padStart(4, '0')}`,
    origin,
    dest: 'Phoenix, AZ',
    refs: [`PO-${52000 + i}`],
    eta: `2026-05-${String(day).padStart(2, '0')}T${String(10 + i % 9).padStart(2, '0')}:${String((i * 7) % 60).padStart(2, '0')}:00Z`,
    pct: 100,
    mabd: `2026-05-${String(day).padStart(2, '0')}T00:00:00Z`,
    appt: `2026-05-${String(day).padStart(2, '0')}T${String(9 + i % 9).padStart(2, '0')}:00:00Z`,
    pallets: 2 + i % 7,
    cartons: 6 + i % 18,
    wt: 480 + i * 95,
    shipper,
    consignee: 'Walmart DC – Phoenix',
    carriers: [{ name: carrier, active: true }]
  };
});
SHIPMENTS.push(...DELIV_DEMO);


/* ─── Helpers ───────────────────────────────────────────────────── */
/* Multi-leg routes — each shipment may have intermediate transit hubs
   between origin and destination, with a different carrier+driver per leg.
   Shipments without an entry default to a single leg. */
const LEGS_BY_ID = {
  1: [
  { to: 'Dallas, TX', carrier: 'XPO Logistics', driver: 'Mark M.' },
  { to: 'Indianapolis, IN', carrier: 'Old Dominion', driver: 'Tania R.' },
  { to: 'Pittsburgh, PA', carrier: 'R+L Carriers', driver: 'James K.' }],

  2: [
  { to: 'Dallas, TX', carrier: 'Old Dominion', driver: 'Tania R.' },
  { to: 'Columbus, OH', carrier: 'XPO Logistics', driver: 'Sandra L.' },
  { to: 'Pittsburgh, PA', carrier: 'Old Dominion', driver: 'James K.' }],

  20: [
  { to: 'Chicago, IL', carrier: 'Old Dominion', driver: 'Jenna Q.' },
  { to: 'Columbus, OH', carrier: 'XPO Logistics', driver: 'Adam W.' },
  { to: 'Boston, MA', carrier: 'Estes Express', driver: 'Olivia T.' }]

};

/* Before a shipment is planned there's no ETA, so the card falls back to the
   commitment date it does have — MABD when set, otherwise the appointment.
   Past due reads red, still-upcoming reads orange. */
const fmtDateOnly = (iso) => iso ? new Date(iso).toLocaleDateString('en-US', { timeZone: 'UTC', month: 'short', day: 'numeric' }) : '—';
const fmtTimeOnly = (iso) => iso ? new Date(iso).toLocaleTimeString('en-US', { timeZone: 'UTC', hour: 'numeric', minute: '2-digit' }) : '';
const apptFallback = (s) => {
  if (!s || s.status !== 'order_created' || s.eta) return null;
  const iso = s.mabd || s.appt;
  if (!iso) return null;
  const past = new Date(iso).getTime() < NOW_TL;
  const window = !s.mabd && s.apptEnd ? ` – ${fmtTimeOnly(s.apptEnd)}` : '';
  const when = `${fmtDateTime(iso)}${window}`;
  return { label: `Appt: ${when}`, cls: past ? 'is-mabd-past' : 'is-mabd-due', past, iso, when };
};

const fmtDateTime = (iso) => {
  if (!iso) return '—';
  return new Date(iso).toLocaleString('en-US', { timeZone: 'UTC', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
};
const cityShort = (full) => full.split(',')[0] + ',' + full.split(',')[1];
const primaryCarrier = (s) => s.carriers && s.carriers.find((c) => c.active) ? s.carriers.find((c) => c.active).name : s.carriers && s.carriers[0] ? s.carriers[0].name : '—';
const interpolate = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];

/* Deterministic seeded jitter — same shipment + tracker always gets the
   same wandering breadcrumb trail, so paths are stable across re-renders. */
const seeded = (seed) => {
  let x = (seed * 9301 + 49297) % 233280;
  return () => {x = (x * 9301 + 49297) % 233280;return x / 233280;};
};

/* Generate a wandering breadcrumb path of ping points from `start` to `end`.
   `side` (+1 / -1) biases the wander direction so two trackers on the same
   shipment trace visibly distinct trails. */
const breadcrumbPath = (start, end, seed, side) => {
  const STEPS = 22;
  const rand = seeded(seed);
  const dy = end[0] - start[0];
  const dx = end[1] - start[1];
  const len = Math.hypot(dy, dx) || 1;
  // unit perpendicular vector in [lat, lng] space
  const perp = [-dx / len, dy / len];
  const pts = [start];
  for (let i = 1; i < STEPS; i++) {
    const t = i / STEPS;
    const mid = interpolate(start, end, t);
    // bell-shaped envelope so the path bulges in the middle, hugs endpoints
    const env = Math.sin(t * Math.PI);
    // wander amplitude scales with route length (in degrees)
    const amp = len * 0.06 * env * side;
    // small random wobble layered on top
    const wobble = (rand() - 0.5) * len * 0.025 * env;
    const off = amp + wobble;
    pts.push([mid[0] + perp[0] * off, mid[1] + perp[1] * off]);
  }
  pts.push(end);
  return pts;
};

/* ─── Route legs / stops ───────────────────────────────────────── */
/* Default leg when no LEGS_BY_ID entry exists — origin straight to dest with
   the shipment's primary carrier. */
const getLegs = (s) => LEGS_BY_ID[s.id] || [
{ to: s.dest, carrier: primaryCarrier(s), driver: 'Mark M.' }];


/* Ordered stops along the route: [origin, leg1.to, leg2.to, ..., dest]. */
const getStops = (s) => {
  const legs = getLegs(s);
  return [s.origin, ...legs.map((l) => l.to)];
};

/* Which leg is currently in progress, given the shipment's overall pct. */
const activeLegIndex = (s) => {
  const legs = getLegs(s);
  if (s.status === 'delivered' || s.pct >= 100) return legs.length; // all past
  const i = Math.floor(s.pct / 100 * legs.length);
  return Math.max(0, Math.min(i, legs.length - 1));
};

/* Walk a multi-point polyline at parameter t∈[0,1] of the cumulative length. */
const interpolateRoute = (points, t) => {
  if (points.length < 2) return points[0];
  const lens = [];
  let total = 0;
  for (let i = 0; i < points.length - 1; i++) {
    const dy = points[i + 1][0] - points[i][0];
    const dx = points[i + 1][1] - points[i][1];
    const l = Math.hypot(dy, dx);
    lens.push(l);total += l;
  }
  if (total === 0) return points[0];
  const target = total * Math.max(0, Math.min(1, t));
  let acc = 0;
  for (let i = 0; i < lens.length; i++) {
    if (acc + lens[i] >= target) {
      const local = (target - acc) / lens[i];
      return interpolate(points[i], points[i + 1], local);
    }
    acc += lens[i];
  }
  return points[points.length - 1];
};

/* Resolve all stops to [lat,lng] using CITY lookup. */
const routePoints = (s) => getStops(s).map((c) => CITY[c]).filter(Boolean);

/* Split a multi-point polyline at parameter t∈[0,1] of the cumulative length
   into [completedPoints, remainingPoints] sharing the interpolated split
   point. Used to draw completed-route as solid and remaining-route as dotted. */
const splitRouteAt = (points, t) => {
  if (points.length < 2) return [points.slice(), []];
  const tt = Math.max(0, Math.min(1, t));
  if (tt <= 0) return [[], points.slice()];
  if (tt >= 1) return [points.slice(), []];
  const lens = [];
  let total = 0;
  for (let i = 0; i < points.length - 1; i++) {
    const dy = points[i + 1][0] - points[i][0];
    const dx = points[i + 1][1] - points[i][1];
    const l = Math.hypot(dy, dx);
    lens.push(l);total += l;
  }
  if (total === 0) return [points.slice(), []];
  const target = total * tt;
  let acc = 0;
  for (let i = 0; i < lens.length; i++) {
    if (acc + lens[i] >= target) {
      const local = (target - acc) / lens[i];
      const mid = interpolate(points[i], points[i + 1], local);
      return [[...points.slice(0, i + 1), mid], [mid, ...points.slice(i + 1)]];
    }
    acc += lens[i];
  }
  return [points.slice(), []];
};

/* ─── Red Zone organic blob shapes ──────────────────────────────── */
const makeBlob = (centerLat, centerLng, radiusLng, radiusLat, seed, n) => {
  const rng = seeded(seed);
  const raw = [];
  for (let i = 0; i < n; i++) raw.push(0.65 + rng() * 0.7);
  let s = raw;
  for (let pass = 0; pass < 5; pass++) {
    s = s.map((v, i) => s[(i - 1 + n) % n] * 0.25 + v * 0.5 + s[(i + 1) % n] * 0.25);
  }
  return s.map((v, i) => {
    const a = i / n * Math.PI * 2;
    return [centerLat + radiusLat * v * Math.sin(a), centerLng + radiusLng * v * Math.cos(a)];
  });
};

const RED_ZONES = [
makeBlob(45.5, -93, 5, 3, 42, 48), // Minnesota/Wisconsin zone
makeBlob(34.8, -84.5, 3.5, 2, 77, 40), // Tennessee/Georgia zone
makeBlob(47.5, -117, 3, 2, 63, 44), // Washington/Idaho zone
makeBlob(35.5, -108, 3.5, 2.2, 91, 40) // New Mexico/Arizona zone
];
const RED_ZONE_NAMES = ['Upper Midwest', 'Southeast', 'Pacific Northwest', 'Southwest Border'];
const zoneCentroid = (coords) => {
  let cx = 0, cy = 0;
  coords.forEach((p) => { cx += p[0]; cy += p[1]; });
  return [cx / coords.length, cy / coords.length];
};

/* Spider-fan position algorithm: returns [{ dx, dy }] container-pixel offsets
   for `n` items arranged in concentric rings around (0,0). First ring sits at
   ~58px so the popover doesn't collide with the fanned pins; subsequent rings
   step out by 52px each. Works up to ~80 items before rings get tight. */
const computeFanPositions = (n) => {
  const positions = [];
  let placed = 0;
  let ring = 1;
  while (placed < n) {
    const radius = 58 + (ring - 1) * 52;
    const circumference = 2 * Math.PI * radius;
    const ringCap = Math.max(6, Math.floor(circumference / 34));
    const inRing = Math.min(n - placed, ringCap);
    const step = (2 * Math.PI) / inRing;
    const offset = ring % 2 === 0 ? step / 2 : 0;
    for (let i = 0; i < inRing; i++) {
      const a = i * step + offset - Math.PI / 2;
      positions.push({ dx: Math.cos(a) * radius, dy: Math.sin(a) * radius });
      placed++;
    }
    ring++;
  }
  return positions;
};

/* Greedy pixel-space clustering: items within `threshold` px of an existing
   cluster's average center collapse into that cluster. Used for grouping
   nearby in-transit truck markers at the current map zoom. */
const clusterByPixel = (map, items, threshold) => {
  const pts = items.map((it) => ({ ...it, px: map.latLngToContainerPoint(it.pos) }));
  const clusters = [];
  const used = new Array(pts.length).fill(false);
  for (let i = 0; i < pts.length; i++) {
    if (used[i]) continue;
    const group = [pts[i]];
    used[i] = true;
    let cx = pts[i].px.x,cy = pts[i].px.y;
    for (let j = i + 1; j < pts.length; j++) {
      if (used[j]) continue;
      if (Math.hypot(pts[j].px.x - cx, pts[j].px.y - cy) < threshold) {
        group.push(pts[j]);
        used[j] = true;
        cx = group.reduce((s, p) => s + p.px.x, 0) / group.length;
        cy = group.reduce((s, p) => s + p.px.y, 0) / group.length;
      }
    }
    clusters.push(group);
  }
  return clusters;
};

/* ─── Icons ─────────────────────────────────────────────────────── */
// Icons sourced from Lucide (https://lucide.dev/icons/)
const I = {
  mark: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="20" y1="12" y2="12" /><line x1="4" x2="20" y1="6" y2="6" /><line x1="4" x2="20" y1="18" y2="18" /></svg>,
  map: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z" /><path d="M15 5.764v15" /><path d="M9 3.236v15" /></svg>,
  layers: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" /><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12" /><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17" /></svg>,
  pkg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" /><path d="M12 22V12" /><path d="m3.3 7 7.703 4.734a2 2 0 0 0 1.994 0L20.7 7" /><path d="m7.5 4.27 9 5.15" /></svg>,
  chart: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v16a2 2 0 0 0 2 2h16" /><path d="m19 9-5 5-4-4-3 3" /></svg>,
  users: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>,
  shuffle: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m18 14 4 4-4 4" /><path d="m18 2 4 4-4 4" /><path d="M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22" /><path d="M2 6h1.972a4 4 0 0 1 3.6 2.2" /><path d="M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45" /></svg>,
  gear: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>,
  filter: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M7 12h10" /><path d="M11 18h6" /></svg>,
  sort: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m3 16 4 4 4-4" /><path d="M7 20V4" /><path d="m21 8-4-4-4 4" /><path d="M17 4v16" /></svg>,
  plus: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>,
  search: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>,
  truck: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" /><path d="M15 18H9" /><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14" /><circle cx="17" cy="18" r="2" /><circle cx="7" cy="18" r="2" /></svg>,
  alert: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>,
  car: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2" /><circle cx="7" cy="17" r="2" /><path d="M9 17h6" /><circle cx="17" cy="17" r="2" /></svg>,
  cloud: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></svg>,
  maximize: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M21 8V5a2 2 0 0 0-2-2h-3" /><path d="M3 16v3a2 2 0 0 0 2 2h3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" /></svg>,
  zoomIn: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>,
  zoomOut: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14" /></svg>,
  info: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>,
  ext: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>,
  close: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>,
  phone: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2" /><path d="M12 18h.01" /></svg>,
  wifiOff: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h.01" /><path d="M8.5 16.429a5 5 0 0 1 7 0" /><path d="M5 12.859a10 10 0 0 1 5.17-2.69" /><path d="M19 12.859a10 10 0 0 0-2.007-1.523" /><path d="M2 8.82a15 15 0 0 1 4.177-2.643" /><path d="M22 8.82a15 15 0 0 0-11.288-3.764" /><path d="m2 2 20 20" /></svg>,
  refresh: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /><path d="M8 16H3v5" /></svg>,
  alertTri: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>,
  clock: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>,
  crosshair: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><line x1="22" x2="18" y1="12" y2="12" /><line x1="6" x2="2" y1="12" y2="12" /><line x1="12" x2="12" y1="6" y2="2" /><line x1="12" x2="12" y1="22" y2="18" /></svg>,
  file: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>,
  download: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>,
  pin: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12 17v5" /><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" /></svg>,
  mapPin: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" /><circle cx="12" cy="10" r="3" /></svg>,
  bookmark: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" /></svg>,
  trash: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" x2="10" y1="11" y2="17" /><line x1="14" x2="14" y1="11" y2="17" /></svg>,
  pencil: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /><path d="m15 5 4 4" /></svg>,
  calendar: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>,
  chevron: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>,
  lock: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>,
  circleShape: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /></svg>,
  polygonShape: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M10.83 2.38a2 2 0 0 1 2.34 0l8 5.74a2 2 0 0 1 .73 2.25l-3.04 9.26a2 2 0 0 1-1.9 1.37H7.24a2 2 0 0 1-1.9-1.38L2.3 10.36a2 2 0 0 1 .73-2.25z" /></svg>,
  check: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
};

/* Progress-bar truck (dark side view) */
const ProgTruck = () =>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#16110D" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" xmlns="http://www.w3.org/2000/svg">
    <path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" />
    <path d="M15 18H9" />
    <path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14" />
    <circle cx="17" cy="18" r="2" />
    <circle cx="7" cy="18" r="2" />
  </svg>;


/* ─── Filter tabs (status) ──────────────────────────────────────── */
const TABS = [
{ key: 'in_transit', label: 'In Transit' },
{ key: 'pickup', label: 'Pickup' },
{ key: 'out_for_delivery', label: 'Out for Delivery' },
{ key: 'order_created', label: 'Order Created' },
{ key: 'delivered', label: 'Delivered' }];

const DEFAULT_TABS = ['in_transit', 'pickup', 'out_for_delivery'];

/* ─── Needs-attention stats (interactive) ───────────────────────── */
const STATS = [
{ key: 'delayed', label: 'Delayed', value: '8', tone: 'red' },
{ key: 'tracking', label: 'Tracking issues', value: '7', tone: 'red' },
{ key: 'redzone', label: 'Red Zone', value: '4', tone: 'red' },
{ key: 'pod', label: 'POD missing', value: '5', tone: 'red' },
{ key: 'early', label: 'Arriving early', value: '2', tone: 'blue' }];


/* match a shipment against a needs-attention tag */
const STAT_MATCH = {
  delayed: (s) => !!s.isDelayed,
  tracking: (s) => isNoTracking(s),
  redzone: (s) => !!s.redZone,
  pod: (s) => !!s.podMissing,
  early: (s) => !!s.arrivingEarly
};

/* ─── Transport mode (drives the Mode filter) ───────────────────── */
/* Stable per-shipment mode so the Mode pills narrow the list meaningfully. */
const SHIPMENT_MODE = {
  7: 'sea', 11: 'sea', 12: 'sea', 16: 'sea',
  8: 'air', 10: 'air', 13: 'air', 18: 'air'
};
const modeOf = (s) => SHIPMENT_MODE[s.id] || 'ground';

/* ─── Date-range filter ─────────────────────────────────────────── */
/* Anchored to the dataset's "now" so Last 7 / Last 30 / Last 3 months return results. */
const DATE_NOW = new Date('2026-05-26T17:30:00Z');
const sameDay = (a, b) =>
a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
const matchesDate = (s, range, pick) => {
  if (!range && !pick) return true;
  if (!s.appt) return false;
  const d = new Date(s.appt);
  if (pick) return sameDay(d, new Date(pick + 'T00:00:00'));
  if (range === 'all') return true;
  if (range === 'today') return sameDay(d, DATE_NOW);
  const days = range === '7' ? 7 : range === '90' ? 90 : 30;
  const start = new Date(DATE_NOW.getTime() - days * 86400000);
  return d >= start && d <= DATE_NOW;
};

const RETAILER_OPTS = ['Amazon', 'Costco', 'Home Depot', 'Target', 'Walmart'];
SHIPMENTS.forEach((s) => { if (!s.retailer) s.retailer = RETAILER_OPTS[s.id % RETAILER_OPTS.length]; });

const EMPTY_FILTERS = { carrier: [], retailer: [], origin: [], dest: [], transit: [], modes: [], dateRange: 'all', datePick: '' };
const anyFilterActive = (f) =>
!!(f.carrier && f.carrier.length || f.retailer && f.retailer.length || f.origin && f.origin.length || f.dest && f.dest.length || f.transit && f.transit.length || f.modes && f.modes.length || f.dateRange && f.dateRange !== 'all' || f.datePick);

// Unique origin / destination options, derived from the shipment set.
const ORIGIN_OPTS = [...new Set(SHIPMENTS.map((s) => s.origin).filter(Boolean))].sort();
const DEST_OPTS = [...new Set(SHIPMENTS.map((s) => s.dest).filter(Boolean))].sort();
const TRANSIT_OPTS = [...new Set(SHIPMENTS.flatMap((s) => [s.origin, s.dest]).filter(Boolean))].sort();
const CARRIER_OPTS = [...new Set(SHIPMENTS.flatMap((s) => (s.carriers || []).map((c) => c.name)).filter(Boolean))].sort();

const EMPTY_SORT = { groupBy: 'none', sortBy: 'none' };
const riskScore = (s) => {
  if (s.redZone) return 4;
  if (s.isDelayed) return 3;
  if (s.podMissing) return 2;
  if (s.arrivingEarly) return 1;
  return 0;
};
const groupKeyFor = (s, key) => {
  if (key === 'broker') return s.shipper || '—';
  if (key === 'carrier') return primaryCarrier(s);
  if (key === 'consignee') return s.consignee || '—';
  return null;
};
const sortComparator = (key) => {
  if (key === 'appt') return (a, b) => (a.appt || '\uffff').localeCompare(b.appt || '\uffff');
  if (key === 'eta') return (a, b) => (a.eta || '\uffff').localeCompare(b.eta || '\uffff');
  if (key === 'risk') return (a, b) => riskScore(b) - riskScore(a);
  if (key === 'created') return (a, b) => b.id - a.id; // newest first (id is monotonic w/ creation)
  return null;
};

/* ─── Shipment mode icons (ground / air / ocean) ───────────────── */
const MODE_ICON = {
  ground: { src: 'uploads/truck.svg', h: 12 },
  air: { src: 'uploads/plane.svg', h: 16 },
  sea: { src: 'uploads/ship.svg', h: 18 }
};
const modeIconOf = (s) => MODE_ICON[modeOf(s)] || MODE_ICON.ground;

/* ─── Shipment card ─────────────────────────────────────────────── */
function ShipmentCard({ s, isSelected, onSelect, isPinned, onTogglePin, activeStats }) {
  const showTruck = s.pct > 4 && s.pct < 99;
  const rzActive = !!(activeStats && activeStats.includes('redzone') && s.redZone);
  return (
    <div className={`fmx-card${isSelected ? ' is-sel' : ''}`} data-bol={s.bol} onClick={() => onSelect(s.id)}>
      <button
        type="button"
        className={`fmx-card-pin${isPinned ? ' is-pinned' : ''}`}
        title={isPinned ? 'Unpin' : 'Pin to top'}
        onClick={(e) => {e.stopPropagation();onTogglePin(s.id);}}>
        {I.pin}
      </button>
      <div className="fmx-card-top">
        <span className="fmx-card-bol">{s.bol}</span>
        {isNoTracking(s) && <span className="fmx-card-badge is-track">No tracking</span>}
        {rzActive
          ? <span className="fmx-card-badge">Red Zone</span>
          : s.isDelayed && <span className="fmx-card-badge">Major Delay</span>}
      </div>
      <div className="fmx-card-routewrap">
        <div className="fmx-card-route">
          {cityShort(s.origin)}<span className="arrow">→</span>{cityShort(s.dest)}
        </div>
        <div className="fmx-card-sub">
          {STATUS_MAP[s.status].label} • {primaryCarrier(s)} • {getLegs(s).length} {getLegs(s).length === 1 ? 'leg' : 'legs'}
        </div>
      </div>
      {(s.shipper || s.consignee) &&
        <dl className="fmx-card-parties">
          {s.shipper && <><dt>Shipper</dt><dd>{s.shipper}</dd></>}
          {s.consignee && <><dt>Consignee</dt><dd>{s.consignee}</dd></>}
        </dl>
      }
      <div className="fmx-prog">
        <div className="fmx-prog-fill" style={{ width: `${Math.max(s.pct, 2)}%` }} />
        <span className="fmx-prog-truck" style={{ left: `${Math.max(2, Math.min(s.pct, 98))}%` }}>
          <img className="fmx-card-mode" src={modeIconOf(s).src} alt={modeOf(s)} style={{ height: modeIconOf(s).h }} />
        </span>
      </div>
      <div className="fmx-card-meta">
        <span className="upd">Last updated: just now</span>
        {apptFallback(s)
          ? <span className={`eta ${apptFallback(s).cls}`} title={apptFallback(s).past ? 'Appointment date has passed' : 'Appointment date'}>{apptFallback(s).label}</span>
          : <span className="eta">ETA: {fmtDateTime(s.eta)}</span>}
      </div>
    </div>);

}

/* ─── Tracking sources ──────────────────────────────────────────── */
/* Per-shipment trackers — a shipment may have a hardware tracker, a driver
   app, or both. Static data; live status is driven by the Tweaks panel. */
const TRACKERS_BY_ID = {
  1: { hw: [
    { id: 'LOCO-2891', model: 'System Loco', temp: 4.2, battery: 87 },
    { id: 'LOCO-2892', model: 'System Loco', temp: 3.9, battery: 64 }],
    driver: { name: 'Mark M.', lastTs: '2026-05-26T13:42:00Z' }, eld: { id: 'ELD-44120', truck: 'T-4412', provider: 'Samsara', duty: 'driving', speed: 58, driveRemaining: 4.5, lastTs: '2026-05-26T13:44:00Z', handoff: { at: 0.55, prevTruck: 'T-3901', prevDriver: 'John B.', endedTs: '2026-05-26T09:18:00Z' } } },
  2: { hw: { id: 'LOCO-3014', model: 'System Loco', temp: 5.1, battery: 72 }, driver: { name: 'Tania R.', lastTs: '2026-05-26T15:18:00Z' }, eld: { id: 'ELD-44218', truck: 'T-4421', provider: 'Motive', duty: 'driving', speed: 64, driveRemaining: 6.2, lastTs: '2026-05-26T15:19:00Z' } },
  3: { driver: { name: 'Erik P.', lastTs: '2026-05-26T08:01:00Z' } },
  4: { hw: { id: 'LOCO-3201', model: 'System Loco', temp: 6.4, battery: 24 } },
  5: { hw: { id: 'LOCO-2750', model: 'System Loco', temp: 12.8, battery: 91 }, driver: { name: 'Sandra L.', lastTs: '2026-05-26T16:02:00Z' }, eld: { id: 'ELD-50091', truck: 'T-5009', provider: 'Geotab', duty: 'on_duty', speed: 0, driveRemaining: 3.1, lastTs: '2026-05-26T16:01:00Z' } },
  6: { driver: { name: 'James K.', lastTs: '2026-05-20T11:55:00Z' } },
  7: { hw: { id: 'LOCO-1808', model: 'System Loco', temp: 5.6, battery: 14 } },
  8: { hw: { id: 'LOCO-3322', model: 'System Loco', temp: 4.9, battery: 81 }, driver: { name: 'Marco D.', lastTs: '2026-05-26T15:47:00Z' }, eld: { id: 'ELD-21034', truck: 'T-2103', provider: 'Samsara', duty: 'driving', speed: 52, driveRemaining: 2.0, lastTs: '2026-05-26T15:46:00Z' } },
  9: { driver: { name: 'Lisa N.', lastTs: '2026-05-26T09:15:00Z' } },
  10: { hw: { id: 'LOCO-2104', model: 'System Loco', temp: 5.2, battery: 66 } },
  11: { hw: { id: 'LOCO-2945', model: 'System Loco', temp: 7.8, battery: 49 }, driver: { name: 'Carlos M.', lastTs: '2026-05-26T14:30:00Z' }, eld: { id: 'ELD-30401', truck: 'T-3040', provider: 'Motive', duty: 'driving', speed: 61, driveRemaining: 5.4, lastTs: '2026-05-26T14:31:00Z' } },
  12: { driver: { name: 'Olivia T.', lastTs: '2026-05-10T10:24:00Z' } },
  13: { hw: { id: 'LOCO-3608', model: 'System Loco', temp: 6.1, battery: 38 } },
  14: { driver: { name: 'Ben S.', lastTs: '2026-05-26T11:30:00Z' }, eld: { id: 'ELD-77820', truck: 'T-7782', provider: 'KeepTruckin', duty: 'driving', speed: 47, driveRemaining: 1.2, lastTs: '2026-05-26T11:32:00Z' } },
  15: { driver: { name: 'Priya V.', lastTs: '2026-05-26T13:45:00Z' } },
  16: { hw: { id: 'LOCO-1920', model: 'System Loco', temp: 4.7, battery: 55 } },
  17: { hw: { id: 'LOCO-2210', model: 'System Loco', temp: 5.3, battery: 78 } },
  18: { hw: { id: 'LOCO-3411', model: 'System Loco', temp: 8.2, battery: 60 }, driver: { name: 'Adam W.', lastTs: '2026-05-26T14:55:00Z' }, eld: { id: 'ELD-66012', truck: 'T-6601', provider: 'Geotab', duty: 'driving', speed: 55, driveRemaining: 7.0, lastTs: '2026-05-26T14:56:00Z' } },
  19: { hw: { id: 'LOCO-2887', model: 'System Loco', temp: 5.8, battery: 30 } },
  20: { driver: { name: 'Jenna Q.', lastTs: null } }
};
const trackersOf = (s) => TRACKERS_BY_ID[s.id] || {};
/* Always returns an array of hw trackers (data may store either a single object or an array). */
const hwListOf = (s) => {
  const raw = trackersOf(s).hw;
  if (!raw) return [];
  return Array.isArray(raw) ? raw : [raw];
};

/* Coordinate offsets so the tracker pins don't sit exactly on top of each other */
const HW_OFFSET = [0.35, -0.55];
const DRIVER_OFFSET = [-0.35, 0.55];
const ELD_OFFSET = [0.55, 0.45];

/* Status presentation lookup */
const HW_STATUS = {
  live: { label: 'Live', cls: 'is-live' },
  alert: { label: 'Alert', cls: 'is-alert' },
  exception: { label: 'Exception', cls: 'is-exception' }
};
const ELD_STATUS = {
  live: { label: 'Live', cls: 'is-live' },
  inactive: { label: 'Inactive', cls: 'is-inactive' },
  offline: { label: 'Offline', cls: 'is-offline' }
};
const DRIVER_STATUS = {
  live: { label: 'Live', cls: 'is-live' },
  inactive: { label: 'Inactive', cls: 'is-inactive' },
  ended: { label: 'Ended', cls: 'is-offline' }
};

/* Tweak defaults — global demo state for tracker statuses */
const TWEAK_DEFAULTS = {
  hwStatus: 'live',
  driverStatus: 'live',
  eldStatus: 'live',
  // Loading simulation: how long the shipment fetch takes after a card click.
  // 'default' keeps the shipped behavior (random 900–1500ms).
  fetchLatency: 'default'
};

/* Fetch-latency options for the loading simulation tweak. `null` = never resolves. */
const FETCH_LATENCY = {
  default: () => 900 + Math.floor(Math.random() * 600),
  instant: () => 0,
  slow: () => 3000,
  crawl: () => 8000,
  stuck: () => null
};

/* Resolves which trackers are 'live' under current tweak state for a shipment */
const liveTrackers = (s, hwStatus, driverStatus, eldStatus) => {
  const t = trackersOf(s);
  return {
    hw: hwListOf(s).length > 0 && hwStatus === 'live',
    driver: !!t.driver && driverStatus === 'live',
    eld: !!t.eld && eldStatus === 'live'
  };
};

/* A shipment counts as "no tracking" if neither tracker is live.
   Delivered shipments retain a completed driver-history trace, so they're
   never "no tracking" — the concept only applies to in-flight loads. */
const isNoTracking = (s, hwStatus, driverStatus, eldStatus) => {
  if (s && s.status === 'delivered') return false;
  if (hwStatus === undefined) hwStatus = window.__fmxTweaks && window.__fmxTweaks.hwStatus || TWEAK_DEFAULTS.hwStatus;
  if (driverStatus === undefined) driverStatus = window.__fmxTweaks && window.__fmxTweaks.driverStatus || TWEAK_DEFAULTS.driverStatus;
  if (eldStatus === undefined) eldStatus = window.__fmxTweaks && window.__fmxTweaks.eldStatus || TWEAK_DEFAULTS.eldStatus;
  const live = liveTrackers(s, hwStatus, driverStatus, eldStatus);
  return !live.hw && !live.driver && !live.eld;
};

/* Formatting helpers */
const fmtTemp = (c) => `${c.toFixed(1)}°C`;
const fmtBatt = (p) => `${p}%`;
const battCls = (p) => p <= 20 ? 'is-low' : p <= 49 ? 'is-mid' : p <= 69 ? 'is-warn' : 'is-ok';
const fmtCoord = (s) => {
  const o = CITY[s.origin],d = CITY[s.dest];
  if (!o || !d) return '—';
  const t = Math.max(0.02, Math.min(0.98, (s.pct || 50) / 100));
  const [lat, lon] = interpolate(o, d, t);
  return `${lat.toFixed(3)}, ${lon.toFixed(3)}`;
};
const fmtAgo = (iso) => {
  if (!iso) return 'No signal';
  return fmtDateTime(iso);
};

/* ─── Tracker cards ─────────────────────────────────────────────── */
function HwTrackerCard({ s, hw, hwStatus, onFocus }) {
  const st = HW_STATUS[hwStatus] || HW_STATUS.live;
  const bcls = battCls(hw.battery);
  const isLive = hwStatus === 'live';
  const clickable = typeof onFocus === 'function';
  return (
    <div
      className="fmx-tsrc is-hw"
      role={clickable ? 'button' : undefined}
      tabIndex={clickable ? 0 : undefined}
      onClick={clickable ? onFocus : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onFocus(); } } : undefined}
      style={clickable ? { cursor: 'pointer' } : undefined}
      title={clickable ? 'Zoom to tracker on map' : undefined}>
      <div className="fmx-tsrc-head">
        <span className="fmx-tsrc-ico">{I.crosshair}</span>
        <div className="fmx-tsrc-body">
          <span className="fmx-tsrc-lbl">Hardware tracker</span>
          <span className="fmx-tsrc-sub">{hw.model} · {hw.id}</span>
        </div>
        <span className={`fmx-tsrc-pill ${st.cls}`}><span className="dot" />{st.label}</span>
      </div>
      <div className="fmx-tsrc-metrics">
        <div className="fmx-tsrc-m">
          <span className="k">Temperature</span>
          <span className={`v${hw.temp > 10 ? ' is-warn' : ''}`}>{fmtTemp(hw.temp)}</span>
        </div>
        <div className="fmx-tsrc-m">
          <span className="k">Battery</span>
          <span className="v">
            <span className={`fmx-tsrc-batt ${bcls}`}>
              <span className="bar"><span className="fill" style={{ width: `${Math.max(0, Math.min(100, hw.battery))}%`, backgroundColor: hw.battery <= 20 ? '#DC2626' : hw.battery <= 49 ? '#F97316' : hw.battery <= 69 ? '#EAB308' : '#10B981' }} /></span>
              <span className={hw.battery <= 20 ? 'is-bad' : ''}>{fmtBatt(hw.battery)}</span>
            </span>
          </span>
        </div>
      </div>
    </div>);

}

/* ─── Tracker cards ─────────────────────────────────────────────── */
const ELD_DUTY_LABEL = {
  driving: 'Driving',
  on_duty: 'On Duty',
  off_duty: 'Off Duty',
  sleeper: 'Sleeper Berth'
};
function EldTrackerCard({ eld, eldStatus, eta, onFocus }) {
  const st = ELD_STATUS[eldStatus] || ELD_STATUS.live;
  const isLive = eldStatus === 'live';
  const clickable = typeof onFocus === 'function';
  const pingLabel = isLive && eld.lastTs ? fmtAgo(eld.lastTs) : '—';
  return (
    <div
      className="fmx-tsrc is-eld"
      role={clickable ? 'button' : undefined}
      tabIndex={clickable ? 0 : undefined}
      onClick={clickable ? onFocus : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onFocus(); } } : undefined}
      style={clickable ? { cursor: 'pointer' } : undefined}
      title={clickable ? 'Zoom to ELD on map' : undefined}>
      <div className="fmx-tsrc-head">
        <span className="fmx-tsrc-ico">{I.truck}</span>
        <div className="fmx-tsrc-body">
          <span className="fmx-tsrc-lbl">ELD</span>
          <span className="fmx-tsrc-sub">{eld.provider} · Truck {eld.truck}</span>
          <span className="fmx-tsrc-ping">
            ETA · {eta ? fmtDateTime(eta) : '—'}
            <span
              className="fmx-tsrc-info"
              tabIndex={0}
              aria-label={`Last ping: ${pingLabel}`}
              data-tip={`Last ping · ${pingLabel}`}>
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" />
              </svg>
            </span>
          </span>
        </div>
        <span className={`fmx-tsrc-pill ${st.cls}`}><span className="dot" />{st.label}</span>
      </div>
    </div>);

}

function DriverTrackerCard({ driver, driverStatus, onFocus }) {
  const st = DRIVER_STATUS[driverStatus] || DRIVER_STATUS.live;
  const clickable = typeof onFocus === 'function';
  return (
    <div
      className="fmx-tsrc is-driver"
      role={clickable ? 'button' : undefined}
      tabIndex={clickable ? 0 : undefined}
      onClick={clickable ? onFocus : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onFocus(); } } : undefined}
      style={clickable ? { cursor: 'pointer' } : undefined}
      title={clickable ? 'Zoom to driver on map' : undefined}>
      <div className="fmx-tsrc-head">
        <span className="fmx-tsrc-ico">{I.phone}</span>
        <div className="fmx-tsrc-body">
          <span className="fmx-tsrc-lbl">Driver app</span>
          <span className="fmx-tsrc-sub">{driver.name}</span>
          <span className="fmx-tsrc-ping">Last ping · {fmtAgo(driver.lastTs)}</span>
        </div>
        <span className={`fmx-tsrc-pill ${st.cls}`}><span className="dot" />{st.label}</span>
      </div>
    </div>);

}

/* ─── Delivered driver history ─────────────────────────────────── */
/* Delivered shipments retain the driver-app trace as *history*: a completed
   breadcrumb from origin → destination, a driver marker planted at the drop,
   and a POD upload point. The POD sometimes lands exactly at the destination
   (uploaded on arrival) and sometimes drifts away — drivers occasionally
   forget and upload later, from wherever they happen to be. Data below is
   deterministically derived from the shipment id so every delivered card is
   stable across reloads. */
const DELIV_DRIVER_NAMES = [
  'Miguel A.', 'Kevin O.', 'Rachel B.', 'Diego H.', 'Tomás F.',
  'Angela K.', 'Marcus L.', 'Priya N.', 'Chen W.', 'Sofía R.',
  'Nate H.', 'Amir S.', 'Delia P.', 'Julian G.', 'Rose M.'];

const DELIV_DRIVER_NAMED = {
  21: 'Diego H.',
  22: 'Rachel B.',
  23: 'Tomás F.'
};

const deliveredDriverOf = (s) => {
  if (!s || s.status !== 'delivered') return null;
  // Any hand-authored driver in TRACKERS_BY_ID wins.
  const explicit = TRACKERS_BY_ID[s.id] && TRACKERS_BY_ID[s.id].driver;
  if (explicit) return null; // fall back to the live DriverTrackerCard
  const rand = seeded(s.id * 41 + 3);
  const name = DELIV_DRIVER_NAMED[s.id] ||
    DELIV_DRIVER_NAMES[Math.floor(rand() * DELIV_DRIVER_NAMES.length)];
  const arrivedTs = s.eta || '2026-05-23T15:00:00Z';
  const arrMs = new Date(arrivedTs).getTime();
  // POD state — some are still outstanding (matches s.podMissing), most are
  // uploaded at the destination, a slice were uploaded miles away.
  const missing = s.podMissing === true;
  let podAtDest = true;
  let podOffset = [0, 0];
  let podTs = null;
  if (!missing) {
    const roll = rand();
    // ~25% of uploads happened away from the destination.
    if (roll < 0.25) {
      podAtDest = false;
      const angle = rand() * Math.PI * 2;
      const dist = 0.25 + rand() * 0.55; // ~15–45 mi
      podOffset = [Math.sin(angle) * dist, Math.cos(angle) * dist];
    }
    const delayMin = podAtDest ?
      Math.round(4 + rand() * 55) :
      Math.round(45 + rand() * 320);
    podTs = new Date(arrMs + delayMin * 60000).toISOString();
  }

  // Ping trail — 9 fixed events walking back from the arrival timestamp,
  // then POD upload at the end. Timings are seeded so each shipment gets a
  // realistic, stable cadence (pickup drive, dwell, then destination drive).
  const min = (n) => Math.round(n) * 60000;
  const t8 = arrMs;                                                // Arrived at destination
  const t7 = t8 - min(35 + rand() * 45);                           // ping ~35–80 min prior
  const t6 = t7 - min(85 + rand() * 70);                           // ping ~85–155 min prior
  const t5 = t6 - min(8 + rand() * 22);                            // BOL uploaded (just before departure)
  const t4 = t5 - min(18 + rand() * 30);                           // Arrived at pickup (dwell 18–48 min)
  const t3 = t4 - min(45 + rand() * 40);                           // ping en route to pickup
  const t2 = t3 - min(55 + rand() * 55);                           // ping en route to pickup
  const t1 = t2 - min(12 + rand() * 20);                           // Driver opened link
  const pingTrail = [
    { key: 'opened',    label: 'Driver opened link',       sub: 'Tracking session started', ts: new Date(t1).toISOString(), kind: 'start' },
    { key: 'route_p_a', label: 'En route to pickup',       sub: null,                       ts: new Date(t2).toISOString(), kind: 'ping' },
    { key: 'route_p_b', label: 'En route to pickup',       sub: null,                       ts: new Date(t3).toISOString(), kind: 'ping' },
    { key: 'arr_p',     label: 'Arrived at pickup',        sub: null,                       ts: new Date(t4).toISOString(), kind: 'arrive' },
    { key: 'bol',       label: 'Signed BOL uploaded',      sub: null,                       ts: new Date(t5).toISOString(), kind: 'doc' },
    { key: 'route_d_a', label: 'En route to destination',  sub: null,                       ts: new Date(t6).toISOString(), kind: 'ping' },
    { key: 'route_d_b', label: 'En route to destination',  sub: null,                       ts: new Date(t7).toISOString(), kind: 'ping' },
    { key: 'arr_d',     label: 'Arrived at destination',   sub: null,                       ts: new Date(t8).toISOString(), kind: 'arrive' },
    missing ?
      { key: 'pod', label: 'POD upload',   sub: 'Awaiting driver',                        ts: null,               kind: 'pending', dashed: true } :
      { key: 'pod', label: 'POD uploaded', sub: podAtDest ? null : `${fmtOffsetMi(podOffset)} from destination`, ts: podTs, kind: 'doc',     dashed: true }
  ];

  return { name, arrivedTs, podTs, podAtDest, podOffset, missing, pingTrail };
};

/* Delivered-ELD synthesis — for delivered shipments without an explicit ELD in
   TRACKERS_BY_ID, generate a stable truck + provider + location ping trail
   (machine-reported every ~40 min, 6 pings between pickup and arrival). */
const DELIV_ELD_PROVIDERS = ['Samsara', 'Motive', 'Geotab', 'KeepTruckin'];
const ODFL_STATUSES = [
'Picked up',
'Arrived at origin SVC',
'Departed origin SVC',
'In transit',
'Arrived at destination SVC',
'Out for delivery'];
const deliveredEldOf = (s) => {
  if (!s || s.status !== 'delivered') return null;
  const explicit = TRACKERS_BY_ID[s.id] && TRACKERS_BY_ID[s.id].eld;
  if (explicit) return null;
  const rand = seeded(s.id * 53 + 11);
  const provider = DELIV_ELD_PROVIDERS[Math.floor(rand() * DELIV_ELD_PROVIDERS.length)];
  const truckNum = 2000 + Math.floor(rand() * 7000);
  const truck = `T-${truckNum}`;
  const id = `ELD-${10000 + Math.floor(rand() * 89999)}`;
  const arrivedTs = s.eta || '2026-05-23T15:00:00Z';
  const arrMs = new Date(arrivedTs).getTime();
  // ELDs report on their own cadence — 6 location pings spaced ~40 min apart,
  // ending a few min before the driver's "arrived" event. Positions along
  // the recorded route (t = 0..1) are offset from the driver-app pings so
  // both trails stay readable on the map.
  const stops = [
    { t: 0.08, dt: -260, speed: 58 },
    { t: 0.22, dt: -220, speed: 62 },
    { t: 0.38, dt: -175, speed: 61 },
    { t: 0.54, dt: -125, speed: 64 },
    { t: 0.70, dt: -75,  speed: 60 },
    { t: 0.86, dt: -20,  speed: 45 }];
  const pingTrail = stops.map((p, i) => ({
    key: `eld-${i}`,
    t: p.t,
    speed: p.speed,
    // Old Dominion reports EDI status events rather than raw location pings,
    // so ODFL loads carry a human status description per stop.
    status: primaryCarrier(s) === 'Old Dominion' ? ODFL_STATUSES[i] : null,
    ts: new Date(arrMs + p.dt * 60000).toISOString()
  }));
  const lastPingTs = pingTrail[pingTrail.length - 1].ts;
  return { provider, truck, id, arrivedTs, lastPingTs, pingTrail };
};

function DeliveredEldCard({ ded, onFocus }) {
  const clickable = typeof onFocus === 'function';
  return (
    <div
      className="fmx-tsrc is-eld is-delivered"
      role={clickable ? 'button' : undefined}
      tabIndex={clickable ? 0 : undefined}
      onClick={clickable ? onFocus : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onFocus(); } } : undefined}
      style={clickable ? { cursor: 'pointer' } : undefined}
      title={clickable ? 'Show ELD ping trail on map' : undefined}>
      <div className="fmx-tsrc-head">
        <span className="fmx-tsrc-ico">{I.truck}</span>
        <div className="fmx-tsrc-body">
          <span className="fmx-tsrc-lbl">ELD</span>
          <span className="fmx-tsrc-sub">{ded.provider} · Truck {ded.truck}</span>
          <span className="fmx-tsrc-ping">Last ping · {fmtDateTime(ded.lastPingTs)}</span>
        </div>
        <span className="fmx-tsrc-pill is-ended"><span className="dot" />Ended</span>
      </div>
    </div>);

}

// Rough conversion — 1° lat/lng ≈ 69 miles in the continental US.
const DEG_TO_MI = 55;
const fmtOffsetMi = (off) => {
  const mi = Math.hypot(off[0], off[1]) * DEG_TO_MI;
  if (mi < 1) return `${(mi * 5280).toFixed(0)} ft`;
  return `${mi.toFixed(1)} mi`;
};

function DeliveredDriverCard({ dd, onFocus }) {
  const clickable = typeof onFocus === 'function';
  return (
    <div
      className="fmx-tsrc is-driver is-delivered"
      role={clickable ? 'button' : undefined}
      tabIndex={clickable ? 0 : undefined}
      onClick={clickable ? onFocus : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onFocus(); } } : undefined}
      style={clickable ? { cursor: 'pointer' } : undefined}
      title={clickable ? 'Show driver history on map' : undefined}>
      <div className="fmx-tsrc-head">
        <span className="fmx-tsrc-ico">{I.phone}</span>
        <div className="fmx-tsrc-body">
          <span className="fmx-tsrc-lbl">Driver app</span>
          <span className="fmx-tsrc-sub">{dd.name}</span>
          <span className="fmx-tsrc-ping">Arrived · {fmtDateTime(dd.arrivedTs)}</span>
        </div>
        <span className="fmx-tsrc-pill is-ended"><span className="dot" />Ended</span>
      </div>
    </div>);

}

/* ─── Timeline tab ──────────────────────────────────────────────── */
/* Fixed demo "now" matching the shipment dataset's timeframe. */
const NOW_TL = new Date('2026-05-26T17:30:00Z').getTime();

/* How far along the status flow a shipment is — drives which milestones
   have actually occurred. */
const STATUS_RANK = {
  order_created: 0, pickup: 1, in_transit: 2,
  out_for_delivery: 3, delivered: 4, billing: 5, invoiced: 6
};

const agoLabel = (ms) => {
  const m = Math.round((NOW_TL - 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 ${m % 60}m ago`;
  return `${Math.floor(h / 24)}d ago`;
};

/* Build the status timeline for a specific shipment, latest event first.
   Every event reflects THIS shipment's status, route cities, and carrier;
   timestamps are seeded per shipment id so each one varies realistically. */
const buildTimeline = (s) => {
  const rank = STATUS_RANK[s.status] != null ? STATUS_RANK[s.status] : 2;
  const carrier = primaryCarrier(s);
  const oCity = s.origin.split(',')[0];
  const dCity = s.dest.split(',')[0];
  const stopCities = getStops(s).map((c) => c.split(',')[0]);
  const hubCity = stopCities.length > 2 ? stopCities[1] : null;
  const currentLabel = s.isDelayed ? 'At hub' : STATUS_MAP[s.status].label;

  // En-route / current-location city, kept to this shipment's own route.
  const transitLoc = hubCity ? `${hubCity} hub · ${carrier}` : `En route to ${dCity} · ${carrier}`;
  const capLoc =
  s.isDelayed ? `${hubCity || dCity} hub · ${carrier}` :
  s.status === 'out_for_delivery' ? `${dCity} · ${carrier}` :
  `${dCity} · ${carrier}`;

  // Display order, top (most recent) first.
  const display = [];
  // Current-status cap — only when it differs from the natural top milestone
  // (i.e. beyond In Transit, or the "At hub" delayed sub-state).
  if (rank >= 3 || rank === 2 && s.isDelayed) {
    display.push({ title: currentLabel, tone: 'orange', loc: capLoc });
  }
  if (rank >= 2) display.push({ title: 'In Transit', tone: 'orange', loc: transitLoc });
  if (rank >= 1) display.push({ title: 'Pickup', tone: 'orange', loc: `${oCity} · ${carrier}` });
  display.push({ title: 'Order Created', tone: 'gray', loc: `${oCity} · ${s.shipper}` });

  // Seeded, chronological timestamps walking back from "now".
  const rand = seeded(s.id + 7);
  let t = NOW_TL - Math.round(10 + rand() * 50) * 60000;
  display[0].time = t;
  for (let i = 1; i < display.length; i++) {
    const cur = display[i].title;
    let gap;
    if (cur === 'Order Created') gap = 1080 + rand() * 900; // 18–33h
    else if (cur === 'Pickup') gap = 240 + rand() * 360; //  4–10h
    else gap = 150 + rand() * 210; //  2.5–6h
    t -= Math.round(gap) * 60000;
    display[i].time = t;
  }
  return display;
};

/* ─── Shipment detail panel ───────────────────────────────────── */
function ShipmentDetail({ s, onClose, onViewOnMap, hwStatus, driverStatus, eldStatus, mapApiRef }) {
  const [tab, setTab] = useState('details');
  const [tracking, setTracking] = useState(false);
  const [actionsOpen, setActionsOpen] = useState(false);
  // Document currently open in the full-screen viewer (null = closed).
  const [docPreview, setDocPreview] = useState(null);
  // Zoom the map to the driver's current breadcrumb position. Mirrors the
  // offset math used by MapView so the focus lands exactly on the driver pin.
  const focusDriver = React.useCallback(() => {
    const map = mapApiRef && mapApiRef.current;
    if (!map || !s) return;
    const pts = routePoints(s);
    if (!pts || pts.length < 2) return;
    const pct = (s.pct || 50) / 100;
    const cur = interpolateRoute(pts, Math.max(0.02, Math.min(0.98, pct)));
    const pos = [cur[0] + DRIVER_OFFSET[0], cur[1] + DRIVER_OFFSET[1]];
    const target = Math.max(map.getZoom() || 6, 11);
    map.flyTo(pos, target, { duration: 0.6 });
  }, [s, mapApiRef]);
  // Zoom to the hardware tracker's current on-map position (uses HW_OFFSET).
  const focusHw = React.useCallback(() => {
    const map = mapApiRef && mapApiRef.current;
    if (!map || !s) return;
    const pts = routePoints(s);
    if (!pts || pts.length < 2) return;
    const pct = (s.pct || 50) / 100;
    const cur = interpolateRoute(pts, Math.max(0.02, Math.min(0.98, pct)));
    const pos = [cur[0] + HW_OFFSET[0], cur[1] + HW_OFFSET[1]];
    const target = Math.max(map.getZoom() || 6, 11);
    map.flyTo(pos, target, { duration: 0.6 });
  }, [s, mapApiRef]);
  // Zoom to the ELD's current on-map position (its truck pin uses ELD_OFFSET).
  const focusEld = React.useCallback(() => {
    const map = mapApiRef && mapApiRef.current;
    if (!map || !s) return;
    const pts = routePoints(s);
    if (!pts || pts.length < 2) return;
    const pct = (s.pct || 50) / 100;
    const cur = interpolateRoute(pts, Math.max(0.02, Math.min(0.98, pct)));
    const pos = [cur[0] + ELD_OFFSET[0], cur[1] + ELD_OFFSET[1]];
    const target = Math.max(map.getZoom() || 6, 11);
    map.flyTo(pos, target, { duration: 0.6 });
  }, [s, mapApiRef]);
  // For delivered shipments: frame the destination + POD upload point (which
  // may sit some distance away when the driver forgot to upload on arrival).
  const focusDelivered = React.useCallback(() => {
    const map = mapApiRef && mapApiRef.current;
    if (!map || !s) return;
    const d = CITY[s.dest];
    if (!d) return;
    const dd = deliveredDriverOf(s);
    if (dd && !dd.missing && !dd.podAtDest) {
      const pod = [d[0] + dd.podOffset[0], d[1] + dd.podOffset[1]];
      map.flyToBounds([d, pod], { padding: [90, 90], duration: 0.6, maxZoom: 12 });
    } else {
      const target = Math.max(map.getZoom() || 6, 11);
      map.flyTo(d, target, { duration: 0.6 });
    }
  }, [s, mapApiRef]);
  const asideRef = useRef(null);
  const scrollTimer = useRef(null);
  const handleScroll = () => {
    const el = asideRef.current;
    if (!el) return;
    el.classList.add('is-scrolling');
    clearTimeout(scrollTimer.current);
    scrollTimer.current = setTimeout(() => el.classList.remove('is-scrolling'), 700);
  };
  useEffect(() => () => clearTimeout(scrollTimer.current), []);
  useEffect(() => {setTracking(false);}, [s && s.id]);
  if (!s) return null;

  return (
    <aside className="fmx-detail">
      <div className="fmx-detail-head">
        <div className="fmx-detail-row1">
          <span className="fmx-detail-bol">{s.bol}</span>
          <span className="fmx-detail-ext" style={{ pointerEvents: 'none' }}>{I.ext}</span>
          <button className="fmx-detail-close" onClick={onClose} aria-label="Close">{I.close}</button>
        </div>
        <div className="fmx-detail-row2">
          <span className="fmx-detail-status">{s.isDelayed ? 'At hub' : STATUS_MAP[s.status].label}</span>
          <span className="fmx-status-pill">{STATUS_MAP[s.status].label}</span>
        </div>
        <div className="fmx-detail-route">
          {cityShort(s.origin)}<span className="arrow">→</span>{cityShort(s.dest)}
        </div>
      </div>

      {(() => {
        const fmtDurAbs = (ms) => {
          const totalMin = Math.max(1, Math.round(Math.abs(ms) / 60000));
          const d = Math.floor(totalMin / (60 * 24));
          const h = Math.floor((totalMin % (60 * 24)) / 60);
          const m = totalMin % 60;
          if (d > 0) return `${d} day${d === 1 ? '' : 's'} ${h} hour${h === 1 ? '' : 's'}`;
          if (h > 0) return `${h} hour${h === 1 ? '' : 's'} ${m} minute${m === 1 ? '' : 's'}`;
          return `${m} minute${m === 1 ? '' : 's'}`;
        };
        const apptMs = s.appt ? new Date(s.appt).getTime() : null;
        const etaMs = s.eta ? new Date(s.eta).getTime() : null;

        const issues = [];
        if (s.podMissing) issues.push({
          icon: I.file,
          title: 'POD missing',
          desc: apptMs ? `No POD since ${fmtDurAbs(NOW_TL - apptMs)} of arrival` : 'Proof of delivery has not been received yet.'
        });
        if (s.arrivingEarly) issues.push({
          icon: I.clock,
          title: 'Arriving Early',
          desc: apptMs && etaMs ? `Arriving ${fmtDurAbs(apptMs - etaMs)} before scheduled time` : 'Arriving before scheduled time.'
        });
        if (s.isDelayed) {
          const isDelivered = s.status === 'delivered' || s.pct >= 100;
          let delayDesc;
          if (apptMs && etaMs && etaMs > apptMs) {
            const late = fmtDurAbs(etaMs - apptMs);
            delayDesc = isDelivered ? `Late by ${late}` : `Delayed by ${late}`;
          } else if (isDelivered && apptMs) {
            delayDesc = `Late by ${fmtDurAbs(NOW_TL - apptMs)}`;
          } else {
            delayDesc = `Appointment: ${fmtDateTime(s.appt)}`;
          }
          issues.push({
            icon: I.alertTri,
            title: 'Critical Delay',
            desc: delayDesc
          });
        }
        const mi = apptFallback(s);
        if (mi && mi.past) issues.push({
          icon: I.alertTri,
          title: 'Appointment date passed',
          desc: `Appt: ${mi.when}`
        });
        if (mi && !mi.past) issues.push({
          icon: I.clock,
          title: 'Appointment coming up',
          desc: `Appt: ${mi.when} — plan the pickup to meet it`,
          soft: true
        });
        if (s.redZone) issues.push({
          icon: I.crosshair,
          title: 'Red Zone entry',
          desc: `Entered Red Zone ${'ABCD'[s.id % 4]} ${15 + s.id % 4 * 10} mins ago`
        });
        if (isNoTracking(s, hwStatus, driverStatus, eldStatus)) issues.push({
          icon: I.wifiOff,
          title: 'No Tracking',
          desc: 'No location data received from driver for 3 hours.'
        });
        if (issues.length === 0) return null;
        const n = issues.filter((x) => !x.soft).length;
        return (
          <div className={`fmx-issues${n === 0 ? ' is-soft' : ''}`}>
            <div className="fmx-issues-title">{n === 0 ? 'Heads up' : `${n} ${n === 1 ? 'issue needs' : 'issues need'} attention`}</div>
            <div className="fmx-issues-list">
              {issues.map((iss, i) =>
              <div key={i} className={`fmx-issue${iss.soft ? ' is-soft' : ''}`}>
                  <span className="fmx-issue-ico">{iss.icon}</span>
                  <div className="fmx-issue-body">
                    <span className="fmx-issue-t">{iss.title}</span>
                    <span className="fmx-issue-s">{iss.desc}</span>
                  </div>
                </div>
              )}
            </div>
          </div>);

      })()}

      <div className="fmx-detail-tabs">
        {[['details', 'Details'], ['timeline', 'Timeline'], ['documents', 'Documents']].map(([k, lbl]) =>
        <button key={k} className={`fmx-detail-tab${tab === k ? ' is-on' : ''}`} onClick={() => setTab(k)} style={{ fontSize: "11px" }}>{lbl}</button>
        )}
      </div>

      <div className="fmx-detail-scroll" ref={asideRef} onScroll={handleScroll}>
      {tab === 'timeline' ?
        <div className="fmx-detail-section">
        <div className="fmx-detail-h">Timeline</div>
        <div className="fmx-tl">
          {buildTimeline(s).map((ev, i) =>
            <div key={i} className={`fmx-tl-event${i === 0 ? ' is-head' : ''}`}>
              <span className={`fmx-tl-dot is-${ev.tone}`} />
              <div className="fmx-tl-body">
                <span className="fmx-tl-name">{ev.title}</span>
                <span className="fmx-tl-meta">{ev.loc}</span>
              </div>
              <div className="fmx-tl-right">
                <span className="fmx-tl-when">{fmtDateTime(new Date(ev.time).toISOString())}</span>
                <span className="fmx-tl-ago">{agoLabel(ev.time)}</span>
              </div>
            </div>
            )}
        </div>
      </div> :
        tab === 'documents' ?
        (() => {
          const podFile = `BOL-${s.id * 8675309 % 90000000 + 10000000}.pdf`;
          const bolFile = `${s.bol}.pdf`;
          const docs = [
            { kind: 'bol', name: 'BOL', file: bolFile, sub: bolFile },
            ...(!s.podMissing ? [{ kind: 'pod', name: 'Proof of Delivery', file: podFile, sub: podFile }] : [])
          ];
          return (
        <div className="fmx-detail-section">
        <div className="fmx-detail-h">Documents</div>
        {docs.map((d) =>
          <div key={d.kind} className="fmx-doc is-clickable" role="button" tabIndex={0}
            onClick={() => setDocPreview({ ...d, s })}
            onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setDocPreview({ ...d, s }); } }}>
          <span className="fmx-doc-ico">{I.file}</span>
          <div className="fmx-doc-body">
            <span className="fmx-doc-name">{d.name}</span>
            <span className="fmx-doc-sub">{d.sub}</span>
          </div>
          <button type="button" className="fmx-doc-dl" aria-label="Download" onClick={(e) => e.stopPropagation()}>{I.download}</button>
        </div>
          )}
        {s.podMissing &&
          <div className="fmx-doc-note">POD not yet available</div>
          }
        {docPreview && <DocViewer doc={docPreview} onClose={() => setDocPreview(null)} />}
      </div>
          );
        })() :
        <React.Fragment>
      <div className="fmx-detail-section">
        <div className="fmx-detail-h">Route Details</div>
        {(() => {
              const legs = getLegs(s);
              const stops = getStops(s);
              const active = activeLegIndex(s);
              const noTracking = isNoTracking(s, hwStatus, driverStatus, eldStatus);
              const isDelivered = s.status === 'delivered' || s.pct >= 100 && active >= stops.length - 1;
              const stopKind = (i) => {
                if (isDelivered) return 'past';
                if (i < active) return 'past';
                if (i === active) return 'current';
                return 'future';
              };
              const mi = apptFallback(s);
              const timeLabel = (i, isLast, kind) => {
                // Past stops report the actual completed event, not a forward-looking ETA.
                if (i === 0) return `${kind === 'past' ? 'Picked up' : 'Pickup'}: ${fmtDateTime(s.appt)}`;
                if (mi) return isLast ? mi.label : '';
                if (kind === 'past') return `${isLast ? 'Delivered' : 'Arrived'}: ${fmtDateTime(s.eta)}`;
                return `ETA: ${fmtDateTime(s.eta)}`;
              };
              const els = stops.map((name, i) => {
                const kind = stopKind(i);
                const isLast = i === stops.length - 1;
                const leg = isLast ? null : legs[i] || legs[legs.length - 1];
                const showNoTrack = kind === 'current' && noTracking;
                // Position class drives marker shape: origin = small dot,
                // transit = numbered circle, dest = teardrop pin. State
                // (past/current/future) drives color. Mirrors the map markers.
                const isOrigin = i === 0;
                const isDest = i === stops.length - 1;
                const pos = isOrigin ? 'origin' : isDest ? 'dest' : 'transit';
                return (
                  <div key={i} className={`fmx-step is-${kind} is-${pos}`}>
                <span className="fmx-step-dot">
                  {pos === 'transit' && i}
                  {pos === 'dest' &&
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                      <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" fill="currentColor" />
                      <circle cx="12" cy="10" r="3" fill="#fff" />
                    </svg>
                  }
                </span>
                <div className="fmx-step-body">
                  <div className="fmx-step-nameRow">
                    <span className="fmx-step-name">{name}</span>
                    {showNoTrack && <span className="fmx-step-notrack">No tracking</span>}
                  </div>
                  {timeLabel(i, isLast, kind) && <span className={`fmx-step-meta${mi && i > 0 ? ' ' + mi.cls : ''}`}>{timeLabel(i, isLast, kind)}</span>}
                  {leg && <span className="fmx-step-meta">Carrier: <b>{leg.carrier}</b></span>}
                  {leg && <span className="fmx-step-meta">Driver: <b>{leg.driver}</b></span>}
                </div>
                <div className="fmx-step-right">
                  {kind === 'past' && <span className="fmx-step-past">Past</span>}
                  {showNoTrack && !tracking &&
                      <button type="button" className="fmx-step-track" onClick={() => setTracking(true)}>Track</button>
                      }
                  {showNoTrack && tracking &&
                      <span className="fmx-step-tracking">Requested</span>
                      }
                  {kind === 'current' &&
                      <button type="button" className="fmx-step-more" onClick={() => setActionsOpen(true)} aria-label="More actions">
                        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
                      </button>
                      }
                </div>
              </div>);

              });
              // Natural route order: origin → transit stops (numbered) → destination.
              return els;
            })()}
      </div>

      <div className="fmx-detail-section" style={{ paddingTop: 0 }}>
        <div className="fmx-detail-h">Tracking Sources</div>
        {(() => {
              const t = trackersOf(s);
              const hws = hwListOf(s);
              const dd = deliveredDriverOf(s);
              const ded = deliveredEldOf(s);
              if (hws.length === 0 && !t.driver && !t.eld && !dd && !ded) {
                return <div className="fmx-tsrc-empty">No tracking sources configured</div>;
              }
              return (
                <React.Fragment>
              {hws.map((hw) => <HwTrackerCard key={hw.id} s={s} hw={hw} hwStatus={hwStatus} onFocus={hwStatus === 'live' ? focusHw : undefined} />)}
              {t.eld && <EldTrackerCard eld={t.eld} eldStatus={eldStatus} eta={s.eta} onFocus={eldStatus === 'live' ? focusEld : undefined} />}
              {t.driver && <DriverTrackerCard driver={t.driver} driverStatus={driverStatus} onFocus={driverStatus === 'live' ? focusDriver : undefined} />}
              {dd && <DeliveredDriverCard dd={dd} onFocus={focusDelivered} />}
              {ded && <DeliveredEldCard ded={ded} onFocus={focusDelivered} />}
            </React.Fragment>);

            })()}
      </div>
      </React.Fragment>
        }
      </div>
      {actionsOpen &&
        <div className="fmx-actions-sheet" onClick={(e) => { if (e.target === e.currentTarget) setActionsOpen(false); }}>
          <div className="fmx-actions-card">
            <div className="fmx-actions-handle" />
            <button className="fmx-actions-item" onClick={() => setActionsOpen(false)}>More actions</button>
            <button className="fmx-actions-item" onClick={() => { setTracking(true); setActionsOpen(false); onViewOnMap && onViewOnMap(); }}>Track</button>
            <button className="fmx-actions-item" onClick={() => { setActionsOpen(false); onViewOnMap && onViewOnMap(); }}>View on Map</button>
          </div>
        </div>
      }
    </aside>);

}

/* ─── Cluster breakdown popover ─────────────────────────────────── */
/* Compact summary card anchored to a cluster pin. Lists every shipment
   in the cluster (scrollable) and exposes "spread on map" + "zoom" actions. */
function ClusterPopover({ cluster, onSelect, onClose, onSpread, onCollapse, onZoom, spread }) {
  if (!cluster) return null;
  const items = cluster.ids
    .map((id) => SHIPMENTS.find((x) => x.id === id))
    .filter(Boolean);
  // Best-effort label for "where is this cluster" — most-common origin city
  // among the cluster's shipments.
  const originCounts = items.reduce((acc, s) => {
    acc[s.origin] = (acc[s.origin] || 0) + 1;
    return acc;
  }, {});
  const topOrigin = Object.entries(originCounts).sort((a, b) => b[1] - a[1])[0];
  const label = topOrigin && topOrigin[1] >= Math.ceil(items.length * 0.6) ?
    `Near ${topOrigin[0].split(',')[0]}` :
    'Multiple shipments';
  const delayed = items.filter((x) => x.isDelayed).length;
  const noTrk = items.filter((x) => isNoTracking(x)).length;
  const sub = [
    `${items.length} shipments`,
    delayed > 0 ? `${delayed} delayed` : null,
    noTrk > 0 ? `${noTrk} no tracking` : null
  ].filter(Boolean).join(' \u00b7 ');
  return (
    <div className="fmx-cluster-pop" id="fmx-cluster-pop">
      <div className="fmx-cluster-pop-head">
        <span className="fmx-cluster-pop-icon">
          {I.truck}
          <span className="fmx-cluster-pop-count">{items.length}</span>
        </span>
        <div className="fmx-cluster-pop-title">
          <span className="t">{label}</span>
          <span className="s">{sub}</span>
        </div>
        <button type="button" className="fmx-cluster-pop-close" onClick={onClose} aria-label="Close">{I.close}</button>
      </div>
      <div className="fmx-cluster-pop-list">
        {items.map((s, i) => {
          const noTrack = isNoTracking(s);
          return (
            <div key={s.id} className="fmx-cluster-pop-row" onClick={() => onSelect(s.id)}>
              <span className="num">{String(i + 1).padStart(2, '0')}</span>
              <div className="body">
                <div className="bolrow">
                  <span className="bol">{s.bol}</span>
                </div>
                <span className="route">{cityShort(s.origin)} → {cityShort(s.dest)} · {primaryCarrier(s)}</span>
              </div>
              {s.isDelayed && <span className="badge is-delay">Delayed</span>}
              {!s.isDelayed && s.redZone && <span className="badge is-redzone">Red Zone</span>}
              {!s.isDelayed && !s.redZone && noTrack && <span className="badge is-track">No tracking</span>}
              {!s.isDelayed && !s.redZone && !noTrack && s.arrivingEarly && <span className="badge is-early">Early</span>}
            </div>
          );
        })}
      </div>
      <div className="fmx-cluster-pop-foot">
        {spread ?
          <button type="button" className="fmx-cluster-pop-btn is-primary" onClick={onCollapse}>
            {I.crosshair} Collapse
          </button> :
          <button type="button" className="fmx-cluster-pop-btn is-primary" onClick={onSpread}>
            {I.shuffle} Spread out
          </button>
        }
        <button type="button" className="fmx-cluster-pop-btn" onClick={onZoom}>
          {I.zoomIn} Zoom
        </button>
      </div>
    </div>
  );
}

/* ─── Map ───────────────────────────────────────────────────────── */
function MapView({ shipments, selectedId, loadingId, onSelect, onClusterClick, mapApiRef, hwStatus, driverStatus, eldStatus, overlays, customRedZones, addRedZoneMode, onMapClickAddRedZone, draftZone, onDraftChange, clusterPopover, onPopoverClose, onSpread, onCollapse, onZoomCluster, spiderIds }) {
  const mapEl = useRef(null);
  const mapRef = useRef(null);
  const [zoomTick, setZoomTick] = useState(0);
  const layerRef = useRef({ origins: null, dests: null, currents: null, routes: null, trackers: null, trackPaths: null, transits: null });
  const fitDoneRef = useRef(false);


  useEffect(() => {
    if (mapRef.current || !mapEl.current) return;
    const map = window.L.map(mapEl.current, {
      center: [39.5, -96], zoom: 4, zoomControl: false, attributionControl: false
    });
    window.L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
      subdomains: 'abcd', maxZoom: 20
    }).addTo(map);
    mapRef.current = map;
    if (mapApiRef) mapApiRef.current = map;

    layerRef.current.routes = window.L.layerGroup().addTo(map);
    layerRef.current.trackPaths = window.L.layerGroup().addTo(map);
    layerRef.current.transits = window.L.layerGroup().addTo(map);
    layerRef.current.origins = window.L.layerGroup().addTo(map);
    layerRef.current.dests = window.L.layerGroup().addTo(map);
    layerRef.current.currents = window.L.layerGroup().addTo(map);
    layerRef.current.spiders = window.L.layerGroup().addTo(map);
    layerRef.current.trackers = window.L.layerGroup().addTo(map);
    layerRef.current.redZones = window.L.layerGroup().addTo(map);
    layerRef.current.draft = window.L.layerGroup().addTo(map);

    requestAnimationFrame(() => map.invalidateSize());
    setTimeout(() => map.invalidateSize(), 160);

    // Re-cluster trucks whenever the viewport changes.
    const bump = () => setZoomTick((v) => v + 1);
    map.on('zoomend', bump);
    map.on('moveend', bump);
  }, []);

  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    const { routes, origins, dests, currents, transits } = layerRef.current;
    routes.clearLayers();origins.clearLayers();dests.clearLayers();currents.clearLayers();transits.clearLayers();
    const allPts = [];

    // While the clicked shipment's data is still being fetched, behave as if
    // nothing is selected so the existing fleet map stays put. The route line,
    // transit/origin/dest pins, and tracker pins only render once loading
    // clears — the thin top progress bar signals that work is in flight.
    const isLoadingSel = loadingId === selectedId && selectedId != null;
    const effSelectedId = isLoadingSel ? null : selectedId;

    // When a shipment is selected, only render markers/route for that one
    // shipment — every other shipment's pins and route line are hidden.
    const renderList = effSelectedId != null ?
    shipments.filter((s) => s.id === effSelectedId) :
    shipments;

    // Collect truck positions for clustering after the loop.
    const truckItems = [];
    // Collect delivered-shipment destinations for clustering after the loop.
    const deliveredItems = [];

    renderList.forEach((s) => {
      const o = CITY[s.origin];const d = CITY[s.dest];
      if (!o || !d) return;
      const isSel = s.id === effSelectedId;
      allPts.push(o, d);

      // Multi-leg route: build the polyline through origin → transit stops → dest.
      const pts = routePoints(s);

      // State-aware stop classifier — mirrors the ShipmentDetail route
      // timeline so map markers and popup markers stay in lockstep
      // (Map Detail Popup Guide · section 6 · parity).
      const activeIdx = activeLegIndex(s);
      const isDelivered = s.status === 'delivered' || s.pct >= 100;
      const stopKind = (idx) => {
        if (isDelivered) return 'past';
        if (idx < activeIdx) return 'past';
        if (idx === activeIdx) return 'current';
        return 'future';
      };

      // Only draw the route line when this shipment is the selected one —
      // by default (nothing selected) the map shows just origin/dest pins.
      if (isSel) {
        // Split the route at the shipment's progress: completed portion
        // draws as a SOLID grey line, the remaining portion as a DASHED
        // black line (per popup guide line states).
        const pct = Math.max(0, Math.min(1, (s.pct || 0) / 100));
        const [donePts, restPts] = splitRouteAt(pts, pct);
        if (donePts.length >= 2) {
          const doneLine = window.L.polyline(donePts, {
            color: '#A9A9A9',
            weight: 2,
            opacity: 1,
            lineCap: 'round',
            lineJoin: 'round'
          });
          doneLine.on('click', () => onSelect(s.id));
          doneLine.addTo(routes);
        }
        if (restPts.length >= 2) {
          const restLine = window.L.polyline(restPts, {
            color: '#14121D',
            weight: 1.5,
            opacity: 0.85,
            dashArray: '6 6',
            lineCap: 'round',
            lineJoin: 'round'
          });
          restLine.on('click', () => onSelect(s.id));
          restLine.addTo(routes);
        }

        // Transit-hub pins between origin and destination — drawn only for the
        // selected shipment so the unselected map stays clean.
        const transitStops = pts.slice(1, -1);
        const transitNames = getStops(s).slice(1, -1);
        transitStops.forEach((p, i) => {
          const name = transitNames[i] || '';
          // pts index for this transit stop
          const stopIdx = i + 1;
          const kind = stopKind(stopIdx);
          const tIcon = window.L.divIcon({
            html: `<div class="fmx-pin is-transit is-${kind}">${i + 1}</div><div class="fmx-pin-lbl">${name.split(',')[0]}</div>`,
            className: '', iconSize: [18, 18], iconAnchor: [9, 9]
          });
          window.L.marker(p, { icon: tIcon }).on('click', () => onSelect(s.id)).addTo(transits);
        });
      }

      // Origin/destination pins only render for the selected shipment —
      // by default (nothing selected) the map shows just the live truck pins.
      if (isSel) {
        const oKind = stopKind(0);
        const dKind = stopKind(pts.length - 1);
        const oPin = window.L.divIcon({ html: `<div class="fmx-pin is-origin is-${oKind}"></div>`, className: '', iconSize: [12, 12], iconAnchor: [6, 6] });
        window.L.marker(o, { icon: oPin }).on('click', () => onSelect(s.id)).addTo(origins);

        const dPin = window.L.divIcon({ html: `<div class="fmx-pin is-dest is-${dKind}"><svg width="18" height="23" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" fill="currentColor" stroke="#fff" stroke-width="1.3" stroke-linejoin="round"/><circle cx="12" cy="10" r="3" fill="none" stroke="#fff" stroke-width="2"/></svg></div>`, className: '', iconSize: [18, 23], iconAnchor: [9, 22] });
        window.L.marker(d, { icon: dPin }).on('click', () => onSelect(s.id)).addTo(dests);
      }

      // Delivered shipments — collect their destination positions; clustered
      // and rendered below so dozens of deliveries at one DC roll up into a
      // single counted pin.
      if (effSelectedId == null && s.status === 'delivered') {
        deliveredItems.push({ id: s.id, pos: d });
      }

      // The live truck pin only renders in the high-level view (nothing
      // selected). When a shipment is selected, the dedicated tracker
      // pins (HW / Driver / ELD) take over and the generic truck is hidden.
      const isLive = ['pickup', 'in_transit', 'out_for_delivery'].includes(s.status);
      if (effSelectedId == null && isLive && s.pct > 0 && s.pct < 100) {
        const cur = interpolateRoute(pts, s.pct / 100);
        truckItems.push({ id: s.id, pos: cur, isSel, redZone: !!s.redZone });
      }
    });

    // ── Cluster nearby trucks based on current viewport pixel distance ──
    // Trucks within CLUSTER_PX of each other collapse into one marker with
    // a count badge; clicking the cluster filters the list to those
    // shipments and zooms to their bounds.
    const CLUSTER_PX = 44;
    const truckGroups = clusterByPixel(map, truckItems, CLUSTER_PX);
    truckGroups.forEach((g) => {
      if (g.length === 1) {
        const it = g[0];
        const cPin = window.L.divIcon({
          html: `<div class="fmx-pin is-current${it.isSel ? ' is-sel' : ''}${it.redZone ? ' is-redzone' : ''}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg></div>`,
          className: '', iconSize: [24, 24], iconAnchor: [12, 12]
        });
        window.L.marker(it.pos, { icon: cPin }).on('click', () => onSelect(it.id)).addTo(currents);
      } else {
        const center = [
        g.reduce((s, p) => s + p.pos[0], 0) / g.length,
        g.reduce((s, p) => s + p.pos[1], 0) / g.length];

        const ids = g.map((p) => p.id);
        const bounds = g.map((p) => p.pos);
        const isOpen = clusterPopover && JSON.stringify([...clusterPopover.ids].sort()) === JSON.stringify([...ids].sort());
        // Hide the cluster pin entirely while it's spread out (the spider
        // pins take its place; we still show the popover anchored to center).
        if (isOpen && spiderIds && spiderIds.length === ids.length) return;
        // Size tier scales the pin so 25 / 50 shipment clusters read at a
        // glance vs. small 2-4 clusters.
        const sizeCls =
        g.length >= 25 ? 'is-xl' :
        g.length >= 10 ? 'is-lg' :
        g.length >= 5 ? 'is-md' : '';
        const openCls = isOpen ? ' is-open' : '';
        const countLabel = g.length > 99 ? '99+' : String(g.length);
        const cPin = window.L.divIcon({
          html: `<div class="fmx-pin is-current is-cluster ${sizeCls}${openCls}"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg><span class="fmx-cluster-badge">${countLabel}</span></div>`,
          className: '', iconSize: [30, 30], iconAnchor: [15, 15]
        });
        window.L.marker(center, { icon: cPin, zIndexOffset: 500 }).
        on('click', () => onClusterClick && onClusterClick(ids, bounds, center)).
        addTo(currents);
      }
    });

    // ── Cluster delivered shipments by destination ───────────────────
    // Many shipments often deliver to the same DC; roll them up by pixel
    // distance into a single green pin with a count badge.
    const deliveredGroups = clusterByPixel(map, deliveredItems, CLUSTER_PX);
    deliveredGroups.forEach((g) => {
      if (g.length === 1) {
        const it = g[0];
        const delivIcon = window.L.divIcon({
          html: `<div class="fmx-pin is-delivered"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg></div>`,
          className: '', iconSize: [22, 22], iconAnchor: [11, 11]
        });
        window.L.marker(it.pos, { icon: delivIcon }).on('click', () => onSelect(it.id)).addTo(currents);
      } else {
        const center = [
        g.reduce((s, p) => s + p.pos[0], 0) / g.length,
        g.reduce((s, p) => s + p.pos[1], 0) / g.length];

        const ids = g.map((p) => p.id);
        const bounds = g.map((p) => p.pos);
        const isOpen = clusterPopover && JSON.stringify([...clusterPopover.ids].sort()) === JSON.stringify([...ids].sort());
        if (isOpen && spiderIds && spiderIds.length === ids.length) return;
        const sizeCls =
        g.length >= 25 ? 'is-xl' :
        g.length >= 10 ? 'is-lg' :
        g.length >= 5 ? 'is-md' : '';
        const openCls = isOpen ? ' is-open' : '';
        const countLabel = g.length > 99 ? '99+' : String(g.length);
        const cPin = window.L.divIcon({
          html: `<div class="fmx-pin is-delivered-cluster ${sizeCls}${openCls}"><span class="fmx-delivered-count">${countLabel}</span><span class="fmx-delivered-tick"><svg viewBox="0 0 24 24" width="7" height="7" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg></span></div>`,
          className: '', iconSize: [28, 28], iconAnchor: [14, 14]
        });
        window.L.marker(center, { icon: cPin, zIndexOffset: 500 }).
        on('click', () => onClusterClick && onClusterClick(ids, bounds, center)).
        addTo(currents);
      }
    });

    if (allPts.length && !fitDoneRef.current) {
      map.fitBounds(allPts, { padding: [50, 50] });
      fitDoneRef.current = true;
    }
  }, [shipments, selectedId, loadingId, onSelect, onClusterClick, zoomTick, clusterPopover, spiderIds]);

  /* Selected-shipment tracker pins + breadcrumb paths: each live source
     gets its own wandering breadcrumb trail from origin to current position. */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    const trkLayer = layerRef.current.trackers;
    const pathLayer = layerRef.current.trackPaths;
    if (!trkLayer || !pathLayer) return;
    trkLayer.clearLayers();
    pathLayer.clearLayers();
    if (selectedId == null) return;
    const s = SHIPMENTS.find((x) => x.id === selectedId);
    if (!s) return;
    const o = CITY[s.origin],d = CITY[s.dest];
    if (!o || !d) return;
    const pts = routePoints(s);
    const pct = (s.pct || 50) / 100;
    const cur = interpolateRoute(pts, Math.max(0.02, Math.min(0.98, pct)));

    // While the shipment's data is still being fetched, hide tracker pins and
    // breadcrumbs entirely. A thin progress bar at the top of the map
    // communicates the loading state instead of a per-marker spinner.
    if (loadingId === selectedId) {
      return;
    }

    const t = trackersOf(s);
    const live = liveTrackers(s, hwStatus, driverStatus, eldStatus);
    const rzCls = s.redZone ? ' is-redzone' : '';

    if (live.hw) {
      const hws = hwListOf(s);
      hws.forEach((hw, hwIdx) => {
        // Stagger multiple HW trackers around the base HW offset so they don't overlap.
        const fan = hws.length > 1 ? (hwIdx - (hws.length - 1) / 2) * 0.42 : 0;
        const pos = [cur[0] + HW_OFFSET[0] + fan, cur[1] + HW_OFFSET[1] + fan * 0.6];
        const path = breadcrumbPath(o, pos, s.id * 13 + 7 + hwIdx * 31, 1);
        window.L.polyline(path, { color: '#34C759', weight: 3, opacity: 0.85, lineCap: 'round', lineJoin: 'round' }).addTo(pathLayer);
        const icon = window.L.divIcon({
          html: `<div class="fmx-trk is-hw${rzCls}"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h3"/><path d="M19 12h3"/><path d="M12 2v3"/><path d="M12 19v3"/><circle cx="12" cy="12" r="7"/></svg></div><div class="fmx-trk-lbl">${hw.id}</div>`,
          className: '', iconSize: [26, 26], iconAnchor: [13, 13]
        });
        window.L.marker(pos, { icon, zIndexOffset: 1000 + hwIdx }).addTo(trkLayer);
      });
    }
    if (live.driver) {
      const pos = [cur[0] + DRIVER_OFFSET[0], cur[1] + DRIVER_OFFSET[1]];
      const path = breadcrumbPath(o, pos, s.id * 17 + 23, -1);
      window.L.polyline(path, { color: '#007AFF', weight: 3, opacity: 0.85, lineCap: 'round', lineJoin: 'round' }).addTo(pathLayer);
      const icon = window.L.divIcon({
        html: `<div class="fmx-trk is-driver${rzCls}"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></div><div class="fmx-trk-lbl">${t.driver.name}</div>`,
        className: '', iconSize: [26, 26], iconAnchor: [13, 13]
      });
      window.L.marker(pos, { icon, zIndexOffset: 1001 }).addTo(trkLayer);
    }
    if (live.eld) {
      const pos = [cur[0] + ELD_OFFSET[0], cur[1] + ELD_OFFSET[1]];
      const path = breadcrumbPath(o, pos, s.id * 23 + 11, 1);
      const handoff = t.eld && t.eld.handoff;
      if (handoff && path.length >= 3) {
        // Split the breadcrumb at the handoff point so we can draw two truck legs:
        // the previous truck's leg (ended) muted, and the current truck's leg live.
        const at = Math.max(0.15, Math.min(0.85, handoff.at));
        const idx = Math.max(1, Math.min(path.length - 2, Math.round(path.length * at)));
        const handoffPos = path[idx];
        const donePath = path.slice(0, idx + 1);
        const restPath = path.slice(idx);
        // First truck's completed leg — muted gray, thinner
        window.L.polyline(donePath, { color: '#8C8C8C', weight: 2.5, opacity: 0.9, lineCap: 'round', lineJoin: 'round' }).addTo(pathLayer);
        // Second (current) truck's active leg — brand gold
        window.L.polyline(restPath, { color: '#FFCC00', weight: 3, opacity: 0.9, lineCap: 'round', lineJoin: 'round' }).addTo(pathLayer);
        // Marker for the ended truck parked at the handoff point
        const prevIcon = window.L.divIcon({
          html: `<div class="fmx-trk is-eld is-ended" title="Handoff — truck ended"><svg viewBox="0 0 24 24" fill="none" stroke="#14121D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg></div><div class="fmx-trk-lbl">${handoff.prevTruck || 'Prev truck'} · ended</div>`,
          className: '', iconSize: [26, 26], iconAnchor: [13, 13]
        });
        window.L.marker(handoffPos, { icon: prevIcon, zIndexOffset: 1000 }).addTo(trkLayer);
      } else {
        window.L.polyline(path, { color: '#FFCC00', weight: 3, opacity: 0.85, lineCap: 'round', lineJoin: 'round' }).addTo(pathLayer);
      }
      const icon = window.L.divIcon({
        html: `<div class="fmx-trk is-eld${rzCls}"><svg viewBox="0 0 24 24" fill="none" stroke="#14121D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg></div><div class="fmx-trk-lbl">${t.eld.truck || t.eld.id}</div>`,
        className: '', iconSize: [26, 26], iconAnchor: [13, 13]
      });
      window.L.marker(pos, { icon, zIndexOffset: 1002 }).addTo(trkLayer);
    }

    /* Delivered-driver history: full trail from origin to the destination
       (the phone stopped pinging when the driver arrived and closed the
       load), a driver marker planted at the drop, and a POD upload point.
       The POD often lands on top of the destination — sometimes it drifts
       away because the driver forgot to hit "upload" until later. */
    const dd = deliveredDriverOf(s);
    if (dd) {
      const path = breadcrumbPath(o, d, s.id * 17 + 23, -1);
      // The polyline visualizes the *en-route* pings only. It starts at the
      // first ping (a real checkpoint) so the beginning of the line has a
      // visible dot on it, and the origin marker (representing the "opened
      // link" state change, not a location) stays visually detached.
      const LINE_START_T = 0.05;
      const linePath = [
      interpolateRoute(path, LINE_START_T),
      ...path.filter((_, idx) => idx / (path.length - 1) > LINE_START_T)];

      window.L.polyline(linePath, {
        color: '#007AFF', weight: 3, opacity: 0.85,
        lineCap: 'round', lineJoin: 'round'
      }).addTo(pathLayer);
      /* Ping-trail beads — each event that the driver app reported
         materializes as a small dot along the recorded breadcrumb. The two
         "en route to pickup" pings and the pickup / BOL cluster sit near
         the origin; the destination-side pings spread along the drive. The
         "opened link" event is *not* on the polyline — it's a state change,
         not a location — so it's rendered separately as the origin driver
         marker below. Event 8 (arrived at destination) is represented by
         the driver marker at the endpoint; event 9 (POD) has its own icon. */
      if (dd.pingTrail && dd.pingTrail.length && path.length >= 2) {
        /* Events that share a location (same t) — e.g. "Arrived at pickup"
           and "Signed BOL" both happen at the pickup dock — collapse into ONE
           stacked bead with a count badge and a combined chronological
           tooltip, instead of two dots faked apart along the route. */
        const trailStops = [
        { i: 1, t: 0.05, kind: 'ping' },
        { i: 2, t: 0.11, kind: 'ping' },
        { i: 3, t: 0.17, kind: 'arrive' },
        { i: 4, t: 0.17, kind: 'doc' },
        { i: 5, t: 0.48, kind: 'ping' },
        { i: 6, t: 0.78, kind: 'ping' }];

        const byT = new Map();
        trailStops.forEach((stopDef) => {
          const ev = dd.pingTrail[stopDef.i];
          if (!ev) return;
          const k = String(stopDef.t);
          if (!byT.has(k)) byT.set(k, []);
          byT.get(k).push({ ...stopDef, ev });
        });
        byT.forEach((group) => {
          const p = interpolateRoute(path, group[0].t);
          const evRow = (ev) =>
          `<div class="fmx-ping-tip-lbl">${ev.label}</div>` +
          `<div class="fmx-ping-tip-time">${ev.ts ? fmtDateTime(ev.ts) : 'Pending'}</div>` +
          (ev.sub ? `<div class="fmx-ping-tip-sub">${ev.sub}</div>` : '');
          let dot, tipHtml;
          if (group.length === 1) {
            dot = window.L.divIcon({
              html: `<div class="fmx-ping-dot is-${group[0].kind}"></div>`,
              className: '', iconSize: [10, 10], iconAnchor: [5, 5]
            });
            tipHtml = `<div class="fmx-ping-tip">${evRow(group[0].ev)}</div>`;
          } else {
            // Stacked bead: primary kind styling + event-count badge.
            const kindCls = group.map((g) => `is-${g.kind}`).join(' ');
            dot = window.L.divIcon({
              html: `<div class="fmx-ping-dot is-stack ${kindCls}"><span class="fmx-ping-stack-n">${group.length}</span></div>`,
              className: '', iconSize: [14, 14], iconAnchor: [7, 7]
            });
            tipHtml =
            `<div class="fmx-ping-tip">` +
            `<div class="fmx-ping-tip-hd">${group.length} events · same location</div>` +
            group.map((g) => `<div class="fmx-ping-tip-row">${evRow(g.ev)}</div>`).join('') +
            `</div>`;
          }
          window.L.marker(p, { icon: dot, zIndexOffset: 999 }).
          bindTooltip(tipHtml, {
            direction: 'top', offset: [0, -6], opacity: 1,
            className: 'fmx-ping-tip-wrap', sticky: false
          }).
          addTo(trkLayer);
        });
      }
      /* ELD ping beads — machine-reported location snapshots along the
         truck's OWN recorded route (distinct from the driver-app breadcrumb).
         Renders a dashed yellow polyline + a bead at each ping so the ELD
         history is legible on its own line rather than overlapping the
         driver trail. */
      const ded = deliveredEldOf(s);
      if (ded && ded.pingTrail && ded.pingTrail.length) {
        // Own breadcrumb with side=+1 so it bulges opposite the driver path.
        const eldPath = breadcrumbPath(o, d, s.id * 17 + 23, +1);
        const ELD_LINE_START_T = 0.05;
        const eldLinePath = [
        interpolateRoute(eldPath, ELD_LINE_START_T),
        ...eldPath.filter((_, idx) => idx / (eldPath.length - 1) > ELD_LINE_START_T)];
        window.L.polyline(eldLinePath, {
          color: '#FFCC00', weight: 2.5, opacity: 0.85,
          lineCap: 'round', lineJoin: 'round'
        }).addTo(pathLayer);
        /* ELD origin marker — "Truck assigned" state change. Mirrors the
           driver origin marker: same truck icon + provider · truck # label
           as the delivered ELD card, disconnected from the polyline (which
           starts a bit further along) to signal that assignment isn't a
           location ping. */
        const eldAssignedEv = ded.pingTrail && ded.pingTrail[0];
        const eldAssignedTipHtml = eldAssignedEv ?
        `<div class="fmx-ping-tip">` +
        `<div class="fmx-ping-tip-lbl">Truck assigned</div>` +
        `<div class="fmx-ping-tip-time">${fmtDateTime(eldAssignedEv.ts)}</div>` +
        `<div class="fmx-ping-tip-sub">${ded.provider} · Truck ${ded.truck}</div>` +
        `</div>` :
        '';
        // ELD origin sits at the same geo point as the driver origin (`o`);
        // shift the icon container ~28px right in screen pixels via iconAnchor
        // so it lands next to the driver marker instead of on top of it.
        const eldOriginIcon = window.L.divIcon({
          html: `<div class="fmx-trk is-eld"><svg viewBox="0 0 24 24" fill="none" stroke="#14121D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg></div><div class="fmx-trk-lbl is-above">${ded.provider} · Truck ${ded.truck}</div>`,
          className: '', iconSize: [26, 26], iconAnchor: [-15, 13]
        });
        const eldOriginMarker = window.L.marker(o, { icon: eldOriginIcon, zIndexOffset: 1000 });
        if (eldAssignedTipHtml) {
          eldOriginMarker.bindTooltip(eldAssignedTipHtml, {
            direction: 'top', offset: [0, -14], opacity: 1,
            className: 'fmx-ping-tip-wrap', sticky: false
          });
        }
        eldOriginMarker.addTo(trkLayer);
        ded.pingTrail.forEach((ev) => {
          const p = interpolateRoute(eldPath, ev.t);
          const dot = window.L.divIcon({
            html: `<div class="fmx-ping-dot is-eld"></div>`,
            className: '', iconSize: [10, 10], iconAnchor: [5, 5]
          });
          const tipHtml = ev.status ?
          `<div class="fmx-ping-tip">` +
          `<div class="fmx-ping-tip-lbl">${ev.status}</div>` +
          `<div class="fmx-ping-tip-time">${fmtDateTime(ev.ts)}</div>` +
          `</div>` :
          `<div class="fmx-ping-tip">` +
          `<div class="fmx-ping-tip-lbl">ELD location ping</div>` +
          `<div class="fmx-ping-tip-time">${fmtDateTime(ev.ts)}</div>` +
          `<div class="fmx-ping-tip-sub">${ded.provider} · ${ev.speed} mph</div>` +
          `</div>`;
          window.L.marker(p, { icon: dot, zIndexOffset: 998 }).
          bindTooltip(tipHtml, {
            direction: 'top', offset: [0, -6], opacity: 1,
            className: 'fmx-ping-tip-wrap', sticky: false
          }).
          addTo(trkLayer);
        });
      }
      /* Origin marker — "Driver opened link" state change. Uses the same
         phone icon + name as the endpoint driver so both ends of the trail
         read as the same person. Disconnected from the polyline (which
         starts a bit further along) to signal that opening the link isn't
         a location ping. */
      const openedEv = dd.pingTrail && dd.pingTrail[0];
      const openedTipHtml = openedEv ?
      `<div class="fmx-ping-tip">` +
      `<div class="fmx-ping-tip-lbl">Driver opened link</div>` +
      `<div class="fmx-ping-tip-time">${fmtDateTime(openedEv.ts)}</div>` +
      `</div>` :
      '';
      const originIcon = window.L.divIcon({
        html: `<div class="fmx-trk is-driver is-delivered"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></div><div class="fmx-trk-lbl">${dd.name}</div>`,
        className: '', iconSize: [26, 26], iconAnchor: [13, 13]
      });
      const originMarker = window.L.marker(o, { icon: originIcon, zIndexOffset: 1000 });
      if (openedTipHtml) {
        originMarker.bindTooltip(openedTipHtml, {
          direction: 'top', offset: [0, -14], opacity: 1,
          className: 'fmx-ping-tip-wrap', sticky: false
        });
      }
      originMarker.addTo(trkLayer);
      // When the POD upload landed exactly at the destination the two markers
      // sit on the same lat/lng — offset the driver icon a bit to the left so
      // both icons stay visible side-by-side. The driver's label sits below
      // the icon by default (may partially collide with the POD label below);
      // hovering the driver marker pops it above the icon so it's readable.
      const driverOverlapsPod = !dd.missing && dd.podAtDest;
      const driverAnchor = driverOverlapsPod ? [28, 13] : [13, 13];
      const driverLblCls = driverOverlapsPod ? 'fmx-trk-lbl is-above-on-hover' : 'fmx-trk-lbl';
      const driverIcon = window.L.divIcon({
        html: `<div class="fmx-trk is-driver is-delivered"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></div><div class="${driverLblCls}">${dd.name}</div>`,
        className: '', iconSize: [26, 26], iconAnchor: driverAnchor
      });
      // Endpoint tooltip mirrors the ping-trail tips: "Arrived at destination"
      // + timestamp, so hovering either end of the trail reveals the event.
      const arrivedEv = dd.pingTrail && dd.pingTrail[7];
      const arrivedTipHtml = arrivedEv ?
      `<div class="fmx-ping-tip">` +
      `<div class="fmx-ping-tip-lbl">Arrived at destination</div>` +
      `<div class="fmx-ping-tip-time">${fmtDateTime(arrivedEv.ts)}</div>` +
      `</div>` :
      '';
      const destMarker = window.L.marker(d, { icon: driverIcon, zIndexOffset: 1001 });
      if (arrivedTipHtml) {
        destMarker.bindTooltip(arrivedTipHtml, {
          direction: 'top', offset: [0, -14], opacity: 1,
          className: 'fmx-ping-tip-wrap', sticky: false
        });
      }
      destMarker.addTo(trkLayer);

      if (!dd.missing) {
        const podPos = [d[0] + dd.podOffset[0], d[1] + dd.podOffset[1]];
        // A dashed connector when the POD upload happened away from the drop —
        // makes the discrepancy visible at a glance.
        if (!dd.podAtDest) {
          window.L.polyline([d, podPos], {
            color: '#A9A9A9', weight: 1.5, opacity: 0.75,
            dashArray: '4 5', lineCap: 'round'
          }).addTo(pathLayer);
        }
        const podCls = dd.podAtDest ? 'is-pod' : 'is-pod is-off';
        // Mirror the driver's side-offset when both markers sit at the
        // destination — POD to the right, driver to the left.
        const podAnchor = dd.podAtDest ? [-2, 13] : [13, 13];
        const podIcon = window.L.divIcon({
          html: `<div class="fmx-trk ${podCls}"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15l2 2 4-4"/></svg></div><div class="fmx-trk-lbl">POD uploaded</div>`,
          className: '', iconSize: [26, 26], iconAnchor: podAnchor
        });
        const podEv = dd.pingTrail && dd.pingTrail[8];
        const podSub = dd.podAtDest ? '' : `<div class="fmx-ping-tip-sub">${fmtOffsetMi(dd.podOffset)} from destination</div>`;
        const podTipHtml =
        `<div class="fmx-ping-tip">` +
        `<div class="fmx-ping-tip-lbl">POD uploaded</div>` +
        `<div class="fmx-ping-tip-time">${podEv && podEv.ts ? fmtDateTime(podEv.ts) : fmtDateTime(dd.podTs)}</div>` +
        podSub +
        `</div>`;
        window.L.marker(podPos, { icon: podIcon, zIndexOffset: 1003 }).
        bindTooltip(podTipHtml, {
          direction: 'top', offset: [0, -14], opacity: 1,
          className: 'fmx-ping-tip-wrap', sticky: false
        }).

        addTo(trkLayer);
      }
    }
  }, [selectedId, loadingId, hwStatus, driverStatus, eldStatus]);

  useEffect(() => {
    if (selectedId == null) return;
    // Wait until the simulated fetch resolves before flying — we don't want
    // the camera to pan to a single shipment that's still loading.
    if (loadingId === selectedId) return;
    const map = mapRef.current;
    const s = SHIPMENTS.find((x) => x.id === selectedId);
    if (!map || !s) return;
    const o = CITY[s.origin];const d = CITY[s.dest];
    if (o && d) map.flyToBounds([o, d], { padding: [70, 70], duration: 0.5 });
  }, [selectedId, loadingId]);

  /* Spider-fan: render individual pins fanned out around the cluster center
     when the user opts to "spread out" a cluster popover. Each fanned pin is
     placed at a fixed pixel offset from the cluster center, with a thin
     connector line back to the center. Positions re-compute on zoom/move so
     they stay anchored to the same geographic point. */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    const spiderLayer = layerRef.current.spiders;
    if (!spiderLayer) return;
    spiderLayer.clearLayers();
    if (!spiderIds || !spiderIds.length || !clusterPopover) return;
    const center = clusterPopover.latLng;
    if (!center) return;
    const centerPx = map.latLngToContainerPoint(center);
    const offsets = computeFanPositions(spiderIds.length);
    // Center marker so users see exactly where these all originate from.
    const centerIcon = window.L.divIcon({
      html: `<div class="fmx-spider-center"></div>`,
      className: '', iconSize: [10, 10], iconAnchor: [5, 5]
    });
    window.L.marker(center, { icon: centerIcon, zIndexOffset: 1500 }).addTo(spiderLayer);
    spiderIds.forEach((id, i) => {
      const s = SHIPMENTS.find((x) => x.id === id);
      if (!s) return;
      const off = offsets[i];
      const pinPx = window.L.point(centerPx.x + off.dx, centerPx.y + off.dy);
      const latLng = map.containerPointToLatLng(pinPx);
      // Connector line
      window.L.polyline([center, latLng], {
        color: '#14121D',
        weight: 1,
        opacity: 0.32,
        dashArray: '2,4',
        interactive: false
      }).addTo(spiderLayer);
      const cls =
        s.isDelayed ? 'is-delay' :
        s.redZone ? 'is-redzone' :
        isNoTracking(s, hwStatus, driverStatus, eldStatus) ? 'is-track' : '';
      const showLabel = spiderIds.length <= 30;
      const icon = window.L.divIcon({
        html: `<div class="fmx-spider-pin ${cls}">${i + 1}</div>${showLabel ? `<div class="fmx-spider-lbl">${s.bol}</div>` : ''}`,
        className: '', iconSize: [24, 24], iconAnchor: [12, 12]
      });
      window.L.marker(latLng, { icon, zIndexOffset: 1700 + i })
        .on('click', () => onSelect(id))
        .addTo(spiderLayer);
    });
  }, [spiderIds, clusterPopover, zoomTick, hwStatus, driverStatus, eldStatus, onSelect]);

  /* Red zone overlay polygons + custom circles */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    const rzLayer = layerRef.current.redZones;
    if (!rzLayer) return;
    rzLayer.clearLayers();
    // Base red-zone polygons render only when the overlay is enabled.
    if (overlays && overlays.redzone) {
      RED_ZONES.forEach((coords, i) => {
        window.L.polygon(coords, {
          color: 'transparent',
          weight: 0,
          fillColor: '#FF1100',
          fillOpacity: 0.10,
          smoothFactor: 2
        }).addTo(rzLayer);
        window.L.marker(zoneCentroid(coords), {
          interactive: false,
          keyboard: false,
          icon: window.L.divIcon({
            className: '',
            html: '<div class="fmx-rz-label">' + RED_ZONE_NAMES[i] + '</div>',
            iconSize: [0, 0]
          })
        }).addTo(rzLayer);
      });
      // Custom user-added zones — circles or polygons depending on z.shape.
      (customRedZones || []).forEach((z) => {
        const style = {
          color: '#FF1100',
          weight: 1.5,
          opacity: 0.55,
          fillColor: '#FF1100',
          fillOpacity: 0.14,
          dashArray: '4 4'
        };
        if (z.shape === 'polygon' && z.vertices && z.vertices.length >= 3) {
          window.L.polygon(z.vertices, style).addTo(rzLayer);
        } else {
          window.L.circle(z.center, { ...style, radius: z.radius_m || DEFAULT_RZ_RADIUS_M }).addTo(rzLayer);
        }
        const labelPos = (z.shape === 'polygon' && z.vertices && z.vertices.length >= 3)
          ? zoneCentroid(z.vertices) : z.center;
        if (z.name && labelPos) {
          window.L.marker(labelPos, {
            interactive: false,
            keyboard: false,
            icon: window.L.divIcon({
              className: '',
              html: '<div class="fmx-rz-label">' + z.name + '</div>',
              iconSize: [0, 0]
            })
          }).addTo(rzLayer);
        }
      });
    }
    // When the user selects a shipment that's currently inside a red zone,
    // draw a highlighted polygon centered on the truck so the truck + tracker
    // pins visibly sit inside the zone — independent of whether the global
    // red-zone overlay is on.
    const sel = shipments.find((x) => x.id === selectedId);
    if (sel && sel.redZone) {
      const pts = routePoints(sel);
      if (pts && pts.length >= 2) {
        const truckPos = interpolateRoute(pts, Math.max(0.02, Math.min(0.98, (sel.pct || 50) / 100)));
        // Find the nearest base polygon to borrow its organic shape; then
        // translate that shape so its centroid lands on the truck position
        // (the original polygons aren't guaranteed to contain the truck).
        let bestIdx = -1;
        let bestD = Infinity;
        const centroids = RED_ZONES.map((coords) => {
          let cx = 0;
          let cy = 0;
          coords.forEach((p) => { cx += p[0]; cy += p[1]; });
          return [cx / coords.length, cy / coords.length];
        });
        centroids.forEach(([cx, cy], idx) => {
          const d = Math.hypot(truckPos[0] - cx, truckPos[1] - cy);
          if (d < bestD) { bestD = d; bestIdx = idx; }
        });
        if (bestIdx >= 0) {
          const [cx, cy] = centroids[bestIdx];
          const dx = truckPos[0] - cx;
          const dy = truckPos[1] - cy;
          // Scale shape down a bit so it reads as a contained zone around the
          // truck instead of a vast region.
          const scale = 0.35;
          const shifted = RED_ZONES[bestIdx].map(([lat, lng]) => [
            cx + (lat - cx) * scale + dx,
            cy + (lng - cy) * scale + dy
          ]);
          window.L.polygon(shifted, {
            color: 'transparent',
            weight: 0,
            fillColor: '#FF1100',
            fillOpacity: 0.10,
            smoothFactor: 2
          }).addTo(rzLayer);
        }
      }
    }
  }, [overlays && overlays.redzone, customRedZones, selectedId, shipments]);

  /* Deselect the currently-chosen shipment when the user clicks empty
     map (not a pin or cluster). Marker clicks do NOT propagate to the map
     in Leaflet by default, so this only fires on true background clicks. */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (addRedZoneMode || draftZone) return; // don't interfere with placement
    const handler = () => { if (selectedId != null && onSelect) onSelect(null); };
    map.on('click', handler);
    return () => map.off('click', handler);
  }, [selectedId, onSelect, addRedZoneMode, draftZone]);

  /* Add-red-zone draw mode: when active, the next map click registers a
     new draft zone at that location (the toolbar then takes over). */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (!addRedZoneMode) return;
    const handler = (e) => {
      if (onMapClickAddRedZone) onMapClickAddRedZone([e.latlng.lat, e.latlng.lng]);
    };
    map.on('click', handler);
    return () => map.off('click', handler);
  }, [addRedZoneMode, onMapClickAddRedZone]);

  /* Draft zone overlay — renders the unsaved geofence with interactive
     handles (drag center / radius / vertices). Live drags mutate the leaflet
     layers directly for smoothness; final values are committed to React
     state on dragend so the rest of the UI (toolbar, etc.) stays in sync. */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    const layer = layerRef.current.draft;
    if (!layer) return;
    layer.clearLayers();
    if (!draftZone) return;

    const sharedStyle = {
      color: '#FF1100',
      weight: 2,
      opacity: 0.9,
      fillColor: '#FF1100',
      fillOpacity: 0.10,
      dashArray: '6 4'
    };

    if (draftZone.shape === 'circle') {
      const circle = window.L.circle(draftZone.center, {
        ...sharedStyle,
        radius: draftZone.radius_m || DEFAULT_RZ_RADIUS_M,
        interactive: false
      }).addTo(layer);

      // Center handle — drag to move the whole geofence.
      const centerMarker = window.L.marker(draftZone.center, {
        icon: window.L.divIcon({
          className: '',
          html: '<div class="fmx-draft-center" title="Drag to move"></div>',
          iconSize: [14, 14],
          iconAnchor: [7, 7]
        }),
        draggable: true,
        zIndexOffset: 2000
      }).addTo(layer);

      let radiusMarker = null;
      const placeRadius = () => {
        const pos = circleEastPoint(
          [centerMarker.getLatLng().lat, centerMarker.getLatLng().lng],
          circle.getRadius()
        );
        if (radiusMarker) radiusMarker.setLatLng(pos);
      };

      centerMarker.on('drag', (e) => {
        const ll = e.target.getLatLng();
        circle.setLatLng(ll);
        placeRadius();
      });
      centerMarker.on('dragend', (e) => {
        const ll = e.target.getLatLng();
        onDraftChange((d) => d && d.shape === 'circle' ? { ...d, center: [ll.lat, ll.lng] } : d);
      });

      // Radius handle — east of the center, drag east/west to resize.
      const radPos = circleEastPoint(draftZone.center, draftZone.radius_m || DEFAULT_RZ_RADIUS_M);
      radiusMarker = window.L.marker(radPos, {
        icon: window.L.divIcon({
          className: '',
          html: '<div class="fmx-draft-radius" title="Drag to resize"></div>',
          iconSize: [14, 14],
          iconAnchor: [7, 7]
        }),
        draggable: true,
        zIndexOffset: 2001
      }).addTo(layer);
      radiusMarker.on('drag', (e) => {
        const c = centerMarker.getLatLng();
        const ll = e.target.getLatLng();
        const r = map.distance(c, ll);
        const clamped = Math.max(MIN_RZ_RADIUS_M, Math.min(MAX_RZ_RADIUS_M, r));
        circle.setRadius(clamped);
      });
      radiusMarker.on('dragend', (e) => {
        const c = centerMarker.getLatLng();
        const ll = e.target.getLatLng();
        const r = map.distance(c, ll);
        const clamped = Math.max(MIN_RZ_RADIUS_M, Math.min(MAX_RZ_RADIUS_M, r));
        onDraftChange((d) => d && d.shape === 'circle' ? { ...d, radius_m: clamped } : d);
      });
    } else if (draftZone.shape === 'polygon' && draftZone.vertices) {
      const verts = draftZone.vertices;
      const poly = window.L.polygon(verts, { ...sharedStyle, interactive: false }).addTo(layer);

      // Vertex handles — drag to reshape; shift-click or right-click to remove
      // (subject to the 3-vertex floor).
      verts.forEach((vp, idx) => {
        const m = window.L.marker(vp, {
          icon: window.L.divIcon({
            className: '',
            html: '<div class="fmx-draft-vertex" title="Drag to move · shift-click to remove"></div>',
            iconSize: [12, 12],
            iconAnchor: [6, 6]
          }),
          draggable: true,
          zIndexOffset: 2100 + idx
        }).addTo(layer);
        m.on('drag', (e) => {
          const ll = e.target.getLatLng();
          const latlngs = poly.getLatLngs()[0].slice();
          latlngs[idx] = ll;
          poly.setLatLngs(latlngs);
        });
        m.on('dragend', (e) => {
          const ll = e.target.getLatLng();
          onDraftChange((d) => {
            if (!d || d.shape !== 'polygon') return d;
            const nv = d.vertices.slice();
            nv[idx] = [ll.lat, ll.lng];
            return { ...d, vertices: nv };
          });
        });
        const removeVertex = (e) => {
          window.L.DomEvent.stop(e);
          onDraftChange((d) => {
            if (!d || d.shape !== 'polygon') return d;
            if (d.vertices.length <= 3) return d;
            const nv = d.vertices.slice();
            nv.splice(idx, 1);
            return { ...d, vertices: nv };
          });
        };
        m.on('contextmenu', removeVertex);
        m.on('click', (e) => {
          if (e.originalEvent && e.originalEvent.shiftKey) removeVertex(e);
        });
      });

      // Midpoint “+” handles between consecutive vertices — click to insert a
      // new vertex at that point (up to the MAX_POLYGON_VERTICES cap).
      if (verts.length < MAX_POLYGON_VERTICES) {
        for (let i = 0; i < verts.length; i++) {
          const a = verts[i];
          const b = verts[(i + 1) % verts.length];
          const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
          const insertAt = i + 1;
          const mm = window.L.marker(mid, {
            icon: window.L.divIcon({
              className: '',
              html: '<div class="fmx-draft-mid" title="Click to add vertex"></div>',
              iconSize: [10, 10],
              iconAnchor: [5, 5]
            }),
            draggable: false,
            zIndexOffset: 1900 + i,
            keyboard: false
          }).addTo(layer);
          mm.on('click', (e) => {
            window.L.DomEvent.stop(e);
            onDraftChange((d) => {
              if (!d || d.shape !== 'polygon') return d;
              if (d.vertices.length >= MAX_POLYGON_VERTICES) return d;
              const nv = d.vertices.slice();
              nv.splice(insertAt, 0, mid);
              return { ...d, vertices: nv };
            });
          });
        }
      }
    }
  }, [draftZone, onDraftChange]);

  /* Cluster popover positioning: write transform directly to the DOM node on
     every map move/zoom so the popover stays glued to the cluster's
     geographic anchor without triggering React re-renders during pan. */
  const popoverRef = useRef(null);
  useEffect(() => {
    const map = mapRef.current;
    if (!map || !clusterPopover) return;
    const update = () => {
      const el = popoverRef.current;
      if (!el) return;
      const px = map.latLngToContainerPoint(clusterPopover.latLng);
      el.style.transform = `translate3d(${Math.round(px.x - el.offsetWidth / 2)}px, ${Math.round(px.y - el.offsetHeight - 28)}px, 0)`;
    };
    update();
    // run after layout has been measured (popover height needs to be known)
    const raf = requestAnimationFrame(update);
    map.on('move', update);
    map.on('zoom', update);
    map.on('resize', update);
    return () => {
      cancelAnimationFrame(raf);
      map.off('move', update);
      map.off('zoom', update);
      map.off('resize', update);
    };
  }, [clusterPopover, zoomTick]);

  return (
    <React.Fragment>
      <div ref={mapEl} style={{ position: 'absolute', inset: 0 }} />
      {clusterPopover &&
        <div
          ref={popoverRef}
          style={{ position: 'absolute', top: 0, left: 0, transform: 'translate3d(-9999px, -9999px, 0)', pointerEvents: 'auto' }}>
          <ClusterPopover
            cluster={clusterPopover}
            onSelect={onSelect}
            onClose={onPopoverClose}
            onSpread={onSpread}
            onCollapse={onCollapse}
            onZoom={onZoomCluster}
            spread={!!(spiderIds && spiderIds.length)} />
        </div>
      }
    </React.Fragment>);

}

/* ─── Saved-zone row with click-to-rename ──────────────────── */
function ZoneRow({ zone, onRename, onRemove, onZoom }) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(zone.name);
  const inputRef = useRef(null);
  useEffect(() => {
    if (editing && inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
  }, [editing]);
  const commit = () => {
    const trimmed = (draft || '').trim();
    if (trimmed && trimmed !== zone.name && onRename) onRename(zone.id, trimmed);
    setEditing(false);
    setDraft(trimmed || zone.name);
  };
  const cancel = () => {
    setEditing(false);
    setDraft(zone.name);
  };
  return (
    <li className="fmx-mapsettings-zone">
      <span className="dot" aria-hidden="true"></span>
      {editing ?
        <input
          ref={inputRef}
          type="text"
          className="name-edit"
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onBlur={commit}
          onKeyDown={(e) => {
            if (e.key === 'Enter') { e.preventDefault(); commit(); }
            else if (e.key === 'Escape') { e.preventDefault(); cancel(); }
          }} />
        :
        <button
          type="button"
          className="name name-btn"
          title={`Zoom to "${zone.name}"`}
          onClick={() => onZoom && onZoom(zone)}>
          {zone.name}
        </button>
      }
      {!editing &&
        <button
          type="button"
          className="ed"
          title="Rename zone"
          aria-label={`Rename ${zone.name}`}
          onClick={() => setEditing(true)}>
          {I.pencil}
        </button>
      }
      <button
        type="button"
        className="rm"
        title="Remove zone"
        aria-label={`Remove ${zone.name}`}
        onClick={() => onRemove && onRemove(zone.id)}>
        {I.trash}
      </button>
    </li>);
}

/* ─── Confirm dialog ─────────────────────────────────────────── */
/* ─── Document viewer ────────────────────────────────────────────
   Full-screen modal that renders a realistic mock of a shipment
   document (Bill of Lading / Proof of Delivery) drawn from the
   shipment's own data. Rendered into document.body so it floats
   above the detail panel. */
function DocViewer({ doc, onClose }) {
  const [zoom, setZoom] = useState(1);
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onClose && onClose(); }
      else if ((e.key === '+' || e.key === '=') && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setZoom((z) => Math.min(2, +(z + 0.25).toFixed(2))); }
      else if (e.key === '-' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setZoom((z) => Math.max(0.5, +(z - 0.25).toFixed(2))); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const zoomOut = () => setZoom((z) => Math.max(0.5, +(z - 0.25).toFixed(2)));
  const zoomIn = () => setZoom((z) => Math.min(2, +(z + 0.25).toFixed(2)));
  const s = doc.s;
  const isPOD = doc.kind === 'pod';
  const fmtDate = (iso) => iso ? new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '—';
  const fmtDateTime = (iso) => iso ? new Date(iso).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' }) : '—';
  const carrier = (s.carriers && s.carriers.find((c) => c.active)) || (s.carriers && s.carriers[0]);
  const carrierName = carrier ? carrier.name : 'Assigned Carrier';
  const proNumber = `PRO ${1000000 + (s.id * 748301 % 8999999)}`;
  return ReactDOM.createPortal(
    <div className="fmx-docv-backdrop" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose && onClose(); }}>
      <div className="fmx-docv" role="dialog" aria-modal="true" aria-label={doc.name}>
        <div className="fmx-docv-bar">
          <span className="fmx-docv-bar-ico">{I.file}</span>
          <div className="fmx-docv-bar-txt">
            <span className="fmx-docv-bar-name">{doc.name}</span>
            <span className="fmx-docv-bar-sub">{doc.file}</span>
          </div>
          <div className="fmx-docv-zoom">
            <button type="button" className="fmx-docv-zbtn" onClick={zoomOut} disabled={zoom <= 0.5} aria-label="Zoom out">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><line x1="5" y1="12" x2="19" y2="12" /></svg>
            </button>
            <button type="button" className="fmx-docv-zval" onClick={() => setZoom(1)} title="Reset zoom">{Math.round(zoom * 100)}%</button>
            <button type="button" className="fmx-docv-zbtn" onClick={zoomIn} disabled={zoom >= 2} aria-label="Zoom in">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
            </button>
          </div>
          <button type="button" className="fmx-docv-bar-btn" aria-label="Download">{I.download}</button>
          <button type="button" className="fmx-docv-bar-btn" onClick={onClose} aria-label="Close">{I.close}</button>
        </div>
        <div className="fmx-docv-scroll">
          <div className="fmx-docv-page" style={{ zoom }}>
            <div className="fmx-docv-head">
              <div className="fmx-docv-brand">
                <div className="fmx-docv-brand-mark">IF</div>
                <div>
                  <div className="fmx-docv-brand-name">Infomatics Freight</div>
                  <div className="fmx-docv-brand-sub">Uniform Straight Bill of Lading</div>
                </div>
              </div>
              <div className="fmx-docv-title">
                <div className="fmx-docv-title-lg">{isPOD ? 'PROOF OF DELIVERY' : 'BILL OF LADING'}</div>
                <div className="fmx-docv-title-sm">{s.bol}</div>
              </div>
            </div>

            <div className="fmx-docv-grid2">
              <div className="fmx-docv-box">
                <div className="fmx-docv-box-h">Ship From</div>
                <div className="fmx-docv-box-name">{s.shipper}</div>
                <div className="fmx-docv-box-line">{s.origin}</div>
              </div>
              <div className="fmx-docv-box">
                <div className="fmx-docv-box-h">Ship To (Consignee)</div>
                <div className="fmx-docv-box-name">{s.consignee}</div>
                <div className="fmx-docv-box-line">{s.dest}</div>
              </div>
            </div>

            <div className="fmx-docv-meta">
              <div className="fmx-docv-meta-i"><span>Carrier</span><b>{carrierName}</b></div>
              <div className="fmx-docv-meta-i"><span>Pro No.</span><b>{proNumber}</b></div>
              <div className="fmx-docv-meta-i"><span>{isPOD ? 'Delivered' : 'Pickup Appt'}</span><b>{isPOD ? fmtDateTime(s.eta) : fmtDateTime(s.appt)}</b></div>
              <div className="fmx-docv-meta-i"><span>References</span><b>{(s.refs || []).join(', ') || '—'}</b></div>
            </div>

            <table className="fmx-docv-table">
              <thead>
                <tr><th>Handling Units</th><th>Package Type</th><th>Description of Articles</th><th className="r">Weight (lb)</th></tr>
              </thead>
              <tbody>
                <tr>
                  <td>{s.pallets || 1}</td>
                  <td>Pallet{(s.pallets || 1) > 1 ? 's' : ''}</td>
                  <td>{s.cartons ? `${s.cartons} cartons — general commodities, palletized` : 'General commodities, palletized'}</td>
                  <td className="r">{s.wt ? s.wt.toLocaleString() : '—'}</td>
                </tr>
                <tr className="fmx-docv-total">
                  <td colSpan={3}>Total</td>
                  <td className="r">{s.wt ? `${s.wt.toLocaleString()} lb` : '—'}</td>
                </tr>
              </tbody>
            </table>

            {isPOD ?
              <div className="fmx-docv-sign">
                <div className="fmx-docv-sign-col">
                  <div className="fmx-docv-sign-line fmx-docv-sign-cursive">{s.consignee.split(' ').map((w) => w[0]).join('')} · Received</div>
                  <div className="fmx-docv-sign-lbl">Received in good condition by</div>
                </div>
                <div className="fmx-docv-sign-col">
                  <div className="fmx-docv-sign-line">{fmtDateTime(s.eta)}</div>
                  <div className="fmx-docv-sign-lbl">Date &amp; time of delivery</div>
                </div>
              </div> :
              <div className="fmx-docv-terms">
                <div className="fmx-docv-terms-h">Freight Charge Terms</div>
                <p>Received, subject to individually determined rates or contracts that have been agreed upon in writing between the carrier and shipper. This is to certify that the above named materials are properly classified, described, packaged, marked and labeled, and are in proper condition for transportation.</p>
                <div className="fmx-docv-sign">
                  <div className="fmx-docv-sign-col">
                    <div className="fmx-docv-sign-line">&nbsp;</div>
                    <div className="fmx-docv-sign-lbl">Shipper signature</div>
                  </div>
                  <div className="fmx-docv-sign-col">
                    <div className="fmx-docv-sign-line">&nbsp;</div>
                    <div className="fmx-docv-sign-lbl">Carrier signature</div>
                  </div>
                </div>
              </div>
            }
            <div className="fmx-docv-foot">{doc.file} · Generated {fmtDate(new Date().toISOString())} · Page 1 of 1</div>
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}

/* Small modal for destructive confirmations (delete one zone / clear all).
   Rendered into document.body so it floats above the menu and isn't
   clipped by any ancestor overflow. */
function ConfirmDialog({ title, message, confirmLabel, cancelLabel, onConfirm, onCancel }) {
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onCancel && onCancel(); }
      else if (e.key === 'Enter') { e.preventDefault(); onConfirm && onConfirm(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onConfirm, onCancel]);
  return ReactDOM.createPortal(
    <div className="fmx-confirm-backdrop" onMouseDown={(e) => {
      // backdrop click cancels; ignore clicks that started inside the card
      if (e.target === e.currentTarget) onCancel && onCancel();
    }}>
      <div className="fmx-confirm" role="alertdialog" aria-modal="true" aria-labelledby="fmx-confirm-title">
        <div id="fmx-confirm-title" className="fmx-confirm-title">{title}</div>
        {message && <div className="fmx-confirm-msg">{message}</div>}
        <div className="fmx-confirm-actions">
          <button type="button" className="fmx-confirm-btn" onClick={onCancel}>
            {cancelLabel || 'Cancel'}
          </button>
          <button type="button" className="fmx-confirm-btn is-danger" onClick={onConfirm} autoFocus>
            {confirmLabel || 'Confirm'}
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

function MapSettings({ addRedZoneMode, onAddRedZone, onClearRedZones, hasCustomZones, zones, onRemoveZone, onRenameZone, mapApiRef }) {
  const [open, setOpen] = useState(false);
  // pending = null | { kind: 'one', id, name } | { kind: 'all' }
  const [pending, setPending] = useState(null);
  const rootRef = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
    };
    window.addEventListener('mousedown', onDown);
    return () => window.removeEventListener('mousedown', onDown);
  }, [open]);
  // Fly the map to a saved zone. Circles use flyTo on center; polygons fit
  // their vertex bounds with a small pad so the whole shape is visible.
  const zoomToZone = useCallback((z) => {
    const map = mapApiRef && mapApiRef.current;
    if (!map) return;
    setOpen(false);
    if (z.shape === 'polygon' && Array.isArray(z.vertices) && z.vertices.length >= 3) {
      map.flyToBounds(z.vertices, { padding: [80, 80], duration: 1.0, maxZoom: 15, easeLinearity: 0.25 });
    } else if (z.center) {
      // Pick a zoom level that comfortably frames the radius.
      const r = z.radius_m || DEFAULT_RZ_RADIUS_M;
      const target = r > 2000 ? 12 : r > 800 ? 13 : 14;
      map.flyTo(z.center, Math.max(map.getZoom() || 5, target), { duration: 1.0, easeLinearity: 0.25 });
    }
  }, [mapApiRef]);
  return (
    <div className="fmx-mapsettings" ref={rootRef}>
      <button
        type="button"
        className={`fmx-mapsettings-btn${open ? ' is-open' : ''}${addRedZoneMode ? ' is-active' : ''}`}
        title="Map settings"
        onClick={() => setOpen((v) => !v)}
        aria-pressed={open}
        aria-haspopup="menu">
        {I.gear}
      </button>
      {open &&
      <div className="fmx-mapsettings-menu" role="menu">
        <div className="fmx-mapsettings-h">Red zones</div>
        <button
          type="button"
          className="fmx-mapsettings-item"
          onClick={() => { setOpen(false); onAddRedZone(); }}>
          <span className="ico">{I.plus}</span>
          <span className="lbl">
            <span className="t">Add red zone</span>
            <span className="d">Search an address or click the map.</span>
          </span>
        </button>
        {hasCustomZones &&
          <React.Fragment>
            <div className="fmx-mapsettings-sep" aria-hidden="true"></div>
            <div className="fmx-mapsettings-h fmx-mapsettings-h-row">
              <span>{zones.length === 1 ? '1 zone' : zones.length + ' zones'}</span>
              {zones.length > 1 &&
                <button
                  type="button"
                  className="fmx-mapsettings-clearlink"
                  onClick={() => setPending({ kind: 'all' })}>
                  Clear all
                </button>
              }
            </div>
            <ul className="fmx-mapsettings-zones" role="list">
              {zones.map((z) =>
                <ZoneRow
                  key={z.id}
                  zone={z}
                  onRename={onRenameZone}
                  onRemove={(id) => setPending({ kind: 'one', id, name: z.name })}
                  onZoom={zoomToZone} />
              )}
            </ul>
          </React.Fragment>
        }
      </div>
      }
      {pending && <ConfirmDialog
        title={pending.kind === 'all' ? 'Clear all red zones?' : 'Delete this red zone?'}
        message={pending.kind === 'all'
          ? `This will remove ${zones.length === 1 ? '1 red zone' : zones.length + ' red zones'} from the map. This can't be undone.`
          : `"${pending.name}" will be removed from the map. This can't be undone.`}
        confirmLabel={pending.kind === 'all' ? 'Clear all' : 'Delete'}
        onCancel={() => setPending(null)}
        onConfirm={() => {
          if (pending.kind === 'all') onClearRedZones();
          else if (pending.kind === 'one') onRemoveZone(pending.id);
          setPending(null);
        }} />}
    </div>);

}

/* ─── Placement banner ─────────────────────────────────────────
   Appears at top of map BEFORE a draft exists. Lets the user search
   an address or coordinates to drop a 250m draft, or just click the
   map. */
function PlacementBanner({ onPick, onCancel }) {
  const [query, setQuery] = useState('');
  const inputRef = useRef(null);
  useEffect(() => {
    // autofocus the search so the user can start typing right away
    if (inputRef.current) inputRef.current.focus();
  }, []);
  const coords = parseCoords(query);
  const suggestions = useMemo(() => {
    if (coords) return [];
    const q = query.trim().toLowerCase();
    if (!q) return [];
    return Object.keys(CITY).filter((c) => c.toLowerCase().includes(q)).slice(0, 6);
  }, [query, coords]);
  const pickCity = (cityName) => {
    const c = CITY[cityName];
    if (!c) return;
    onPick(c, cityName);
  };
  const submit = () => {
    if (coords) {
      // Coordinates don't get a meaningful auto-name — let the parent
      // assign a numbered "Zone N" instead of pasting raw lat/lng.
      onPick(coords);
      return;
    }
    if (suggestions.length > 0) pickCity(suggestions[0]);
  };
  return (
    <div className="fmx-place-banner" role="region" aria-label="Place red zone">
      <div className="fmx-place-banner-lead">
        <span className="ico">{I.crosshair}</span>
        <span>Place red zone</span>
      </div>
      <div className="fmx-place-banner-search">
        <span className="ico">{I.search}</span>
        <input
          ref={inputRef}
          type="text"
          value={query}
          placeholder="Search address, city, zip"
          onChange={(e) => setQuery(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') { e.preventDefault(); submit(); }
            else if (e.key === 'Escape') { e.preventDefault(); onCancel(); }
          }} />
        {(coords || suggestions.length > 0 || query.trim()) &&
          <div className="fmx-place-suggest" role="listbox">
            {coords &&
              <button
                type="button"
                className="sug"
                onMouseDown={(e) => { e.preventDefault(); onPick(coords); }}>
                <span className="pin">{I.crosshair}</span>
                <span>{coords[0].toFixed(4)}, {coords[1].toFixed(4)}</span>
                <span className="kind">Coords</span>
              </button>
            }
            {!coords && suggestions.map((c) =>
              <button
                key={c}
                type="button"
                className="sug"
                onMouseDown={(e) => { e.preventDefault(); pickCity(c); }}>
                <span className="pin">{I.mapPin}</span>
                <span>{c}</span>
                <span className="kind">Address</span>
              </button>
            )}
            {!coords && suggestions.length === 0 && query.trim() &&
              <div className="empty">No matches. Try a city or "lat, lng".</div>
            }
          </div>
        }
      </div>
      <button type="button" className="fmx-place-banner-cancel" onClick={onCancel}>
        Cancel
      </button>
    </div>);
}

/* ─── Drawing toolbar ──────────────────────────────────────────
   Once a draft zone exists, this toolbar lets the user tune the
   geofence: rename it, switch between Circle and Polygon shapes,
   adjust the radius (circle), or see the vertex count (polygon).
   Save commits it to the saved-zones list; Cancel discards it. */
/* ─── Tooltip ───────────────────────────────────────────────────── */
/* Small portal-based tooltip used inside overflow:hidden toolbars where
   a CSS ::after can't escape the bounding box. Anchors to the wrapping
   span and positions itself above on hover/focus. */
function Tip({ text, children }) {
  const ref = useRef(null);
  const [pos, setPos] = useState(null);
  const show = () => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    setPos({ left: r.left + r.width / 2, top: r.top });
  };
  const hide = () => setPos(null);
  return (
    <span
      ref={ref}
      className="fmx-tip"
      tabIndex={0}
      role="button"
      aria-label="Editing tips"
      onMouseEnter={show}
      onMouseLeave={hide}
      onFocus={show}
      onBlur={hide}>
      {children}
      {pos && ReactDOM.createPortal(
        <div className="fmx-tip-bubble" style={{ left: pos.left, top: pos.top }}>
          {text}
        </div>,
        document.body
      )}
    </span>
  );
}

/* ─── DrawToolbar ───────────────────────────────────────────────── */
function DrawToolbar({ draft, onNameChange, onShapeChange, onRadiusChange, onSave, onCancel }) {
  if (!draft) return null;
  const isCircle = draft.shape === 'circle';
  const isPoly = draft.shape === 'polygon';
  const vcount = isPoly && draft.vertices ? draft.vertices.length : 0;
  const atVertexCap = vcount >= MAX_POLYGON_VERTICES;
  return (
    <div className="fmx-drawtoolbar" role="region" aria-label="Edit red zone">
      {/* Row 1 — identity + save/cancel actions (always reachable) */}
      <div className="fmx-drawtoolbar-row fmx-drawtoolbar-row-1">
        <div className="fmx-drawtoolbar-sec fmx-drawtoolbar-sec-name">
          <span className="fmx-drawtoolbar-mark">{I.crosshair}</span>
          <input
            type="text"
            className="fmx-drawtoolbar-name"
            value={draft.name || ''}
            placeholder="New red zone"
            onChange={(e) => onNameChange(e.target.value)} />
        </div>
        <div className="fmx-drawtoolbar-actions">
          <button type="button" className="fmx-drawtoolbar-btn" onClick={onCancel}>
            Cancel
          </button>
          <button
            type="button"
            className="fmx-drawtoolbar-btn is-primary"
            onClick={onSave}>
            Save
          </button>
        </div>
      </div>

      {/* Row 2 — shape + measure */}
      <div className="fmx-drawtoolbar-row fmx-drawtoolbar-row-2">
        <div className="fmx-drawtoolbar-sec">
          <div className="fmx-shape-toggle" role="group" aria-label="Shape">
            <button
              type="button"
              className={`fmx-shape-btn${isCircle ? ' is-on' : ''}`}
              onClick={() => onShapeChange('circle')}
              aria-pressed={isCircle}>
              {I.circleShape}<span>Circle</span>
            </button>
            <button
              type="button"
              className={`fmx-shape-btn${isPoly ? ' is-on' : ''}`}
              onClick={() => onShapeChange('polygon')}
              aria-pressed={isPoly}
              title="Polygon — up to 40 vertices">
              {I.polygonShape}<span>Polygon</span>
            </button>
          </div>
        </div>
        <div className="fmx-drawtoolbar-sec fmx-drawtoolbar-sec-measure">
          {isCircle ?
            <div className="fmx-drawtoolbar-stat">
              <div className="slider-row">
                <span className="k">Radius</span>
                <input
                  type="range"
                  min={MIN_RZ_RADIUS_M}
                  max={MAX_RZ_RADIUS_M}
                  step={10}
                  value={Math.round(draft.radius_m || DEFAULT_RZ_RADIUS_M)}
                  onChange={(e) => onRadiusChange(parseFloat(e.target.value))} />
                <span className="v">{formatRadius(draft.radius_m || DEFAULT_RZ_RADIUS_M)}</span>
              </div>
            </div>
            :
            <div className="fmx-drawtoolbar-stat">
              <div className="slider-row">
                <span className="k">Vertices</span>
                <span className={`v${atVertexCap ? ' is-warn' : ''}`}>
                  {vcount} / {MAX_POLYGON_VERTICES}
                </span>
              </div>
              <span className="hint">
                <span className="txt">Drag to reshape</span>
                <Tip text={"Drag a vertex to move it.\nClick a midpoint to add a vertex.\nShift-click a vertex to remove it."}>
                  {I.info}
                </Tip>
              </span>
            </div>
          }
        </div>
      </div>
    </div>);
}

function MapOverlays({ overlays, setOverlays, hidden, mobileOpen, onMobileClose }) {
  const rows = [
  { key: 'redzone', label: 'Red Zone', ico: I.alert },
  { key: 'traffic', label: 'Traffic', ico: I.car },
  { key: 'weather', label: 'Weather', ico: I.cloud }];

  return (
    <div className={`fmx-overlays${hidden ? ' is-hidden' : ''}${mobileOpen ? ' is-mobile-open' : ''}`}>
      <div className="fmx-overlays-title">Map Overlays</div>
      {rows.map((r) =>
      <div key={r.key} className="fmx-ov-row">
          <span className="ico">{r.ico}</span>
          <span className="lbl">{r.label}</span>
          <button
          className={`fmx-toggle${overlays[r.key] ? ' is-on' : ''}`}
          onClick={() => setOverlays({ ...overlays, [r.key]: !overlays[r.key] })}
          aria-pressed={overlays[r.key]} />
        
        </div>
      )}
    </div>);

}

/* ─── View Switcher (shared style with Freight Table) ─────────── */
function ViewSwitcher() {
  const [active, setActive] = useState('map');
  const views = [
  { key: 'table', label: 'Table View', href: 'Freight Table.html' },
  { key: 'map', label: 'Map View', href: 'Freight Map.html' }];


  return (
    <div className="view-switcher" role="tablist" aria-label="View">
      {views.map((v) => {
        const isActive = active === v.key;
        const cls = `view-switcher-btn${isActive ? ' is-active' : ''}`;
        if (v.href && !isActive) {
          return <a key={v.key} href={v.href} className={cls} role="tab" aria-selected={false}>{v.label}</a>;
        }
        return (
          <button key={v.key} type="button" role="tab" aria-selected={isActive} onClick={() => setActive(v.key)} className={cls}>
            {v.label}
          </button>);

      })}
    </div>);

}

/* ─── Multi-select dropdown ─────────────────────────────────────── */
function MultiSelectFilter({ label, icon, placeholder, options, value, onChange }) {
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const handler = (e) => {if (ref.current && !ref.current.contains(e.target)) setOpen(false);};
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  const toggle = (v) => {
    onChange(value.includes(v) ? value.filter((x) => x !== v) : [...value, v]);
  };

  const visible = search.trim() ?
  options.filter((o) => o.toLowerCase().includes(search.trim().toLowerCase())) :
  options;

  const displayLabel = value.length === 0 ?
  placeholder :
  value.length === 1 ?
  value[0] :
  `${value.length} selected`;

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div className="fmx-fp-field">
        <span className="ico">{icon}</span>
        <button
          type="button"
          className={`fmx-fp-select${value.length === 0 ? ' is-empty' : ''}`}
          onClick={() => setOpen((o) => !o)}
          style={{ textAlign: 'left', cursor: 'pointer', width: '100%' }}>
          {displayLabel}
        </button>
      </div>
      {open &&
      <div className="fmx-ms-panel" style={{
        position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4,
        background: '#fff', border: '1px solid var(--line)', borderRadius: 8,
        boxShadow: '0 8px 24px rgba(0,0,0,.13)', zIndex: 200,
        display: 'flex', flexDirection: 'column', overflow: 'hidden'
      }}>
          <div className="fmx-ms-search" style={{ padding: '6px 8px', borderBottom: '1px solid var(--line)' }}>
            <input
            className="fmx-fp-input fmx-ms-search-input"
            autoFocus
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            placeholder="Search…"
            style={{ height: 28, fontSize: 12 }} />
          </div>
          {value.length > 0 &&
        <button
          type="button"
          className="fmx-ms-clear"
          onClick={() => onChange([])}
          style={{
            background: 'transparent', border: 0, borderBottom: '1px solid var(--line)',
            padding: '6px 10px', fontSize: 11, fontWeight: 600,
            color: 'var(--blue)', cursor: 'pointer', textAlign: 'left',
            fontFamily: 'inherit'
          }}>
              Clear all
            </button>
        }
          <div className="fmx-ms-list" style={{ maxHeight: 180, overflowY: 'auto' }}>
            {visible.map((opt) => {
            const checked = value.includes(opt);
            return (
              <div
                key={opt}
                className={`fmx-ms-item${checked ? ' is-checked' : ''}`}
                onClick={() => toggle(opt)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 8,
                  padding: '5px 10px', cursor: 'pointer',
                  background: checked ? 'rgba(255,214,2,0.12)' : 'transparent',
                  transition: 'background 80ms', fontSize: 12, color: 'var(--ink)'
                }}
                onMouseEnter={(e) => {if (!checked) e.currentTarget.style.background = '#fafafa';}}
                onMouseLeave={(e) => {if (!checked) e.currentTarget.style.background = 'transparent';}}>
                  <div style={{
                  width: 14, height: 14, borderRadius: 3, flexShrink: 0,
                  border: `1.5px solid ${checked ? '#D4A800' : 'var(--line)'}`,
                  background: checked ? '#FFCC00' : '#fff',
                  display: 'flex', alignItems: 'center', justifyContent: 'center'
                }}>
                    {checked &&
                  <svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#3a2e00" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M20 6 9 17l-5-5" />
                      </svg>
                  }
                  </div>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{opt}</span>
                </div>);

          })}
            {visible.length === 0 &&
          <div style={{ padding: '10px', fontSize: 12, color: 'var(--faint)', textAlign: 'center' }}>No matches</div>
          }
          </div>
        </div>
      }
    </div>);

}

/* ─── Calendar popover (custom, styled) ────────────────────────── */
function formatDateLabel(iso) {
  if (!iso) return '';
  const [y, m, d] = iso.split('-').map(Number);
  const dt = new Date(y, m - 1, d);
  return dt.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
}
function pad2(n) { return String(n).padStart(2, '0'); }
function toIso(y, m, d) { return `${y}-${pad2(m + 1)}-${pad2(d)}`; }

function CalendarPopover({ value, onPick, onClear }) {
  const today = new Date();
  const seed = value ? new Date(value + 'T00:00:00') : today;
  const [view, setView] = useState({ y: seed.getFullYear(), m: seed.getMonth() });
  const firstOfMonth = new Date(view.y, view.m, 1);
  const startDay = (firstOfMonth.getDay() + 6) % 7; // Monday-first
  const daysInMonth = new Date(view.y, view.m + 1, 0).getDate();
  const daysInPrev = new Date(view.y, view.m, 0).getDate();
  const cells = [];
  for (let i = 0; i < startDay; i++) cells.push({ d: daysInPrev - startDay + 1 + i, out: true, before: true });
  for (let d = 1; d <= daysInMonth; d++) cells.push({ d, out: false });
  while (cells.length % 7 !== 0 || cells.length < 42) cells.push({ d: cells.length - startDay - daysInMonth + 1, out: true, after: true });
  const monthLabel = firstOfMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric' });
  const nav = (delta) => setView((v) => {
    const nm = v.m + delta;
    return { y: v.y + Math.floor(nm / 12), m: (nm % 12 + 12) % 12 };
  });
  const isToday = (d) => today.getFullYear() === view.y && today.getMonth() === view.m && today.getDate() === d;
  const isSelected = (d) => value === toIso(view.y, view.m, d);
  return (
    <div className="fmx-cal-pop" role="dialog" aria-label="Choose a date">
      <div className="fmx-cal-head">
        <button type="button" className="fmx-cal-nav" onClick={() => nav(-1)} aria-label="Previous month">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
        </button>
        <span className="fmx-cal-title">{monthLabel}</span>
        <button type="button" className="fmx-cal-nav" onClick={() => nav(1)} aria-label="Next month">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
        </button>
      </div>
      <div className="fmx-cal-dow">
        {['M','T','W','T','F','S','S'].map((w, i) => <span key={i}>{w}</span>)}
      </div>
      <div className="fmx-cal-grid">
        {cells.map((c, i) => {
          if (c.out) return <span key={i} className="fmx-cal-cell is-out">{c.d}</span>;
          const cls = ['fmx-cal-cell'];
          if (isToday(c.d)) cls.push('is-today');
          if (isSelected(c.d)) cls.push('is-sel');
          return (
            <button
              key={i}
              type="button"
              className={cls.join(' ')}
              onClick={() => onPick(toIso(view.y, view.m, c.d))}>
              {c.d}
            </button>
          );
        })}
      </div>
      <div className="fmx-cal-foot">
        <button type="button" className="fmx-cal-today" onClick={() => onPick(toIso(today.getFullYear(), today.getMonth(), today.getDate()))}>Today</button>
        {value && <button type="button" className="fmx-cal-clear" onClick={onClear}>Clear</button>}
      </div>
    </div>);
}

/* ─── Filter panel (overlays the shipment list) ─────────────────── */
function FilterPanel({ filters, setFilters, onClose, resultCount }) {
  const set = (patch) => setFilters((f) => ({ ...f, ...patch }));
  const [dpOpen, setDpOpen] = useState(false);
  const dpRef = useRef(null);
  useEffect(() => {
    if (!dpOpen) return;
    const onDoc = (e) => { if (dpRef.current && !dpRef.current.contains(e.target)) setDpOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [dpOpen]);
  const toggleMode = (m) => setFilters((f) => ({
    ...f, modes: f.modes.includes(m) ? f.modes.filter((x) => x !== m) : [...f.modes, m]
  }));

  return (
    <div className="fmx-filter-panel">
      <div className="fmx-fp-head">
        <span className="fmx-fp-title">Filter</span>
        <div className="fmx-fp-head-r">
          {anyFilterActive(filters) && <button type="button" className="fmx-fp-reset" onClick={() => setFilters({ ...EMPTY_FILTERS })}>Reset All</button>}
          <button type="button" className="fmx-fp-close" onClick={onClose} aria-label="Close">{I.close}</button>
        </div>
      </div>
      <div className="fmx-fp-body">
        {/* Broker section — mobile-only per Figma */}

        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Carrier</span>
          <MultiSelectFilter
            icon={I.truck}
            placeholder="Any carrier"
            options={CARRIER_OPTS}
            value={filters.carrier || []}
            onChange={(v) => set({ carrier: v })} />
        </div>
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Retailer</span>
          <MultiSelectFilter
            icon={I.pkg}
            placeholder="Any retailer"
            options={RETAILER_OPTS}
            value={filters.retailer || []}
            onChange={(v) => set({ retailer: v })} />
        </div>
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Locations</span>
          <MultiSelectFilter
            icon={I.mapPin}
            placeholder="Any origin"
            options={ORIGIN_OPTS}
            value={filters.origin || []}
            onChange={(v) => set({ origin: v })} />
          <div style={{ marginTop: 6 }}>
            <MultiSelectFilter
              icon={I.mapPin}
              placeholder="Any destination"
              options={DEST_OPTS}
              value={filters.dest || []}
              onChange={(v) => set({ dest: v })} />
          </div>
          <div style={{ marginTop: 6 }}>
            <MultiSelectFilter
              icon={I.mapPin}
              placeholder="Any transit"
              options={TRANSIT_OPTS}
              value={filters.transit || []}
              onChange={(v) => set({ transit: v })} />
          </div>
        </div>
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Mode</span>
          <div className="fmx-fp-modes">
            {[['air', 'Air'], ['ground', 'Ground'], ['sea', 'Sea']].map(([k, l]) =>
            <button key={k} type="button" className={`fmx-fp-mode${filters.modes.includes(k) ? ' is-on' : ''}`} onClick={() => toggleMode(k)}>{l}</button>
            )}
          </div>
        </div>
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Date Range</span>
          <div className="fmx-fp-radios">
            {[['all', 'All Time'], ['7', 'Last 7 days'], ['30', 'Last 30 days'], ['90', 'Last 3 months']].map(([k, l]) =>
            <label key={k} className="fmx-fp-radio">
                <input type="radio" name="fmx-daterange" checked={filters.dateRange === k} onChange={() => set({ dateRange: k, datePick: '' })} />
                {l}
              </label>
            )}
          </div>
          <div className="fmx-fp-field fmx-fp-date" ref={dpRef}>
            <span className="ico">{I.calendar}</span>
            <button
              type="button"
              className={`fmx-fp-input fmx-fp-datebtn${filters.datePick ? '' : ' is-empty'}`}
              onClick={() => setDpOpen((o) => !o)}
              aria-haspopup="dialog"
              aria-expanded={dpOpen}>
              {filters.datePick ? formatDateLabel(filters.datePick) : 'Pick a date'}
            </button>
            {dpOpen &&
              <CalendarPopover
                value={filters.datePick}
                onPick={(iso) => { set({ datePick: iso, dateRange: '' }); setDpOpen(false); }}
                onClear={() => { set({ datePick: '' }); setDpOpen(false); }} />
            }
          </div>
        </div>
      </div>
    </div>);

}

/* ─── Sort panel (overlays the shipment list) ───────────────────── */
function SortPanel({ sort, setSort, onClose }) {
  const set = (patch) => setSort((s) => ({ ...s, ...patch }));
  const groupOpts = [['none', 'None'], ['broker', 'Broker'], ['carrier', 'Carrier'], ['consignee', 'Consignee']];
  const sortOpts = [['none', 'None'], ['appt', 'Appointment date'], ['eta', 'ETA'], ['risk', 'Risk Level'], ['created', 'BOL date']];
  return (
    <div className="fmx-filter-panel">
      <div className="fmx-fp-head">
        <span className="fmx-fp-title">Sort & Group</span>
        <div className="fmx-fp-head-r">
          {(sort.groupBy !== 'none' || sort.sortBy !== 'none') && <button type="button" className="fmx-fp-reset" onClick={() => setSort({ ...EMPTY_SORT })}>Reset All</button>}
          <button type="button" className="fmx-fp-close" onClick={onClose} aria-label="Close">{I.close}</button>
        </div>
      </div>
      <div className="fmx-fp-body">
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Group by</span>
          <div className="fmx-fp-radios">
            {groupOpts.map(([k, l]) =>
            <label key={k} className="fmx-fp-radio">
                <input type="radio" name="fmx-groupby" checked={sort.groupBy === k} onChange={() => set({ groupBy: k })} />
                {l}
              </label>
            )}
          </div>
        </div>
        <div className="fmx-fp-sec">
          <span className="fmx-fp-label">Sort by</span>
          <div className="fmx-fp-radios">
            {sortOpts.map(([k, l]) =>
            <label key={k} className="fmx-fp-radio">
                <input type="radio" name="fmx-sortby" checked={sort.sortBy === k} onChange={() => set({ sortBy: k })} />
                {l}
              </label>
            )}
          </div>
        </div>
      </div>
    </div>);

}

/* ─── App ───────────────────────────────────────────────────────── */
function App() {
  const [tw, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  // Mirror to a global so standalone helpers (STAT_MATCH / ShipmentCard) can
  // see current tweak state without prop-drilling.
  window.__fmxTweaks = tw;

  const [search, setSearch] = useState('');
  const [activeTabs, setActiveTabs] = useState(DEFAULT_TABS);
  const [selectedId, setSelectedId] = useState(null);
  // Simulated data-fetch loading state. When a card is clicked we kick off a
  // short delay before the tracker / detail data is "ready"; during that
  // window a thin progress bar at the top of the map signals loading,
  // mirroring the latency of the real backend.
  const [loadingId, setLoadingId] = useState(null);
  const loadingTimerRef = useRef(null);
  const handleSelect = useCallback((id) => {
    setDetailHidden(false);
    if (loadingTimerRef.current) {
      clearTimeout(loadingTimerRef.current);
      loadingTimerRef.current = null;
    }
    if (id == null) {
      setLoadingId(null);
      setSelectedId(null);
      return;
    }
    setSelectedId(id);
    setLoadingId(id);
    // Simulated fetch. Default is a random-ish 900–1500ms so the loading bar
    // shows realistically; the Loading simulation tweak can slow it down,
    // skip it, or hold it open indefinitely.
    const mode = (latencyRef.current in FETCH_LATENCY) ? latencyRef.current : 'default';
    const delay = FETCH_LATENCY[mode]();
    if (delay === null) return; // held open until the tweak changes or another card is clicked
    loadingTimerRef.current = setTimeout(() => {
      setLoadingId((cur) => cur === id ? null : cur);
      loadingTimerRef.current = null;
    }, delay);
  }, []);
  const latencyRef = useRef(tw.fetchLatency);
  latencyRef.current = tw.fetchLatency;
  // Leaving 'stuck' releases whatever load is being held open.
  useEffect(() => {
    if (tw.fetchLatency !== 'stuck') return;
    return () => setLoadingId(null);
  }, [tw.fetchLatency]);
  const selectedIdRef = useRef(null);
  selectedIdRef.current = selectedId;
  const replayLoading = useCallback(() => {
    if (selectedIdRef.current != null) handleSelect(selectedIdRef.current);
  }, [handleSelect]);
  useEffect(() => () => {
    if (loadingTimerRef.current) clearTimeout(loadingTimerRef.current);
  }, []);
  const [overlays, setOverlays] = useState({ redzone: false, traffic: false, weather: false });
  // Custom red zones the user has SAVED. Each entry is
  // { id, name, shape: 'circle'|'polygon', center, radius_m, vertices } —
  // shape determines which fields are populated. New zones go to the front so
  // the most-recent zone is listed first in the settings menu.
  const [customRedZones, setCustomRedZones] = useState([]);
  // The in-progress (unsaved) zone being placed/adjusted. Same shape as a
  // saved zone but lives separately so Save/Cancel can commit or discard.
  const [draftZone, setDraftZone] = useState(null);
  // `addRedZoneMode` is the placement entry state: the user has hit "Add red
  // zone" but no point has been dropped yet. The next map click or address
  // search promotes things to a draft. Once a draft exists, the drawing
  // toolbar takes over and `addRedZoneMode` is no longer relevant.
  const [addRedZoneMode, setAddRedZoneMode] = useState(false);

  const startAddRedZone = useCallback(() => {
    setAddRedZoneMode(true);
    // Auto-enable the red-zone overlay so the new zone is immediately visible.
    setOverlays((o) => o.redzone ? o : { ...o, redzone: true });
  }, []);
  const cancelAddRedZone = useCallback(() => {
    setAddRedZoneMode(false);
    setDraftZone(null);
  }, []);

  // Find the nearest CITY entry to a click point so a zone has a meaningful name.
  const nearestCityName = useCallback((center) => {
    let best = null;
    let bestD = Infinity;
    const [lat, lng] = center;
    for (const name of Object.keys(CITY)) {
      const [clat, clng] = CITY[name];
      const dy = (clat - lat);
      const dx = (clng - lng) * Math.cos(lat * Math.PI / 180);
      const d = dy * dy + dx * dx;
      if (d < bestD) { bestD = d; best = name; }
    }
    // Roughly 1.5° at mid-latitudes — if the click is far from any known city,
    // fall back to a generic label.
    if (bestD > 2.25) return null;
    return best;
  }, []);

  // Start a fresh draft at `center`, autoname it, and zoom the map in so the
  // 250m default circle is actually visible. This is the entry point used by
  // BOTH the map-click and address-search flows. Map clicks have no explicit
  // name and get an incrementing "Zone N" label rather than coordinates.
  const beginDraftAt = useCallback((center, explicitName) => {
    const nextZoneName = () => {
      // Find the highest existing "Zone N" suffix among saved zones so the
      // next draft picks the next free number.
      let max = 0;
      (customRedZones || []).forEach((z) => {
        const m = /^Zone\s+(\d+)$/i.exec((z.name || '').trim());
        if (m) max = Math.max(max, parseInt(m[1], 10));
      });
      return `Zone ${max + 1}`;
    };
    const name = explicitName || nextZoneName();
    const makeDraft = () => setDraftZone({
      name,
      shape: 'circle',
      center,
      radius_m: DEFAULT_RZ_RADIUS_M,
      vertices: null
    });
    setAddRedZoneMode(false);
    // Make sure the red-zone overlay is on so the draft is rendered.
    setOverlays((o) => o.redzone ? o : { ...o, redzone: true });
    const map = mapApiRef.current;
    if (map) {
      // Smoothly fly in first, then drop the red zone draft once we've
      // landed at the new zoom — otherwise the circle pops in at the
      // current (zoomed-out) view and flashes red across the map.
      const targetZoom = Math.max(map.getZoom() || 5, 14);
      map.once('moveend', makeDraft);
      map.flyTo(center, targetZoom, { duration: 1.1, easeLinearity: 0.25 });
    } else {
      makeDraft();
    }
  }, [customRedZones]);

  // Convert the in-progress draft between circle and polygon shapes,
  // preserving area as well as possible across the switch.
  const setDraftShape = useCallback((shape) => {
    setDraftZone((d) => {
      if (!d || d.shape === shape) return d;
      if (shape === 'polygon') {
        const verts = circleToPolygon(
          d.center,
          d.radius_m || DEFAULT_RZ_RADIUS_M,
          INITIAL_POLYGON_VERTICES
        );
        return { ...d, shape: 'polygon', vertices: verts, radius_m: null };
      } else {
        // polygon → circle: use centroid + mean radius
        const c = polygonCentroid(d.vertices || []);
        const lat0 = c[0] * Math.PI / 180;
        const earth = 6378137;
        let sum = 0;
        (d.vertices || []).forEach((p) => {
          const dlat = (p[0] - c[0]) * Math.PI / 180;
          const dlng = (p[1] - c[1]) * Math.PI / 180;
          const dy = dlat * earth;
          const dx = dlng * earth * Math.cos(lat0);
          sum += Math.sqrt(dy * dy + dx * dx);
        });
        const r = (sum / ((d.vertices || []).length || 1)) || DEFAULT_RZ_RADIUS_M;
        return { ...d, shape: 'circle', center: c, radius_m: r, vertices: null };
      }
    });
  }, []);

  const updateDraftRadius = useCallback((r) => {
    setDraftZone((d) => {
      if (!d || d.shape !== 'circle') return d;
      const clamped = Math.max(MIN_RZ_RADIUS_M, Math.min(MAX_RZ_RADIUS_M, r));
      return { ...d, radius_m: clamped };
    });
  }, []);

  const updateDraftName = useCallback((name) => {
    setDraftZone((d) => d ? { ...d, name } : d);
  }, []);

  const commitDraft = useCallback(() => {
    setDraftZone((d) => {
      if (!d) return d;
      const safeName = (d.name || '').trim() || 'New red zone';
      setCustomRedZones((zs) => [{
        id: 'rz-' + Date.now(),
        name: safeName,
        shape: d.shape,
        center: d.center,
        radius_m: d.radius_m,
        vertices: d.vertices
      }, ...zs]);
      return null;
    });
  }, []);

  const cancelDraft = useCallback(() => {
    setDraftZone(null);
    setAddRedZoneMode(false);
  }, []);

  // Click on map either places the very first point of a draft (addRedZoneMode)
  // or, if a draft already exists in polygon mode, is a no-op (vertex edits
  // happen via the per-vertex handles to avoid stray clicks adding points).
  const handleMapClickAddRedZone = useCallback((center) => {
    if (draftZone) return;
    if (!addRedZoneMode) return;
    beginDraftAt(center);
  }, [addRedZoneMode, draftZone, beginDraftAt]);

  const removeRedZone = useCallback((id) => {
    setCustomRedZones((zs) => zs.filter((z) => z.id !== id));
  }, []);
  const renameRedZone = useCallback((id, name) => {
    const trimmed = (name || '').trim();
    if (!trimmed) return;
    setCustomRedZones((zs) => zs.map((z) => z.id === id ? { ...z, name: trimmed } : z));
  }, []);
  const clearCustomRedZones = useCallback(() => setCustomRedZones([]), []);

  // Esc cancels either the placement step or the in-progress draft.
  useEffect(() => {
    if (!addRedZoneMode && !draftZone) return;
    const onKey = (e) => {
      if (e.key === 'Escape') {
        setAddRedZoneMode(false);
        setDraftZone(null);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [addRedZoneMode, draftZone]);
  const [activeStats, setActiveStats] = useState([]);
  const [filters, setFilters] = useState({ ...EMPTY_FILTERS });
  const [clusterFilter, setClusterFilter] = useState(null);
  const [filtersOpen, setFiltersOpen] = useState(false);
  const [sortOpen, setSortOpen] = useState(false);
  // Mobile bottom-sheet collapsed state — starts collapsed on narrow viewports
  const [listCollapsed, setListCollapsed] = useState(
    typeof window !== 'undefined' && window.innerWidth <= 768
  );
  // Mobile-only: whether the Map Overlays popover is open
  const [mobileOverlaysOpen, setMobileOverlaysOpen] = useState(false);
  // Info-hint tooltip on the map
  const [infoHintOpen, setInfoHintOpen] = useState(false);
  const [infoHintPos, setInfoHintPos] = useState(null); // {top, left, targetRect}
  // When info hint opens, locate a cluster pin, highlight it, and anchor
  // the callout above-left of it.
  useEffect(() => {
    if (!infoHintOpen) { setInfoHintPos(null); return; }
    const compute = () => {
      const mapEl = document.querySelector('.fmx-map');
      if (!mapEl) return;
      const mapRect = mapEl.getBoundingClientRect();
      // Prefer a cluster; fall back to any current pin
      const pins = Array.from(mapEl.querySelectorAll('.fmx-pin.is-current.is-cluster'));
      const chosen = pins[0] || mapEl.querySelector('.fmx-pin.is-current');
      if (!chosen) return;
      // Mark it for the red ring
      mapEl.querySelectorAll('.fmx-pin.is-hinted').forEach((n) => n.classList.remove('is-hinted'));
      chosen.classList.add('is-hinted');
      const pinRect = chosen.getBoundingClientRect();
      const relLeft = pinRect.left - mapRect.left;
      const relTop = pinRect.top - mapRect.top;
      // Place callout above and slightly to the left of the pin
      const calloutW = Math.min(320, mapRect.width - 32);
      let left = relLeft + pinRect.width / 2 - calloutW + 30;
      left = Math.max(12, Math.min(left, mapRect.width - calloutW - 12));
      const top = Math.max(12, relTop - 12 - 130);
      setInfoHintPos({ top, left, width: calloutW, tailLeft: (relLeft + pinRect.width / 2) - left });
    };
    compute();
    const onResize = () => compute();
    window.addEventListener('resize', onResize);
    const t = setInterval(compute, 500); // re-anchor if the map pans/zooms
    return () => {
      window.removeEventListener('resize', onResize);
      clearInterval(t);
      document.querySelectorAll('.fmx-pin.is-hinted').forEach((n) => n.classList.remove('is-hinted'));
    };
  }, [infoHintOpen]);
  const [sort, setSort] = useState({ ...EMPTY_SORT });
  const [pinnedIds, setPinnedIds] = useState([]);
  const [collapsedGroups, setCollapsedGroups] = useState({});
  const [clusterPopover, setClusterPopover] = useState(null);
  const [spiderIds, setSpiderIds] = useState(null);
  const toggleGroup = (label) => setCollapsedGroups((c) => ({ ...c, [label]: !c[label] }));
  const mapApiRef = useRef(null);

  const filtered = useMemo(() => {
    let arr = SHIPMENTS;
    // Cluster filter wins over everything else — it represents an explicit
    // drilldown into a group of nearby shipments on the map.
    if (clusterFilter && clusterFilter.length) {
      arr = arr.filter((s) => clusterFilter.includes(s.id));
    } else if (activeStats.length) {
      // Needs-attention tags drive the list (OR among active tags). When none
      // are active, fall back to the status tabs.
      arr = arr.filter((s) => activeStats.some((k) => STAT_MATCH[k] && STAT_MATCH[k](s)));
    } else if (activeTabs.length) {
      arr = arr.filter((s) => activeTabs.includes(s.status));
    }
    if (search.trim()) {
      const q = search.trim().toLowerCase();
      arr = arr.filter((s) => [s.bol, s.origin, s.dest, s.shipper, s.consignee, ...(s.refs || [])].join(' ').toLowerCase().includes(q));
    }
    // Filter-panel criteria (broker / carrier / mode / date) — AND on top.
    if (filters.carrier && filters.carrier.length) {
      arr = arr.filter((s) => (s.carriers || []).some((c) => filters.carrier.includes(c.name)));
    }
    if (filters.retailer && filters.retailer.length) {
      arr = arr.filter((s) => filters.retailer.includes(s.retailer));
    }
    if (filters.origin && filters.origin.length) {
      arr = arr.filter((s) => filters.origin.includes(s.origin));
    }
    if (filters.dest && filters.dest.length) {
      arr = arr.filter((s) => filters.dest.includes(s.dest));
    }
    if (filters.transit && filters.transit.length) {
      arr = arr.filter((s) => filters.transit.includes(s.origin) || filters.transit.includes(s.dest));
    }
    if (filters.modes.length) {
      arr = arr.filter((s) => filters.modes.includes(modeOf(s)));
    }
    if (filters.dateRange || filters.datePick) {
      arr = arr.filter((s) => matchesDate(s, filters.dateRange, filters.datePick));
    }
    return arr;
  }, [search, activeTabs, activeStats, clusterFilter, filters]);

  // Pinned shipments are surfaced at the top and stay visible regardless of
  // the active filters; the rest follow in normal filtered order.
  const displayList = useMemo(() => {
    let base = filtered;
    const cmp = sortComparator(sort.sortBy);
    if (cmp) base = [...base].sort(cmp);
    if (!pinnedIds.length) return base;
    const pinnedSet = new Set(pinnedIds);
    const pinned = pinnedIds.map((id) => SHIPMENTS.find((x) => x.id === id)).filter(Boolean);
    const rest = base.filter((s) => !pinnedSet.has(s.id));
    return [...pinned, ...rest];
  }, [filtered, pinnedIds, sort.sortBy]);

  // When grouping is active, partition the displayList into ordered groups
  // (first-seen wins) so the list visibly reorders under section headers.
  const groupedList = useMemo(() => {
    if (sort.groupBy === 'none') return null;
    const map = new Map();
    displayList.forEach((s) => {
      const k = groupKeyFor(s, sort.groupBy);
      if (!map.has(k)) map.set(k, []);
      map.get(k).push(s);
    });
    return Array.from(map, ([label, items]) => ({ label, items }));
  }, [displayList, sort.groupBy]);

  const togglePin = (id) => setPinnedIds((cur) => cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]);
  const filtersActive = anyFilterActive(filters);
  const clearFilters = () => setFilters({ ...EMPTY_FILTERS });

  const toggleTab = (key) => setActiveTabs((cur) => cur.includes(key) ? cur.filter((k) => k !== key) : [...cur, key]);
  const toggleStat = (key) => setActiveStats((cur) => cur.includes(key) ? cur.filter((k) => k !== key) : [...cur, key]);
  const reset = () => {setActiveStats([]);setActiveTabs([]);setSearch('');setClusterFilter(null);setClusterPopover(null);setSpiderIds(null);};

  const onClusterClick = useCallback((ids, bounds, center) => {
    setClusterFilter(ids);
    setSelectedId(null);
    setSpiderIds(null);
    // Use the cluster center as the popover anchor (not bounds — many
    // clusters have all pins at the same lat/lng so bounds is degenerate).
    setClusterPopover({
      ids,
      bounds,
      latLng: window.L.latLng(center[0], center[1])
    });
    // Also zoom in on the cluster area so the user lands focused on it. If
    // every pin sits at the same lat/lng (degenerate bounds), just bump zoom
    // around the cluster center instead of fitting to a zero-area box.
    const map = mapApiRef.current;
    if (!map) return;
    const dy = bounds && bounds.length ? Math.max(...bounds.map((p) => p[0])) - Math.min(...bounds.map((p) => p[0])) : 0;
    const dx = bounds && bounds.length ? Math.max(...bounds.map((p) => p[1])) - Math.min(...bounds.map((p) => p[1])) : 0;
    const isDegenerate = dy < 0.05 && dx < 0.05;
    if (bounds && bounds.length > 1 && !isDegenerate) {
      map.flyToBounds(bounds, { padding: [160, 160], duration: 0.5, maxZoom: 11 });
    } else {
      const target = Math.min(11, Math.max((map.getZoom() || 4) + 2, 8));
      map.flyTo([center[0], center[1]], target, { duration: 0.5 });
    }
  }, []);

  const closeClusterPopover = useCallback(() => {
    setClusterPopover(null);
    setSpiderIds(null);
  }, []);

  const spreadCluster = useCallback(() => {
    if (!clusterPopover) return;
    setSpiderIds(clusterPopover.ids);
  }, [clusterPopover]);

  const collapseCluster = useCallback(() => {
    setSpiderIds(null);
  }, []);

  const zoomCluster = useCallback(() => {
    if (!clusterPopover) return;
    const map = mapApiRef.current;
    if (!map) return;
    const { bounds } = clusterPopover;
    if (bounds && bounds.length > 1) {
      // Add tiny jitter so degenerate (all-identical) bounds still pan to point
      const padded = bounds.map((p, i) => [p[0] + i * 1e-5, p[1] + i * 1e-5]);
      map.flyToBounds(padded, { padding: [120, 120], duration: 0.5, maxZoom: 12 });
    } else {
      map.flyTo(clusterPopover.latLng, Math.min(12, (map.getZoom() || 4) + 3), { duration: 0.5 });
    }
  }, [clusterPopover]);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key !== 'Escape') return;
      if (clusterPopover) {
        setClusterPopover(null);
        setSpiderIds(null);
        return;
      }
      setSelectedId(null);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [clusterPopover]);

  // Clear popover/spider whenever the user selects a shipment (the right
  // panel takes over and per-shipment trackers replace the spider pins).
  useEffect(() => {
    if (selectedId != null) {
      setClusterPopover(null);
      setSpiderIds(null);
    }
  }, [selectedId]);

  // Deep-link handler: ?bol=...&openDetail=true selects a shipment, opens its
  // detail panel, and scrolls the matching card into view in the left list.
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const bolParam = params.get('bol');
    const openDetail = params.get('openDetail') === 'true';
    if (!bolParam) return;
    const match = SHIPMENTS.find((x) => x.bol === bolParam);
    if (!match) return;
    if (openDetail) setSelectedId(match.id);
    // Defer scroll until cards have rendered.
    const tries = [60, 180, 360];
    tries.forEach((delay) => setTimeout(() => {
      const card = document.querySelector(`.fmx-card[data-bol="${CSS.escape(bolParam)}"]`);
      const list = document.querySelector('.fmx-cards');
      if (card && list) {
        const offset = card.offsetTop - list.offsetTop - 16;
        list.scrollTop = Math.max(0, offset);
      }
    }, delay));
  }, []);

  // Command Center "View on map" bridge: the CC iframe posts { ccViewOnMap: ref };
  // resolve it to a shipment (by bol/ref, else the first red-zone unit) and open it.
  useEffect(() => {
    const onMsg = (e) => {
      if (!e.data || !e.data.ccViewOnMap) return;
      const ref = String(e.data.ccViewOnMap);
      let match = SHIPMENTS.find((x) => x.bol === ref || (x.refs || []).includes(ref));
      if (!match) match = SHIPMENTS.find((x) => x.redZone);
      if (!match) return;
      setClusterPopover(null);
      setSpiderIds(null);
      handleSelect(match.id);
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, [handleSelect]);

  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const [detailHidden, setDetailHidden] = useState(false);
  const [railExpanded, setRailExpanded] = useState(() => {
    try { return localStorage.getItem('fmx-rail-expanded') === '1'; } catch (e) { return false; }
  });
  useEffect(() => {
    try { localStorage.setItem('fmx-rail-expanded', railExpanded ? '1' : '0'); } catch (e) {}
  }, [railExpanded]);
  const [sbCollapsed, setSbCollapsed] = useState(() => {
    try { const v = localStorage.getItem('broker-sidebar-collapsed'); return v === null ? true : v === '1'; } catch (e) { return true; }
  });
  const toggleSb = () => setSbCollapsed((c) => {
    const n = !c;
    try { localStorage.setItem('broker-sidebar-collapsed', n ? '1' : '0'); } catch (e) {}
    return n;
  });
  const [shipmentsOpen, setShipmentsOpen] = useState(true);
  const RAIL_NAV = [
  { ico: I.map, label: 'Map', active: true, href: 'Freight Map.html' },
  { ico: I.layers, label: 'Table', href: 'Freight Table.html' },
  { ico: I.pkg, label: 'Shipments', href: 'Freight Table.html' },
  { ico: I.chart, label: 'Analytics', href: '#' },
  { ico: I.users, label: 'Team', href: '#' },
  { ico: I.shuffle, label: 'Routing', href: '#' },
  { ico: I.gear, label: 'Settings', href: '#' }];


  return (
    <div className="fmx-root">
      {/* Broker sidebar — shared with Freight Table etc. */}
      <svg width="0" height="0" style={{ position: 'absolute' }} aria-hidden="true">
        <defs>
          <linearGradient id="ai-agent-grad" x1="0%" y1="50%" x2="100%" y2="50%">
            <stop offset="0%" stopColor="#5386EF" />
            <stop offset="50%" stopColor="#8FEFFF" />
            <stop offset="100%" stopColor="#D58FFF" />
          </linearGradient>
        </defs>
      </svg>
      <aside className={`sidebar${sbCollapsed ? ' is-collapsed' : ''}`} style={{ backgroundColor: 'rgb(10, 10, 10)' }}>
        <div className="brand">
          <div className="brand-text">
            <svg className="brand-wordmark" viewBox="0 0 457 71" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Infomatics" style={{ width: '110px', height: '16px' }}>
              <g clipPath="url(#clip0_fmx_infomatics)">
                <path d="M0 1.36011H14.84V69.2601H0V1.36011Z" fill="#fff" />
                <path d="M61.89 39.1899V69.2599H47.05V42.2899C47.05 33.7499 45.01 30.5499 40.26 30.5499C35.22 30.5499 32.11 34.9199 32.11 42.5799V69.2499H17.27V20.7599H30.17L31.04 26.8699C33.85 23.2799 38.31 19.8899 44.52 19.8899C55.97 19.8899 61.88 26.3899 61.88 39.1899H61.89Z" fill="#fff" />
                <path d="M83.7099 17.27V20.76H95.8299V31.43H83.7099V69.26H68.8699V31.43H61.3999V20.76H68.8699V15.91C68.8699 5.72 76.1499 0 86.6199 0C89.5299 0 92.6299 0.48 95.9299 1.36V11.06C93.1199 10.57 90.8899 10.28 89.2399 10.28C84.8699 10.28 83.7099 12.12 83.7099 17.26V17.27Z" fill="#fff" />
                <path d="M92.54 45.01C92.54 29.59 102.34 19.79 117.37 19.79C132.4 19.79 142.2 29.68 142.2 45.01C142.2 60.34 132.5 70.23 117.37 70.23C102.24 70.23 92.54 60.43 92.54 45.01ZM126.68 45.01C126.68 36.09 122.99 30.94 117.37 30.94C111.75 30.94 108.06 36.08 108.06 45.01C108.06 53.94 111.75 59.08 117.37 59.08C122.99 59.08 126.68 53.94 126.68 45.01Z" fill="#fff" />
                <path d="M214.56 39.19V69.26H199.72V42.29C199.72 33.75 197.88 31.43 193.51 31.43C189.14 31.43 186.04 35.02 186.04 42.59V69.26H171.2V42.29C171.2 33.75 169.36 31.43 164.99 31.43C160.62 31.43 157.52 35.02 157.52 42.59V69.26H142.68V20.76H155.58L156.45 26.87C159.17 23.09 163.34 19.79 169.55 19.79C175.37 19.79 179.73 23.09 182.84 27.65C185.94 23.67 190.6 19.79 197.39 19.79C208.54 19.79 214.56 26.68 214.56 39.19Z" fill="#fff" />
                <path d="M263.26 58.59V69.26C261.03 69.75 258.99 70.04 257.25 70.04C251.72 70.04 248.04 67.81 246.19 64.22C243.47 67.71 239.11 70.23 232.03 70.23C220.2 70.23 214.09 65.38 214.09 56.07C214.09 46.76 220.49 42.01 235.14 40.36L243.87 39.29C243.58 32.31 241.74 30.46 237.66 30.46C233.58 30.46 231.55 32.79 230.77 36.67H216.22C216.8 26.48 225.63 19.79 238.14 19.79C251.82 19.79 258.7 27.74 258.7 40.74V54.8C258.7 57.52 259.67 58.87 261.51 58.87C262.09 58.87 262.58 58.77 263.26 58.58V58.59ZM243.86 49.76V48.01L236.49 48.98C232.51 49.47 229.6 51.41 229.6 55.19C229.6 58.97 232.41 60.91 235.23 60.91C240.86 60.91 243.86 57.22 243.86 49.75V49.76Z" fill="#fff" />
                <path d="M283.63 58.4901C284.7 58.4901 286.05 58.3901 287.61 58.3001V69.2601C284.51 69.5501 281.79 69.6501 279.36 69.6501C266.94 69.6501 263.45 65.5801 263.45 54.0301V31.4301H256.95V20.7601H263.45V9.99008L278.29 7.08008V20.7601H287.6V31.4301H278.29V52.3801C278.29 57.1301 279.07 58.4901 283.63 58.4901Z" fill="#fff" />
                <path d="M288.09 1.36011H302.93V14.3601H288.09V1.36011ZM288.09 20.7601H302.93V69.2601H288.09V20.7601Z" fill="#fff" />
                <path d="M303.41 45.01C303.41 29.49 313.01 19.79 328.05 19.79C340.95 19.79 349.29 26.48 350.75 38.03H335.52C334.74 33.18 332.61 30.95 328.05 30.95C322.04 30.95 318.93 35.99 318.93 45.02C318.93 54.05 322.03 59.09 328.05 59.09C332.03 59.09 334.65 55.69 335.52 50.46H350.75C349.2 62.58 340.37 70.25 328.05 70.25C313.01 70.25 303.41 60.55 303.41 45.03V45.01Z" fill="#fff" />
                <path d="M349.29 53.25H364.42C364.42 56.84 367.23 59.55 372.18 59.55C377.13 59.55 379.65 57.51 379.65 54.89C379.65 45.58 350.36 56.35 350.36 35.1C350.36 25.88 358.8 19.77 371.22 19.77C383.64 19.77 392.08 26.56 392.08 35.39H377.34C377.34 31.8 374.62 30.35 370.94 30.35C368.22 30.35 364.93 31.61 364.93 34.13C364.93 42.86 394.22 33.06 394.22 53.92C394.22 63.81 385.59 70.22 372.69 70.22C357.75 70.22 349.31 63.24 349.31 53.25H349.29Z" fill="#fff" />
                <path d="M416.71 39.8701V46.1701H392.75V39.8701H416.71Z" fill="#fff" />
                <path d="M439.21 39.8701V46.1701H415.25V39.8701H439.21Z" fill="#fff" />
              </g>
              <defs><clipPath id="clip0_fmx_infomatics"><rect width="456.05" height="70.23" fill="#fff" /></clipPath></defs>
            </svg>
          </div>
          <button className="brand-collapse" aria-label={sbCollapsed ? 'Expand sidebar' : 'Collapse sidebar'} onClick={toggleSb}>
            {sbCollapsed
              ? <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M14 8H5" /><path d="M10 5l3 3-3 3" /><path d="M3 3v10" /></svg>
              : <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M2 8h9" /><path d="M6 5l-3 3 3 3" /><path d="M13 3v10" /></svg>}
          </button>
        </div>
        <div className="nav-section">
          <a className="ai-ask-pill" href="AI Agent.html" aria-label="Ask anything">
            <span className="ai-ask-pill-l">
              <svg className="ai-ask-sparkles" viewBox="0 0 24 24" fill="none" stroke="url(#ai-agent-grad)" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" />
                <path d="M20 2v4" /><path d="M22 4h-4" /><circle cx="4" cy="20" r="2" />
              </svg>
              <span className="ai-ask-pill-text">Ask anything!</span>
            </span>
            <svg className="ai-ask-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m6 4 4 4-4 4" /></svg>
          </a>
          <div className={`nav-group ${shipmentsOpen ? 'is-open' : ''}`}>
            <button className="nav-item nav-group-head" onClick={() => setShipmentsOpen((o) => !o)}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" style={{ width: '18px', height: '18px' }}><rect x="1" y="6" width="14" height="11" rx="1" /><path d="M15 9h4l3 4v4h-7" /><circle cx="6" cy="19" r="2" /><circle cx="18" cy="19" r="2" /></svg>
              <span>All Shipments</span>
              <svg className="nav-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
            </button>
            {shipmentsOpen &&
            <div className="nav-subgroup">
              <a className="nav-subitem" href="Freight Table.html"><span>Freight</span></a>
              <a className="nav-subitem" href="Postal Table.html"><span>Postal</span></a>
            </div>}
          </div>
          <a className="nav-item" href="Quoting.html">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" /><path d="M14 8H8" /><path d="M16 12H8" /><path d="M13 16H8" /></svg>
            <span>Quoting</span>
          </a>
          <a className="nav-item" href="Network.html">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="7" r="4" /><path d="M3 21v-1a6 6 0 0 1 12 0v1" /><circle cx="17" cy="9" r="3" /><path d="M21 21v-1a4 4 0 0 0-4-4" /></svg>
            <span>Network</span>
          </a>
          <a className="nav-item" href="Trackers.html">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /><circle cx="12" cy="10" r="3" /></svg>
            <span>Trackers</span>
          </a>
          <a className="nav-item" href="#">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>
            <span>Settings</span>
          </a>
        </div>
        <div className="sidebar-foot">
          <button type="button" className="persona-switch" title="Switch persona">
            <span className="av">DV</span>
            <span className="who"><b>Diana Vega</b><span>Broker · Alfred Logistics</span></span>
            <span className="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg></span>
          </button>
        </div>
      </aside>

      <div className="fmx-main">
        {/* Mobile black status bar with menu (hidden on desktop) */}
        <div className="fmx-mstatus">
          <button className="fmx-mstatus-btn" aria-label="Menu" onClick={() => setMobileMenuOpen(true)}>
            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M4 5h16"/><path d="M4 12h16"/><path d="M4 19h16"/></svg>
          </button>
        </div>
        {mobileMenuOpen &&
          <div className="fmx-mdrawer" onClick={(e) => { if (e.target === e.currentTarget) setMobileMenuOpen(false); }}>
            <aside className="fmx-mdrawer-panel" role="menu" aria-label="Main menu">
              <div className="fmx-mdrawer-head">
                <span className="fmx-mdrawer-mark">{I.mark}</span>
                <button className="fmx-mdrawer-close" aria-label="Close" onClick={() => setMobileMenuOpen(false)}>{I.close}</button>
              </div>
              <nav className="fmx-mdrawer-nav">
                {RAIL_NAV.map((n, i) =>
                  <a key={i} className={`fmx-mdrawer-item${n.active ? ' is-active' : ''}`} href={n.href}>
                    <span className="fmx-mdrawer-ico">{n.ico}</span>
                    <span className="fmx-mdrawer-lbl">{n.label}</span>
                  </a>
                )}
              </nav>
              <div className="fmx-mdrawer-foot">
                <span className="fmx-mdrawer-avatar">JA</span>
                <span className="fmx-mdrawer-user">John Anderson</span>
              </div>
            </aside>
          </div>
        }
        {/* Mobile topbar: breadcrumb + notifications.
            Hidden on desktop via CSS. */}
        <div className="fmx-mtop">
          <nav className="fmx-mtop-crumbs" aria-label="Breadcrumb">
            <span className="fmx-mtop-crumb">All Shipments</span>
            <svg className="fmx-mtop-sep" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
            <span className="fmx-mtop-crumb is-current">Freight</span>
          </nav>
          <button className="fmx-mtop-btn fmx-mtop-bell" aria-label="Notifications" onClick={(e) => e.preventDefault()}>
            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
            <span className="fmx-mtop-badge">3</span>
          </button>
        </div>

        {/* Header: brand + needs attention */}
        <header className="fmx-header">
          <div className="fmx-brand-row">
            <div className="fmx-brand">
              Freight
            </div>
            <button className="fmx-topbar-bell" aria-label="Notifications" title="Notifications" onClick={(e) => e.preventDefault()}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
              <span className="badge-dot"></span>
            </button>
          </div>
          <div className="fmx-attn">
            <span className="fmx-attn-title">Needs attention</span>
            <span className="fmx-attn-of">26<span className="fmx-attn-of-total"> of 100</span></span>
            {STATS.map((st) => activeStats.includes(st.key) ?
            <button key={st.key} className="fmx-pill-delayed" onClick={() => toggleStat(st.key)}>
                {st.label} <span className="v">{st.value}</span>
              </button> :

            <button key={st.key} className={`fmx-stat${st.tone ? ' is-' + st.tone : ''}`} onClick={() => toggleStat(st.key)}>
                <span className="lbl">{st.label}</span><span className="val">{st.value}</span>
              </button>
            )}
            {filtersActive &&
            <span className="fmx-reset" onClick={clearFilters}>Clear all filters</span>
            }
          </div>
        </header>

        {/* Body */}
        <div className={`fmx-body${filtersOpen || sortOpen ? ' has-panel' : ''}`}>
          {/* Icon toolbar */}
          <div className="fmx-toolbar">
            <button className={`fmx-tool-btn fmx-tool-btn-filter${filtersOpen ? ' is-on' : ''}${anyFilterActive(filters) ? ' has-dot' : ''}`} title="Filter" onClick={() => {setSortOpen(false);setFiltersOpen((o) => !o);}}>{I.filter}</button>
            <button className={`fmx-tool-btn fmx-tool-btn-sort${sortOpen ? ' is-on' : ''}`} title="Sort" onClick={() => {setFiltersOpen(false);setSortOpen((o) => !o);}}>{I.sort}</button>
            <button className="fmx-tool-btn" title="New">{I.plus}</button>
          </div>

          {/* Filter / Sort panels — sit between toolbar and list, never replace it */}
          {filtersOpen &&
          <FilterPanel
            filters={filters}
            setFilters={setFilters}
            onClose={() => setFiltersOpen(false)}
            resultCount={filtered.length} />
          }
          {sortOpen &&
          <SortPanel
            sort={sort}
            setSort={setSort}
            onClose={() => setSortOpen(false)} />
          }

          {/* Shipment list */}
          <div className={`fmx-list${listCollapsed ? ' is-collapsed' : ''}`}>
            <div
              className="fmx-list-head"
              onPointerDown={(e) => {
                const el = e.currentTarget;
                el._dragY = e.clientY;
                el._dragMoved = false;
                try { el.setPointerCapture(e.pointerId); } catch (_) {}
              }}
              onPointerMove={(e) => {
                const el = e.currentTarget;
                if (el._dragY == null) return;
                const dy = e.clientY - el._dragY;
                if (Math.abs(dy) > 6) {
                  el._dragMoved = true;
                  if (dy < 0 && listCollapsed) setListCollapsed(false);
                  else if (dy > 0 && !listCollapsed) setListCollapsed(true);
                  el._dragY = null;
                }
              }}
              onPointerUp={(e) => {
                const el = e.currentTarget;
                try { el.releasePointerCapture(e.pointerId); } catch (_) {}
                if (!el._dragMoved) setListCollapsed((c) => !c);
                el._dragY = null;
                el._dragMoved = false;
              }}>
              <span className="t">Shipments</span>
              <span className="c">{filtered.length} of {SHIPMENTS.length}</span>
            </div>
            {clusterFilter && clusterFilter.length > 0 &&
            <div className="fmx-cluster-bar">
                <span className="fmx-cluster-bar-dot" />
                <span className="fmx-cluster-bar-lbl">Cluster view · {clusterFilter.length} shipments</span>
                <button className="fmx-cluster-bar-clear" onClick={() => setClusterFilter(null)}>Clear</button>
              </div>
            }
            <div className="fmx-search">
              <span className="ico">{I.search}</span>
              <input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search by carrier, ref, city..." />
            </div>
            <div className="fmx-tabs">
              {TABS.map((t) =>
              <button key={t.key} className={`fmx-tab${activeTabs.includes(t.key) ? ' is-on' : ''}`} onClick={() => toggleTab(t.key)}>
                  {t.label}
                </button>
              )}
            </div>
            <div className="fmx-cards" onScroll={(e) => {const el = e.currentTarget;el.classList.add('is-scrolling');clearTimeout(el._st);el._st = setTimeout(() => el.classList.remove('is-scrolling'), 700);}}>
              {displayList.length === 0 && <div className="fmx-empty">No shipments match.</div>}
              {groupedList ?
              groupedList.map((g) => {
                const collapsed = !!collapsedGroups[g.label];
                const delayed = g.items.filter((x) => x.isDelayed).length;
                const noTrk = g.items.filter((x) => isNoTracking(x)).length;
                return (
                  <React.Fragment key={g.label}>
                  <button type="button" className="fmx-grouphead" onClick={() => toggleGroup(g.label)}>
                    <span className={`fmx-grouphead-chev${collapsed ? ' is-collapsed' : ''}`}>{I.chevron}</span>
                    <span className="fmx-grouphead-name">{g.label}</span>
                    <span className="fmx-grouphead-count">{g.items.length}</span>
                    {(noTrk > 0 || delayed > 0) &&
                      <span className="fmx-grouphead-badges">
                      {noTrk > 0 && <span className="fmx-grouphead-badge is-track">{noTrk} no tracking</span>}
                      {delayed > 0 && <span className="fmx-grouphead-badge is-delay">{delayed} delayed</span>}
                    </span>
                      }
                  </button>
                  {!collapsed && g.items.map((s) =>
                    <ShipmentCard key={s.id} s={s} isSelected={s.id === selectedId} onSelect={handleSelect} isPinned={pinnedIds.includes(s.id)} onTogglePin={togglePin} activeStats={activeStats} />
                    )}
                </React.Fragment>);
              }) :
              displayList.map((s) =>
              <ShipmentCard key={s.id} s={s} isSelected={s.id === selectedId} onSelect={handleSelect} isPinned={pinnedIds.includes(s.id)} onTogglePin={togglePin} activeStats={activeStats} />
              )
              }
            </div>
          </div>

          {/* Shipment detail panel */}
          {selectedId != null && !detailHidden &&
          <ShipmentDetail
            s={SHIPMENTS.find((x) => x.id === selectedId)}
            onClose={() => handleSelect(null)}
            onViewOnMap={() => {
              setDetailHidden(true);
              setListCollapsed(true);
              // After the sheet collapses, Leaflet needs to remeasure
              // and re-fit the shipment's origin↔destination bounds.
              setTimeout(() => {
                const map = mapApiRef.current;
                if (!map) return;
                map.invalidateSize();
                const s = SHIPMENTS.find((x) => x.id === selectedId);
                if (!s) return;
                const o = CITY[s.origin]; const d = CITY[s.dest];
                if (o && d) map.flyToBounds([o, d], { padding: [70, 70], duration: 0.5 });
              }, 260);
            }}
            hwStatus={tw.hwStatus}
            driverStatus={tw.driverStatus}
            eldStatus={tw.eldStatus}
            mapApiRef={mapApiRef} />

          }

          {/* Map */}
          <div className={`fmx-map${addRedZoneMode && !draftZone ? ' is-drawing' : ''}`}>
            {loadingId === selectedId && selectedId != null &&
              <div className="fmx-map-loader" aria-hidden="true"></div>
            }
            <MapView
              shipments={filtered}
              selectedId={selectedId}
              loadingId={loadingId}
              onSelect={handleSelect}
              onClusterClick={onClusterClick}
              mapApiRef={mapApiRef}
              hwStatus={tw.hwStatus}
              driverStatus={tw.driverStatus}
              eldStatus={tw.eldStatus}
              overlays={overlays}
              customRedZones={customRedZones}
              addRedZoneMode={addRedZoneMode && !draftZone}
              onMapClickAddRedZone={handleMapClickAddRedZone}
              draftZone={draftZone}
              onDraftChange={setDraftZone}
              clusterPopover={clusterPopover}
              onPopoverClose={closeClusterPopover}
              onSpread={spreadCluster}
              onCollapse={collapseCluster}
              onZoomCluster={zoomCluster}
              spiderIds={spiderIds} />
            
            {addRedZoneMode && !draftZone &&
              <PlacementBanner
                onPick={(center, name) => beginDraftAt(center, name)}
                onCancel={cancelAddRedZone} />
            }
            {draftZone &&
              <DrawToolbar
                draft={draftZone}
                onNameChange={updateDraftName}
                onShapeChange={setDraftShape}
                onRadiusChange={updateDraftRadius}
                onSave={commitDraft}
                onCancel={cancelDraft} />
            }

            <MapOverlays overlays={overlays} setOverlays={setOverlays} hidden={addRedZoneMode || !!draftZone} mobileOpen={mobileOverlaysOpen} onMobileClose={() => setMobileOverlaysOpen(false)} />

            {/* Mobile-only: layers button that opens the overlays popover */}
            <button
              className="fmx-mlayers-btn"
              aria-label="Map overlays"
              aria-pressed={mobileOverlaysOpen}
              onClick={() => setMobileOverlaysOpen((o) => !o)}>
              <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/></svg>
            </button>

            <div className="fmx-mapctrls">
              <button className="fmx-mapbtn" title="Fullscreen" onClick={() => {const el = document.querySelector('.fmx-map');if (el && el.requestFullscreen) el.requestFullscreen();}}>{I.maximize}</button>
              <button className="fmx-mapbtn" title="Zoom in" onClick={() => mapApiRef.current && mapApiRef.current.zoomIn()}>{I.zoomIn}</button>
              <button className="fmx-mapbtn" title="Zoom out" onClick={() => mapApiRef.current && mapApiRef.current.zoomOut()}>{I.zoomOut}</button>
              <button className="fmx-mapbtn" title="Info" onClick={() => setInfoHintOpen((o) => !o)} aria-pressed={infoHintOpen}>{I.info}</button>
            </div>

            {infoHintOpen && infoHintPos &&
              <div
                className="fmx-info-hint"
                role="dialog"
                aria-label="Map legend"
                style={{ top: infoHintPos.top, left: infoHintPos.left, width: infoHintPos.width }}>
                <p>The red icon with a number shows the total number of shipments. If the icon is red, it means that some of these shipments have alerts that need your attention.</p>
                <div className="fmx-info-hint-foot">
                  <span className="fmx-info-hint-count">1/1</span>
                </div>
                <button className="fmx-info-hint-close" aria-label="Close" onClick={() => setInfoHintOpen(false)}>{I.close}</button>
                <span className="fmx-info-hint-tail" style={{ left: Math.max(16, Math.min(infoHintPos.tailLeft - 7, infoHintPos.width - 22)) }} />
              </div>
            }

            <ViewSwitcher />
          </div>
        </div>
      </div>

      {/* Tweaks panel */}
      <window.TweaksPanel title="Tweaks">
        <window.TweakSection label="Hardware tracker" />
        <window.TweakRadio
          label="Status"
          value={tw.hwStatus}
          options={['live', 'alert', 'exception']}
          onChange={(v) => setTweak('hwStatus', v)} />
        
        <window.TweakSection label="Driver app" />
        <window.TweakRadio
          label="Status"
          value={tw.driverStatus}
          options={['live', 'inactive', 'ended']}
          onChange={(v) => setTweak('driverStatus', v)} />
        
        <window.TweakSection label="ELD" />
        <window.TweakRadio
          label="Status"
          value={tw.eldStatus}
          options={['live', 'inactive', 'offline']}
          onChange={(v) => setTweak('eldStatus', v)} />

        <window.TweakSection label="Loading simulation" />
        <window.TweakSelect
          label="Fetch latency"
          value={tw.fetchLatency}
          options={[
            { label: 'Default (0.9–1.5s)', value: 'default' },
            { label: 'Instant', value: 'instant' },
            { label: 'Slow (3s)', value: 'slow' },
            { label: 'Very slow (8s)', value: 'crawl' },
            { label: 'Stuck (never resolves)', value: 'stuck' }
          ]}
          onChange={(v) => setTweak('fetchLatency', v)} />
        <window.TweakButton label="Replay loading" secondary onClick={replayLoading} />

      </window.TweaksPanel>
      {/* Mobile home indicator — hidden on desktop */}
      <div className="fmx-home-indicator" aria-hidden="true"></div>
    </div>);

}

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