:root {
  --bg: #F7F9FE;
  --ink: #0A0E1A;
  --muted: #5B6578;
  --faint: #8A94A6;
  --line: #E8EDF6;
  --line-strong: #D6DEEE;
  --blue: #0B5CFF;
  --blue-hover: #0A4FE0;
  --blue-soft: #EAF1FF;
  --green-bg: #E7F6EE; --green-ink: #0E8345;
  --red-bg: #FDEBEB;   --red-ink: #C0362C;
  --amber-bg: #FEF3C7; --amber-ink: #B45309;
  --track: #EEF1F6;
  --card-shadow: 0 1px 3px rgba(16, 24, 40, .06);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
/* `min-height`, not `height`: a fixed 100% caps the body box at one viewport,
   and a sticky child cannot travel past its container — which is why `.topbar`,
   sticky since this file was written, never actually stuck on a long page. Same
   fix as admin.html. */
html, body { min-height: 100%; }
body {
  font-family: Inter, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--ink);
  font-size: 14px;
  /* No `min-width: 1024px` here. It was the whole of QA 386 #6: the cabinet was
     built at one width, so a phone or a half-screen window scrolled sideways —
     most absurdly on the sign-in card, which is a 420px box that fits anywhere.
     Every wide thing that remains carries its own scroll (`.tablewrap`) or
     collapses at the breakpoints at the end of this stylesheet. */
}
button { font: inherit; cursor: pointer; }
a { color: var(--blue); text-decoration: none; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  border-radius: 9999px; padding: 9px 18px;
  font-weight: 600; font-size: 13px; line-height: 1;
  border: 1px solid transparent; transition: background .15s, border-color .15s;
}
.btn--primary { background: var(--blue); color: #fff; }
.btn--primary:hover { background: var(--blue-hover); }
.btn--secondary { background: #fff; border-color: var(--line-strong); color: var(--ink); }
.btn--secondary:hover { border-color: #B9C6DE; }
.btn--dark { background: var(--ink); color: #fff; }
.btn--dark:hover { background: #1B2233; }
.btn--danger-ghost { background: #fff; border-color: #F2C6C2; color: var(--red-ink); }
.btn--danger-ghost:hover { background: var(--red-bg); }
.btn:disabled { opacity: .5; cursor: default; }
.btn--sm { padding: 6px 14px; font-size: 12px; }

/* ---------- Top bar ---------- */
.topbar {
  height: 64px; display: flex; align-items: center; gap: 24px;
  padding: 0 28px;
  background: linear-gradient(180deg, #EAF1FF 0%, #FFFFFF 100%);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 30;
}
.brand { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
.brand .word { font-weight: 700; font-size: 17px; letter-spacing: -.2px; }
.brand .suffix { font-weight: 300; font-size: 17px; color: var(--muted); letter-spacing: -.2px; }
.topbar .spacer { flex: 1; }
/* Entity picker — shown ONLY when the account has more than one served entity
   (see renderEntitySwitch). With one entity there is nothing to switch between
   and a lone dropdown reads as a setting the user is supposed to change. */
.entity-switch { display: flex; align-items: center; gap: 8px; margin-right: 12px; }
.entity-switch label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 600; }
.entity-switch select {
  font: inherit; font-weight: 600; font-size: 13px; color: var(--ink);
  background: #fff; border: 1px solid var(--line-strong); border-radius: 9999px;
  padding: 7px 14px; outline: none; max-width: 280px; cursor: pointer;
}
.entity-switch select:hover { border-color: var(--faint); }
.entity-switch select:focus { outline: 2px solid var(--blue-soft); border-color: var(--blue); }
.usermenu { position: relative; }
.userchip {
  display: flex; align-items: center; gap: 10px;
  background: #fff; border: 1px solid var(--line-strong); border-radius: 9999px;
  padding: 5px 12px 5px 5px; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--ink); cursor: pointer;
}
.userchip:hover { border-color: var(--faint); }
.userchip .avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--blue-soft); color: var(--blue);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; flex-shrink: 0;
}
.userchip .caret { width: 12px; height: 12px; color: var(--faint); flex-shrink: 0; }
.usermenu-pop {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40;
  min-width: 240px; background: #fff; border: 1px solid var(--line-strong);
  border-radius: 12px; box-shadow: 0 12px 32px rgba(10, 14, 26, .12);
  padding: 6px; overflow: hidden;
}
.usermenu-id { padding: 10px 12px 12px; border-bottom: 1px solid var(--line); margin-bottom: 6px; }
.usermenu-name { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.usermenu-email { font-size: 12.5px; color: var(--muted); margin-top: 2px; word-break: break-all; }
.usermenu-role { font-size: 11px; color: var(--faint); margin-top: 4px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
.usermenu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border: 0; background: none; border-radius: 8px;
  font: inherit; font-size: 13.5px; font-weight: 500; color: var(--muted); text-align: left; cursor: pointer;
  text-decoration: none;
}
.usermenu-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.usermenu-item:hover { background: var(--bg); color: var(--ink); }

/* ---------- Settings tabs ---------- */
/* Settings content reads as a form column — cap it so cards don't sprawl. */
.settings-tabs, #settingsBody { max-width: 760px; }
/* ...except the VASP profile tab, which is two cards side by side and needs the
   whole content column. The selector carries the ID on purpose: the cap above is
   set through `#settingsBody`, so a bare class would lose the specificity
   contest and the tab would silently stay 760px wide. */
#settingsBody.stab-body--entity { max-width: none; }
/* Editable card left, read-only Entity details right. `minmax(0, 1fr)` twice so
   a long TRP host or website cannot push a track wider than its half, and
   `align-items: start` so the shorter card keeps its own height instead of
   stretching to match the taller one. No cap on the columns themselves —
   reading width is bounded on the prose inside the cards, see the `.main`
   comment above. */
.entity-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; }
.entity-cols > * { min-width: 0; }
.settings-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 24px; }
.stab {
  border: 0; background: none; font: inherit; font-size: 13.5px; font-weight: 500;
  color: var(--muted); padding: 10px 14px; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.stab:hover { color: var(--ink); }
.stab.active { color: var(--blue); border-bottom-color: var(--blue); font-weight: 600; }

/* ---------- Secret reveal (API keys) ----------
   What is left of the old two-factor CSS block: the API-keys tab shows a
   just-minted `trk_live_…` once, in the same box the TOTP secret used to use.
   The `.tfa-*` / `.qr-box` / `.code-input` rules went with the 2FA surface. */
.secret-box {
  display: flex; align-items: center; gap: 10px; margin-top: 6px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
}
.secret-box code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px; letter-spacing: .08em; word-break: break-all; color: var(--ink); }

/* ---------- Entity profile (Settings › VASP profile) ---------- */
/* A missing optional field is NORMAL — an LEI routinely arrives weeks after
   onboarding — so the completeness prompt is quiet amber, never a red error.
   Colouring it as a failure would teach customers to ignore real failures. */
.nudge { font-size: 11.5px; font-weight: 600; color: var(--amber-ink); }
.field-note { font-size: 11.5px; color: var(--faint); line-height: 1.45; max-width: 340px; }
/* Opt out of the 340px prose cap where the note IS the full-width caption of
   the card above it, not a column beside a control (QA 2026-09-18 #1). */
.field-note--wide { max-width: none; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; padding: 16px 20px 20px; }
.form-grid .field { min-width: 0; }
.form-grid .field--wide { grid-column: 1 / -1; }
.form-foot { display: flex; align-items: center; gap: 12px; padding: 0 20px 20px; }

/* ---------- API keys (Settings) ---------- */
.notice--ok { background: var(--green-bg); color: var(--green-ink); }
.docs-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  background: var(--blue-soft); border: 1px solid #D3E2FF; border-radius: 12px;
  padding: 14px 18px; margin-bottom: 20px;
}
.docs-bar b { font-size: 13.5px; }
.docs-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ---------- Shell ---------- */
.shell { display: flex; min-height: calc(100vh - 64px); }
.sidebar {
  width: 232px; flex-shrink: 0; background: #fff;
  border-right: 1px solid var(--line); padding: 20px 12px;
  /* Column + `margin-top:auto` on the foot pins Log out to the bottom-left
     without a fixed height. */
  display: flex; flex-direction: column;
  /* STICKY (QA, 2026-08-28 #3) — the same fix the admin panel got in QA 387 #1,
     for the same reason: pinned to the bottom of a column as tall as the page,
     "bottom" is wherever the longest table ends, so on Transfers or Audit the
     nav and Log out were thousands of pixels down. Bounding the rail to the
     viewport under the 64px topbar keeps both always where they were.
     `overflow-y: auto` is the escape hatch for a short window; it needs the
     `html, body` rule above to use min-height rather than height 100%, or the
     body box is capped at one viewport and a sticky child cannot travel. */
  position: sticky; top: 64px; align-self: flex-start;
  height: calc(100vh - 64px); overflow-y: auto;
}
/* `.section-label` is GONE from the markup with the "Compliance" heading (QA,
   2026-08-28 #6) — it labelled a group of five items that is the entire menu,
   so it named nothing. The rule stays because the 1023px strip layout still
   hides any label a future group adds, and deleting it would only move the
   question. */
.sidebar .section-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--faint); font-weight: 600; padding: 8px 12px 6px;
}
.sidebar-foot { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line); }
/* `.nav-item` is styled for <button>; the foot entry is an <a> because it is a
   real navigation to /sso/logout, so it needs the underline reset. */
.sidebar-foot .nav-item { text-decoration: none; margin-bottom: 0; }
.sidebar-foot .nav-item:hover { background: var(--bg); color: var(--red-ink, #C0392B); }
.nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; margin-bottom: 2px;
  border: 0; background: none; border-radius: 10px;
  color: var(--muted); font-weight: 500; font-size: 13.5px; text-align: left;
}
.nav-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.nav-item:hover { background: var(--bg); color: var(--ink); }
.nav-item.active { background: var(--blue-soft); color: var(--blue); font-weight: 600; }

/* No `max-width` and no `margin-inline: auto` (QA, 2026-08-28 #1). The cabinet
   used to cap the content column at 1240px and centre it, which on a zoomed-out
   or ultrawide window drew the whole product in a band down the middle with dead
   space on both sides. The admin panel lost the same cap in QA 388 round 3 #3 and
   this is the cabinet's turn; the two now behave identically.
   Reading width is still bounded where it belongs — on the PROSE (`.empty p`,
   `.field-note`, `.settings-tabs`/`#settingsBody`) — rather than on the frame, so
   tables and KPI grids use the window they are given and paragraphs never run to
   200 characters. The signed-out card is unaffected: `.signin-wrap` centres it
   with `justify-content: center`, which never depended on this cap. */
.main { flex: 1; padding: 28px 32px 48px; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 20px; gap: 16px; }
.page-head h1 { font-size: 22px; font-weight: 700; letter-spacing: -.3px; }
.page-head p { color: var(--muted); margin-top: 4px; font-size: 13.5px; }

/* ---------- Cards / KPIs ---------- */
.card {
  background: #fff; border: 1px solid var(--line);
  border-radius: 16px; box-shadow: var(--card-shadow);
}
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.stat { padding: 20px; }
.stat .label { font-size: 12px; font-weight: 600; color: var(--muted); }
.stat .value { font-size: 28px; font-weight: 700; letter-spacing: -.5px; margin-top: 8px; }
.stat .sub { font-size: 12px; color: var(--faint); margin-top: 6px; }
.stat .sub.good { color: var(--green-ink); }
.stat .sub.bad { color: var(--red-ink); }

/* ---------- Dashboard modules ---------- */
/* THREE columns and TWO floors (QA 2026-09-09), not a 12-column autoflow. The
   wide Transfers card occupies the left half over both floors; the grade and
   reachability cards sit side by side on the first floor of the right half, and
   the volume chart spans the width of both of them on the second.

   The `dc-*` classes are now PLACEMENTS, not spans — a card is named, and the
   grid says where that name goes. Adding a fifth card means deciding which
   column and which floor it belongs on, on purpose.

   Below the breakpoint every card is a full-width row in DOM order, and the
   explicit placement is simply not applied. */
.dash-grid { display: grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 24px; }
.dash-grid .card { min-width: 0; }
@media (min-width: 1080px) {
  .dash-grid {
    /* Left half, then the right half split ~1:2 — the grade card is a ring and
       a pill and needs no more, the reachability card carries four labelled
       tracks and needs the rest.

       The grade card's minimum is set by its LABEL, not by its ring: at 190px
       "EBA GL/2024/11 grade" plus the help icon was 9px too wide, wrapped to two
       lines and made the card 14px taller than the reachability card beside it —
       which is that card's remaining slack, since row 1 is as tall as the taller
       of the two (QA 2026-09-24 #6.1, measured in a browser). 208px is the label
       on one line plus the card's 20px padding either side, with a few pixels of
       headroom for a font that renders wider. It comes out of the reachability
       card's track, which only makes its bars fractionally shorter. */
    grid-template-columns: minmax(0, 1fr) minmax(208px, 0.33fr) minmax(0, 0.67fr);
    /* Floor 1 is content-sized; floor 2 takes whatever is left, so a Transfers
       card taller than the two above lengthens the CHART rather than opening a
       gap under it. */
    grid-template-rows: auto 1fr;
  }
  .dc-transfers { grid-column: 1; grid-row: 1 / span 2; }
  .dc-grade { grid-column: 2; grid-row: 1; }
  .dc-reach { grid-column: 3; grid-row: 1; }
  .dc-volume {
    grid-column: 2 / span 2; grid-row: 2;
    /* QA 2026-09-24 #6.3 — the chart takes the height the grade card gave up.
       It does NOT get it for free: floor 2 is `1fr`, but `1fr` only distributes
       FREE space, and this grid has none. Its height is floor 1 + this card's
       own content, and the Transfers card spanning both rows is shorter than
       that (394px against 443px), so it anchors nothing — shrinking floor 1 just
       made the whole dashboard 69px shorter and left the plot at 136px, measured
       in a browser.

       So the card asks for the space. 328px is its content height (259px) plus
       exactly the 69px the grade card released, which puts the dashboard back at
       the height it had and gives every one of those pixels to the plot —
       `.vol` is `flex: 1 1 auto` inside a `height: 100%` `.mod`, so the bars
       absorb it and nothing else moves.

       Inside the breakpoint only: below it every card is a full-width row in DOM
       order and a floor of this size would be a tall empty box on a phone. */
    min-height: 328px;
  }
}
.mod { padding: 20px; display: flex; flex-direction: column; gap: 16px; height: 100%; }
.mod-label { flex: 0 0 auto; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--faint); font-weight: 600; }
.mod-help { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; margin-left: 6px; border-radius: 50%; border: 1px solid var(--line); color: var(--faint); font-size: 10px; font-weight: 700; line-height: 1; text-transform: none; letter-spacing: 0; text-decoration: none; cursor: help; vertical-align: middle; }
.mod-help:hover { border-color: var(--faint); color: var(--muted); }
.mod-big { font-size: 24px; font-weight: 700; letter-spacing: -.5px; line-height: 1; }
.tnum { font-variant-numeric: tabular-nums; }

/* colour dots (shared) */
.dot--blue { background: var(--blue); }
.dot--green { background: var(--green-ink); }
.dot--amber { background: var(--amber-ink); }
.dot--red { background: var(--red-ink); }
.dot--gray { background: var(--faint); }

/* split rows (transfers + response modules) */
.split { display: flex; flex-direction: column; gap: 11px; }
.split-row { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.split-row .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.split-row .k { color: var(--muted); }
.split-row .v { margin-left: auto; font-weight: 700; color: var(--ink); }

/* grade module */
/* Vertical: ring over the delta (QA 2026-09-09). `min-height: 0` so that when
   the card is squeezed it is THIS block that gives, never the `.mod-label`
   above it — a flex item's default `min-height: auto` refuses to shrink and the
   header is what gets pushed out of the padding box instead. `.mod-label`
   carries `flex: 0 0 auto` for the same reason. */
.grade-wrap {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; min-height: 0; flex: 1 1 auto;
}
/* The delta pill and its "vs prior 7d" caption on ONE line (QA 2026-09-24 #6.2).
   Stacked, the caption cost this card ~19px of height — measured in a browser —
   and that was most of what made it taller than *Counterparty reachability*
   beside it, which is what left the slack under that card's last bar. Read
   across, "0pp · vs prior 7d" is the same sentence it was reading down. */
.grade-delta { display: flex; align-items: baseline; justify-content: center; gap: 7px; }
/* SIZED IN CSS, not by the `width`/`height` attributes on the `<svg>` (QA
   2026-09-24 #6.2). The viewBox stays 96×96, so the stroke, the arc arithmetic
   and the centred text scale with it and `ringGauge` needs no second set of
   numbers — the percentage label lands at ~14px on screen from its 21px user
   units.

   WHY IT SHRANK. This card and *Counterparty reachability* share row 1 of
   `.dash-grid`, so the row is as tall as the taller of the two and the shorter
   one stretches. At 96px the grade card was ~45px taller than the four
   reachability bars, which showed up as slack under the last bar — QA's arrow.
   The two are within a few pixels of each other now, which also hands those
   pixels to the volume chart on floor 2 (#6.3): that row is `1fr` under a
   Transfers card spanning both, so whatever row 1 gives up, the chart takes. */
.ring { flex-shrink: 0; width: 64px; height: 64px; }
.ring-txt { font-size: 21px; font-weight: 700; fill: var(--ink); }
.delta-pill { display: inline-flex; align-items: center; gap: 4px; border-radius: 9999px; padding: 4px 11px; font-size: 12px; font-weight: 700; }
.delta--up { background: var(--green-bg); color: var(--green-ink); }
.delta--down { background: var(--red-bg); color: var(--red-ink); }
.delta--flat { background: var(--track); color: var(--muted); }
.delta-note { font-size: 11px; color: var(--faint); }

/* The `.resp-head` and `.stack` rules are DELETED with the *Counterparty
   response* and *Transfer states* cards, which QA 2026-09-08 #1 merged into the
   Transfers card below. `.legend`/`.legend-item` survive: the volume card's
   direction legend uses them. */
.legend { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.legend-item .dot { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.legend-item b { color: var(--ink); font-weight: 700; }

/* ---- the merged Transfers card (QA 2026-09-08 #1) ---- */

/* Shares `.tx-grid`'s columns (see the markup) so each heading sits over the
   column it names; overrides only the rules that separate the two blocks. */
.tx-head { border-top: 0; padding-top: 0; text-align: center; }
/* The card's title, occupying the head row's first cell — over the legend
   column, on the baseline of the three column headings rather than a row of its
   own above them (QA 2026-09-09). Left-aligned against `.tx-head`'s `center`. */
.tx-title { text-align: left; align-self: start; padding-top: 1px; }
.tx-head-col { min-width: 0; }
.tx-head-k {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); margin-bottom: 10px;
}
.tx-head-k .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
/* Reserves the line's height even when empty (the Total column has no share),
   so the three big numbers keep one baseline. */
.tx-head-sub { font-size: 12px; color: var(--faint); margin-top: 7px; min-height: 1.2em; }

.tx-grid {
  /* One row height, shared by the legend column, the three value columns and
     the bars. Fixed rather than content-driven because four independent columns
     have to line up row for row, and the bar spans all seven rows so it cannot
     be a grid item alongside them. */
  --tx-row: 34px;
  /* Tighter than it was on both axes (QA 2026-09-09): the value columns were
     wide enough that a two-digit count sat a long way from the bar it belongs
     to, which read as empty space rather than as a column. */
  display: grid; grid-template-columns: minmax(112px, 1fr) repeat(3, minmax(76px, 0.78fr));
  gap: 0 10px; border-top: 1px solid var(--line); padding-top: 14px;
}
/* The card is as tall as the three on the right added together — that is the
   layout QA asked for, and this card's seven rows may well not need all of it.
   The block takes the slack (`flex: 1`) and the seven rows SHARE it — one
   `flex: 1 1 auto` used by the legend rows and the value cells alike, so the two
   sides cannot spread differently and stay aligned row for row. `--tx-row` is
   the floor, for the narrow-viewport case where the card is content-sized.

   Do NOT cap the row height instead and leave the slack at the bottom: the bar
   is a stretched flex sibling of the value block, so a capped row block leaves
   it hanging below the last number. Both were built and looked at. */
.mod > .tx-grid:not(.tx-head) { flex: 1 1 auto; }
.tx-legend, .tx-vals { display: flex; flex-direction: column; }
.tx-row, .tx-cell { flex: 1 1 auto; min-height: var(--tx-row); }
.tx-legend { min-width: 0; }
.tx-row { display: flex; align-items: center; gap: 10px; }
.tx-row .dot { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.tx-k {
  /* `stateLabel` yields `inquiry received`, so the first letter is lifted and
     the rest left alone: `capitalize` would render `Inquiry Received`, which is
     neither the mockup nor how the state badges spell it. */
  font-size: 13px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tx-k::first-letter { text-transform: uppercase; }

/* bar + values side by side, the values carrying the row rhythm.
   `center` and a non-growing value block on purpose: let `.tx-vals` grow and the
   right-aligned numbers travel to the far edge of a wide column, leaving the bar
   they belong to stranded on the other side of a gap. */
.tx-col { display: flex; align-items: stretch; justify-content: center; gap: 8px; min-width: 0; }
.tx-bar {
  width: 10px; flex: 0 0 10px; border-radius: 9999px; overflow: hidden;
  /* Not flush with the card's padding box: stretched to the row block's full
     height the rail ended exactly on the bottom edge and read as clipped. */
  margin-bottom: 8px;
  /* The empty rail, visible when the column has no transfers at all — which is
     what the mockup shows for an entity with nothing incoming. A blank gap
     would read as a missing chart rather than an empty one. */
  background: var(--track); box-shadow: inset 0 0 0 1px var(--line);
  display: flex; flex-direction: column;
}
.tx-bar span { display: block; width: 100%; }
.tx-vals { flex: 0 1 auto; min-width: 68px; }
.tx-cell { display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
/* Smaller than they were, and the share smaller and quieter than the count it
   qualifies (QA 2026-09-09) — the count is the figure, the share is the gloss. */
.tx-n { font-size: 14.5px; font-weight: 700; color: var(--ink); }
.tx-p { font-size: 11px; font-weight: 500; color: var(--faint); opacity: .85; min-width: 27px; text-align: right; }
/* The row's state colour, carried to the number (QA 2026-09-09). Square-ish,
   like the legend dot it repeats and unlike the round direction dots in the
   head. It sits in the right-aligned group WITH the number rather than pinned to
   the far side of the cell: the point is to colour the figure, and a dot parked
   against the bar would just be the bar's own colour again. */
.tx-dot { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }
/* A state that cannot occur in this direction. Its own class so it is a
   deliberate absence rather than a zero that happens to look like one. */
.tx-na { color: var(--line); font-size: 15px; }

/* The seven-state palette. Read by BOTH the legend dot and the bar segment via
   `stateTone()` in views/dashboard.js — see the comment there for why this is
   not `badgeClass`, which groups seven states into four colours and would make
   the bar unreadable. */
.t-inquiry-received { background: #7FA8FF; }
.t-inquiry-sent { background: var(--blue); }
.t-approved { background: #6EE7B7; }
.t-rejected { background: var(--red-ink); }
.t-confirmed { background: var(--green-ink); }
.t-canceled { background: var(--faint); }
.t-expired { background: var(--amber-ink); }

/* Below the KPI breakpoint the four columns cannot all fit: the legend keeps its
   own column and the three value columns share the remaining width. */
@media (max-width: 700px) {
  .tx-grid { grid-template-columns: minmax(84px, 1.2fr) repeat(3, minmax(56px, 1fr)); gap: 0 8px; }
  /* The title cannot share the head row here — an 84px cell would break
     "Transfers · all time" over three lines beside the figures — so it takes a
     row of its own, and the three headings are pinned back to columns 2-4 so
     they still sit over the body columns they name. */
  .tx-title { grid-column: 1 / -1; margin-bottom: 8px; }
  /* `:nth-child(2)` and NOT `:first-of-type`: the title is a div too, so
     `.tx-head-col:first-of-type` matches nothing and the headings stay in
     columns 1-3, one column left of the values they head. */
  .tx-head > .tx-head-col:nth-child(2) { grid-column: 2; }
  .tx-col { gap: 6px; }
  .tx-bar { width: 8px; flex: 0 0 8px; }
  .tx-n { font-size: 13.5px; }
  /* The share and the state dot both go: at this width the count alone is what
     fits, and the legend row is a few pixels to the left of it anyway. */
  .tx-p, .tx-dot { display: none; }
}

/* volume bars — stacked and split by direction (QA 2026-09-08 #2) */
.mod-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.mod-legend { display: flex; gap: 14px; }
/* Taller plot, narrower bars (QA 2026-09-09). The plot grows with the card —
   `flex: 1` here, `flex: 1` on `.vol-plot`, percentage bar heights — so the
   tallest day always reaches the top of the plot and no headroom is left above
   it, whatever the card's height turns out to be. `min-height` is a floor for a
   short card, not the chart's size. */
.vol { display: flex; align-items: stretch; gap: 8px; flex: 1 1 auto; min-height: 186px; }
.vol-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 4px; min-width: 0; }
/* The plot area of one column: everything above the weekday and below the
   captions. The bar's percentage height resolves against THIS box. */
.vol-plot { flex: 1 1 auto; width: 100%; display: flex; align-items: flex-end; justify-content: center; }
.vol-cap { font-size: 11px; color: var(--muted); font-weight: 600; }
/* The `N out · M in` line. `nowrap` on purpose: wrapped to two lines it would
   push the bars of the busy days down and desynchronise the baseline. */
.vol-split { font-size: 10px; color: var(--faint); white-space: nowrap; margin-bottom: 2px; }
.vol-bar {
  width: 100%; max-width: 18px; border-radius: 5px 5px 0 0;
  /* The floor a zero day lands on: `height: 0` plus this is the "minimum
     visible stub" QA asked for, and it also keeps a 1-of-40 day from vanishing. */
  min-height: 4px;
  /* No background of its own any more: a zero day renders `height: 0` and the
     two segments fill the whole bar between them, so a base colour would only
     show through as a rounding artefact. */
  overflow: hidden; display: flex; flex-direction: column;
}
.vol-bar span { display: block; width: 100%; }
/* A day with no transfers: a few visible pixels of empty rail rather than
   nothing at all (QA 2026-09-09). It draws no segments — see the assertion in
   appUiCabinetDashboard.test.ts — so the colour has to be on the bar itself.
   A gap under the caption reads as a broken chart; a stub reads as a quiet day. */
.vol-bar--empty { background: var(--track); box-shadow: inset 0 0 0 1px var(--line); }
.vol-bar .v-in { background: var(--green-ink); }
.vol-bar .v-out { background: var(--blue); }
.vol-day { font-size: 10.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .04em; }

/* reachability rows */
/* Tighter rows and a narrower label column (QA 2026-09-09): the card is one of
   the narrow right-hand pair now, and its height hugs the four rows — the slack
   that used to sit under the last one is gone with the full-width row it lived
   in. */
.reach { display: flex; flex-direction: column; gap: 12px; }
.reach-row { display: grid; grid-template-columns: minmax(96px, auto) minmax(40px, 1fr) auto; align-items: center; gap: 10px; }
.reach-row .rk { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.reach-track { height: 10px; border-radius: 9999px; background: var(--track); overflow: hidden; }
.reach-fill { display: block; height: 100%; border-radius: 9999px; min-width: 0; }
.reach-fill.f-protocol { background: var(--blue); }
/* `.f-fallback` went with the "Email fallback" row it coloured (QA
   cabinet_dash #3): a delivered email is a success, so it is green, and a failed
   send is its own red row rather than being folded in amber with it. */
.reach-fill.f-sent { background: var(--green-ink); }
.reach-fill.f-failed { background: var(--red-ink); }
.reach-fill.f-hold { background: var(--faint); }
.reach-row .rv { font-size: 12.5px; font-weight: 700; color: var(--ink); text-align: right; min-width: 28px; }
/* `.reach-note` is DELETED with the sentence it styled: the tile drew one
   instead of four zero bars when nothing had been measured, and QA 2026-09-08
   #4.2 asked for the bars in every case. */

/* The `.att*` rules for the Needs-attention card are deleted with the card (QA
   cabinet_dash #4). `.att-chip` further down this file is an unrelated name
   collision (comment attachments) and stays. */

/* ---------- Badges ---------- */
.badge {
  display: inline-flex; align-items: center;
  border-radius: 9999px; padding: 3px 10px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.badge--green { background: var(--green-bg); color: var(--green-ink); }
.badge--red { background: var(--red-bg); color: var(--red-ink); }
.badge--blue { background: var(--blue-soft); color: var(--blue); }
.badge--gray { background: #EEF1F6; color: var(--muted); }

/* ---------- Delivery badge (QA 2026-09-09 #1.1 / #1.2) ----------
   What happened to the Travel Rule MESSAGE, as opposed to the transfer's state.
   QA asked for the two to be visually distinguishable, so this is deliberately
   NOT a second `.badge`: it is smaller, OUTLINED rather than filled, and carries
   a leading dot. Two filled pills side by side read as two states. */
.statecell { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.dlv {
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: 6px; padding: 1px 7px 1px 6px;
  font-size: 10.5px; font-weight: 600; white-space: nowrap;
  background: #fff; border: 1px solid var(--line-strong); color: var(--muted);
}
.dlv-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
/* Tone from the OUTCOME, never from `badgeClass` — that maps transfer states,
   and borrowing its palette would claim a delivery outcome is one. The two
   collect-and-hold cases are amber together: nothing failed, but nobody was
   asked. */
.dlv--green { border-color: var(--green-ink); color: var(--green-ink); }
.dlv--red { border-color: var(--red-ink); color: var(--red-ink); }
.dlv--amber { border-color: var(--amber-ink); color: var(--amber-ink); }
.dlv--blue { border-color: var(--blue); color: var(--blue); }
.dlv--gray { border-color: var(--line-strong); color: var(--muted); }
/* In the detail header the badge sits BESIDE the state, in a flex title row, so
   it needs no column wrapper — only vertical alignment with the taller pill. */
.detail-title-row .dlv { align-self: center; }

/* ---------- Chips ---------- */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.chip {
  border-radius: 9999px; padding: 7px 14px; font-size: 12.5px; font-weight: 600;
  background: #fff; border: 1px solid var(--line-strong); color: var(--muted);
}
.chip:hover { color: var(--ink); }
.chip.active { background: var(--ink); border-color: var(--ink); color: #fff; }

/* ---------- Table ---------- */
.tablewrap { overflow-x: auto; }
/* The Blockchain filter's popover lives in a `<th>` INSIDE this scroll
   container, and `overflow-x: auto` clips both axes — so on a short table the
   list was cut off at the bottom of the table and the wrapper grew a scrollbar
   to reach it. `wireTypeahead` toggles this class for exactly as long as the
   popover is open. The cost is that a wide table cannot be scrolled sideways
   during those few seconds, which is self-healing; the alternative is a
   JS-positioned `position: fixed` popover, i.e. a second layout engine to keep
   in step with the cell. */
.tablewrap.tw-pop { overflow: visible; }

/* ---------- Pager ---------- */
/* Deliberately identical to `ui/admin.css`'s block, for the same reason
   `ui/app/pager.js` is a second copy of `ui/admin/pager.js`: the two stylesheets
   and the two module graphs are served by separate routes (the admin one gated)
   and neither may reach into the other. If these two drift, one of them is a bug.
   The pager sits INSIDE the card, directly after `.tablewrap`, so `border-top` is
   the separator between the table and the controls. */
.pager {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 16px; border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--muted);
}
/* The pager is rendered at both ends of the transfers list (QA 2026-09-09 #2).
   The top copy separates itself from the table BELOW it, so the rule moves to
   the other edge — otherwise it draws a line against the card's own top border.
   Byte-identical to `ui/admin.css`'s block, which shipped with the audit log's
   double pager: two stylesheets served by separate routes (that one gated) and
   neither may import from the other. */
.pager--top { border-top: 0; border-bottom: 1px solid var(--line); }
.pager .spacer { flex: 1; }
.pager .pageno { font-variant-numeric: tabular-nums; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.tbl th {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--faint); font-weight: 600; text-align: left;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
}
table.tbl td { padding: 13px 16px; border-bottom: 1px solid var(--line); vertical-align: middle; }

/* ---------- The Transfers filter row (QA cabinet_txs #2) ----------
   It is the FIRST ROW OF THE TABLE HEAD, so the browser's own column algorithm
   keeps every control over the column it filters at every width — see the long
   note on `filterHeadRow`. Consequences that need styling:
     - these `th`s hold controls, not labels, so they lose the uppercase
       letter-spaced treatment and the bottom border (the label row below carries
       it, and two rules would draw a line through the middle of the head);
     - `overflow: visible` is required for the Blockchain typeahead's popover,
       which is absolutely positioned and would otherwise be clipped by the cell.
   The controls are sized in `%` so a filter can never be the thing that widens
   its column past the data — QA's "optimise if the filter is wider than the
   column". */
table.tbl thead tr.filter-head th {
  padding: 10px 12px 4px; border-bottom: 0; text-transform: none;
  letter-spacing: 0; font-weight: 500; overflow: visible;
  /* TOP, not the default middle. Every cell is a stack of labelled fields and
     two of them hold two fields, so middle alignment centred the single-field
     cells against the taller ones and left five controls lining up with nothing
     (seen in a browser — jsdom does no layout). Top-aligned, the first field of
     every cell sits on one line. */
  vertical-align: top;
}
table.tbl thead tr.filter-head input,
table.tbl thead tr.filter-head select {
  width: 100%; min-width: 0; max-width: 100%;
  font: inherit; font-size: 12.5px; color: var(--ink);
  padding: 6px 8px; border: 1px solid var(--line-strong); border-radius: 8px;
  background: #fff;
}
table.tbl thead tr.filter-head input:focus,
table.tbl thead tr.filter-head select:focus { outline: 2px solid var(--blue-soft); border-color: var(--blue); }
/* Every filter is a labelled field in a stack (QA 2026-09-09 #1.3). The State
   column holds two — State and Delivery — and so does Updated; the rest hold
   one. Labelled throughout because two selects both reading "All" cannot say
   which is which, and stacked uniformly because a taller cell in the middle of
   the row leaves every other control aligned with nothing. */
.th-stack { display: flex; flex-direction: column; gap: 6px; }
.th-field { display: flex; flex-direction: column; gap: 2px; }
.th-field label {
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint);
}

/* `.th-dates` is GONE: the two date bounds are now two `.th-field`s in the same
   `.th-stack` as every other filter, which is what keeps the cells the same
   shape (see `filterHeadRow`). */
/* The empty state sits INSIDE the table now (in a `colspan` cell) so that a
   filter matching nothing cannot take away the filter row that would clear it.
   The row is not clickable and must not light up on hover like a transfer. */
table.tbl tbody tr.empty-row { cursor: default; }
table.tbl tbody tr.empty-row:hover { background: transparent; }
table.tbl tbody tr.empty-row td { padding: 0; border-bottom: 0; }

/* Apply / Clear live in the page head, not in a `th`: two buttons in a table
   cell would set that column's width and steal it from the data (the defect of
   QA 400 #3, one screen over). */
.head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/* The pager's per-page select (QA cabinet_txs #3). */
.pager-group { display: inline-flex; align-items: center; gap: 6px; }
.pager-group label { color: var(--faint); font-size: 11.5px; }
.pager-group select {
  font: inherit; font-size: 12.5px; color: var(--ink); padding: 4px 6px;
  border: 1px solid var(--line-strong); border-radius: 8px; background: #fff;
}
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl tbody tr { cursor: pointer; }
table.tbl tbody tr:hover { background: var(--bg); }
.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }

/* Timestamps carry two values: the absolute UTC instant, which is what a
   compliance record is quoted and compared by, and the relative form, which is
   only a glance aid. The zone is spelled out — a timestamp whose zone the
   reader has to guess is worse than no timestamp. */
td.when { white-space: nowrap; }
.when-abs { display: block; color: var(--muted); font-variant-numeric: tabular-nums; }
.when-rel { display: block; margin-top: 2px; font-size: 11.5px; color: var(--faint); }
.dir { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 12.5px; }
.dir .arrow { font-size: 13px; }
.dir--in .arrow { color: var(--green-ink); }
.dir--out .arrow { color: var(--blue); }
.amount { font-weight: 600; }
.amount .asset { color: var(--muted); font-weight: 500; margin-left: 4px; }

/* An id cell is 8 characters plus an ellipsis, so what is on screen is never the
   value anyone needs — it goes into a ticket, a `?id=` filter or a reply to the
   counterparty. The copy button IS the cell: without it the only way to get the
   full id is the hover title, which cannot be selected (QA cabinet_dash #5.3).
   Named `icon-copy`, not `.copy-btn`: that is the text pill the drawer, deposits
   and API keys use, and this is an icon square — two similar names in one
   stylesheet is a trap for the next reader. */
.idcell { display: inline-flex; align-items: center; gap: 6px; }
.icon-copy {
  border: 1px solid var(--line-strong); background: #fff; border-radius: 7px;
  width: 22px; height: 22px; flex-shrink: 0; padding: 0; color: var(--faint);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.icon-copy:hover { color: var(--blue); border-color: var(--blue); }
.icon-copy svg { width: 12px; height: 12px; }

/* The chain the transfer moved on, under the amount (QA cabinet_dash #5.2).
   Rendered only when the gateway recorded one — an inbound TRP row carries a DTI
   and no network, and any row written before migration 015 has none — so this
   rule must never supply a placeholder of its own (no `::after`, no
   `min-height`): a blank line here is the honest answer, where an em-dash would
   assert that the transfer HAS no network. */
.amt-net {
  display: block; margin-top: 2px; font-size: 11px; letter-spacing: .04em;
  color: var(--faint); font-weight: 600;
}
td .muted, .muted { color: var(--muted); }
td .faint, .faint { color: var(--faint); }

/* ---------- Empty states ---------- */
.empty { padding: 56px 32px; text-align: center; }
.empty .glyph {
  width: 48px; height: 48px; margin: 0 auto 16px; border-radius: 14px;
  background: var(--blue-soft); color: var(--blue);
  display: flex; align-items: center; justify-content: center; font-size: 20px;
}
.empty h3 { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.empty p { color: var(--muted); font-size: 13.5px; max-width: 420px; margin: 0 auto 18px; line-height: 1.55; }

/* ---------- Sign-in card ---------- */
.signin-wrap { display: flex; justify-content: center; padding: 72px 24px; }
.signin { max-width: 420px; width: 100%; padding: 40px 36px; text-align: center; }
.signin .glyph {
  width: 56px; height: 56px; margin: 0 auto 20px; border-radius: 16px;
  background: var(--blue-soft); color: var(--blue);
  display: flex; align-items: center; justify-content: center; font-size: 24px;
}
.signin h2 { font-size: 19px; font-weight: 700; margin-bottom: 8px; }
.signin p { color: var(--muted); font-size: 13.5px; line-height: 1.6; margin-bottom: 24px; }
/* Failure variant of the centred card: red glyph. The ONLY centred card that
   reports something broken is the gateway outage — "no entities linked" is a
   normal, expected state and is rendered as ordinary page content instead
   (renderNoTenants), so the two can never be mistaken for each other. */
.signin--alert .glyph { background: var(--red-bg); color: var(--red-ink); }
/* The PRODUCT icon. QA 386 #1/#3 put it on the two centred cards that stand in
   for the whole product ("sign in", "no entities are linked"); QA's 2026-08-28
   #2 extends it to EVERY tile that showed the ◆ typographic diamond — every
   empty state and section failure in the cabinet and the admin panel. The
   diamond is not the AMLBot mark and read as a placeholder glyph. Same artwork
   as the tab icon and ui/amlbot-icon.svg; each tile keeps its size and
   soft-blue background, so nothing but the glyph moves. */
.glyph--brand svg { width: 60%; height: 60%; display: block; }

/* ---------- Drawer ---------- */
.drawer-backdrop {
  position: fixed; inset: 0; background: rgba(10, 14, 26, .32);
  opacity: 0; pointer-events: none; transition: opacity .18s; z-index: 40;
}
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: 460px;
  background: #fff; border-left: 1px solid var(--line);
  box-shadow: -8px 0 32px rgba(16, 24, 40, .08);
  transform: translateX(100%); transition: transform .2s ease; z-index: 41;
  display: flex; flex-direction: column;
}
body.drawer-open .drawer { transform: translateX(0); }
body.drawer-open .drawer-backdrop { opacity: 1; pointer-events: auto; }
.drawer-head { padding: 20px 24px 16px; border-bottom: 1px solid var(--line); display: flex; align-items: flex-start; gap: 12px; }
.drawer-head h2 { font-size: 16px; font-weight: 700; }
.drawer-head .sub { font-size: 12px; color: var(--faint); margin-top: 4px; }
.drawer-close {
  margin-left: auto; border: 1px solid var(--line-strong); background: #fff;
  border-radius: 9999px; width: 30px; height: 30px; color: var(--muted);
  display: flex; align-items: center; justify-content: center; font-size: 14px;
}
.drawer-body { flex: 1; overflow-y: auto; padding: 20px 24px; }
.drawer dl { display: grid; grid-template-columns: 160px 1fr; row-gap: 0; font-size: 13px; }
.drawer dt {
  color: var(--faint); font-weight: 500; padding: 8px 12px 8px 0;
  border-bottom: 1px solid var(--bg); word-break: break-word;
}
.drawer dd {
  color: var(--ink); padding: 8px 0; border-bottom: 1px solid var(--bg);
  word-break: break-word; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px;
}
.drawer-actions { border-top: 1px solid var(--line); padding: 16px 24px; background: var(--bg); border-radius: 0; }
.drawer-actions .hint { font-size: 12px; color: var(--muted); margin-bottom: 10px; line-height: 1.5; }
.drawer-actions input[type=text] {
  width: 100%; font: inherit; font-size: 13px; padding: 9px 12px;
  border: 1px solid var(--line-strong); border-radius: 10px; margin-bottom: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.drawer-actions input[type=text]:focus { outline: 2px solid var(--blue-soft); border-color: var(--blue); }
.drawer-actions .row { display: flex; gap: 10px; }

/* ---------- Confirm dialog ----------
   Replaces window.confirm(), which blocked the whole tab until dismissed by
   hand — that froze the page for any automated run (E2E, extensions) and did
   not look like the rest of the app. */
.confirm-backdrop {
  position: fixed; inset: 0; background: rgba(10, 14, 26, .4);
  display: none; align-items: center; justify-content: center;
  padding: 24px; z-index: 70;
}
body.confirm-open .confirm-backdrop { display: flex; }
.confirm {
  background: #fff; border: 1px solid var(--line); border-radius: 14px;
  width: 440px; max-width: 100%; box-shadow: 0 18px 48px rgba(16, 24, 40, .22);
}
.confirm-head { padding: 20px 22px 0; font-size: 16px; font-weight: 700; }
.confirm-body { padding: 8px 22px 4px; color: var(--muted); font-size: 13.5px; line-height: 1.55; }
.confirm-foot { padding: 16px 22px 18px; display: flex; gap: 10px; justify-content: flex-end; }

/* ---------- Toast ----------
   Top of the viewport, not the bottom (QA, cabinet 3.4). `top: 80px` rather
   than 24px because `.topbar` is a 64px sticky bar at every width: the toast
   has z-index 60 and would have been drawn ON TOP of the header, which reads
   as a rendering bug rather than a notification. 80px = the bar plus a 16px
   gap. The hidden-state transform is negative for the same reason it used to
   be positive — the toast must slide IN from off-screen, so from above now.
   Below 1023px the nav rail lies down as a strip under the bar and a toast can
   float over it; that is accepted, since the toast is transient and the strip
   is not something the reader is mid-interaction with while a toast shows.
   Kept byte-identical to the copy in admin.css apart from indentation. */
.toast {
  position: fixed; top: 80px; left: 50%; transform: translate(-50%, -12px);
  background: var(--ink); color: #fff; border-radius: 9999px;
  padding: 11px 20px; font-size: 13px; font-weight: 500;
  opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s; z-index: 60;
  box-shadow: 0 8px 24px rgba(16, 24, 40, .24);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

.section-card { margin-bottom: 24px; }
.card-head { padding: 18px 20px 0; display: flex; align-items: center; justify-content: space-between; }
.card-head h2 { font-size: 15px; font-weight: 600; }
/* Prose directly inside a card (no table, no empty-state glyph) — lines up with
   .card-head's horizontal padding. Byte-identical to the rule in ui/admin.css:
   the two module graphs are served by separate routes and neither may import
   from the other, so this is two copies kept the same on purpose (the toast has
   the same arrangement). Used by the PLANNED cards in views/planned.js. */
.card-copy { padding: 10px 20px 20px; max-width: 68ch; }

/* ---------- Inline forms ---------- */
.form-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; padding: 16px 20px 20px; }
/* A row followed by `.card-notes` gives up its own bottom padding, so the
   gap between the control and its caption is one gap and not two. */
.form-row--notes { padding-bottom: 8px; }
.card-notes { display: grid; gap: 6px; padding: 0 20px 18px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 12px; font-weight: 600; color: var(--muted); }
.field input, .field select {
  font: inherit; font-size: 13px; padding: 9px 12px;
  border: 1px solid var(--line-strong); border-radius: 10px; background: #fff; color: var(--ink);
}
.field input:focus, .field select:focus { outline: 2px solid var(--blue-soft); border-color: var(--blue); }
.field--grow { flex: 1; min-width: 220px; }
.field input.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }

/* ---------- Copy button / notices ---------- */
.copy-btn {
  border: 1px solid var(--line-strong); background: #fff; color: var(--muted);
  border-radius: 9999px; padding: 3px 10px; font-size: 11px; font-weight: 600; margin-left: 8px;
}
.copy-btn:hover { color: var(--ink); border-color: #B9C6DE; }
.notice {
  margin: 16px 20px 20px; padding: 12px 16px; border-radius: 12px;
  background: var(--blue-soft); color: var(--ink); font-size: 12.5px; line-height: 1.55;
}
.notice--warn { background: #FFF6E6; color: #9A6700; }
.notice code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; }
table.tbl tbody tr.rowplain { cursor: default; }
table.tbl tbody tr.rowplain:hover { background: transparent; }
.kv { display: grid; grid-template-columns: 140px 1fr; row-gap: 8px; padding: 16px 20px 20px; font-size: 13px; }
.kv .k { color: var(--faint); font-weight: 500; }
.kv .v { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; word-break: break-all; }

/* ---------- Transfer detail (rich slide-over) ---------- */
.drawer.drawer--detail { width: min(1080px, calc(100vw - 72px)); }
.detail { display: flex; flex-direction: column; height: 100%; }
.detail-topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 24px; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, #F3F7FF 0%, #FFFFFF 100%);
}
.detail-back {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line-strong); background: #fff; color: var(--muted);
  border-radius: 9999px; padding: 7px 14px; font-weight: 600; font-size: 13px;
}
.detail-back:hover { color: var(--ink); border-color: #B9C6DE; }
.detail-topbar .who { font-size: 12px; color: var(--faint); font-weight: 600; }
.detail-topbar .spacer { flex: 1; }
.detail-scroll { flex: 1; overflow-y: auto; padding: 24px; }
.detail-scroll > * + * { margin-top: 20px; }

.detail-amount { font-size: 30px; font-weight: 700; letter-spacing: -.6px; }
.detail-amount .asset { font-size: 18px; color: var(--muted); font-weight: 600; margin-left: 8px; }
.detail-title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.detail-meta-row { display: flex; align-items: center; gap: 8px 18px; flex-wrap: wrap; margin-top: 12px; color: var(--muted); font-size: 12.5px; }
.detail-meta-row .mono { color: var(--ink); }
.detail-meta-row .sep { color: var(--line-strong); }
.dirchip { display: inline-flex; align-items: center; gap: 6px; border-radius: 9999px; padding: 4px 12px; font-size: 12px; font-weight: 600; border: 1px solid var(--line-strong); background: #fff; }
.dirchip--in { color: var(--green-ink); border-color: #BCE3CD; background: var(--green-bg); }
.dirchip--out { color: var(--blue); border-color: #C3D8FF; background: var(--blue-soft); }
/* The chain under the amount + asset (QA cabinet_txs #4). `block` so it sits on
   its own line under the figure, matching the table's `.amt-net` sub-label. */
.detail-net { display: block; font-size: 11.5px; font-weight: 600; letter-spacing: .04em; color: var(--faint); margin-top: 2px; }
/* Created / Updated / Responds by are two-line items now (absolute over
   relative, QA cabinet_txs #4), so the row aligns on the TOP of each item
   rather than centring one-line spans. */
.detail-meta-row { align-items: flex-start; }
.detail-when { display: inline-flex; flex-direction: column; gap: 1px; }
.detail-when .k { color: var(--faint); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.detail-when .when-abs { color: var(--ink); font-size: 12.5px; }
.detail-when .when-rel { color: var(--faint); font-size: 11.5px; }
/* The id and its Copy button are one item, so the button reads as attached to
   the value it copies rather than to the row. */
.detail-idline { display: inline-flex; align-items: center; gap: 6px; }

.detail-txid { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; padding: 12px 14px; background: var(--bg); border: 1px solid var(--line); border-radius: 12px; font-size: 12.5px; }
.detail-txid .lbl { font-weight: 600; color: var(--muted); }
.detail-txid .mono { word-break: break-all; }

/* flow band */
.flow { display: flex; align-items: stretch; gap: 12px; padding: 20px; }
.party { flex: 1; min-width: 0; position: relative; border: 1px solid var(--line); border-radius: 14px; padding: 16px; background: var(--bg); }
.party--yours { border-color: #C3D8FF; background: #F4F8FF; }
.party-role { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; font-weight: 700; color: var(--faint); }
.party-tag { position: absolute; top: 14px; right: 14px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; padding: 3px 8px; border-radius: 9999px; }
.party-tag--yours { background: var(--blue-soft); color: var(--blue); }
.party-tag--cp { background: #EEF1F6; color: var(--muted); }
.party-name { font-size: 17px; font-weight: 700; margin-top: 10px; letter-spacing: -.2px; }
.party-name.empty { color: var(--faint); font-weight: 600; }
.party-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.party-addr { margin-top: 14px; }
.party-addr .k { font-size: 11px; color: var(--faint); font-weight: 600; }
.party-addr .val { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.party-addr .mono { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pill { display: inline-flex; align-items: center; border-radius: 9999px; padding: 3px 10px; font-size: 11px; font-weight: 600; }
.pill--natural { background: #EEF1F6; color: var(--muted); }
.pill--legal { background: #EDE9FE; color: #6D28D9; }
.pill--unknownp { background: #EEF1F6; color: var(--faint); }
.pill--custodial { background: var(--blue-soft); color: var(--blue); }
.pill--selfhosted { background: #FFF3DB; color: #9A6700; }
.pill--unknownc { background: #EEF1F6; color: var(--faint); }

.flow-link { position: relative; flex: 0 0 210px; display: flex; align-items: center; justify-content: center; padding: 0 6px; }
.flow-link::before { content: ''; position: absolute; left: 4px; right: 12px; top: 50%; height: 2px; background: var(--line-strong); }
.flow-link::after { content: ''; position: absolute; right: 4px; top: 50%; transform: translateY(-50%); width: 0; height: 0; border-left: 8px solid var(--line-strong); border-top: 6px solid transparent; border-bottom: 6px solid transparent; }
.vasp { position: relative; z-index: 1; background: #fff; border: 1px solid var(--line-strong); border-radius: 12px; padding: 10px 14px; text-align: center; min-width: 124px; box-shadow: var(--card-shadow); }
.vasp .k { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 700; }
.vasp .n { font-size: 13px; font-weight: 700; margin-top: 4px; letter-spacing: -.1px; }
.vasp .t { font-size: 11px; color: var(--muted); margin-top: 2px; }

/* compliance strip */

.detail-grid { display: grid; grid-template-columns: 300px 1fr; gap: 20px; align-items: start; }

/* TRP protocol message */
.trp-legs { padding: 6px 18px 18px; display: flex; flex-direction: column; gap: 14px; }
.trp-leg { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.trp-leg-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 8px 12px; background: var(--bg); border-bottom: 1px solid var(--line); }
.trp-leg-title { font-weight: 700; font-size: 12.5px; }
.trp-leg-dir { font-size: 11.5px; color: var(--muted); }
.trp-req { font-family: ui-monospace, Menlo, monospace; font-size: 11.5px; color: var(--blue); padding: 8px 12px 0; word-break: break-all; }
.trp-hdr { margin: 6px 12px 0; padding: 8px 10px; background: #F1F5FC; border-radius: 6px; color: var(--muted); font-size: 11px; white-space: pre; overflow-x: auto; }
.trp-body { margin: 8px 12px; padding: 10px 12px; background: #0E1420; color: #E7ECF5; border-radius: 8px; font-size: 11.5px; white-space: pre; overflow-x: auto; line-height: 1.5; }
.trp-leg .copy-btn { margin: 0 12px 12px; }
.trp-toggle { display: inline-flex; gap: 0; border: 1px solid var(--line-strong); border-radius: 8px; overflow: hidden; }
.trp-toggle button { font: inherit; font-size: 11.5px; font-weight: 600; padding: 4px 10px; border: 0; background: #fff; color: var(--muted); cursor: pointer; }
.trp-toggle button.active { background: var(--blue); color: #fff; }

/* lifecycle timeline */
/* Both tabs' lists scroll INSIDE the card (QA 2026-09-09 #5). A transfer whose
   IVMS-101 has been revealed ten times pushed the Comments card and everything
   under it off the bottom of the drawer, and the drawer's own scrollbar was the
   only way back. `max-height` rather than a fixed height, so a two-event trail
   does not sit in a half-empty box. `overflow-x: hidden` because `overflow-y:
   auto` alone computes the other axis to `auto` too, and the timeline's dots are
   absolutely positioned at `left: 0` of a padded item — the same clipping
   surprise `.ta--block` hit in the admin panel's filter row. */
.timeline { list-style: none; padding: 8px 6px 4px; max-height: 420px; overflow-y: auto; overflow-x: hidden; }
.tl-item { position: relative; padding: 0 0 20px 24px; }
.tl-item::before { content: ''; position: absolute; left: 5px; top: 14px; bottom: -2px; width: 2px; background: var(--line); }
.tl-item.tl-last::before { display: none; }
.tl-dot { position: absolute; left: 0; top: 3px; width: 12px; height: 12px; border-radius: 50%; background: #fff; border: 2px solid var(--blue); box-sizing: border-box; }
.tl-item.tl-last .tl-dot { background: var(--blue); }
/* `text-transform: capitalize` is GONE (QA 2026-09-09 #6). It rendered every
   label word-capitalised — `Inquiry Sent`, and `Comment Added` for the labels
   this round adds — which is the same defect the merged dashboard card had. The
   labels are written correctly at source (`eventLabel` in ui/app/drawer.js), so
   the stylesheet has no business re-casing them. */
.tl-label { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tl-time { font-size: 11.5px; color: var(--faint); margin-top: 2px; display: flex; flex-direction: column; gap: 1px; }
.tl-time .when-abs { color: var(--muted); }
.tl-meta { font-size: 11.5px; color: var(--faint); margin-top: 3px; word-break: break-all; }
/* WHICH file an `attachment.*` event was about — the trail used to say only that
   something had been uploaded (QA 2026-09-09 #6). Its own line, above the actor:
   see the note in `timelineHtml`. `break-all` because a filename has no spaces
   to break at and the card is 300px wide. */
.tl-file {
  font-size: 11.5px; color: var(--muted); font-weight: 600;
  margin-top: 3px; word-break: break-all;
}
/* Named, not inferred: only the two counterparty actors get this (see
   COUNTERPARTY_ACTORS in ui/app/drawer.js) — QA cabinet_txs #5. */
.tl-tag {
  text-transform: none; letter-spacing: 0; font-size: 10.5px; font-weight: 600;
  color: var(--muted); background: var(--bg); border: 1px solid var(--line-strong);
  border-radius: 9999px; padding: 1px 8px;
}
/* Event dot colours agree with the STATUS badge colours already in the cabinet
   (QA cabinet_txs #5) — `badgeClass` is the one definition and `eventDotClass`
   maps through it, so a `transfer.rejected` dot is the red its badge is.
   `--plain` is `pii.reveal` and anything else that is not a transfer state:
   deliberately neutral, because colouring it like a state would claim it is one.
   The `.tl-last` fill is per-colour so the final dot stays filled. */
.tl-dot--green { border-color: var(--green-ink); }
.tl-dot--red { border-color: var(--red-ink); }
.tl-dot--gray { border-color: var(--faint); }
.tl-dot--blue { border-color: var(--blue); }
.tl-dot--plain { border-color: var(--line-strong); }
.tl-item.tl-last .tl-dot--green { background: var(--green-ink); }
.tl-item.tl-last .tl-dot--red { background: var(--red-ink); }
.tl-item.tl-last .tl-dot--gray { background: var(--faint); }
.tl-item.tl-last .tl-dot--blue { background: var(--blue); }
.tl-item.tl-last .tl-dot--plain { background: var(--line-strong); }

/* The Lifecycle / PII access switch. Two tabs in one card head, because the two
   are different questions about the same transfer — what happened to it, and who
   opened its person data. */
.seg-group { display: inline-flex; gap: 4px; background: var(--bg); border: 1px solid var(--line); border-radius: 9999px; padding: 3px; }
.seg {
  border: 0; background: none; border-radius: 9999px; padding: 4px 11px;
  font: inherit; font-size: 11.5px; font-weight: 600; color: var(--muted);
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
}
.seg:hover { color: var(--ink); }
.seg--on { background: #fff; color: var(--ink); box-shadow: var(--card-shadow); }
.seg-n { font-variant-numeric: tabular-nums; color: var(--faint); font-weight: 600; }
.seg--on .seg-n { color: var(--blue); }

/* comments */
.cmt-list { display: flex; flex-direction: column; gap: 14px; padding: 4px 4px 0; }
.cmt { display: flex; gap: 10px; }
.cmt .ava { width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%; background: var(--blue-soft); color: var(--blue); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; }
.cmt-main { flex: 1; min-width: 0; }
.cmt-top { display: flex; align-items: baseline; gap: 8px; }
.cmt-author { font-weight: 600; font-size: 13px; }
.cmt-when { font-size: 11.5px; color: var(--faint); display: inline-flex; align-items: baseline; gap: 6px; }
.cmt-when .when-abs { color: var(--muted); }
.cmt-body { font-size: 13px; line-height: 1.5; margin-top: 3px; white-space: pre-wrap; word-break: break-word; }
.cmt-atts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.att-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--bg); border: 1px solid var(--line); border-radius: 9999px; padding: 4px 10px; font-size: 11.5px; color: var(--muted); }
.att-chip .sz { color: var(--faint); }
.att-chip .rm { border: 0; background: none; color: var(--faint); font-size: 14px; line-height: 1; padding: 0 0 0 2px; }
.att-chip .rm:hover { color: var(--red-ink); }
/* Real upload state on the composer's chips (QA cabinet_txs #6): the chip used
   to look finished the instant a file was chosen, while nothing had been sent. */
.att-chip--uploading { border-style: dashed; color: var(--blue); }
.att-chip--uploading .sz { color: var(--blue); }
.att-chip--error { border-color: #F3C6C2; background: var(--red-bg); color: var(--red-ink); }
.att-chip--error .sz { color: var(--red-ink); }
.att-chip .rm--wait { padding: 0 0 0 2px; color: var(--blue); }
/* A posted chip that CAN be downloaded is a button and says so; one without an
   id is a pre-upload row with no bytes behind it, and stays inert text. */
.att-chip--dl { cursor: pointer; }
.att-chip--dl:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }
.att-chip--legacy { cursor: default; }
.cmt-empty { font-size: 13px; color: var(--muted); padding: 8px 4px 12px; }
.composer { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 16px; }
.composer textarea { width: 100%; font: inherit; font-size: 13px; line-height: 1.5; padding: 11px 13px; border: 1px solid var(--line-strong); border-radius: 12px; resize: vertical; min-height: 76px; }
.composer textarea:focus { outline: 2px solid var(--blue-soft); border-color: var(--blue); }
.composer-bar { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.composer-bar .spacer { flex: 1; }
.cmt-count { font-size: 11.5px; color: var(--faint); }
.cmt-count.over { color: var(--red-ink); font-weight: 600; }
.composer-note { font-size: 11.5px; color: var(--faint); margin-top: 8px; }

/* ---------- Typeahead suggestions (QA cabinet_txs #2) ----------
   A SECOND COPY of the block in ui/admin.css, for the reason `ui/app/typeahead.js`
   states in full: the two panels are served by two routes, one gated, so neither
   stylesheet may reference the other. Kept byte-identical in shape so a reader
   who knows one knows the other — the toast's precedent (QA cabinet Settings).

   Why the control exists at all: QA asked for a searchable Blockchain dropdown,
   and a native `<datalist>` was rejected for the admin filters in QA 388 round 3
   #2 — the browser draws it in its own font with no room for the second line. */
.ta { position: relative; display: inline-flex; width: 100%; }
.ta > input { width: 100%; }
.ta-pop {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
  /* `min()` so the popover can never be the thing that makes the page scroll
     sideways on a narrow window — it shrinks instead. */
  min-width: 210px; max-width: min(300px, calc(100vw - 40px));
  background: var(--ink); border-radius: 14px; padding: 8px;
  box-shadow: 0 16px 40px rgba(10, 14, 26, .28);
}
/* The scroll lives on the LIST, not on the popover: a scroll container clips its
   own overflow, and the pointer below is positioned outside the box. */
.ta-list { max-height: 264px; overflow-y: auto; }
.ta-arrow {
  position: absolute; top: -5px; left: 24px;
  width: 12px; height: 12px; background: var(--ink); border-radius: 2px;
  transform: rotate(45deg);
}
.ta-opt {
  position: relative; display: block; width: 100%; text-align: left;
  border: 0; background: none; border-radius: 10px; padding: 7px 12px;
  color: #fff; font: inherit; cursor: pointer;
}
.ta-opt .slug { display: block; font-size: 13px; font-weight: 700; }
.ta-opt .name { display: block; font-size: 12.5px; color: #A9B4C9; margin-top: 1px; }
/* `.active` is the keyboard cursor, hover is the mouse. Both look the same on
   purpose — there is only ever one highlighted row. */
.ta-opt:hover, .ta-opt.active { background: rgba(255, 255, 255, .10); }
.ta-note { padding: 7px 12px; color: #A9B4C9; font-size: 12.5px; line-height: 1.45; }
.ta-foot {
  margin-top: 6px; padding: 8px 12px 2px; border-top: 1px solid rgba(255, 255, 255, .12);
  color: #8F9BB3; font-size: 11.5px; line-height: 1.45;
}

.hidden { display: none !important; }
.brand-logo{display:block}

/* ---------- Narrow windows (QA 386 #6) ----------
   The cabinet had exactly one layout: `body { min-width: 1024px }`, a 232px
   rail, a four-column KPI grid and a 1080px detail drawer. The screen QA filed
   is the one that needs it least — the sign-in stub — but the same sideways
   scroll was on every view, and a compliance officer opening the cabinet from a
   phone to check one transfer is a real thing.

   Three breakpoints, mirroring admin.html so the two panels behave the same
   way. Below the last one the layout stacks rather than shrinking further:
   tables keep their own `.tablewrap` horizontal scroll, which is the right
   answer for a wide table and always was. */
@media (max-width: 1180px) {
  .main { padding: 24px 20px 40px; }
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1023px) {
  /* The rail lies down as a strip under the topbar: at this width a 232px
     column is a quarter of the viewport spent on five links. */
  .shell { flex-direction: column; }
  .sidebar {
    /* Still sticky, now as a strip under the topbar — the point of the fix is
       that the nav and Log out never need a scroll to reach. `height: auto` and
       `overflow: visible` undo the viewport-bounded column above. */
    width: 100%; border-right: 0; border-bottom: 1px solid var(--line);
    padding: 10px 16px; top: 64px; z-index: 20;
    height: auto; overflow: visible; align-self: auto;
    display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px;
  }
  /* Laid down, "bottom" is the end of the strip: Log out sits after the nav
     items with no border rule of its own. */
  .sidebar-foot { margin-top: 0; margin-left: auto; padding-top: 0; border-top: 0; }
  .sidebar .section-label { display: none; }
  .nav-item { width: auto; margin-bottom: 0; }
  /* The drawer stops being a side panel and becomes the sheet it already is at
     this width — `min(1080px, 100vw - 72px)` left a 72px dead gutter beside a
     full-width sheet on a phone. */
  .drawer, .drawer.drawer--detail { width: 100%; }
}
@media (max-width: 820px) {
  .topbar { padding: 0 16px; gap: 12px; }
  .page-head { flex-direction: column; align-items: flex-start; }
  .kpis { grid-template-columns: 1fr; }
  .form-grid { grid-template-columns: 1fr; }
  /* Stacked, the read-only Entity details card goes FIRST — that is what QA's
     "move the card to the top of the page" means once there is only one column.
     An `order` flip rather than a DOM change: the editable card is first in the
     markup because it is the primary one at every width where both fit, and
     tab order should follow that. */
  .entity-cols { grid-template-columns: 1fr; }
  .entity-cols > :nth-child(1) { order: 2; }
  .entity-cols > :nth-child(2) { order: 1; }
  /* A tab row that no longer fits scrolls instead of wrapping into a second
     line that looks like two rows of unrelated tabs. */
  .settings-tabs { overflow-x: auto; }
  .stab { white-space: nowrap; }
  /* 160px of label + a value in ~200px of remaining width is unreadable; the
     drawer's definition lists stack. */
  .drawer dl { grid-template-columns: 1fr; }
  .signin-wrap { padding: 40px 16px; }
  .signin { padding: 32px 24px; }
}
@media (max-width: 560px) {
  /* The range text, two buttons and "Page N of M" do not fit a phone width. */
  .pager { flex-wrap: wrap; row-gap: 8px; }
  /* Phone-width topbar. The AMLBot lockup is a fixed 205px of SVG and the
     account chip another 153px with the user's name in it, so on a 390px screen
     they overflowed the bar by a few pixels and the whole page scrolled
     sideways — the last of the horizontal scroll after the body min-width came
     out. Two independent fixes, because either alone leaves the other
     combination (an account with an entity picker) overflowing again:
       - the chip drops to avatar + caret, and
       - the bar may wrap, so ANY set of controls stacks instead of overflowing.
     The name is still in the menu the chip opens. */
  .topbar { height: auto; min-height: 56px; flex-wrap: wrap; padding: 8px 16px; gap: 8px 12px; }
  .userchip #userName { display: none; }
  .userchip { padding: 5px; }
  .entity-switch label { display: none; }
  .entity-switch select { max-width: 180px; }
}
