/* ============================================================================
   cmm-base.css — the shared base stylesheet.

   Loaded FIRST by every page; the page's own overlay stylesheet loads second.
   That layering is copied from the one place receivx does it correctly:
   Views/Imports/Index.cshtml loads exports.css then imports.css, and
   imports.css defines zero tokens of its own. Every other receivx page
   re-declares the whole token block, which is why their palette lives in nine
   files at once.

   Contents, in order:
     1.  Theme tokens (light / midnight / slate)
     2.  Base document, ambient background
     3.  Bootstrap overrides
     4.  Page shell
     5.  Buttons
     6.  Section container
     7.  Toolbar / filter bar / fields
     8.  Data table
     9.  Empty state
     10. Status badge
     11. Toast
     12. Footer
     13. App nav  (extracted from app-nav.js)
     14. Confirm modal
   ========================================================================== */


/* ============ 1. THEME TOKENS ==============================================
   Lifted verbatim from receivx. Their values are identical across all nine
   stylesheets that declare them — there is no drift to reconcile, only the
   duplication itself, which this file removes.

   ALL FIVE semantic tokens are defined here, in every theme:
       --ok  --warn  --error  --info  --text-muted   (+ their -bg variants)
   receivx leaves --ok/--warn/--error-bg/--info referenced-but-undefined in
   receiving.css and config.css, so those colours render unset on two of their
   biggest screens. Nothing here may reference a token this block does not
   define.
   ========================================================================== */
:root, [data-theme="light"] {
  --bg: #f6f5f1; --surface: #ffffff; --surface-2: #faf9f6; --surface-3: #f0eee8;
  --border: #e6e3dc; --border-bright: #d4d0c6;
  --text: #1a1d20; --text-dim: #5a626c; --text-muted: #8a8f97;
  --accent: #1f4d2b; --accent-bg: #e8f0ea; --accent-hover: #163a20; --accent-fg: #ffffff; --accent-2: #0a8a92;
  --error: #c0392b; --error-bg: #fceae7;
  --warn: #b86e00; --warn-bg: #fdf4e3;
  --ok: #2d7d4f; --ok-bg: #e7f3ec;
  --info: #1f6fb8; --info-bg: #e3eef9;
  --shadow-sm: 0 1px 2px rgba(20, 22, 24, 0.04);
  --shadow-md: 0 4px 16px rgba(20, 22, 24, 0.06);
  --shadow-lg: 0 20px 60px rgba(20, 22, 24, 0.12);
  --grid-line: rgba(0,0,0,0.018);
  --ambient-1: rgba(31, 77, 43, 0.04);
  --ambient-2: rgba(10, 138, 146, 0.03);
}
[data-theme="midnight"] {
  --bg: #0e1116; --surface: #161a21; --surface-2: #1c2128; --surface-3: #232932;
  --border: #2a313c; --border-bright: #3a4250;
  --text: #e6e8ea; --text-dim: #9aa3b0; --text-muted: #6b7382;
  --accent: #5fd49a; --accent-bg: rgba(95, 212, 154, 0.12); --accent-hover: #4dbf85; --accent-fg: #0e1116; --accent-2: #4cd4dc;
  --error: #ff6b6b; --error-bg: rgba(255,107,107,0.12);
  --warn: #f0a73a; --warn-bg: rgba(240,167,58,0.12);
  --ok: #5fd49a; --ok-bg: rgba(95,212,154,0.12);
  --info: #5aa8e8; --info-bg: rgba(90,168,232,0.12);
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
  --shadow-md: 0 4px 16px rgba(0,0,0,0.4);
  --shadow-lg: 0 20px 60px rgba(0,0,0,0.6);
  --grid-line: rgba(255,255,255,0.025);
  --ambient-1: rgba(95, 212, 154, 0.05);
  --ambient-2: rgba(76, 212, 220, 0.04);
}
[data-theme="slate"] {
  --bg: #eef1f4; --surface: #ffffff; --surface-2: #f7f9fb; --surface-3: #e9edf2;
  --border: #dce2e9; --border-bright: #c5cdd6;
  --text: #1c2530; --text-dim: #4a5562; --text-muted: #7d8794;
  --accent: #2e4d6b; --accent-bg: #e3ebf3; --accent-hover: #213a52; --accent-fg: #ffffff; --accent-2: #1e6fc4;
  --error: #c43b3b; --error-bg: #fbe7e7;
  --warn: #c47611; --warn-bg: #fef0dc;
  --ok: #2d8a5d; --ok-bg: #e0f0e7;
  --info: #1e6fc4; --info-bg: #e0edfa;
  --shadow-sm: 0 1px 3px rgba(28, 37, 48, 0.05);
  --shadow-md: 0 4px 18px rgba(28, 37, 48, 0.08);
  --shadow-lg: 0 24px 64px rgba(28, 37, 48, 0.15);
  --grid-line: rgba(28, 37, 48, 0.02);
  --ambient-1: rgba(46, 77, 107, 0.04);
  --ambient-2: rgba(30, 111, 196, 0.03);
}


/* ============ 2. BASE DOCUMENT ============================================ */
* { box-sizing: border-box; }
html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Roboto', 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px;
  line-height: 1.45;
  letter-spacing: -0.005em;
  min-height: 100vh;
  transition: background-color 0.25s ease, color 0.25s ease;
}
body { font-weight: 400; }
body::before {
  content: ''; position: fixed; inset: 0; z-index: 0;
  background:
    radial-gradient(ellipse 900px 500px at 15% 0%, var(--ambient-1), transparent 65%),
    radial-gradient(ellipse 700px 400px at 85% 100%, var(--ambient-2), transparent 65%);
  pointer-events: none;
}
body::after {
  content: ''; position: fixed; inset: 0; z-index: 0;
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 48px 48px;
  pointer-events: none;
}
.app { position: relative; z-index: 1; }


/* ============ 3. FORM CONTROLS (was: BOOTSTRAP OVERRIDES) =================
   REPLACES bootstrap.min.css, which every page used to fetch from
   cdn.jsdelivr.net on load. Same objection as the webfonts: on a locked-down
   network the request hangs or is blocked, and each page load told a third
   party that someone opened this application.

   WHAT BOOTSTRAP WAS ACTUALLY DOING HERE, and what had to be replaced:

     1. .form-control / .form-select box model. The block below only ever set
        COLOURS and a radius; the display, width, padding and border came from
        Bootstrap. Removing the link without this would have left the inputs
        as bare browser defaults.
     2. Three lines of its reboot the pages silently relied on - heading and
        paragraph margin normalisation, and form elements inheriting the page
        font. Without them headings gain a browser default top margin and every
        input reverts to the browser's own face, which is the change nobody
        would have connected to deleting a stylesheet.

   The values below are Bootstrap 5.3.3's, converted from rem to px at the
   16px root this application uses, so the rendered result is the same.
   ========================================================================== */

/* -- the reboot lines that were load-bearing, and only those -- */
h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; }
p { margin-top: 0; margin-bottom: 1rem; }
table { border-collapse: collapse; }
input, button, select, optgroup, textarea {
  font-family: inherit; font-size: inherit; line-height: inherit; margin: 0;
}

/* -- .form-control / .form-select -- */
.form-select, .form-control {
  display: block;
  width: 100%;
  padding: 6px 12px;              /* Bootstrap .375rem .75rem */
  line-height: 1.5;
  background-color: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 14px;
  border-radius: 8px;
  appearance: none;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

/* The chevron Bootstrap draws on a select once appearance:none removes the
   native one. Inlined as a data URI - it is a control affordance, not a
   typeface, and at ~200 bytes it costs nothing. Stroked in a neutral grey
   rather than Bootstrap's near-black so it stays visible on the midnight and
   slate themes, where the original was almost invisible against the surface. */
.form-select {
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%238a8f97' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 14px 11px;
}

.form-select:focus, .form-control:focus {
  background-color: var(--surface);
  color: var(--text);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
  outline: 0;
}
.form-control::placeholder { color: var(--text-muted); opacity: 1; }
.form-select:disabled, .form-control:disabled {
  background-color: var(--surface-3);
  opacity: 1;
  cursor: not-allowed;
}
/* The five Bootstrap .modal-* overrides that used to sit here are GONE. They were
   inherited from receivx and never applied to anything: this application's only
   dialog is window.confirmAction, which builds its own .confirm-modal-* DOM in
   app-nav.js and is styled in section 14. Verified at the injection sites rather
   than by grepping markup - a runtime-built dialog is invisible to a markup
   search - and every class those 18 sites inject was enumerated: not one is a
   Bootstrap modal class. With bootstrap.min.css gone they could not have worked
   anyway, since nothing was left to override. */


/* ============ 4. PAGE SHELL =============================================== */
.page-wrap { max-width: 1480px; margin: 24px auto; padding: 0 24px 72px; }
.page-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  margin-bottom: 22px; gap: 14px; flex-wrap: wrap;
}
.page-eyebrow {
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--accent); font-weight: 600; margin-bottom: 6px;
}
.page-title {
  font-size: 32px; font-weight: 500; letter-spacing: -0.025em;
  line-height: 1.05; color: var(--text); margin-bottom: 4px;
}
.page-subtitle { color: var(--text-dim); font-size: 14px; margin: 0; }


/* ============ 5. BUTTONS ================================================== */
.btn {
  border-radius: 8px;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-weight: 600; font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.08em;
  padding: 8px 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: all 0.15s ease;
  display: inline-flex; align-items: center; gap: 6px;
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
}
.btn:hover { background: var(--surface-2); border-color: var(--border-bright); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-fg); }
.btn-danger { color: var(--error); border-color: color-mix(in srgb, var(--error) 30%, var(--border)); }
.btn-danger:hover { background: color-mix(in srgb, var(--error) 10%, transparent); border-color: var(--error); }
/* A SECONDARY action that sits beside the thing it acts on - currently "Inspect",
   next to a filename in a table cell.

   IT USED TO BE INVISIBLE AT REST: transparent background, transparent border,
   revealing itself only on hover. An affordance nobody can see is an affordance
   nobody uses, and all ten Inspect links were on the page for a day without
   being findable except by Ctrl+F.

   IT WAS ALSO THE WRONG WAY ROUND. At 13px/600 it rendered LARGER and heavier
   than the 12px filename beside it, so the row read as three things of equal
   weight. The file is the subject; this is the action. It is now smaller and
   dimmer than the text it accompanies, and takes full contrast only on hover -
   discoverable at rest, subordinate always. */
.btn-icon {
  padding: 4px 8px;
  font-size: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  box-shadow: none;
}
.btn-icon:hover {
  background: var(--surface);
  border-color: var(--border-bright);
  color: var(--text);
}
.btn-icon.danger { color: var(--error); }
.btn-icon.danger:hover { background: var(--error-bg); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }


/* ============ 6. SECTION CONTAINER ======================================== */
.section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  margin-bottom: 18px;
}
.section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.section-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 500; color: var(--text);
}
.section-title i { color: var(--accent); }
.meta-line {
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace; font-size: 11px;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em;
}
.meta-line b { font-weight: 600; color: var(--text); }

/* ---- Section body + lead ---------------------------------------------------
   THE GAP THESE CLOSE. .section is a card with no body padding of its own: it
   only ever held .section-head plus .table-wrap or .empty-row, all of which pad
   themselves. Queue is the first page to put PROSE and FORMS inside a section,
   and without a padded container they sit flush against the card border.

   .section-lead is the explanatory paragraph under a section head. On Queue it
   is not decoration - the page's entire design is that every section says what
   it means and whether it resolves itself ("these will NEVER be retried
   automatically" / "the next cycle retries these on its own"). Eight sections,
   eight leads.

   Both are page-agnostic and belong here rather than in queue.css: Dashboard,
   History and Settings each acquire prose in a card the moment they stop being
   stubs. Adding them to a page overlay would be the first step towards the two
   vocabularies this convergence exists to prevent.
   -------------------------------------------------------------------------- */
.section-body { padding: 16px 20px; }

.section-lead {
  color: var(--text-dim); font-size: 13px;
  line-height: 1.55; margin: 0;
}
.section-lead strong { color: var(--text); font-weight: 600; }
.section-lead + .section-lead { margin-top: 8px; }

/* Left-aligned monospace for IDENTIFIERS - filenames, release ids, raw response
   bodies. .data-table td.num is already monospace but is right-aligned and
   numeric; this is the text form of the same idea, and the reason a filename
   should not simply borrow .num. */
.mono { font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace; font-size: 12px; }


/* ============ 7. TOOLBAR / FIELDS =========================================
   Flex, not grid. receivx has both — Transactions/Pos use a grid with an
   explicit per-page track list, Dashboard uses flex with a min-width on the
   select. The flex form is strictly more general (no per-page track list, it
   wraps naturally) and produces the same result, so that is the one kept.
   .field-group and .field-label are byte-identical across all three receivx
   files that declare them; taken verbatim.
   ========================================================================== */
.toolbar {
  display: flex; align-items: flex-end; flex-wrap: wrap; gap: 12px;
  padding: 14px 20px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.toolbar-spacer { flex: 1; }

/* ---- HOW WIDE EACH CONTROL IN A TOOLBAR WANTS TO BE ----------------------
   The layout above is the component; these are the sizes its children need,
   and they live here because .form-control is display:block; width:100% -
   right everywhere else in this application and wrong in a filter row, where
   it puts four controls and a button on five lines and fills the viewport
   before a single row of data appears. Filters are the way TO the data, not
   something to scroll past on the way.

   PROMOTED FROM history.css. They started there when History was the only page
   with a filter row, under a comment saying "only the widths are this page's".
   That stopped being true when Queue grew one: three pages want them now, they
   describe THIS file's component, and they had been copied rather than shared -
   which history.css's own header warned was how two stylesheets drift apart.
   -------------------------------------------------------------------------- */

/* Undo the 100% only inside a toolbar: elsewhere - Settings, the Queue resend
   and requeue forms - full-width stacked controls are exactly right, and this
   must not reach them. */
.toolbar .form-control,
.toolbar .form-select { width: auto; }

/* The text boxes take whatever is left after the short controls, down to a
   floor that still shows a release id or a file name. */
.toolbar input[type="text"].form-control { flex: 1 1 18rem; min-width: 11rem; }

/* Fixed and short: every option in these is one or two words. */
.toolbar .form-select { flex: 0 0 auto; min-width: 9.5rem; }

/* The two dates pair naturally - same width, side by side, wrapping together
   rather than one of them trailing onto a line of its own. */
.toolbar input[type="date"].form-control { flex: 0 0 auto; min-width: 9.5rem; }

/* The button belongs on the row with the controls it submits, not under them. */
.toolbar .btn { flex: 0 0 auto; }

/* ---- A LIVE WINDOW, AND THE MOMENT IT CLOSES -----------------------------
   A panel that reports whether this system can currently transmit renders BOTH
   states into the DOM and lets these rules decide which is visible. cmm-time.js
   is handed one absolute instant, decides one thing - has it passed - and
   toggles .is-lapsed on the panel. The badge, the border, the tint and the
   sentence all hang off that one class, so they change together or not at all.

   THE BUG THIS PREVENTS, on two pages in two shapes. The Dashboard's countdown
   was already client-side and correctly read "expired 53s ago" under a header
   still saying LIVE, because that sentence was rendered server-side at snapshot
   time and nothing re-evaluated it: one card, two opposite claims. Queue had no
   countdown at all - the whole banner was rendered once - so it never
   contradicted itself and was simply wrong, silently, on any tab left open past
   the expiry.

   :not() rather than toggling display back on, so nothing has to remember what
   each element's natural display was - a span in a meta line and a paragraph in
   a body restore differently, and hardcoding either is a bug waiting for
   whichever element got the wrong one.

   PROMOTED FROM dashboard.css, where they were copied into queue.css when the
   lease logic became shared. Two pages, one behaviour, one definition.
   -------------------------------------------------------------------------- */
.lease-panel:not(.is-lapsed) .lease-when-lapsed { display: none; }
.lease-panel.is-lapsed .lease-when-live { display: none; }

/* A countdown that has run out. Expiry is a different state, not a smaller
   number, so it is not merely dimmed. Applied by cmm-time.js on both pages,
   which is why the rule is here rather than twice with two different selectors. */
.is-expired { color: var(--text-muted); text-decoration: line-through; }
.field-group { display: flex; flex-direction: column; gap: 4px; }
.field-label {
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.15em;
  color: var(--text-muted); padding-left: 2px;
}
.field-group .form-select, .field-group .form-control { min-width: 170px; }

/* ---- Rhythm for STACKED controls -------------------------------------------
   MORE SPACE BETWEEN GROUPS THAN WITHIN ONE. That is the whole rule, and it is
   what turns a column of controls into readable structure instead of one
   undifferentiated block.

   .field-group's own 4px gap binds a label to its input - they are one thing.
   These rules separate one group from the NEXT, so a value control and a reason
   control read as two decisions rather than six stacked lines at identical
   pitch.

   Shared, not Settings-only: Queue's Requeue and Resend forms are the same
   shape (a control, a button, a note, inside a table cell) and were cramped in
   exactly the same way. Fixing it at the component is one change instead of
   three, and the next form built from these classes inherits it.

   General sibling (~) rather than adjacent (+) for the buttons: Save and Clear
   sit side by side, and with + only the first would take the top margin, which
   would knock the pair out of line with each other. */
.field-group ~ .field-group { margin-top: 14px; }
.field-group ~ .btn         { margin-top: 14px; }

/* A bare control followed by its button - the Queue shape, with no field-group. */
.form-control ~ .btn, .form-select ~ .btn { margin-top: 10px; }

/* A trailing explanation: after the form, or after the button it qualifies. */
form + .section-lead, .btn + .section-lead { margin-top: 10px; }

/* Buttons sharing a line. A single whitespace character between two inline-flex
   buttons is not a gap, it is a typo that happens to render. */
.btn + .btn { margin-left: 8px; }

/* ...except inside .toolbar, which already spaces its children with flex gap.
   The two together would double-space, and the toolbar's own value should win. */
.toolbar .btn + .btn { margin-left: 0; }
.search-box { position: relative; }
.search-box i {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); font-size: 14px; pointer-events: none;
}
.search-box .form-control { padding-left: 36px; }


/* ============ 8. DATA TABLE ===============================================
   Taken from receivx's transactions.css, which pos.css duplicates almost
   byte-for-byte — two independent files agreeing is the strongest signal of
   intent available. masters.css uses roomier padding and exports.css uses a
   different header size/colour; both are treated as drift and dropped in
   favour of this one. The comfortable-density variant is a modifier.
   ========================================================================== */
.table-wrap { overflow-x: auto; }
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.data-table thead th {
  background: var(--surface-2);
  color: var(--text-muted);
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.15em;
  text-align: left;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  position: sticky; top: 0; z-index: 1;
}
.data-table thead th.num { text-align: right; }
.data-table tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);

  /* TOP, not middle. Middle-aligning is invisible while every cell is one line, and wrong
     the moment one is not: a row whose Change column holds a stacked form is several times
     the height of its neighbours, and centring floats the status badge and the source in
     the middle of all that whitespace, anchored to nothing. The same applies to Queue,
     where a wrapped error message pushes its badge off the first line. Aligning to the top
     lines every cell up with the first line of the tallest one. */
  vertical-align: top;
}
.data-table tbody td.num {
  text-align: right;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-weight: 500;
}
.data-table tbody tr { transition: background 0.1s ease; }
.data-table tbody tr:hover { background: var(--surface-2); }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table [hidden] { display: none; }

/* Modifiers, opt-in per page. */
.data-table--wide { min-width: 1100px; }
.data-table--comfortable thead th { padding: 12px 20px; }
.data-table--comfortable tbody td { padding: 14px 20px; }
.data-table--clickable tbody tr { cursor: pointer; }


/* ============ 9. EMPTY STATE ============================================== */
.empty-row {
  padding: 48px 20px;
  text-align: center;
  color: var(--text-muted);
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.empty-row i { display: block; font-size: 32px; margin-bottom: 10px; opacity: 0.5; }


/* ============ 10. STATUS BADGE ============================================
   Cloned from receivx's exports.css with ONE structural change: the modifiers
   are SCOPED to the component.

       receivx:  .status-queued { ... }
       here:     .status-badge.status-queued { ... }

   receivx's unscoped modifiers colour any element that happens to carry the
   class — their dashboard.js emits a bare <span class="status-normal"> that is
   only safe because that page does not load exports.css. Scoping removes the
   accident.

   Five semantic levels, five tokens. Every application status maps onto one of
   these through the helper in js/status-badge.js — pages never build the class
   name by string interpolation.
   ========================================================================== */
.status-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 100px;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  font-weight: 600;
  white-space: nowrap;
}
.status-badge.status-ok      { background: var(--ok-bg);      color: var(--ok); }
.status-badge.status-info    { background: var(--info-bg);    color: var(--info); }
.status-badge.status-warn    { background: var(--warn-bg);    color: var(--warn); }
.status-badge.status-error   { background: var(--error-bg);   color: var(--error); }
.status-badge.status-neutral { background: var(--surface-3);  color: var(--text-muted); }

/* An unrecognised status is a BUG, and must look like one. Never muted, never
   unstyled. See js/status-badge.js — six of receivx's seven call sites render
   an unknown status as a silent unstyled pill and one renders "undefined". */
.status-badge.status-unknown {
  background: var(--error);
  color: #ffffff;
  outline: 2px dashed var(--error);
  outline-offset: 1px;
}


/* ============ 11. TOAST ===================================================
   receivx duplicates this into five stylesheets with 2px of drift between
   copies (Config uses bottom:28px, Dashboard uses 14px/20px padding). Taken
   from the three that agree.
   ========================================================================== */
.toast-host {
  position: fixed;
  bottom: 30px; left: 50%;
  transform: translateX(-50%);
  z-index: 1100;
  pointer-events: none;
}
.custom-toast {
  background: var(--surface);
  border: 1px solid var(--accent);
  border-left: 3px solid var(--accent);
  padding: 12px 18px;
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  display: flex; align-items: center; gap: 12px;
  opacity: 0; transform: translateY(20px);
  transition: all 0.3s ease;
  max-width: 400px;
}
.custom-toast.show { opacity: 1; transform: translateY(0); }
.custom-toast.danger { border-color: var(--error); border-left-color: var(--error); }
.custom-toast .icon-wrap {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--accent-bg); color: var(--accent);
  display: grid; place-items: center;
  font-size: 13px;
  flex-shrink: 0;
}
.custom-toast.danger .icon-wrap { background: var(--error-bg); color: var(--error); }
.custom-toast .msg { font-size: 13px; color: var(--text); }
.custom-toast .msg small {
  display: block;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 10px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.1em;
  margin-top: 2px;
}


/* ============ 12. FOOTER ================================================== */
.footer {
  padding: 12px 20px;
  background: var(--surface-2);
  border-top: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: center;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 11px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.1em;
}


/* ============ 13. APP NAV =================================================
   EXTRACTED from app-nav.js. In receivx this entire block lives as a ~585-line
   template string inside the script and is injected into <head> at runtime —
   45% of that file is CSS. Here it themes through the same cascade as
   everything else, and app-nav.js is ~700 lines of actual behaviour.
   ========================================================================== */
.app-nav {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow-sm);
  z-index: 50;
  transition: transform 0.25s ease, opacity 0.25s ease, width 0.25s ease;
}
.app-nav.position-horizontal {
  position: sticky; top: 0;
  display: flex; align-items: center;
  padding: 10px 24px; gap: 14px;
  border-bottom: 1px solid var(--border);
}
.app-nav.position-horizontal.behavior-static { position: relative; }
.app-nav.position-horizontal.behavior-hidden { transform: translateY(-100%); }

.app-nav.position-vertical {
  position: fixed; top: 0; left: 0; bottom: 0;
  width: 240px;
  display: flex; flex-direction: column;
  padding: 0 12px 14px; gap: 4px;
  border-right: 1px solid var(--border);
  box-shadow: var(--shadow-md);
}
.app-nav.position-vertical.collapsed { width: 64px; padding: 0 8px 14px; }
.app-nav.position-vertical.collapsed .app-nav-brand .name,
.app-nav.position-vertical.collapsed .app-nav-item .label,
.app-nav.position-vertical.collapsed .app-nav-trail { display: none; }
.app-nav.position-vertical.collapsed .app-nav-item { justify-content: center; padding: 10px 0; }
.app-nav.position-vertical.behavior-hidden { transform: translateX(-100%); }

body.has-vertical-nav .app { padding-left: 240px; transition: padding-left 0.25s ease; }
body.has-vertical-nav.nav-collapsed .app { padding-left: 64px; }
body.has-vertical-nav.nav-hidden .app { padding-left: 0; }

/* Utility bar — vertical mode only. [hamburger] ... [profile] */
.app-nav-utility {
  position: fixed; top: 0; right: 0; left: 240px; height: 56px;
  display: none; align-items: center; justify-content: space-between;
  gap: 10px; padding: 0 20px;
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--border);
  z-index: 45;
  transition: left 0.25s ease;
}
.app-nav-utility-left, .app-nav-utility-right { display: flex; align-items: center; gap: 10px; }
body.has-vertical-nav .app-nav-utility { display: flex; }
body.has-vertical-nav.nav-collapsed .app-nav-utility { left: 64px; }
body.has-vertical-nav.nav-hidden .app-nav-utility { left: 0; }
body.has-vertical-nav .app { padding-top: 56px; }

/* Brand */
.app-nav-brand {
  display: flex; align-items: center; gap: 10px;
  text-decoration: none; color: var(--text);
  padding: 4px 6px; border-radius: 8px;
}
.app-nav-brand:hover { color: var(--text); }
.app-nav-brand .logo {
  width: 32px; height: 32px; border-radius: 8px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: grid; place-items: center;
  color: var(--accent-fg);
  box-shadow: var(--shadow-sm);
}
.app-nav-brand .logo i { font-size: 16px; }
.app-nav-brand .name { font-size: 16px; font-weight: 500; letter-spacing: -0.01em; line-height: 1; }
.app-nav-brand .ver {
  display: block;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 9px; font-weight: 600;
  color: var(--accent);
  text-transform: uppercase; letter-spacing: 0.18em;
  margin-top: 2px;
}
.app-nav.position-vertical .app-nav-brand {
  height: 56px; padding: 0 8px; margin-bottom: 8px; gap: 12px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.app-nav.position-vertical .app-nav-brand .logo { width: 36px; height: 36px; border-radius: 9px; }
.app-nav.position-vertical .app-nav-brand .logo i { font-size: 18px; }
.app-nav.position-vertical .app-nav-brand .name { font-size: 15px; }
.app-nav.position-vertical.collapsed .app-nav-brand { padding: 0; justify-content: center; }

/* Menu */
.app-nav-menu { display: flex; gap: 2px; flex: 1; }
.app-nav.position-vertical .app-nav-menu { flex-direction: column; flex: 1; gap: 3px; }
.app-nav.position-vertical .app-nav-item { width: 100%; padding: 10px 14px; font-size: 14px; gap: 12px; }
.app-nav.position-vertical .app-nav-item i { font-size: 17px; }
.app-nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; border-radius: 8px;
  text-decoration: none; color: var(--text-dim);
  font-size: 14px; font-weight: 500;
  transition: all 0.12s ease;
  white-space: nowrap;
}
.app-nav-item i { font-size: 16px; opacity: 0.85; }
.app-nav-item:hover { background: var(--surface-2); color: var(--text); }
.app-nav-item.active { background: var(--accent-bg); color: var(--accent); }
.app-nav-item.active i { opacity: 1; }

/* Trailing controls */
.app-nav-trail { display: flex; align-items: center; gap: 10px; }
.app-nav.position-vertical .app-nav-trail { display: none; }

/* Hamburger — split button with a divider one third from the left. */
.app-nav-hamburger {
  background: var(--surface);
  border: 0.5px solid var(--border);
  width: 56px; height: 36px;
  border-radius: 8px;
  display: flex; align-items: stretch;
  padding: 0; overflow: hidden;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.18s ease;
  flex-shrink: 0;
}
.app-nav-hamburger::before {
  content: ''; width: 33.33%;
  border-right: 0.5px solid var(--border);
  flex-shrink: 0;
  transition: border-right-color 0.15s ease;
}
.app-nav-hamburger:hover { border-color: var(--border-bright); box-shadow: var(--shadow-md); }
.app-nav-hamburger:hover::before { border-right-color: var(--border-bright); }
.app-nav-hamburger:active { transform: scale(0.96); }
.app-nav-chevron-wrap { width: 66.67%; display: inline-flex; align-items: center; justify-content: center; }
.app-nav-chevron-icon {
  font-size: 14px; line-height: 1; color: var(--text-dim);
  transition: transform 0.25s ease, color 0.15s ease;
}
.app-nav-hamburger:hover .app-nav-chevron-icon { color: var(--text); }
body.has-vertical-nav.nav-collapsed .app-nav-chevron-icon { transform: rotate(180deg); }

/* Profile */
.app-nav-profile { position: relative; }
.app-nav-profile-trigger {
  display: flex; align-items: center; gap: 10px;
  padding: 4px 12px 4px 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 100px;
  cursor: pointer;
  transition: all 0.15s ease;
  box-shadow: var(--shadow-sm);
}
.app-nav-profile-trigger:hover { background: var(--surface-2); border-color: var(--border-bright); }
.app-nav-profile-trigger .avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-fg);
  display: grid; place-items: center;
  font-weight: 700; font-size: 11px;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
}
.app-nav-profile-trigger .who { display: flex; flex-direction: column; gap: 1px; line-height: 1.1; }
.app-nav-profile-trigger .who-name { font-size: 13px; color: var(--text); font-weight: 500; }
.app-nav-profile-trigger .who-sub {
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace; font-size: 10px;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em;
}
.app-nav-profile-trigger .chev {
  color: var(--text-muted); font-size: 13px; margin-left: 4px;
  transition: transform 0.2s ease, color 0.15s ease;
}
.app-nav-profile-trigger[aria-expanded="true"] { background: var(--surface-2); border-color: var(--border-bright); }
.app-nav-profile-trigger[aria-expanded="true"] .chev { transform: rotate(180deg); color: var(--accent); }

.app-nav-profile-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  background: var(--surface);
  border: 1px solid var(--border-bright);
  border-radius: 12px;
  min-width: 260px; padding: 6px;
  box-shadow: var(--shadow-lg);
  display: none; z-index: 60;
}
.app-nav-profile-menu.open { display: block; }
.app-nav-profile-card {
  padding: 14px 14px 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}
.app-nav-profile-card .name-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.app-nav-profile-card .big-avatar {
  width: 38px; height: 38px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-fg);
  display: grid; place-items: center;
  font-weight: 700; font-size: 14px;
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
}
.app-nav-profile-card .name-big { font-size: 14px; font-weight: 500; color: var(--text); line-height: 1.2; }
.app-nav-profile-card .meta-line {
  font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace; font-size: 11px;
  color: var(--text-dim); padding: 3px 0;
  display: flex; justify-content: space-between; gap: 12px;
  text-transform: none; letter-spacing: 0;
}
.app-nav-profile-card .meta-line span:first-child {
  color: var(--text-muted); text-transform: uppercase;
  letter-spacing: 0.1em; font-size: 10px;
}
.app-nav-profile-menu-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 7px;
  text-decoration: none; color: var(--text);
  font-size: 13px; cursor: pointer;
  border: none; background: transparent;
  width: 100%; text-align: left;
}
.app-nav-profile-menu-item:hover { background: var(--surface-2); color: var(--text); }
.app-nav-profile-menu-item i { color: var(--text-muted); font-size: 14px; }
.app-nav-profile-menu-item.danger { color: var(--error); }
.app-nav-profile-menu-item.danger i { color: var(--error); }
.app-nav-profile-divider { height: 1px; background: var(--border); margin: 4px 0; }

/* Theme submenu */
.app-nav-theme-toggle {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border: none; background: transparent;
  color: var(--text); font-size: 13px; border-radius: 7px;
  cursor: pointer; text-align: left;
}
.app-nav-theme-toggle:hover { background: var(--surface-2); }
.app-nav-theme-toggle > i:first-child { color: var(--text-muted); font-size: 14px; }
.app-nav-theme-toggle .theme-current {
  margin-left: auto; font-family: 'Roboto Mono', ui-monospace, Consolas, 'Cascadia Mono', Menlo, 'DejaVu Sans Mono', monospace;
  font-size: 11px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.08em;
}
.app-nav-theme-toggle .submenu-chev { color: var(--text-muted); font-size: 12px; transition: transform 0.2s ease; }
.app-nav-theme-toggle[aria-expanded="true"] .submenu-chev { transform: rotate(90deg); color: var(--accent); }
.app-nav-theme-submenu {
  display: none; flex-direction: column; gap: 2px;
  padding: 4px 4px 6px 4px; margin: 2px 0 4px;
  background: var(--surface-2); border-radius: 7px;
}
.app-nav-theme-submenu.open { display: flex; }
.app-nav-theme-option {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px; border: none; background: transparent;
  color: var(--text); font-size: 13px; border-radius: 6px;
  cursor: pointer; text-align: left; width: 100%;
}
.app-nav-theme-option:hover { background: var(--surface); }
.app-nav-theme-option .dot { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; }
.app-nav-theme-option .dot.light    { background: linear-gradient(135deg, #f6f5f1 50%, #ffffff 50%); border: 1px solid #d4d0c6; }
.app-nav-theme-option .dot.midnight { background: linear-gradient(135deg, #0e1116 50%, #5fd49a 50%); }
.app-nav-theme-option .dot.slate    { background: linear-gradient(135deg, #eef1f4 50%, #2e4d6b 50%); }
.app-nav-theme-option .label { flex: 1; }
.app-nav-theme-option .check { color: var(--accent); font-size: 14px; display: none; }
.app-nav-theme-option.active { background: var(--accent-bg); color: var(--accent); }
.app-nav-theme-option.active .check { display: inline; }

/* Floating hamburger + reveal bar, shown only when the nav is hidden */
.app-nav-floating-hamburger {
  position: fixed; top: 14px; left: 14px; z-index: 49;
  display: none; box-shadow: var(--shadow-md);
}
.app-nav-floating-hamburger:hover { transform: scale(1.05); }
.nav-hidden .app-nav-floating-hamburger { display: flex; }
.app-nav-reveal-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 6px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 35%, transparent), transparent);
  z-index: 48; display: none;
}
.nav-hidden .app-nav-reveal-bar { display: block; }
.nav-hidden.position-vertical .app-nav-reveal-bar {
  top: 0; left: 0; bottom: 0; right: auto;
  width: 6px; height: auto;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 35%, transparent), transparent);
}

@media (max-width: 700px) {
  .app-nav.position-horizontal { padding: 10px 14px; gap: 8px; }
  .app-nav-item .label { display: none; }
  .app-nav-profile-trigger .who { display: none; }
  .app-nav-hamburger { width: 62px; height: 40px; }
}


/* ============ 14. CONFIRM MODAL ===========================================
   Extracted from app-nav.js's second IIFE, which self-injected it. Layout
   tokens track the theme; the semantic tints are intentionally hard-coded so
   danger / warning / info read identically in all three themes.
   ========================================================================== */
.confirm-modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.45);
  z-index: 1080;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
}
.confirm-modal-card {
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  max-width: 420px; width: 100%;
  padding: 24px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  color: var(--text);
}
.confirm-modal-icon {
  width: 36px; height: 36px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1;
}
.confirm-modal-icon.danger  { background: #FCEBEB; color: #A32D2D; }
.confirm-modal-icon.warning { background: #FAEEDA; color: #854F0B; }
.confirm-modal-icon.info    { background: #E6F1FB; color: #0C447C; }
.confirm-modal-title { font-size: 16px; font-weight: 500; line-height: 1.4; margin: 14px 0 6px 0; }
.confirm-modal-message { font-size: 13px; line-height: 1.6; color: var(--text-muted); margin: 0; }
.confirm-modal-details {
  background: var(--surface-2); border-radius: 6px;
  padding: 12px 14px; margin-top: 14px; font-size: 13px;
}
.confirm-modal-details .cm-row { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; }
.confirm-modal-details .cm-label { color: var(--text-muted); }
.confirm-modal-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
.confirm-modal-btn {
  padding: 8px 16px; border-radius: 6px; font-size: 13px;
  cursor: pointer; border: 1px solid; line-height: 1.4;
  font-family: inherit;
}
.confirm-modal-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.confirm-modal-btn-cancel { background: transparent; border-color: var(--border); color: var(--text); }
.confirm-modal-btn-cancel:hover { background: var(--surface-2); }
.confirm-modal-btn-confirm { background: var(--accent); border-color: var(--accent); color: #fff; }
.confirm-modal-btn-confirm.danger { background: #E24B4A; border-color: #A32D2D; color: #fff; }
.confirm-modal-btn-confirm.danger:hover { background: #C13F3E; }

/* The nav's LIVE pill (app-nav.js). Positioned, not restyled: it is the shared
   .status-badge, and this only keeps it from sitting flush against the brand. */
.app-nav-live { margin-left: 10px; align-self: center; }
