/* ── Control center — Persian, right-to-left ───────────────────────────
   Written mobile-first with logical properties only. There is no `left`,
   `right`, `margin-left`, `padding-right` or `text-align: right` anywhere
   in this file, and no `[dir]`-prefixed override pair patching a physical
   rule after the fact — control/internal/console's guard test fails the
   build on any of them. Flexbox and grid already follow the writing mode,
   so a `row-reverse` would be a sign a physical property leaked in
   somewhere.

   The token names and values are borrowed from internal/webapp/web/app.css
   on purpose: an operator moving between a node console and the control
   center should not have to learn a second product. The *stylesheet* is
   not borrowed — that one is physical and left-to-right throughout.

   Where the node console is a dense always-on console watched from before
   the open to the close, this is a desk surface: the page scrolls
   normally, the rail sticks, and each screen does one thing — a short
   summary, one list, one detail panel.                           */

/* ── 0. the self-hosted Persian font ──────────────────────────────────
   Served from this directory, never a CDN: the control host must render
   correctly on a machine with no outbound internet access. The fallback
   stack carries the page on its own if the webfont is blocked.     */
@font-face {
  font-family: 'Vazirmatn';
  src: url('fonts/Vazirmatn-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('fonts/Vazirmatn-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

:root {
  color-scheme: dark;

  /* Blue-biased neutrals, never flat grey. */
  --ground: #0a0f16;
  --rail: #111925;
  --panel: #121a24;
  --panel-2: #17212d;
  --inset: #0c121a;
  --field: #0b1118;
  --line: #243240;
  --line-soft: #1c2834;
  --line-hard: #2e3f4e;

  --ink: #edf2f8;
  --ink-2: #afbece;
  --ink-3: #8294a5;
  --ink-4: #5c6e7e;

  --accent: #84b4e2;
  --accent-dim: #315778;
  --accent-wash: #152433;
  --accent-bright: #d7eaff;

  --good: #7ce6b2;
  --good-wash: #0e3025;
  --good-line: #2c6a50;
  --warn: #f6c978;
  --warn-wash: #2f2614;
  --warn-line: #7a5f28;
  --bad: #f89a9a;
  --bad-wash: #311a1d;
  --bad-line: #79383e;

  --sans: 'Vazirmatn', 'Sahel', 'IRANSans', Tahoma, 'Segoe UI', system-ui, sans-serif;
  --mono: 'SFMono-Regular', Consolas, 'Cascadia Mono', ui-monospace, monospace;

  --radius: 10px;
  --tap: 44px;              /* smallest comfortable touch target */
  --page: clamp(12px, 3vw, 28px);
}

/* ── 1. base ──────────────────────────────────────────────────────────── */

* { box-sizing: border-box; min-width: 0; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 15px/1.65 var(--sans);
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { margin: 0; font-weight: 700; line-height: 1.35; }
h1 { font-size: 20px; }
h2 { font-size: 17px; }
h3 { font-size: 15px; }
p { margin: 0; }

button, input, select { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
[hidden] { display: none !important; }

/* tabular-nums aligns digits down a column; it is not a font swap. The
   family is the one thing that must never reach Persian text: --mono is a
   Latin stack with no Persian coverage, so a mixed run falls back glyph by
   glyph and loses its joining. Ask for alignment (.tnum) everywhere, and
   for the monospace family (.mono) only where the whole run is an
   identifier. */
.tnum { font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Every LTR identifier on the page goes through format.js's ltr(), which
   produces one of these: isolation by markup, never by a bidi control
   character. */
/* The same isolation for a run that is Persian text rather than an
   identifier: no direction and no family are imposed on it, so a stored
   value that turns out to be a phrase still reads as one. */
.words { unicode-bidi: isolate; overflow-wrap: anywhere; }

.ltr {
  unicode-bidi: isolate;
  /* An age recipient or a sha256 hash is longer than its row. Breaking it
     anywhere beats clipping it: these are identifiers an operator copies. */
  overflow-wrap: anywhere;
  /* But `anywhere` also tells the layout a one-character column is fine, and
     a table column takes that offer: a node id splits across two lines while
     the column next to it keeps its slack. In a cell the word still counts
     toward the column's width and breaks only when it truly cannot fit. */
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.94em;
}

th .ltr, td .ltr { overflow-wrap: break-word; }
/* A short identifier — a node id, a version — reads as one word or not at
   all, and its hyphen is a break opportunity the layout will take as soon as
   the column is tight. The column it sits in says so; a correlation id or a
   hash is not in one and still wraps rather than widening its table. */
th.id, td.id { white-space: nowrap; }

.lede { color: var(--ink-2); font-size: 14px; max-width: 68ch; }
.hint { color: var(--ink-3); font-size: 13px; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── 3. the top rail ──────────────────────────────────────────────────
   Brand, the Tehran clock every time on the page is read against, the
   market session, projection lag, and who is logged in. Sticky, because
   the session cutoff and the lag indicator should not scroll away. */

.rail {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  padding: 10px var(--page);
  background: var(--rail);
  border-block-end: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 10px; }
.mark {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  border-radius: 7px;
  background: var(--accent-dim);
  border: 1px solid #3f6d96;
  color: var(--accent-bright);
  font-weight: 700;
}
.brand strong { font-size: 16px; white-space: nowrap; }

.clock { display: flex; align-items: baseline; gap: 8px; }
.clock-time { font: 700 19px/1 var(--mono); font-variant-numeric: tabular-nums; }
.clock-zone { color: var(--ink-3); font-size: 12px; white-space: nowrap; }

.gauge { display: flex; flex-direction: column; }
/* No letter-spacing: a cursive script has no space between letters to
   give, and asking for some breaks the joins instead. */
.gauge-k { color: var(--ink-3); font-size: 11px; white-space: nowrap; }
/* A gauge holds either a figure or a sentence. The lag gauge reads
   "<n> seconds ago", so the monospace family belongs to the .figure
   variant and not to every gauge. */
.gauge-v {
  font-weight: 700; font-size: 14px; line-height: 1.3;
  font-variant-numeric: tabular-nums; color: var(--accent);
}
.gauge-v.figure { font-family: var(--mono); }

.spacer { flex: 1 1 0; }

.who { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.who-name { font-weight: 700; font-size: 14px; }

/* ── 4. navigation ────────────────────────────────────────────────────
   Four destinations, as a scrollable row on a phone and a plain row
   everywhere else. No drawer, no hamburger: four items fit. */

.nav {
  display: flex;
  gap: 4px;
  padding: 0 var(--page);
  background: var(--rail);
  border-block-end: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
.nav::after { content: ''; flex: none; inline-size: var(--page); }
.nav a {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  /* Never shrink: the row scrolls, the labels do not compress. */
  flex: none;
  padding: 0 14px;
  color: var(--ink-3);
  text-decoration: none;
  white-space: nowrap;
  border-block-end: 2px solid transparent;
}
.nav a:hover { color: var(--ink); }
.nav a[aria-current='page'] {
  color: var(--accent-bright);
  border-block-end-color: var(--accent);
  font-weight: 700;
}

/* ── 5. the page body ─────────────────────────────────────────────────── */

main {
  display: block;
  padding: var(--page);
  padding-block-end: 64px;
}
.page { display: grid; gap: 18px; max-width: 1400px; margin-inline: auto; }
/* The head is one band: the title and its lede on the start side, the
   tabs and the primary action on the end side, so the first viewport
   carries rows rather than a title and a stripe of background. It wraps
   to two lines on a narrow screen, where there is no width to share. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 10px 24px;
}
.page-head-text { display: grid; gap: 6px; flex: 1 1 34ch; min-inline-size: 0; }
/* Inside the band the controls are already on the end side; the margin
   that separates them from a title above belongs to the stacked case. */
.page-head > .tabs-row { margin-block-start: 0; }

/* ── 6. summary cards ─────────────────────────────────────────────────
   At most three per screen. The 1px gap over a line-coloured background
   is the node console's divider trick, which needs no borders. */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.cards:empty { display: none; }
.card { background: var(--panel); padding: 12px 15px; }
.card .k { color: var(--ink-3); font-size: 13px; font-weight: 600; }
/* The family comes from the value itself: an identifier arrives wrapped
   in .ltr and keeps the monospace stack, a figure does not need it. */
.card .v { font-weight: 700; font-size: 22px; line-height: 1.25; font-variant-numeric: tabular-nums; }
.card .v .ltr { font-size: inherit; }
.card .v.ok { color: var(--good); }
.card .v.warn { color: var(--warn); }
.card .v.bad { color: var(--bad); }
.card .d { color: var(--ink-3); font-size: 12.5px; }

/* ── 7. panels ───────────────────────────────────────────────────────── */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 12px 15px;
  background: var(--panel-2);
  border-block-end: 1px solid var(--line);
}
.panel-body { padding: 15px; }
.panel-body.flush { padding: 0; }
/* A line about the whole list, under a flush table: padded like the
   search bar above it, so it does not touch the panel's edge. */
.panel-note { margin: 0; padding: 11px 14px; border-block-start: 1px solid var(--line); }

.empty {
  padding: 40px 20px;
  text-align: center;
  display: grid;
  gap: 8px;
  justify-items: center;
}
.empty h3 { color: var(--ink-2); }

/* ── 8. banners: stale, offline, error ────────────────────────────────
   Staleness is first-class in this architecture — heartbeat age,
   projection lag, desired versus observed — so it gets a real place on
   the page rather than a line of prose that a refresh can overwrite. */

.banner {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 15px;
  border-radius: var(--radius);
  border: 1px solid var(--warn-line);
  background: var(--warn-wash);
}
.banner .dot { width: 10px; height: 10px; border-radius: 50%; margin-block-start: 7px; flex: none; background: var(--warn); }
.banner strong { display: block; color: var(--warn); }
.banner p { color: var(--ink-2); font-size: 13.5px; }
.banner.bad { border-color: var(--bad-line); background: var(--bad-wash); }
.banner.bad .dot { background: var(--bad); }
.banner.bad strong { color: var(--bad); }

/* ── 9. status chips ─────────────────────────────────────────────────
   The state enum is the class name, verbatim, exactly as the node
   console does it — so a chip is greppable against the wire contract. */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  background: var(--panel-2);
  border: 1px solid var(--line-hard);
  color: var(--ink-2);
}
.chip::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.chip.plain::before { display: none; }

/* good: running and settled-well states */
.chip.healthy, .chip.completed, .chip.approved, .chip.converged, .chip.trading, .chip.open {
  background: var(--good-wash); border-color: var(--good-line); color: var(--good);
}
/* attention: moving, waiting, or not yet where it should be */
.chip.degraded, .chip.pending, .chip.under_review, .chip.submitted, .chip.preparing,
.chip.prepared, .chip.executing, .chip.tracking, .chip.assigned, .chip.enrolling, .chip.canary {
  background: var(--warn-wash); border-color: var(--warn-line); color: var(--warn);
}
/* bad: stopped, refused, or gone */
.chip.offline, .chip.revoked, .chip.failed, .chip.blocked, .chip.expired, .chip.cancelled, .chip.drifted, .chip.holiday {
  background: var(--bad-wash); border-color: var(--bad-line); color: var(--bad);
}
/* the one live state worth a pulse, and only one */
.chip.executing::before { animation: beat 2.2s ease-out infinite; }
@keyframes beat {
  0%   { box-shadow: 0 0 0 0 rgba(246, 201, 120, .5); }
  70%  { box-shadow: 0 0 0 8px rgba(246, 201, 120, 0); }
  100% { box-shadow: 0 0 0 0 rgba(246, 201, 120, 0); }
}

.chip.buy { background: var(--good-wash); border-color: var(--good-line); color: var(--good); }
.chip.sell { background: var(--bad-wash); border-color: var(--bad-line); color: var(--bad); }

.tag {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 6px;
  margin-inline-end: 5px;
  background: var(--inset);
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 12.5px;
}

/* ── 9b. segmented control and tab rows ──────────────────────────────
   The node console's .seg, re-expressed logically: it divides one screen
   into views instead of spending a navigation slot on each. */

.seg {
  display: flex;
  flex: none;
  border: 1px solid var(--line-hard);
  border-radius: 8px;
  overflow: hidden;
  background: var(--inset);
}
.seg button {
  min-height: 38px;
  flex: none;
  padding: 0 16px;
  background: transparent;
  border: 0;
  border-inline-end: 1px solid var(--line-hard);
  color: var(--ink-3);
  cursor: pointer;
  white-space: nowrap;
}
.seg button:last-child { border-inline-end: 0; }
.seg button:hover { color: var(--ink-2); }
.seg button[aria-selected='true'] {
  background: var(--accent-wash);
  color: var(--accent-bright);
  font-weight: 700;
}

.tabs-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-block-start: 4px;
}

/* A page rendered inside a page (the finance tabs) should not add a second
   set of outer margins. */
.page .page { gap: 18px; }

/* ── 10. buttons ─────────────────────────────────────────────────────── */

.btn {
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  gap: 8px;
  padding: 0 16px;
  border-radius: 8px;
  background: var(--panel-2);
  border: 1px solid var(--line-hard);
  color: var(--ink-2);
  cursor: pointer;
}
.btn:hover { color: var(--ink); background: #1b2734; border-color: #44596b; }
.btn.primary {
  background: var(--accent-dim);
  border-color: #4a7ba6;
  color: var(--accent-bright);
  font-weight: 700;
}
.btn.primary:hover { background: #3a6a92; }
.btn.quiet { background: transparent; border-color: transparent; color: var(--ink-3); }
.btn.quiet:hover { color: var(--ink); background: var(--panel-2); }
.btn.danger { border-color: var(--bad-line); color: var(--bad); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; }

/* ── 11. fields ──────────────────────────────────────────────────────── */

.field { display: grid; gap: 6px; }
.field > label { font-size: 13.5px; font-weight: 600; color: var(--ink-2); }
.field input, .field select, .field textarea {
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: 8px;
  background: var(--field);
  border: 1px solid var(--line-hard);
  color: var(--ink);
  width: 100%;
}
.field textarea { min-height: 76px; padding: 10px 12px; line-height: 1.6; resize: vertical; }
.field .hint { font-size: 12.5px; }
/* A code-like value an operator types or reads stays LTR inside the RTL
   form, without flipping the label or the field's own layout. */
.field input.code { direction: ltr; text-align: start; font-family: var(--mono); }
/* An operator account name is an ASCII handle, so it reads left-to-right
   — but a person can type Persian into it, and the monospace stack would
   render that unjoined, which reads as a broken page rather than as the
   wrong input. It keeps the page font. */
.field input.handle { direction: ltr; text-align: start; }

/* ── 11b. the order-entry form ───────────────────────────────────────
   One column on a phone, two where there is room. Long notes under a
   field span both columns so the field itself stays narrow enough to
   read. */

.form-grid { display: grid; gap: 16px; }
@media (min-width: 720px) {
  .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.bad-text { color: var(--bad); }

/* A field whose chooser carries the way to make what it is choosing
   from: the customer chooser with "new customer" beside it, the account
   chooser with "add account". The button keeps its own width and the
   chooser takes the rest, and they wrap on a phone rather than squeezing
   the input down to nothing. */
.field-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: start; }
.field-row > .combo { flex: 1 1 200px; min-inline-size: 0; }
.field-row > .btn { flex: 0 0 auto; }

/* A checkbox is the one control that is not full width: it sits beside
   its own words rather than above them. */
.field label > input[type='checkbox'] {
  inline-size: auto;
  min-height: 0;
  margin-inline-end: 4px;
  vertical-align: middle;
}

/* ── 11c. the searchable chooser ─────────────────────────────────────
   Used for every list that grows with the business. The list is absolutely
   positioned against the field, so it overlays the next row instead of
   pushing the form around as the operator types. */

.combo { position: relative; }
.combo-input { inline-size: 100%; padding-inline-end: 38px; }
.combo-toggle {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  inline-size: 38px;
  block-size: var(--tap);
  background: transparent;
  border: 0;
  color: var(--ink-3);
  cursor: pointer;
}
.combo-list {
  position: absolute;
  inset-block-start: calc(var(--tap) + 4px);
  inset-inline: 0;
  z-index: 30;
  margin: 0;
  padding: 4px;
  max-block-size: 260px;
  overflow-y: auto;
  list-style: none;
  background: var(--panel-2);
  border: 1px solid var(--line-hard);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(5, 9, 14, .55);
}
.combo-option {
  min-block-size: 38px;
  display: flex;
  align-items: center;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.combo-option:hover, .combo-option.active { background: var(--accent-wash); color: var(--accent-bright); }
.combo-option[aria-selected='true'] { font-weight: 700; }
.combo-empty { padding: 10px; color: var(--ink-3); }

/* ── 12. tables, and the same data as cards on a phone ───────────────
   Below the breakpoint each row becomes a stacked card and every cell
   carries its column name from data-label, which is the node console's
   responsive-table trick. A table that scrolls sideways on a phone is a
   table nobody reads. */

.table-wrap { overflow-x: auto; }

/* The filter bar above every table. It sits inside the panel's flush body,
   so it carries the padding the table head would otherwise have given it,
   and it stays above the rows while they scroll sideways. */
.table-search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 11px 14px;
  background: var(--inset);
  border-block-end: 1px solid var(--line);
}
.table-search-input {
  flex: 1 1 14rem;
  max-inline-size: 26rem;
  padding: 8px 11px;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line-hard);
  border-radius: 8px;
  font: inherit;
  font-size: 13.5px;
}
.table-search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.table-nomatch { flex: 1 1 100%; color: var(--ink-3); font-size: 13px; }

/* A row the filter excluded. A class rather than the hidden attribute: the
   phone layout below gives every row display:block, which would win
   against [hidden]'s display:none. */
.filtered-out { display: none; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
thead th {
  padding: 11px 14px;
  text-align: start;
  background: var(--inset);
  color: var(--ink-3);
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  border-block-end: 1px solid var(--line);
}
tbody td { padding: 12px 14px; border-block-end: 1px solid var(--line-soft); vertical-align: middle; }
tbody tr:last-child td { border-block-end: 0; }
tbody tr.selectable { cursor: pointer; }
tbody tr.selectable:hover { background: var(--panel-2); }
tbody tr[aria-selected='true'] { background: var(--accent-wash); }
/* A numeric column is aligned, not re-fonted: its cells hold a phrase
   (an order size, a gap in milliseconds) as often as a bare figure. */
.num { text-align: end; font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-3); }

/* A node id that is also a way out of this screen: the fleet row, and
   from there that node's own console, which is where a line's depth
   lives. Underlined on hover only, so a table of them does not read as a
   page of links. */
.node-link { color: var(--accent); text-decoration: none; }
.node-link:hover { color: var(--accent-bright); text-decoration: underline; text-underline-offset: 3px; }

/* An old → new pair. Flex rather than a joined string, so the order is the
   writing mode's and not the bidi algorithm's guess. */
.change { display: inline-flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }

/* Two lines in one cell: the Jalali Tehran rendering, and the ISO-8601
   UTC value of the same instant beneath it. Audit rows and exports always
   carry both, so a record can be correlated with a run log. */
.when { display: grid; gap: 2px; }
.when .iso { color: var(--ink-4); font-size: 11.5px; }
/* An ISO instant is one token an operator copies whole; broken across
   lines it reads as a column of digits. The block takes its content's
   width, so the table column grows to hold it instead of clipping it. */
.when { inline-size: max-content; }
.when .iso { white-space: nowrap; }
/* Lines that belong to one cell, one under another: identifiers, so each
   keeps its width rather than splitting where the column is tight. */
.stack { display: grid; gap: 2px; inline-size: max-content; }

@media (max-width: 760px) {
  table, thead, tbody, tr, td { display: block; }
  thead { display: none; }
  tbody tr {
    padding: 12px 14px;
    border-block-end: 1px solid var(--line);
  }
  tbody tr:last-child { border-block-end: 0; }
  tbody td {
    display: flex;
    gap: 12px;
    align-items: baseline;
    justify-content: space-between;
    padding: 4px 0;
    border: 0;
  }
  tbody td::before {
    content: attr(data-label);
    color: var(--ink-3);
    font-size: 12.5px;
    font-weight: 600;
    flex: none;
  }
  tbody td:empty { display: none; }
  .num { text-align: end; }
}

/* ── 13. list and detail ─────────────────────────────────────────────
   One list, one detail panel. Side by side when there is room; over the
   page, as a drawer from the top, when there is not. On one column the
   detail is never another block in the page: it would sit under the rows,
   the page would have to travel down to it, and the operator would lose
   the row they clicked and their place in the list on the way. The drawer
   leaves the list exactly where it was, underneath. */

.split { display: grid; gap: 18px; }

@media (max-width: 1099px) {
  .split > .detail {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: grid;
    align-content: start;
    justify-items: center;
    gap: 10px;
    padding-block-end: 20px;
    /* The same scrim as the dialog: one overlay language, not two. */
    background: rgba(5, 9, 14, .74);
    /* The drawer scrolls inside itself, and the page behind it does not
       scroll with it: what is under the drawer stays where the operator
       left it, which is the whole point of covering the page instead of
       pushing it around. */
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* The sheet comes down from the top, so the start edge stays square
     against the screen and the end edge is rounded. A second panel in the
     same drawer is a card inside it, and is rounded all round. */
  .split > .detail > .panel {
    inline-size: 100%;
    max-inline-size: 620px;
    border-start-start-radius: 0;
    border-start-end-radius: 0;
    border-end-start-radius: 14px;
    border-end-end-radius: 14px;
  }
  .split > .detail > .panel ~ .panel { border-radius: var(--radius); }
}

@media (min-width: 1100px) {
  .split { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); align-items: start; }
  /* Beside the list the panel is a column, not a drawer: it scrolls with
     the page and keeps its place under the rail. */
  .split > .detail { position: sticky; inset-block-start: 120px; }
}

/* The drawer's way out, on the end of its own head. It belongs to the
   narrow layout only: beside the list there is nothing to close back to. */
.detail-close { margin-inline-start: auto; }
@media (min-width: 1100px) { .detail-close { display: none; } }

/* A detail can be more than one panel — a customer is their record and
   their broker accounts — so the column spaces itself the way the split
   does. */
.detail { display: grid; gap: 18px; align-content: start; }

.detail-rows { display: grid; gap: 0; }
.detail-row {
  display: grid;
  gap: 2px 14px;
  padding: 10px 0;
  border-block-end: 1px solid var(--line-soft);
}
.detail-row:last-child { border-block-end: 0; }
.detail-row .k { color: var(--ink-3); font-size: 12.5px; font-weight: 600; }
.detail-row .v { font-size: 14px; overflow-wrap: anywhere; }
@media (min-width: 560px) {
  .detail-row { grid-template-columns: 11rem minmax(0, 1fr); align-items: baseline; }
}

/* A heading for a second block inside one panel — the holdings table that
   follows a person's detail rows. Smaller than the panel's own h2, so the
   panel still reads as one thing with a part in it. */
.panel-sub {
  margin-block-start: 6px;
  color: var(--ink-2);
  font-size: 13.5px;
  font-weight: 700;
}

/* ── 14. the revision history ────────────────────────────────────────── */

.timeline { display: grid; gap: 14px; }
.rev {
  padding: 12px 14px;
  border-radius: 8px;
  background: var(--inset);
  border: 1px solid var(--line);
  /* The marker sits on the start edge, which mirrors with the page. */
  border-inline-start: 3px solid var(--line-hard);
  display: grid;
  gap: 6px;
}
.rev.current { border-inline-start-color: var(--accent); }
.rev.invalidated { border-inline-start-color: var(--bad-line); }
.rev-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rev-head strong { font-size: 14px; }
.rev-change { color: var(--ink-2); font-size: 13.5px; }
.rev-reason { color: var(--ink-3); font-size: 13px; }

/* ── 15. one overlay pattern, a bottom sheet on a phone ──────────────
   The `hidden` attribute is the open/close switch, as it is on every
   overlay in the node console. */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: end center;
  padding: 0;
  background: rgba(5, 9, 14, .74);
}
.overlay-card {
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-hard);
  border-start-start-radius: 14px;
  border-start-end-radius: 14px;
  padding: 18px;
  display: grid;
  gap: 16px;
}
@media (min-width: 620px) {
  .overlay { place-items: center; padding: 20px; }
  .overlay-card { max-width: 620px; border-radius: 14px; }
}
.overlay-head { display: flex; align-items: flex-start; gap: 12px; }
.overlay-head h2 { flex: 1 1 auto; }

/* ── 16. the login screen ────────────────────────────────────────────── */

.login {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--page);
}
.login-card {
  width: 100%;
  max-width: 420px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: clamp(18px, 5vw, 28px);
  display: grid;
  gap: 16px;
}
.login-brand { display: flex; align-items: center; gap: 10px; }

/* ── 17. the footer ──────────────────────────────────────────────────── */

.foot {
  padding: 14px var(--page) 28px;
  color: var(--ink-4);
  font-size: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  border-block-start: 1px solid var(--line-soft);
}

/* ── 19. the customer's app, in a phone frame ────────────────────────
   The frame is not decoration: this surface is only ever read on a phone,
   and reviewing it at desk width would hide exactly the problems it has.
   It borrows the panel's own colors rather than imitating Telegram's,
   because the real app follows Telegram's theme parameters at runtime and
   a hardcoded imitation here would be a third palette to keep in step. */

.phone-row { display: flex; justify-content: center; }
.phone {
  inline-size: 100%;
  max-inline-size: 390px;
  background: var(--panel);
  border: 1px solid var(--line-hard);
  border-radius: 20px;
  overflow: hidden;
  display: grid;
  /* The body scrolls inside the frame, as it does on the device. */
  grid-template-rows: auto auto minmax(0, 1fr);
  max-block-size: 760px;
}
.phone-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 15px;
  background: var(--inset);
  border-block-end: 1px solid var(--line);
}
.phone-who { color: var(--ink-3); font-size: 12.5px; }
.phone-nav { padding: 10px 12px 0; }
.phone-nav .seg { inline-size: 100%; }
.phone-nav .seg button { flex: 1 1 0; padding: 8px 6px; font-size: 12.5px; }
.phone-body { padding: 14px; overflow-y: auto; }
.phone-stack { display: grid; gap: 14px; }
.phone-card {
  padding: 12px 13px;
  border-radius: 10px;
  background: var(--inset);
  border: 1px solid var(--line);
  display: grid;
  gap: 7px;
}
