@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:300;font-display:swap;src:url('fonts/ibm-plex-sans-300.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ibm-plex-sans-400.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/ibm-plex-sans-500.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/ibm-plex-sans-600.woff2') format('woff2');}

:root {
  /* ===================== LEDGER PALETTE — spec_track_ledger_v1 §1.1 ========
     TWO chromatic hues exist in this application, in either theme:

       --accent   INTERACTIVE  links, focus rings, primary button, hover
       --alert    ACT NOW      only where §3.6 permits; <=5 per page view

     Everything else is achromatic. There is no third hue and no leftover
     palette; leftovers are how the christmas tree grew. If a case seems to
     need a third hue the answer is type weight, vertical position, a hairline
     rule, or a word (§1.2) — escalate to Iris rather than adding one here.

     §1.3 is a BUILD GATE, not advice: zero chromatic hex/rgba() literals may
     exist anywhere else in this file. The only allow-listed exceptions are the
     map markers (§2.7) and the greyscale Plotly series (§2.6), both of which
     are achromatic or use --alert's literal. Run the gate before handover.

     Contrast, verified both themes: all ink pairs >= 4.5:1;
     accent 6.3:1 / 7.9:1; alert 6.9:1 / 7.6:1.
     ====================================================================== */
  --accent: #1550E0;
  --alert:  #B0201A;

  /* ---- Neutral ramp (LIGHT = default; flipped under [data-theme=dark]) ---- */
  --bg:      #FFFFFF;
  --surface: #F7F8FA;
  --line:    #E3E7EC;
  --line-2:  #C9D0D8;
  --ink:     #0F141A;
  --ink-2:   #5A6572;
  --ink-3:   #667181;
  --rail:    #F7F8FA;

  /* ---- F2: application chrome (topbar + rail) ----------------------------
     Iris removed the ABM brand navy: it is a chromatic family and fails the
     §1.3 gate. Alexis has been told it is going and that it comes back
     cheaply — so it is a token flip, NOT an architectural decision.

     ### RESTORING ABM BRAND NAVY IS THE ONE LINE BELOW. ###
     Edit that single line to:
         chrome-bg = hex 0D1634, chrome-fg = hex FFFFFF, chrome-line = hex 0D1634
     (hex values written WITHOUT the leading hash here on purpose, so the §1.3
     gate stays at zero until someone deliberately restores the navy; type the
     hash when you do). All three declarations already sit on that one physical
     line, it is the only place in the file that names the chrome, and both
     themes read from it — the dark block does not redeclare --chrome-*, so one
     edit covers light and dark. Restoring it WILL make the gate report a leak;
     that is the gate telling the truth about a deliberate choice.
     ---------------------------------------------------------------------- */
  --chrome-bg: var(--surface); --chrome-fg: var(--ink); --chrome-line: var(--line);

  /* ---- Legacy role-token aliases -----------------------------------------
     ~900 call sites across this file already say --fg / --border / --recess.
     Rather than churn every one of them (and risk missing some), the old
     names are now thin aliases onto the ledger ramp above. var() is late-bound,
     so these resolve per-theme without being redeclared in the dark block.
     New code should use the ledger names directly. ---------------------- */
  --fg:            var(--ink);
  --fg-secondary:  var(--ink-2);
  --fg-muted:      var(--ink-3);
  --border:        var(--line);
  --border-light:  var(--line);
  --border-strong: var(--line-2);
  --field-border:  var(--line-2);
  --surface-2:     var(--line);
  --recess:        var(--surface);
  --active-nav:    var(--line);
  --rail-bg:       var(--chrome-bg);
  --rail-fg:       var(--chrome-fg);
  --topbar-bg:     var(--chrome-bg);

  /* ---- Primary button (§2.5). Both themes stated on purpose: white on
         #7FA8FF would fail, and a one-theme fix is not a fix. ---- */
  --primary-btn-bg: var(--accent);
  --primary-btn-fg: #FFFFFF;

  /* ---- Control boundary (Iris, filters remediation 2026-08-05). A chip
         border must hold >=3:1 against the --recess panel it sits on;
         --line-2 misses. Re-verified 3.20:1 light / 3.60:1 dark vs --recess.
         SCOPED TO .fchip — do not repoint --border-strong at this. ---- */
  --border-control: #848B9A;

  /* ---- Achromatic scrim + elevation. --elev-* survive only on the mobile
         drawer and the comment popover (§4: no shadows on home). ---- */
  --scrim:     rgba(15,20,26,.45);
  --shadow-sm: rgba(15,20,26,.06);
  --shadow-lg: rgba(15,20,26,.18);
  --elev-sm: 0 2px 8px var(--shadow-sm);
  --elev-lg: 0 8px 24px var(--shadow-lg);
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] {
  color-scheme: dark;
  --accent: #7FA8FF;
  --alert:  #F8827A;
  --bg:      #0B1018;
  --surface: #111823;
  --line:    #212B39;
  --line-2:  #2E3A4B;
  --ink:     #E9EDF3;
  --ink-2:   #98A4B4;
  --ink-3:   #818E9F;
  --rail:    #0E141D;
  /* §6.1: NOT white on --accent in dark — that pairing fails. */
  --primary-btn-fg: #0B1018;
  --border-control: #5F7095;
  --scrim:     rgba(0,0,0,.62);
  --shadow-sm: rgba(0,0,0,.4);
  --shadow-lg: rgba(0,0,0,.55);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  color: var(--fg);
  background: var(--bg);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}
/* BACKSTOP — no anchor may fall through to the user-agent stylesheet.
   A bare <a> with no class inherits the UA link colour, which is chromatic,
   which reintroduces a third hue at render time, and which the §1.3
   source-text gate CANNOT see because there is no literal to find. The §7 G2
   computed-style sweep found two such sites (the rail brand mark, and the
   unclassed SID links in the /borders detention table). This rule is
   specificity (0,0,1) — the lowest possible — so every existing class rule
   still wins; it only catches what nothing else styles. Links are
   interactive, so --accent is the correct default per §1.1. */
a { color: var(--accent); }

.container { max-width: 800px; margin: 0 auto; padding: 16px; }
/* (.search-box retired — the pre-IA landing search; no renderer emits it.) */
/* §4: one border weight, radius 6 max, NO shadows. --elev-* survive only on
   the mobile drawer and the comment popover. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-bottom: 12px;
  overflow: hidden;
}
.card-header {
  padding: 12px 16px;
  font-weight: 600;
  font-size: 14px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--fg);
}
.card-body { padding: 12px 16px; }
.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  /* §2.4: badges are no longer filled pills. Plain tabular text; state is
     carried by the word. Delivered is de-emphasised grey, never green. */
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.badge-preparing { color: var(--ink-2); }
.badge-enroute { color: var(--ink-2); }
.badge-delivered { color: var(--ink-3); }
.stage-group { margin-bottom: 8px; }
.stage-group h3 {
  font-size: 13px;
  color: var(--fg-secondary);
  padding: 8px 0 4px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
}
.stage-group h3 .count {
  background: var(--border);
  color: var(--fg);
  border-radius: 999px;
  padding: 0 8px;
  font-size: 12px;
}
html { scroll-behavior: smooth; }
/* HOME shipment groups boxed-with-shadow (PART 2). The .card supplies the
   box + shadow; the .stage-details inside sits flush (no inner margin) and its
   summary spans the full card width so the boxed look matches borders/kampala.
   P2 punch #4 (Iris S4): unified anchor-target scroll margins. <900px the
   sticky mobile topbar (56px) overlaps anchored sections -> 64px clearance;
   >=900px the rail is lateral and nothing is sticky on top -> 8px breathing
   room. Covers every in-page drill anchor (KPI ladder + pipeline strip). */
.stage-card .stage-details { margin-bottom: 0; }
#at-a-glance, #yard-pipeline, #recently-departed,
[id^="section-"], [id^="phase-"], [id^="timeline-leg-"] { scroll-margin-top: 8px; }
@media (max-width: 899.98px) {
  #at-a-glance, #yard-pipeline, #recently-departed,
  [id^="section-"], [id^="phase-"], [id^="timeline-leg-"] { scroll-margin-top: 64px; }
}
.stage-card .stage-details summary { border-radius: 0; }
/* HOME outer inset: standard container padding (16px, 8px mobile) like every
   other tab. The old `home-inset` no-op marker class is fully retired
   (Phase 1 §4 dead-class burn-down). */
.stage-details summary {
  list-style: none;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  padding: 10px 12px;
  min-height: 44px;
  border-left: 3px solid var(--line-2);
  background: var(--surface);
  border-radius: 0 6px 6px 0;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: background 0.2s;
  user-select: none;
  -webkit-user-select: none;
}
.stage-details summary:hover { background: var(--line); }
.stage-details summary::-webkit-details-marker { display: none; }
.stage-details summary::before {
  content: '\25B6';
  font-size: 10px;
  transition: transform 0.2s;
  display: inline-block;
  color: var(--fg);
}
.stage-details[open] > summary::before { transform: rotate(90deg); }
/* §2.1: the count pill loses its fill. Plain tabular text beside the label —
   the number is the information; the pill was decoration. */
.stage-details summary .stage-badge {
  display: inline-block;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}
.stage-details .section-content { padding: 4px 0 8px; }
/* HOME inner horizontal padding (rev fix). On the home page the shipment rows
   and subsection-headers live DIRECTLY inside `.section-content` (no inner
   card-body), so they had ZERO horizontal padding and the text touched the box
   border on both sides. On borders/kampala the equivalent rows sit inside an
   inner `.card > .card-body` (12px 16px) so they were already inset — only home
   was affected. Add 14px each side to the home stage-card section-content so the
   rows/headers are inset to match the summary header (10px 12px + 3px border ≈
   15px) and the kampala/borders card-body feel. Scoped to `.stage-card` so the
   borders `.border-section .section-content` (which wraps inner cards, not flush
   rows) is untouched. */
.stage-card .section-content { padding-left: 14px; padding-right: 14px; }
.pipeline-stage {
  display: inline-flex;
  align-items: center;
  margin-right: 12px;
  font-size: 12px;
  text-decoration: none;
  color: inherit;
  padding: 4px 10px;
  border-radius: 8px;
  transition: background 0.2s, box-shadow 0.2s;
  cursor: pointer;
}
.pipeline-stage:hover { background: var(--line); }
.shipment-row {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 6px;
}
.shipment-row:last-child { border-bottom: none; }
.shipment-row .shipment-content {
  flex: 1;
  min-width: 0;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
}
.shipment-row a {
  color: var(--fg);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
  flex-shrink: 0;
}
.shipment-row a:hover { color: var(--accent); }
.shipment-row .summary {
  font-size: 12px;
  color: var(--fg-secondary);
  max-width: 60%;
  text-align: right;
}
/* (.shipment-doc-icon retired — P2-4: the doc count now lives in the
   ship-table Docs cell via .doc-count-link.) */
/* List-row supplier cell. HIDDEN BY DEFAULT (mobile + narrow desktop) so the
   mobile single-column layout stays clean; revealed only at the wide desktop
   breakpoint below. Empty cell when a row has no real supplier (shows blank). */
.row-suppliers {
  display: none;
}
.row-suppliers .row-supplier-chip {
  display: inline-block;
  font-size: 11px;
  font-weight: 500;
  color: var(--fg);
  background: var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
/* List-row "Latest comment" cell classes (P2-4: now table-cell content inside
   td.st-comment on the home ship-table; the old .row-comment reveal column is
   retired with the fixed-flex pseudo-table). Values per the Phase-2 mockup
   contract. Unread keeps the pink meta accent, matching the icon state. */
.row-comment-meta { display: block; font-size: 11px; color: var(--fg-muted); }
.row-comment-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12px;
  color: var(--fg-secondary);
}
td.st-comment.unread .row-comment-meta { color: var(--accent); font-weight: 600; }
.detail-section {
  margin-bottom: 16px;
}
.detail-section h2 {
  font-size: 15px;
  color: var(--fg);
  margin-bottom: 8px;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--fg);
}
.kv-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 16px;
}
.kv-grid .label {
  font-size: 12px;
  color: var(--fg-secondary);
}
.kv-grid .value {
  font-size: 14px;
  font-weight: 500;
  color: var(--fg);
}
/* Suppliers: real 3rd-party vendor chips on the detail-page header card.
   Rendered only when present (empty -> nothing emitted, see render_shipment_detail). */
.supplier-block {
  margin-top: 10px;
}
.supplier-block .label {
  font-size: 12px;
  color: var(--fg-secondary);
}
.supplier-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.supplier-chip {
  display: inline-block;
  font-size: 12px;
  font-weight: 500;
  color: var(--fg);
  background: var(--border);
  border-radius: 999px;
  padding: 2px 10px;
  white-space: nowrap;
}
/* Timeline: the vertical bar is shifted RIGHT to open a left gutter column.
   --tl-gutter is the width of that left column (place names for the first &
   last event of each leg render there, opposite their node). --tl-bar is the
   distance from the container's left edge to the vertical bar / node centre. */
.timeline {
  position: relative;
  --tl-gutter: 124px;
  --tl-bar: 150px;
  padding-left: var(--tl-bar);
}
.timeline::before {
  content: '';
  position: absolute;
  left: calc(var(--tl-bar) - 16px);
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--border);
}
.timeline-item {
  position: relative;
  padding: 6px 0;
  font-size: 13px;
}
.timeline-item::before {
  content: '';
  position: absolute;
  left: -20px;
  top: 12px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--fg-muted);
  border: 2px solid var(--surface);
}
.timeline-item.actual::before { background: var(--ink); }
.timeline-item.estimated::before { background: var(--fg-muted); }
.timeline-item.implied::before { background: var(--ink-3); }
.timeline-item .event-type { font-weight: 500; color: var(--fg); }
.timeline-item .event-date { color: var(--fg-secondary); font-size: 12px; }
.timeline-item .event-date.actual-date { color: var(--fg); font-weight: 600; }
.timeline-item .event-date.implied-date { color: var(--fg-muted); font-size: 12px; font-style: italic; }
/* Left-gutter location label: anchored opposite the event node, right-aligned
   against the vertical bar so the place name sits just to its left. */
.timeline-item .event-loc-left {
  position: absolute;
  left: calc(-1 * var(--tl-bar));
  width: var(--tl-gutter);
  top: 9px;
  text-align: right;
  /* right-side gap so the place name never touches/overlaps the vertical bar */
  padding-right: 8px;
  box-sizing: border-box;
  font-size: 12px;
  line-height: 1.3;
  color: var(--fg-secondary);
  word-break: break-word;
}
/* Mobile: shrink the gutter so the event content keeps usable width on narrow
   screens. The bar stays left of the content; labels wrap within the column. */
@media (max-width: 480px) {
  .timeline { --tl-gutter: 84px; --tl-bar: 100px; }
  .timeline-item .event-loc-left { font-size: 11px; padding-right: 6px; }
}
.leg-header {
  font-size: 11px;
  color: var(--fg-secondary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin: 12px 0 4px calc(-1 * var(--tl-bar, 80px));
  font-weight: 600;
  background: var(--recess);
  padding: 4px 12px;
  position: relative;
  z-index: 1;
  display: inline-block;
  border-radius: 4px;
}
/* ---- P2-3 table grammar (spec_track_uplift_v1 Phase 2; exports .data-table
        port). Row borders + hover, NO zebra (never both). Sticky theads are
        desktop-only and must NOT sit inside an overflow-x wrapper at the
        breakpoints where sticky applies (a scrollport that never scrolls
        vertically kills position sticky). .table-wrap is for non-sticky
        tables and sub-900px safety only. ---- */
.table-wrap { overflow-x: auto; }
table.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
table.data-table th {
  text-align: left;
  padding: 7px 12px;
  background: var(--surface);
  color: var(--fg-muted);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
  border-bottom: 1px solid var(--line-2);
}
table.data-table td {
  /* §4 density: 9px/12px at 13px/1.5 gives a ~37px row. */
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.data-table tbody tr:hover td { background: var(--recess); }
table.data-table td.num, table.data-table th.num {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
table.data-table tr.subsection-row th {
  background: var(--recess); font-size: 12px; text-transform: none;
  letter-spacing: 0; color: var(--fg); font-weight: 600; padding: 6px 10px;
  border-bottom: 1px solid var(--border);
}
table.data-table tr.total-row td {
  border-top: 2px solid var(--border); border-bottom: none; font-weight: 600;
}
@media (min-width: 900px) {
  table.data-table--sticky thead th { position: sticky; top: 0; z-index: 2; }
}
/* Sticky support: .card sets overflow:hidden (rounded-corner clip), which
   makes the card the sticky containing block — a clip box that never
   scrolls, so the thead would never stick (caught in the live scroll test
   on deploy 2). The two homes of sticky tables (home stage cards + the PO
   line-items card) opt out of the corner clip. */
.card.stage-card, .card.card--flow { overflow: visible; }
/* Sortable headers (SORT_JS): the server emits a real button inside
   th[data-key]; JS only binds. aria-sort is the programmatic signal; the
   glyph is secondary. */
.th-sort {
  all: unset; cursor: pointer; font: inherit; color: inherit;
  letter-spacing: inherit; text-transform: inherit;
}
.th-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.th-sort::after { content: " \2195"; opacity: .45; }
th[aria-sort="ascending"] .th-sort::after { content: " \2191"; opacity: 1; }
th[aria-sort="descending"] .th-sort::after { content: " \2193"; opacity: 1; }
.leg-card {
  border-left: 3px solid var(--line-2);
  padding: 8px 12px;
  margin-bottom: 8px;
  background: var(--recess);
  border-radius: 0 8px 8px 0;
}
.leg-card .leg-title { font-weight: 600; font-size: 13px; color: var(--fg); }
/* P2·10 per-leg delay chip sits right of the title; wraps under it on mobile */
.leg-title-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.leg-card .leg-detail { font-size: 12px; color: var(--fg-secondary); }
.leg-card .leg-remark { font-size: 12px; color: var(--fg-secondary); margin-top: 4px; }
.leg-card .leg-remark .tmk-badge {
  display: inline-block; font-size: 10px; font-weight: 600; letter-spacing: 0.3px;
  text-transform: uppercase; color: var(--ink-2); background: none;
  border-radius: 4px; padding: 2px 8px; margin-right: 6px; vertical-align: middle;
}
.leg-card .leg-remark .cta-badge { color: var(--ink); background: none;
  border: 1px solid var(--line-2); }
.leg-card-link { text-decoration: none; color: inherit; display: block; }
.leg-card-link .leg-card { transition: background 0.2s, box-shadow 0.2s; cursor: pointer; }
.leg-card-link .leg-card:hover { background: var(--surface-2); }
.leg-card-link .leg-title::after { content: ' \2193'; font-size: 11px; color: var(--fg-muted); }
.map-link {
  display: inline-block;
  padding: 8px 16px;
  background: var(--accent);
  color: var(--primary-btn-fg);
  border-radius: 8px;
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  margin-top: 4px;
  transition: background 0.2s;
}
.map-link:hover { opacity: .88; }
.no-results {
  text-align: center;
  padding: 32px;
  color: var(--fg-secondary);
  font-size: 15px;
}
.pipeline-bar {
  display: flex;
  height: 32px;
  border-radius: 8px;
  overflow: hidden;
  margin: 8px 0;
}
.pipeline-segment {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  /* Phase 0 §3 honesty: NO min-width floor — length must encode value.
     Tiny segments simply clip their (already-dropped) inline count; the
     legend + title + sr-only text carry the number. */
  overflow: hidden;
  /* Iris punch #2: each segment is its own inline-size container so the
     inline count drops at TRUE 24px client-side (home + kampala bars).
     The 5% server gate in the renderers stays as a coarse filter. */
  container-type: inline-size;
}
@container (max-width: 23.9px) {
  .pipeline-segment > span[aria-hidden] { display: none; }
}
/* §2.1: the bar is ONE neutral track. Proportional width already carries the
   comparison honestly — that is the preattentive encoding, and it works better
   without hue. The largest segment steps up in tone, not in colour. */
.pipeline-segment { background: var(--line-2); }
.pipeline-segment.seg-max { background: var(--ink-3); }
.pipeline-segment > span[aria-hidden] { color: var(--ink); }
.pipeline-segment.seg-max > span[aria-hidden] { color: var(--bg); }
.results-count {
  font-size: 13px;
  font-weight: 400;
  color: var(--fg-secondary);
  margin-bottom: 8px;
}
/* ---- Browse / search filters (Piet spec_track_filters, 2026-08-05) ----
   Five dimensions, chips as <button aria-pressed>. Selected state uses
   --accent (interactive territory, §6 of the spec: filters add ZERO alert-hue
   instances; form is Iris's at post-build review). Counts live in .fcount and
   are written ONLY by the client-side recount over the rendered rows, so the
   number can never disagree with the list it describes. Zero-count chips stay
   visible and disabled — hiding a chip hides the vocabulary. */
.fwrap { margin: 4px 0 10px; }
.fbar { display: flex; flex-wrap: wrap; gap: 10px 18px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--recess); }
.fgroup { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; border: 0;
  margin: 0; padding: 0; min-width: 0; }
.fgroup .flegend { font-size: 11px; font-weight: 600; color: var(--fg-muted);
  text-transform: uppercase; letter-spacing: 0.04em; margin-right: 2px; }
.fchip { font: inherit; font-size: 12px; line-height: 1; color: var(--fg);
  background: var(--bg); border: 1px solid var(--border-control);
  border-radius: 999px; padding: 6px 10px; cursor: pointer; }
.fchip:hover { border-color: var(--accent); }
.fchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Selected: --primary-btn-fg, the §6.1 token minted for text-on---accent —
   NOT literal white, which reads 2.35:1 on the dark theme's #7FA8FF accent
   (Iris S2; token gives 6.3:1 light / 8.1:1 dark). The ::before tick is the
   NON-HUE selected channel (Iris ruling: chips stay buttons, the tick
   restores the tick-box mental model without a checkbox rebuild). */
.fchip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent);
  color: var(--primary-btn-fg); }
.fchip[aria-pressed="true"]::before { content: "\2713\00A0"; }
.fchip:disabled { color: var(--fg-muted); border-color: var(--border);
  cursor: default; }
.fchip .fcount { font-variant-numeric: tabular-nums; opacity: 0.75;
  margin-left: 3px; }
/* Disabled counts at full opacity: 0.75 composited the muted ink below the
   4.5:1 text floor (Iris S3: 3.05→4.95 light / 3.70→5.71 dark). The muted
   colour alone already says "disabled". */
.fchip:disabled .fcount { opacity: 1; }
/* Platform 44px touch floor below the rail breakpoint (Iris S3). */
@media (max-width: 899px) {
  .fchip { min-height: 44px; }
}
.fclear { font: inherit; font-size: 12px; color: var(--accent); background: none;
  border: none; text-decoration: underline; cursor: pointer; padding: 6px 4px; }
.fclear[hidden] { display: none; }
/* The announcement line: renders whenever N < T and NEVER collapses, at any
   viewport (spec §2). It sits OUTSIDE the mobile disclosure on purpose. */
.fshowing { font-size: 13px; color: var(--fg); margin: 6px 0 8px; }
.fshowing b { font-variant-numeric: tabular-nums; }
.fshowing[hidden] { display: none; }
/* Mobile: the bar folds behind a labelled, counted disclosure. Desktop keeps
   the bar open and hides the summary (a <details> cannot be forced open by
   CSS alone, so page JS opens it at >=900px on load). */
.fdisc > summary { list-style: none; cursor: pointer; font-size: 13px;
  font-weight: 600; color: var(--fg); padding: 10px 4px; min-height: 44px;
  display: flex; align-items: center; gap: 6px; }
.fdisc > summary::before { content: "\25B8"; font-size: 11px;
  color: var(--fg-muted); }
.fdisc[open] > summary::before { content: "\25BE"; }
@media (min-width: 900px) {
  .fdisc > summary { display: none; }
}
/* Exit links on an empty state. --accent is theme-paired (6.3:1 / 7.9:1).
   44px touch target below the rail breakpoint, where the rail is behind a
   burger and these are the reader's only visible way out. */
.no-results a { color: var(--accent); text-decoration: underline; }
@media (max-width: 899px) {
  .no-results a { display: inline-block; padding: 11px 4px; min-height: 44px; }
}
.subsection-header {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  padding: 10px 0 6px;
  margin-top: 12px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 6px;
}
.subsection-header:first-child { margin-top: 0; border-top: none; }
.subsection-header .sub-count {
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-secondary);
}
.toggle-buttons { display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 12px; }
/* §3.7.6: a real density control that costs no colour — quiet --ink-2 text
   with a --line-2 hairline. */
.toggle-btn {
  padding: 6px 16px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--recess);
  color: var(--fg);
  font-size: 13px;
  cursor: pointer;
  font-family: 'IBM Plex Sans', sans-serif;
  min-height: 36px;
}
.toggle-btn:hover { border-color: var(--fg); color: var(--fg); }
/* P5-12 (F18): Export Excel is DEMOTED out of the filled-primary slot.
   It was a solid dark filled pill in the top-right of EVERY page — the
   highest-contrast control on a triage screen, and on mobile the single most
   prominent control on the page. One primary per view, and the primary slot
   belongs to the primary task; exporting is never the primary task on an
   index page. Ghost treatment: transparent fill, 1px strong border, secondary
   ink — visible and reachable, but no longer the loudest thing on screen.
   (Mockup contract: .btn-ghost.) */
.export-btn {
  padding: 7px 11px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--fg-secondary);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  font-family: 'IBM Plex Sans', sans-serif;
  min-height: 36px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.export-btn:hover {
  background: var(--recess);
  border-color: var(--accent);
  color: var(--fg);
}
.export-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Client-side search highlight */
.search-highlight { background: var(--line-2); border-radius: 4px; padding: 0 2px; }
/* Border & Kampala page styles */
.border-section {
  margin-bottom: 16px;
}
.border-section .section-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  padding: 10px 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.border-stats {
  display: flex;
  gap: 16px;
  font-size: 12px;
  color: var(--fg-secondary);
  margin-bottom: 8px;
}
.border-stats .stat-val { font-weight: 600; color: var(--fg); }
.border-row {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 8px;
}
.border-row:last-child { border-bottom: none; }
.border-row a {
  color: var(--fg);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
  flex-shrink: 0;
}
.border-row a:hover { color: var(--accent); }
.border-row .border-meta {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--fg-secondary);
  min-width: 0;
}
.border-row .border-meta .container-txt {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 140px;
}
.days-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: #FFFFFF;
  flex-shrink: 0;
}
/* §2.4: days badges are no longer filled. Plain tabular text — the number
   is the signal; the fill was decoration. */
.days-badge.green, .days-badge.orange, .days-badge.red { color: var(--ink-3); }
/* P2·9 container-detention CSS deleted 2026-08-05 with the feature. */
/* P2·10 schedule-adherence chips + no_movement sub-line */
.sched-sub { color: var(--fg-secondary); }
.sched-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* §2.4: adherence chips lose their fills; plain text, weight carries it. */
.sched-chip.green, .sched-chip.amber { color: var(--ink-2); }
.sched-chip.red { color: var(--ink); font-weight: 600; }
.leg-idle-flag {
  margin-top: 4px; font-size: 12px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* F2: tier the idle-flag colour off idle_tier — a Watch idle (>2× norm) is
   amber, not identical to a Critical red. Matches the sched-chip band colours. */
/* §2.4: idle flags are --alert ONLY on an Action-tier row (.flag ancestor);
   otherwise weight, not hue. */
.leg-idle-flag.amber, .leg-idle-flag.red { color: var(--ink-2); font-weight: 600; }
tr.flag .leg-idle-flag.red, .flag .leg-idle-flag.red { color: var(--alert); }
.hdr-adherence { margin-left: 8px; vertical-align: middle; }
/* GRN (Goods-Received) receipt axis (Iris §9). Deliberately DISTINCT from the
   solid-white delivery .badge-* pills: outlined soft-tint chips + coloured text
   + a leading fill glyph (● ◐ ○ ↻) — three independent channels, colour-blind
   safe (glyph/word primary, colour secondary). Light-first; hex chosen to match
   the dark-theme spec's severity soft-chip tokens for a free flip. */
.grn-chip { display:inline-flex; align-items:center; gap:5px; padding:2px 8px;
  border-radius:999px; font-size:12px; font-weight:500; line-height:1.4; white-space:nowrap; }
/* §5.2 honesty glyph set: glyph + word carry the state, so WCAG 1.4.1 holds
   with zero colour. No green on Received, no amber on Partial. */
.grn-recd  { color:var(--ink);   background:none; border:1px solid var(--line-2); }
.grn-part  { color:var(--ink-2); background:none; border:1px solid var(--line); }
.grn-none  { color:var(--fg-secondary); background:var(--recess);    border:1px solid var(--border); }
.grn-standing { color:var(--fg-secondary); background:var(--recess); border:1px solid var(--border); }
.grn-qty { font-variant-numeric:tabular-nums; color:var(--fg); font-weight:500; }
.grn-callout { display:inline-flex; align-items:center; gap:6px; padding:4px 8px;
  color:var(--ink); background:var(--surface); border-left:3px solid var(--line-2); border-radius:4px;
  font-size:12px; font-weight:500; margin-top:10px; }
.grn-bar { height:4px; border-radius:999px; background:var(--border); overflow:hidden; margin-top:5px; max-width:220px; }
.grn-bar-fill { height:100%; border-radius:999px; width:0; }   /* width set via GRN_JS (data-pct), colour by state */
.grn-receipts { display:inline; }
.grn-receipts summary { cursor:pointer; color:var(--fg-muted); font-size:12px; list-style:none;
  display:inline-flex; align-items:center; gap:4px; min-height:24px; }
.grn-receipts summary::-webkit-details-marker { display:none; }
.grn-receipts summary:hover { color:var(--fg); }
.grn-receipts summary:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.grn-receipts .grn-rcpt-list { margin:4px 0 2px; font-size:12px; color:var(--fg-secondary); }
.grn-rcpt-row { display:flex; justify-content:space-between; gap:10px; padding:2px 0;
  font-variant-numeric:tabular-nums; }
.grn-rcpt-more { color:var(--fg-muted); font-size:11px; padding-top:2px; }
/* per-line cell layout: chip left, qty fraction right; date/receipts on line 2 */
.grn-line1 { display:flex; justify-content:space-between; align-items:center; gap:8px; }
.grn-line2 { color:var(--fg-muted); font-size:12px; margin-top:2px; font-variant-numeric:tabular-nums; }
.grn-po-summary { margin-top:6px; }
.grn-rollup-val { display:inline-flex; align-items:center; gap:5px;
  font-variant-numeric:tabular-nums; font-weight:500; }
.grn-glyph { font-style:normal; }
/* text-only state colours (rollup line + PO summary + callout glyph) — no bg */
.g-recd { color:var(--ink); }
.g-part { color:var(--ink-2); }
.g-none { color:var(--fg-secondary); }
/* DOCUMENT axis (Iris spec_docs_checklist.md §9) — mandatory export-docs
   checklist. Outlined soft-tint check-chips ✓ ~ ○ (PRESENCE), reusing the GRN
   severity palette (green/amber/grey) so the app reads as one system, but a
   DISTINCT glyph family from GRN's ● ◐ ○ (fill) and from the solid .badge-*
   delivery pills. NO red state. Light-first; dark rides spec_track_dark_theme. */
.doc-checklist { margin:8px 0 12px; }
.doc-summary { font-size:13px; font-weight:600; color:var(--fg); margin-bottom:2px; }
.doc-caveat  { font-size:11px; color:var(--fg-secondary); margin-bottom:12px; line-height:1.4; }
.doc-group   { margin:14px 0 4px; }
.doc-group-head { font-size:11px; font-weight:600; letter-spacing:.03em; text-transform:uppercase;
                  color:var(--fg-secondary); margin-bottom:6px; }
.doc-group--critical { border-left:3px solid var(--line-2); padding:8px 0 8px 12px;
                       background:var(--surface); border-radius:0 6px 6px 0; }
/* §5.2: Group C keeps its clearance-critical line — in words, not in red. */
.doc-group--critical .doc-group-head { color:var(--ink); font-weight:600; }
.doc-group-note { font-size:11px; color:var(--fg-secondary); margin:-2px 0 6px; font-style:italic; }
.doc-group-defer { font-size:12px; color:var(--fg-muted); font-style:italic; margin:10px 0 4px; }
.doc-row   { display:flex; align-items:baseline; gap:10px; padding:6px 0; border-bottom:1px solid var(--border); }
.doc-chip  { display:inline-flex; align-items:center; gap:5px; padding:2px 8px; border-radius:999px;
             font-size:12px; font-weight:500; line-height:1.4; white-space:nowrap; flex-shrink:0; min-width:92px; }
.doc-present   { color:var(--ink);   background:none; border:1px solid var(--line-2); }
.doc-uncertain { color:var(--ink-2); background:none; border:1px solid var(--line); }
.doc-missing   { color:var(--fg-secondary); background:var(--recess);    border:1px solid var(--border); }
.doc-glyph { font-style:normal; }
.doc-label  { font-weight:500; color:var(--fg); flex-shrink:0; }
.doc-reason { font-size:12px; color:var(--fg-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; min-width:0; }
.doc-file   { font-size:12px; color:var(--fg-muted); text-decoration:none; flex-shrink:0; }
.doc-file:hover { color:var(--fg); }
.doc-empty  { background:var(--recess); border:1px solid var(--border); border-radius:8px;
              padding:14px 16px; font-size:13px; color:var(--fg-secondary); }
.doc-empty strong { color:var(--fg); font-weight:600; display:block; margin-bottom:2px; }
/* Consol-grain OGEFREM banner — full-width, distinct FORM from the per-folder
   .doc-row (so it reads consol-level, not this-folder). State colour REUSES the
   .doc-present / .doc-uncertain severity role tokens (no new token → dark theme
   rides automatically). Never red, never grey-missing. */
.doc-consol { display:flex; align-items:flex-start; gap:8px; width:100%;
              padding:8px 10px; margin:2px 0 10px; border-radius:8px;
              border:1px solid var(--border); background:var(--recess); }
.doc-consol--green  { border-color:var(--line-2); background:var(--surface); }
.doc-consol--amber  { border-color:var(--line);   background:var(--surface); }
.doc-consol-tag { font-size:10px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
                  color:var(--fg-secondary); border:1px solid var(--border); border-radius:4px;
                  padding:2px 6px; flex-shrink:0; margin-top:2px; }
.doc-consol-body { min-width:0; flex:1; }
.doc-consol-head { font-size:12px; font-weight:600; display:flex; align-items:center; gap:6px; }
.doc-consol--green .doc-consol-head { color:var(--ink); }
.doc-consol--amber .doc-consol-head { color:var(--ink-2); }
.doc-consol-glyph { font-style:normal; }
.doc-consol-sub  { display:block; font-size:11px; color:var(--fg-secondary); line-height:1.4;
                   margin-top:1px; overflow-wrap:anywhere; }
@media (max-width:899px){
  .doc-consol { flex-wrap:wrap; }
  .doc-row { flex-wrap:wrap; }
  .doc-reason { flex-basis:100%; order:3; }
  .doc-file   { order:4; margin-left:auto; min-height:44px; display:inline-flex; align-items:center; }
}
/* Kampala stage groups */
.yard-stage-group {
  margin-bottom: 12px;
}
.yard-stage-group summary {
  list-style: none;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  padding: 10px 12px;
  min-height: 44px;
  background: var(--surface);
  border-radius: 0 8px 8px 0;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: background 0.2s;
  user-select: none;
  -webkit-user-select: none;
}
.yard-stage-group summary:hover { background: var(--recess); }
.yard-stage-group summary::-webkit-details-marker { display: none; }
.yard-stage-group summary::before {
  content: '\25B6';
  font-size: 10px;
  transition: transform 0.2s;
  display: inline-block;
  color: var(--fg);
}
.yard-stage-group[open] > summary::before { transform: rotate(90deg); }
.yard-stage-group summary .yard-count {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: #FFFFFF;
  margin-left: auto;
}
/* Kampala Yard hub node: a distinct bordered transition card injected between
   the Kampala-arrival leg and the DRC Road leg in the detail-page Timeline. */
.yard-hub-card {
  margin: 10px 0;
  border: 1px solid var(--border);
  border-left: 3px solid var(--line-2);
  border-radius: 0 12px 12px 0;
  background: var(--recess);
  overflow: hidden;
  position: relative;
  z-index: 1;
}
/* When injected inside the detail-page Timeline, the card is a normal flow
   child and so already begins at the timeline's left padding edge (X=--tl-bar,
   150px desktop) — i.e. EXACTLY where the leg event rows begin. We do NOT cancel
   --tl-bar (the old negative margin pulled the card ~130px LEFT of everything,
   flush to the gutter edge — wrong). Instead add a POSITIVE indent so the card's
   left edge sits clearly to the RIGHT of the leg events / vertical bar, reading
   as a distinct nested hub inset from the main timeline. The card still has no
   width cap so it extends to the timeline's right edge and stays comfortably
   WIDE. Scoped to .timeline so the standalone fallback (no timeline parent) is
   unaffected. Desktop: leg events start at X=150px, bar at X=134px; card left
   edge = 150 + 24 = X=174px (24px right of events, 40px right of the bar). */
.timeline > .yard-hub-card {
  --yard-hub-indent: 24px;
  margin-left: var(--yard-hub-indent);
}
.yard-hub-card .yard-stage-group { margin-bottom: 0; }
.yard-hub-card .yard-stage-group summary {
  background: var(--recess);
  border-radius: 0;
  font-size: 13px;
}
.yard-hub-card .yard-stage-group summary:hover { background: var(--surface-2); }
.yard-hub-card .yard-hub-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}
.yard-hub-card .yard-hub-summary-meta {
  margin-left: auto;
  font-size: 11px;
  font-weight: 500;
  color: var(--fg-secondary);
}
.yard-hub-card .timeline {
  padding: 8px 12px 12px;
  background: var(--surface);
}
/* Yard hub timeline has NO left-gutter location labels, so the inherited big
   left padding (var(--tl-bar)=150px) is wrong here: it shoves the vertical bar
   far right and crams the milestone text against it. Re-flow so the bar sits on
   the LEFT with comfortable spacing, and the card uses its full content width. */
.yard-hub-card .timeline {
  --tl-bar: 28px;        /* bar sits near the left edge, not at ~134px */
  --tl-gutter: 0px;
  padding-left: 40px;    /* room for the bar + breathing space before the text */
  padding-right: 16px;
  margin-left: 20px;     /* indent yard sub-timeline relative to parent timeline */
}
.yard-hub-card .timeline::before {
  left: 18px;            /* fixed bar position independent of --tl-bar math */
}
.yard-hub-card .timeline-item::before {
  left: -27px;           /* node dot centred on the bar at left:18px */
}
.yard-hub-card .timeline-item {
  padding: 8px 0;
}
/* Per-step operational detail sub-line under a milestone label/date. */
.yard-hub-card .yard-step-detail {
  display: inline-block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--fg-secondary);
  line-height: 1.35;
}
/* "Yard details" summary line for details not tied to a shown milestone. */
.yard-details-summary {
  font-size: 12px;
  color: var(--fg-secondary);
  padding: 8px 16px 4px;
  background: var(--surface);
  line-height: 1.4;
}
@media (max-width: 480px) {
  /* Mobile: --tl-bar is 100px so leg events start at X=100px, bar at X=84px.
     Scale the indent down to 16px to preserve usable card width on narrow
     screens (card left edge = 100 + 16 = X=116px, still clearly right of the
     events and bar, no horizontal overflow). */
  .timeline > .yard-hub-card { --yard-hub-indent: 16px; }
  .yard-hub-card .timeline {
    padding-left: 34px;
    padding-right: 10px;
    margin-left: 12px;   /* scaled-down indent for mobile */
  }
  .yard-hub-card .timeline::before { left: 15px; }
  .yard-hub-card .timeline-item::before { left: -24px; }
}
.yard-row {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 8px;
}
.yard-row:last-child { border-bottom: none; }
.yard-row a {
  color: var(--fg);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
  flex-shrink: 0;
}
.yard-row a:hover { color: var(--accent); }
.yard-row .yard-meta {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--fg-secondary);
  min-width: 0;
  flex-wrap: wrap;
}
.yard-row .yard-meta .ctr-txt {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 130px;
}
/* P3: at narrow widths (e.g. 390px) stack the yard-meta onto its own full-width
   line so the Gate date is never clipped or pushed into a horizontal scroll. */
@media (max-width: 480px) {
  .yard-row { flex-wrap: wrap; }
  .yard-row .yard-meta { flex-basis: 100%; }
}
/* Transhipped container on the Kampala list (CHANGE 1A): the displayed value is
   the NEW onward container; a dotted underline + title="Transhipped from <old>"
   signals the swap. Subtle so the row stays clean. */
.yard-row .yard-meta .ctr-tx {
  border-bottom: 1px dotted var(--fg-muted);
  cursor: help;
}
.milestone-dots {
  display: inline-flex;
  gap: 2px;
  align-items: center;
  flex-shrink: 0;
}
.milestone-dots .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  /* §2.8: hollow = pending, filled = done. Shape, not hue. */
  border: 1.5px solid var(--line-2);
  background: transparent;
}
.milestone-dots .dot.filled { border-color: var(--ink); background: var(--ink); }
.at-a-glance-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0;
}
.at-a-glance-bar .glance-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--recess);
  color: var(--fg);
  font-weight: 500;
}
.at-a-glance-bar .glance-item .glance-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Today's Activity strip (HOME). Chip row + compact event rows. Uses the
   SHARED .activity-row / .activity-event / .activity-meta block (Phase 1 §4
   dedupe — the old page-scoped .ta-activity-* twins are retired). */
.ta-glance { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0 4px; }
.ta-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 12px; border-radius: 999px; font-size: 13px; font-weight: 600;
  background: var(--recess); color: var(--fg);
}
.ta-chip b { font-weight: 700; }
.ta-empty {
  padding: 16px 14px; font-size: 13px; color: var(--fg-muted);
  text-align: center;
}
/* Full /today page: category groups. Reuses the shared .activity-row lines. */
.ta-group { border-top: 1px solid var(--border); }
.ta-group:first-of-type { border-top: none; }
.ta-group-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px 8px; font-size: 13px; font-weight: 700; color: var(--fg);
  background: var(--recess);
}
.ta-group-count {
  margin-left: auto; font-size: 12px; font-weight: 700; color: var(--fg-muted);
  background: var(--surface); border-radius: 999px; padding: 1px 9px;
}
/* Empty key-value placeholder on the shipment detail page: a muted em dash so
   operators can tell "no data" apart from a dangling label (units are never
   rendered without a value). */
.kv-empty { color: var(--fg-muted); }
@media (max-width: 600px) {
  .kv-grid { grid-template-columns: 1fr; }
  .container { padding: 8px; }
}

/* --- Sync Issues page --- */
.si-glance { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 4px 0; }
.si-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--recess); border-radius: 999px;
  padding: 4px 12px; font-size: 13px; font-weight: 500;
}
.si-chip b { font-weight: 700; }
/* §2.4: alert keeps the exception colour; warn becomes weight; info loses
   its chip entirely and becomes plain secondary text. */
.si-chip.alert { background: none; color: var(--alert); font-weight: 600; padding-left: 0; }
.si-chip.warn  { background: none; color: var(--ink); font-weight: 600;
                 border-left: 2px solid var(--line-2); border-radius: 0; padding-left: 6px; }
.si-chip.info  { background: none; color: var(--ink-2); font-weight: 400; padding-left: 0; }
.si-card { margin-bottom: 14px; }
.si-card .card-header { display: flex; align-items: center; gap: 8px; }
.si-sev-alert { border-left: 4px solid var(--alert); }
.si-sev-warn  { border-left: 2px solid var(--line-2); }
.si-sev-info  { border-left: 2px solid var(--line); }
.si-row {
  display: flex; flex-direction: column; gap: 4px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.si-row:last-child { border-bottom: none; }
/* Section divider header on the merged Sync page (issues | recent activity). */
.sync-divider {
  margin: 24px 0 12px 0; padding-bottom: 6px;
  border-bottom: 2px solid var(--border);
  font-size: 15px; font-weight: 600; color: var(--fg);
}
/* HOME-LIST comment indicator (fixed-width cell, mirrors the doc icon). */
.cmt-cell { display: inline-block; width: 22px; flex: none; text-align: center; vertical-align: middle; }
.cmt-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; margin: 0;
  background: none; border: none; border-radius: 8px;
  line-height: 0; cursor: pointer; -webkit-appearance: none; appearance: none;
  transition: color 0.15s ease, background 0.15s ease;
}
.cmt-icon .cmt-svg { display: block; }
.cmt-icon.unread { color: var(--accent); }
.cmt-icon.read   { color: var(--fg-muted); }
.cmt-icon:hover  { background: var(--line); }
.cmt-icon.unread:hover { color: var(--accent); opacity: .8; }
.cmt-icon.read:hover   { color: var(--fg); }
.cmt-icon:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cmt-icon.is-open { background: var(--line-2); }
/* Comment icon touch target on mobile: keep the 22px glyph but grow the hit
   area to 44px via padding + negative margin (zero layout shift). MUST come
   after the base .cmt-icon rule (same specificity — source order wins).
   899.98px matches the rail/topbar breakpoint exactly (no 1px overlap band
   at 900px where both desktop rail and mobile hit-area rules applied). */
@media (max-width: 899.98px) {
  .cmt-icon { width: 44px; height: 44px; padding: 11px; margin: -11px; }
}

/* Styled card popover showing the latest comment. position:fixed so it escapes
   the scrolling <details>/.section-content overflow and is never clipped by a
   row. Positioned by JS (CMT_POPOVER_JS). One open at a time. */
.cmt-popover {
  position: fixed; z-index: 1000; display: none;
  max-width: 280px; min-width: 160px;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--elev-sm);
  padding: 8px 12px; font-size: 12px; line-height: 1.4;
  text-align: left; pointer-events: auto;
}
.cmt-popover.is-open { display: block; }
.cmt-popover .cmt-pop-head {
  font-size: 11px; font-weight: 600; color: var(--fg-muted);
  margin-bottom: 4px;
}
.cmt-popover .cmt-pop-text {
  color: var(--fg); white-space: pre-wrap; word-break: break-word;
}
.si-row-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.si-row-top a { font-weight: 600; }
.si-pill {
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.4px; padding: 2px 8px; border-radius: 999px; color: var(--bg);
}
.si-pill.alert { background: var(--alert); color: var(--bg); }
.si-pill.warn  { background: none; color: var(--ink); font-weight: 600;
                 border: 1px solid var(--line-2); }
.si-pill.info  { background: none; color: var(--ink-2); }
.si-summary { font-size: 13px; color: var(--fg); }
.si-meta { font-size: 11px; color: var(--fg-muted); }
.si-empty {
  text-align: center; padding: 48px 20px; color: var(--fg-muted);
}
/* §2.4: a calm empty state is not a celebration. */
.si-empty .si-check { font-size: 24px; color: var(--ink-3); display: block; margin-bottom: 10px; }

/* Recent Sync (activity changelog) — same visual language as Sync Issues. */
.sa-glance { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0; }
.sa-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--surface); color: var(--ink-2); border: 1px solid var(--line);
}
.sa-chip b { font-weight: 700; }
.sa-day-card { margin-bottom: 16px; }
.sa-day-card .card-header { display: flex; align-items: center; gap: 8px; }
.sa-meta { font-size: 11px; color: var(--fg-muted); }
.sa-empty {
  text-align: center; padding: 48px 20px; color: var(--fg-muted);
}
.sa-empty .sa-check { font-size: 40px; display: block; margin-bottom: 10px; }

/* ---------------------------------------------------------------------------
   Desktop width utilisation.
   The base `.container` keeps its 800px cap (tuned for mobile / readable line
   length on detail + timeline views). On larger viewports we progressively
   widen, but ONLY card-list views opt in via `.container--wide`; the shipment
   detail + timeline deliberately stay moderate so the tuned --tl-bar/--tl-gutter
   gutter math and prose stay readable. Header (and its persistent search) is
   outside `.container`, so it is full-bleed regardless and is unaffected.
   --------------------------------------------------------------------------- */
@media (min-width: 1000px) {
  /* Modest bump for every view, including detail/timeline: better use of a
     laptop screen without pushing line length past a comfortable measure. */
  .container { max-width: 1040px; }
}
/* P2-5 detail page 2-col — engages at >=1200px ONLY (the 240px rail eats
   into every query; 1200 leaves ~930px usable: left >=520px for the timeline
   — its gutter math is UNTOUCHED, it just lives in a narrower column, same
   as it already does at 390px — right 360-420px for cargo/docs/comments).
   Below 1200px nothing changes: single column, current DOM order. */
@media (min-width: 1200px) {
  .container--detail { max-width: 1280px; }
  .detail-grid {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 420px);
    gap: 0 20px; align-items: start;
  }
  .detail-col-main, .detail-col-side { min-width: 0; }
  /* PO line-items: the wrap's overflow clamp lifts here so the sticky thead
     gets a real (viewport) scrollport; below 1200 the wrap still guards
     against narrow-desktop overflow (sticky simply stays inert there). */
  .po-lines-wrap { overflow-x: visible; }
}
@media (min-width: 1100px) {
  /* Card-list views (home / borders / kampala / search results) breathe wider.
     The result rows are full-width flex items whose meta column already spreads
     to fill available width, so simply lifting the cap uses the extra space
     without touching the live-search show/hide (display toggling) logic. */
  .container--wide { max-width: 1280px; }
  /* SUPPLIER reveal for the borders/kampala flex rows (their rows still emit
     .row-suppliers; the HOME pseudo-table columns are gone — replaced by the
     P2-4 ship-table below). */
  .container--wide .row-suppliers {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-content: flex-start;
    gap: 4px;
    width: 240px;
    margin-left: 24px;
  }
}
@media (min-width: 1500px) {
  .container--wide { max-width: 1440px; }
  .container--wide .row-suppliers { width: 260px; margin-left: 24px; }
}

/* ---------------------------------------------------------------------------
   P2-4 HOME SHIP-TABLE — one DOM, two renderings (po-lines precedent
   generalized). >=1100px: a real table (sticky uppercase thead; Suppliers
   joins at >=1400px only — column-budget honesty, the 240px rail leaves
   ~800px at 1100). <1100px the SAME rows transform back into the card look
   (SID + summary + icons); rows keep class="shipment-card" data-search so
   live search / Open-Hide-All / badge counts are untouched.
   --------------------------------------------------------------------------- */
table.ship-table a.sid { font-weight: 600; text-decoration: none; color: var(--accent); }
:root[data-theme="dark"] table.ship-table a.sid { color: var(--accent); }
table.ship-table a.sid:hover { text-decoration: underline; }
table.ship-table td.st-status { color: var(--fg-secondary); }
table.ship-table td.st-route {
  color: var(--fg-secondary); max-width: 150px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
table.ship-table td.st-eta { font-variant-numeric: tabular-nums; white-space: nowrap; }
table.ship-table td.st-comment { max-width: 250px; }
table.ship-table .row-supplier-chip {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  background: var(--active-nav); color: var(--fg); font-size: 11px;
  margin: 1px 2px 1px 0; white-space: nowrap;
  /* Long vendor names ellipsize inside the fixed track (the retired
     .row-suppliers chips capped at 160px; without a cap a long name bled
     into the Docs column — caught in the live eyeball). Full list rides
     the cell's title attribute. */
  max-width: 170px; overflow: hidden; text-overflow: ellipsis;
  vertical-align: middle;
}
td.st-docs { white-space: nowrap; color: var(--ink-3); font-size: 12px; }

/* ---------------------------------------------------------------------------
   §3 CONSIGNMENT GROUPING. The parent row is the consignment; children are its
   containers, hidden until asked for. One <tbody> per group.
   --------------------------------------------------------------------------- */
/* §2.4: an over-free-time dwell row is marked by WEIGHT, not a wash. */
tr.over-threshold td { font-weight: 600; }
.grp-toggle {
  all: unset; cursor: pointer; font: inherit; font-weight: 600;
  color: var(--accent); display: inline-flex; align-items: center; gap: 6px;
}
.grp-toggle:hover { text-decoration: underline; }
.grp-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* CSS triangle — same idiom as the <details> summary marker. */
.grp-caret {
  flex: 0 0 auto; width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform .15s;
}
.grp-toggle[aria-expanded="true"] .grp-caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .grp-caret { transition: none; } }
/* §3.3: the truthful prompt to expand when siblings disagree. */
.st-more { color: var(--ink-3); font-size: 12px; }
/* §3.2: Ctr renders only when the group has >=2 containers, so a number in
   that column MEANS "this is a group" — the sparse numerals pop by position
   rather than by decoration. */
td.st-ctr { color: var(--ink); font-variant-numeric: tabular-nums; }
tr.sib-row > td.st-sid {
  padding-left: 20px; border-left: 2px solid var(--line);
}
.ctr-no { color: var(--ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }
/* §3.6: --alert on the table is confined to the exception band's top-5 set. */
tr.flag > td:first-child { box-shadow: inset 2px 0 0 var(--alert); }
.subsection-count { color: var(--fg-muted); font-weight: 400; }
@media (min-width: 1100px) {
  table.ship-table { table-layout: fixed; }
  th.st-sid { width: 180px; } th.st-ctr { width: 48px; } th.st-route { width: 150px; }
  th.st-eta { width: 92px; } th.st-comment { width: 200px; } th.st-docs { width: 56px; }
  /* Status: single-line ellipsis + title carries the full text (spec P2-4). */
  table.ship-table td.st-status {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Popover icon replaced by the inline Latest-comment column on desktop. */
  table.ship-table .cmt-icon { display: none; }
}
@media (min-width: 1400px) {
  th.st-comment { width: 250px; }
}
@media (max-width: 1099.9px) {
  /* Card transform — mirrors the existing shipment-row look (mockup contract). */
  table.ship-table thead { display: none; }
  table.ship-table, table.ship-table tbody { display: block; }
  table.ship-table tr {
    display: flex; align-items: baseline; gap: 8px;
    padding: 8px 0; border-bottom: 1px solid var(--border);
  }
  table.ship-table tr:last-child { border-bottom: none; }
  table.ship-table td { display: block; border: none; padding: 0; }
  table.ship-table td.st-comment { display: none; }
  table.ship-table td.st-status { flex: 1 1 auto; min-width: 0; font-size: 13px; }
  table.ship-table td.st-docs { flex: 0 0 auto; margin-left: auto; }
  /* §6.2: Route and ETA stay on the card, labelled, on their own line. */
  table.ship-table td.st-route, table.ship-table td.st-eta {
    flex: 0 0 auto; font-size: 12px; color: var(--ink-3);
  }
  table.ship-table td[data-l]::before {
    content: attr(data-l) ": "; color: var(--ink-3);
  }
  table.ship-table td.st-status[data-l]::before { content: none; }
  /* Ctr renders with its label prefix; blank cells stay out of the flow. */
  table.ship-table td.st-ctr { flex: 0 0 auto; font-size: 12px; color: var(--ink-3); }
  table.ship-table td.st-ctr:empty { display: none; }
  table.ship-table td.st-route:empty { display: none; }
  /* §6.2: full-width 44px tap target for the group toggle. */
  table.ship-table .grp-toggle { min-height: 44px; }
  table.ship-table tr { flex-wrap: wrap; }
  /* §6.2: the parent/child relationship must survive the loss of table
     structure. */
  table.ship-table tr.sib-row {
    border-left: 2px solid var(--line); padding-left: 12px;
  }
  table.ship-table tr.sib-row > td.st-sid { padding-left: 0; border-left: none; }
  /* §6.2: flagged rows in card mode match the band's left rule. */
  table.ship-table tr.flag { border-left: 2px solid var(--alert); padding-left: 8px; }
  table.ship-table tr.flag > td:first-child { box-shadow: none; }
  table.ship-table tr.subsection-row { display: block; padding: 6px 0 2px; border-bottom: none; }
  table.ship-table tr.subsection-row th {
    display: block; background: none; padding: 0;
    font-size: 12px; color: var(--fg-muted); border-bottom: none;
  }
  table.ship-table tbody tr:hover td { background: none; }
}

/* ---------------------------------------------------------------------------
   --- rail --- (IA v1: left sidebar navigation, spec_track_ia_sidebar.md)
   One nav surface per breakpoint:
     >=900px  fixed 240px left rail (collapsible to 56px), NO top header.
     <900px   slim navy topbar (hamburger + brand + search) + the SAME rail
              markup off-canvas as a 280px focus-managed drawer. NO bottom bar.
   ALL rail-collapse rules are scoped >=900px so a stale localStorage
   ia.rail=1 can never strip labels/search out of the mobile drawer.
   --------------------------------------------------------------------------- */
.rail {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: 240px;
  background: var(--rail-bg);
  border-right: 1px solid var(--border);
  z-index: 300;
  display: flex;
  flex-direction: column;
}
/* ---- Circle Lab brand mark ------------------------------------------------
   An IDENTITY MARK, not application colour: it keeps its own brand colours,
   is exempt from the two-hue budget, and never theme-flips. Shipped as an
   asset (see _ALLOWED_ICONS / _brand_mark) so no brand hex sits in this file.
   Both variants are in the markup and exactly ONE renders per theme, chosen
   by the same pure-CSS data-theme flip the sun/moon icons use — correct from
   first paint, no JS. Only the nucleus differs between the two files; the
   three orbital strokes are identical brand colour in both. */
.brand-mark { display: block; }
:root[data-theme="light"] .brand-mark-dark  { display: none; }
:root[data-theme="dark"]  .brand-mark-light { display: none; }

.rail-brand {
  display: flex; align-items: center; gap: 10px;
  padding: 16px 16px 12px;
  text-decoration: none;
  flex: 0 0 auto;
  /* MUST be explicit, and it is STILL load-bearing — do not delete it.
     This <a> has no color of its own, so it inherits the USER-AGENT default
     link blue and paints every unstyled descendant in it. A source-text gate
     cannot see a UA fallback — there is no literal to find; the §7 G2
     computed-style sweep is what caught it. (Writing that hex here, even in a
     comment, trips §1.3 — as it should.) The brand mark itself no longer
     depends on this: it is a brand-coloured <img> asset, not currentColor. */
  color: var(--chrome-fg);
}
.rail-brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rail-brand .brand-mark { width: 32px; height: 32px; flex-shrink: 0; }
.rail-brand-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rail-brand .wordmark {
  font-size: 14px; letter-spacing: 0.04em; line-height: 1;
  color: var(--fg); white-space: nowrap;
}
.rail-brand .wm-circle { font-weight: 500; }
.rail-brand .wm-lab { font-weight: 300; margin-left: 2px; }
.rail-brand .rail-sub {
  font-size: 10px; letter-spacing: 0.08em; font-weight: 600;
  color: var(--fg-secondary); white-space: nowrap;
}
/* Rail search (light styling — the rail is white, unlike the navy topbar). */
.rail-search {
  display: flex; align-items: stretch; gap: 6px;
  padding: 0 12px 10px;
  flex: 0 0 auto;
}
.rail-search input[type="search"] {
  flex: 1 1 auto; min-width: 0;
  padding: 8px 12px;
  font-size: 13px;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  border: 1px solid var(--border);
  border-radius: 8px;
  outline: none;
  background: var(--recess);
  color: var(--fg);
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
  -webkit-appearance: none;
  appearance: none;
}
.rail-search input[type="search"]::placeholder { color: var(--fg-muted); }
.rail-search input[type="search"]:focus {
  border-color: var(--accent);
  background: var(--surface);
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.rail-search button {
  flex: 0 0 auto;
  width: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--recess);
  color: var(--fg-secondary);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.rail-search button:hover { background: var(--border); color: var(--fg); }
/* Collapsed-rail magnifier (replaces the input at 56px so search stays
   reachable). Hidden by default; revealed only by the >=900px collapsed rules. */
.rail-search-ico {
  display: none;
  align-items: center; justify-content: center;
  width: 36px; height: 36px;
  margin: 2px auto 8px;
  background: none; border: none; border-radius: 8px;
  color: var(--fg-muted); cursor: pointer;
}
.rail-search-ico:hover { background: var(--recess); color: var(--fg); }
.rail-nav {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 2px 0 8px;
}
.rail-item {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  min-height: 36px;
  padding: 8px 12px;
  margin: 2px 8px;
  border-radius: 8px;
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-secondary);
  transition: background 0.15s, color 0.15s;
}
.rail-item .rail-ico { display: inline-flex; flex: 0 0 auto; color: var(--fg-muted); }
.rail-item:hover { background: var(--recess); color: var(--fg); }
.rail-item:hover .rail-ico { color: var(--fg); }
.rail-item.active {
  background: var(--active-nav);
  color: var(--fg); /* 13.4:1 on var(--active-nav) */
  font-weight: 600;
}
.rail-item.active .rail-ico { color: var(--fg); }
.rail-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rail-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-badge {
  flex: 0 0 auto;
  margin-left: auto;
  /* §1.2 rule 2: the count already carries the meaning, so the fill spends
     nothing here.
     The COLLAPSED rail turns this into a bare 8px dot (font-size:0). That
     variant IS colour-only, and it takes --alert on its own justification:
     the count survives in the accessibility tree at font-size:0, so the
     colour is a redundant visual cue, never the sole carrier.
     (Rewritten 2026-08-04, Iris S3. It used to justify itself by analogy to
     "the mobile hamburger dot (§2.9)" — which this build deleted, because
     the page it counted for is gone. A comment must not rest its reasoning
     on a rule that no longer exists.) */
  background: var(--line-2);
  color: var(--ink);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  min-width: 18px;
  padding: 2px 6px;
  border-radius: 999px;
  text-align: center;
}
.rail-admin-label {
  display: flex; align-items: center; gap: 8px;
  margin: 10px 20px 2px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--fg-secondary);
}
.rail-admin-label::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.rail-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: 8px 12px;
  display: flex; align-items: center; gap: 8px;
}
.rail-session { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--fg-secondary); }
.rail-session .rail-email {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail-session a { color: var(--fg); font-weight: 600; text-decoration: none; font-size: 12px; }
.rail-session a:hover { text-decoration: underline; }
.rail-collapse {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: none; border: none; border-radius: 8px;
  color: var(--fg-muted); cursor: pointer;
}
.rail-collapse:hover { background: var(--recess); color: var(--fg); }
.rail-collapse:focus-visible, .rail-search-ico:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
/* ---- Theme toggle: rail footer (desktop) + topbar twin (mobile) ---- */
.rail-theme {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: none; border: none; border-radius: 8px;
  color: var(--fg-muted); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.rail-theme:hover { background: var(--recess); color: var(--fg); }
.rail-theme:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rail-theme .ico { display: inline-flex; }
.tb-theme {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: 8px;
  color: var(--chrome-fg); cursor: pointer;
}
.tb-theme:hover { background: rgba(127,127,127,0.14); }
.tb-theme:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tb-theme .ico { display: inline-flex; }
/* Pure-CSS state: LIGHT shows the sun, DARK shows the moon (no JS icon sync). */
:root[data-theme="light"] .ico-moon { display: none; }
:root[data-theme="dark"]  .ico-sun  { display: none; }

/* ---- Mobile topbar (<900px only; display:none on desktop) ---- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 250;
  background: var(--chrome-bg);
  color: var(--chrome-fg);
  border-bottom: 1px solid var(--chrome-line);
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  min-height: 56px;
}
.tb-burger {
  position: relative;
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: 8px;
  color: var(--chrome-fg); cursor: pointer;
}
.tb-burger:hover { background: rgba(127,127,127,0.14); }
.tb-burger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* .tb-dot (the burger's open-Issues exception dot) REMOVED 2026-08-04 with
   the /logistics nav item — it signalled a condition whose page is deleted,
   so it could never be cleared or navigated to. See _topbar_html(). */
.tb-brand {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; color: var(--chrome-fg);
}
/* MOBILE BRAND RULE (Iris brand-identity §5): 24px coloured mark only —
   the topbar lives <900px by definition, so no visible words at any of its
   widths; the anchor's aria-label carries the name. */
.tb-brand .brand-mark { width: 24px; height: 24px; flex-shrink: 0; }
.tb-search {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: stretch; gap: 6px;
}
.tb-search input[type="search"] {
  flex: 1 1 auto; min-width: 0;
  padding: 8px 12px;
  font-size: 16px; /* 16px avoids iOS zoom-on-focus */
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  outline: none;
  background: var(--bg);
  color: var(--ink);
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
  -webkit-appearance: none;
  appearance: none;
}
.tb-search input[type="search"]::placeholder { color: var(--ink-3); }
.tb-search input[type="search"]:focus {
  border-color: var(--accent);
  background: var(--bg);
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.tb-search button {
  flex: 0 0 auto;
  width: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent);
  color: var(--primary-btn-fg);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: opacity 0.2s;
}
.tb-search button:hover { opacity: .88; }
.drawer-backdrop {
  display: none;
  position: fixed; inset: 0;
  background: var(--scrim);
  z-index: 350;
}

/* ---- Desktop >=900px: rail pinned, content shifted, no topbar ---- */
@media (min-width: 900px) {
  .topbar { display: none; }
  .drawer-backdrop { display: none !important; }
  body.with-rail { padding-left: 240px; }
  .rail { transition: width 0.15s ease; }
  /* Collapse-to-56px. EVERY collapsed rule lives inside this media block
     (exports lesson, binding): a stale ia.rail=1 must never affect <900px. */
  html.rail-collapsed body.with-rail { padding-left: 56px; }
  html.rail-collapsed .rail { width: 56px; }
  html.rail-collapsed .rail-brand { justify-content: center; padding: 14px 8px 8px; }
  html.rail-collapsed .rail-brand-text,
  html.rail-collapsed .rail-search,
  html.rail-collapsed .rail-label,
  html.rail-collapsed .rail-admin-label,
  html.rail-collapsed .rail-session { display: none; }
  html.rail-collapsed .rail-search-ico { display: flex; }
  html.rail-collapsed .rail-item { justify-content: center; padding: 8px 0; margin: 2px 6px; gap: 0; }
  /* Count badges collapse to an 8px dot pinned to the icon corner. */
  html.rail-collapsed .rail-badge {
    position: absolute; top: 5px; right: 8px;
    width: 8px; height: 8px; min-width: 8px;
    padding: 0; border-radius: 50%;
    font-size: 0; line-height: 0;
    margin-left: 0;
    background: var(--alert);
  }
  /* Collapsed rail: keep the Admin group SEPARATED (Iris S3, 2026-08-04).
     The label is display:none when collapsed, so the group boundary used to
     be carried only by run length — six items then one. At three items then
     one, four undifferentiated glyphs give the reader no cue that the last
     one is a different class of thing. Re-purpose the (already aria-hidden)
     label element as a 1px rule: more space between groups than within them.
     Must come AFTER the display:none grouping above to win on source order.
     --border is theme-paired, so one rule covers light and dark. */
  html.rail-collapsed .rail-admin-label {
    display: block; height: 1px; margin: 10px 14px 6px;
    padding: 0; font-size: 0; background: var(--border);
  }
  html.rail-collapsed .rail-admin-label::after { display: none; }
  html.rail-collapsed .rail-foot { flex-direction: column; justify-content: center; gap: 4px; padding: 8px 4px; }
  html.rail-collapsed .rail-collapse svg { transform: rotate(180deg); }
}

/* ---- Mobile <900px: rail becomes the off-canvas drawer ---- */
@media (max-width: 899.98px) {
  .rail {
    width: 280px;
    max-width: 85vw;
    z-index: 400;
    transform: translateX(-102%);
    /* visibility:hidden while closed removes the off-canvas links from the
       tab order + a11y tree (Rune minor: transform-only hiding left them
       tabbable). The 0.2s visibility delay keeps the close slide visible;
       on open, visibility flips immediately so the slide-in is visible. */
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0s linear 0.2s;
    box-shadow: var(--elev-lg);
  }
  body.drawer-open .rail {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.2s ease;
  }
  body.drawer-open .drawer-backdrop { display: block; }
  body.drawer-open { overflow: hidden; }
  /* >=44px touch rows in the drawer (desktop density stays 36px). */
  .rail-item { padding: 12px 16px; min-height: 44px; }
  .rail-collapse { display: none; } /* collapse is a desktop-only affordance */
  /* MOBILE BRAND RULE (Iris brand-identity §4): drawer head shows the 24px
     coloured mark only — the anchor's aria-label keeps the accessible name.
     >=900px the rail keeps its full 32px lockup + sub untouched. */
  .rail-brand-text { display: none; }
  .rail-brand .brand-mark { width: 24px; height: 24px; }
}

/* ---- /search compact result rows (S3 #7) ---- */
.result-row {
  display: block;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: var(--fg);
  transition: background 0.15s;
}
/* Result lists are <ul>/<li> (WCAG 1.3.1 — a 105-row list must announce its
   extent). THE TRAP: once each <a> is the only child of its <li>, EVERY row
   becomes :last-child and all 105 separators vanish. So the separator is
   restored on the anchor and suppressed on the last LIST ITEM instead.
   --border is theme-paired; one rule covers light and dark. */
.card > li { list-style: none; }
.result-row:last-child { border-bottom: 1px solid var(--border); }
.card > li:last-child .result-row { border-bottom: none; }
.result-row:hover { background: var(--recess); }
.result-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.result-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.result-id { font-weight: 600; flex: 0 0 auto; }
.result-route {
  margin-left: auto; flex: 0 1 auto; min-width: 0;
  font-size: 12px; color: var(--fg-secondary);
  text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.result-summary {
  display: block; font-size: 13px; color: var(--fg-secondary); margin-top: 1px;
}

/* ---- Shared utilities (Tranche 2) ---- */
.tnum { font-variant-numeric: tabular-nums; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
/* Phase 0 §4: tabular figures on every count badge so stacked numbers align. */
.badge, .stage-badge, .rail-badge, .sub-count, .ta-group-count, .days-badge,
.pipeline-segment, .yard-count, .exc-count {
  font-variant-numeric: tabular-nums;
}

/* ---- Page-head shell (Phase 0 §2, exports parity) ---- */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 1000;
  background: var(--surface); color: var(--fg);
  padding: 8px 16px; border-radius: 0 0 8px 0;
  font-size: 13px; font-weight: 600; text-decoration: none;
  box-shadow: var(--elev-sm);
}
.skip-link:focus-visible { left: 0; outline: 2px solid var(--accent); }
/* P2-Excel §2: ONE export affordance, ONE position — right-aligned on the h1
   row of the page-head shell (page_wrapper export_href/export_dataset). */
.page-head {
  margin: 2px 0 16px;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px;
}
.page-head-main { min-width: 0; }
.page-head h1 {
  font-size: 21px; font-weight: 700; letter-spacing: -0.01em;
  color: var(--fg); line-height: 1.3;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.page-head p { font-size: 12px; color: var(--ink-3); margin-top: 2px;
  font-variant-numeric: tabular-nums; }
.page-head--pad { padding: 12px 16px 0; margin-bottom: 4px; }
.page-head .export-btn { flex: 0 0 auto; }
@media (max-width: 899px) {
  /* >=44px touch target on the export download (P2-Excel §2). */
  .page-head .export-btn { padding: 11px 14px; min-height: 44px; }
}

/* ---- KPI grammar. §3.7 deletes the 5-card KPI grid on HOME and replaces it
        with the stage tiles below; .kpi-grid survives ONLY on the pages that
        genuinely have a card ladder (/kampala, /borders, /sync, /issues).
        Boxes: one border weight, radius 6, no shadow (§4). ---- */
.kpi-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 12px; margin-bottom: 16px;
}
.kpi-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 6px; padding: 14px 12px;
  text-align: center;
}
.kpi-value {
  font-size: 24px; font-weight: 600; line-height: 1.2;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.kpi-label { font-size: 12px; color: var(--ink-2); margin-top: 2px; }
.kpi-sub { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
a.kpi-link {
  display: block; text-decoration: none; color: inherit;
  transition: border-color .15s, background .15s;
}
a.kpi-link:hover, a.kpi-link:focus-visible {
  border-color: var(--accent); background: var(--surface);
}
a.kpi-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- §3.7.2 stage tiles (HOME). Replaces the 5-card KPI grid. One axis per
        row — stages only. Whole tile is the anchor link. No box, no shadow. */
.stage-tiles {
  display: flex; flex-wrap: wrap; gap: 32px;
  margin: 0 0 12px;
}
.stage-tile {
  display: block; text-decoration: none; color: inherit;
  border-radius: 6px; padding: 2px 4px; margin: -2px -4px;
}
.stage-tile .st-fig {
  display: block; font-size: 24px; font-weight: 600; color: var(--ink);
  line-height: 1.15; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.stage-tile .st-lab { display: block; font-size: 12px; color: var(--ink-2); }
.stage-tile:hover .st-fig, .stage-tile:hover .st-lab { color: var(--accent); }
.stage-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* §3.7.3 the two carried KPI facts, as text links under the bar. */
.glance-facts { font-size: 12px; color: var(--ink-2); margin-top: 8px; }
.glance-facts a { color: var(--ink-2); text-decoration: none; }
.glance-facts a:hover { color: var(--accent); text-decoration: underline; }
.glance-legend { font-size: 12px; color: var(--ink-2); margin-top: 8px;
  display: flex; flex-wrap: wrap; gap: 4px 12px; }
.glance-legend a { color: var(--ink-2); text-decoration: none; }
.glance-legend a:hover { color: var(--accent); text-decoration: underline; }
@media (max-width: 480px) {
  .stage-tiles { gap: 16px; }
  .stage-tile .st-fig { font-size: 20px; }
}


/* ---- P3-2 Plotly chart containers. Explicit height + width:100% — Plotly
        (autosize + responsive config) fills the box, so charts track the
        card width at every breakpoint and can never cause body h-scroll.
        min-width:0 guards against flex/grid ancestors refusing to shrink. */
.chart-box { width: 100%; min-width: 0; height: 240px; }
.chart-box + .chart-sub { margin-top: 16px; }
.chart-sub {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--fg-muted); margin-bottom: 4px;
}
@media (max-width: 600px) { .chart-box { height: 200px; } }

/* Reduced motion (Phase 0 §7): kill smooth-scroll, spinners, transitions.
   P2-2: also kill the linked-KPI hover lift (transform is not a transition). */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* §2.1/§2.2/§2.3: the entire categorical hue layer is DELETED. Stage, mode,
   activity category, border phase and yard phase are CATEGORIES — they are
   named in words and ordered by position, and categories never earn hue
   (§1.2 rule 1). The .hbg-/.hc-/.hbd- classes, the --hue-*/--huetext-* tokens
   and the .legend-dot element are all gone; Python emits no hue class names.
   Kampala stage groups keep the rule, in neutral. */
.yard-stage-group.staged {
  border-left: 3px solid var(--line-2);
  border-radius: 0 8px 8px 0;
}
/* Doc-count link (was inline style + onmouseover/out handlers, Phase 0 §6). */
.doc-count-link {
  text-decoration: none; font-size: 13px; color: var(--fg-muted);
  transition: color 0.2s; white-space: nowrap;
}
.doc-count-link:hover { color: var(--accent); }
.doc-count-link--sm { font-size: 11px; }

/* ---- Activity feed rows (shared: home strip, /today, borders, kampala).
        Was duplicated verbatim in the borders + kampala extra_head blocks
        AND re-declared as .ta-activity-* for the home strip (Phase 1 §4
        dedupe: ONE block, all four surfaces). ---- */
.activity-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.activity-row:last-child { border-bottom: none; }
.activity-row a {
  color: var(--fg); text-decoration: none; font-weight: 600;
  font-size: 14px; flex-shrink: 0;
}
.activity-row a:hover { color: var(--accent); }
/* §2.2: the event VERB is the signal. No fill, no border, no radius. */
.activity-event {
  display: inline-block; padding: 0;
  font-size: 12px; font-weight: 500; color: var(--ink);
  flex-shrink: 0; white-space: nowrap;
}
.activity-meta {
  flex: 1; display: flex; align-items: center; gap: 8px; justify-content: flex-end;
  font-size: 12px; color: var(--fg-secondary); min-width: 0; flex-wrap: wrap;
}
.activity-meta .ctr-txt {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 130px;
}
.activity-meta .ta-loc {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 180px;
}

/* ---- §1 Exception-first home band (Iris §1) ---- */
.exc-band {
  border: 1px solid var(--line); border-left: 4px solid var(--alert);
  border-radius: 6px; background: var(--surface);
  padding: 10px 14px; margin-bottom: 14px;
}
/* Quiet state: absence of the alert IS the good news (§2.4). No green. */
.exc-band-quiet { border-left-color: var(--line-2); }
.exc-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
.exc-glance { font-size: 14px; color: var(--fg); }
.exc-glance b { color: var(--ink); font-weight: 600; }
.exc-muted { color: var(--fg-secondary); }
.exc-ok { color: var(--ink-2); font-weight: 600; }
.exc-viewall {
  font-size: 12px; color: var(--accent); text-decoration: none;
  white-space: nowrap; flex: 0 0 auto;
}
.exc-viewall:hover { text-decoration: underline; }
.exc-rows { margin-top: 8px; }
.exc-row {
  display: block; text-decoration: none; color: var(--fg);
  padding: 7px 0; border-top: 1px solid var(--border);
}
.exc-row:hover { background: var(--recess); }
.exc-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.exc-line1 {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.exc-sid { font-weight: 600; flex: 0 0 auto; }
.exc-count {
  display: inline-block; margin-left: 5px; padding: 0 6px;
  background: none; color: var(--ink-3); border-radius: 0;
  font-size: 12px; font-weight: 400; vertical-align: middle;
  font-variant-numeric: tabular-nums;
}
.exc-pill {
  flex: 0 0 auto; font-size: 11px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px;
  background: none; color: var(--ink-2); border: 1px solid var(--line);
}
.exc-age {
  margin-left: auto; flex: 0 0 auto; text-align: right;
  font-size: 12px; color: var(--fg-secondary); white-space: nowrap;
}
.exc-summary {
  display: block; font-size: 12px; color: var(--fg-secondary); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 899px) {
  /* Mobile: exactly 3 rows (hide 4-5), zero JS (Iris §1.6). */
  .exc-row:nth-child(n+4) { display: none; }
  /* Iris F1: the pill is the shrink element so SID + full stuck-age never clip
     on a field phone at 360/390 (she measured the overflow). Pill ellipsizes,
     the sort-figure stays intact. */
  .exc-pill { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .exc-sid { min-width: 0; }
}

/* ---- §2a Cargo manifest (PO-grouped) + PO chips ---- */
table.data-table.cargo-manifest td, table.data-table.po-lines td { vertical-align: top; }
.cargo-po-row td { background: var(--recess); border-top: 2px solid var(--border); }
.cargo-po-id { font-weight: 700; }
.cargo-po-id a { color: var(--fg); text-decoration: none; }
.cargo-po-id a:hover { text-decoration: underline; }
.cargo-po-vendor { margin-left: 10px; color: var(--fg-secondary); font-size: 12px; }
.cargo-po-meta { text-align: right; color: var(--fg-secondary); font-size: 12px; white-space: nowrap; }
.cargo-item { white-space: nowrap; }
.cargo-desc { min-width: 220px; }
.po-chip-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 12px; }
/* .po-chip-label deleted 2026-08-05 — the "PO" text it styled was removed as
   redundant against POA000nnnn chip values. No emitter remains. */
.po-chip {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  background: var(--active-nav); color: var(--fg); text-decoration: none;
  font-size: 12px; line-height: 1.5;
}
.po-chip:hover { background: var(--surface-2); text-decoration: underline; }

/* ---- §2b Per-PO page ---- */
.po-glance {
  margin-top: 12px; padding: 8px 12px; border-radius: 8px;
  background: var(--recess); font-size: 13px; color: var(--fg);
}
@media (max-width: 899px) {
  /* Line-items -> card-per-row (Iris §2b.5): no horizontal scroll on a phone. */
  table.po-lines thead { display: none; }
  table.po-lines, table.po-lines tbody, table.po-lines tr, table.po-lines td { display: block; width: auto; }
  table.po-lines tr { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; padding: 8px 10px; }
  table.po-lines td { border: none; padding: 2px 0; text-align: left !important; }
  /* P2-3: no hover wash on transformed card rows (real-table hover only). */
  table.po-lines tbody tr:hover td { background: none; }
  table.po-lines td.cargo-desc { font-weight: 600; }
  /* GRN cell reflows onto its own card line (Iris §6): chip line + date line
     stack full-width; 44px touch target on the receipts disclosure. */
  table.po-lines td.grn-cell { padding-top: 4px; }
  .grn-receipts summary { min-height: 44px; }
  .grn-bar { max-width: none; }
}

/* ---- Map canvas: chrome-offset heights (was inline calc(100vh - 170px),
        tuned for the old header+bottom-bar; now topbar-only on mobile and
        rail-only on desktop) ---- */
/* ---- Server-computed severity dots -> themeable CSS classes (spec §4.3) ---- */
.pri-badge { display:inline-block; font-size:10px; font-weight:700; padding:2px 6px;
  border-radius:4px; margin-right:4px; vertical-align:middle; }
/* §1.2: three tiers, one axis -> a TONE ramp. No hue is doing work here. */
.pri-badge.pri-1 { background: var(--ink); color: var(--bg); }
.pri-badge.pri-2 { background: var(--ink-3); color: var(--bg); }
.pri-badge.pri-3 { background: none; color: var(--ink-2); border: 1px solid var(--line-2); }
.flash-banner { padding:10px 14px; border-radius:6px; font-size:13px; margin-bottom:12px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink); }
.flash-banner.flash-ok  { border-left: 3px solid var(--line-2); }
.flash-banner.flash-err { border-left: 3px solid var(--alert); }

.glance { margin: 0 0 32px; }
.page-foot { font-size: 12px; color: var(--ink-3); margin-top: 24px; }
/* §4 spacing: more space BETWEEN groups than within them. */
.card.stage-card { margin-bottom: 24px; }
.map-canvas {
  width: 100%;
  height: calc(100vh - 140px);
  min-height: 400px;
  display: flex; align-items: center; justify-content: center;
  color: var(--fg-muted); font-size: 15px;
}
@media (min-width: 900px) {
  .map-canvas { height: calc(100vh - 72px); }
}


/* ================================================================
   track.abmdrc.app v2 — CONSOLIDATED STYLESHEET, PER-PAGE SCOPED
   Merged from Iris's three mockups: HOME (/), ROOM (/at/…), RECORD (/c/…).

   SCOPING CONTRACT — every v2 page must render exactly one of:
       <body class="v2 v2-home">
       <body class="v2 v2-room">
       <body class="v2 v2-record">
   Nothing here matches a page that does not carry class "v2", so the ~2000
   lines of v1 CSS and the v1 pages (/sync, /po, /search, legacy detail) are
   untouched.

   Prefix meanings:
     body.v2 …          rule was authored identically in ALL THREE mockups
     body.v2-a, body.v2-b …  authored identically in exactly those two
     body.v2-home …     authored by that page only, OR that page's own version
                        of a selector the other pages authored differently
   Every page therefore receives byte-identical declarations to its own
   mockup — no merged hybrids, no cross-page inheritance.

   Zero declaration values changed. Mockup scaffolding removed (.mockbar,
   body.m390 .canvas). @container (max-width: 430px) → @media (max-width:688px),
   rules inside unchanged; container-type:inline-size dropped from .canvas.
   ================================================================ */

/* ================================================================
   ORIGINAL FILE HEADERS (Iris's design notes, verbatim)
   ---------------------------------------------------------------- */
/* HOME:
   track.abmdrc.app — HOME (/) — Iris, design to spec_track_architecture_v2
   rev 2026-08-02b (§8 delta table 1–9).
   Container is the atom. Depths sum to 105. Worklist ordered sequentially,
   origin → destination, one row per ISSUE (one observed cause + every
   container it holds; n=1 is the common case).
   Two hues only: --accent (interactive) · --alert (conflicts only, 4 marks).
   No green. Achromatic everything else.
   Container queries stand in for media queries so one file shows both
   widths; shipped as @media (max-width:688px) -- see the note on the block. */
/* ROOM:
   track.abmdrc.app — ROOM (/at/kampala) — Iris, to spec §2.2
   One template, 8–9 instances. Kampala yard shown because it carries
   every optional part: ladder slot, inbound-from-Entebbe,
   two parallel reporting writers.
   Two hues. --alert budget on this page: ≤2. */
/* RECORD:
   track.abmdrc.app — THE RECORD (/c/S41590-A) — Iris, to spec §2.3
   rev 2026-08-02b: the page is PER CONTAINER, not per stem (delta 7). The stem
   survives in exactly one place — Related containers — where co-location is
   OBSERVED, never asserted: every sibling carries its own room, state and age.
   The artefact ABM puts in front of a counterparty six weeks later.
   Fourth column (told us) is mandatory. Contested renders as contested.
   Derived values carry their derivation permanently and never alert.
   --alert budget on this page: ≤1. */

/* ================================================================
   a. TOKENS — scoped to the v2 body subtree.
   The app stamps data-theme on <html> pre-paint, so the ancestor attribute
   matches; the custom properties then inherit to every descendant of body.
   v1 pages keep whatever v1 declares for these same names.
   Union of all three files. All shared token values verified byte-identical
   across home/room/record — zero value disagreements. --accent-soft and
   --alert-soft are declared by HOME only; adding them to the shared block is
   purely additive (room/record reference neither).
   ================================================================ */
[data-theme="light"] body.v2{
  --bg:#F6F7F9; --surface:#FFFFFF; --recess:#F1F3F6; --recess-2:#E9EDF3;
  --fg:#0D1634; --fg-2:#3F4A63; --fg-muted:#67728A;
  --border:#E2E6EC; --border-strong:#C8D0DC; --rule:#EDF0F4;
  --accent:#1550E0; --accent-soft:#E8EEFC;
  --alert:#B0201A; --alert-soft:#FBEDEC; --alert-rule:#B0201A;
  --focus:#1550E0;
  --shadow:0 1px 2px rgba(13,22,52,.05);
  /* Drawer scrim (Iris rail spec §5) — the ONE new token this build adds.
     Same neutral family as --shadow's literal, achromatic, declared in BOTH
     theme blocks (a one-theme token is not a token). */
  --scrim:rgba(13,22,52,.45);
}
[data-theme="dark"] body.v2{
  --bg:#0B111F; --surface:#141B2E; --recess:#1B2338; --recess-2:#222B43;
  --fg:#E9ECF4; --fg-2:#B6BFD4; --fg-muted:#8B94AA;
  --border:#26304A; --border-strong:#3A4762; --rule:#1F283E;
  --accent:#7FA8FF; --accent-soft:#17223C;
  --alert:#F8827A; --alert-soft:#2A1614; --alert-rule:#F8827A;
  --focus:#7FA8FF;
  --shadow:0 1px 2px rgba(0,0,0,.35);
  --scrim:rgba(0,0,0,.62);
}

/* ================================================================
   b. BASE / RESET — identical in all three files
   NB: the authored `html,body{margin:0;padding:0}` drops its `html` half —
   `html` must not be touched globally. No impact: the UA 8px default lives
   on body, not html.
   ================================================================ */
body.v2 *{box-sizing:border-box}
body.v2{margin:0;padding:0}
body.v2{background:var(--bg);color:var(--fg);
  font-family:"IBM Plex Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}
body.v2 a{color:var(--accent);text-decoration:none}
body.v2 a:focus-visible,body.v2 button:focus-visible,body.v2 summary:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:3px}
body.v2 .tn{font-variant-numeric:tabular-nums}
body.v2 .mu{color:var(--fg-muted)}

/* ================================================================
   c. SHELL — canvas, top bar, page frame
   ================================================================ */
/* ---------- canvas ---------- */
/* container-type:inline-size dropped: mockup-only, media queries ship instead */
body.v2 .canvas{margin:0 auto;background:var(--bg);min-height:100vh}

/* ---------- top bar: 3 leaves, search, sync ---------- */
/* .top — HOME carries gap:0 and the 30/20/15 sticky stack; ROOM/RECORD use 20 */
body.v2-home .top{display:flex;align-items:center;gap:0;height:46px;padding:0 20px;
  background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:30}
body.v2-room .top,body.v2-record .top{display:flex;align-items:center;height:46px;padding:0 20px;background:var(--surface);
  border-bottom:1px solid var(--border);position:sticky;top:0;z-index:20}

/* .brand — the ONE desktop brand placement (Iris spec_track_brand_identity §2):
   home anchor, full Circle Lab lockup + app token. Lockup letter-spacing is
   +.04em everywhere — brand type, not display type (the old home -.01em
   special-case is deliberately dead). All colours are theme tokens, so both
   themes are covered by definition. */
body.v2 .brand{display:flex;align-items:center;gap:8px;margin-right:22px;
  text-decoration:none;white-space:nowrap}
body.v2 .brand:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
body.v2 .brand:hover .brand-words{opacity:.82}
/* NO `display` on .brand-mark HERE. The theme flip is :root[data-theme=...]
   .brand-mark-* {display:none} at specificity (0,3,0); this selector is
   (0,3,1), so declaring display would out-specify it and render BOTH marks at
   once -- the inbound-leakage trap in reverse. Size only; v1's .brand-mark
   {display:block} and the flip keep ownership of visibility. */
body.v2 .brand .brand-mark{width:22px;height:22px;flex-shrink:0}
body.v2 .brand-words{display:flex;align-items:baseline;gap:8px;white-space:nowrap}
body.v2 .wordmark{font-size:13px;letter-spacing:.04em;line-height:1;color:var(--fg)}
body.v2 .wm-circle{font-weight:500}
body.v2 .wm-lab{font-weight:300;margin-left:2px}
body.v2 .bsub{font-size:10.5px;letter-spacing:.08em;font-weight:500;
  text-transform:uppercase;color:var(--fg-muted)}
/* Space guard (railoff only): with leaves + search in the bar at 900-1180px,
   the app token yields first; the lockup NEVER does. */
@media (min-width:900px) and (max-width:1179.98px){
  html.railoff body.v2 .bsub{display:none}
}

/* .leaves is the ONLY shrinkable box in the bar (.search and .topright are both
   flex:none), so it absorbs every squeeze. overflow:hidden is load-bearing:
   .leaf is white-space:nowrap, so without it a bar that runs out of room
   OVERLAPS the search field instead of clipping. That matters most in the
   689-780px band, which lost ~70px of slack when .signout joined .topright —
   just above the 431-688px overflow band fixed in ced220b. */
body.v2 .leaves{display:flex;gap:2px;flex:1;min-width:0;overflow:hidden}
body.v2 .leaf{font-size:12.5px;font-weight:500;color:var(--fg-2);padding:6px 11px;border-radius:6px;white-space:nowrap}
body.v2 .leaf:hover{background:var(--recess)}
body.v2 .leaf[aria-current="page"]{color:var(--fg);font-weight:600;background:var(--recess-2)}
/* Worklist count. It sits on the leaf that NAVIGATES TO THE WORK, replacing a
   bar under the corridor strip that marked rooms on a page which no longer
   holds the work (Iris, 2026-08-04). Muted by default -- it is a wayfinding
   figure, not an alarm -- and it takes the leaf's own colour when current. */
body.v2 .leaf .lcnt{margin-left:2px;font-size:11px;font-weight:600;
  color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2 .leaf[aria-current="page"] .lcnt{color:var(--fg-2)}
body.v2 .search{display:flex;align-items:center;gap:7px;background:var(--recess);border:1px solid var(--border);
  border-radius:7px;height:30px;padding:0 10px;width:260px;flex:none}
body.v2 .search input{border:0;background:transparent;font:inherit;font-size:12.5px;color:var(--fg);width:100%;outline:0}
body.v2 .search input::placeholder{color:var(--fg-muted)}
body.v2 .search svg{width:14px;height:14px;flex:none;stroke:var(--fg-muted);fill:none;stroke-width:1.8}
/* The magnifier label. On desktop it is inert-looking but still focuses the
   field; on mobile it IS the control that opens it. */
body.v2 .sicon{display:flex;align-items:center;justify-content:center;flex:none;cursor:text}
body.v2 .topright{display:flex;align-items:center;gap:14px;margin-left:14px;flex:none}
body.v2 .asat{font-size:11px;color:var(--fg-muted);white-space:nowrap}
body.v2 .adminleaf{font-size:11.5px;color:var(--fg-muted)}
body.v2 .signout{display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--fg-muted);
  text-decoration:none;white-space:nowrap}
body.v2 .signout:hover{color:var(--fg)}
body.v2 .signout svg{width:14px;height:14px;flex:none;stroke:currentColor;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}

/* Theme switch. The v2 chrome shipped with NO theme control at any breakpoint:
   Today, the room pages and the record page could READ the persisted theme but
   could not change it, so switching meant navigating to a v1-wrapped page
   (/search, /po, /sync) and back. The v1 rail button was the only control in
   the app, and v1 no longer owns the pages people spend their day on.
   Reuses v1's .ico-sun/.ico-moon pair — those flip rules are :root-scoped, not
   v1-scoped, so the sun/dark swap is inherited free and there is exactly one
   icon-state mechanism in the app rather than two that can disagree.
   Sized to .signout so the two controls read as one cluster. */
body.v2 .themeswitch{display:flex;align-items:center;justify-content:center;flex:none;
  width:24px;height:24px;padding:0;background:none;border:0;border-radius:6px;
  color:var(--fg-muted);cursor:pointer}
body.v2 .themeswitch:hover{color:var(--fg);background:var(--recess)}
body.v2 .themeswitch .ico{display:inline-flex}
body.v2 .themeswitch svg{width:14px;height:14px;flex:none;stroke:currentColor;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}

/* .page — HOME 16px top, ROOM/RECORD 12px top */
body.v2-home .page{padding:16px 20px 72px}
body.v2-room .page,body.v2-record .page{padding:12px 20px 72px}

/* ---------- crumb + h1 (ROOM + RECORD; HOME has neither) ---------- */
body.v2-room .crumb,body.v2-record .crumb{font-size:11.5px;color:var(--fg-muted);margin:0 0 6px}
body.v2-room .crumb a,body.v2-record .crumb a{color:var(--fg-muted)}
body.v2-room .crumb a:hover,body.v2-record .crumb a:hover{color:var(--accent)}
/* h1 — ROOM 19px with bottom margin, RECORD 20px flush */
body.v2-room h1{font-size:19px;font-weight:600;letter-spacing:-.01em;margin:0 0 10px}
body.v2-record h1{font-size:20px;font-weight:600;letter-spacing:-.01em;margin:0}

/* ================================================================
   d. SHARED COMPONENTS — section heads, tables, measures, claims
   ================================================================ */
/* ---------- section headings ---------- */
/* .sechead — three different margins; RECORD also hangs a .btn on the right */
body.v2-home .sechead{display:flex;align-items:baseline;gap:10px;margin:22px 0 7px}
body.v2-room .sechead{display:flex;align-items:baseline;gap:10px;margin:20px 0 7px}
body.v2-record .sechead{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin:0 0 7px}
body.v2 .sechead h2{font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-2);margin:0}
body.v2 .sechead .cnt{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums}

/* ---------- tables ---------- */
/* NB: no overflow:hidden on the table — it creates a scroll container and
   kills position:sticky. Corners are rounded on the corner cells instead. */
body.v2 .tbl{width:100%;border-collapse:separate;border-spacing:0;background:var(--surface);
  border:1px solid var(--border);border-radius:9px;box-shadow:var(--shadow)}
body.v2 .tbl thead th:first-child{border-top-left-radius:8px}
body.v2 .tbl thead th:last-child{border-top-right-radius:8px}

/* .tbl caption — HOME adds white-space:nowrap */
body.v2-home .tbl caption{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
body.v2-room .tbl caption,body.v2-record .tbl caption{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* .tbl thead th — HOME z-index:20 (its band sits at 15 beneath), others 10 */
/* F6: the band header sticks, and the column headers sit BELOW it. On a
   6109px page with ten bands you were mid-Kampala (43 rows) with column
   headers and no idea which room you were in. Same pattern already endorsed
   on the worklist bands. --bg resolves in both theme blocks; no new literals. */
body.v2 .fband{position:sticky;top:46px;z-index:3;background:var(--bg);
  padding-top:6px;padding-bottom:4px}
body.v2-home .tbl.queue thead th{top:76px}
body.v2-home .tbl thead th{position:sticky;top:46px;z-index:20;background:var(--recess);
  font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;color:var(--fg-muted);
  text-align:left;padding:7px 12px;border-bottom:1px solid var(--border);white-space:nowrap}
/* NO 76px OFFSET ON THE ROOM PAGE. A sticky offset is a property of THE PAGE
   THAT STACKS, not of the table -- it must be derived per scope from what
   actually sits above it there (Iris, rule recorded 2026-08-04). Only home
   stacks ten .fband headers; a room page has ONE room and an <h1> naming it,
   and zero .fband elements. Copying home's offset here left a 30px slot
   between the topbar (bottom 46) and the thead (top 76) with a live data row
   clipped and fully legible inside it, at every scroll position, on all eight
   occupied room pages, in both themes. */
body.v2-room .tbl thead th,body.v2-record .tbl thead th{position:sticky;top:46px;z-index:10;background:var(--recess);
  font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;color:var(--fg-muted);
  text-align:left;padding:7px 12px;border-bottom:1px solid var(--border);white-space:nowrap}

/* .tbl tbody td — HOME/ROOM 8px vertical, RECORD 7px */
body.v2-home .tbl tbody td,body.v2-room .tbl tbody td{padding:8px 12px;border-bottom:1px solid var(--rule);vertical-align:top}
body.v2-record .tbl tbody td{padding:7px 12px;border-bottom:1px solid var(--rule);vertical-align:top}

body.v2 .tbl tbody tr:last-child td{border-bottom:0}
/* row hover is ROOM+RECORD only — HOME deliberately scopes hover to tr.issue
   and tr.mem, and a blanket tr:hover would out-specify tr.mem's recess-2 */
body.v2-room .tbl tbody tr:hover td,body.v2-record .tbl tbody tr:hover td{background:var(--recess)}

/* bottom corner radii — HOME scopes to the last tbody (it has one per room
   band); ROOM scopes to any tbody's last row. Different page scopes, so the
   two forms can never both fire on one table. */
body.v2-home .tbl tbody:last-of-type tr:last-child td:first-child{border-bottom-left-radius:8px}
body.v2-home .tbl tbody:last-of-type tr:last-child td:last-child{border-bottom-right-radius:8px}
body.v2-room .tbl tbody tr:last-child td:first-child{border-bottom-left-radius:8px}
body.v2-room .tbl tbody tr:last-child td:last-child{border-bottom-right-radius:8px}

body.v2 .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}

/* ---------- shipment id — HOME 12.5px/500 inline, ROOM 13px/600 block ---------- */
body.v2-home .sid{font-size:12.5px;font-weight:500;color:var(--accent);white-space:nowrap}
body.v2-room .sid{font-size:13px;font-weight:600;color:var(--accent);white-space:nowrap;display:block}

/* ---------- Queue bands: vessel / rung / flight ----------
   (Piet item 1 2026-08-05; items 5 and 6 2026-08-06.)
   ACHROMATIC STRUCTURE — position and words. Vessel, rung and flight are
   categories, and categories are never coloured (§1.3 holds: every value
   below is a token). Desktop: a rowgroup header inside the queue table, same
   idiom as the worklist's th.band. Scoped to BOTH surfaces that render a
   banded queue — the room page AND home's fleet sections (item 6.1: banding
   is a property of the queue, and the same band object renders wherever the
   queue renders; the typographic subordination of these sub-headers under
   home's room headings is Iris's post-build). */
body.v2-home .tbl tbody th.vband,body.v2-room .tbl tbody th.vband{
  padding:10px 12px 7px;background:var(--recess);
  border-bottom:1px solid var(--rule);text-align:left}
body.v2-home .vband .vn,body.v2-room .vband .vn{font-size:12.5px;font-weight:600;letter-spacing:.02em;color:var(--fg)}
/* Iris F1 (2026-08-05): --fg-muted on --recess is 4.34:1 in light — the
   "same idiom as th.band" claim broke on background (the worklist band puts
   the token on --surface, 4.83). --fg-2 = 7.97:1 L / 8.48:1 D; the recess
   fill stays, hierarchy held by size/weight, not ink strength. */
body.v2-home .vband .vd,body.v2-room .vband .vd{font-size:11px;font-weight:400;color:var(--fg-2);
  font-variant-numeric:tabular-nums;margin-left:9px;white-space:normal}
/* Kampala rung bands (Piet item 5, 2026-08-06) reuse the exact same band
   grammar. A ZERO-COUNT band is a compact header-only line: tighter padding,
   header at body weight — the count is the reading, not a heading over
   members it does not have. Same achromatic tokens; no new colour. */
body.v2-home .tbl tbody th.vband.z,body.v2-room .tbl tbody th.vband.z{padding:5px 12px 4px}
body.v2-home .vband.z .vn,body.v2-room .vband.z .vn{font-weight:500;color:var(--fg-2)}

/* ---------- value / baseline — the two-word suffix IS the legend, per row ---------- */
/* .msr — HOME adds color:var(--fg) */
/* F9: .msr is SHARED by the Days-here and Last-report columns — an identical
   13px/600 tabular token twice per row. Only Last report can ever carry
   .nowrite. The hue's scope is therefore defined by which column the class
   sits in, not by the class: do not attach meaning to .msr itself. */
body.v2-home .msr{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--fg);white-space:nowrap}
body.v2-room .msr{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2-home .msr s,body.v2-room .msr s{text-decoration:none;font-weight:400;color:var(--fg-muted)}
/* THE SECOND HUE, AND THE ONLY OTHER ONE. Two roles in this app: the accent
   means "you can act on this", --alert means "THE RECORD HAS FAILED, NOT THE
   CARGO". Contested and dark are two conditions carrying that one meaning, so
   they share one hue and one budget (Iris, 2026-08-04).
   Deliberately NOT coloured: thresholds on a continuum (the retired
   `slow past p90` tier was the precedent) are already encoded by sort
   position and by the number itself -- colouring them double-encodes, and
   tiers are how you get a christmas tree. `blocked` is a
   category of CAUSE, and colouring categories is the original sin.
   The mark goes on the TOKEN THAT CARRIES THE FAILURE, never on a row
   background -- 17 of 107 are dark, worst band 6 of 43. The muted <s> rule
   above still applies, so "18d / 6d cadence" prints the failing figure in
   alert and its baseline grey: the comparison stays legible and only the
   failure is coloured.
   NAMED .nowrite, NOT .dark -- a class called `dark` sitting beside
   data-theme="dark" is a trap for whoever edits this next. */
body.v2-home .msr.nowrite,body.v2-room .msr.nowrite{color:var(--alert)}
/* .msr-b — HOME 10.5px, ROOM 11px + tabular-nums */
body.v2-home .msr-b{font-size:10.5px;color:var(--fg-muted);white-space:nowrap;display:block}
body.v2-room .msr-b{font-size:11px;color:var(--fg-muted);white-space:nowrap;display:block;font-variant-numeric:tabular-nums}

/* ---------- contested: a stack of equal-weight claims, achromatic grouping rule, no winner ---------- */
/* .claims — HOME margin-top:3px, ROOM margin-left:-11px. Never both. */
body.v2-home .claims{display:block;border-left:2px solid var(--border-strong);padding-left:9px;margin-top:3px}
body.v2-room .claims{display:block;border-left:2px solid var(--border-strong);padding-left:9px;margin-left:-11px}
body.v2-home .claims .c,body.v2-room .claims .c{display:block;padding:1px 0}
body.v2-home .claims .c + .c,body.v2-room .claims .c + .c{border-top:1px solid var(--rule);margin-top:3px;padding-top:4px}
body.v2-home .claims .cv,body.v2-room .claims .cv{font-size:12px;color:var(--fg);display:block}
body.v2-home .claims .cw,body.v2-room .claims .cw{font-size:10.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums;display:block}

/* ---------- attribution — HOME muted 11px, RECORD fg-2 12px ---------- */
body.v2-home .who{color:var(--fg-muted);font-size:11px;white-space:nowrap}
body.v2-record .who{font-size:12px;color:var(--fg-2);white-space:nowrap}
body.v2-home .dash{color:var(--fg-muted)}

/* ---------- conflict marking (identical in HOME and ROOM) ---------- */
body.v2-home tr.conflict > td:first-child,body.v2-room tr.conflict > td:first-child{box-shadow:inset 2px 0 0 var(--alert-rule)}

/* ================================================================
   e. HOME-ONLY
   ================================================================ */
/* ================= A. CORRIDOR STRIP =================
   Ten rooms, fixed geographic order, never re-sorted, never hidden when empty.
   Depth is CONTAINERS and the ten depths sum to 105 exactly — no footer
   reconciliation (delta 4). Per cell: name · depth+denominator · oldest. */
body.v2 .strip{display:grid;grid-template-columns:repeat(10,minmax(0,1fr));
  background:var(--surface);border:1px solid var(--border);border-radius:9px;overflow:hidden;
  box-shadow:var(--shadow)}
body.v2 .room{display:block;padding:9px 11px 10px;border-right:1px solid var(--rule);color:inherit;min-width:0;
  position:relative}
body.v2 .room:last-child{border-right:0}
body.v2 .room:hover{background:var(--recess)}
body.v2 .rn{display:block;font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;
  color:var(--fg-muted);line-height:1.3;height:26px;overflow:hidden}
body.v2 .rd{display:flex;align-items:baseline;gap:5px;margin-top:2px}
body.v2 .rd b{font-size:23px;font-weight:600;line-height:1.05;font-variant-numeric:tabular-nums;color:var(--fg)}
body.v2 .rd i{font-style:normal;font-size:10.5px;color:var(--fg-muted)}
body.v2 .room.empty .rd b{color:var(--fg-muted);font-weight:400}
/* THE STRIP OFF HOME. Reduced to name + depth: the need there is jumping, not
   reading, and the room's own header already carries its state.

   .strip AND .mstrip ARE TWO COMPONENTS, NOT ONE AT TWO WIDTHS. A modifier
   added to one must be added to the other or explicitly declared
   desktop-only -- `.strip.reduced .room` never matches `.mstrip.reduced .room`,
   there is no descendant relation between them. Scoped to .strip alone, the
   reduced form did not reduce at all below 688px: the cell stayed 74px with
   50px of content, so the strip took 376px = 44.5% of an 844px viewport,
   pushing the room's own tile header to y=507 and the first queue card below
   the fold -- at exactly the width the reduction was designed for. Second time
   a rule scoped to one silently failed to reach the other (Iris, rule recorded
   2026-08-04).

   THE NUMERALS SHARE A BASELINE BY CONSTRUCTION, NOT BY CLAMP. The strip's
   only quantitative reading is a ten-way magnitude comparison encoded in
   position and length, so the ten depth numerals must sit on one baseline;
   letting labels wrap freely gave 2 baselines at 900/1100/1280 and 3 at 689.
   The first fix was .rn{height:26px}, which worked but reserved a blank
   second line in every cell -- natural label height measures 13px at both
   140px and 182px, so that line is never used -- and silently TRUNCATED any
   3-line label at 689-900px.
   Pinning .rd to the cell's bottom edge instead is strictly better (Iris,
   measured in page): the grid already stretches every cell to equal height,
   so space-between puts every numeral on the same line for free. 1440: 66px
   -> 53px. 390: 327px -> 262px, on both the room and record pages. One
   baseline at every width, and no label can be cut off. */
body.v2 .strip.reduced .room,body.v2 .mstrip.reduced .room{
  display:flex;flex-direction:column;justify-content:space-between}
body.v2 .strip.reduced .rn,body.v2 .mstrip.reduced .rn{height:auto}
body.v2 .strip.reduced .room{padding:8px 11px 9px}
body.v2 .mstrip.reduced .room{padding:8px 11px 9px;min-height:0}
body.v2 .strip.reduced .rd b,body.v2 .mstrip.reduced .rd b{font-size:18px}
/* No .mtot closes the compact card, so it must close itself. */
body.v2 .mstrip.reduced .mrooms{border-radius:9px}
body.v2 .corridor-nav{margin:0 0 18px}
/* THE STRIP IS CHROME, SO IT RENDERS AS CHROME (Alexis, 2026-08-05).
   Off home it now sits OUTSIDE <main id="main">, in a band between the top bar
   and the page — above the breadcrumb, above the h1, above the record.

   It used to render inside <main>, after the record's identity and header
   metrics: "you can see at the top the detailed information about this
   shipment. And then below, you've got your menus with all the rooms. That
   doesn't make sense." He is right. A navigation control placed beneath the
   content it navigates away from is neither navigation nor content — it is
   read as a footer menu, which is not what it is.

   THIS IS NOT A REVERSAL OF PIET'S RULING, it is the ruling applied to the
   right tree. "The page's own subject always comes first in reading order"
   constrains the PAGE'S CONTENT, and the h1 is still the first thing inside
   <main>. The breadcrumb has always preceded the h1 and nobody has ever read
   that as the page burying its subject, for exactly this reason: chrome is not
   in the ordering. Moving the strip out of <main> is what makes the ruling
   true rather than merely obeyed — and it repairs a second thing that was
   quietly wrong, because "Skip to content" was landing the keyboard user
   ABOVE a ten-item navigation control it is the skip link's whole job to skip.

   HOME IS UNCHANGED and must stay that way: there the full strip carries the
   partition footer, it is the page's own claim, and it is content.

   Not sticky. The top bar is; this band scrolls away with the page. A jump
   control is used on arrival, and ten cells pinned to every viewport is a
   different (worse) page than the one asked for. */
body.v2 .chromenav{padding:12px 20px 0}
body.v2 .chromenav .corridor-nav{margin:0}
/* YOU ARE HERE. The current cell is TEXT, NOT A LINK, plus aria-current --
   the information is carried completely by non-linkness, so under the
   earn-colour rule there is nothing left for a hue to add. Achromatic for a
   second reason too: --accent is interaction-only and a current cell is
   precisely non-interactive.
   DELIBERATELY NOT the 3px bottom rule Iris deleted on 2026-08-04. Exactly one
   current cell makes that idiom technically available again, but reusing it
   teaches back the vocabulary she removed and hands the next person a
   precedent for drawing six. */
/* NO BACKGROUND. --recess is :hover's exact token (measured 1.11:1 light /
   1.10:1 dark against the strip), so under the pointer two cells looked
   current -- a persistent state marker and transient pointer feedback sharing
   one treatment. Non-linkness + cursor + aria-current + .rn going
   --fg-muted -> --fg (4.83:1 -> 15.99:1 light, 5.64:1 -> 13.22:1 dark) carry
   it completely, and NONE of those four is touched by hover, which is the
   actual test. */
body.v2 .room.current{cursor:default}
body.v2 .room.current .rn{color:var(--fg)}
body.v2 .room.current:hover{background:none}
body.v2 .rm{display:block;font-size:10.5px;font-variant-numeric:tabular-nums;color:var(--fg-muted);
  line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
body.v2-home .stripfoot{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums;
  padding:6px 2px 0;display:flex;justify-content:space-between;gap:12px}

/* ================= B. WORKLIST ================= */
/* --- the room band. Same 10px caps token as the strip cell, and it repeats
   the strip's own number, so the two blocks are visibly the same object.
   Sticky under the sticky thead: scrolling the worklist is walking the corridor. */
/* top MUST be >= topbar(46) + thead height(29.5) or a half-pixel sliver of the
   previous band ghosts above the header. Measured, not guessed. */
body.v2-home .worklist tbody th.band{position:sticky;top:76px;z-index:15;background:var(--surface);
  text-align:left;font-weight:400;padding:8px 12px 7px;
  border-top:1px solid var(--border-strong);border-bottom:1px solid var(--border);}
body.v2-home .worklist tbody:first-of-type th.band{border-top:0}
body.v2-home .band .bi{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
body.v2-home .band .bn{font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;color:var(--fg)}
body.v2-home .band .bd{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums;margin-left:9px;
  font-weight:400;letter-spacing:0;text-transform:none}
body.v2-home .band .br{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}

/* --- issue rows --- */
body.v2-home tr.issue > td{padding-top:9px;padding-bottom:9px}
body.v2-home tr.issue:hover > td{background:var(--recess)}
body.v2-home .cause{display:flex;gap:8px;align-items:flex-start;max-width:520px}
body.v2-home .exp{flex:none;appearance:none;background:transparent;border:0;padding:5px 4px;margin:-4px 0 0 -4px;
  cursor:pointer;min-width:24px;min-height:24px;display:flex;align-items:center;justify-content:center}
body.v2-home .exp::before{content:"";width:0;height:0;border-left:5px solid var(--fg-muted);
  border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform .12s}
body.v2-home .exp[aria-expanded="true"]::before{transform:rotate(90deg)}
body.v2-home .ctext{min-width:0}
body.v2-home .ctext q{quotes:"\201C" "\201D";font-style:normal;font-size:12.5px;color:var(--fg);display:block}
body.v2-home .ctext .plain{font-size:12.5px;color:var(--fg-2);display:block}
body.v2-home .ctext .span{font-size:10.5px;color:var(--fg-muted);display:block;margin-top:1px}

/* the reason is a WORD, never a chip */
body.v2-home .rsn{font-size:10.5px;font-weight:600;letter-spacing:.075em;text-transform:uppercase;
  color:var(--fg-2);white-space:nowrap;display:block}
body.v2-home .rsn-sub{font-size:11px;color:var(--fg-muted);white-space:nowrap;display:block;margin-top:1px}
/* a stated condition — remark refreshed 14+ consecutive days — recedes:
   still listed, no longer shouting. Weight and colour, never a new hue. */
body.v2-home tr.stated .rsn{font-weight:500;color:var(--fg-muted)}
body.v2-home tr.conflict .rsn{color:var(--alert)}

body.v2-home .call{font-size:12px;color:var(--fg-2);white-space:nowrap}
body.v2-home .call span{display:block}

/* --- members: the expansion, in place, exportable --- */
body.v2-home tr.mem > td{background:var(--recess);padding-top:5px;padding-bottom:5px;border-bottom:1px solid var(--rule)}
body.v2-home tr.mem:hover > td{background:var(--recess-2)}
/* Item 3 (Piet, 2026-08-05): inline member IDs on n<=3 issue rows — the IDs
   are the payload, linked, on the row itself. .noexp keeps the un-buttoned
   rows aligned with the (n>3) rows that still carry the expand triangle. */
body.v2-home .cause.noexp{padding-left:21px}
body.v2-home .ctext .mids{display:block;margin-top:2px}
body.v2-home .ctext .mids .sid{margin-right:10px;white-space:nowrap}
body.v2-home .mid{display:inline-block;padding-left:26px;font-size:12.5px;font-weight:500;color:var(--accent);
  white-space:nowrap}
body.v2-home .mpos{color:var(--fg-muted);font-size:11.5px;margin-left:10px;white-space:nowrap}
body.v2-home tr.expfoot > td{background:var(--recess);padding-top:7px;padding-bottom:9px;
  border-bottom:1px solid var(--border)}
body.v2-home .expacts{padding-left:26px;display:flex;gap:18px;align-items:center;flex-wrap:wrap}
body.v2-home .expacts a,body.v2-home .expacts button{appearance:none;background:transparent;border:0;padding:0;font:inherit;
  font-size:11.5px;font-weight:500;color:var(--accent);cursor:pointer}
body.v2-home .expacts .note{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums}

/* ================= C/D. disclosure blocks ================= */
body.v2-home details.blk{background:var(--surface);border:1px solid var(--border);border-radius:9px;
  box-shadow:var(--shadow);overflow:hidden;margin-top:22px}
body.v2-home details.blk > summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;
  padding:10px 14px;font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-2)}
body.v2-home details.blk > summary::-webkit-details-marker{display:none}
body.v2-home details.blk > summary::before{content:"";width:0;height:0;border-left:5px solid var(--fg-muted);
  border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform .12s}
body.v2-home details.blk[open] > summary::before{transform:rotate(90deg)}
body.v2-home details.blk > summary .cnt{font-weight:400;letter-spacing:0;text-transform:none;font-size:11.5px;
  color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2-home details.blk .tbl{border:0;border-radius:0;border-spacing:0;border-top:1px solid var(--border);box-shadow:none}
body.v2-home details.blk .tbl thead th{position:static}
body.v2 .blk-empty{margin:0;padding:0 14px 12px;color:var(--fg-muted)}
body.v2-home .subroom{font-size:10.5px;color:var(--fg-muted);display:block}

/* ================= HOME mobile pieces (hidden on desktop) ================= */
body.v2-home .mcards{display:none}
body.v2 .mstrip{display:none}

/* ================================================================
   f. ROOM-ONLY
   ================================================================ */
/* ---------- header metric strip — ROOM 6 columns, RECORD 5 ---------- */
body.v2-room .hstrip{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));
  background:var(--surface);border:1px solid var(--border);border-radius:9px;overflow:hidden;
  box-shadow:var(--shadow)}
body.v2-room .hm,body.v2-record .hm{padding:9px 13px 10px;border-right:1px solid var(--rule);min-width:0}
body.v2-room .hm:last-child,body.v2-record .hm:last-child{border-right:0}
/* .hm .l — ROOM height:26px (two-line labels), RECORD height:14px */
body.v2-room .hm .l{display:block;font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;
  color:var(--fg-muted);line-height:1.3;height:26px}
body.v2-room .hm .v{display:flex;align-items:baseline;gap:6px;margin-top:2px}
body.v2-room .hm .v b{font-size:23px;font-weight:600;line-height:1.05;font-variant-numeric:tabular-nums}
body.v2-room .hm .v i{font-style:normal;font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
/* Iris F7: WRAPS, never ellipsises. This was nowrap+ellipsis, relaxed only
   BELOW 688px, so "17 Jul · reported by CEVA Nakawa" survived on a phone and
   on a 1440px desktop and was cut on everything between — including 1024px,
   iPad landscape and a very common laptop width. On the page whose rule is
   that nothing appears without a source, the one position statement lost its
   source across the entire tablet-and-small-laptop band, with no title
   attribute to recover it from. The tile is min-height driven and has room.
   SHARED WITH THE ROOM PAGE, deliberately: the same sub-line carries the same
   kind of attribution there, and truncating a source is no more acceptable on
   /at/<room> than it is here. */
body.v2-room .hm .s,
body.v2-record .hm .s{display:block;font-size:10.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums;
  line-height:1.4;white-space:normal;overflow:visible;text-overflow:clip}
body.v2-room .hm a{color:var(--accent)}

/* ---------- the fleet list (home): room bands over the queue table ----------
   The band heading is the ONLY thing separating one room's table from the
   next, so it has to read as a boundary without becoming a second navigation
   layer. Type, not chrome: no box, no fill, no rule above — the table's own
   header row already draws one, and doubling it made the page look like ten
   cards rather than one continuous list. */
body.v2 .fband{display:flex;align-items:baseline;gap:9px;margin:26px 0 7px}
body.v2 .fband h3{font-size:14px;font-weight:600;letter-spacing:.02em;color:var(--fg);margin:0}
body.v2 .fband h3 a{color:inherit;text-decoration:none}
body.v2 .fband h3 a:hover{text-decoration:underline}
body.v2 .fband .cnt{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
/* An empty room keeps its heading and says so in words. It must not look like
   a rendering failure, and it must not look like a room that does not exist. */
body.v2 .fempty{font-size:12px;margin:0 0 4px;padding:7px 12px}
/* Identifier column. The superseded reference stays visible underneath rather
   than being swapped out silently — see _unit_cell(). */
body.v2 .unit{font-size:12.5px;font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2 .unit-prior{display:block;font-size:10.5px;color:var(--fg-muted);
  font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2 .plate{font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2 .unitc,body.v2 .docsc{white-space:nowrap}
body.v2 .dash{color:var(--fg-muted)}
/* Document count. currentColor outline glyph, never the emoji — see
   _V2_DOC_GLYPH. It is a count of files, never a completeness claim. */
body.v2 .docs{display:inline-flex;align-items:center;gap:3px;font-size:11.5px;
  color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2 .docs .dg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}

/* ---------- room tables ---------- */
body.v2 .sid-sub{font-size:10.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2 .rung{font-size:12px;color:var(--fg);white-space:nowrap}
body.v2 .rung b{font-weight:600;font-variant-numeric:tabular-nums;margin-right:5px;color:var(--fg-2)}
body.v2 .ev{font-size:12px;color:var(--fg-2);max-width:400px}
body.v2 .ev q{quotes:"\201C" "\201D";color:var(--fg);font-style:normal;display:block}
/* Containers sharing an observed cause are BANDED, never merged. Two channels,
   both free: a stronger rule opens each cause run, and the verbatim string —
   which is repeated on every row because it is the data, not a label — recedes
   after its first appearance, so the eye stops once per cause. Time-in-room
   remains the sort; grouping annotates the order, it never reorders it. A
   non-contiguous run simply opens a second band, which is honest. */
body.v2 tr.causefirst > td{border-top:1px solid var(--border-strong)}
body.v2 .ev q.echo{color:var(--fg-muted)}
/* Alert budget on a room page is 2. A contested run therefore carries ONE
   continuous left rule down the whole run and ONE word on its first row —
   two marks, not two per container. */
body.v2 tr.conflict.cecho > td{border-top-color:var(--rule)}
body.v2 .bandn{display:block;font-size:10.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums;margin-top:1px}
/* F4: a FLOOR, not a fixed height — taller rows still grow. Rows whose
   report has no "who / date" sub-line collapsed to 31px against 47px
   neighbours, a 32% step inside the At supplier band. Iris scoped this to
   home; applied to the queue table on BOTH surfaces because it is one table
   and the same cliff exists on /at/supplier. */
body.v2 .tbl.queue tbody tr{height:47px}
body.v2 .tbl.queue tbody td{padding:6px 12px}
body.v2 .tbl.queue .sid{font-size:12.5px;font-weight:500}
body.v2 .det{font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap}
/* NB: functionally redundant with the shared `tr.conflict > td:first-child`
   above — ROOM authored it a second time without the child combinator. */
body.v2 tr.conflict td:first-child{box-shadow:inset 2px 0 0 var(--alert-rule)}
body.v2 tr.conflict .flag{color:var(--alert);font-size:10.5px;font-weight:600;letter-spacing:.075em;
  text-transform:uppercase;display:block;margin-top:2px}

/* The 9-rung ladder slot CSS (.ladder/.rungcell) was deleted 2026-08-06 with
   _slot_ladder (Piet item 5): the bands ARE the ladder now. */
body.v2-room .slotfoot{font-size:11px;color:var(--fg-muted);padding:6px 2px 0;font-variant-numeric:tabular-nums}

/* ---------- ROOM mobile ---------- */
/* THE BASE-STATE HIDE. It MUST stay in step with the `display:block` inside
   @media (max-width:688px) below — the two rules are one mechanism, and
   scoping them differently is not a style difference, it is a broken page.
   Scoped `body.v2-room` while the media-query rule said `body.v2`, home had no
   base rule at all: a bare <div> defaults to display:block, so above 688px
   every band rendered its desktop table AND the same containers again as
   unstyled inline cards — the entire 107-container fleet duplicated on the
   primary page (Rune C-2). Change one, change the other. */
body.v2 .mq{display:none}

/* ================================================================
   g. RECORD-ONLY
   ================================================================ */
body.v2-record .idline{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:0 0 10px}
body.v2-record .idmeta{font-size:12px;color:var(--fg-muted)}
body.v2-record .idmeta b{font-weight:500;color:var(--fg-2)}

/* ---------- header metric strip (RECORD variant) ---------- */
body.v2-record .hstrip{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  background:var(--surface);border:1px solid var(--border);border-radius:9px;overflow:hidden;
  box-shadow:var(--shadow)}
body.v2-record .hm .l{display:block;font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;
  color:var(--fg-muted);line-height:1.3;height:14px}
body.v2-record .hm .v{display:flex;align-items:baseline;gap:6px;margin-top:4px}
body.v2-record .hm .v b.txt{font-size:16px;letter-spacing:-.01em}
body.v2-record .hm .v b{font-size:22px;font-weight:600;line-height:1.05;font-variant-numeric:tabular-nums}
body.v2-record .hm .v s{text-decoration:none;font-size:12px;color:var(--fg-muted);font-variant-numeric:tabular-nums}

/* ---------- two-column body ---------- */
body.v2-record .body{display:grid;grid-template-columns:minmax(0,1fr) 292px;gap:16px;margin-top:20px;align-items:start}

body.v2-record .sechead .lh{display:flex;align-items:baseline;gap:10px;min-width:0}
body.v2-record .sechead + .sechead{margin-top:20px}
body.v2-record .sec + .sec{margin-top:20px}

body.v2-record .btn{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;
  padding:6px 11px;border-radius:7px;border:1px solid var(--border-strong);
  background:var(--surface);color:var(--accent);cursor:pointer;white-space:nowrap;min-height:30px}
body.v2-record .btn:hover{background:var(--recess)}

/* ---------- ledger cells ---------- */
body.v2-record .what{font-size:12.5px;color:var(--fg)}
body.v2-record .dt{font-size:12.5px;font-variant-numeric:tabular-nums;white-space:nowrap}
body.v2-record .lag{font-size:12px;font-variant-numeric:tabular-nums;color:var(--fg-muted);white-space:nowrap}
body.v2-record .lag.wide{color:var(--fg-2);font-weight:500}

/* contested group: both values, both timestamps, no winner */
body.v2-record tr.cxhead td{padding-top:9px;padding-bottom:2px;border-bottom:0}
body.v2-record .cxlbl{font-size:10.5px;font-weight:600;letter-spacing:.075em;text-transform:uppercase;color:var(--alert)}
body.v2-record .cxlbl s{text-decoration:none;color:var(--fg-muted);font-weight:400;letter-spacing:0;text-transform:none;
  margin-left:8px;font-size:11px}
body.v2-record tr.cx td{border-bottom:0;background:var(--recess)}
body.v2-record tr.cx td:first-child{box-shadow:inset 2px 0 0 var(--border-strong)}
body.v2-record tr.cx.cxend td{border-bottom:1px solid var(--rule);padding-bottom:9px}

/* derived values carry their derivation permanently, and never alert */
body.v2-record .derived{border-bottom:1px dotted var(--fg-muted)}
body.v2-record .dtag{display:inline-block;font-size:10px;color:var(--fg-muted);letter-spacing:.03em;margin-left:7px;
  white-space:nowrap}
body.v2-record .tomb{font-style:italic;color:var(--fg-muted)}

/* ---------- rail ---------- */
body.v2-record .reccard{background:var(--surface);border:1px solid var(--border);border-radius:9px;
  box-shadow:var(--shadow);overflow:hidden}
body.v2-record .reccard h3{font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;color:var(--fg-muted);
  margin:0;padding:9px 13px 7px;border-bottom:1px solid var(--rule)}
body.v2-record .reccard + .reccard{margin-top:14px}
body.v2-record .kv{display:flex;justify-content:space-between;gap:10px;padding:6px 13px;font-size:12.5px;
  border-bottom:1px solid var(--rule)}
body.v2-record .kv:last-child{border-bottom:0}
body.v2-record .kv .k{color:var(--fg-muted)}
body.v2-record .kv .v{font-variant-numeric:tabular-nums;text-align:right}
body.v2-record .docrow{display:flex;align-items:baseline;gap:8px;padding:6px 13px;font-size:12.5px;border-bottom:1px solid var(--rule)}
body.v2-record .docrow:last-child{border-bottom:0}
body.v2-record .docrow .m{width:14px;flex:none;text-align:center;font-size:12px;font-variant-numeric:tabular-nums}
body.v2-record .docrow.present .m{color:var(--fg)}
body.v2-record .docrow.present .n{color:var(--fg)}
body.v2-record .docrow.verify .m,body.v2-record .docrow.verify .n{color:var(--fg-2)}
body.v2-record .docrow.missing .m,body.v2-record .docrow.missing .n{color:var(--fg-muted)}
body.v2-record .docrow .st{margin-left:auto;font-size:11px;color:var(--fg-muted);white-space:nowrap}
body.v2-record .chips{display:flex;flex-wrap:wrap;gap:6px;padding:9px 13px}
body.v2-record .chip{font-size:12px;font-weight:500;padding:3px 9px;border-radius:6px;background:var(--recess);
  border:1px solid var(--border);color:var(--accent);white-space:nowrap}
body.v2-record .acts{display:flex;flex-direction:column;gap:7px;padding:10px 13px}
body.v2-record .acts .btn{justify-content:center;width:100%}
body.v2-record details.ctr > summary{list-style:none;cursor:pointer;padding:7px 13px;font-size:12px;color:var(--accent);
  border-top:1px solid var(--rule)}
body.v2-record details.ctr > summary::-webkit-details-marker{display:none}
body.v2-record .ctrgrid{display:grid;grid-template-columns:1fr 1fr;gap:0 10px;padding:2px 13px 10px;
  font-size:11.5px;font-variant-numeric:tabular-nums;color:var(--fg-2)}

/* Related containers — the sibling list. The current container is marked by
   weight plus a word, never by colour alone (WCAG 1.4.1). */
body.v2-record .tbl.rel tbody td{padding:6px 12px}
body.v2-record .tbl.rel .sid{font-size:12.5px;font-weight:500}
body.v2-record .tbl.rel .sid.here{color:var(--fg);font-weight:600}
body.v2-record .tbl.rel .heretag{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--fg-muted);margin-left:8px}
body.v2-record .tbl.rel tr.thisone > td{background:var(--recess)}
body.v2-record .tbl.rel tr.thisone:hover > td{background:var(--recess)}

/* ---------- RECORD: the six dimensions ----------
   Each dimension is a <details>. The summary line is ALWAYS on screen and
   always carries its own count — including at zero, which on an evidence page
   is a finding rather than an emptiness. Bodies open on demand with no
   JavaScript: this page is read on a bad North Kivu connection, and
   disclosure that depends on a script is disclosure that can fail to arrive.

   A collapsed dimension must read as INFORMATIVE, not as a closed drawer.
   That is why the count sits on the summary line in full sentence form and
   why the whole head is a surface with a hover state — the reader learns
   something without opening anything. */
body.v2-record .dim{background:var(--surface);border:1px solid var(--border);border-radius:9px;
  box-shadow:var(--shadow);overflow:hidden}
body.v2-record .dim + .dim{margin-top:10px}
body.v2-record .dimhead{list-style:none;cursor:pointer;display:flex;align-items:baseline;
  justify-content:space-between;gap:10px;padding:10px 13px}
body.v2-record .dimhead::-webkit-details-marker{display:none}
body.v2-record .dimhead:hover{background:var(--recess)}
body.v2-record .dimhead .lh{display:flex;align-items:baseline;gap:10px;min-width:0;flex-wrap:wrap}
/* Iris F10b: the hierarchy was inverted. The dimension NAME is identical on
   all 270 records; the COUNT is the entire reason the summary line exists, and
   it was the quieter of the two. Label recedes, count and its numerals come
   forward. Summary quality includes summary prominence. */
body.v2-record .dimhead h2{font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--fg-muted);margin:0}
body.v2-record .dimhead .cnt{font-size:12.5px;color:var(--fg-2);font-variant-numeric:tabular-nums}
/* Iris also asked for the NUMERALS in bold inside the count. Not done, and
   deliberately: `count_line` is plain text escaped inside _rec_dim, which is
   Cyrus M-3's fix for a parameter that used to take raw HTML by implicit
   contract. Emitting <b> would hand that trap straight back. The size and
   colour inversion above already puts the count ahead of the label, which is
   the hierarchy point; the extra weight is not worth reopening an XSS shape. */
/* Disclosure caret, drawn in CSS so it needs no icon asset and no script. */
body.v2-record .dimmark{flex:none;width:8px;height:8px;border-right:1.6px solid var(--fg-muted);
  border-bottom:1.6px solid var(--fg-muted);transform:rotate(45deg);margin:0 3px 3px 0;
  transition:transform .12s ease}
body.v2-record .dim[open] > .dimhead .dimmark{transform:rotate(-135deg);margin-bottom:0}
body.v2-record .dimbody{border-top:1px solid var(--rule)}
body.v2-record .dimbody > .tbl{border:0;border-radius:0;box-shadow:none;width:100%}
body.v2-record .dimempty{margin:0;padding:11px 13px;font-size:12.5px;color:var(--fg-muted)}
body.v2-record .dimnote{margin:0;padding:9px 13px;font-size:11.5px;color:var(--fg-muted);
  border-top:1px solid var(--rule)}
/* An agent comment riding along with a claim. Verbatim, never truncated. */
body.v2-record .cnote{display:block;font-size:11.5px;color:var(--fg-muted);margin-top:2px}
/* Told-us EARLIER than the event it asserts. Marked, never clamped, and
   deliberately not spending the alert budget — a self-inconsistent date is a
   different animal from two parties disagreeing. */
body.v2-record .lag.lagneg{color:var(--fg-2);font-weight:500;text-decoration:underline dotted}
body.v2-record .docsub{padding:8px 13px 4px;font-size:10px;font-weight:600;letter-spacing:.055em;
  text-transform:uppercase;color:var(--fg-muted);border-top:1px solid var(--rule)}

/* ---------- RECORD: cargo ---------- */
body.v2-record .tbl.cargo .mono{font-variant-numeric:tabular-nums;font-size:12px;color:var(--fg-2);white-space:nowrap}
body.v2-record tr.pogrp td{background:var(--recess);padding-top:8px;padding-bottom:8px}
body.v2-record .pochip{font-size:12px;font-weight:600;color:var(--accent)}
/* The true vendor sits on the PO row, not on every line — it is 1:1 per PO,
   so repeating it down each line would be noise asserting nothing extra. */
body.v2-record .povend{font-size:11.5px;color:var(--fg-2);margin-left:9px}
body.v2-record .pogn{font-size:11px;color:var(--fg-muted);margin-left:9px}

/* ---------- RECORD: photos ----------
   SINGLE-COLUMN STACK, CONTAINED NOT CROPPED, and this is the whole point.
   A seal number, a plate or a damaged pallet is usually the reason the
   photograph was taken, and object-fit:cover can crop exactly that out of
   frame. The two-column cropped thumbnail grid is forbidden here.

   Scoped to body.v2-record on purpose: the Kampala modal still uses
   .photo-item / .photo-modal-body with object-fit:cover, and reusing those
   class names would have silently re-laid-out that surface too. */
body.v2-record .phcat{display:flex;align-items:baseline;gap:9px;padding:9px 13px 5px;
  border-top:1px solid var(--rule)}
body.v2-record .dimbody > .phcat:first-child{border-top:0}
body.v2-record .phcat h3{margin:0;font-size:10px;font-weight:600;letter-spacing:.055em;
  text-transform:uppercase;color:var(--fg-muted)}
body.v2-record .phcat .cnt{font-size:11px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2-record .phitem{display:block;margin:0 13px 9px;border:1px solid var(--border);
  border-radius:8px;overflow:hidden;background:var(--recess);text-decoration:none;color:var(--fg)}
/* Iris F6: RESERVE THE BOX. Unloaded, each card was 50px; loaded, up to 70vh
   (~591px on a 390x844 phone), and loading="lazy" fires them progressively as
   the reader scrolls — so content under the thumb reflowed repeatedly and a
   tap mid-load opened a DIFFERENT photo than the one aimed at. On an evidence
   page that means looking at the wrong seal. aspect-ratio + min-height hold
   the space; object-fit:contain still guarantees no crop, so the letterbox
   becomes deliberate rather than accidental. */
body.v2-record .phitem img{display:block;width:100%;height:auto;
  aspect-ratio:4/3;min-height:180px;max-height:60vh;
  object-fit:contain;background:var(--recess-2)}
body.v2-record .phsub{display:block;padding:6px 10px;font-size:11.5px;color:var(--fg-muted);
  font-variant-numeric:tabular-nums}
body.v2-record .phnoimg{padding:14px 10px}

/* ---------- RECORD: rail forms ---------- */
body.v2-record .racts{display:flex;flex-direction:column;gap:8px;padding:10px 13px}
body.v2-record .racts textarea{font:inherit;font-size:12.5px;color:var(--fg);background:var(--recess);
  border:1px solid var(--border);border-radius:7px;padding:7px 9px;resize:vertical;width:100%}
body.v2-record .racts input[type=file]{font:inherit;font-size:11.5px;color:var(--fg-2);max-width:100%}
body.v2-record .racts .btn{justify-content:center;width:100%}
body.v2-record .cbx{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-2)}
body.v2-record .fhint{display:block;padding:0 13px 10px;font-size:11px;color:var(--fg-muted)}
body.v2-record .racts .fhint{padding:0}
/* Iris F5: the inactive strip carries Status + Last reported at, so it needs
   its own widths. -1 is the degenerate case (no dated claim at all). */
body.v2-record .hstrip-1{grid-template-columns:1fr}
body.v2-record .hstrip-2{grid-template-columns:repeat(2,minmax(0,1fr))}
body.v2-record .hstrip-none{grid-template-columns:1fr}
/* NOTE: no .norail rule. The rail always carries Actions, Add-a-comment and
   Upload, so the 292px column is never empty and a collapse rule would be
   dead CSS. If the rail ever becomes conditional, add it back here. */

/* ---------- RECORD mobile ---------- */
body.v2-record .mled{display:none}

/* ================================================================
   h. RESPONSIVE — one merged 430px breakpoint.
   Converted from @container (max-width: 430px) in all three mockups, widened to 688px;
   declarations inside are byte-identical to the authored container-query rules.
   ================================================================ */
/* 688px, NOT 430px. Below ~688px the six-column worklist and queue tables
   exceed the viewport: their Reason / value / who-to-call / numeric columns
   are all white-space:nowrap, so the table has a hard minimum width and the
   PAGE scrolls sideways between 431px and 688px (Rune M-7).

   The obvious fix -- wrapping the table in overflow-x:auto -- is the one
   thing that must not be done here, and Iris left the reason forty lines
   above: an overflow container kills position:sticky, and setting overflow-x
   forces overflow-y with it, so the sticky corridor band and thead both die.

   So instead the breakpoint moves: below 688px the page uses the CARD layout
   Iris already authored for exactly this situation. No new design, no
   horizontal scroll, sticky intact. FLAGGED FOR IRIS at the conformance pass
   -- she chose 430px and this widens her intent to cover the gap. */
@media (max-width:688px){
  /* ---- shell, all three pages ---- */
  /* Brand rules moved to the 899.98px block (Iris brand-identity §3): the
     mobile boundary is 900px, not 688px, and the accessible name now rides
     the anchor's aria-label so display:none on the words loses nothing. */
  body.v2 .leaves{flex:1 1 auto;min-width:0;justify-content:flex-end;gap:0}
  body.v2 .leaf{padding:6px 8px;font-size:12px}
  /* Search is tier-3: on a phone it is a 44px icon that OPENS a field.
     The input is collapsed to zero width but is NOT display:none — it has to
     stay in the focus order for the <label> to open it, and a form with no
     focusable control and no submit button cannot be submitted at all. That
     was the bug: the icon looked like a control and did nothing. */
  body.v2 .search{width:44px;min-width:44px;height:44px;flex:none;justify-content:center;
    background:transparent;border:0;padding:0;gap:0;transition:width .12s ease-out}
  body.v2 .search input{width:0;min-width:0;flex:none;padding:0;opacity:0}
  body.v2 .sicon{width:44px;height:44px}
  body.v2 .search svg{width:17px;height:17px}
  /* Focused: the field takes the bar. Enter submits (iOS shows a Search key on
     type=search), so no submit button is needed. */
  body.v2 .search:focus-within{width:auto;flex:1 1 auto;min-width:0;justify-content:flex-start;
    background:var(--recess);border:1px solid var(--border);border-radius:7px;
    height:34px;padding:0 8px;gap:6px}
  body.v2 .search:focus-within input{width:100%;flex:1 1 auto;opacity:1;font-size:16px}
  body.v2 .search:focus-within .sicon{width:17px;height:17px}
  /* Give the field the whole bar while it is open. :has() is the only way to
     reach a PRECEDING sibling; without it the field is merely narrower, so
     this degrades to usable rather than broken. */
  body.v2 .top:has(.search:focus-within) .leaves,
  body.v2 .top:has(.search:focus-within) .topright{display:none}
  body.v2 .top:has(.search:focus-within) .brand{display:none}
  body.v2 .topright{margin-left:0;gap:10px}
  /* .asat stays hidden on a phone (Iris) — it is a stamp, not a control.
     .adminleaf and .signout are CONTROLS and now stay reachable; the admin
     leaf only renders for admins at all, so it costs nothing for most users.
     .signout collapses to its icon. */
  body.v2 .asat{display:none}
  body.v2 .adminleaf{font-size:12px}
  body.v2 .signout .solabel{position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap}
  /* 44px box, like .sicon above and .cmt-icon elsewhere. This is a GET that
     ends the session — a 17px glyph 10px from the admin Sync leaf is a mistap
     into being signed out. The icon stays 17px; the TARGET is 44px. */
  body.v2 .signout{width:44px;min-width:44px;height:44px;justify-content:center;gap:0}
  body.v2 .signout svg{width:17px;height:17px}
  /* Iris F10a: a 24px control wedged between two 44px ones fails the plain
     target minimum, and it sits next to a GET that ends the session. The
     TARGET grows to 44px; the glyph stays 14px. */
  body.v2 .themeswitch{width:44px;min-width:44px;height:44px}

  /* ---- shell, per-page variants ---- */
  /* .top — HOME adds gap:6px */
  body.v2-home .top{padding:0 8px 0 12px;gap:6px;height:46px}
  body.v2-room .top,body.v2-record .top{padding:0 8px 0 12px;height:46px}
  /* .page — HOME 12px top, ROOM/RECORD 10px top */
  body.v2-home .page{padding:12px 12px 60px}
  body.v2-room .page,body.v2-record .page{padding:10px 12px 60px}
  /* the chrome band tracks .page's gutter, 20px -> 12px */
  body.v2 .chromenav{padding:10px 12px 0}
  /* h1 — ROOM 17px, RECORD 18px */
  body.v2-room h1{font-size:17px}
  body.v2-record h1{font-size:18px}

  /* ---- HOME @430 ---- */
  /* strip: the four rooms holding a contested or dark container, + fleet total */
  body.v2 .strip{display:none}
  body.v2-home .stripfoot{display:none}
  body.v2 .mstrip{display:block}
  body.v2 .mstrip .mrooms{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule);
    border:1px solid var(--border);border-radius:9px 9px 0 0;overflow:hidden}
  body.v2 .mstrip .room{border-right:0;background:var(--surface);padding:9px 11px 10px;min-height:74px}
  body.v2-home .mstrip .mtot{display:flex;justify-content:space-between;align-items:baseline;
    padding:9px 12px;background:var(--surface);border:1px solid var(--border);border-top:0;
    font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
  body.v2-home .mstrip .mtot b{font-size:15px;font-weight:600;color:var(--fg)}

  /* worklist: corridor order preserved. First five issues as cards, then the
     remaining fourteen as one-line rows — nothing is truncated away, because
     sequential order puts the DRC road last and it must still be reachable. */
  body.v2-home .tbl.worklist{display:none}
  body.v2-home .mcards{display:block}
  body.v2-home .mband{font-size:10px;font-weight:600;letter-spacing:.055em;text-transform:uppercase;
    color:var(--fg-muted);margin:14px 0 6px;display:flex;justify-content:space-between;
    align-items:baseline;gap:10px}
  body.v2-home .mband .bd{font-weight:400;letter-spacing:0;text-transform:none;font-size:11px;
    font-variant-numeric:tabular-nums}
  body.v2-home .mcard{display:block;background:var(--surface);border:1px solid var(--border);border-radius:9px;
    padding:10px 12px 11px;margin-bottom:8px;color:inherit;min-height:44px;box-shadow:var(--shadow)}
  body.v2-home .mcard .r1{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
  body.v2-home .mcard .mrsn{font-size:11px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--fg-2)}
  body.v2-home .mcard.stated .mrsn{font-weight:500;color:var(--fg-muted)}
  body.v2-home .mcard.conflict{border-left:2px solid var(--alert-rule)}
  body.v2-home .mcard.conflict .mrsn{color:var(--alert)}
  body.v2-home .mcard .mmsr{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
  body.v2-home .mcard .mmsr s{text-decoration:none;font-weight:400;color:var(--fg-muted)}
  body.v2-home .mcard .mcause{font-size:13px;color:var(--fg);margin-top:4px}
  body.v2-home .mcard .mcause q{quotes:"\201C" "\201D";font-style:normal}
  body.v2-home .mcard .r4{font-size:11.5px;color:var(--fg-muted);margin-top:6px;display:flex;
    justify-content:space-between;gap:10px;font-variant-numeric:tabular-nums}
  /* the verbatim remark is the data — it wraps, it is never truncated */
  body.v2-home .mrow{display:flex;align-items:center;gap:10px;background:var(--surface);
    border:1px solid var(--border);border-radius:7px;padding:9px 11px;min-height:44px;
    margin-bottom:6px;color:inherit}
  body.v2-home .mrow .mc{flex:1;min-width:0;font-size:12.5px;color:var(--fg)}
  body.v2-home .mrow .mc q{quotes:"\201C" "\201D";font-style:normal}
  body.v2-home .mrow .mn{font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}
  body.v2-home .mrow.conflict{border-left:2px solid var(--alert-rule)}
  /* Item 3 (2026-08-05): member IDs on the mobile worklist. The band label is
     the room link; cards are containers; the IDs are the links. */
  /* Iris F2 (2026-08-05): the room jump moved up here from the cards, and
     hover does not exist on the phones that carry ~46% of usage — the link
     must announce itself AT REST. Underline, not --accent: the band label is
     uppercase 10px structure text and an accent-coloured token there would
     read as a status, which the two-hue budget exists to prevent. */
  body.v2-home .mband a{color:inherit;text-decoration:underline;
    text-underline-offset:2px}
  body.v2-home .mcard .mids,body.v2-home .mrow .mids{margin-top:4px}
  body.v2-home .mids .sid{margin-right:10px;white-space:nowrap;font-size:12.5px;font-weight:500}
  body.v2-home details.mexp{margin-top:4px}
  body.v2-home details.mexp summary{list-style:none;cursor:pointer;font-size:12px;
    color:var(--accent);padding:6px 0;min-height:34px;display:flex;align-items:center}
  body.v2-home details.mexp summary::-webkit-details-marker{display:none}
  body.v2-home details.mexp summary::before{content:"";width:0;height:0;
    border-left:5px solid var(--fg-muted);border-top:4px solid transparent;
    border-bottom:4px solid transparent;margin-right:7px;transition:transform .12s}
  body.v2-home details.mexp[open] summary::before{transform:rotate(90deg)}

  body.v2-home details.blk > summary{min-height:44px;padding:12px 14px}
  body.v2-home details.blk .tbl thead{display:none}
  body.v2-home details.blk .tbl tbody td{display:block;padding:2px 12px;border:0}
  body.v2-home details.blk .tbl tbody td:first-child{padding-top:9px}
  body.v2-home details.blk .tbl tbody td:last-child{padding-bottom:9px;border-bottom:1px solid var(--rule)}
  body.v2-home details.blk .tbl tbody td.num{text-align:left}
  /* WCAG 2.5.8 / house rule: 44px touch. A 15px inline link inside a stacked
     card row is the classic miss — the row reads fine and cannot be tapped. */
  body.v2-home details.blk .tbl tbody td a.sid{display:flex;align-items:center;min-height:44px}

  /* ---- metric strip @430 — ROOM keeps the radius, RECORD drops it;
          ROOM 74px cells, RECORD 64px ---- */
  body.v2-room .hstrip{grid-template-columns:1fr 1fr;gap:1px;background:var(--rule);border-radius:9px}
  body.v2-record .hstrip{grid-template-columns:1fr 1fr;gap:1px;background:var(--rule)}
  body.v2-room .hm{border-right:0;background:var(--surface);min-height:74px}
  body.v2-record .hm{border-right:0;background:var(--surface);min-height:64px}
  body.v2-room .hm .s,body.v2-record .hm .s{white-space:normal;line-height:1.4}
  body.v2-room .hm .l,body.v2-record .hm .l{height:auto}

  /* ---- ROOM @430 ---- */
  /* (.ladder/.rungcell mobile override deleted 2026-08-06 with the slot.) */
  body.v2 .tbl.queue{display:none}
  body.v2 .mq{display:block}
  body.v2 .qcard{display:block;background:var(--surface);border:1px solid var(--border);border-radius:9px;
    padding:10px 12px 11px;margin-bottom:8px;color:inherit;box-shadow:var(--shadow);min-height:44px}
  body.v2 .qcard.conflict{border-left:2px solid var(--alert-rule)}
  body.v2 .qcard .r1{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
  body.v2 .qcard .r2{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-top:3px;
    font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
  /* The identifier subline. Container/AWB and plate at 46% of usage — the one
     field that ties the screen to the box in front of you (Felix). */
  body.v2 .qcard .r2b{margin-top:3px;font-size:11.5px;color:var(--fg-muted);
    font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
  body.v2 .qcard .r2b .unit-prior{display:inline;margin-left:4px}
  body.v2 .qcard .r3{font-size:12px;color:var(--fg-2);margin-top:5px}
  body.v2 .qcard .r4{display:flex;justify-content:space-between;gap:10px;margin-top:5px;
    font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
  /* The report measure is shared markup with the desktop table now, so its
     .msr carries 13px/600/--fg. A card's r4 is a muted footer line and must
     stay one -- neutralise the size and weight here, but let .nowrite through,
     because "only the failure is coloured" has to hold at 390px too. */
  body.v2 .qcard .r4 .msr{font-size:inherit;font-weight:inherit;color:inherit}
  body.v2 .qcard .r4 .msr.nowrite{color:var(--alert)}
  /* At-sea vessel bands, mobile (Piet item 1). The band header ALWAYS renders
     in full — it is four values — and the member cards sit behind a labelled,
     counted disclosure (the standing members-collapse pattern). */
  body.v2 .mvband{margin:14px 0 4px}
  body.v2 .mvband .vn{display:block;font-size:11px;font-weight:600;
    letter-spacing:.055em;text-transform:uppercase;color:var(--fg-2)}
  body.v2 .mvband .vd{display:block;font-size:11.5px;color:var(--fg-muted);
    font-variant-numeric:tabular-nums;margin-top:1px}
  /* Zero-count rung band, mobile (Iris MINOR-6 ruling, 2026-08-06): demotes
     exactly as the desktop .vband.z — tighter margin, header at body-adjacent
     weight. vn is already --fg-2 in this block; both themes hold unchanged. */
  body.v2 .mvband.z{margin:8px 0 0}
  body.v2 .mvband.z .vn{font-weight:500}
  body.v2 details.mvmem summary{list-style:none;cursor:pointer;font-size:12px;
    color:var(--accent);padding:8px 0;min-height:44px;display:flex;align-items:center}
  body.v2 details.mvmem summary::-webkit-details-marker{display:none}
  body.v2 details.mvmem summary::before{content:"";width:0;height:0;
    border-left:5px solid var(--fg-muted);border-top:4px solid transparent;
    border-bottom:4px solid transparent;margin-right:7px;transition:transform .12s}
  body.v2 details.mvmem[open] summary::before{transform:rotate(90deg)}

  /* ---- RECORD @430 ---- */
  body.v2-record .hm:last-child{grid-column:1 / -1}
  body.v2-record .idline{gap:2px}
  body.v2-record .sechead{flex-wrap:wrap}
  body.v2-record .sechead .btn{width:100%;justify-content:center;min-height:44px;margin-top:6px;order:3}
  body.v2-record .body{grid-template-columns:minmax(0,1fr);gap:0;margin-top:16px}
  /* remarks stack: verbatim text is the row; attribution drops beneath it */
  body.v2-record .tbl.remarks thead{display:none}
  body.v2-record .tbl.remarks tbody td{display:block;padding:2px 12px;border:0}
  body.v2-record .tbl.remarks tbody td.what{padding-top:10px;font-size:12.5px}
  body.v2-record .tbl.remarks tbody td.who,body.v2-record .tbl.remarks tbody td.dt{display:inline-block;font-size:11.5px;color:var(--fg-muted)}
  body.v2-record .tbl.remarks tbody td.dt{text-align:left;padding-left:0}
  body.v2-record .tbl.remarks tbody td.dt::before{content:"· "}
  body.v2-record .tbl.remarks tbody tr{display:block;border-bottom:1px solid var(--rule);padding-bottom:9px}
  body.v2-record .tbl.remarks tbody tr:last-child{border-bottom:0}
  body.v2-record .recrail{margin-top:20px}
  body.v2-record .acts .btn{min-height:44px}
  body.v2-record .docrow,body.v2-record .kv{min-height:38px;align-items:center}
  /* The dimension head is the primary control on this page at 390px — it is
     what a reader taps six times to work through the record — so it gets a
     full 44px target and the count is allowed to wrap onto its own line
     rather than being ellipsised. Losing "3 present · 2 to verify · 3
     missing" to an ellipsis would remove the whole value of the summary. */
  body.v2-record .dimhead{min-height:44px;align-items:center;padding:9px 12px}
  body.v2-record .dimhead .lh{gap:2px 9px}
  body.v2-record .dim + .dim{margin-top:8px}
  body.v2-record .phitem{margin:0 12px 9px}
  body.v2-record .phitem img{max-height:70vh}
  body.v2-record .racts .btn{min-height:44px}
  /* Cargo at 390: ONLY the item number drops. Description and QUANTITY stay.
     Iris F2 — this rule was written against a four-column table (Item ·
     Description · Qty · UOM) and kept after aggregation reduced it to three,
     so `:last-child` stopped meaning UOM and started meaning QTY. On the
     device carrying ~46% of usage the packing list rendered as descriptions
     with no counts, and nothing told the reader a column had gone. Piet §3.4
     is explicit that Qty_Loaded is the one reliable quantity fact and that it
     renders; a short-delivery dispute reading this page would have seen a
     record that looks complete and is not. Three characters, 364px of table.
     :not(.pogrp) stays load-bearing: the PO group row is a single colspan cell
     and is therefore first-child, so an unqualified rule would hide the
     purchase-order heading and merge every PO into one undifferentiated list. */
  body.v2-record .tbl.cargo thead th:first-child,
  body.v2-record .tbl.cargo tbody tr:not(.pogrp) td:first-child{display:none}
  body.v2-record .tbl.ledger{display:none}
  body.v2-record .mled{display:block}
  body.v2-record .lrow{background:var(--surface);border:1px solid var(--border);border-radius:9px;padding:9px 12px 10px;
    margin-bottom:7px;box-shadow:var(--shadow)}
  body.v2-record .lrow .w{font-size:12.5px;color:var(--fg)}
  body.v2-record .lrow .m{display:flex;justify-content:space-between;gap:10px;margin-top:4px;font-size:11.5px;
    color:var(--fg-muted);font-variant-numeric:tabular-nums}
  body.v2-record .lrow.cx{border-left:2px solid var(--border-strong);background:var(--recess)}
  body.v2-record .lgrp{border:1px solid var(--border);border-radius:9px;padding:9px 12px 10px;margin-bottom:7px;
    background:var(--surface);box-shadow:var(--shadow)}
  body.v2-record .lgrp .lrow{border:0;border-radius:0;box-shadow:none;margin:0;padding:7px 0;background:transparent}
  body.v2-record .lgrp .lrow + .lrow{border-top:1px solid var(--rule)}
  /* Related containers at 390: three lines per sibling, no truncation, 44px
     touch target. Room and state stay on one line because "Kampala yard ·
     B3 E-seal" is the comparison the block exists to make. */
  body.v2-record .tbl.rel thead{display:none}
  body.v2-record .tbl.rel tbody tr{display:grid;grid-template-columns:auto 1fr auto;
    padding:8px 12px;border-bottom:1px solid var(--rule);min-height:44px}
  body.v2-record .tbl.rel tbody tr:last-child{border-bottom:0}
  body.v2-record .tbl.rel tbody tr td{display:block;padding:0;border:0;background:none!important;line-height:1.5}
  body.v2-record .tbl.rel tbody tr td:nth-child(1){grid-area:1/1/2/3}
  body.v2-record .tbl.rel tbody tr td:nth-child(4){grid-area:1/3/2/4;text-align:right;font-size:12.5px}
  body.v2-record .tbl.rel tbody tr td:nth-child(2){grid-area:2/1/3/2;font-size:11.5px}
  body.v2-record .tbl.rel tbody tr td:nth-child(3){grid-area:2/2/3/4;font-size:11.5px;color:var(--fg-muted);padding-left:8px}
  body.v2-record .tbl.rel tbody tr td:nth-child(5){grid-area:3/1/4/4;font-size:11px;color:var(--fg-muted);margin-top:1px}
  body.v2-record .tbl.rel tr.thisone{background:var(--recess)}
}

/* ================================================================
   g. LEFT RAIL + DRAWER + OFF-HOME STRIP DISCLOSURE
   Piet spec_track_alexis_feedback_0805 item 2 · Iris
   spec_track_rail_design.md (2026-08-05). The A/B: strip = state layer,
   rail = navigation; both live until Piet's end-criterion decides.

   CLASS NAMES DEVIATE FROM IRIS'S PROPOSALS ON PURPOSE. Her mockup says
   .rail/.rail-item/.rail-search — those are LIVE v1 GLOBAL selectors in
   this same stylesheet (CSS_V2 is appended after v1's CSS and unscoped v1
   rules match v2 markup — the exact leak that hit the record page's
   .rail -> .recrail rename). One shared name would leak v1's
   position:fixed/56px-collapse/navy drawer onto this rail, silently. So:
   .vrail / .vr-* / .vscrim / .railtoggle / .mdisc — zero overlap with v1,
   verified. Her spec §0 marks all class names as proposals; the tokens,
   geometry, and behaviours below are hers verbatim.

   STATE: html.railoff (desktop hide, localStorage t.rail, pre-paint boot
   in v2_page) - default is VISIBLE (unset => rail on).
   body.v2.vdrawer-open (mobile drawer). ALL railoff rules live inside the
   >=900px block and ALL drawer rules inside the <900px block (exports'
   stale-localStorage lesson: a stale t.rail=0 must never touch the
   drawer). Never in the URL - the URL is the filter-state carrier. */

/* ---- the toggle: ONE control, leftmost in the bar, both breakpoints ---- */
body.v2 .railtoggle{position:relative;display:flex;align-items:center;justify-content:center;
  width:32px;height:32px;margin-right:4px;padding:0;background:none;border:0;border-radius:6px;
  color:var(--fg-muted);cursor:pointer;flex:none}
body.v2 .railtoggle:hover{color:var(--fg);background:var(--recess)}
body.v2 .railtoggle svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
/* Mobile-only worklist badge on the toggle (leaves leave the <900px bar).
   A COUNT, never a dot - v1's hamburger dot stays dead. Hidden on desktop
   (the count lives in the rail row / leaf there) and while the drawer is
   open (the drawer row shows it). Server omits it entirely at n=0. */
body.v2 .railtoggle .tbadge{display:none;position:absolute;top:1px;right:0;font-size:10px;
  font-weight:600;font-variant-numeric:tabular-nums;line-height:1;min-width:16px;padding:2px 3px;
  text-align:center;background:var(--recess-2);color:var(--fg);
  border:1px solid var(--border-strong);border-radius:8px}

/* ---- the rail: fixed BELOW the 46px bar (deviation D1 - the bar is the
        strip arm of the A/B and survives both rail states). display:none
        at every width by default; the two media blocks below turn it on. ---- */
body.v2 .vrail{display:none;position:fixed;top:46px;left:0;bottom:0;width:240px;
  background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;z-index:20;
  flex-direction:column;padding:10px 0 12px}
/* Drawer-only header (brand anchor + focus landing); hidden on desktop
   where the brand stays in the top bar (deviation D3: no rail footer). */
body.v2 .vr-head{display:none;align-items:center;gap:8px;min-height:52px;padding:0 14px;
  font-size:13px;font-weight:600;color:var(--fg);flex:none}
body.v2 .vr-head .brand-mark{width:24px;height:24px;flex-shrink:0}
body.v2 .vr-search{display:flex;gap:6px;margin:2px 12px 10px;flex:none}
body.v2 .vr-search input{flex:1;min-width:0;height:32px;padding:0 10px;font:inherit;font-size:12.5px;
  background:var(--recess);color:var(--fg);border:1px solid var(--border);border-radius:7px;outline:0}
body.v2 .vr-search input::placeholder{color:var(--fg-muted)}
body.v2 .vr-search input:focus-visible{border-color:var(--focus);outline:2px solid var(--focus);outline-offset:1px}
body.v2 .vr-search button{width:32px;height:32px;flex:none;display:flex;align-items:center;justify-content:center;
  background:var(--recess);border:1px solid var(--border);border-radius:7px;color:var(--fg-muted);
  cursor:pointer;padding:0}
body.v2 .vr-search button:hover{color:var(--fg)}
body.v2 .vr-search svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8}
body.v2 .vr-nav{flex:1;min-height:0}
/* Rows: 32px desktop density (D4 - ten rooms + leaves + admin must fit a
   900px viewport; measured stack ~640px, fits). */
body.v2 .vr-item,body.v2 .vr-room{display:flex;align-items:center;min-height:32px;padding:6px 12px;
  margin:1px 8px;border-radius:8px;font-size:12.5px;font-weight:500;color:var(--fg-2)}
body.v2 .vr-item:hover,body.v2 .vr-room:hover{background:var(--recess);color:var(--fg)}
/* Active marking: aria-current + the live .leaf grammar verbatim -
   recess-2 + fg + 600, two non-colour channels; --accent stays
   interactive-only (WCAG 1.4.1, the two-hue budget). */
body.v2 .vr-item[aria-current="page"],body.v2 .vr-room[aria-current="page"]{
  background:var(--recess-2);color:var(--fg);font-weight:600}
/* Inset ring - the rail is 240px, an outset ring clips at its border. */
body.v2 .vr-item:focus-visible,body.v2 .vr-room:focus-visible{outline:2px solid var(--focus);
  outline-offset:-2px;border-radius:8px}
/* Counts/depths: numbers, never dots - magnitude matters. On an ACTIVE row
   they switch --fg-muted -> --fg-2: fg-muted on recess-2 measures 4.11:1 in
   light, under the 4.5 text floor (Iris finding 1; .leaf .lcnt precedent). */
body.v2 .vr-cnt{margin-left:auto;font-size:11px;font-weight:600;color:var(--fg-muted);
  font-variant-numeric:tabular-nums}
body.v2 .vr-depth{margin-left:auto;font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2 .vr-item[aria-current="page"] .vr-cnt,
body.v2 .vr-room[aria-current="page"] .vr-depth{color:var(--fg-2)}
/* Rooms group: native details/summary, zero JS. Caret is --fg-muted -
   4.83:1 L / 5.64:1 D, clears the 3:1 UI floor; never a dimmer token. */
body.v2 .vr-group{margin:6px 0 2px}
body.v2 .vr-group summary{display:flex;align-items:center;gap:7px;list-style:none;cursor:pointer;
  min-height:30px;padding:4px 12px;margin:0 8px;border-radius:8px;font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--fg-2)}
body.v2 .vr-group summary::-webkit-details-marker{display:none}
body.v2 .vr-group summary:hover{background:var(--recess)}
body.v2 .vr-caret{display:inline-flex;transition:transform .12s;color:var(--fg-muted)}
body.v2 .vr-group[open]>summary .vr-caret{transform:rotate(90deg)}
body.v2 .vr-caret svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
/* Admin divider: the v2 voice of v1's .rail-admin-label. */
body.v2 .vr-admin{display:flex;align-items:center;gap:8px;margin:10px 20px 2px;font-size:10px;
  font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-2)}
body.v2 .vr-admin::after{content:'';flex:1;height:1px;background:var(--border)}
body.v2 .vscrim{display:none}

/* ---- you-are-here on the REDUCED strip cell (Iris §1.5: room pages
        double-mark; rail row and strip cell must agree). Scoped to
        .strip.reduced - the FULL home strip keeps its no-background
        decision untouched. ---- */
body.v2 .strip.reduced .room[aria-current="page"]{background:var(--recess-2)}
body.v2 .strip.reduced .room[aria-current="page"] .rn{color:var(--fg)}

/* ---- mdisc: the off-home strip disclosure, <900px only (Piet §2.2).
        IN FLOW inside .chromenav, NEVER sticky - zero interaction with the
        46px thead offsets and the /at/sea band headers. Collapsed by
        default, unpersisted (state-layer control, not chrome preference). ---- */
body.v2 .mdisc{display:none;background:var(--surface);border:1px solid var(--border);
  border-radius:9px;box-shadow:var(--shadow)}
body.v2 .mdisc summary{display:flex;align-items:center;gap:7px;min-height:44px;padding:0 12px;
  list-style:none;cursor:pointer}
body.v2 .mdisc summary::-webkit-details-marker{display:none}
body.v2 .mdisc .dl{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--fg-muted)}
body.v2 .mdisc .dot{color:var(--fg-muted)}
body.v2 .mdisc .dr{font-size:12.5px;font-weight:600;color:var(--fg)}
body.v2 .mdisc .dc{font-size:11.5px;color:var(--fg-muted);font-variant-numeric:tabular-nums}
body.v2 .mdisc .vr-caret{margin-left:auto}
body.v2 .mdisc[open]>summary .vr-caret{transform:rotate(90deg)}
body.v2 .mdisc .mrooms{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule);
  border-top:1px solid var(--rule);border-radius:0 0 9px 9px;overflow:hidden}
body.v2 .mdisc .room{display:flex;flex-direction:row;align-items:baseline;justify-content:flex-start;
  gap:8px;border-right:0;background:var(--surface);min-height:44px;padding:8px 11px}
body.v2 .mdisc .rn{height:auto}
body.v2 .mdisc .rd{margin:0}
body.v2 .mdisc .rd b{font-size:16px}
body.v2 .mdisc .room[aria-current="page"]{background:var(--recess-2)}
body.v2 .mdisc .room[aria-current="page"] .rn{color:var(--fg)}

/* ---- >=900px: rail on/off. railoff = today's live chrome byte-identical
        (the A/B control arm; nothing else in the bar is touched). Hide is
        FULL (deviation D2): ten rooms cannot be iconified at 56px and depth
        counts cannot render there; railoff loses nothing because the bar
        carries the complete chrome. ---- */
@media (min-width:900px){
  html:not(.railoff) body.v2 .vrail{display:flex}
  /* Horizontal-only: NO sticky offset changes anywhere. Home's 46/76 stack
     and the room thead keep their numbers; the rail spends width, not
     height (and stays collision-free with the vessel bands). */
  html:not(.railoff) body.v2 .chromenav,
  html:not(.railoff) body.v2 .page{margin-left:240px}
  /* The rail carries leaves + search + Sync; ONE search visible per state. */
  html:not(.railoff) body.v2 .top .leaves,
  html:not(.railoff) body.v2 .top .search,
  html:not(.railoff) body.v2 .top .adminleaf{display:none}
  html:not(.railoff) body.v2 .topright{margin-left:auto}
}

/* ---- <900px: the SAME markup is the drawer (exports pattern + its focus
        deltas, in V2_RAIL_JS). Leaves leave the bar - the drawer carries
        them with their counts (removes the 431-780px clipping band; flagged
        as a live-mobile-chrome change). ---- */
@media (max-width:899.98px){
  /* MOBILE BRAND RULE (Iris brand-identity §3): 24px coloured mark ONLY,
     zero visible wordmark. The anchor's aria-label carries the name, so
     display:none is safe (supersedes the old <=688px sr-only clip). */
  body.v2 .brand{margin-right:6px}
  body.v2 .brand-words{display:none}
  body.v2 .brand .brand-mark{width:24px;height:24px}
  body.v2 .top .leaves{display:none}
  body.v2 .top .adminleaf{display:none}
  /* Bar order: toggle - brand - (space) - search - theme - signout. */
  body.v2 .top .search{margin-left:auto}
  body.v2 .topright{margin-left:0}
  body.v2 .railtoggle{width:44px;height:44px}
  body.v2 .railtoggle .tbadge{display:block}
  body.v2.vdrawer-open .railtoggle .tbadge{display:none}
  body.v2 .vrail{display:flex;top:0;width:280px;max-width:85vw;z-index:400;
    transform:translateX(-102%);visibility:hidden;
    transition:transform .2s ease,visibility 0s linear .2s;
    box-shadow:0 8px 24px var(--scrim)}
  body.v2.vdrawer-open .vrail{transform:none;visibility:visible;transition:transform .2s ease}
  body.v2 .vscrim{position:fixed;inset:0;background:var(--scrim);z-index:350}
  body.v2.vdrawer-open .vscrim{display:block}
  body.v2.vdrawer-open{overflow:hidden}
  body.v2 .vr-head{display:flex}
  /* 44px targets INSIDE the drawer only; desktop density unchanged.
     16px input: iOS zooms any focused field below 16px (the <=688px
     top-bar search already ships the same escape). */
  body.v2 .vr-search input{height:44px;font-size:16px}
  body.v2 .vr-search button{width:44px;height:44px}
  body.v2 .vr-item,body.v2 .vr-room{min-height:44px;font-size:13px}
  body.v2 .vr-group summary{min-height:44px}
  /* Off home the always-open reduced strip becomes the mdisc. Scoped to
     .chromenav so the HOME strip is untouched at every width (S1c). */
  body.v2 .chromenav .strip{display:none}
  body.v2 .mdisc{display:block}
}

@media (prefers-reduced-motion:reduce){body.v2 *{transition:none!important}}
