/* InsureVision Cloud — dark+purple theme, matched to the on-device
   DashcamAP gallery so the visual language is identical across UIs.
   Adapted from jetson-runtime/python/events_server.py:GALLERY_HTML. */

:root {
  --bg:       #14141a;
  --bg-2:     #1d1d26;
  --bg-3:     #262630;
  --bg-4:     #2f2f3c;
  --line:     #2f2f3c;
  --line-2:   #3a3a4a;
  --txt:      #ececf0;
  --txt-2:    #a4a3b3;
  --txt-3:    #6c6b7d;
  --accent:   #a07cff;
  --accent-2: #7d52e0;
  --green:    #4dc783;
  --yellow:   #e8c25e;
  --red:      #ef6b6b;
  --pin:      #c98aff;
  --pad:      14px;
  --radius:   14px;
  --shadow:   0 4px 14px rgba(0,0,0,.42);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; overflow-x: hidden; }
html { -webkit-text-size-adjust: 100%; }
body {
  background: var(--bg);
  color: var(--txt);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;   /* hard guard against horizontal overflow on mobile */
  min-width: 0;
}
img { max-width: 100%; height: auto; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code { background: var(--bg-3); padding: 1px 6px; border-radius: 4px;
       font: 13px/1.4 ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
h2 { font-size: 22px; }
h3 { font-size: 16px; }
h4 { font-size: 14px; color: var(--txt-2); text-transform: uppercase;
     letter-spacing: .08em; font-weight: 600; margin-bottom: 8px; }

/* Buttons — borderless, solid fills (Polestar-app style). Inputs
   still get borders since they're not buttons. */
button, .btn {
  font: inherit;
  background: var(--bg-3);
  color: var(--txt);
  border: none;
  padding: 8px 14px;
  cursor: pointer;
  transition: background .15s, color .15s;
  -webkit-tap-highlight-color: transparent;
}
button:hover:not(:disabled), .btn:hover { background: var(--bg-4); }
button:active, .btn:active { background: var(--line); }
button:disabled { color: var(--txt-3); cursor: not-allowed; background: var(--bg-2); }

.btn-primary  { background: var(--accent-2); color: #fff; }
.btn-primary:hover { background: var(--accent); }

.btn-danger   { background: var(--red); color: #fff; }
.btn-danger:hover { background: #d65555; }

/* Inputs keep their border so they read as a field, not a button. */
select, input, textarea {
  font: inherit;
  background: var(--bg-3);
  color: var(--txt);
  border: 1px solid var(--line);
  padding: 8px 12px;
}
input, textarea, select { width: 100%; cursor: text; }
input:focus, textarea:focus, select:focus { outline: 1px solid var(--accent);
                                            border-color: var(--accent); }
textarea { min-height: 80px; resize: vertical; font-family: inherit; }

/* ─── top bar ─────────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, var(--bg-2), var(--bg));
  border-bottom: 1px solid var(--line);
  width: 100%;
}
.brand-row {
  /* Two equal columns so the vertical divider (env-tag's right
     border) lands EXACTLY at the horizontal centre of the topbar. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  padding: 14px 14px 8px;
}
.brand-row > .brand { justify-self: start; padding-left: 14px; }
/* "DEVELOPMENT PLATFORM" pill sits to the left of the logo with a
   right-border acting as the | separator. Small, muted, monospace
   so the eye doesn't compete with the brand mark.
   The InsureVision logo image has more empty pixels at the top
   than the bottom (the pink "swoop" sits above the wordmark and
   adds bounding-box height that the centerline doesn't account
   for). Plain align-items:center makes the env-tag look high vs.
   the visual centre of "INSUREVISION". Compensate with a small
   margin-top so the glyph centerlines line up. */
.env-tag {
  display: flex; align-items: center; justify-content: center;
  align-self: stretch;
  /* Light grey — visible but not competing with the logo. */
  color: rgba(235, 233, 245, 0.85);
  font-size: 15px; font-weight: 300; letter-spacing: 0.10em;
  font-family: ui-monospace, monospace;
  line-height: 1.15;
  padding: 0 6px;
  border-right: 1px solid var(--line);
  /* Two-line layout: drop nowrap so <br> takes effect. */
  white-space: normal;
  text-align: center;
  /* In the brand-row's 1fr 1fr grid, this fills the left column;
     text is centered horizontally within that half. */
  width: 100%;
  min-height: 36px;
  margin-top: 14px;   /* visual-centre offset for the logo's top whitespace */
}
.brand {
  display: flex; align-items: center; gap: 14px;
  color: var(--txt); font-weight: 600; font-size: 22px;
  padding: 4px 10px; border-radius: 10px;
  transition: background .15s;
  max-width: 100%; min-width: 0;
}
.brand:hover { background: var(--bg-3); text-decoration: none; }
.brand img {
  height: 84px; width: auto;
  max-height: 16vw;        /* shrinks gracefully on narrow screens */
  min-height: 56px;
}
.brand span {
  color: var(--txt-2); font-weight: 300; font-size: 18px;
  border-left: 1px solid var(--line); padding-left: 14px;
  letter-spacing: .04em;
}
.nav-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 14px 10px;
  border-top: 1px solid var(--line);
  background: var(--bg);
}
.tabs {
  display: flex; gap: 4px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  flex: 1 1 auto; min-width: 0;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  background: transparent;
  padding: 6px 12px; min-height: auto;
  color: var(--txt-2); white-space: nowrap;
  flex: 0 0 auto;
}
.tabs button.active { background: var(--bg-3); color: var(--txt); }
.tabs button:hover:not(:disabled) { color: var(--txt); background: transparent; }
.tabs button.active:hover { background: var(--bg-3); }
.topbar-right {
  display: flex; align-items: center; gap: 6px;
  flex: 0 0 auto;
}
.status-line {
  color: var(--txt-3); font-size: 11px;
  max-width: 110px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Icon-only buttons in the topbar — square hit area, no chrome,
   subtle hover. SVG inside fills via currentColor. */
.refresh, .auth-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 32px; padding: 0;
  background: transparent; color: var(--txt-2);
  flex: 0 0 auto;
}
.refresh:hover, .auth-toggle:hover { background: var(--bg-3); color: var(--txt); }
.refresh svg, .auth-toggle svg { display: block; }

/* Phone breakpoint — collapse the wordmark, shrink chrome */
@media (max-width: 520px) {
  .brand { font-size: 18px; gap: 10px; padding: 2px 6px; }
  .brand img { height: 70px; max-height: 18vw; min-height: 50px; }
  .brand span { display: none; }     /* logo alone is enough */
  .brand-row { padding: 10px 10px 6px; gap: 10px; }
  .env-tag { font-size: 14px; letter-spacing: 0.08em; padding: 0 4px; margin-top: 10px; }
  .nav-row { padding: 6px 10px 8px; gap: 6px; }
  .tabs button { padding: 5px 10px; font-size: 13px; }
  .status-line { display: none; }    /* keeps just the icons in the corner */
  .topbar-right { gap: 4px; }
}
@media (max-width: 360px) {
  .brand img { height: 58px; }
  .env-tag { font-size: 13px; padding: 0 2px; margin-top: 8px; }
  .tabs button { padding: 4px 8px; font-size: 12px; }
}

/* ─── auth dialog ─────────────────────────────────────────────── */
dialog {
  background: var(--bg-2); color: var(--txt); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px; max-width: 400px;
  box-shadow: var(--shadow);
}
dialog::backdrop { background: rgba(0,0,0,.6); }
dialog h3 { margin-bottom: 6px; }
dialog .sub { color: var(--txt-2); font-size: 13px; margin-bottom: 14px; }
dialog input { margin-bottom: 14px; }
dialog .actions { display: flex; gap: 8px; justify-content: flex-end; }

/* ─── main + tabs ─────────────────────────────────────────────── */
main { padding: 14px; max-width: 1600px; margin: 0 auto; min-width: 0; }
.tab { display: none; }
.tab.active { display: block; }
@media (max-width: 520px) {
  main { padding: 10px; }
  h2 { font-size: 18px; }
  h3 { font-size: 15px; }
}

.card {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  margin-bottom: 14px;
}
.card-hdr {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px; border-bottom: 1px solid var(--line);
}
.card-hdr h3 { flex: 1 1 auto; min-width: 0; }
.card-hdr .sub { color: var(--txt-3); font-size: 12px; }
@media (max-width: 520px) {
  .card-hdr { padding: 12px; gap: 8px; }
}

.empty {
  text-align: center; color: var(--txt-2); padding: 60px 20px;
  background: var(--bg-2); border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* ─── fleet tab ───────────────────────────────────────────────── */
.fleet-grid {
  display: grid; grid-template-columns: 1fr 320px; gap: 14px;
  height: calc(100vh - 140px);   /* room for the taller two-row header */
  min-height: 500px;
}
@media (max-width: 800px) {
  .fleet-grid {
    grid-template-columns: 1fr;
    grid-template-rows: 50vh auto;
    height: auto; min-height: 0;
  }
  #map { height: 100%; min-height: 300px; }
}
#map { background: var(--bg-3); border: 1px solid var(--line);
       border-radius: var(--radius);
       /* Leaflet tile layers are absolutely positioned and overflow the
          container by design; without overflow:hidden iOS Safari
          horizontally scrolls the whole page. */
       overflow: hidden;
       max-width: 100%; min-width: 0;
       box-sizing: border-box; }

/* Persistent online/offline pill above each fleet marker. Leaflet
   wraps our HTML in .leaflet-tooltip.fleet-marker-tooltip — we strip
   its default white background + arrow and let .fleet-pill paint
   itself. */
.leaflet-tooltip.fleet-marker-tooltip {
  background: transparent; border: 0; box-shadow: none;
  padding: 0; white-space: nowrap;
}
.leaflet-tooltip.fleet-marker-tooltip::before { display: none !important; }
.fleet-pill {
  display: inline-block; font-size: 10px; font-weight: 700;
  padding: 3px 8px; border-radius: 999px; letter-spacing: .04em;
  color: #fff; box-shadow: 0 2px 6px rgba(0,0,0,.5);
}
.fleet-pill.green  { background: var(--green); color: #0a1f12; }
.fleet-pill.yellow { background: var(--yellow); color: #2a1f00; }
.fleet-pill.red    { background: var(--red); }

/* Vehicle position marker — solid status-coloured disc with a
   white car glyph inside and a soft coloured halo. Replaces
   Leaflet's default pin icon with something that looks at home
   on a modern dashboard. divIcon container needs no background
   so only our .fleet-dot visual shows. */
.fleet-dot-icon { background: transparent !important; border: 0 !important; }
.fleet-dot {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--accent);
  /* Outer ring + outer halo for depth without being heavy. Halo
     spread kept large so the marker still reads from a distance
     even with the smaller centre disc. */
  box-shadow:
    0 0 0 2px rgba(0,0,0,0.35),         /* dark hairline */
    0 0 0 5px rgba(160,124,255,0.0),    /* halo placeholder (state below) */
    0 4px 10px rgba(0,0,0,0.55);        /* drop shadow */
  transition: background .2s, box-shadow .2s;
}
.fleet-dot::before {
  /* White car glyph (SVG) — drawn as a CSS mask so it inherits
     the dot's white text colour and stays crisp at all zooms. */
  content: '';
  width: 13px; height: 13px;
  background: #fff;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 11l1.5-4.5A2 2 0 0 1 8.4 5h7.2a2 2 0 0 1 1.9 1.5L19 11h.5a1.5 1.5 0 0 1 1.5 1.5V17a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1H6v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-4.5A1.5 1.5 0 0 1 4.5 11H5zm2.2 0h9.6l-1-3H8.2l-1 3zM6.5 14.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm11 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z'/></svg>") no-repeat center / contain;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 11l1.5-4.5A2 2 0 0 1 8.4 5h7.2a2 2 0 0 1 1.9 1.5L19 11h.5a1.5 1.5 0 0 1 1.5 1.5V17a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1H6v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-4.5A1.5 1.5 0 0 1 4.5 11H5zm2.2 0h9.6l-1-3H8.2l-1 3zM6.5 14.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm11 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z'/></svg>") no-repeat center / contain;
}
.fleet-dot.green {
  background: var(--green);
  box-shadow:
    0 0 0 2px rgba(0,0,0,0.35),
    0 0 12px 2px rgba(74,222,128,0.55),
    0 4px 10px rgba(0,0,0,0.55);
}
.fleet-dot.yellow {
  background: var(--yellow);
  box-shadow:
    0 0 0 2px rgba(0,0,0,0.35),
    0 0 12px 2px rgba(250,204,21,0.45),
    0 4px 10px rgba(0,0,0,0.55);
}
.fleet-dot.red {
  background: var(--red);
  box-shadow:
    0 0 0 2px rgba(0,0,0,0.35),
    0 0 12px 2px rgba(239,107,107,0.4),
    0 4px 10px rgba(0,0,0,0.55);
  opacity: 0.85;        /* slightly faded when offline */
}
.fleet-side { display: flex; flex-direction: column; min-height: 0; }
.fleet-side .card { display: flex; flex-direction: column;
                     flex: 1; min-height: 0; margin-bottom: 0; }
.fleet-list { list-style: none; padding: 0; margin: 0; overflow: auto; flex: 1; }
.fleet-list li {
  padding: 12px 14px; border-bottom: 1px solid var(--line);
  cursor: pointer; transition: background .1s;
}
.fleet-list li:hover { background: var(--bg-3); }
.fleet-list .name { font-weight: 600; }
.fleet-list .name small { color: var(--txt-3); font-weight: 400; }
.fleet-list .meta .loc.stale-fix { color: var(--txt-3); font-style: italic; }
.fleet-list .meta { color: var(--txt-2); font-size: 12px; margin-top: 4px;
                     display: flex; gap: 10px; flex-wrap: wrap; }
.fleet-list .stale .name { color: var(--txt-3); }
.fleet-list .stale .meta { color: var(--red); }
.fleet-list .dot { width: 8px; height: 8px; border-radius: 50%;
                   display: inline-block; align-self: center; }
.fleet-list .dot.green { background: var(--green); box-shadow: 0 0 6px var(--green); }
.fleet-list .dot.yellow { background: var(--yellow); }
.fleet-list .dot.red { background: var(--red); }

/* ─── vehicle detail ──────────────────────────────────────────── */
.vehicle-hero {
  padding: 16px; margin-bottom: 12px;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* Two-column grid: left = identity + vitals, right = live preview + chips */
.vehicle-hero.hero-2col {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 16px; align-items: start;
}
.vehicle-hero .hero-label {
  color: var(--txt-3); font-size: 11px;
  text-transform: uppercase; letter-spacing: .1em;
}
.vehicle-hero .hero-left  { min-width: 0; }
.vehicle-hero .hero-right {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
}
.vehicle-hero h2 {
  margin: 4px 0; font-size: 20px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vehicle-hero .sub {
  color: var(--txt-2); font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vehicle-hero .sub.muted {
  color: var(--txt-3); font-style: italic; font-size: 12px;
}
/* Driver score + ONLINE side-by-side under the vehicle info. */
.hero-score-row {
  display: flex; gap: 6px; margin-top: 10px;
}
.hero-score-row .driver-hero,
.hero-score-row .status-hero {
  flex: 1 1 0; border-radius: 0;
}
.vehicle-hero .hero-right .conn-chip {
  align-self: stretch; justify-content: center;
}
/* LTE + GPS share one horizontal row to compress vertical space. */
.vehicle-hero .hero-right .chip-row {
  display: flex; gap: 6px; align-self: stretch;
}
.vehicle-hero .hero-right .chip-row .conn-chip {
  flex: 1 1 0; min-width: 0;
}

/* Live preview tile — the latest snapshot from the front camera with
   a translucent overlay carrying the play icon + record dot + label.
   Click anywhere on the tile to open the live modal. When no snapshot
   has landed yet, the img is .empty and only the overlay shows. */
.live-tile {
  position: relative; display: block; width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0; border: none;
  border-radius: 10px;
  background: var(--bg-3); cursor: pointer;
  overflow: hidden;
}
.live-tile-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.live-tile-img.empty { display: none; }
.live-tile-overlay {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  gap: 9px;
  background: linear-gradient(rgba(20,20,30,0.40), rgba(125,82,224,0.70));
  color: #fff;
  font-size: 13px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase;
  pointer-events: none;
}
.live-tile:hover  .live-tile-overlay { background: linear-gradient(rgba(20,20,30,0.20), rgba(125,82,224,0.55)); }
.live-tile:active .live-tile-overlay { background: linear-gradient(rgba(20,20,30,0.60), rgba(125,82,224,0.80)); }
.live-tile-img:not(.empty) ~ .live-tile-overlay {
  /* When a real frame is showing, fade the overlay so the image is
     still mostly visible. Hover/click brings it back. */
  background: linear-gradient(rgba(20,20,30,0.05), rgba(20,20,30,0.55));
}
.live-tile:hover .live-tile-img:not(.empty) ~ .live-tile-overlay {
  background: linear-gradient(rgba(20,20,30,0.15), rgba(125,82,224,0.55));
}

/* WiFi / LTE / no-net chip. Colour-coded by channel: WiFi green
   (free), LTE amber (metered), no-net red. Flat / square — the hero
   is the "vital signs" of the vehicle, kept clean and unstyled so
   colour does the signalling, not chrome. */
.conn-chip {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
  min-width: 0;
  text-align: center;
}
.conn-chip svg { display: block; flex-shrink: 0; }
/* If a chip's text is genuinely too long for its column (rare —
   `MODEM CRITICAL 71°C · paused` is the worst case), wrap instead
   of overflowing the parent. */
.vehicle-hero .conn-chip { white-space: normal; }
/* Solid-fill chips, white text, no border. Polestar-style. */
.conn-chip.link-wifi    { background: #2c9756; color: #fff; }
.conn-chip.link-lte     { background: #b88629; color: #fff; }
.conn-chip.link-other   { background: #5a5a6e; color: #fff; }
.conn-chip.link-none    { background: #c14747; color: #fff; }
.conn-chip.link-loading { background: var(--bg-3); color: var(--txt-3); }
.conn-chip.therm-cool     { background: #2c9756; color: #fff; }
.conn-chip.therm-warm     { background: #c69830; color: #fff; font-weight: 600; }
.conn-chip.therm-hot      { background: #d27c20; color: #fff; font-weight: 700; }
.conn-chip.therm-critical { background: #c14040; color: #fff; font-weight: 700; animation: thermPulse 1.6s ease-in-out infinite; }
/* Thermal chip text is the longest of all chip variants ("WARM 56°C
   · ORIG→LOW" etc.) — drop a little font + letter-spacing + padding so
   it doesn't graze the chip border on narrow viewports. */
.conn-chip.therm-warm,
.conn-chip.therm-hot,
.conn-chip.therm-critical {
  font-size: 10.5px;
  letter-spacing: 0.03em;
  padding: 5px 7px;
}
@keyframes thermPulse { 0%,100% { opacity: 1 } 50% { opacity: .55 } }

/* Score + status share a horizontal slot so they read as a pair, not
   as two stacked banners. They auto-shrink together on small screens.
   Use min-width: max-content so the box can't be squeezed narrower
   than the longest status word (CRITICAL @ 14 px ≈ 78 px). */
/* Driver score + ONLINE — solid colour rectangles with dark text on
   top, Polestar-style. Variant classes below pick the fill colour. */
.status-hero, .driver-hero {
  text-align: center; padding: 8px 12px;
  border: none;
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--bg);
  background: var(--bg-3);
}
/* Sub-labels ("driver score" / "status") inherit dark text but slightly
   muted via opacity so the headline number/word reads first. */
.status-hero .sub, .driver-hero .lbl { color: var(--bg); opacity: 0.75; }
.status-hero {
  font-size: 14px; font-weight: 600; letter-spacing: .01em;
  min-width: max-content;
}
.status-hero .sub {
  display: block; font-size: 8px; font-weight: 600;
  letter-spacing: .08em; color: var(--txt-3);
  text-transform: uppercase; margin-top: 2px;
  white-space: normal;
}
/* Status hero — solid colour fill, dark text on top. */
.status-hero.green   { background: var(--green);  }
.status-hero.yellow  { background: var(--yellow); }
.status-hero.red     { background: var(--red);    }
.status-hero.offline { background: var(--bg-3); color: var(--txt-2); }
.status-hero.offline .sub { color: var(--txt-2); }

.driver-hero { min-width: max-content; }
.driver-hero .num {
  font-size: 20px; font-weight: 600; letter-spacing: .01em;
  line-height: 1;
}
.driver-hero .lbl {
  display: block; font-size: 8px; font-weight: 600;
  letter-spacing: .08em; color: var(--txt-3);
  text-transform: uppercase; margin-top: 2px;
  white-space: normal;
}

@media (max-width: 520px) {
  .vehicle-hero { padding: 12px; }
  .vehicle-hero.hero-2col { gap: 10px; }
  .vehicle-hero h2 { font-size: 16px; }
  .vehicle-hero .sub { font-size: 12px; }
  .hero-score-row .driver-hero,
  .hero-score-row .status-hero { padding: 5px 6px; }
  .driver-hero .num { font-size: 18px; }
  .status-hero { font-size: 13px; }
  .status-hero .sub, .driver-hero .lbl { font-size: 7.5px; letter-spacing: .06em; }
  .live-tile-overlay { font-size: 11px; gap: 6px; }
}
/* Driver score — solid colour fill, dark text on top. */
.driver-hero.excellent { background: var(--green);  }
.driver-hero.good      { background: var(--accent); }
.driver-hero.warn      { background: var(--yellow); }
.driver-hero.poor      { background: var(--red);    }

/* ─── Activity strip (last N days headline) ──────────────────────── */
.activity-strip {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 14px;
  margin-bottom: 12px;
}
.activity-strip .activity-label {
  color: var(--txt-3); font-size: 10px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  margin-bottom: 8px;
}
.activity-strip .activity-tiles {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
}
.activity-strip .act-tile {
  background: var(--bg-3); border-radius: 8px;
  padding: 10px 12px; min-width: 0;
}
.activity-strip .act-tile .av {
  font-size: 22px; font-weight: 600; color: var(--accent);
  line-height: 1; letter-spacing: .01em;
}
.activity-strip .act-tile .av .au {
  font-size: 11px; font-weight: 500; color: var(--txt-3);
  margin-left: 3px;
}
.activity-strip .act-tile .al {
  font-size: 10px; color: var(--txt-3); margin-top: 4px;
  text-transform: uppercase; letter-spacing: .08em;
}
@media (max-width: 520px) {
  .activity-strip { padding: 8px 10px; }
  .activity-strip .activity-tiles { gap: 6px; }
  .activity-strip .act-tile { padding: 8px 10px; }
  .activity-strip .act-tile .av { font-size: 18px; }
}

/* Collapsible health card — collapsed by default so the metric grid
   doesn't dominate the vehicle view. Click the summary to expand. */
.health-details {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--radius); margin: 0 0 14px;
  overflow: hidden;
}
.health-details > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; font-size: 13px;
}
.health-details > summary::-webkit-details-marker { display: none; }
.health-details > summary .chevron {
  color: var(--txt-3); width: 14px; height: 14px; flex-shrink: 0;
  transition: transform .15s;
}
.health-details[open] > summary .chevron { transform: rotate(90deg); }
.health-details > summary .health-summary-label {
  font-weight: 600; color: var(--txt);
}
.health-details > summary .health-summary-hint {
  margin-left: auto; color: var(--txt-3); font-size: 11px;
  font-family: ui-monospace, monospace;
  text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.health-details[open] > summary { border-bottom: 1px solid var(--line); }

/* health card — same metric tiles as on-device, compressed */
.health-card { padding: 10px; }
.health-section-label {
  font-size: 9px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--txt-3);
  margin: 10px 0 4px; padding: 0 2px;
}
.health-section-label:first-child { margin-top: 0; }
.health-grid {
  display: grid; gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
}
@media (max-width: 520px) {
  .health-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }
  .metric { padding: 5px 6px; }
  .metric .value { font-size: 13px; }
}
.metric {
  background: var(--bg-3); border-radius: 6px; padding: 6px 8px;
  cursor: pointer; transition: background .08s, transform .08s;
  user-select: none;
}
.metric:hover { background: var(--bg-2); }
.metric:active { transform: scale(0.99); }
.metric .label { color: var(--txt-2); font-size: 9px;
                  text-transform: uppercase; letter-spacing: .06em;
                  line-height: 1.1; }
.metric .value { font-weight: 600; font-size: 15px; margin-top: 1px;
                  color: var(--accent); line-height: 1.15; }
.metric .value .unit { color: var(--txt-2); font-size: 11px;
                        font-weight: 400; margin-left: 2px; }
.metric.warn .value  { color: var(--yellow); }
.metric.alert .value { color: var(--red); }
.metric.muted .value { color: var(--txt-3); }
.metric .sub { color: var(--txt-3); font-size: 10px; margin-top: 1px;
                line-height: 1.15; }

/* metric history modal — opened by clicking any tile. z-index has to
   beat Leaflet's internal stack (it uses up to 700 for control layers),
   otherwise the route map's tile pane pokes through the dim backdrop. */
.metric-modal {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,.6);
  display: none; align-items: center; justify-content: center;
}
.metric-modal.open { display: flex; }

/* Pulsing red record dot — used inside the live tile overlay. */
.live-dot {
  width: 8px; height: 8px;
  background: #ff5b5b;
  box-shadow: 0 0 0 0 rgba(255,91,91,0.55);
  animation: livePulse 1.5s ease-out infinite;
}
@keyframes livePulse {
  0%   { box-shadow: 0 0 0 0   rgba(255,91,91,0.55); }
  70%  { box-shadow: 0 0 0 7px rgba(255,91,91,0);    }
  100% { box-shadow: 0 0 0 0   rgba(255,91,91,0);    }
}

.live-modal .metric-modal-inner { max-width: 1100px; width: 95%; }
.live-modal .live-body {
  padding: 0; background: #000;
  display: flex; align-items: center; justify-content: center;
  min-height: 360px;
}
.live-modal #live-img {
  max-width: 100%; max-height: 75vh; display: block;
  background: #111; min-height: 240px;
}

/* When ANY modal is open, hide Leaflet container internal stacking so
   tiles can't escape above the backdrop. Targets every leaflet pane.
   Both :has() (modern browsers) and a body.modal-open class fallback
   (set/cleared by JS) so iOS Safari can't dodge this. */
body:has(.metric-modal.open) .leaflet-container,
body:has(.modal.open) .leaflet-container,
body.modal-open .leaflet-container {
  z-index: 1 !important;
}
.metric-modal-inner {
  background: var(--bg-2); border: 1px solid var(--line-2);
  border-radius: 10px; width: min(560px, 92vw); max-height: 86vh;
  display: flex; flex-direction: column;
  box-shadow: 0 18px 60px rgba(0,0,0,.5);
}
.metric-modal-top {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
}
.metric-modal-top .title {
  font-weight: 600; font-size: 17px; color: var(--txt);
}
.metric-modal-top .sub {
  color: var(--txt-3); font-size: 10px; margin-top: 2px;
  font-family: ui-monospace, monospace;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.metric-modal-top .close {
  background: transparent; border: 0; color: var(--txt-2);
  cursor: pointer; padding: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
}
.metric-modal-top .close:hover { background: var(--bg-3); color: var(--txt); }
.metric-modal-top .close svg { display: block; }
.metric-modal-top .metric-current {
  font-size: 22px; font-weight: 600; color: var(--accent);
  white-space: nowrap; line-height: 1;
}
.metric-modal-top .metric-current .unit {
  color: var(--txt-2); font-size: 12px; font-weight: 400; margin-left: 3px;
}
.metric-modal-body { padding: 12px 14px; overflow: auto; }
.metric-section-label {
  color: var(--txt-3); font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; margin: 4px 0 6px;
}
.metric-spark { width: 100%; height: 60px; background: var(--bg-3);
                border-radius: 6px; padding: 6px; margin-bottom: 12px; }
.metric-history {
  list-style: none; padding: 0; margin: 0;
  max-height: 320px; overflow: auto; font-size: 12px;
}
.metric-history li {
  display: grid; grid-template-columns: auto 1fr auto;
  gap: 10px; align-items: center;
  padding: 6px 6px; border-bottom: 1px solid var(--line);
}
.metric-history li:last-child { border-bottom: 0; }
.metric-history .ts {
  color: var(--txt-3); font-family: ui-monospace, monospace;
  font-size: 11px; white-space: nowrap;
}
.metric-history .trans { color: var(--txt); font-family: ui-monospace, monospace; }
.metric-history .trans b { color: var(--accent); font-weight: 600; }
.metric-history .delta {
  font-size: 10px; font-family: ui-monospace, monospace; font-weight: 600;
  padding: 1px 6px; border-radius: 3px;
}
.metric-history .delta.up   { color: var(--yellow); background: rgba(255,193,7,.1); }
.metric-history .delta.down { color: var(--green);  background: rgba(77,199,131,.1); }
.metric-flat {
  color: var(--txt-2); font-size: 13px; padding: 14px 0;
  text-align: center; line-height: 1.5;
}
.metric-flat b { color: var(--accent); font-weight: 600; }
.metric-empty {
  color: var(--txt-3); font-size: 13px; padding: 24px 0; text-align: center;
}
.metric-note {
  color: var(--txt-3); font-size: 11px; margin-top: 14px;
  padding: 8px 10px; background: var(--bg-3); border-radius: 6px;
  line-height: 1.5;
}
.metric-note code {
  background: var(--bg-2); padding: 1px 5px; border-radius: 3px;
  font-size: 10.5px; color: var(--txt-2);
}

/* Kernel error/warning lines surfaced inside the metric modal */
.kernel-lines {
  list-style: none; padding: 0; margin: 0;
  max-height: 280px; overflow: auto;
  font-family: ui-monospace, monospace; font-size: 11px;
}
.kernel-lines li {
  display: grid; grid-template-columns: auto auto 1fr;
  gap: 6px; padding: 4px 6px; align-items: baseline;
  border-bottom: 1px solid var(--line);
}
.kernel-lines li:last-child { border-bottom: 0; }
.kernel-lines .ts {
  color: var(--txt-3); white-space: nowrap; font-size: 10px;
}
.kernel-lines .prio {
  font-weight: 700; letter-spacing: .04em;
  font-size: 10px; padding: 0 4px; border-radius: 3px;
}
.kernel-lines li.warn .prio { color: var(--yellow); background: rgba(232,194,94,.12); }
.kernel-lines li.err  .prio { color: var(--red);    background: rgba(239,107,107,.12); }
.kernel-lines li.crit .prio { color: #fff; background: var(--red); }
.kernel-lines .msg { color: var(--txt-2); word-break: break-word; }

/* Known-benign kernel chatter (Tegra USB3 event-37, Realtek 8192EU
   driver, uvc URB resubmit) — demoted so real errors stand out. */
.kernel-lines li.benign { opacity: .55; }
.kernel-lines li.benign .prio {
  color: var(--txt-3); background: transparent;
  border: 1px solid var(--line);
}
.kernel-lines li.benign .msg { color: var(--txt-3); }
.kernel-noise {
  margin-top: 8px; border-top: 1px dashed var(--line); padding-top: 8px;
}
.kernel-noise summary {
  color: var(--txt-3); font-size: 11px; cursor: pointer;
  list-style: none; padding: 4px 0;
}
.kernel-noise summary::-webkit-details-marker { display: none; }
.kernel-noise summary:hover { color: var(--txt-2); }
.kernel-noise[open] summary { color: var(--txt-2); margin-bottom: 6px; }

/* Clip request modal (on-demand video) — reuses .metric-modal frame */
.clip-vid-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.clip-vid-grid .vid-wrap {
  background: var(--bg-3); border-radius: 6px; overflow: hidden;
  display: flex; flex-direction: column;
}
.clip-vid-grid .vid-label {
  padding: 4px 8px; font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--txt-2);
  background: var(--bg-2);
}
.clip-vid-grid video { width: 100%; display: block; background: #000; }
.clip-vid-grid .vid-meta { padding: 4px 8px; font-size: 11px;
                            color: var(--txt-3); }

.clip-quality-line {
  font-size: 12px; color: var(--txt-2);
  padding: 4px 0 12px;
}
.clip-quality-line a { color: var(--accent); text-decoration: none; }
.clip-quality-line a:hover { text-decoration: underline; }
.clip-quality-line b { color: var(--txt); }

.clip-request-buttons {
  display: flex; gap: 10px; justify-content: center;
  margin: 14px 0 6px; flex-wrap: wrap;
}
.clip-request-buttons button {
  display: flex; flex-direction: column; align-items: center;
  padding: 10px 18px; min-width: 160px; cursor: pointer;
  border-radius: 6px; font-weight: 600; font-size: 13px;
  border: 1px solid var(--line-2);
}
.clip-request-buttons .btn-primary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.clip-request-buttons .btn-primary:hover { background: var(--accent-2); }
.clip-request-buttons .btn-secondary {
  background: var(--bg-3); color: var(--txt);
}
.clip-request-buttons .btn-secondary:hover { background: var(--bg-4); }
.clip-request-buttons .sub {
  font-size: 10px; font-weight: 400; color: var(--txt-3);
  margin-top: 2px; text-transform: none; letter-spacing: 0;
}
.clip-request-buttons .btn-primary .sub { color: rgba(255,255,255,.7); }

.vehicle-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
@media (max-width: 900px) { .vehicle-cols { grid-template-columns: 1fr; } }

.drives-list, .incidents-list {
  list-style: none; padding: 0; margin: 0; max-height: 700px; overflow: auto;
}
.drives-list li.drive-row {
  padding: 0; border-bottom: 1px solid var(--line); display: block;
}
.drives-list .drive-hdr {
  padding: 12px 14px; display: flex; align-items: center; gap: 10px;
  cursor: pointer; user-select: none;
}
.drives-list .drive-hdr:hover { background: var(--bg-3); }
.drives-list .chevron { color: var(--txt-3); font-size: 10px; width: 12px; flex-shrink: 0; }
/* Per-journey thumbnail (front-cam PNG of the first clip in the trip).
   Same 64×36 16:9 size as the clip-card thumbs so the dashboard reads
   consistently — and small enough that the row's text isn't pushed off. */
.drives-list .journey-thumb {
  width: 64px; height: 36px; object-fit: cover; border-radius: 4px;
  background: #000; flex-shrink: 0;
}
.drives-list .journey-thumb-empty {
  width: 64px; height: 36px; border-radius: 4px;
  background: var(--bg-3); flex-shrink: 0;
}
.drives-list .drive-body-text {
  display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;
}
.drives-list .drive-num { font-weight: 600; color: var(--accent); min-width: 80px; }
.drives-list .meta { color: var(--txt-2); font-size: 12px; flex: 1; }
.drives-list .drive-counts { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.drives-list .drive-counts .badge { background: var(--red); color: #fff; }
.drives-list .drive-counts .badge.clip { background: var(--bg-3); color: var(--txt-2); }
.drives-list .drive-counts .badge.pin  { background: var(--pin); color: #fff; }

/* Vehicle-picker (shown on the Vehicle tab when no vehicle is selected) */
.vehicle-picker-list { list-style: none; padding: 0; margin: 0; }
.vehicle-picker-list .vp-row {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--line);
  cursor: pointer; transition: background .1s;
}
.vehicle-picker-list .vp-row:hover { background: var(--bg-3); }
.vehicle-picker-list .vp-dot {
  width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
}
.vehicle-picker-list .vp-dot.green  { background: var(--green); }
.vehicle-picker-list .vp-dot.yellow { background: var(--yellow); }
.vehicle-picker-list .vp-dot.red    { background: var(--red); }
.vehicle-picker-list .vp-main {
  display: flex; flex-direction: column; gap: 2px;
  flex: 1; min-width: 0;
}
.vehicle-picker-list .vp-name {
  font-weight: 600; font-size: 15px; color: var(--txt-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vehicle-picker-list .vp-id  { color: var(--txt-2); font-size: 12px; }
.vehicle-picker-list .vp-loc { color: var(--txt-3); font-size: 11px;
                               font-family: ui-monospace, monospace; }
.vehicle-picker-list .vp-arrow {
  color: var(--accent); font-size: 18px; flex-shrink: 0;
  opacity: 0.7; transition: opacity 0.15s, transform 0.15s;
}
.vehicle-picker-list .vp-row:hover .vp-arrow {
  opacity: 1; transform: translateX(2px);
}

.drives-list .drive-thumb {
  flex: 0 0 auto; width: 64px; height: 36px;
  border-radius: 6px; overflow: hidden; background: var(--bg-3);
  position: relative;
}
.drives-list .drive-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.drives-list .drive-thumb.missing img,
.drives-list .drive-thumb.empty img { display: none; }
.drives-list .drive-thumb.missing,
.drives-list .drive-thumb.empty {
  background-image: linear-gradient(135deg,
    var(--bg-3) 0 49%, var(--line) 50%, var(--bg-3) 51% 100%);
  background-size: 8px 8px;
}
.drives-list .drive-body-text {
  display: flex; flex-direction: column; gap: 2px;
  flex: 1; min-width: 0;
}
.drives-list .drive-body-text .meta { font-size: 12px; color: var(--txt-2); }
.drives-list .drive-body-text .drive-counts { margin-top: 2px; }
@media (max-width: 520px) {
  .drives-list .drive-thumb { width: 56px; height: 32px; }
}
.drives-list .drive-open {
  color: var(--accent); font-weight: 600; padding-left: 6px; flex-shrink: 0;
  opacity: 0.8; transition: opacity 0.15s ease, transform 0.15s ease;
}
.drives-list .drive-hdr:hover .drive-open { opacity: 1; transform: translateX(2px); }

.drives-list .drive-body { display: none; padding: 0; }
.drives-list li.open .drive-body { display: block;
  padding: 8px 14px 16px; background: var(--bg-2); border-top: 1px solid var(--line); }

/* ─── clip grid (per-drive browse) ─────────────────────────── */
.clip-filterbar {
  display: flex; gap: 10px 14px; align-items: center; flex-wrap: wrap;
  padding: 6px 2px 12px; font-size: 13px;
}
.clip-filterbar label {
  color: var(--txt-2);
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
  cursor: pointer;
}
.clip-filterbar input[type="checkbox"] {
  flex-shrink: 0; width: 14px; height: 14px; margin: 0;
}
.clip-filterbar select {
  background: var(--bg-2); color: var(--txt); border: 1px solid var(--line);
  border-radius: 4px; padding: 3px 6px; font-size: 12px;
}
.clip-filterbar .clip-count {
  margin-left: auto; color: var(--txt-3); font-size: 12px;
  font-family: ui-monospace, monospace;
  white-space: nowrap;
}
@media (max-width: 600px) {
  .clip-filterbar {
    gap: 8px 12px;
    font-size: 12px;
  }
  .clip-filterbar .clip-count {
    margin-left: 0; flex-basis: 100%; text-align: right;
    padding-top: 2px; border-top: 1px solid var(--line);
  }
}

/* Drive header — on mobile the meta line can be huge.
   Collapse it under the drive number row instead of stuffing
   everything onto one line. */
@media (max-width: 600px) {
  .drives-list .drive-hdr {
    flex-wrap: wrap;
    padding: 10px 12px;
    column-gap: 8px; row-gap: 4px;
  }
  .drives-list .meta {
    flex-basis: 100%; flex: 0 0 100%;
    margin-left: 22px;   /* indent under the drive-num */
    font-size: 11px;
  }
  .drives-list .drive-counts {
    margin-left: auto;
  }
}

.clip-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.clip-empty {
  padding: 18px; color: var(--txt-3); text-align: center; font-size: 13px;
}
.clip-empty.err { color: var(--red); }

.clip-card {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px;
  overflow: hidden; transition: transform .08s, border-color .08s;
}
.clip-card.clickable { cursor: pointer; }
.clip-card.clickable:hover {
  border-color: var(--accent); transform: translateY(-1px);
}
.clip-thumb {
  position: relative; aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #1a1f3a, #0d1226);
  display: flex; align-items: flex-end; padding: 4px;
  overflow: hidden;
}
/* Multi-camera cells layout — front fills the card when alone,
   otherwise N cells split it. */
.clip-thumb-cells {
  position: absolute; inset: 0;
  display: grid; gap: 1px; background: #000;
}
.clip-thumb-cells.cells-1 { grid-template-columns: 1fr; }
.clip-thumb-cells.cells-2 { grid-template-columns: 1fr 1fr; }
.clip-thumb-cells.cells-3 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.clip-thumb-cells.cells-3 .clip-thumb-cell:first-child {
  grid-row: 1 / span 2; grid-column: 1 / span 1;
}
.clip-thumb-cells.cells-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.clip-thumb-cell {
  position: relative; overflow: hidden; background: #14141a;
}
.clip-thumb-cell.missing img { display: none; }
.clip-thumb-cell.missing::after {
  content: 'no thumb yet'; position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; color: var(--txt-3); letter-spacing: .05em;
}
.clip-thumb-cell img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.clip-thumb-cell .cell-label {
  position: absolute; bottom: 2px; left: 2px;
  font-size: 8px; font-weight: 700; color: #fff;
  background: rgba(0,0,0,.55); padding: 1px 4px; border-radius: 2px;
  letter-spacing: .04em; pointer-events: none;
}
.clip-thumb .corner-badge {
  position: absolute; font-size: 10px; font-weight: 700; padding: 2px 6px;
  border-radius: 3px; letter-spacing: .04em; line-height: 1.1;
}
.clip-thumb .corner-badge.pin   { top: 4px; left: 4px;  background: var(--pin); color: #fff; }
.clip-thumb .corner-badge.inc   { top: 4px; right: 4px; background: var(--red); color: #fff; }
/* Stream-presence tags (CAB / L / R) used to be corner badges INSIDE
   the thumbnail; now they live in the .clip-meta row alongside motion
   + size, freeing the thumbnail grid from overlay clutter. */
.clip-stream-tags {
  display: inline-flex; gap: 2px; flex-wrap: nowrap;
}
.stream-tag {
  font-size: 8px; font-weight: 700; padding: 1px 4px; border-radius: 2px;
  letter-spacing: .02em; line-height: 1.2;
}
.stream-tag.cabin { background: rgba(160,124,255,.85); color: #fff; }
.stream-tag.side  { background: rgba(77,199,131,.85);  color: #fff; }
/* Prediction badges (ACC / D#) — render as their own row above the
   timestamp so they don't fight for the bottom-left corner. */
.clip-thumb .clip-badges {
  position: absolute; bottom: 22px; left: 4px;
  display: flex; gap: 4px; pointer-events: none;
}
/* Timestamp lives in its own row below the 2x2 grid so it stays
   legible regardless of the cells underneath. */
.clip-ts {
  padding: 4px 8px; font-size: 11px;
  font-family: ui-monospace, monospace;
  color: var(--txt-2); background: var(--bg-2);
  border-top: 1px solid var(--line);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.clip-meta {
  display: flex; gap: 8px; padding: 6px 8px;
  align-items: center; justify-content: space-between;
  font-size: 11px;
}
.clip-meta .motion { text-transform: uppercase; letter-spacing: .04em;
                     font-weight: 600; color: var(--txt-3); }
.clip-meta .motion.mov  { color: var(--green); }
.clip-meta .motion.park { color: var(--txt-3); }
.clip-meta .size { color: var(--txt-3); font-family: ui-monospace, monospace; }

#v-route-map { height: 400px; }

/* ─── incidents tab ───────────────────────────────────────────── */
.incidents-list li {
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  cursor: pointer; transition: background .1s;
}
.incidents-list li:hover { background: var(--bg-3); }

/* Rich incident cards (with thumbnail + category labels). */
.incidents-list li.incident-card {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 10px;
  align-items: stretch;
}
.incidents-list .incident-card.reviewed { opacity: .55; }

.inc-thumb {
  width: 96px; aspect-ratio: 16 / 9; border-radius: 6px; overflow: hidden;
  background: var(--bg-3); position: relative; align-self: center;
}
.inc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.inc-thumb.missing img { display: none; }
.inc-thumb.missing::after {
  content: 'no thumb'; position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--txt-3); font-size: 10px;
}

.inc-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.inc-top {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px;
}
.inc-when {
  font-weight: 600; font-size: 14px; color: var(--txt-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inc-rev {
  color: var(--green); font-size: 11px; font-weight: 600;
  white-space: nowrap;
}
.inc-context { color: var(--txt-2); font-size: 11px; }
.inc-cats { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.inc-cats .cat {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
}
.inc-cats .cat-acc { background: var(--red); color: #fff; }
.inc-cats .cat-highrisk {
  background: var(--red); color: #fff;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.25) inset;
  font-weight: 700;
}
.inc-cats .cat-acc-low {
  background: transparent; border: 1px solid var(--yellow); color: var(--yellow);
}
.inc-cats .cat-dgr { background: var(--yellow); color: #14141a; }
.inc-cats .cat-unknown {
  background: transparent; border: 1px solid var(--line); color: var(--txt-3);
}
.inc-where-row { display: flex; gap: 8px; flex-wrap: wrap; }
.inc-where, .inc-speed {
  color: var(--txt-3); font-size: 11px; white-space: nowrap;
}

.badge { display: inline-block; padding: 2px 8px; border-radius: 999px;
         font-size: 11px; font-weight: 700; letter-spacing: .03em; }
.badge.alert  { background: var(--red);    color: #fff; }
.badge.alert-low {
  background: transparent; border: 1px solid var(--yellow); color: var(--yellow);
}
.badge.high-risk {
  background: var(--red); color: #fff; font-weight: 800;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.3) inset, 0 0 8px rgba(239,107,107,0.45);
}
.badge.danger { background: var(--yellow); color: #14141a; }
.badge.pin    { background: var(--pin);    color: #fff; }
.badge.cabin  { background: rgba(160,124,255,.85); color: #fff; }

@media (max-width: 520px) {
  .incidents-list li.incident-card {
    grid-template-columns: 80px 1fr; gap: 8px;
    padding: 8px 10px;
  }
  .inc-thumb { width: 80px; }
  .inc-when { font-size: 13px; }
}

.filter-bar { display: flex; gap: 12px; align-items: center;
              margin-left: auto; flex-wrap: wrap; min-width: 0; }
.filter-bar label { color: var(--txt-2); font-size: 13px;
                    display: flex; align-items: center; gap: 6px; cursor: pointer; }
.filter-bar input[type=checkbox] { width: 16px; height: 16px; cursor: pointer; }
.filter-bar select { width: auto; padding: 6px 10px; font-size: 13px;
                      max-width: 100%; }
@media (max-width: 520px) {
  .filter-bar { margin-left: 0; width: 100%; gap: 8px; }
  .filter-bar label { font-size: 12px; }
}

/* ─── admin form ──────────────────────────────────────────────── */
.form { padding: 14px; display: flex; flex-direction: column; gap: 12px;
        max-width: 100%; }
@media (min-width: 520px) { .form { max-width: 420px; } }
.form label { display: flex; flex-direction: column; gap: 4px;
              color: var(--txt-2); font-size: 12px;
              text-transform: uppercase; letter-spacing: .06em; }
.form .actions { display: flex; gap: 8px; }
#new-vehicle-result {
  margin: 0; padding: 14px; background: var(--bg-3);
  border-top: 1px solid var(--line);
  font: 12px/1.4 ui-monospace, monospace;
  color: var(--txt-2); white-space: pre-wrap;
  word-break: break-all; overflow-wrap: anywhere;
}
#new-vehicle-result:empty { display: none; }

/* ─── modal (incident detail) ─────────────────────────────────── */
.modal {
  position: fixed; inset: 0; background: rgba(0,0,0,.85);
  z-index: 1000; display: none; align-items: center; justify-content: center;
  padding: 20px;
}
.modal.open { display: flex; }
.modal-inner {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--radius); max-width: 1200px;
  width: 100%; max-height: 90vh; overflow: auto;
  box-shadow: var(--shadow);
}
.modal-top {
  display: flex; align-items: center; gap: 12px;
  padding: 14px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--bg-2); z-index: 1;
}
.modal-close {
  background: transparent; color: var(--txt-2); padding: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
}
.modal-close:hover { background: var(--bg-3); color: var(--txt); }
.modal-close svg { display: block; }
.modal-title { flex: 1; font-weight: 600; }

/* No-video placeholder shown in place of the video grid when the clip
   hasn't been pulled from the dashcam yet. Centred, prominent
   call-to-action so the user can't miss the way to fetch footage. */
.no-video-placeholder {
  padding: 24px; background: var(--bg-3);
  display: flex; align-items: center; justify-content: center;
  min-height: 200px;
}
.no-video-inner {
  text-align: center; max-width: 420px;
}
.no-video-title {
  font-size: 16px; font-weight: 600; color: var(--txt); margin-bottom: 6px;
}
.no-video-sub {
  font-size: 13px; color: var(--txt-2); margin-bottom: 14px; line-height: 1.4;
}
.no-video-placeholder .btn-primary {
  padding: 10px 22px; font-size: 14px;
}

.modal-videos {
  display: grid; grid-template-columns: 2fr 1fr; gap: 14px;
  padding: 14px; background: #000;
}
/* `display: grid` above otherwise overrides the HTML `hidden` attr's
   default `display: none`. Without this, the "no video yet" placeholder
   and the empty video grid both show at once. */
.modal-videos[hidden] { display: none; }
@media (max-width: 800px) { .modal-videos { grid-template-columns: 1fr; } }
.vid-wrap { position: relative; }
.vid-label {
  position: absolute; top: 8px; left: 8px;
  background: rgba(0,0,0,.6); color: var(--txt);
  padding: 3px 10px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: .1em;
  z-index: 1;
}
.modal-videos video { width: 100%; aspect-ratio: 16/9; background: #000;
                      border-radius: 8px; }
.vid-wrap .m-dl-btn {
  position: absolute; top: 8px; right: 8px;
  width: 36px; height: 36px;
  background: rgba(0,0,0,.6); color: #fff;
  border: none; border-radius: 8px;
  font-size: 18px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none; z-index: 2;
}
.vid-wrap .m-dl-btn:hover { background: rgba(125,82,224,0.85); }
.vid-wrap .m-dl-btn:active { transform: scale(0.92); }

.modal-meta { padding: 14px; }
.meta-tiles {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  margin-bottom: 14px;
}
.meta-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
  margin-bottom: 14px;
}
@media (max-width: 700px) { .meta-cols { grid-template-columns: 1fr; } }

.file-list { display: flex; flex-wrap: wrap; gap: 6px; }
.file-list a {
  background: var(--bg-3); border: 1px solid var(--line);
  padding: 4px 10px; border-radius: 999px;
  font-size: 12px; color: var(--txt); text-decoration: none;
}
.file-list a:hover { background: var(--accent-2); border-color: var(--accent); }
.file-list .size { color: var(--txt-3); font-size: 11px; margin-left: 4px; }

.incident-map { height: 280px; border-radius: 8px; background: var(--bg-3); }
.incident-map:empty { display: none; }

/* Per-clip accident-probability sparkline shown in the incident
   modal. Lets the operator tell a single-frame model spike apart
   from a sustained near-miss event at a glance. */
.risk-curve {
  background: var(--bg-3);
  border-radius: 8px;
  padding: 10px 12px;
  margin: 8px 0 12px 0;
}
.risk-curve-empty {
  text-align: center; padding: 16px 8px; font-size: 12px;
  color: var(--txt-3);
}
.risk-curve-hdr {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; color: var(--txt-2); margin-bottom: 4px;
}
.risk-curve-svg {
  width: 100%; height: 90px;
}
.risk-curve-foot {
  font-size: 11px; color: var(--txt-3); margin-top: 4px;
}
.risk-tag {
  font-size: 11px; padding: 2px 8px; border-radius: 4px;
  letter-spacing: 0.04em;
}
.risk-tag.real  { background: rgba(239,107,107,0.25); color: #ff8e8e; }
.risk-tag.spike { background: rgba(250,204,21,0.18);  color: #facc15; }
.risk-tag.low   { background: rgba(168,162,184,0.18); color: var(--txt-2); }

/* Per-journey route map shown above the clip grid in the expanded
   journey card. Same look as the incident map but a bit shorter so
   the clip grid still fits the viewport on mobile. */
.journey-map {
  height: 220px;
  border-radius: 8px;
  background: var(--bg-3);
  margin: 0 0 12px 0;
}

/* Leaflet popup theming */
.leaflet-popup-content-wrapper { background: var(--bg-2); color: var(--txt);
                                  border: 1px solid var(--line); border-radius: 8px; }
.leaflet-popup-tip { background: var(--bg-2); border: 1px solid var(--line); }
.leaflet-popup-content { margin: 8px 12px; font-size: 13px; }
.leaflet-popup-content b { color: var(--accent); }
.leaflet-container a.leaflet-popup-close-button { color: var(--txt-2); }

/* ─── drive detail view ──────────────────────────────────────────── */

#tab-drive #drive-detail { display: flex; flex-direction: column; gap: 10px; }

.drive-detail-hdr {
  display: flex; align-items: center; gap: 10px; padding: 0;
  flex-wrap: wrap;
}
.drive-detail-hdr h2 {
  margin: 0; color: var(--accent); font-size: 16px; font-weight: 700;
  line-height: 1.2;
}
.drive-detail-hdr .sub { color: var(--txt-2); font-size: 12px; }
.back-link {
  background: var(--bg-3); color: var(--txt);
  padding: 6px 12px; cursor: pointer; font-size: 12px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 6px;
}
.back-link:hover { background: var(--bg-4); }
.back-link svg { display: block; }

.drive-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.drive-stat {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px;
  padding: 6px 8px; display: flex; flex-direction: column; gap: 1px;
  min-width: 0;
}
.drive-stat .dsv {
  font-size: 14px; font-weight: 600; color: var(--txt-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.drive-stat .dsl {
  font-size: 9px; color: var(--txt-3); text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.drive-stat.pin   { border-color: rgba(201,138,255,.5); }
.drive-stat.pin   .dsv { color: var(--pin); }
.drive-stat.alert { border-color: rgba(239,107,107,.5); }
.drive-stat.alert .dsv { color: var(--red); }

#drive-route-map {
  width: 100%; height: 320px; border-radius: 10px; overflow: hidden;
  background: var(--bg-2);
}
#drive-route-map.no-route::before {
  content: 'no GPS fixes recorded for this drive';
  display: flex; align-items: center; justify-content: center;
  height: 100%; color: var(--txt-3); font-size: 13px;
}

#drive-alerts .incident-row {
  padding: 12px 14px; border-bottom: 1px solid var(--line);
  cursor: pointer; display: flex; flex-direction: column; gap: 4px;
}
#drive-alerts .incident-row:hover { background: var(--bg-3); }
#drive-alerts .ir-top {
  display: flex; justify-content: space-between; gap: 10px; align-items: center;
}
#drive-alerts .ir-ts { font-weight: 600; color: var(--txt-1); }
#drive-alerts .ir-badges { display: flex; gap: 6px; flex-wrap: wrap; }
#drive-alerts .ir-meta { color: var(--txt-3); font-size: 12px;
  display: flex; gap: 10px; flex-wrap: wrap; }
.badge.ok { background: rgba(77,199,131,.2); color: var(--green); }

#drive-clips-body .clip-filterbar { margin-bottom: 8px; }

@media (max-width: 520px) {
  .drive-stats { grid-template-columns: repeat(3, 1fr); gap: 5px; }
  .drive-stat { padding: 5px 6px; }
  .drive-stat .dsv { font-size: 13px; }
  .drive-stat .dsl { font-size: 8.5px; }
  .drive-detail-hdr h2 { font-size: 15px; }
  .drive-detail-hdr .sub { font-size: 11px; }
  #drive-route-map { height: 220px; }
}

/* ─── clip-request modal: per-camera cells ─────────────────────────── */

.clip-thumb-preview {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  margin: 8px 0 10px;
}
.clip-thumb-preview .ctp-cell {
  position: relative; background: var(--bg-3); border-radius: 8px;
  overflow: hidden;
  display: flex; flex-direction: column;
}
/* Loading state — a spinning ring centred in the 16:9 area. Sits
   behind the <img> via z-index; once the img loads (and the cell
   gets .loaded) the spinner disappears.
   We split ring (::before, animated) from the static label
   (::after) so the text doesn't rotate with the ring. */
.clip-thumb-preview .ctp-cell::before {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 28px; height: 28px;
  margin: -28px 0 0 -14px;       /* lifted above the label */
  border-radius: 50%;
  border: 2px solid var(--bg-2);
  border-top-color: var(--accent);
  z-index: 0;
  animation: ctp-spin 0.9s linear infinite;
}
.clip-thumb-preview .ctp-cell::after {
  content: 'loading thumbnail';
  position: absolute;
  top: 50%; left: 0; right: 0;
  margin-top: 14px;
  text-align: center;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--txt-3);
  z-index: 0;
  pointer-events: none;
}
.clip-thumb-preview .ctp-cell.loaded::before,
.clip-thumb-preview .ctp-cell.loaded::after,
.clip-thumb-preview .ctp-cell.ready::before,
.clip-thumb-preview .ctp-cell.ready::after { display: none; }
@keyframes ctp-spin {
  to { transform: rotate(360deg); }
}
.clip-thumb-preview .ctp-cell img,
.clip-thumb-preview .ctp-cell video {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
  background: transparent;
  position: relative; z-index: 1;
}
/* When the thumbnail 404s after all retry attempts, replace the loading
   spinner with a flat "tap ORIG or LOW" hint. ::before (spinner ring)
   is hidden by .missing; ::after's text content is swapped out via
   another rule below. */
.clip-thumb-preview .ctp-cell.missing img { visibility: hidden; }
.clip-thumb-preview .ctp-cell.missing::before { display: none; }
.clip-thumb-preview .ctp-cell.missing::after {
  content: 'tap ORIG or LOW to fetch';
  position: absolute;
  top: 0; left: 0; right: 0; aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
  margin: 0;
  background: var(--bg-3);
  color: var(--txt-3);
  font-size: 11px; letter-spacing: 0.04em;
  text-align: center; padding: 4px;
  z-index: 0;
}
/* Thermal-paused — amber tint on the row + clear cause message. */
.clip-thumb-preview .ctp-cell.thermal-paused .ctp-progress-row {
  background: rgba(232, 140, 40, 0.18);
  border-top-color: rgba(232, 140, 40, 0.4);
}
.clip-thumb-preview .ctp-cell.thermal-paused .ctp-progress-text {
  color: #ffba6a;
}
/* Offline cell uses the same row but in red. */
.clip-thumb-preview .ctp-cell.offline .ctp-progress-row {
  background: rgba(214, 72, 72, 0.18);
  border-top-color: rgba(214, 72, 72, 0.4);
}
.clip-thumb-preview .ctp-cell.offline .ctp-progress-text {
  color: #ff9a9a;
}
.clip-thumb-preview .ctp-label {
  position: absolute; left: 6px; top: 6px;
  background: rgba(0,0,0,0.6); color: #fff;
  font-size: 10px; font-weight: 600; letter-spacing: 0.05em;
  padding: 2px 6px; border-radius: 4px;
  pointer-events: none;
}
.clip-thumb-preview .ctp-cell.ready .ctp-label { background: rgba(125,82,224,0.85); }
.clip-thumb-preview .ctp-cell.err   .ctp-label { background: rgba(239,107,107,0.85); }

.clip-thumb-preview .ctp-btns {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
  background: var(--bg-3);
}
.clip-thumb-preview .ctp-btn {
  background: var(--bg-2); color: var(--txt-1);
  border: none; border-top: 1px solid var(--line);
  padding: 8px 4px; font-size: 12px; font-weight: 600;
  cursor: pointer; text-transform: uppercase; letter-spacing: 0.04em;
}
.clip-thumb-preview .ctp-btn:hover { background: var(--bg-3); }
.clip-thumb-preview .ctp-btn.orig { color: var(--accent); }
.clip-thumb-preview .ctp-btn.orig:hover { background: rgba(160,124,255,0.15); }
.clip-thumb-preview .ctp-btn.low { color: var(--txt-2); }
.clip-thumb-preview .ctp-btn.low:hover { background: rgba(160,124,255,0.08); }

.clip-thumb-preview .ctp-cell.pending img { filter: brightness(0.55); }

/* Pending state: progress row sits BELOW the thumb in the same
   vertical slot the ORIG/LOW buttons occupy on other cells. Keeps
   the 2x2 grid row-heights uniform and avoids overlaying the
   thumb image (previous absolute-positioned pill design was hard
   to read against the underlying frame). */
.clip-thumb-preview .ctp-progress-row {
  background: var(--bg-2);
  padding: 8px 10px 10px;
  display: flex; flex-direction: column; gap: 5px;
  border-top: 1px solid var(--line);
}
.clip-thumb-preview .ctp-progress-track {
  height: 6px;
  background: var(--bg-4);
  overflow: hidden;
  position: relative;
}
.clip-thumb-preview .ctp-progress-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 0%;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  transition: width 0.5s linear;
}
.clip-thumb-preview .ctp-progress-text {
  color: var(--txt-2); font-size: 11px;
  text-align: center; letter-spacing: 0.02em;
}
.clip-thumb-preview .ctp-ready-meta {
  background: var(--bg-2); color: var(--txt-3);
  padding: 4px 8px; font-size: 11px; border-top: 1px solid var(--line);
  text-align: right;
}

/* Action buttons on each ready cell — top-right corner, clear of
   both the kind label (top-left) and the native player controls
   (bottom). 32×32 each, big enough for mobile thumbs. */
.clip-thumb-preview .ctp-actions {
  position: absolute; top: 6px; right: 6px;
  display: flex; gap: 4px; z-index: 2;
}
.clip-thumb-preview .ctp-act-btn {
  width: 32px; height: 32px;
  background: rgba(0,0,0,0.55); color: #fff;
  border: none; border-radius: 6px;
  font-size: 16px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none; font-weight: 700;
}
.clip-thumb-preview .ctp-act-btn:hover { background: rgba(125,82,224,0.85); }
.clip-thumb-preview .ctp-act-btn:active { transform: scale(0.92); }
/* Back-compat shim — older inline HTML used `.ctp-fs-btn`; keep the
   single-button styling alive in case a cell still carries that class. */
.clip-thumb-preview .ctp-fs-btn {
  position: absolute; top: 6px; right: 6px;
  width: 32px; height: 32px;
  background: rgba(0,0,0,0.55); color: #fff;
  border: none; border-radius: 6px;
  font-size: 16px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  z-index: 2;
}

.ctp-status {
  padding: 6px 10px; border-radius: 6px; font-size: 12px;
  margin-bottom: 8px;
}
.ctp-status.err     { background: rgba(239,107,107,0.15); color: var(--red); }
.ctp-status.pending { background: rgba(160,124,255,0.12); color: var(--accent); }
.ctp-status.ok      { background: rgba(77,199,131,0.12);  color: var(--green); }

.ctp-all-footer {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 0 4px; margin-top: 4px; border-top: 1px solid var(--line);
}
.ctp-all-label {
  color: var(--txt-3); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.04em; margin-right: 4px;
}
.ctp-btn-sm {
  background: var(--bg-2); color: var(--txt-1);
  border: 1px solid var(--line); border-radius: 6px;
  padding: 5px 12px; font-size: 11px; font-weight: 600; cursor: pointer;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.ctp-btn-sm:hover { background: var(--bg-3); border-color: var(--accent); }
.ctp-btn-sm.orig:hover { color: var(--accent); }

@media (max-width: 520px) {
  .clip-thumb-preview { gap: 6px; }
  .clip-thumb-preview .ctp-btn { padding: 7px 4px; font-size: 11px; }
}
