/* ==========================================================================
   App shell — a transparent overlay layer floating above the full-bleed
   map (#12). It fills the viewport but stays click-through: only the actual
   panels re-enable pointer events, so the map is still draggable in the gaps.
   ========================================================================== */

.app-shell {
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}

/* Re-enable interaction on the real panels; the shell and the layout-only
   .side-rail/.left-rail wrappers stay click-through. */
.nav-pill,
.transfers-strip,
.transfer-detail-panel,
.floating-panel {
  pointer-events: auto;
}

/* --- Header bar (#21) -------------------------------------------------
   A deliberate deviation from DESIGN.md's documented floating-pill nav
   pattern (Section 1/7) — see DESIGN.md's App-Specific header section for
   the rationale. Spans the full viewport width and docks flush at the top
   instead of floating as a content-sized pill; its rendered height is
   tracked into --nav-height (public/js/components/nav.js) so panels
   pinned below it (.transfers-strip, .side-rail) clear it exactly instead
   of guessing with a fixed pixel offset. */
.nav-pill {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-glass);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid var(--color-glass-border);
  box-shadow: var(--shadow-2);
}

.nav-pill__brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.nav-pill__mark {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 30%, var(--color-signal-light), var(--color-ink) 78%);
}

.nav-pill__title {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.02em;
}

.nav-pill__subtitle {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-muted);
}

.nav-pill__status {
  margin: 0;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  background: var(--color-canvas);
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text-charcoal);
  white-space: nowrap;
}

/* Frosted panel — the shared overlay surface. Every panel now sits over live
   map tiles, so it needs its own translucent-cream plate + blur to stay
   legible instead of resting on the (now hidden) opaque canvas.
   `overflow: hidden` clips every child — headers included — to this rounded
   shape; without it a sticky header's square corners visibly overhang past
   the panel's own rounded ones (a design bug found live, since nothing was
   otherwise clipping child boxes to the curve). */
.panel {
  background: var(--color-glass);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid var(--color-glass-border);
  border-radius: var(--radius-button);
  padding: var(--space-3);
  box-shadow: var(--shadow-2);
  overflow: hidden;
}

/* ==========================================================================
   Shared panel header (#27) — one pattern for every panel's heading row:
   Transfers list, Transfer Detail, the Agent Timeline console frame,
   Network Status, and Simulate Incident all use this instead of five
   ad-hoc, copy-pasted variants. Eyebrow typography, sticky-to-panel-top,
   and a bottom-border separator so a header always reads as "the top of
   this panel" rather than floating text. Documented in DESIGN.md.
   ========================================================================== */
.panel-header {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--color-border);
  /* Opaque, not another layer of .panel's own translucent --color-glass:
     stacking two blurred, semi-transparent glass layers compounds their
     opacity, so the header rendered visibly whiter than the rest of the
     panel below it (a design bug found live). --color-canvas-lifted is
     the same Lifted Cream hue at full opacity — the token's own comment
     ("nested/raised panels") is exactly this case: a header raised above
     the scrolled content inside a translucent panel, not a second sheet
     of the same glass. */
  background: var(--color-canvas-lifted);
}
.panel-header.eyebrow {
  /* .eyebrow's own bottom margin is for stand-alone labels; inside the
     header row spacing is handled by .panel-header's padding instead. */
  margin: 0;
}

/* Headers with a trailing action (the console frame's Copy button, the
   detail header's status pill) push it to the far end; single-content
   eyebrow headers must NOT get this — .eyebrow is itself a flex row (bullet
   + label), and space-between on the same element shoves those two apart
   instead of justifying the header's own children. */
.panel-header--split {
  justify-content: space-between;
}

/* Headers whose scroll container already sits inside an outer .panel's own
   padding (Transfer Detail) don't need their own horizontal inset — adding
   one would double it. Headers whose container zeroes that padding onto its
   children instead (Network Status, per #23) keep the default side padding
   above. */
.panel-header--flush {
  padding-left: 0;
  padding-right: 0;
}

/* A header that is the first child of a .panel-padded ancestor (Simulate
   Incident) sits ~24px inside the panel's own padding on every side — miles
   clear of the panel's rounded corners, unlike Network Status's header,
   which is genuinely flush against the panel edge (its ancestor's own
   padding is zeroed, #23/#30) and reads as the panel's own top bar. Design
   feedback (live, with screenshots): the inset version reads as a separate
   floating rectangle sitting inside the panel, not the same surface —
   Network Status's flush, edge-to-edge, rounded-corner header is the one
   that "looks right" (#34 follow-up).
   This modifier pulls such a header back out to the panel's true edge with
   a negative margin equal to the ambient padding it's sitting inside, then
   relies on the header's own *unmodified* base padding
   (`padding: var(--space-3) var(--space-3) var(--space-2)`) to push the
   text back to exactly where it was — the margin and the padding cancel,
   so this changes the header's box position (and lets .panel's
   `overflow: hidden` clip its corners to match the panel's own rounding)
   without moving a single line of text. */
/* Compounded with .panel-header (not just the modifier alone) to outrank
   .panel-header.eyebrow's own margin: 0 above — both are equally specific,
   so without this the eyebrow rule would win by only being a few lines
   earlier and silently zero these margins back out. */
.panel-header.panel-header--bleed-corner {
  margin: calc(-1 * var(--space-3)) calc(-1 * var(--space-3)) 0;
}

/* Left + right, no top — for the Transfers list header. Top is already
   handled by .transfers-strip's own zeroed padding-top (below), so only the
   horizontal insets need cancelling here (unlike --bleed-corner, which also
   bleeds top for headers whose ancestor keeps its default padding-top).
   This was left-only from the #34 follow-up until the #34 third follow-up,
   on the theory that the header shared its scroll container with a
   persistent scrollbar (#14/#25) on the right and bleeding that side would
   put it flush against the scrollbar thumb. The #34 second follow-up split
   the header out of the scroll container entirely (.transfers-strip__list-body
   owns the scrollbar now), so that concern no longer applied — leaving it
   left-only just meant the header stopped ~24px short of the panel's true
   right edge, reading as a floating rectangle again on that side (found
   live, by screenshot, against Network Status's fully-flush header).
   Transfer Detail's header and the Manager Reply / Regional Director
   Override titles had the same floating-rectangle bug for the same reason
   and briefly used this class too (#34 third follow-up) — #35 removed the
   need for it there instead by splitting Transfer Detail into its own
   zero-padding panel (see .transfer-detail-panel below), so its header now
   reaches the panel's true edges for free, the same way Network Status's
   does, with no bleed hack required. */
.panel-header.panel-header--bleed-sides {
  margin-left: calc(-1 * var(--space-3));
  padding-left: var(--space-3);
  margin-right: calc(-1 * var(--space-3));
  padding-right: var(--space-3);
}

/* Transfer Detail's header shares its scroll container with the Agent
   Timeline console frame's own sticky header (#27) — two stickies at the
   same top:0 would fight over the same slot, with the later one covering
   the former. Transfer Detail only adopts the shared typography/padding/
   separator here; genuine sticky-ness for it is out of scope (a follow-up
   issue redesigns this header to read as a ticket expansion instead).
   Also used by Network Status (#30), Simulate Incident (#34 follow-up), and
   the Transfers list, none of which scroll internally — the Transfers list
   header sits above its own non-scrolling wrapper (.transfers-strip), with
   only .transfers-strip__list-body scrolling beneath it, so sticky
   positioning is functionally inert for all three — and worse than inert
   once .panel gained overflow:hidden (#34), since that makes the panel
   itself a scroll container that sticky's `top: 0` then anchors to,
   clamping the header at the panel's *padding* edge and silently cancelling
   a --bleed-corner negative margin trying to pull it out to the true edge.
   --static sidesteps that entirely. */
.panel-header--static {
  position: static;
}

/* Dark "on-ink" variant for headers inside ink-toned surfaces (the Agent
   Timeline console frame) — same shape, inverted palette, top corners
   matched to the frame it caps (#22), and a tighter bottom gap to keep the
   console's dense, terminal-like feel. */
.panel-header--on-ink {
  padding-top: var(--space-2);
  padding-bottom: 4px;
  background: var(--color-ink);
  border-bottom-color: var(--color-border-on-ink);
  border-radius: var(--radius-frame) var(--radius-frame) 0 0;
}

/* ==========================================================================
   Left rail (#12, #35) — Transfers list and Transfer Detail as two
   independent floating panels stacked with a gap, the same arrangement
   .side-rail already uses for Network Status / Simulate Incident. Used to
   be one continuous card with Transfer Detail reading as the list's own
   "ticket expansion" (#29/#33) — that shared-panel seam kept producing
   header inset/gap bugs across three follow-ups on #34 (found live, by
   screenshot, each time), so #35 splits them into separate panels instead:
   a real gap now carries the separation, and Transfer Detail gets a close
   button since it's no longer just an always-present lower half of the
   list's own card.
   ========================================================================== */

.left-rail {
  position: absolute;
  left: var(--space-3);
  top: calc(var(--nav-height) + var(--space-3)); /* clears the header bar above it (#21) */
  bottom: var(--space-3);
  width: var(--transfers-strip-width);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  pointer-events: none; /* re-enabled on .transfers-strip/.transfer-detail-panel above, matching .side-rail/.floating-panel */
}

/* Continuous drag-to-resize handle (#26) — replaces the #16 two-state
   expand toggle. The element itself is a wide (12px) invisible hit area on
   the rail's right edge. Lives on the rail itself (not just the Transfers
   list panel) so dragging resizes both panels' width together (#35). The
   visible grab tabs are separate elements (.rail-grab-tab, below) — one per
   panel, not one on this handle — since the two panels aren't the same
   height and Transfers' own height changes with the detail panel open/closed
   (#40); their vertical position is computed in railGrabTabs.js. */
.left-rail__resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  right: -6px;
  width: 12px;
  cursor: ew-resize;
  touch-action: none;
  z-index: 3;
  pointer-events: auto;
}
.left-rail__resize-handle:focus-visible {
  outline: none;
}

/* Grab tab (#40) — a raised pill straddling the rail's edge, reading as a
   physical tab to grab rather than an invisible full-height strip. `top` is
   set inline by railGrabTabs.js (centered on the tab's own panel, which
   this element doesn't otherwise know the height of); pointer-events: none
   so the actual drag hit area stays the sibling .left-rail__resize-handle,
   which spans the full rail — clicking/dragging anywhere works the same,
   these are decoration only. Solid Clay Brown, not a translucent tint
   (found nearly invisible live — an rgba fill reads as a ghost outline
   against both the light panel and the dark map behind it); z-index above
   the panels so the half of the tab overlapping a panel isn't visually lost
   under it. */
.rail-grab-tab {
  position: absolute;
  z-index: 5;
  right: -4px;
  width: 8px;
  height: 52px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
  background: var(--color-clay);
  border: 1px solid var(--color-signal);
  box-shadow: var(--shadow-1);
  pointer-events: none;
  transition: background 120ms ease, border-color 120ms ease;
}
.left-rail__resize-handle:hover ~ .rail-grab-tab,
.left-rail__resize-handle:focus-visible ~ .rail-grab-tab,
.left-rail--resizing .rail-grab-tab {
  background: var(--color-signal);
  border-color: var(--color-signal-light);
}
.left-rail--resizing {
  user-select: none;
}

/* Transfers list panel — header + its own scroll region, capped short of
   the rail's full height so there's always room left for the detail panel
   below when it's open (#35; previously capped via the rail's own flex
   layout instead of a fixed max-height, back when this panel's sibling was
   an internal region rather than a separate panel). */
.transfers-strip {
  /* flex-shrink: 0 — a shrink factor here competed with the detail panel's
     own (much taller, unconstrained-by-overflow) auto flex-basis for
     negative space and lost badly, squashing the list down to a sliver
     whenever a ticket was open. Fixed in px, not %, and sized to show
     exactly 3 rows (header + 3 * row height + gaps + bottom padding,
     measured live) — any more than that just pushes the detail panel
     further down than it needs to be; the list itself still scrolls for
     the rest. */
  flex: 0 0 288px;
  max-height: 288px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Zeroed instead of the .panel default (#34 follow-up): the header stays
     position: sticky (it genuinely needs to pin while rows scroll beneath
     it), and sticky's `top: 0` anchors to this panel's own *padding* edge
     now that `overflow: hidden` makes it a scroll container. Leaving
     padding-top at the .panel default would anchor the header 24px below
     the panel's true top — same effect as the corner-flush problem
     `--bleed-corner` solves with a margin, except a margin can't win here
     (see .panel-header--bleed-sides above), so the padding itself has to
     move instead. Left/right/bottom stay at the .panel default; only the
     header sits at this container's true top, so only top needs zeroing. */
  padding-top: 0;
}

/* When Transfer Detail is closed, let the list use all the rail's height
   instead of stopping at 55% and leaving dead space below it (#35). */
.left-rail:has(> .transfer-detail-panel[hidden]) .transfers-strip {
  flex: 1 1 auto;
  max-height: none;
}

.transfers-strip__list-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--space-2);
  /* Gutter (#25) — rows had no right-side padding of their own, so they ran
     flush against the scrollbar thumb. */
  padding-right: var(--space-2);
}

/* Transfer Detail panel (#35) — its own floating card now, not a region
   inside the Transfers list's panel. Zero-padding, same as Network Status
   (#30): the header (.detail-header, plain .panel-header with no
   --flush/--bleed-sides needed) sits directly against this panel's own
   true edges, and .transfer-detail-panel__body supplies the inset for
   everything below it instead. */
.transfer-detail-panel {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
}

/* The [hidden] attribute's UA-stylesheet `display: none` loses to this
   panel's own `display: flex` above (author rules always beat the UA
   sheet regardless of selector order/specificity) — without this rule the
   panel showed even while `hidden`, defeating #35's whole point of it being
   closed by default until a ticket is clicked. */
.transfer-detail-panel[hidden] {
  display: none;
}

.transfer-detail-panel__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Breathing room below the header's border-bottom, matching
     .floating-panel--network__body (#23) — without it the console frame
     sits flush against the divider. */
  margin-top: var(--space-2);
  padding: 0 var(--space-3) var(--space-3);
}

/* One scrollbar per logical region (#14): thin, ink-toned, cross-browser via
   plain CSS (scrollbar-width/-color + the -webkit- pseudo-elements) rather
   than a dependency — two properties cover every evergreen browser. Also
   applied to .floating-panel--network__body (#25, moved off the panel
   itself by #30 once the header was split out of the scroll region) so the
   Network Status scroll region matches instead of falling back to the OS
   default. */
.transfers-strip__list-body,
.transfer-detail-panel__body,
.floating-panel--network__body {
  scrollbar-width: thin;
  scrollbar-color: rgba(20, 20, 19, 0.22) transparent;
}
.transfers-strip__list-body::-webkit-scrollbar,
.transfer-detail-panel__body::-webkit-scrollbar,
.floating-panel--network__body::-webkit-scrollbar {
  width: 6px;
}
.transfers-strip__list-body::-webkit-scrollbar-thumb,
.transfer-detail-panel__body::-webkit-scrollbar-thumb,
.floating-panel--network__body::-webkit-scrollbar-thumb {
  background: rgba(20, 20, 19, 0.22);
  border-radius: var(--radius-pill);
}
.transfers-strip__list-body::-webkit-scrollbar-track,
.transfer-detail-panel__body::-webkit-scrollbar-track,
.floating-panel--network__body::-webkit-scrollbar-track {
  background: transparent;
}

/* ==========================================================================
   Side rail — Network Status over Simulate Incident, docked to the right
   edge (#12). A layout-only wrapper (click-through); its panels re-enable
   pointer events. Capped short of the bottom so the zoom control and OSM
   attribution keep the bottom-right corner to themselves.
   ========================================================================== */

.side-rail {
  position: absolute;
  top: calc(var(--nav-height) + var(--space-3)); /* clears the header bar above it (#21) */
  right: var(--space-3);
  width: clamp(300px, 23vw, 360px);
  max-height: calc(100vh - var(--nav-height) - var(--space-3) - 100px);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  pointer-events: none;
}

/* #23 zeroed this panel's own padding and restored it individually on
   .panel-header / .site-filter / .site-cards, because a sticky heading's
   `top: 0` anchors to the *padding edge* of its nearest scrolling ancestor —
   and this panel was both the frosted panel and the scroll container in one
   element, so the sticky heading could never reach the panel's true top.
   #30 splits the header out into its own non-scrolling element instead (see
   .floating-panel--network__body below); the original sticky-anchor reason
   for zeroing this padding is gone, but the arrangement itself still holds
   for a simpler one — .panel-header now carries its own inset independently
   (via .panel-header--static, not this padding), same as .site-filter and
   .site-cards already do below. */
.floating-panel--network {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
}

/* The actual scroll region: everything below the static header. Owns the
   scrollbar styling (#14/#25, below) now that it — not the panel — is the
   scrolling element. */
.floating-panel--network__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Breathing room below the header's border-bottom — without it the
     filter row sits flush against the divider (design feedback, live). */
  margin-top: var(--space-2);
}

.floating-panel--sim {
  flex: 0 0 auto;
}

/* ==========================================================================
   Network Status — Site Type filter
   ========================================================================== */

.site-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-3); /* replaces the side inset .floating-panel--network used to provide (#23) */
}

.site-filter__btn {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  padding: 3px 10px;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  opacity: 0.35;
  transition: opacity 120ms ease, transform 120ms ease;
}

.site-filter__btn:hover {
  transform: translateY(-1px);
}

.site-filter__btn.is-active {
  opacity: 1;
}

.site-filter__btn.type-XL {
  background: var(--color-site-xl);
  color: var(--color-site-xl-strong);
}
.site-filter__btn.type-L {
  background: var(--color-site-l);
  color: var(--color-site-l-strong);
}
.site-filter__btn.type-M {
  background: var(--color-site-m);
  color: var(--color-site-m-strong);
}
.site-filter__btn.type-S {
  background: var(--color-site-s);
  color: var(--color-site-s-strong);
}

/* ==========================================================================
   Network Status — Site cards
   ========================================================================== */

.site-cards {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}

/* The floating Network Status panel is a narrow sidebar, not a full grid
   cell — one column reads far better than two cramped ones. */
.floating-panel--network .site-cards {
  grid-template-columns: 1fr;
  /* Replaces the side/bottom inset .floating-panel--network used to provide
     before its padding moved onto children for the sticky heading (#23). */
  padding: 0 var(--space-3) var(--space-3);
}

.site-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-button);
  padding: 14px 16px;
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

/* Mirrors the map's Link Blue selection ring (#19) so the card and its
   marker read as the same selection, not two unrelated highlight colors. */
.site-card--selected {
  border-color: var(--color-link);
  box-shadow: 0 0 0 2px rgba(56, 96, 190, 0.25);
}

/* Eyebrow-style category line — Site Type + MRT is exactly the kind of
   classification data DESIGN.md's eyebrow pattern is for. */
.site-card__eyebrow {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 8px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Fixed footprint (#39) -- a set width + centered text instead of
   label-driven horizontal padding, so XL/L/M/S read as "the same shape,
   different label" rather than XL visibly outsizing S. Width comes from
   "XL" (the widest label) at this font-size/weight, plus a hair of
   breathing room; the map marker (mapView.js's MARKER_SIZE) is already
   fixed-size on its own and untouched by this. */
.site-card__badge {
  display: inline-block;
  flex: none;
  width: 24px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  padding: 1px 0;
  border-radius: var(--radius-pill);
}
.site-card.type-XL .site-card__badge {
  background: var(--color-site-xl);
  color: var(--color-site-xl-strong);
}
.site-card.type-L .site-card__badge {
  background: var(--color-site-l);
  color: var(--color-site-l-strong);
}
.site-card.type-M .site-card__badge {
  background: var(--color-site-m);
  color: var(--color-site-m-strong);
}
.site-card.type-S .site-card__badge {
  background: var(--color-site-s);
  color: var(--color-site-s-strong);
}

/* Card title (H3 role) — DESIGN.md: 500 weight, tight tracking, 1.2 line-height */
.site-card__name {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.site-card__stats {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.site-card__stat-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}

.site-card__stat-label {
  font-size: 12px;
  font-weight: 450;
  color: var(--color-text-charcoal);
}

/* Mono is reserved for actual numbers — the utility/data face, kept out of
   labels and titles so it reads as data rather than as the card's voice. */
.site-card__stat-current {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}

/* Gauge: filled bar = current stock; two ticks mark Operating Threshold
   (static hard floor) and min_buffer (dynamic soft floor) on the same
   track, so their relative position is legible without reading CONTEXT.md. */
.site-card__gauge {
  position: relative;
  height: 5px;
  border-radius: var(--radius-pill);
  background: var(--color-canvas);
  overflow: visible;
}

.site-card__gauge-fill {
  position: absolute;
  inset: 0 auto 0 0;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-text-charcoal);
}

/* Threshold above the bar, Buffer below (#18) — a consistent position
   convention on top of the color coding, so the two read as distinct
   without needing the legend. */
.site-card__gauge-mark {
  position: absolute;
  width: 2px;
  height: 6px;
  border-radius: 1px;
  transform: translateX(-1px);
  cursor: help;
}
.site-card__gauge-mark--threshold {
  top: -9px;
  background: var(--color-link); /* Link Blue */
}
.site-card__gauge-mark--buffer {
  top: 100%;
  margin-top: 2px;
  background: var(--color-signal-light); /* Light Signal Orange */
}

/* Custom hover/focus tooltip (#18) — replaces the native `title` tooltip
   with one styled like the rest of the UI. `content: attr(aria-label)`
   means the visible tooltip and the accessible name are the same string,
   with no separate data attribute to keep in sync. Keyboard-reachable via
   the mark's tabindex, not just mouse hover. */
.site-card__gauge-mark::after {
  content: attr(aria-label);
  position: absolute;
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  white-space: nowrap;
  background: var(--color-ink);
  color: var(--color-text-on-ink);
  font-size: 11px;
  font-weight: 500;
  padding: 4px 9px;
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-1);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
  z-index: 5;
}
.site-card__gauge-mark--threshold::after {
  bottom: calc(100% + 6px);
}
.site-card__gauge-mark--buffer::after {
  top: calc(100% + 6px);
  transform: translateX(-50%) translateY(-4px);
}
.site-card__gauge-mark:hover::after,
.site-card__gauge-mark:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Small info tooltip (#42) — same attr(aria-label)-as-tooltip technique as
   .site-card__gauge-mark above, just self-positioned instead of relative to
   a gauge. Used next to the Agent Timeline heading to explain when/why a
   Transfer's donor can change mid-negotiation (Concession Protocol). Always
   rendered on a light surface regardless of where the hint itself sits,
   since its one use so far (the on-ink console header) would otherwise put
   an ink tooltip on an ink background. */
.info-hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  position: relative;
}
.info-hint::after {
  content: attr(aria-label);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  width: 240px;
  transform: translateX(-50%) translateY(-4px);
  text-transform: none;
  letter-spacing: normal;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  white-space: normal;
  background: var(--color-canvas-lifted);
  color: var(--color-text-charcoal);
  padding: 8px 10px;
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-1);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
  z-index: 5;
}
.info-hint:hover::after,
.info-hint:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.site-card__stat-legend {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Breathing room off the gauge above (#28) — 5px read as touching the
     tick marks; this puts real space between the two. */
  margin-top: 12px;
  font-size: 10px;
  color: var(--color-text-muted);
}
.site-card__stat-legend b {
  font-family: var(--font-mono);
  font-weight: 600;
}

/* Color-match the legend text to its gauge mark (#28) — same tokens as
   .site-card__gauge-mark--threshold/--buffer, so the correspondence is
   visible without hovering for the tooltip. */
.site-card__stat-legend-item--threshold,
.site-card__stat-legend-item--threshold b {
  color: var(--color-link);
}
.site-card__stat-legend-item--buffer,
.site-card__stat-legend-item--buffer b {
  color: var(--color-signal-light);
}

.site-card__stat.is-low .site-card__stat-label,
.site-card__stat.is-low .site-card__stat-current {
  color: var(--color-signal);
}
.site-card__stat.is-low .site-card__gauge-fill {
  background: var(--color-signal);
}
/* The is-low alert color (--color-signal, a saturated rust) and the Min
   Buffer legend's --color-signal-light are both warm oranges but distinct
   enough in value to stay legible together — is-low doesn't touch the
   legend at all, so the two never compete on the same word. */

/* ==========================================================================
   Ops Map (signature element) — full-bleed, fixed ink background layer (#12)
   ========================================================================== */

/* The map is the page background: pinned to the viewport, behind the overlay
   shell, with no card chrome (no max-width, radius, border, or shadow). */
.map-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
}

.map-frame {
  position: absolute;
  inset: 0;
  background: var(--color-map-base);
  overflow: hidden;
}

.map {
  width: 100%;
  height: 100%;
  background: var(--color-map-base);
}

/* --- Ink-toned monochrome tile recolor -----------------------------------
   OSM ships warm, fully-saturated cartography (green parks, blue water,
   cream land). To fold it down to a dark near-monochrome that sits *under*
   the Ink UI, we filter ONLY the Leaflet tile pane — markers, route SVGs,
   tooltips and the zoom control live in sibling panes and keep their color.
   Each filter stage is a named token in tokens.css (#15) — see there for
   what each value does and why; this rule just composes them. */
.map--ink .leaflet-tile-pane {
  filter: grayscale(var(--map-tile-grayscale)) invert(var(--map-tile-invert))
    brightness(var(--map-tile-brightness)) contrast(var(--map-tile-contrast))
    sepia(var(--map-tile-sepia)) hue-rotate(var(--map-tile-hue-rotate))
    saturate(var(--map-tile-saturate));
}

/* Leaflet controls over the ink base: frost them to match the panels and
   keep the native +/- zoom affordance legible on the dark plate. Nudged in
   from the edge so they clear the floating panels. */
.map-frame .leaflet-top.leaflet-left,
.map-frame .leaflet-top.leaflet-right,
.map-frame .leaflet-bottom.leaflet-left,
.map-frame .leaflet-bottom.leaflet-right {
  margin: var(--space-3);
}

.leaflet-control-zoom.leaflet-bar {
  border: 1px solid var(--color-glass-border);
  border-radius: var(--radius-button);
  overflow: hidden;
  box-shadow: var(--shadow-2);
}

.leaflet-control-zoom a {
  background: var(--color-glass-ink);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--color-text-on-ink);
  border-bottom: 1px solid var(--color-border-on-ink);
}

.leaflet-control-zoom a:hover {
  background: rgba(20, 20, 19, 0.82);
  color: #fff;
}

.leaflet-control-attribution {
  background: var(--color-glass-ink) !important;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--color-text-on-ink-muted) !important;
  border-radius: var(--radius-chip) 0 0 0;
  padding: 2px 8px;
}

.leaflet-control-attribution a {
  color: var(--color-text-on-ink) !important;
}

/* Reset Leaflet's default white/bordered div-icon box — the badge itself
   (.map-marker) supplies its own shape and color. */
.leaflet-div-icon.map-marker-icon {
  background: transparent;
  border: none;
}

/* Base pin shape/shadow — color now comes from the Site Type modifiers
   below (#24), not this single ink-tint. --color-marker stays as the
   pre-type-color fallback (e.g. a site of an unrecognized type). */
.map-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-ink);
  background: var(--color-marker);
  color: var(--color-ink);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  transition: transform 120ms ease;
}

.map-marker:hover {
  transform: scale(1.08);
}

/* Site Type color-coding (#24) — the same light-fill/strong-border pairing
   already used for the Site Card badge and the type filter buttons, so a
   site type reads as the same color everywhere in the app, not just on the
   map. The light pastel fills stay legible against the dark map base from
   #15 the same way the old single ink-tint did. Defined before the
   donor/receiver role rules below so an active transfer's endpoints still
   override this with the Signal Orange treatment. */
.map-marker--XL {
  background: var(--color-site-xl);
  border-color: var(--color-site-xl-strong);
  color: var(--color-site-xl-strong);
}
.map-marker--L {
  background: var(--color-site-l);
  border-color: var(--color-site-l-strong);
  color: var(--color-site-l-strong);
}
.map-marker--M {
  background: var(--color-site-m);
  border-color: var(--color-site-m-strong);
  color: var(--color-site-m-strong);
}
.map-marker--S {
  background: var(--color-site-s);
  border-color: var(--color-site-s-strong);
  color: var(--color-site-s-strong);
}

/* Selected transfer endpoints (#13): both ends of the projected move light
   up in the Signal family so the route reads "from this site, to this site".
   The donor stays a light pin with a signal ring (stock leaves it); the
   receiver fills solid Signal Orange (stock lands there). */
.map-marker--donor {
  border-color: var(--color-signal);
  box-shadow:
    0 0 0 3px rgba(207, 69, 0, 0.35),
    0 2px 10px rgba(0, 0, 0, 0.45);
}

.map-marker--receiver {
  border-color: var(--color-signal);
  background: var(--color-signal);
  color: #fff;
  box-shadow:
    0 0 0 3px rgba(207, 69, 0, 0.35),
    0 2px 10px rgba(0, 0, 0, 0.45);
}

/* Card -> map highlight (#19): a Link Blue outline ring, deliberately a
   different color family and a different CSS property (outline, not
   box-shadow) than the donor/receiver Signal Orange treatment above, so a
   site that's both a card selection AND a route endpoint shows both rings
   at once instead of one clobbering the other. */
.map-marker--card-selected {
  outline: 3px solid var(--color-link);
  outline-offset: 3px;
}

/* Route annotations (#13): wrapper is a zero-size Leaflet div-icon; the
   children center themselves on the anchor point. */
.leaflet-div-icon.map-anno-icon {
  background: transparent;
  border: none;
  overflow: visible;
}

/* Haversine distance / courier ETA chip at the arc midpoint. */
.map-route-label {
  position: absolute;
  transform: translate(-50%, -140%);
  white-space: nowrap;
  padding: 3px 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-on-ink);
  background: var(--color-glass-ink);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--color-border-on-ink);
  border-radius: var(--radius-pill);
  pointer-events: none;
}

/* The courier truck that sweeps the arc donor -> receiver — the motion is
   the direction indicator. */
.map-courier {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--color-signal);
  color: #fff;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.65), 0 2px 8px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

.map-courier__icon {
  width: 13px;
  height: 13px;
}

.map-route {
  fill: none;
  stroke: var(--color-signal-light);
  stroke-width: 1.4px;
  stroke-linecap: round;
  stroke-dasharray: 3 5;
  opacity: 0.55;
  animation: orbit-flow 6s linear infinite;
}

.map-route--selected {
  opacity: 0.95;
  stroke-width: 2px;
}

@keyframes orbit-flow {
  to {
    stroke-dashoffset: -64;
  }
}

/* Completed-transfer route (#20) — deliberately its own class, not
   .map-route + a modifier, since .map-route always carries the ambient
   orbit-flow animation. Static and muted (Dust Taupe): a historical record
   of where the asset moved, not something still happening. Different dash
   rhythm (2/6 vs the active routes' 3/5) so the two read as different
   materials even in a single screenshot, not just "animated vs paused". */
.map-route--completed {
  fill: none;
  stroke: var(--color-text-on-ink-muted);
  stroke-width: 1.6px;
  stroke-linecap: round;
  stroke-dasharray: 2 6;
  opacity: 0.85;
}

/* Static direction arrowhead at the completed route's midpoint — rotated
   per-instance via an inline transform (public/js/components/mapView.js
   computes the bearing), same muted tone as the line it belongs to. */
.map-route-arrow {
  position: absolute;
  display: flex;
  color: var(--color-text-on-ink-muted);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.map-route-arrow__icon {
  width: 16px;
  height: 16px;
}

.leaflet-tooltip.map-tooltip {
  background: var(--color-surface);
  color: var(--color-ink);
  border: none;
  border-radius: var(--radius-chip);
  padding: 8px 12px;
  font-size: 12px;
  line-height: 1.35;
  box-shadow: var(--shadow-1);
  white-space: nowrap;
}

.map-tooltip__title {
  font-weight: 600;
  margin-bottom: 2px;
}

.map-tooltip__row {
  font-family: var(--font-mono);
  color: var(--color-text-charcoal);
  font-size: 11px;
}

/* ==========================================================================
   Controls (Simulate Incident, forms)
   ========================================================================== */

.controls-row {
  display: flex;
  gap: var(--space-1);
  margin: var(--space-1) 0;
  flex-wrap: wrap;
  align-items: center;
}

/* Breathing room below Simulate Incident's header, matching the other
   panel headers (design feedback, live) — bigger than the incidental 8px
   .controls-row already gets from its own top/bottom margin above, which
   exists for spacing between stacked rows, not specifically for clearing
   a header's border-bottom. */
#sim-heading + .controls-row {
  margin-top: var(--space-2);
}

.select,
.input {
  border: 1.5px solid var(--color-ink);
  border-radius: var(--radius-button);
  padding: 8px 18px;
  font-size: 14px;
  font-weight: 450;
  letter-spacing: -0.01em;
  background: var(--color-surface);
  color: var(--color-text);
}

.input::placeholder {
  color: var(--color-text-muted);
}

/* Selects read as a chosen label, not typed text — same weight as a button
   label — and get a custom chevron so no native browser arrow slips in. */
.select {
  appearance: none;
  -webkit-appearance: none;
  font-weight: 500;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23141413' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 10px 6px;
  cursor: pointer;
}
.select:hover {
  background-color: var(--color-canvas);
}

.input--qty {
  width: 5.5rem;
}

.input--grow {
  flex: 1;
  min-width: 200px;
}

/* Joins Hardware Type + drop-to-quantity into one compound control (#40) --
   Site stays its own separate .select in the row above/beside this. Shared
   border comes from collapsing the touching edge (round only the group's
   outer corners, drop the second item's left border) rather than each
   .select/.input keeping its own full independent border. */
.input-group {
  display: flex;
}
.input-group > .select,
.input-group > .input {
  border-radius: 0;
}
.input-group > :first-child {
  border-top-left-radius: var(--radius-button);
  border-bottom-left-radius: var(--radius-button);
}
.input-group > :last-child {
  border-top-right-radius: var(--radius-button);
  border-bottom-right-radius: var(--radius-button);
  border-left: none;
}

/* Anchors Trigger shortage to the panel's bottom-right (#40) instead of
   sitting inline with the selectors. */
.sim-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-2);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  cursor: pointer;
  border-radius: var(--radius-button);
  padding: 8px 20px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  border: 1.5px solid transparent;
  transition: transform 100ms ease, opacity 100ms ease;
}
.btn:active {
  transform: scale(0.97);
}

.btn--primary {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-text-on-ink);
}
.btn--primary:hover {
  opacity: 0.9;
}

.btn--outline {
  background: var(--color-surface);
  border-color: var(--color-ink);
  color: var(--color-ink);
}
.btn--outline:hover {
  background: var(--color-canvas);
}

.btn--signal {
  background: var(--color-signal);
  border-color: var(--color-signal);
  color: #fff;
  font-size: 13px;
  padding: 6px 18px;
  border-radius: var(--radius-signal);
}
.btn--signal:hover {
  opacity: 0.92;
}

.btn--ghost-on-ink {
  background: transparent;
  border-color: var(--color-border-on-ink);
  color: var(--color-text-on-ink);
  padding: 4px 14px;
  font-size: 12px;
}
.btn--ghost-on-ink:hover {
  background: rgba(255, 255, 255, 0.06);
}

/* ==========================================================================
   Transfers list
   ========================================================================== */

.transfer-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Breathing room below the header's border-bottom — without it the
     first row sits flush against the divider (design feedback, live). */
  margin-top: var(--space-2);
}

.transfer-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-button);
  padding: 10px 16px;
  background: var(--color-surface);
  cursor: pointer;
  font-size: 13.5px;
}
.transfer-row:hover {
  background: var(--color-canvas);
}
.transfer-row.is-selected {
  border-color: var(--color-ink);
}

/* Status pill — tone maps to TransferStatus */
.status-pill {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 14px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.status-pill--neutral {
  background: var(--color-canvas);
  color: var(--color-text-muted);
}
.status-pill--info {
  background: rgba(56, 96, 190, 0.12);
  color: var(--color-link);
}
.status-pill--ink {
  background: var(--color-ink);
  color: var(--color-text-on-ink);
}
.status-pill--signal {
  background: rgba(207, 69, 0, 0.12);
  color: var(--color-signal);
}
.status-pill--transit {
  background: rgba(243, 115, 56, 0.16);
  color: var(--color-clay);
}
.status-pill--done {
  background: var(--color-canvas);
  color: var(--color-text-charcoal);
}

/* ==========================================================================
   Transfer Detail
   ========================================================================== */

/* .detail-header now wraps a single full-width child (.detail-header__title)
   instead of splitting title vs. actions itself: the ticket pill, status
   pill, and close button all sit in one row (.detail-header__top-row)
   inside that child, with the summary line below it — so "Completed", the
   close button, and "Transfer #N" all line up together, and the summary is
   free to run its own line underneath. */
.detail-header__title {
  flex: 1 1 auto;
  min-width: 0; /* lets the summary line truncate instead of overflowing */
}

.detail-header__top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Ticket pill — a transfer is a ticket, so its number gets the same pill
   treatment as the status pill beside it, not the section-label .eyebrow
   treatment (no dot, no caret). */
.ticket-pill {
  display: inline-block;
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 13px; /* 1px less than .status-pill's 4px/14px to net out the added border */
  border: 1px solid var(--color-ink);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-ink);
  white-space: nowrap;
}

/* Groups the status pill with the close button (#35) as one flex item —
   with .panel-header--split's justify-content: space-between, a bare third
   child here would float in the middle of the header instead of sitting
   next to the close button on the far end. */
.detail-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

/* Close button (#35) — top-right of the detail header, alongside the status
   pill. Ghost/icon-only so it doesn't compete with the pill for weight;
   .panel-header--split already pushes this trailing group to the far end. */
.detail-header__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: none;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.detail-header__close:hover,
.detail-header__close:focus-visible {
  background: var(--color-canvas);
  color: var(--color-text-charcoal);
}
.detail-header__close .ph-icon {
  width: 16px;
  height: 16px;
}

/* The summary line reuses the exact wording from the transfer row that was
   clicked (transferSummary() in format.js) — same words, larger type,
   rather than a differently-phrased heading for the same transfer. */
.detail-header__summary {
  margin: 4px 0 0;
  font-size: 16px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.detail-block {
  margin-top: var(--space-3);
}

/* Was a bare, unstyled <h3> — now shares the ticket's own panel-header
   language (#33) instead of reading as a fourth, different heading style:
   eyebrow dot + uppercase label, the same border-bottom/background as
   .detail-header above it. --static for the same reason as .detail-header
   (this scroll container already has a sticky header — the Agent Timeline
   console's — and a second sticky element at top:0 would fight it for the
   slot); --flush because .transfer-detail-panel__body supplies its own
   horizontal inset already (#35 — previously .transfers-strip__timeline,
   before Transfer Detail became its own panel). */
.detail-block__title {
  margin: 0 0 8px;
}

/* --- Agent Timeline — the other "media frame" surface, grouped by
       negotiation state instead of one flat scrolling log (#5) ----------- */

.console-frame {
  /* Replaces .detail-header's old margin-bottom, since the header is now a
     .panel-header with no bottom margin of its own (#27). */
  margin-top: var(--space-3);
  background: var(--color-ink);
  border-radius: var(--radius-frame); /* explicit exception (#22) — see tokens.css */
  /* Top/sides moved onto .panel-header--on-ink and #agent-timeline (#27),
     matching the zero-padding-onto-children convention from #23. */
  padding: 0 0 var(--space-3);
  /* No box-shadow (#33) — a floating-card shadow read as a second card
     nested inside the ticket, on top of the ink background already telling
     the "live agent log" apart from the ticket's own chrome. The frame is
     an inset region of the ticket's scroll surface, not a separate raised
     panel, so it shouldn't compete with .panel's shadow for elevation. */
}

.agent-timeline {
  color: var(--color-text-on-ink);
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.55;
  padding: 0 var(--space-3);
  /* No independent scroll region (#14) — this used to nest its own
     overflow-y:auto inside the scroll container above (.transfers-strip__timeline,
     now .transfer-detail-panel__body, #35), producing a scrollbar-inside-a-
     scrollbar. It now grows to full height and relies on the parent as the
     single scroll container for this section. */
  white-space: normal;
}

.timeline-group {
  margin-bottom: 12px;
}
.timeline-group:last-child {
  margin-bottom: 0;
}

.timeline-group__label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.timeline-group__label::-webkit-details-marker {
  display: none;
}
.timeline-group__caret {
  width: 0.7em;
  height: 0.7em;
  transition: transform 0.15s ease;
}
.timeline-group[open] > .timeline-group__label .timeline-group__caret {
  transform: rotate(90deg);
}

.timeline-group__events {
  margin: 8px 0 0 6px;
  padding-left: 14px;
  border-left: 2px solid var(--color-border-on-ink);
}

/* --- Per-turn rendering (#10) --------------------------------------------
   Each turn is one <article class="turn">: a collapsible reasoning block plus
   a domain-specific tool card. Two surface tiers, matching DESIGN.md's
   "paper on paper" idea: tool actions read as subtle data cards on the ink
   frame; emails/replies read as cream "document" paper laid on top. */

.turn {
  margin-bottom: 14px;
}
.turn:last-child {
  margin-bottom: 0;
}

/* When this turn happened (#37) -- one line shared by every tool_name
   variant instead of threading a timestamp through each card renderer. */
.turn__time {
  margin: 0 0 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-on-ink-muted);
}

/* Reasoning — collapsed by default (older turns), latest may be open. */
.turn-thought {
  margin-bottom: 8px;
}
.turn-thought__summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-on-ink-muted);
}
.turn-thought__summary::-webkit-details-marker {
  display: none;
}
.turn-thought__caret {
  width: 0.85em;
  height: 0.85em;
  transition: transform 0.15s ease;
}
.turn-thought[open] > .turn-thought__summary .turn-thought__caret {
  transform: rotate(90deg);
}
.turn-thought__body {
  margin: 6px 0 0;
  padding-left: 18px;
  color: var(--color-text-on-ink-muted);
}

/* Prose rendered from the safe Markdown renderer. Reads better in the display
   face than the mono timeline default. */
.md {
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.5;
}
.md p {
  margin: 0 0 6px;
}
.md p:last-child {
  margin-bottom: 0;
}
.md ul,
.md ol {
  margin: 4px 0 6px;
  padding-left: 20px;
}
.md li {
  margin-bottom: 2px;
}
.md code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  padding: 1px 5px;
  border-radius: var(--radius-chip);
  background: rgba(255, 255, 255, 0.08);
}
.md strong {
  font-weight: 700;
}

/* --- Tool cards (data tier) ---------------------------------------------- */

.tool-card {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--color-border-on-ink);
  border-radius: var(--radius-button);
  padding: 12px 14px;
}
.tool-card__head {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 10px;
}
.tool-card__icon {
  width: 1.05em;
  height: 1.05em;
  color: var(--color-signal-light);
}
.tool-card__title,
.tool-card__tool {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-on-ink-muted);
}
.tool-card__tool {
  color: var(--color-signal-light);
}
.tool-card__note {
  margin: 0;
  font-family: var(--font-display);
  font-size: 12.5px;
  color: var(--color-text-on-ink-muted);
}
.tool-card__error {
  margin: 0;
  font-family: var(--font-display);
  font-size: 12.5px;
  color: #f3a897;
}

/* Key/value grid used by dispatch, zpl, and the generic fallback. */
.kv-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 14px;
}
.kv {
  display: contents;
}
.kv__k {
  color: var(--color-text-on-ink-muted);
  font-size: 11px;
}
.kv__v {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--color-text-on-ink);
  word-break: break-word;
}

/* rank_donors table */
.donor-tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}
.donor-tbl th {
  text-align: left;
  font-weight: 600;
  color: var(--color-text-on-ink-muted);
  padding: 0 10px 6px 0;
  border-bottom: 1px solid var(--color-border-on-ink);
}
.donor-tbl td {
  padding: 5px 10px 5px 0;
  border-bottom: 1px solid rgba(243, 240, 238, 0.08);
}
.donor-tbl tr:last-child td {
  border-bottom: none;
}
.donor-tbl__rank {
  color: var(--color-text-on-ink-muted);
  width: 1.5em;
}
.donor-tbl__num {
  font-family: var(--font-mono);
  text-align: right;
  white-space: nowrap;
}

/* ZPL / generic JSON payload preview */
.code-preview {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-radius: var(--radius-chip);
  background: rgba(0, 0, 0, 0.28);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.45;
  color: var(--color-text-on-ink);
  white-space: pre-wrap;
  overflow-x: auto;
}

/* Compact ticket status line */
.ticket-line {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
}
.ticket-line .tool-card__title {
  margin: 0;
}
.ticket-line__status {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
}
.ticket-line__status--open {
  background: rgba(56, 96, 190, 0.22);
  color: #a9c2f0;
}
.ticket-line__status--closed {
  background: rgba(243, 240, 238, 0.12);
  color: var(--color-text-on-ink-muted);
}
.ticket-line__status--error {
  background: rgba(207, 69, 0, 0.22);
  color: #f3a897;
}
.ticket-line__detail {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-on-ink-muted);
}

/* Terminal outcome ("none" tool step) */
.tool-card--outcome {
  display: flex;
  align-items: center;
  gap: 10px;
}
.tool-card--outcome .tool-card__title {
  margin: 0;
}

/* Generic fallback: result JSON tucked into a <details>. */
.tool-json {
  margin-top: 8px;
}
.tool-json__summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  font-size: 11px;
  color: var(--color-text-on-ink-muted);
}
.tool-json__summary::-webkit-details-marker {
  display: none;
}
.tool-json__caret {
  width: 0.85em;
  height: 0.85em;
  transition: transform 0.15s ease;
}
.tool-json[open] > .tool-json__summary .tool-json__caret {
  transform: rotate(90deg);
}

/* --- Email document (paper tier) ----------------------------------------- */

.email-doc {
  background: var(--color-canvas-lifted);
  color: var(--color-text);
  border-radius: var(--radius-button);
  padding: 14px 16px;
  box-shadow: var(--shadow-1);
}
.email-doc--inbound {
  background: var(--color-canvas);
}
.email-doc__head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: 10px;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--color-border);
}
.email-doc__icon {
  width: 1.2em;
  height: 1.2em;
  margin-top: 1px;
  color: var(--color-text-muted);
}
.email-doc__meta {
  display: grid;
  gap: 1px;
  min-width: 0;
}
.email-doc__row {
  display: flex;
  gap: 8px;
  font-size: 12px;
}
.email-doc__k {
  flex: none;
  width: 3.5em;
  color: var(--color-text-muted);
  font-weight: 600;
}
.email-doc__v {
  color: var(--color-text-charcoal);
  min-width: 0;
  word-break: break-word;
}
/* Groups the badge with its new timestamp (#37) so .email-doc__head's
   3-column grid (icon / meta / this) doesn't need a 4th column. */
.email-doc__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex: none;
}

.email-doc__time {
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.email-doc__badge {
  flex: none;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.email-doc__badge--initial {
  background: var(--color-canvas);
  color: var(--color-text-muted);
}
.email-doc__badge--firmness_lock {
  background: var(--color-ink);
  color: var(--color-text-on-ink);
}
.email-doc__badge--escalation {
  background: rgba(207, 69, 0, 0.14);
  color: var(--color-signal);
}
.email-doc__badge--inbound {
  background: rgba(56, 96, 190, 0.12);
  color: var(--color-link);
}
.email-doc__body {
  color: var(--color-text-charcoal);
}
/* Inline code on the cream email paper needs a dark-on-light chip, not the
   white-on-ink one used inside the dark timeline frame. */
.email-doc__body.md code {
  background: rgba(20, 20, 19, 0.06);
}
.email-doc__note {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--color-signal);
}

/* ==========================================================================
   Toasts — transient completion callouts over the map (#13)
   ========================================================================== */

.toast-root {
  position: fixed;
  top: var(--space-3);
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  background: var(--color-ink);
  color: var(--color-text-on-ink);
  border: 1px solid var(--color-border-on-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-2);
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 220ms ease, transform 220ms ease;
}

.toast--visible {
  opacity: 1;
  transform: translateY(0);
}

.toast__icon {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--color-signal-light);
}

.toast__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 13px;
  line-height: 1.3;
}

.toast__body strong {
  font-weight: 600;
}

.toast__body span {
  color: var(--color-text-on-ink-muted);
  font-size: 12px;
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Two side-by-side rails need room to spare on either edge of the map. Below
   that, the map stays a fixed ambient backdrop and the shell becomes a single
   scrollable column of frosted panels stacked over it (#12). */
@media (max-width: 1023px) {
  .app-shell {
    position: relative; /* scroll in flow, but paint above the fixed map */
    z-index: 1;
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    max-width: 640px;
    margin: 0 auto;
    padding: var(--space-3);
  }

  .nav-pill {
    position: static;
  }

  .left-rail {
    position: static;
    width: auto !important; /* overrides the drag-resize inline style (#26) */
    top: auto;
    bottom: auto;
    pointer-events: auto;
  }
  /* The rail is already full-width in the stacked mobile/tablet layout, so
     the resize handle has nothing to do (#16, #26) — same for its grab tabs
     (#40). */
  .left-rail__resize-handle,
  .rail-grab-tab {
    display: none;
  }
  /* Both panels are content-sized here, not fenced to a share of the fixed-
     height desktop rail (#35) — the second selector also overrides the
     fill-available rule that applies when Transfer Detail is closed. */
  .transfers-strip,
  .left-rail:has(> .transfer-detail-panel[hidden]) .transfers-strip {
    flex: none;
    max-height: 240px;
  }
  .transfer-detail-panel {
    flex: none;
  }
  .transfer-detail-panel__body {
    overflow: visible;
  }

  .side-rail {
    position: static;
    width: auto;
    max-height: none;
    pointer-events: auto;
  }
  .floating-panel--network__body {
    overflow: visible;
  }
}

@media (max-width: 599px) {
  .site-cards {
    grid-template-columns: 1fr;
  }
  /* Full-width bar (#21), not the pill shape — stack brand above status
     instead of squeezing both into one row at this width. */
  .nav-pill {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--space-2);
  }
}
