/* ============================================================================
   operator-xnet.css — Liva Operator Platform · XNet design system (v1)
   ----------------------------------------------------------------------------
   The new operator design language, extracted faithfully from the approved
   "XNet Operator Platform" mockup (2026-07-18). Flat, editorial, near-square,
   Barlow / Barlow Condensed, slate-blue accent, crosshair registration marks,
   master-detail drawers, KPI stat tiles and a command bar.

   This layer is ADDITIVE and self-contained: it defines its own `--x-*` token
   namespace (so it never fights the legacy `--lv-*` layer) and is scoped under
   the `.xnet` root class that inc_header sets on <body>. Legacy pages that do
   NOT carry `.xnet` are untouched, so the rollout can proceed page-by-page.

   Companion doc: docs/operator-modernization/OP-XNET-design-system.md
   ========================================================================== */

/* ----- Fonts (loaded via <link> in inc_header; Kanit kept as fallback) ---- */

:root {
  /* Surfaces */
  --x-bg:          #f2f2f3;   /* app canvas                              */
  --x-surface:     #ffffff;   /* cards / panels                          */
  --x-surface-2:   #f5f5f8;   /* subtle inset panel                      */
  --x-surface-3:   #e9e9ea;   /* topbar / raised chrome                  */
  --x-tint:        #eef6ff;   /* primary tint — copilot / selected row   */

  /* Ink */
  --x-ink:         #1d1f20;                              /* primary text */
  /* ⭐ TEXT SCALE REBUILT SO ALL THREE TIERS CLEAR AA. Measured against the eight neutral
     grounds these tokens actually render on (white, #f5f5f8, #f6f6f6, #f2f2f3 light;
     #1e2124, #24282c, #2b3035, #16181a dark).
     ⛔ THE TERTIARY TIER COULD NOT SURVIVE AA AT ITS OLD VALUE. At .42 its worst ground was
     2.54; the AA floor is .63 — which was EXACTLY the secondary's value, so enforcing AA
     collapsed the two tiers into one indistinguishable pair. Shifting the whole scale is the
     only arrangement where three tiers exist AND all three pass.
       light  tertiary .64 (4.78-4.99) · secondary .80 (8.06-8.65) · primary 1.0 (14.79-16.55)
       dark   tertiary .58 (4.79-5.65) · secondary .78 (7.32-9.25) · primary 1.0 (10.95-14.63)
     ⚠ Secondary text darkens. That is the cost, it is deliberate, and it only ever RAISES
     contrast — no consumer of either token can be made worse by it on a neutral ground. */
  --x-ink-2:       rgba(29,31,32,.80);                  /* secondary    */
  /* ⭐ .62 -> .63 on 2026-08-31. At .62 the secondary ink measured 4.49:1 on the footer
     ground (#f2f2f3) and 4.54 on the inset panel — short of, and sitting right on, the 4.5
     line. At .63 the worst ground is 4.63 and the best 4.83. One alpha point is visually
     nil and can only ever RAISE contrast, never lower it. */
  --x-ink-3:       rgba(29,31,32,.64);                  /* tertiary     */
  --x-line:        rgba(29,31,32,.16);                  /* divider      */
  --x-line-2:      rgba(29,31,32,.08);                  /* hairline     */

  /* Brand — slate blue */
  --x-primary:     #5980a6;
  --x-primary-600: #4a6b8a;
  --x-primary-700: #2c455d;   /* deep slate — active nav, strong CTAs    */
  --x-on-primary:  #f6f8fb;

  /* Status */
  --x-success:     #1f5f3f;   --x-success-bg: #e7f2ec;
  --x-warning:     #8a5a00;   --x-warning-bg: #f6eeda;
  --x-danger:      #8a2b2b;   --x-danger-bg:  #f7e2e2;
  /* INK tokens — the FOREGROUND halves of --x-primary-700 / --x-danger.
     Those two are used in BOTH roles: as a background (with color:#fff) and as a
     text colour. Only the text role can follow the theme — flipping the token
     itself would turn .x-btn--strong and .modal-header.bg-primary into white on
     white. So the foreground gets its own token, and the background keeps the
     original. --x-tint-hover exists for the same reason: the hover was a hardcoded
     #e4f0ff, which in dark mode put a light background under light ink. */
  --x-primary-ink: #2c455d;  /* = --x-primary-700 in light; goes light in dark   */
  --x-danger-ink:  #8a2b2b;  /* = --x-danger in light; goes light in dark        */
  --x-tint-hover:  #e4f0ff;  /* hover partner for --x-tint, per theme            */
  --x-success-ink: #1f5f3f;  /* = --x-success in light; goes light in dark      */
  --x-warning-ink: #8a5a00;  /* = --x-warning in light; goes light in dark      */
  --x-primary-ink-hover: #1d3448; /* link hover: darker in light, BRIGHTER in dark */
  --x-info:        #2c455d;   --x-info-bg:    #eef6ff;

  /* Radius — deliberately near-square */
  --x-r-sm: 2px;
  --x-r-md: 4px;
  --x-r-lg: 7px;
  --x-r-pill: 999px;

  /* Elevation */
  --x-shadow-sm: 0 1px 2px rgba(43,43,45,.14);
  --x-shadow-md: 0 3px 10px rgba(43,43,45,.14);
  --x-shadow-lg: 0 12px 32px rgba(43,43,45,.20);

  /* Type */
  --x-font-body:    "Barlow", system-ui, -apple-system, sans-serif;
  --x-font-head:    "Barlow Condensed", system-ui, sans-serif;

  /* Layout */
  --x-rail-w: 232px;
  --x-topbar-h: 64px;
  --x-gap: 20px;
}

/* ============================================================================
   1. Base / reset (scoped to .xnet so legacy pages are untouched)
   ========================================================================== */

body.xnet {
  margin: 0;
  background: var(--x-bg);
  color: var(--x-ink);
  font-family: var(--x-font-body);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.xnet h1, .xnet h2, .xnet h3, .xnet h4, .xnet .x-head {
  font-family: var(--x-font-head);
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--x-ink);
  margin: 0;
}

.xnet a:not(.x-btn) { color: var(--x-primary-ink); text-decoration: none; }
.xnet a:not(.x-btn):hover { color: var(--x-primary-ink-hover); }

.xnet ::selection { background: rgba(89,128,166,.25); }

/* Eyebrow / small-caps label */
.x-eyebrow {
  font-family: var(--x-font-body);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--x-ink-2);
}

/* Page head — title block left, primary action right.
   15 templates already mark this up as a two-column header (a title <div> + an action), but the
   class had NO rule anywhere in css/, so it fell back to display:block and every action stacked
   UNDER the title at the left edge. On Inventory > Itinerary that also broke the "New route"
   dropdown: .dropdown-menu-right makes Popper place the menu 'bottom-end' (right edge on the
   button's right edge), so from a left-hand button the 160px menu opened at x=205 while the rail
   ends at x=232 — 27px of it behind the rail, which wins on z-index (1030 > 1000) and clipped the
   items. Restoring the intended row puts the button back on the right, inside the content column.
   :last-child:not(:first-child) — several pageheads gate their action behind a privilege check
   (roles/list.php), and a lone title must not be pushed right when the action isn't rendered. */
.x-pagehead {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.x-pagehead > :last-child:not(:first-child) { margin-left: auto; }

/* ============================================================================
   2. App shell — rail + topbar + content
   ========================================================================== */

.x-app { min-height: 100vh; }

/* ---- Left rail ---- */
.x-rail {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--x-rail-w);
  background: var(--x-surface);
  border-right: 1px solid var(--x-line);
  display: flex;
  flex-direction: column;
  z-index: 1030;
}
.x-rail__brand {
  display: flex; align-items: center; gap: 10px;
  height: var(--x-topbar-h);
  padding: 0 20px;
  border-bottom: 1px solid var(--x-line-2);
  flex: 0 0 auto;
}
.x-rail__logo {
  width: 30px; height: 30px; border-radius: var(--x-r-md);
  background: var(--x-primary-700); color: #fff;
  display: grid; place-items: center;
  font-family: var(--x-font-head); font-weight: 600; font-size: 16px;
  flex: 0 0 auto;
}
/* The rail brand is the OPERATOR's identity (their logo + company name), not the
   platform's. When the vendor has an uploaded logo we show it plain — no coloured
   tile behind it, since an operator logo carries its own background. Without one we
   fall back to the initial-letter tile above. */
.x-rail__logo--img { background: transparent; overflow: hidden; }
.x-rail__logo--img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.x-rail__brand-name {
  font-family: var(--x-font-head); font-weight: 600; font-size: 21px;
  letter-spacing: -.01em;
  /* Operator names are arbitrary length and the rail is ~250px (user-resizable),
     so the name truncates instead of wrapping the 56px-tall brand row. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.x-rail__nav { flex: 1 1 auto; overflow-y: auto; padding: 12px 12px; }
.x-rail__foot { flex: 0 0 auto; padding: 12px; border-top: 1px solid var(--x-line-2); }

.x-nav-item {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 12px;
  margin-bottom: 2px;
  border-radius: var(--x-r-md);
  color: var(--x-ink-2);
  font-weight: 500;
  cursor: pointer;
  position: relative;
  transition: background .12s, color .12s;
}
.x-nav-item .feather { width: 18px; height: 18px; stroke-width: 2; }
.x-nav-item:hover { background: var(--x-surface-2); color: var(--x-ink); }
.x-nav-item.is-active {
  background: var(--x-primary-700);
  color: #fff;
}
.x-nav-item.is-active .feather { color: #fff; }
.x-nav-item__badge {
  margin-left: auto;
  background: var(--x-danger); color: #fff;
  font-family: var(--x-font-head); font-weight: 600;
  font-size: 11px; line-height: 1;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: var(--x-r-pill);
  display: inline-flex; align-items: center; justify-content: center;
}
/* QA F1. White on this badge measured 4.49 against a 4.5 bar - it MISSED, by 0.01, and the
   live audit could never see it because .x-nav-item__badge only renders when its counter is
   non-zero and no counter was non-zero on the tier. The lighter the veil, the worse the
   contrast, so the fix is LESS white, not more. .22 gives 5.27; .26 would have cleared the bar
   at 4.75 but a fix sitting a quarter of a point above the line is one token nudge from
   failing again, and this one already arrived at 4.49 that way. */
.x-nav-item.is-active .x-nav-item__badge { background: rgba(255,255,255,.22); }

/* Collapsible nav sections (transition: sub-pages until pages consolidate) */
.x-nav-sect { cursor: pointer; }
.x-nav-sect .x-nav-caret { margin-left: auto; width: 16px; height: 16px; transition: transform .15s; color: var(--x-ink-3); }
.x-nav-item__badge + .x-nav-caret { margin-left: 8px; }
.x-nav-sect[aria-expanded="true"] .x-nav-caret, .x-nav-sect.x-open .x-nav-caret { transform: rotate(180deg); }
/* Dashboard section header NAVIGATES (it is the operator home page) while the group still expands,
   so its caret lives in a sibling <button> instead of inside the anchor. The row keeps the anchor
   full-width and parks the button on the right; the caret rotation is keyed on the BUTTON's
   aria-expanded, because the .x-nav-sect[aria-expanded] rule above can no longer see it. */
.x-nav-sect-row { display: flex; align-items: center; }
.x-nav-sect-row .x-nav-sect { flex: 1 1 auto; min-width: 0; }
.x-nav-caret-btn {
  flex: 0 0 auto;
  background: none; border: 0; padding: 9px 8px; margin-bottom: 2px;
  border-radius: var(--x-r-md); cursor: pointer; color: var(--x-ink-3);
  display: flex; align-items: center;
}
.x-nav-caret-btn:hover { background: var(--x-surface-2); }
.x-nav-caret-btn .x-nav-caret { width: 16px; height: 16px; transition: transform .15s; }
.x-nav-caret-btn[aria-expanded="true"] .x-nav-caret { transform: rotate(180deg); }

.x-nav-sub { padding-left: 12px; margin-bottom: 4px; }
.x-nav-sub .x-nav-item { font-size: 13.5px; padding: 7px 12px; }
.x-nav-sub .x-nav-item .feather { width: 16px; height: 16px; }

/* Tenant menu opens upward from the rail foot */
.x-tenant-wrap .dropdown-menu { box-shadow: var(--x-shadow-md); border: 1px solid var(--x-line); }
.x-tenant-wrap .x-tenant { cursor: pointer; }

/* Copilot launcher (rail foot) */
.x-copilot-launch {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: var(--x-tint);
  border: 1px solid rgba(89,128,166,.35);
  border-radius: var(--x-r-md);
  color: var(--x-primary-ink);
  font-weight: 600;
  cursor: pointer;
}
.x-copilot-launch:hover { background: var(--x-tint-hover); }
.x-copilot-launch .x-count {
  margin-left: auto;
  background: var(--x-primary-700); color: #fff;
  font-size: 11px; border-radius: var(--x-r-sm);
  padding: 1px 7px; font-family: var(--x-font-head);
}

/* Tenant switcher */
.x-tenant {
  margin-top: 10px;
  border: 1px solid var(--x-line);
  border-radius: var(--x-r-md);
  padding: 8px 12px;
  cursor: pointer;
  background: var(--x-surface);
}
.x-tenant__name { font-weight: 600; color: var(--x-primary-ink); font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.x-tenant__sub { font-size: 11px; color: var(--x-ink-2); }

/* Resize handle on the rail's right edge + collapse state (desktop) */
.x-rail__resize {
  position: absolute; top: 0; right: -3px; bottom: 0; width: 6px;
  cursor: col-resize; z-index: 3;
}
.x-rail__resize::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 2px;
  background: transparent; transition: background .12s;
}
.x-rail__resize:hover::after, body.x-rail-resizing .x-rail__resize::after { background: var(--x-primary); }
body.x-rail-resizing { cursor: col-resize; user-select: none; }
/* #4356 - the collapsed-rail preference is a DESKTOP idea (a 64px icon rail; inc_header.php sets it by default
   for every visitor). Below 901px the rail is the burger drawer, which .is-open opens. These three rules used
   to apply at every width, and body.x-rail-collapsed .x-rail (0,2,1) out-ranked the phone rule
   .x-rail.is-open (0,2,0), so on a phone the menu never slid in. Scoped to the same 901px breakpoint the inline
   mini-rail rules in inc_header.php use. */
@media (min-width: 901px) {
  body.x-rail-collapsed .x-nav-caret-btn { display: none; }
  body.x-rail-collapsed .x-rail { transform: translateX(-100%); overflow: hidden; }
  body.x-rail-collapsed .x-main { margin-left: 0; } /* moved unchanged; .x-main's own margin is still the physical margin-left */
}
.x-railtoggle {
  width: 36px; height: 36px; display: grid; place-items: center; flex: 0 0 auto;
  border: 1px solid var(--x-line); background: var(--x-surface); border-radius: var(--x-r-md);
  color: var(--x-ink-2); cursor: pointer;
}
.x-railtoggle:hover { color: var(--x-ink); border-color: var(--x-ink-3); }

/* ---- Topbar ---- */
.x-main {
  margin-left: var(--x-rail-w);
  min-height: 100vh;
  display: flex; flex-direction: column;
}
.x-topbar {
  height: var(--x-topbar-h);
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 18px;
  padding: 0 28px;
  border-bottom: 1px solid var(--x-line);
  background: var(--x-bg);
  position: sticky; top: 0; z-index: 1020;
}
/* Breadcrumb (#3478, owner ruling Q7). The top bar names the SECTION the page lives in; the page's own title is the
   H1 in the page. Mirrors the storefront's .lv-breadcrumb (flex list, small muted text, a "›" between items) from
   existing --x-* tokens only. Logical properties throughout, and the separator mirrors under dir="rtl". */
.x-breadcrumb {
  list-style: none; margin: 0; padding: 0; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-family: var(--x-font-body); font-size: 14px; color: var(--x-ink-3);
}
.x-breadcrumb[hidden] { display: none; }
.x-breadcrumb li { display: flex; align-items: center; gap: 6px; min-width: 0; }
.x-breadcrumb li:not(:last-child)::after { content: "\203A"; color: var(--x-ink-3); }
html[dir="rtl"] .x-breadcrumb li:not(:last-child)::after { transform: scaleX(-1); }
.x-breadcrumb a { color: var(--x-primary-ink); text-decoration: none; }
.x-breadcrumb a:hover { color: var(--x-primary-ink-hover); text-decoration: underline; }
.x-cmdbar {
  flex: 1 1 auto;
  max-width: 560px;
  display: flex; align-items: center; gap: 10px;
  height: 38px; padding: 0 14px;
  background: var(--x-surface-3);
  border: 1px solid transparent;
  border-radius: var(--x-r-md);
  color: var(--x-ink-3);
  cursor: text;
}
.x-cmdbar:focus-within { border-color: var(--x-primary); background: var(--x-surface); }
.x-cmdbar kbd {
  font-family: var(--x-font-body); font-size: 11px;
  background: var(--x-surface); border: 1px solid var(--x-line);
  border-radius: var(--x-r-sm); padding: 1px 6px; color: var(--x-ink-2);
}
.x-cmdbar input {
  flex: 1 1 auto; border: 0; background: transparent; outline: 0;
  font-family: var(--x-font-body); font-size: 14px; color: var(--x-ink);
}
.x-topbar__spacer { flex: 1 1 auto; }
.x-topbar__date { font-size: 13px; color: var(--x-ink-2); white-space: nowrap; }
.x-iconbtn {
  width: 38px; height: 38px; display: grid; place-items: center;
  border: 1px solid var(--x-line); background: var(--x-surface);
  border-radius: var(--x-r-md); color: var(--x-ink-2); cursor: pointer;
  position: relative;
}
.x-iconbtn:hover { color: var(--x-ink); border-color: var(--x-ink-3); }
.x-iconbtn__dot {
  position: absolute; top: 8px; right: 9px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--x-primary); border: 1.5px solid var(--x-surface);
}
/* Inline theme toggle (light / dark / system) — segmented control in the topbar. Token-driven so it
   adapts in dark mode automatically. Reuses the §6 display-pref plumbing (see inc_header). */
.x-themetoggle { display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  background: var(--x-surface-3); border-radius: var(--x-r-pill); }
.x-themetoggle__btn { width: 28px; height: 28px; display: grid; place-items: center;
  border: 0; background: none; border-radius: var(--x-r-pill); color: var(--x-ink-2); cursor: pointer; }
.x-themetoggle__btn:hover { color: var(--x-ink); }
.x-themetoggle__btn.is-active { background: var(--x-surface); color: var(--x-primary-ink); box-shadow: var(--x-shadow-sm); }
.x-themetoggle__btn .feather { width: 16px; height: 16px; }

/* ---- Content region ---- */
.x-content { flex: 1 1 auto; padding: 28px; }
.x-content--narrow { max-width: 1180px; }

/* Two-column split (main + side) */
.x-split {
  display: grid;
  grid-template-columns: 1fr 360px;
  gap: var(--x-gap);
  align-items: start;
}
.x-split--wide { grid-template-columns: 1fr 420px; }

/* ============================================================================
   3. Cards, stat tiles, crosshair registration marks
   ========================================================================== */

.x-card {
  background: var(--x-surface);
  border: 1px solid var(--x-line);
  border-radius: var(--x-r-lg);
  padding: 22px 24px;
}
.x-card--flush { padding: 0; overflow: hidden; }
.x-card__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 16px;
}
.x-card__title { font-family: var(--x-font-head); font-weight: 600; font-size: 20px; }

/* Crosshair marks — the signature registration "+" at the corners of a group */
.x-cross { position: relative; }
.x-cross::before, .x-cross::after,
.x-cross > .x-cross-b::before, .x-cross > .x-cross-b::after { content: none; }
.x-cross-marks { position: relative; }
.x-cross-marks > .xm {
  position: absolute; width: 11px; height: 11px; pointer-events: none;
  color: var(--x-ink-3);
}
.x-cross-marks > .xm::before, .x-cross-marks > .xm::after {
  content: ""; position: absolute; background: currentColor;
}
.x-cross-marks > .xm::before { left: 5px; top: 0; width: 1px; height: 11px; }
.x-cross-marks > .xm::after  { top: 5px; left: 0; height: 1px; width: 11px; }
.x-cross-marks > .xm-tl { top: -5px; left: -5px; }
.x-cross-marks > .xm-tr { top: -5px; right: -5px; }
.x-cross-marks > .xm-bl { bottom: -5px; left: -5px; }
.x-cross-marks > .xm-br { bottom: -5px; right: -5px; }

/* Stat tile row */
.x-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  border: 1px solid var(--x-line);
  border-radius: var(--x-r-lg);
  overflow: hidden;
  background: var(--x-surface);
}
.x-stat { padding: 18px 22px; border-left: 1px solid var(--x-line-2); }
.x-stat:first-child { border-left: 0; }
.x-stat__label { display: block; margin-bottom: 8px; }
.x-stat__value {
  font-family: var(--x-font-head); font-weight: 600; font-size: 30px;
  line-height: 1; letter-spacing: -.01em;
}
.x-stat__sub { font-size: 12px; color: var(--x-ink-2); margin-top: 6px; }
.x-stat__sub.is-pos { color: var(--x-success-ink); }
.x-stat__sub.is-warn { color: var(--x-warning-ink); }
.x-stat__sub.is-neg { color: var(--x-danger-ink); }

/* ============================================================================
   4. Tables / list rows (master-detail source)
   ========================================================================== */

.x-table { width: 100%; border-collapse: collapse; }
.x-table thead th {
  text-align: left;
  font-family: var(--x-font-body);
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--x-ink-2);
  padding: 12px 16px;
  border-bottom: 1px solid var(--x-line);
}
.x-table tbody td {
  padding: 14px 16px;
  border-bottom: 1px solid var(--x-line-2);
  vertical-align: middle;
}
.x-table tbody tr { cursor: pointer; transition: background .1s; }
.x-table tbody tr:hover { background: var(--x-surface-2); }
.x-table tbody tr.is-selected { background: var(--x-tint); box-shadow: inset 3px 0 0 var(--x-primary); }
.x-table tbody tr:last-child td { border-bottom: 0; }
.x-table .x-ref { color: var(--x-primary-ink); font-weight: 600; }
.x-table__hint {
  padding: 12px 16px; font-size: 12px; color: var(--x-ink-3);
  border-top: 1px solid var(--x-line-2);
}

/* Definition rows (drawer / detail) */
.x-dl { display: grid; grid-template-columns: auto 1fr; gap: 10px 16px; }
.x-dl dt { color: var(--x-ink-2); font-size: 13px; }
.x-dl dd { margin: 0; text-align: right; font-weight: 500; }

/* ============================================================================
   5. Pills / badges / chips
   ========================================================================== */

/* Scroll container for a table inside .x-card--flush.
   ⛔ NOT cosmetic. .x-card--flush is { padding:0; overflow:hidden }, so a table wider than its
   card is CLIPPED AND UNREACHABLE without this — not scrolled. Measured 2026-08-31: 8 templates
   already write div.x-table-wrap and then declare this exact rule in their OWN page-local <style>
   block; all 8 declarations are byte-identical and NO stylesheet defined it. A page-local dialect
   wearing the canonical x- prefix is the hazard this layer exists to remove.
   ADDITIVE BY MEASUREMENT: identical to the 8 copies it supersedes, so it cannot change their
   rendering; it only lets those templates delete their style block. */
.x-table-wrap { overflow-x: auto; }

.x-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 500; line-height: 1;
  padding: 5px 10px; border-radius: var(--x-r-pill);
  /* --x-surface-2, not -3: on #e9e9ea the secondary ink measures 4.48 — under the line.
     The inset surface clears it. Same correction already applied to .rs-pill and
     .badge-secondary. Variants (.x-pill--*) set their own ground and are unaffected. */
  background: var(--x-surface-2); color: var(--x-ink-2);
  white-space: nowrap;
}
/* #3523 — a pill that is an ACTION is a <button>, so the keyboard reaches it and Enter/Space
   activate it natively (the booking-detail dialog's View ticket / Edit / Cancel / Force cancel).
   Strip the UA button chrome so it renders as the span did; the focus ring reuses --x-primary-ink,
   which is dark on the light surface and light on the dark one. */
button.x-pill { border: 0; font-family: inherit; cursor: pointer; }
button.x-pill:focus:not(:focus-visible) { outline: 0; }
button.x-pill:focus-visible { outline: 2px solid var(--x-primary-ink); outline-offset: 2px; }
.x-pill--success { background: var(--x-success-bg); color: var(--x-success-ink, var(--x-success)); }
.x-pill--warning { background: var(--x-warning-bg); color: var(--x-warning-ink, var(--x-warning)); }
.x-pill--danger  { background: var(--x-danger-bg);  color: var(--x-danger-ink, var(--x-danger)); }
.x-pill--info    { background: var(--x-info-bg);    color: var(--x-info-ink, var(--x-info)); }
.x-pill--muted   { background: transparent; color: var(--x-ink-2); }

/* Filter chips (segmented) */
.x-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.x-chip {
  font-size: 13px; font-weight: 500;
  padding: 7px 14px; border-radius: var(--x-r-md);
  border: 1px solid transparent; background: transparent; color: var(--x-ink-2);
  cursor: pointer;
}
.x-chip:hover { background: var(--x-surface-2); color: var(--x-ink); }
/* colour moved to --x-primary-ink 2026-08-31: this was the ONLY text-role use of
   --x-primary-700 in the layer, and it is what forced user-prefs.css to flip that token
   in dark - which broke every BACKGROUND use of it. Foreground uses take the ink token. */
.x-chip.is-active { background: var(--x-surface); border-color: var(--x-primary); color: var(--x-primary-ink); }
.x-chip .x-chip__n { color: var(--x-ink-3); margin-left: 4px; }

/* ============================================================================
   6. Buttons
   ========================================================================== */

.x-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font-family: var(--x-font-head); font-weight: 600; font-size: 14px;
  letter-spacing: .01em;
  padding: 8px 16px;
  border-radius: var(--x-r-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  white-space: nowrap;
  text-decoration: none;
}
.x-btn .feather { width: 16px; height: 16px; }

/* Icon sizing inside a governance banner (gap G-B).
   ⭐ IT MIRRORS THE INLINE DECLARATION EXACTLY - width + flex, and DELIBERATELY NO HEIGHT - so
   the 43 call sites already carrying style="width:15px;flex:0 0 auto" render IDENTICALLY and can
   simply delete it. Adding height:15px would have been the obvious rule and it would have moved
   all 43 by 1px, because the inline style sets width and never height, so those icons are 15x16.
   ⛔ THE BLAST RADIUS WAS MEASURED STRUCTURALLY, NOT GREPPED, AFTER THREE TEXT SEARCHES GAVE
   THREE ANSWERS (40/34, 41/30, 43/31) AND A FOURTH GAVE 48/113. Live DOM across 29 pages:
   18 .x-gov elements, 10 icons inside, 10 already inline-sized, 0 unstyled. Element-scoped source
   parse over all 34 .x-gov templates: 65 elements, 43 inline-sized, 7 unstyled - all 7 in
   reports/view.php, one per mutually-exclusive $mode branch, so ONE renders per page load.
   ⛔ AND THE PREMISE EVERY LANE CARRIED WAS FALSE: "unstyled icons render at feather's 24px".
   operator-modern.css:306 already declares .feather{width:16px;height:16px} globally, so they
   render 16x16. This rule takes those ~7 from 16px to 15px wide - a 1px change on one icon per
   /reports load - and changes nothing else anywhere. */
.xnet .x-gov .feather { width: 15px; flex: 0 0 auto; }

/* ⛔ --x-primary CANNOT CARRY WHITE TEXT: #5980a6 light / #6f9fce dark measured 4.15 / 2.79.
   ⭐ ALIGNED WITH THE TREATMENT THE PLATFORM HAS CONVERGED ON. .btn-primary and
   .btn-outline-secondary both landed on #006fa7 / #005c8a — a value gds.css already carried as a
   hover — so this introduces no new colour and makes our own primary button agree with the
   Bootstrap one beside it.
   ⭐ THE STRONG VARIANT STAYS DISTINCT: .x-btn--strong is --x-primary-700 #2c455d, a deep SLATE.
   #006fa7 is a saturated BLUE. Different hue and lightness, so the two remain separable by eye
   and by role — primary action vs strong CTA. */
.x-btn--primary { background: #006fa7; border-color: #006fa7; color: #fff; }
.x-btn--primary:hover { background: #005c8a; border-color: #005c8a; color:#fff; }
.x-btn--strong { background: var(--x-primary-700); border-color: var(--x-primary-700); color: #fff; }
.x-btn--strong:hover { background: #24384c; color:#fff; }
.x-btn--ghost { background: var(--x-surface); border-color: var(--x-line); color: var(--x-ink); }
.x-btn--ghost:hover { border-color: var(--x-ink-3); color: var(--x-ink); }
.x-btn--tint { background: var(--x-tint); border-color: rgba(89,128,166,.35); color: var(--x-primary-ink); }
.x-btn--tint:hover { background: var(--x-tint-hover); color: var(--x-primary-ink); }
.x-btn--danger { background: var(--x-surface); border-color: var(--x-danger-ink); color: var(--x-danger-ink); }
.x-btn--danger:hover { background: var(--x-danger-bg); }
/* semantic action colours — same outline pattern as --danger, using the existing tokens */
.x-btn--success { background: var(--x-surface); border-color: var(--x-success-ink); color: var(--x-success-ink); }
.x-btn--success:hover { background: var(--x-success-bg); }
.x-btn--warning { background: var(--x-surface); border-color: var(--x-warning-ink); color: var(--x-warning-ink); }
.x-btn--warning:hover { background: var(--x-warning-bg); }
.x-btn--sm { padding: 5px 11px; font-size: 12.5px; }
.x-btn--block { width: 100%; }
.x-btn:disabled { opacity: .5; cursor: not-allowed; }

.x-btn-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* Link-style action */
.x-link { color: var(--x-primary-ink); font-weight: 600; cursor: pointer; }
.x-link:hover { color: var(--x-primary-ink-hover); }

/* ============================================================================
   7. Drawer (master-detail slide-in) & Copilot panel
   ========================================================================== */

.x-drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 420px; max-width: 92vw;
  background: var(--x-surface);
  border-left: 1px solid var(--x-line);
  box-shadow: var(--x-shadow-lg);
  transform: translateX(100%);
  transition: transform .22s cubic-bezier(.4,0,.2,1);
  z-index: 1050;
  display: flex; flex-direction: column;
}
.x-drawer.is-open { transform: translateX(0); }
.x-drawer__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 20px 22px 14px;
  border-bottom: 1px solid var(--x-line-2);
}
.x-drawer__title { font-family: var(--x-font-head); font-weight: 600; font-size: 26px; }
.x-drawer__body { flex: 1 1 auto; overflow-y: auto; padding: 20px 22px; }
.x-drawer__close {
  width: 30px; height: 30px; border: 0; background: transparent;
  color: var(--x-ink-2); cursor: pointer; border-radius: var(--x-r-md);
}
.x-drawer__close:hover { background: var(--x-surface-2); color: var(--x-ink); }
/* ⛔ THE BOX WAS STATED AND THE GLYPH WAS NOT. .x-drawer__close sizes a 30px button and says
   nothing about what sits inside it. Its four pre-existing call sites all hold
   <span data-feather="x">, an SVG that sizes itself — so the omission was invisible for as
   long as every caller happened to use an icon. Lane M3's four company drawers close with the
   `&times;` CHARACTER, which inherits body type at ~14px and reads as a stray mark in a 30px
   target. ⭐ P REOPENED ON MEASURED DEMAND: 4 templates, one declaration, and M3 had already
   written it page-locally in each — which is the signal that it belongs here instead.
   ⚠ Deliberately NOT "use a feather icon". inc_luggage records feather.replace() failing in
   the wild, so the character is the more robust choice; the layer should support it. */
.xnet .x-drawer__close { font-size: 20px; line-height: 1; display: inline-flex;
  align-items: center; justify-content: center; }

/* THE MISSING MEMBER OF AN EXISTING FAMILY. __head, __body, __title and __close all
   ship here; __foot never did, so every drawer that needed one hand-rolled it:
     booking/list.php + reservations/view.php   inline, byte-identical
     company/inc_businessconfig.php             .bc2-drawer__foot
     company/inc_companyprofile.php             .cp2-drawer__foot
     notificationsettings/view.php              .ns-drawer__foot
   Five drawers, four implementations, no two agreeing on padding or gap. The values
   below are the inline pair's EXACTLY, so the two templates migrating now do not move
   a pixel. The three locally-prefixed footers are deliberately NOT converged here:
   they differ in padding, gap and border token, so adopting these values would move
   their pixels for consistency's sake alone - a separate, evidence-led change.
   ONE INTENTIONAL DIFFERENCE: flex 0 0 auto. The inline version inherited 0 1 auto and
   would squash if the body ever overflowed; __body already claims 1 1 auto, and
   .x-rail__foot - the same role in the rail - already uses 0 0 auto. */
/* QA F4. TWO intentional differences from the inline original, not one. `flex: 0 0 auto` was
   documented above; `align-items: center` was NOT - the inline style declared no align-items and
   computed to `normal`. It moves no pixel HERE because both footer children have equal natural
   height, but it changes this primitive's contract: under `normal` a shorter sibling stretches
   to the tallest, under `center` it does not. That difference will surface when the three local
   footers converge - .ns-drawer__foot already declares center, .bc2- and .cp2- do not. */
.x-drawer__foot {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 10px;
  padding: 14px 22px;
  border-top: 1px solid var(--x-line-2);
}
/* QA F6: ZERO call sites today. Deliberately pre-built for the deferred convergence of the
   three local footers, all three of which right-align. Stated, not silent dead code. */
.x-drawer__foot--end { justify-content: flex-end; }

/* 520px is a VARIANT, not a new default: of five drawers two ask for it inline and two
   are happy on the 420px base. A modifier keeps the other two untouched. The 92vw
   max-width and the <=900px width:100% rule at 1079 still govern. */
.x-drawer--wide { width: 520px; }

/* QA F5, and it is the exact sin this programme exists to remove. .x-grow was added here as
   `flex: 1 1 auto` after checking operator-xnet.css and finding no duplicate. WRONG DENOMINATOR:
   the shell loads Bootstrap 4.3.1 FIRST (inc_header.php:92, entry 1 of sheets.js) and Bootstrap
   ships `.flex-fill{flex:1 1 auto!important}` - the same declaration, already loaded, and
   already used by profile, review, onboarding and notifications. A de-duplication programme had
   added a duplicate. The class is withdrawn and the two call sites use .flex-fill.
   THE RULE THAT FAILED: check a candidate primitive against the LOADED CASCADE, not against
   this file. sheets.js is the canonical list of what is loaded and is the right denominator. */
.x-backdrop {
  position: fixed; inset: 0; background: rgba(20,22,24,.28);
  opacity: 0; visibility: hidden; transition: opacity .2s; z-index: 1040;
}
.x-backdrop.is-open { opacity: 1; visibility: visible; }

/* Copilot card blocks */
.x-copilot-block {
  background: var(--x-surface-2);
  border-radius: var(--x-r-md);
  padding: 14px 16px; margin-bottom: 12px;
}
.x-copilot-block--accent { background: var(--x-tint); border-left: 3px solid var(--x-primary); }
.x-copilot-block__title { font-weight: 600; margin-bottom: 4px; }
.x-copilot-block__meta { font-size: 12px; color: var(--x-ink-2); margin-bottom: 10px; }
.x-copilot-note { font-size: 12px; color: var(--x-ink-3); margin-top: 8px; }

/* Governance banner (prepare-and-recommend actions) */
.x-gov {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: 12.5px; color: var(--x-ink-2);
  background: var(--x-surface-2); border: 1px dashed var(--x-line);
  border-radius: var(--x-r-md); padding: 10px 14px; margin-top: 8px;
}

/* ============================================================================
   8. Section header (page hero with anchor sub-nav)
   ========================================================================== */

.x-page-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 20px;
}
.x-page-head__title { font-family: var(--x-font-head); font-weight: 600; font-size: 30px; }
.x-anchornav {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 1px solid var(--x-line);
  margin-bottom: 22px;
}
.x-anchornav a {
  padding: 10px 14px; font-weight: 500; color: var(--x-ink-2);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.x-anchornav a:hover { color: var(--x-ink); }
.x-anchornav a.is-active { color: var(--x-primary-ink); border-bottom-color: var(--x-primary); }

/* ============================================================================
   9. Forms
   ========================================================================== */

/* superseded by section 9b below — see OPERATOR-UI-CONSISTENCY Phase 2 */

/* Toggle */
.x-toggle { position: relative; display: inline-block; width: 40px; height: 22px; }
.x-toggle input { display: none; }
.x-toggle__track {
  position: absolute; inset: 0; background: var(--x-line); border-radius: var(--x-r-pill);
  transition: background .15s;
}
.x-toggle__track::before {
  content: ""; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
  background: #fff; border-radius: 50%; transition: transform .15s; box-shadow: var(--x-shadow-sm);
}
.x-toggle input:checked + .x-toggle__track { background: var(--x-primary); }
.x-toggle input:checked + .x-toggle__track::before { transform: translateX(18px); }

/* Day picker */
.x-days { display: flex; gap: 6px; }
.x-day {
  width: 40px; height: 34px; display: grid; place-items: center;
  border: 1px solid var(--x-line); border-radius: var(--x-r-md);
  font-size: 12.5px; color: var(--x-ink-2); cursor: pointer;
}
.x-day.is-on { background: var(--x-primary); border-color: var(--x-primary); color: #fff; }

/* ============================================================================
   10. Simple bar chart (Growth)
   ========================================================================== */

.x-bars { display: flex; align-items: flex-end; gap: 10px; height: 150px; }
.x-bars .x-bar { flex: 1 1 0; background: var(--x-primary); border-radius: var(--x-r-sm) var(--x-r-sm) 0 0; min-width: 8px; opacity: .85; }
.x-bars .x-bar:hover { opacity: 1; }

/* Utilities */
.x-mt-0 { margin-top: 0; } .x-mt-1 { margin-top: 8px; } .x-mt-2 { margin-top: 16px; } .x-mt-3 { margin-top: 24px; }
.x-mb-2 { margin-bottom: 16px; } .x-mb-3 { margin-bottom: 24px; }
.x-flex { display: flex; align-items: center; }
/* ⛔ THE FLEX/GRID OVERFLOW IDIOM, not a style choice. A flex or grid child defaults to
   min-width:auto, so long content refuses to shrink and overflows its container instead of
   truncating. min-width:0 is the standard release valve and every use of it in this estate is
   that same fix. MEASURED: 30 inline occurrences, one single value, no variants - so it is
   mechanical, carries no design decision, and is additive (a new class name cannot change what
   any existing element renders). That is the whole test for adding to the shared layer, and it
   is why this is here while a font-size SCALE is not: font-size has 12 competing values and
   choosing among them is a design decision, not a migration. */
.x-minw-0 { min-width: 0; }

/* flex-wrap moved in from the call sites: 6 of the 8 .x-between uses declared it inline, so
   wrapping is what this class IS, not a per-page decision. For the other 2 it engages only on
   overflow, where wrapping beats overflowing. Held since 2026-08-31 because the LIGHT visual
   baselines were invalid and a layout change to a shared class cannot be regression-tested
   against a broken reference; released now that the operator light baselines are re-captured
   through the cascade gate. */
.x-between { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.x-gap-1 { gap: 8px; } .x-gap-2 { gap: 16px; }
.x-muted { color: var(--x-ink-2); }
.x-strong { font-weight: 600; }
.x-mono-num { font-family: var(--x-font-head); font-weight: 600; }
.x-right { text-align: right; } .x-center { text-align: center; }
.x-pos { color: var(--x-success-ink); } .x-neg { color: var(--x-danger-ink); } .x-warn { color: var(--x-warning-ink); }

/* ============================================================================
   10b. Backward-compat: host legacy Bootstrap page bodies inside the XNet shell
   ----------------------------------------------------------------------------
   Existing pages emit `<main class="col-12 col-lg-10 ml-sm-auto ...">` inside the
   `.app-content > .row` that inc_header opens and inc_footer closes. In the XNet
   shell that main sits inside `.x-main` (which already provides the rail offset),
   so we neutralise ONLY the top-level column so bodies flow full-width — nested
   grids inside each page are untouched.
   ========================================================================== */
.xnet .x-main .app-content { background: transparent; margin: 0; padding: 0; max-width: 100%; }
.xnet .x-main .app-content > .row { margin-left: 0; margin-right: 0; }
.xnet .x-main .app-content > .row > * { flex: 0 0 100%; max-width: 100%; margin-left: 0 !important; }

/* ============================================================================
   10c. Reservations (booking) v1 — restyle the existing list in place
   ----------------------------------------------------------------------------
   Pure-CSS restyle over booking.js's markup (ids/placeholders untouched): white
   bordered panels get the XNet radius, list header labels become eyebrow caps,
   result rows get hover + hairline dividers, refno picks up the brand ink.
   ========================================================================== */
.xnet .container-fluid .col-xl.border,
.xnet .container-fluid .col-sm.border,
.xnet .container-fluid .col-md-8 > .border,
.xnet .container-fluid .col-12 > .border { border-color: var(--x-line) !important; border-radius: var(--x-r-lg); }
.xnet #booklist-table .font-weight-bold,
.xnet #rqstlist-table .font-weight-bold,
.xnet #todaylist-table .font-weight-bold {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--x-ink-2); font-weight: 600 !important;
}
.xnet #booklist-container .rowitem,
.xnet #rqstlist-container .rowitem,
.xnet #todaylist-container .rowitem { border-color: var(--x-line-2) !important; transition: background .1s; cursor: pointer; }
.xnet #booklist-container .rowitem:hover,
.xnet #rqstlist-container .rowitem:hover,
.xnet #todaylist-container .rowitem:hover { background: var(--x-surface-3); }
/* ⛔ REMOVED - THIS RULE NEVER APPLIED. liva-admin-design-system.css:161 sets
   `.text-primary, .font-blue, .font-blue-bold { color: var(--lv-primary) !important }`, and
   !important beats an ID selector. The re-tone now lives with .text-primary below, where the
   same problem was already solved correctly, and covers every use rather than two containers.
   ⚠ Two templates carry page-local copies of this same workaround (combinedroute/edit.php:25,
   combinedroute/list.php:10). Those lack !important too and are equally inert - they are dead
   weight in those files, not a second opinion about the colour. */

/* ============================================================================
   10d. Reservations v2b — booking detail modal restyled as a right-side drawer
   ----------------------------------------------------------------------------
   CSS-ONLY: repositions the existing Bootstrap modal (#modal-booking-view) to
   slide in from the right as a full-height master-detail panel. The modal
   MECHANISM and every action handler (edit / cancel / refund / force-cancel /
   email / ticket) are untouched — only the dialog's position/size/animation.
   ========================================================================== */
.xnet #modal-booking-view .modal-dialog {
  margin: 0 0 0 auto;
  max-width: 560px; width: 100%;
  min-height: 100vh;
}
.xnet #modal-booking-view .modal-content {
  min-height: 100vh; border: 0; border-left: 1px solid var(--x-line);
  border-radius: 0; box-shadow: var(--x-shadow-lg);
}
.xnet #modal-booking-view.fade .modal-dialog { transform: translateX(48px); transition: transform .25s ease; }
.xnet #modal-booking-view.show .modal-dialog { transform: translateX(0); }
.xnet #modal-booking-view .modal-header { align-items: flex-start; }
.xnet #modal-booking-view .modal-header .mbv-refno { font-family: var(--x-font-head); font-weight: 600; font-size: 22px; }
@media (max-width: 600px){ .xnet #modal-booking-view .modal-dialog { max-width: 100%; } }

/* ============================================================================
   10e. Shared-component overrides under XNet
   ----------------------------------------------------------------------------
   - Breadcrumb: gds.css hardcodes `.breadcrumb{background:#0088cf}` (the off-brand
     bright-blue bar). Re-tone it to the XNet surface with dark ink.
   - Anchor-nav one-pager (anchornav.js / .lv-anchorpage): the app's anchor-menu
     component (used by the Settings one-pager) uses --lv-* fallbacks; map its
     active/hover states onto XNet tokens so route/vehicle/station editors match.
   ========================================================================== */
.xnet .breadcrumb { background: var(--x-surface-3) !important; border-radius: var(--x-r-md); }
/* ⛔ THE SAME HALF-APPLIED PAIR AS THE BADGE MAPPING, ONE LINE APART. The line above takes the
   breadcrumb bar from the brand blue to var(--x-surface-3) WITH !important, and wins. These
   colour lines had none, so operator-modern.css:151 (.breadcrumb-item color #fff !important)
   still won — white text, correct on the old blue bar, left behind on the new light one.
   Measured on a live /booking page, 1440px: the title Bookings rendered rgb(255,255,255) on rgb(233,233,234)
   = 1.21:1. Not hard to read — INVISIBLE. Dark was fine at 13.32:1, which is why it survived.
   ⚠ The breadcrumb carries the PAGE TITLE, and 53 templates use one. */
.xnet .breadcrumb, .xnet .breadcrumb .breadcrumb-item, .xnet .breadcrumb strong { color: var(--x-ink) !important; }
.xnet .breadcrumb a { color: var(--x-primary-ink) !important; }
.xnet .breadcrumb-item + .breadcrumb-item::before { color: var(--x-ink-3) !important; }
/* #4104: on a page whose only visible title is the current breadcrumb item, that item IS the page's
   <h1> (<li aria-current="page"><h1 class="x-crumb-title"><strong>…</strong></h1></li>). The h1 takes
   the crumb's own type and flow - no second visible title (one title per page) and no pixel moves. */
.xnet .breadcrumb .x-crumb-title { display: inline; font: inherit; letter-spacing: inherit; color: inherit; margin: 0; }

/* Modal headers: re-tone Bootstrap's bright bg-primary/success/danger/info (used on ~90 operator
   modals) to the XNet palette so pop-ups match the rest of the platform instead of flashing
   off-brand blue/green. Colour only — layout/close-button untouched. */
.xnet .modal-header.bg-primary { background: var(--x-primary-700) !important; }
.xnet .modal-header.bg-success { background: var(--x-success) !important; }
.xnet .modal-header.bg-danger  { background: var(--x-danger)  !important; }
.xnet .modal-header.bg-info    { background: var(--x-primary) !important; }

/* Status/count badges: re-tone Bootstrap's bright `.badge.badge-*` (status labels, counts)
   to the XNet pill palette so they match .x-pill without touching every template. Colour +
   softer weight/radius only; the badge markup + JS hooks are untouched. Scoped to plain
   Bootstrap badges — the shell's own `.x-nav-item__badge` and `.x-pill` are unaffected. */
.xnet .badge { font-weight: 500; border-radius: var(--x-r-pill); }
/* ⛔ THE BADGE VARIANTS DELIBERATELY DO NOT USE THE -ink FALLBACK. Independent QA measured the
   DEPLOYED CASCADE on 2026-08-31 and caught a REGRESSION this file introduced: liva-admin-design-
   system.css sets .badge-* background with !important, so it WINS over the background here while
   the colour does not. Adopting the -ink token therefore lightened the TEXT while the background
   stayed bright, and dark contrast went DOWN - success 2.42->1.65, info 2.84->1.85, danger
   1.95->1.77. Reverted to the base token: that restores the pre-existing values instead of
   pretending to fix them.
   ⭐ THE LESSON IS THE METHOD. The change was verified against THIS FILE alone. A token's effect
   is a property of the WHOLE cascade - this is one of nine stylesheets and is not the last word.
   Legacy .badge-* dark contrast remains a pre-existing defect owned by the design-system layer;
   .x-pill above is the primitive to migrate onto, and it is measured good (7.85/7.57/6.52/8.11). */
/* ⛔ THESE SIX RULES NEVER APPLIED THEIR BACKGROUND, AND THAT MADE THINGS WORSE, NOT NEUTRAL.
   liva-admin-design-system.css:163-167 sets .badge-* background with !important, which beats
   specificity, so only the COLOUR half of the mapping above ever landed: the pill's dark
   foreground on the design system's saturated fill. Measured by render, both themes, against all
   eight stylesheets in production load order (qa-harness/browser/badge-contrast.js):
       success 2.42 · warning 2.05 · danger 1.95 · info 2.84 · primary 2.56  — all below AA 4.5:1
   and IDENTICAL in light and dark, because the design system declares no dark variant at all.
   ⭐ BOTH HALVES MUST MOVE TOGETHER. Changing only the foreground to the dark-mode ink is exactly
   what was tried and reverted on 2026-08-31 (defect D-1): it put light ink on the SAME saturated
   fill and lowered contrast further. The background has to win first.
   ⚠ #booklist-container keeps its own deliberate brand chips — an ID selector outranks this. */
.xnet .badge.badge-success   { background: var(--x-success-bg) !important; color: var(--x-success-ink, var(--x-success)) !important; }
.xnet .badge.badge-danger    { background: var(--x-danger-bg)  !important; color: var(--x-danger-ink,  var(--x-danger)) !important; }
.xnet .badge.badge-warning   { background: var(--x-warning-bg) !important; color: var(--x-warning-ink, var(--x-warning)) !important; }
.xnet .badge.badge-info      { background: var(--x-info-bg)    !important; color: var(--x-info-ink,    var(--x-info)) !important; }
.xnet .badge.badge-primary   { background: var(--x-info-bg)    !important; color: var(--x-primary-ink, var(--x-primary-700)) !important; }
/* --x-surface-2, not -3: independent QA measured this variant at 4.48 in light — the one of the
   six that did not clear 4.5, missed by 0.02 and shipped. --x-surface-3 (#e9e9ea) is too dark a
   ground for the secondary ink; the inset surface clears it. Same correction as .rs-pill.off. */
.xnet .badge.badge-secondary { background: var(--x-surface-2)  !important; color: var(--x-ink-2) !important; }

/* OP-COLOR: Booking / Reservations list — restore the legacy (old-system, gds.css) row-chip
   palette. The admin design system flattened .badge-info from teal to blue (--lv-primary-600),
   collapsing the Book-Date / pax / trip-time chips into a single bright-blue wall. Bring back the
   old two-colour scheme: blue Book Date & trip date (#0088cf), teal pax counts & trip time
   (#16BECF). Scoped to the list container so the rest of the app keeps the design-system badges.
   Higher specificity + !important beats both the admin-system and the .xnet muted rules. */
#booklist-container .badge.badge-primary { background:#0088cf !important; color:#fff !important; }
#booklist-container .badge.badge-info    { background:#16BECF !important; color:#fff !important; }

/* .text-primary is hardcoded to the bright customer-brand blue (#0088cf, gds.css) — off-brand
   against the muted XNet slate. Re-tone it (nav icons, section links) to the XNet primary. */
.xnet .text-primary,
.xnet .font-blue,
.xnet .font-blue-bold { color: var(--x-primary-ink) !important; }
/* ⭐ .text-dark IS PURE BOOTSTRAP AND HAS NO DARK VALUE ANYWHERE. Nothing in this repo styles it;
   bootstrap 4.3.1 sets `color:#343a40!important` and stops. On a dark card that measured 1.41:1 —
   invisible — while light was fine at 11.51, which is why it was never noticed. 24 templates,
   63 uses.
   ⚠ I FIRST RECORDED THIS AS "NOT OURS TO FIX", reasoning that .text-dark is shared with the wider
   admin estate. That was too cautious: an override SCOPED TO .xnet touches only operator-platform
   pages and changes nothing elsewhere — the same pattern already used above for .text-primary and
   below for .badge-* and .breadcrumb-item. Measured after: light 16.55, dark 13.30. */
.xnet .text-dark { color: var(--x-ink) !important; }
/* .text-muted took --lv-muted straight from the design system in LIGHT (#757575 = 4.12:1 on the
   footer). Dark was already remapped to --x-ink-2 in user-prefs.css; this makes light agree, so one
   token governs muted text in both themes. 82 uses across 25 templates. */
.xnet .text-muted { color: var(--x-ink-2) !important; }

/* ⛔ THE LARGEST DARK-MODE DEFECT IN THE PLATFORM, AND THIS LAYER HAD NEVER STYLED IT.
   Bootstrap's .card hardcodes background-color:#fff with no dark variant, and nothing in this
   repo overrode it. In dark mode the card stayed WHITE while the text inside correctly took the
   dark theme's light ink — measured 1.14:1 on /company, /setting, /notificationsettings and
   /profile. 455 uses across 96 templates.
   ⚠ FOUND ONLY BY WIDENING THE AUDIT. A single-page desktop audit could never have seen it:
   /booking uses .x-card, this layer's own component, and carries no Bootstrap card at all.
   ⭐ .card-header and .card-footer need no background rule — Bootstrap tints them with
   rgba(0,0,0,.03), which composites over whatever is beneath, so they follow the card into dark
   on their own. Only the base surface and the borders are hardcoded.
   No !important needed: `.xnet .card` (0,2,0) already outranks Bootstrap's `.card` (0,1,0). */
.xnet .card { background-color: var(--x-surface); border-color: var(--x-line); }
.xnet .card-header, .xnet .card-footer { border-color: var(--x-line); }

/* ⭐ .btn-outline-secondary IS NOT AN OUTLINE BUTTON HERE. gds.css:431 repaints it as a FILLED
   brand-blue button (color:#fff; background:#0088cf), and white on #0088cf is 3.87:1 — short of
   the 4.5 needed at 15px/500, in BOTH themes. 15 uses across 9 templates; the Search button is one.
   ⭐ THE FIX INTRODUCES NO NEW COLOUR. #006fa7 is the value gds.css ALREADY defines one line below
   as this same button's hover, and white on it measures 5.48. Resting takes the old hover, hover
   takes one further step (#005c8a, 7.25). Deliberately NOT re-toned to the XNet slate and NOT
   turned back into a real outline button: both are redesign, and the brief is defects. */
/* ⛔ REGRESSION FIX — !important REMOVED. Independent QA caught this live on /profile.
   profile/view.php:32 deliberately turns this class back into a REAL outline button
   (border-color:var(--x-line); color:var(--x-ink); background:var(--x-surface)) at the SAME
   specificity (0,2,0) with no !important. The !important took the background and left that rule's
   near-black text behind: the theme switcher rendered #1d1f20 on #006fa7 — light 16.55 -> 3.02,
   hover 15.34 -> 2.32, and the selected state became invisible. On the page that switches themes.
   ⛔ THE HALF-APPLIED PAIR AGAIN, HALVES SWAPPED. This file already carries the rule from the
   badge revert — both halves must move together. I wrote that warning and then took a background
   without its foreground.
   ⭐ THE !important WAS NEVER NEEDED. Nothing else declares this class !important; the only
   competitor is gds.css:431 at (0,1,0), which this selector already outranks on specificity alone.
   Without it, a page-local re-tone at equal specificity wins on source order — which is correct:
   a page that has deliberately restyled the control keeps its own answer. */
.xnet .btn-outline-secondary { background-color: #006fa7; border-color: #006fa7; }

/* ⛔ .btn-outline-primary — flagged by independent QA, same shape as its siblings.
   liva-admin-design-system.css:157 forces `color: var(--lv-primary) !important` = #0088cf, so
   profile/view.php:30's re-tone to --x-primary-700 has NEVER applied. Measured 3.87 light /
   4.18 dark. Hover is worse: :158 forces white on #0088cf = 3.87. 4 templates, 9 uses.
   ⭐ PAIRING CHECK DONE BEFORE WRITING THIS, per the /profile regression. Exactly one page-local
   rule exists (profile/view.php:30) and it pairs `background: var(--x-surface)` — a neutral
   surface that reads correctly with the ink token in both themes, so changing the FOREGROUND
   strands nothing. The hover rule there sets no colour, so forcing its background strands nothing
   either; the white comes from the design system and is kept.
   ⚠ Border stays --x-primary: it is a boundary, not text, and 3:1 is its bar. */
.xnet .btn-outline-primary { color: var(--x-primary-ink) !important; border-color: var(--x-primary) !important; }
.xnet .btn-outline-primary:hover,
.xnet .btn-outline-primary:focus { background: #006fa7 !important; color: #fff !important; }

/* ⛔ .bg-primary — Bootstrap's utility, used as a SELECTED state (users/view.php:182 pairs it with
   text-white on the active contact card). It resolves to --lv-primary #0088cf, and white on it is
   3.87 in both themes. 19 templates, 85 uses.
   ⭐ THE ESTATE HAD ALREADY ANSWERED THIS, NINE TIMES, ONE TEMPLATE AT A TIME:
   profile/view.php:37 `.xnet .card.bg-primary{ background:var(--x-primary-700) !important }` and
   eight `.xnet .modal-header.bg-primary` rules all reach for --x-primary-700. Adopting THEIR
   answer rather than inventing one makes those page-local rules redundant instead of contradicted,
   and the more specific ones (0,3,0) still win while setting the identical value.
   ⭐ PAIRING CHECK: .bg-primary travels with text-white. --x-primary-700 + white = 9.92 both
   themes — the same pairing .x-btn--strong already uses. Nothing is stranded. */
.xnet .bg-primary { background-color: var(--x-primary-700) !important; }

/* ⛔ THE HALF-APPLIED PAIR ON .bg-white. operator-modern.css:165 re-themes `main .bg-white` to the
   surface token, but Bootstrap's `.btn{color:#212529}` is left behind — so a button carrying
   bg-white renders near-black text on a dark panel: measured 1.05 on /profile's language picker.
   Pairing the foreground with the background that was already re-themed. Scoped to .btn so it
   cannot repaint bg-white containers whose children set their own colour. */
.xnet .btn.bg-white { color: var(--x-ink); }

/* ⛔ BOOTSTRAP ALERTS HAVE NO DARK VARIANT AND THIS LAYER NEVER STYLED THEM. bootstrap 4.3.1 fixes
   .alert-info at background #d1ecf1 / colour #0c5460, and the light background NEVER FLIPS. In dark
   the panel stays pale while the text and any links inside take the dark theme's light ink:
   measured 1.53 on /setting and 1.47 on /instant-booking (an <a> inside .alert-secondary).
   Used variants only: info 10, warning 11, secondary 2, success 1.
   ⭐ NO !important, DELIBERATELY. `.xnet .alert-info` (0,2,0) already outranks Bootstrap's (0,1,0),
   and leaving it off means a page-local rule at equal specificity still wins on source order —
   which is what the /profile regression taught. The five templates that carry their own alert
   rules are corrected in place instead of being overridden. */
.xnet .alert-info      { background-color: var(--x-info-bg);    border-color: var(--x-info);    color: var(--x-info-ink, var(--x-info)); }
.xnet .alert-success   { background-color: var(--x-success-bg); border-color: var(--x-success); color: var(--x-success-ink, var(--x-success)); }
.xnet .alert-warning   { background-color: var(--x-warning-bg); border-color: var(--x-warning); color: var(--x-warning-ink, var(--x-warning)); }
.xnet .alert-danger    { background-color: var(--x-danger-bg);  border-color: var(--x-danger);  color: var(--x-danger-ink, var(--x-danger)); }
.xnet .alert-secondary { background-color: var(--x-surface-2);  border-color: var(--x-line);    color: var(--x-ink); }

/* ⛔ .btn-danger — HALF-APPLIED, AND MY OWN INK SWEEP MADE THE DARK HALF SLIGHTLY WORSE.
   Six templates write `.xnet .btn-danger{ background:var(--x-surface); border-color:var(--x-danger);
   color:var(--x-danger...) }` — an OUTLINE button. But liva-admin-design-system.css:160 forces
   `background: var(--lv-danger) !important` (#d64545), so only their COLOUR half ever applied:
   danger ink on a mid-red fill. Pre-existing at 1.95 both themes; the ink sweep moved the dark
   text to #e88b8b and took it to 1.77.
   ⭐ MY SWEEP'S SAFETY ARGUMENT HAD A THIRD CASE I MISSED. I reasoned the background is either a
   flipping --x-*-bg or the page surface, so light could not change and dark could only improve.
   It can also be a FIXED design-system colour forced with !important — and there the dark ink is
   the wrong direction. One family out of 34 templates hit it. Measured, not assumed.
   ⭐ The fix adopts the six templates' own intent rather than inventing one, exactly as .bg-primary
   did: restore the outline button by making the background win. Pairing check: all six pair
   --x-surface with the danger ink, which is what this imposes. Nothing is stranded. */
.xnet .btn-danger { background: var(--x-surface) !important; border-color: var(--x-danger) !important; color: var(--x-danger-ink, var(--x-danger)) !important; }
.xnet .btn-danger:hover,
.xnet .btn-danger:focus { background: var(--x-danger-bg) !important; }
/* ⚠ --x-ink, not --x-ink-2: the muted ink passed at 4.69 but took light mode DOWN from
   Bootstrap own 8.55. An alert carries a message, and Bootstrap sets near-full-strength text
   there (#383d41); the full ink matches that intent and does not trade contrast away. */
/* ⛔ .btn-primary HAS THE SAME DEFECT AS .btn-outline-secondary ABOVE, and a wider reach:
   19 templates, 82 uses, 5 of the 22 audited pages. liva-admin-design-system.css:155 sets
   `background: var(--lv-primary) !important` = #0088cf, and white on it is 3.87:1 in BOTH themes.
   ⚠ Its own hover token cannot be the fix: --lv-primary-600 is #0091d5, LIGHTER than the resting
   colour, so white on it is worse. Reuses #006fa7 / #005c8a — the pair already landed for
   .btn-outline-secondary, and #006fa7 is a value gds.css already carries. No new colour enters
   the estate and the two button families stay consistent with each other. */
.xnet .btn-primary { background: #006fa7 !important; border-color: #006fa7 !important; }
.xnet .btn-primary:hover,
.xnet .btn-primary:focus { background: #005c8a !important; border-color: #005c8a !important; }
.xnet .btn-outline-secondary:hover,
.xnet .btn-outline-secondary:focus { background-color: #005c8a; border-color: #005c8a; }
/* ⭐ .font-blue-bold JOINED HERE 2026-08-31, found by tools/qa/inert-rules2.js. It is named in the
   SAME design-system rule as .text-primary, so it always had the same defect; only .text-primary
   had been given the !important that makes the re-tone stick. Measured at 14px/700 (bold, but
   under 18.66px, so the AA threshold is 4.5 not 3.0): brand blue #0088cf rendered 3.87 on the
   light card and 4.18 on the dark one - both short. 47 uses across 13 templates. */
.xnet a.text-primary:hover, .xnet a.text-primary:focus { color: var(--x-primary-ink-hover) !important; }

.xnet .lv-anchornav { background: var(--x-surface); border: 1px solid var(--x-line); border-radius: var(--x-r-lg); }
.xnet .lv-anchornav-title { color: var(--x-ink-2); font-family: var(--x-font-body); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; font-size: 11px; }
.xnet .lv-anchornav-link { color: var(--x-ink-2); }
.xnet .lv-anchornav-link:hover { background: var(--x-surface-2); color: var(--x-ink); }
.xnet .lv-anchornav-link.is-active { background: var(--x-tint); color: var(--x-primary-ink); }
.xnet .lv-anchor-section { scroll-margin-top: calc(var(--x-topbar-h) + 16px); }
/* Hide the original Bootstrap tab bar ONLY after anchornav has converted the page to a
   one-pager (buildOne stamps data-lv-anchornav="on"). The `hidden` attribute alone doesn't
   work — Bootstrap's `.nav{display:flex}` overrides it — so we force it here. Keyed on the
   conversion flag so that if anchornav ever fails to run, the tabs remain fully functional. */
.xnet .lv-anchorpage[data-lv-anchornav="on"] .nav-tabs { display: none !important; }

/* ============================================================================
   10f. Advanced-filter drawer — reflow the wide inline search form
   ----------------------------------------------------------------------------
   The booking advanced-search form was built for a wide inline grid (col-xl-3
   input-groups). Relocated into the ~520px drawer, those columns clip to 25%
   width and truncate. Force every field full-width and stack each label
   (input-group-text) above its control so the whole form is readable.
   ========================================================================== */
.xnet #xnet-advfilter-drawer #advanced-search-container { display: block; }
.xnet #xnet-advfilter-drawer #advanced-search-container > * {
  flex: 0 0 100% !important; max-width: 100% !important; width: 100% !important;
  padding-left: 0; padding-right: 0; margin: 0 0 14px;
}
.xnet #xnet-advfilter-drawer .input-group { display: block; }
.xnet #xnet-advfilter-drawer .input-group-prepend { display: block; height: auto; margin-bottom: 5px; }
.xnet #xnet-advfilter-drawer .input-group-text {
  display: block; width: 100%; border: 0; background: transparent; padding: 0;
  font-size: 12.5px; font-weight: 500; color: var(--x-ink-2); white-space: normal; height: auto;
}
.xnet #xnet-advfilter-drawer .input-group > .form-control,
.xnet #xnet-advfilter-drawer .form-control,
.xnet #xnet-advfilter-drawer select {
  width: 100% !important; border-radius: var(--x-r-md) !important;
}
.xnet #xnet-advfilter-drawer .form-group.my-3 { margin-top: 0 !important; }

/* ============================================================================
   11. Responsive — tablet & mobile
   ========================================================================== */

/* Mobile top bar (hidden on desktop) */
.x-mobtop { display: none; }

@media (max-width: 1100px) {
  .x-split, .x-split--wide { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  :root { --x-rail-w: 0px; }
  .x-rail {
    transform: translateX(-100%);
    transition: transform .2s ease;
    width: 260px;
    box-shadow: var(--x-shadow-lg);
  }
  body.xnet.x-rail-open .x-rail, .x-rail.is-open { transform: translateX(0); }
  /* #4356 - the open drawer must sit ABOVE the dimming backdrop (.x-backdrop, z-index 1040), or its links are dimmed and taps land on the backdrop. */
  .x-rail { z-index: 1041; }
  .x-main { margin-left: 0; }
  .x-mobtop {
    display: flex; align-items: center; gap: 12px;
    height: 56px; padding: 0 16px;
    background: var(--x-surface); border-bottom: 1px solid var(--x-line);
    position: sticky; top: 0; z-index: 1015;
  }
  .x-mobtop__btn { width: 40px; height: 40px; display: grid; place-items: center;
    border: 1px solid var(--x-line); border-radius: var(--x-r-md); background: var(--x-surface); }
  .x-topbar { flex-wrap: wrap; height: auto; padding: 12px 16px; gap: 12px; }
  /* The phone top bar already hid the page title (the H1 is the only title there) and wraps into stacked rows; a trail
     would add a row and push every page down, so the breadcrumb is a desktop element (#3478). */
  .x-breadcrumb { display: none; }
  .x-cmdbar { max-width: none; order: 3; width: 100%; }
  .x-topbar__date { display: none; }
  .x-content { padding: 16px; }
  .x-stats { grid-template-columns: 1fr 1fr; }
  .x-stat { border-left: 0; border-top: 1px solid var(--x-line-2); }
  .x-stat:nth-child(-n+2) { border-top: 0; }
  .x-stat:nth-child(even) { border-left: 1px solid var(--x-line-2); }
  .x-drawer { width: 100%; }
  .x-btn-grid { grid-template-columns: 1fr; }
}

@media (max-width: 520px) {
  .x-stats { grid-template-columns: 1fr; }
  .x-stat { border-left: 0 !important; border-top: 1px solid var(--x-line-2); }
  .x-stat:first-child { border-top: 0; }
  .x-table thead { display: none; }
  .x-table.x-table--stack tbody td { display: flex; justify-content: space-between; padding: 8px 14px; border: 0; }
  .x-table.x-table--stack tbody td::before { content: attr(data-label); color: var(--x-ink-2); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
  .x-table.x-table--stack tbody tr { display: block; padding: 8px 0; border-bottom: 1px solid var(--x-line-2); }
}

/* Dark palette. Two ways in:
   (a) system-follow — vendor chose "Match system"; body.x-allow-dark + the OS is dark;
   (b) forced       — vendor chose "Dark"; body.x-force-dark, on ANY OS.
   (b) was missing (the palette only lived inside the media query), so the Profile "Dark"
   option — and the topbar toggle's Dark — had no effect on a light-OS machine. Same values.

   ⭐ THIS IS NOW THE ONLY PLACE THE DARK PALETTE IS DECLARED. Owner ruling 2026-09-01:
   "operator-xnet.css is the canonical Operator design-token owner; user-prefs.css must not
   remain an accidental second palette overriding 19 canonical tokens."

   ⛔ NINETEEN TOKENS MOVED IN FROM user-prefs.css, AND THAT FILE WAS WINNING ALL OF THEM.
   It loads after this one and redeclared them under these SAME two selectors, so its values
   were the rendered values and edits here did nothing — the note that used to sit in that
   file said exactly that ("editing the xnet dark block alone changed nothing — measured: 39
   dark failures survived it untouched"). Fourteen of the nineteen were declared in both
   files, ELEVEN of those with DIFFERENT values (the ink/line scale on 231,233,236 rather
   than 233,234,236; --x-line-2 at .09 not .08; four surface/status backgrounds); five
   (--x-primary, --x-primary-600 and the three shadows) existed ONLY there, so this block had
   no dark value for them at all and they fell through to the LIGHT :root.
   ⭐ EVERY ONE OF THE NINETEEN KEEPS THE VALUE THAT WAS ALREADY RENDERING. Nothing here is a
   new colour; this is a change of owner, not of palette.

   ⭐ AND THE USER PREFERENCE IS UNTOUCHED, which is the distinction that made the move safe.
   The preference is the SELECTOR — .x-allow-dark / .x-force-dark, written on <body> by
   inc_header from the lv_prefs cookie — and it stays exactly where it was. What moved is the
   VALUES the selector resolves to. Measured on the only writer of that cookie
   (profile.php::ajaxUpdateDisplayPref): the whole of what an operator may choose is
   theme_mode(light|dark|system), density(comfortable|compact), font_size(sm|md|lg), each an
   enumerated whitelist. ⛔ NO COLOUR IS USER-CONFIGURABLE ANYWHERE IN THIS APP, so not one of
   the nineteen could have been "a token set because the user chose it". */
body.xnet.x-force-dark {
  color-scheme: dark;
  --x-bg: #16181a; --x-surface: #1e2124; --x-surface-2: #24282c; --x-surface-3: #2b3035;
  --x-ink: #e7e9ec; --x-ink-2: rgba(231,233,236,.78); --x-ink-3: rgba(231,233,236,.58);
  --x-line: rgba(231,233,236,.16); --x-line-2: rgba(231,233,236,.09); --x-tint: #1c2b3a;
  --x-primary: #6f9fce; --x-primary-600: #5c8cbb;
  /* --x-primary-700 DELIBERATELY NOT FLIPPED. It is a BACKGROUND token, always paired with
     color:#fff (.x-btn--strong, .x-rail__logo, .modal-header.bg-primary). Flipping it to a
     light blue rendered white-on-light-blue at 1.86:1 in dark mode - the exact failure
     operator-xnet.css:44 warned about. Foreground uses take --x-primary-ink, which IS
     flipped, just below. Measured 2026-08-31. */
  --x-primary-ink: #9fc0e0; --x-danger-ink: #e88b8b; --x-tint-hover: #24384d;
  --x-success-ink: #6fcf97; --x-warning-ink: #e0b055; --x-primary-ink-hover: #c9dcf0;
  --x-success-bg: #122c20; --x-warning-bg: #2e2510; --x-danger-bg: #331a1a;
  --x-info-bg: #182635; --x-info-ink: #9fc0e0;
  /*[Note]:these three exist ONLY here and in :root. Without them the alert
    variants keep a light pastel background under dark ink — the bug class
    commit 15690aa was written to remove, reintroduced in the other direction. */
  --x-shadow-sm: 0 1px 2px rgba(0,0,0,.5);
  --x-shadow-md: 0 3px 10px rgba(0,0,0,.5);
  --x-shadow-lg: 0 12px 32px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: dark) {
  body.xnet.x-allow-dark {
    color-scheme: dark;
    --x-bg: #16181a; --x-surface: #1e2124; --x-surface-2: #24282c; --x-surface-3: #2b3035;
    --x-ink: #e7e9ec; --x-ink-2: rgba(231,233,236,.78); --x-ink-3: rgba(231,233,236,.58);
    --x-line: rgba(231,233,236,.16); --x-line-2: rgba(231,233,236,.09); --x-tint: #1c2b3a;
    --x-primary: #6f9fce; --x-primary-600: #5c8cbb;
    /* --x-primary-700 DELIBERATELY NOT FLIPPED. It is a BACKGROUND token, always paired with
       color:#fff (.x-btn--strong, .x-rail__logo, .modal-header.bg-primary). Flipping it to a
       light blue rendered white-on-light-blue at 1.86:1 in dark mode - the exact failure
       operator-xnet.css:44 warned about. Foreground uses take --x-primary-ink, which IS
       flipped, just below. Measured 2026-08-31. */
    --x-primary-ink: #9fc0e0; --x-danger-ink: #e88b8b; --x-tint-hover: #24384d;
    --x-success-ink: #6fcf97; --x-warning-ink: #e0b055; --x-primary-ink-hover: #c9dcf0;
    --x-success-bg: #122c20; --x-warning-bg: #2e2510; --x-danger-bg: #331a1a;
    --x-info-bg: #182635; --x-info-ink: #9fc0e0;
    /*[Note]:these three exist ONLY here and in :root. Without them the alert
      variants keep a light pastel background under dark ink — the bug class
      commit 15690aa was written to remove, reintroduced in the other direction. */
    --x-shadow-sm: 0 1px 2px rgba(0,0,0,.5);
    --x-shadow-md: 0 3px 10px rgba(0,0,0,.5);
    --x-shadow-lg: 0 12px 32px rgba(0,0,0,.6);
  }
}

/* ============================================================================
   OP-DRAWER: booking detail modal -> wide right-side drawer.
   The shared booking-detail (#modal-booking-view) was a cramped, centred
   Bootstrap modal. Re-present it as a full-height drawer anchored to the right:
   half the desktop width, full width on mobile, with the Booking / Trip cards
   stacked for legibility. Pure presentation — booking.js still calls
   .modal('show')/.modal('hide') and the × / backdrop still close it, so this is
   safe across every page that opens the modal (booking, reservations, instant,
   pending, dashboard, today-departure, finance, route).
   ============================================================================ */
#modal-booking-view.modal { padding-right: 0 !important; }
#modal-booking-view .modal-dialog {
	position: fixed; top: 0; right: 0; bottom: 0; margin: 0;
	max-width: none; width: 50vw; min-width: 560px; height: 100vh;
	display: flex; align-items: stretch;
	transform: translateX(40px); transition: transform .22s ease-out;
}
#modal-booking-view.show .modal-dialog { transform: translateX(0); }
#modal-booking-view .modal-content {
	width: 100%; height: 100vh; border: 0; border-radius: 0;
	display: flex; flex-direction: column;
	box-shadow: -14px 0 40px rgba(16,32,52,.16);
}
#modal-booking-view .modal-header { flex: 0 0 auto; padding: 16px 22px; }
#modal-booking-view .modal-body   { flex: 1 1 auto; overflow-y: auto; padding: 18px 22px; }
#modal-booking-view .modal-footer { flex: 0 0 auto; }
/* Legibility: stack the Booking / Trip cards vertically in the narrower drawer,
   and give the label/value rows more breathing room. */
#modal-booking-view .modal-body > .row > [class*="col-md-6"] { flex: 0 0 100%; max-width: 100%; }
#modal-booking-view .modal-body > .row > [class*="col-md-6"] + [class*="col-md-6"] { margin-top: 4px; }
#modal-booking-view .card { margin-bottom: 16px; }
#modal-booking-view .list-group-item { padding: .7rem 1rem; }
#modal-booking-view .list-group-item .small { color: #6b7784; margin-bottom: 2px; }
/* The Booking-Date / Booking-Status pair collided ("Booking DateBooking Status")
   because both sat in a justify-between row with no gap — give the columns a gap. */
#modal-booking-view .list-group-item.d-flex { gap: 16px; }
@media (max-width: 768px) {
	#modal-booking-view .modal-dialog { width: 100vw; min-width: 0; }
	#modal-booking-view .modal-content, #modal-booking-view .modal-dialog { height: 100dvh; }
}

/* ============================================================
   FINANCE — "Report by Days" balance chart (Accounting > Dashboard)
   The chart used to inherit Chart.js defaults: a grey hairline squashed into a
   7.5:1 canvas (height="40"), so give it a real, fixed drawing height and let
   maintainAspectRatio:false fill it. Colours come from the --x-* tokens in
   js/finance.js, so light/dark follow the user's Display preference.
   ============================================================ */
.smrygraph-canvas-wrap {
	position: relative;
	height: 260px;
	margin-bottom: 18px;
}
@media (max-width: 768px) {
	.smrygraph-canvas-wrap { height: 200px; }
}
.smrygraph-empty {
	height: 120px;
	display: flex; align-items: center; justify-content: center;
	margin-bottom: 18px;
	border: 1px dashed var(--x-line, rgba(29,31,32,.16));
	border-radius: 6px;
	color: var(--x-ink-3, rgba(29,31,32,.42));
	font-size: .9rem;
}

/* Facility group heading on the vehicle/station forms.
   The catalogue is 36 items across nine categories; a flat list reads as a wall.
   Emitted by a change-detect in the template (the list arrives ordered by category),
   so an uncategorised tail simply renders with no heading. */
.op-facgrp { margin: 10px 0 2px; font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
             text-transform: uppercase; color: #8a97a5; }
.op-facgrp:first-child { margin-top: 0; }

/* ============================================================================
   9b. FORM PRIMITIVE — extended, and it ABSORBS .form-control
   ----------------------------------------------------------------------------
   OPERATOR-UI-CONSISTENCY Phase 2. Additive only: every rule below either names
   a class that existed and was unstyled, or scopes an existing Bootstrap class
   INSIDE .x-field. Nothing outside a .x-field wrapper changes appearance.

   ⛔ WHY .form-control IS ABSORBED RATHER THAN REPLACED. A form input's look is
   currently decided by SIX sources — Bootstrap (CDN), gds.css, operator.css,
   operator-modern.css, liva-admin-design-system.css and this file — with cascade
   order picking the winner. .form-control carries 748 call sites; .x-input 113.
   Introducing a third control class would BE a second design system, which is
   the one thing this phase must not do. So a .form-control inside .x-field is
   styled identically to .x-input: an existing page becomes consistent by being
   WRAPPED, never by renaming 748 attributes. 45 call sites already pair them
   (class="form-control x-input"), so this formalises a pattern the codebase
   started rather than inventing one.

   ⚠ The reference (Company Settings) uses ZERO .form-control — it replaced
   Bootstrap with page-local cr2-*. The BEHAVIOUR below is taken from it; the CSS
   deliberately is not, because cr2-* is one page's vocabulary, not a primitive.
   ========================================================================== */

.xnet .x-field { margin-bottom: 16px; }
.xnet .x-field > label,
.xnet .x-field__label {
  display: block; margin-bottom: 6px;
  font-size: 12.5px; font-weight: 500; color: var(--x-ink-2);
}
.xnet .x-field--req > label::after,
.xnet .x-field--req .x-field__label::after {
  content: "*"; margin-left: 3px; color: var(--x-danger-ink, var(--x-danger));
}

/* ⛔ REGRESSION FIX, found by independent QA on slice L1-02, 2026-08-31.
   The rule below sets width:100% on every .form-control inside .x-field. Bootstrap's
   .input-group is flex-wrap:wrap and sizes its children flex:1 1 auto / width:1%, so a
   100% width makes EACH control claim the full row and the compound control unwraps into a
   stack. MEASURED on the shipped bytes: the payment-amount group went from 1 row/38px to
   4 rows/160px at every width in both themes, stranding its "TO" separator on its own line.
   ⭐ FIXED IN THE LAYER, NOT THE TEMPLATE, ON MEASURED DEMAND: .input-group appears 898 times
   across 29 templates, so every later migration would hit this. Only searchfilter add/edit have
   one inside an .x-field TODAY - the other 28 templates meet it the moment they migrate.
   This restores Bootstrap's own declaration for those children and nothing else. */
.xnet .x-field .input-group > .form-control,
.xnet .x-field .input-group > .custom-select { width: 1%; flex: 1 1 auto; }

/* ⛔ THE HALF-FLIPPING PAIR ON .input-group-text — a measured AA failure, not a tidy-up.
   Found by independent QA on searchfilter/add, which was a PROVEN template: the addon reads
   1.63 in dark against a 4.5 threshold, and had been doing so the whole time.
   THE CAUSE IS ONE MISSING DECLARATION. operator-modern.css:231 gives .input-group-text
   `background: var(--lv-surface-3)`, which DOES follow the theme, and never sets `color` — so
   the ink stays Bootstrap's pinned #495057 while the ground moves out from under it. Measured:
     light  #495057 on #eff0f2 = 6.90   (fine, which is why nobody saw it)
     dark   #495057 on #2b3035 = 1.63   (fails)
   Naming the foreground half from a token that flips completes the pair:
     light  --x-ink-2 on #eff0f2 = 7.94
     dark   --x-ink-2 on #2a2e33 = 7.57
   ⭐ P REOPENED ON MEASURED DEMAND, which is the only thing that reopens it: 312 uses across
   26 templates, so this is a class, not an instance. Fixing it in the six templates that pair
   it with .bg-white would have left the other twenty failing in the dark nobody looks at.
   COLOUR ONLY, deliberately. The background already flips; adding one here would change the
   surface on 312 live controls to fix a foreground bug. The narrower change is the provable one.
   ⚠ .xnet #xnet-advfilter-drawer .input-group-text (above) is more specific and is untouched. */
.xnet .input-group-text { color: var(--x-ink-2); }

/* the control itself — .form-control joins .x-input here, deliberately */
.xnet .x-field .form-control,
.xnet .x-field .custom-select,
.xnet .x-input, .xnet .x-select, .xnet .x-textarea {
  width: 100%; font-family: var(--x-font-body); font-size: 14px; color: var(--x-ink);
  /*[Note]:background-COLOR, not the `background` shorthand. Measured in a browser 2026-08-30:
    the shorthand wiped Bootstrap's background-image on .custom-select and the dropdown arrow
    disappeared. The shorthand resets every background-* longhand, including the image. */
  background-color: var(--x-surface); border: 1px solid var(--x-line);
  border-radius: var(--x-r-md); padding: 9px 12px;
  /*[Note]:min-height, because line-height does NOT govern a NATIVE <select>. Measured
    2026-08-30: .x-select computed 38.5px against 41px for every other control — a select with
    appearance:auto takes its height from the platform widget. Bootstrap's .custom-select escapes
    this only because it sets appearance:none. Rather than strip the native arrow off .x-select to
    win 2.5px, state the floor, derived from the values above: 1.5em + 18px padding + 2px border. */
  min-height: calc(1.5em + 20px);
  /*[Note]:line-height is LOAD-BEARING for height parity. Without it a <select> computed 38.5px
    against 41px for input/textarea, because a select's intrinsic line box is shorter — a NEW
    inconsistency introduced by this very primitive. Stating it makes all five controls agree. */
  line-height: 1.5;
  /*[Note]:height:auto is LOAD-BEARING. Bootstrap and liva-admin-design-system pin
    .form-control to a computed 38px; .x-input sizes from padding at 41px. Measured in a
    browser 2026-08-30: an absorbed control sat 3px short and side-by-side fields
    misaligned — the exact defect this primitive exists to remove. */
  height: auto;
}
.xnet .x-field .form-control:focus,
.xnet .x-field .custom-select:focus,
.xnet .x-input:focus, .xnet .x-select:focus, .xnet .x-textarea:focus {
  outline: 0; border-color: var(--x-primary); box-shadow: 0 0 0 3px rgba(89,128,166,.15);
}
.xnet .x-field .form-control:disabled,
.xnet .x-field .custom-select:disabled,
.xnet .x-input:disabled, .xnet .x-select:disabled, .xnet .x-textarea:disabled {
  background: var(--x-surface-2); color: var(--x-ink-3); cursor: not-allowed;
}

/* hint and error. .x-field__err:empty collapses, so a template may render the
   node unconditionally and fill it only when there is something to say. */
.xnet .x-field__hint { margin-top: 5px; font-size: 11.5px; color: var(--x-ink-3); }
.xnet .x-field__err  { margin-top: 5px; font-size: 11.5px; color: var(--x-danger-ink, var(--x-danger)); }
.xnet .x-field__err:empty { display: none; }

.xnet .x-field.is-invalid .form-control,
.xnet .x-field.is-invalid .custom-select,
.xnet .x-field.is-invalid .x-input,
.xnet .x-field.is-invalid .x-select,
.xnet .x-field.is-invalid .x-textarea {
  /*[Note]:SHORTHAND, deliberately. `border-color` alone was measured NOT to reach an
    absorbed .form-control on 2026-08-30 even though the selector matched and the token
    resolved — a competing `border:` shorthand re-applied the base colour. Stating the
    whole border here removes the ambiguity for both control kinds. */
  border: 1px solid var(--x-danger);
}
.xnet .x-field.is-invalid .form-control:focus,
.xnet .x-field.is-invalid .custom-select:focus,
.xnet .x-field.is-invalid .x-input:focus,
.xnet .x-field.is-invalid .x-select:focus,
.xnet .x-field.is-invalid .x-textarea:focus {
  /*[Note]:the plain rgba() first is a FALLBACK, not a duplicate. color-mix() is a parse-time
    error on engines that lack it, which drops the whole declaration; without this line the
    lower-specificity :focus rule then applies and an INVALID field shows the normal blue
    ring — degrading to "looks valid", the wrong direction. */
  box-shadow: 0 0 0 3px rgba(138, 43, 43, .18);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--x-danger) 18%, transparent);
}

/* rows of fields; collapses to one column on small screens */
.xnet .x-fieldrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 14px; }

/* ============================================================================
   9c. ALERT / VALIDATION PRIMITIVE — genuinely new
   ----------------------------------------------------------------------------
   .x-alert had ZERO definitions and ZERO uses. Pages signal problems today with
   Bootstrap `alert` (187 uses) and bare `text-danger` (117), so there is no
   shared way to state a refusal.

   Behaviour is taken from the pattern the refund editor proved: name what is
   wrong, say what it means for the person affected, and offer the way out. The
   markup is a title + body, so a template cannot render a bare colour and call
   it an error message.
   ========================================================================== */

.xnet .x-alert {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 11px 14px; border-radius: var(--x-r-md);
  border: 1px solid var(--x-line); background: var(--x-surface-2);
  color: var(--x-ink-2); font-size: 13px; line-height: 1.5;
}
.xnet .x-alert + .x-alert { margin-top: 8px; }
.xnet .x-alert__title { font-weight: 600; color: var(--x-ink); display: block; margin-bottom: 2px; }
.xnet .x-alert__body { min-width: 0; }
.xnet .x-alert__icon { flex: 0 0 auto; width: 16px; height: 16px; margin-top: 1px; opacity: .85; }
.xnet .x-alert--info    { border-color: var(--x-primary); background: var(--x-tint); }
.xnet .x-alert--info    .x-alert__title { color: var(--x-primary-ink); }
.xnet .x-alert--success { border-color: var(--x-success); background: var(--x-success-bg, var(--x-surface-2)); }
.xnet .x-alert--success .x-alert__title { color: var(--x-success-ink); }
.xnet .x-alert--warning { border-color: var(--x-warning); background: var(--x-warning-bg, var(--x-surface-2)); }
.xnet .x-alert--warning .x-alert__title { color: var(--x-warning-ink); }
.xnet .x-alert--danger  { border-color: var(--x-danger);  background: var(--x-danger-bg, var(--x-surface-2)); }
.xnet .x-alert--danger  .x-alert__title { color: var(--x-danger-ink); }

/* ============================================================================
   9d. EMPTY-STATE PRIMITIVE — the class was already in use and styled NOTHING
   ----------------------------------------------------------------------------
   .x-empty appears in 3 templates and was defined in no stylesheet, so those
   call sites render unstyled text. This gives them the appearance they were
   already asking for; no markup changes.
   ========================================================================== */

/*[Note]:⛔ BARE .x-empty CARRIES NOTHING, DELIBERATELY. Its three existing call sites
  (promotion/mypromos.php:12, coupon/list.php:9, travelagent/list.php:13) already style it in
  their own page-local <style> blocks, each scoped by an id so they outrank anything here.
  An earlier version of this section styled bare .x-empty and silently repainted all three —
  a dashed frame nested inside the .x-card frame they already sit in. The claim that it was
  "defined nowhere" came from grepping css/ and not templates. The framed treatment is now
  OPT-IN; those pages migrate explicitly later, or not at all. */
.xnet .x-empty--boxed {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 34px 20px; text-align: center;
  color: var(--x-ink-3); font-size: 13px;
  border: 1px dashed var(--x-line); border-radius: var(--x-r-md);
  background: var(--x-surface);
}
.xnet .x-empty__title { font-family: var(--x-font-head); font-size: 14px; font-weight: 600; color: var(--x-ink-2); }
.xnet .x-empty__body { max-width: 46ch; }
.xnet .x-empty__action { margin-top: 6px; }
/* inside a table the frame is noise — the row already provides one */
.xnet td > .x-empty--boxed, .xnet .x-table .x-empty--boxed { border: 0; background: none; padding: 26px 12px; }

/* ============================================================================
   9e. MODAL PRIMITIVE — presentation OVER Bootstrap's .modal, not a replacement
   ----------------------------------------------------------------------------
   ⛔ 654 modal uses across 25 templates, all Bootstrap, opened by JS calling
   .modal('show'). A new modal implementation would break every one of them.
   This follows the precedent already set in this file by the booking-detail
   drawer: restyle Bootstrap's own markup and leave its JS contract untouched.

   Opt in per dialog with .x-modal on the .modal element. Without that class a
   dialog renders exactly as it does today.
   ========================================================================== */

.xnet .modal.x-modal .modal-content {
  border: 1px solid var(--x-line); border-radius: var(--x-r-lg);
  background: var(--x-surface); color: var(--x-ink);
  box-shadow: var(--x-shadow-lg, 0 18px 44px rgba(0,0,0,.22));
}
.xnet .modal.x-modal .modal-header {
  border-bottom: 1px solid var(--x-line-2); padding: 14px 18px; align-items: center;
}
.xnet .modal.x-modal .modal-title {
  font-family: var(--x-font-head); font-size: 15px; font-weight: 600; color: var(--x-ink);
}
.xnet .modal.x-modal .modal-body { padding: 16px 18px; font-size: 14px; color: var(--x-ink-2); }
.xnet .modal.x-modal .modal-footer {
  border-top: 1px solid var(--x-line-2); padding: 12px 18px; gap: 8px;
}
.xnet .modal.x-modal .close, .xnet .modal.x-modal .btn-close {
  color: var(--x-ink-3); opacity: 1; text-shadow: none;
}
.xnet .modal.x-modal .close:hover { color: var(--x-ink); }
/* a destructive confirm reads as destructive before the button is found */
.xnet .modal.x-modal--danger .modal-header { border-bottom-color: var(--x-danger); }
.xnet .modal.x-modal--danger .modal-title { color: var(--x-danger-ink); }

@media (max-width: 575px) {
  .xnet .modal.x-modal .modal-dialog { margin: 0; max-width: 100%; }
  .xnet .modal.x-modal .modal-content { border-radius: 0; min-height: 100vh; }
}

/* ----------------------------------------------------------------------------
   9e-i. COLOURED MODAL HEADER — the ink pairs with the GROUND, not the theme.
   ----------------------------------------------------------------------------
   ⛔ THE DEFECT THE PRIMITIVE ABOVE INTRODUCED, and it fails in LIGHT, which is why
   ten rounds of dark-only verification passed it. §9e styles `.modal-title` and
   `.close` DIRECTLY with theme ink — (0,4,0) and (0,4,1) — which outranks
   operator-modern.css:292 `.modal-header.bg-primary{color:#fff}` at (0,2,0) and
   the template's own `.text-white`. So every dialog that adopted .x-modal on a
   COLOURED header had its white ink replaced by --x-ink and its × by --x-ink-3,
   on a ground the utility pins independently of the theme. Measured on a leased
   tier, served bytes, both themes, revealed dialogs, 2026-09-01:

     h5.modal-title  #1d1f20 on #2c455d  1.67   h6.modal-title #1d1f20 on #2c455d 1.67
     h6.modal-title  #1d1f20 on #5980a6  3.99   h6.modal-title #e7e9ec on #6f9fce 2.29
     span ×  rgba(29,31,32,.64) on #2c455d 1.41  … on #5980a6 2.49
     span ×  rgba(231,233,236,.58) on #6f9fce 1.66

   ⭐ WHICH HALF MOVES IS DECIDED BY MEASUREMENT, AND IT IS NOT THE SAME HALF TWICE.
   A `bg-*` utility on a modal header carries STATE (this dialog is primary /
   success / danger / info-flavoured), not THEME. Four of the five grounds this
   layer re-tones at §10e are DEEP AND THEME-STABLE — none of them is flipped in
   the dark block — and each carries white in BOTH themes:
       --x-primary-700 #2c455d  9.92   --x-success #1f5f3f  7.59
       --x-danger      #8a2b2b  8.53
   For those the ground is right and the INK is the half that broke: it is put
   back to the ground's pair, which is what operator-modern.css and every one of
   these templates' own `.text-white` already said.

   ⛔ bg-info IS THE ONE EXCEPTION, AND ITS GROUND IS WHAT MOVES — forced by
   measurement, not by taste. It is the only coloured header mapped to --x-primary,
   the layer's MID-TONE, which DOES flip with the theme and CARRIES NOTHING AT AA
   IN EITHER DIRECTION:
       light #5980a6:  white 4.15 · #1d1f20 3.99 · #e7e9ec 3.41   (needs 4.5)
       dark  #6f9fce:  white 2.79 · #e7e9ec 2.29                  (needs 4.5)
   No ink rescues a ground that fails from both sides, so no ink was chosen. The
   ground moves to var(--x-info) — which :root already defines as #2c455d, the
   same deep slate every other coloured header uses. That is ADOPTING THIS LAYER'S
   OWN TOKEN, not inventing one: §10e line 881 was the single place in the estate
   that reached past --x-info for --x-primary, and --x-primary-600 was checked and
   rejected too (5.58 light / 3.54 dark — still fails dark).
   ⚠ It costs the info header its distinct hue. That is the price of a header that
   can carry text; --x-info was already #2c455d, so the vocabulary never claimed
   otherwise — only that one line did.

   ⚠ SCOPE IS DELIBERATE AND NARROW: `.modal.x-modal .modal-header.bg-*`. Every one
   of the seven rows above is produced by §9e's OWN ink rules, so this is a
   regression of the primitive and the fix belongs at the primitive. Nothing that
   merely SHARES --x-primary/--x-primary-700 as a ground can match — .x-btn--strong,
   .x-nav-item.is-active, .x-chip.is-active, .x-pill--info, .badge, the anchornav
   and non-dialog .bg-primary are all outside this selector and were measured
   IDENTICAL before and after.
   ⛔ bg-warning is DELIBERATELY NOT LISTED. §10e re-tones only primary/success/
   danger/info; a bg-warning header would resolve to gds.css's bright yellow, where
   white does NOT pass — and there are ZERO bg-warning modal headers in the estate
   (enumerated: bg-primary 83, bg-success 9, bg-info 1, bg-danger 1). Writing a
   rule for it would be a guess about a ground this layer does not control.
   ⛔ THE × KEEPS ITS SOFTENING IN THE COLOUR CHANNEL, NOT IN `opacity`. §9e sets
   opacity:1 and operator-modern used opacity:.85. Element opacity is composited by
   the browser but is NOT read back into the measured foreground, so an opacity-based
   softening would MEASURE 9.92 while RENDERING 7.70 — an instrument reading better
   than the pixels. rgba() ink is read back, so the number stays honest at 7.70.
   -------------------------------------------------------------------------- */

/* THE GROUND MOVES — bg-info only, because nothing can sit on the mid-tone. */
.xnet .modal.x-modal .modal-header.bg-info { background: var(--x-info) !important; }

/* THE INK MOVES — the header, and the title/× that override its inherited colour. */
.xnet .modal.x-modal .modal-header.bg-primary,
.xnet .modal.x-modal .modal-header.bg-success,
.xnet .modal.x-modal .modal-header.bg-danger,
.xnet .modal.x-modal .modal-header.bg-info { color: #fff; }

.xnet .modal.x-modal .modal-header.bg-primary .modal-title,
.xnet .modal.x-modal .modal-header.bg-success .modal-title,
.xnet .modal.x-modal .modal-header.bg-danger .modal-title,
.xnet .modal.x-modal .modal-header.bg-info .modal-title { color: #fff; }

.xnet .modal.x-modal .modal-header.bg-primary .close,
.xnet .modal.x-modal .modal-header.bg-success .close,
.xnet .modal.x-modal .modal-header.bg-danger .close,
.xnet .modal.x-modal .modal-header.bg-info .close {
  color: rgba(255,255,255,.85); opacity: 1; text-shadow: none;
}
.xnet .modal.x-modal .modal-header.bg-primary .close:hover,
.xnet .modal.x-modal .modal-header.bg-success .close:hover,
.xnet .modal.x-modal .modal-header.bg-danger .close:hover,
.xnet .modal.x-modal .modal-header.bg-info .close:hover { color: #fff; }

/* ============================================================================
   9f. DIVIDER — the token-driven replacement for Bootstrap's .border-bottom
   ----------------------------------------------------------------------------
   ⛔ WHY THIS EXISTS. Bootstrap's .border-bottom hardcodes #dee2e6 and does NOT
   flip. Measured 2026-08-30 while migrating searchfilter/list.php: converting a
   panel from `bg-white` to .x-card made its SURFACE token-driven while its
   dividers stayed on Bootstrap's light grey, so in dark mode the card went dark
   and the rules stayed near-white. Before that migration the page was uniformly
   light and therefore self-consistent; the half-migration was WORSE than the
   legacy page. That slice was reverted and this primitive written first.

   ⚠ It applies to EVERY family, not just that one. Any page whose surface moves
   to a token while its rules stay on a Bootstrap utility will invert the same way.

   Two forms, one vocabulary:
     .x-divider          on a block  -> token bottom rule (replaces .border-bottom)
     hr.x-divider        standalone  -> token horizontal rule
   One variant: --soft, for a hairline where a full divider is too loud.
   ========================================================================== */

.xnet .x-divider { border-bottom: 1px solid var(--x-line); }
.xnet .x-divider--soft { border-bottom-color: var(--x-line-2); }

/*[Note]:an <hr> carries UA borders on all four sides, so they are cleared before
  the rule is drawn on top. Declared after the block form so it wins for hr. */
.xnet hr.x-divider {
  border: 0; border-top: 1px solid var(--x-line);
  margin: 16px 0; height: 0;
}
.xnet hr.x-divider--soft { border-top-color: var(--x-line-2); }

/* ============================================================================
   9g · PAGINATION — dark-mode correction. Defect D2, 20 templates.
   ============================================================================
   ⛔ THE DEFECT: gds.css:429 hardcodes `background-color:#fff` on .page-link and
   on .page-item.disabled .page-link. gds.css carries ZERO dark rules and ZERO
   --x- tokens, so in dark mode the page-number strip stays a white block on a
   dark card. Two further legacy sheets style the same control on --lv- tokens.
   ⛔ color-scheme:dark does NOT rescue this the way it rescues native checkboxes:
   .page-link is an author-styled anchor, and color-scheme governs only the
   browser's OWN widgets, never a colour an author declared.

   ⭐ ABSORPTION, NOT REPLACEMENT — per Lionel: do not migrate templates merely to
   adopt a new class if the shared layer can safely absorb the existing contract.
   The markup is NOT ours to change anyway: `<ul class="pagination">` ships EMPTY
   and js/common.js:185 (UpdateDataTablePagination) fills it at runtime with
     <li class="page-item [active|disabled]">
       <a class="page-link [cursor-default]" href="#" data-pageno="N">…</a></li>
   So these rules target exactly what that generator emits. No template changes.

   ⚠ DARK-ONLY, AND THE SCOPE IS DELIBERATE. Light mode is left BYTE-IDENTICAL:
   1. The defect is a dark-mode defect. Light is not broken.
   2. The approved visual baselines (op_dashboard, op_list_booking, op_form_setting)
      were captured in LIGHT — op_list_booking is booking/list.php, one of the 20.
      Re-colouring light mode would invalidate approved baselines and produce a
      visual-regression FAIL that is genuinely this change. Dark-only cannot.
   3. Dark cannot be visually verified at all while B1 is open, so the change is
      confined to the axis that is already wrong, where the downside is bounded.
   Unifying the light palette (GDS #0088cf -> --x-primary) is a CONSISTENCY change
   with no defect behind it. It is deliberately NOT done here: it belongs with the
   dark work, once both can be proven in one run.

   ⛔ BEHAVIOUR IS UNTOUCHED. These rules set COLOUR ONLY. In particular they never
   restate `pointer-events`: Bootstrap's `.page-item.disabled .page-link{pointer-
   events:none}` is what actually stops a click reaching a disabled page number,
   and re-declaring it here risks reviving it. No layout, no cursor, no display,
   no z-index, and nothing that touches data-pageno.

   ⚠ SPECIFICITY: `body.xnet.x-force-dark` = (0,3,1) before the control's own
   classes, so every rule outranks gds.css (0,1,0 / 0,2,0) and operator-modern.css
   without an !important anywhere. user-prefs.css loads AFTER this file but styles
   no pagination, so there is no later conflict.

   ⚠ ONE MARKUP ODDITY, PRESERVED ON PURPOSE: four buttons in bookingrequest carry
   `class="request-type-bttn page-item page-link disabled"` — both classes on ONE
   element. Bootstrap's `.page-item.disabled .page-link` is a DESCENDANT selector,
   so it does not match them today and they render as ordinary page-links. The
   rules below reproduce exactly that: `.page-link` reaches them, the disabled rule
   does not. Their appearance relative to the rest is unchanged.
   ============================================================================ */
body.xnet.x-force-dark .page-link {
  color: var(--x-primary-ink);
  background-color: var(--x-surface);
  border-color: var(--x-line);
}
body.xnet.x-force-dark .page-link:hover {
  color: var(--x-primary-ink-hover);
  background-color: var(--x-tint-hover);
  border-color: var(--x-line);
}
body.xnet.x-force-dark .page-link:focus {
  box-shadow: 0 0 0 .2rem rgba(111, 159, 206, .30);
}
body.xnet.x-force-dark .page-item.active .page-link {
  color: var(--x-primary-ink-hover);
  background-color: var(--x-tint-hover);
  border-color: var(--x-primary);
}
body.xnet.x-force-dark .page-item.disabled .page-link {
  color: var(--x-ink-3);
  background-color: var(--x-surface-2);
  border-color: var(--x-line-2);
}
@media (prefers-color-scheme: dark) {
  body.xnet.x-allow-dark .page-link {
    color: var(--x-primary-ink);
    background-color: var(--x-surface);
    border-color: var(--x-line);
  }
  body.xnet.x-allow-dark .page-link:hover {
    color: var(--x-primary-ink-hover);
    background-color: var(--x-tint-hover);
    border-color: var(--x-line);
  }
  body.xnet.x-allow-dark .page-link:focus {
    box-shadow: 0 0 0 .2rem rgba(111, 159, 206, .30);
  }
  body.xnet.x-allow-dark .page-item.active .page-link {
    color: var(--x-primary-ink-hover);
    background-color: var(--x-tint-hover);
    border-color: var(--x-primary);
  }
  body.xnet.x-allow-dark .page-item.disabled .page-link {
    color: var(--x-ink-3);
    background-color: var(--x-surface-2);
    border-color: var(--x-line-2);
  }
}

/* ============================================================================
   9g-i · LIST-GROUP — dark-mode correction, and it is the SAME SHAPE AS 9e-i.
   ============================================================================
   ⛔ THE DEFECT, raised by independent QA-2 and reproduced here on served bytes:
   Bootstrap 4.3.1 (CDN) pins `.list-group-item{background-color:#fff}` at (0,1,0)
   and NO sheet in css/ overrides it for dark — grepped, not assumed; the only
   list-group rules that exist anywhere in css/ are operator-xnet.css:1268-1272,
   which set padding and gap. So the ink follows --x-ink and the ground does not:

     span "Pick-Up"  fg=rgb(231,233,236) on bg=rgb(255,255,255)  1.22  (needs 4.5)
     chain: span -> td.p-0.pr-2 -> tr.mbpickup-container -> table
            -> li.list-group-item{#fff} -> div.x-card.x-card--flush{#1e2124}

   ⭐ THE CARD ALREADY FLIPS. .x-card--flush is rgb(30,33,36) in dark and correct;
   the list item nested inside it is the half that stayed pinned. Moving the ground
   to --x-surface gives 13.30. .x-card--flush is NOT touched by any rule below and
   was measured identical before and after.

   ⚠ DARK-ONLY, for the reason 9g gives and one extra of its own. --x-surface is
   #ffffff in light — byte-identical to Bootstrap's #fff — so a theme-agnostic rule
   would be a no-op in light anyway; but --x-line is NOT byte-identical to
   Bootstrap's rgba(0,0,0,.125), so pairing the border theme-agnostically WOULD move
   light and invalidate approved light baselines. Confining the whole correction to
   dark keeps light provably untouched: no rule below matches in light at all.

   ⛔ THE BORDER DELIBERATELY DOES NOT MOVE, AND IT WAS TRIED AND WITHDRAWN ON EVIDENCE.
   Pairing it with --x-line looks right by 9f's logic, and it MEASURED WRONG: setting/
   view.php:34 already answers this exact defect page-locally —
   `.xnet .st-colcard .list-group-item{background-color:var(--x-surface);
   border-color:var(--x-line-2);color:var(--x-ink)}`, with a comment at :31 that names
   Bootstrap's pinned #fff — and that rule is (0,3,0) against this one's (0,4,1). The
   border declaration therefore OVERRODE a page's deliberate, already-correct hairline:
   measured on setting in dark, rgba(231,233,236,.09) -> rgba(231,233,236,.16). It fixed
   no measured row and contradicted a lane that had already decided, which is the exact
   anti-pattern this file records under .bg-primary ("adopting THEIR answer rather than
   inventing one makes those page-local rules redundant instead of contradicted").
   ⚠ SO ONE THING IS KNOWINGLY LEFT: Bootstrap's rgba(0,0,0,.125) over #1e2124
   composites to rgb(26,29,32) — a divider slightly DARKER than its own ground, where in
   light it is lighter. It is inverted, it is NOT invisible, and it is not a contrast
   failure. Recorded here rather than fixed, because fixing it costs another page's
   correct answer. Only the GROUND — the half that carries the 1.22 — moves.
   ⭐ The background declaration is safe against that same page for the opposite reason:
   setting/view.php chose var(--x-surface) too, so this rule sets the IDENTICAL value.

   ⛔ AND `.small` WOULD HAVE STRANDED — this is the row moving the ground CREATES.
   operator-xnet.css:1269 hardcodes `#modal-booking-view .list-group-item .small
   {color:#6b7784}`. On #fff that is 4.57 and passes, which is why it is not in any
   failure list today. On #1e2124 it is 3.54 and FAILS. Pairing it with --x-ink-2
   (8.59) is part of this fix, not an addition to it; shipping the ground alone would
   have traded one measured failure for a new one. It is dark-only too, so light keeps
   #6b7784 at 4.57.

   ⛔ `.active` IS EXCLUDED, AND THE HOVER IS RESTORED AT MATCHING SPECIFICITY.
   disruption/index.php:206-208 already answers both states page-locally, on tokens:
   `.xnet main .list-group-item.active{background:var(--x-primary)}` and
   `.xnet main .list-group-item-action:hover{background:var(--x-surface-2)}`, each
   (0,3,1) — the SAME specificity as an unguarded `body.xnet.x-force-dark
   .list-group-item`, so the winner would have been decided by source order alone.
   :not(.active) states the intent instead of relying on that, and the hover partner
   below re-asserts the page's OWN token (--x-surface-2) at a specificity that clears
   the :not(), so the affordance survives rather than being flattened by this rule.
   ⚠ 25 `.list-group-item` uses across 3 templates (booking/modal.php — reused
   verbatim by bookingrequest — disruption/index.php, setting/view.php); exactly 2 are
   `.list-group-item-action`, both in disruption. Enumerated, not assumed.
   ⚠ Only the .x-force-dark arm is instrument-verified: the scanner switches themes
   with `body.classList.add('x-force-dark')` and never exercises prefers-color-scheme.
   The .x-allow-dark twin is the same declarations and is NOT separately measured.
   ============================================================================ */

body.xnet.x-force-dark .list-group-item:not(.active) { background-color: var(--x-surface); }
body.xnet.x-force-dark .list-group-item-action:not(.active):hover,
body.xnet.x-force-dark .list-group-item-action:not(.active):focus {
  background-color: var(--x-surface-2);
}
body.xnet.x-force-dark #modal-booking-view .list-group-item .small { color: var(--x-ink-2); }

@media (prefers-color-scheme: dark) {
  body.xnet.x-allow-dark .list-group-item:not(.active) { background-color: var(--x-surface); }
  body.xnet.x-allow-dark .list-group-item-action:not(.active):hover,
  body.xnet.x-allow-dark .list-group-item-action:not(.active):focus {
    background-color: var(--x-surface-2);
  }
  body.xnet.x-allow-dark #modal-booking-view .list-group-item .small { color: var(--x-ink-2); }
}

/* ============================================================================
   9h · CUSTOM-CONTROL (switch / radio / checkbox) — dark-mode compatibility.
   Defect D1 residue: 3 candidate templates. NOT a checkbox primitive.
   ============================================================================
   ⛔ WHY ONLY THREE PAGES, AND WHY NO GENERAL ABSTRACTION. 44 of the 47 templates
   carrying checkbox markup use NATIVE inputs, and `color-scheme: dark` — declared
   on both dark paths in css/user-prefs.css — is exactly the mechanism by which a
   browser paints native controls for a dark surface. Those 44 are left ALONE, on
   purpose: restyling a control the browser already handles correctly would be a
   regression dressed as consistency. Lionel, 2026-08-30: do not create a
   platform-wide checkbox abstraction unless these three pages justify one.

   ⭐ WHY color-scheme CANNOT reach these three. Bootstrap 4.3.1 (CDN, loaded at
   inc_header.php:92) sets `.custom-control-input{position:absolute;z-index:-1;
   opacity:0}` — THE NATIVE CONTROL IS HIDDEN — and draws the visible box as a
   `::before` pseudo-element on the label with `background-color:#fff` hardcoded.
   There is no native widget left for color-scheme to paint. None of our four
   stylesheets touches custom-control at all, so nothing else corrects it either.

   ⭐ ONE SHARED RULE IS JUSTIFIED BECAUSE THE CONTRACT IS IDENTICAL — measured, all
   three, no page-local override on any of them:
     profile/view.php     1 × custom-control custom-switch          #copilot_enabled
     setting/view.php     3 × custom-control custom-radio inline    #refundpolicy1..3
     promotion/create.php 1 × custom-control custom-checkbox        .route-cb / .route-row
   Same three-element structure every time: a .custom-control wrapper, a
   .custom-control-input, a .custom-control-label. Only the variant differs.

   ⚠ LIGHT MODE IS UNTOUCHED AND THAT IS A MEASURED DECISION, not caution. All three
   render stock Bootstrap in light with no page-local custom-control CSS anywhere,
   so they are already consistent with each other and nothing is demonstrably wrong.
   Per Lionel: dark-targeted unless measurement proves a light inconsistency too.

   ⛔ BEHAVIOURAL HOOKS THAT MUST SURVIVE — these are read by real code:
     .route-row / .route-cb   querySelectorAll in promotion/create.php; it assigns
                              .checked and counts `.route-cb:checked`
     refundpolicy             radio group name, read as aCancellation.refundpolicy,
                              and carries `required`
     #copilot_enabled         posted by name
   ⛔ THEREFORE NOTHING HERE MAY TOUCH `.custom-control-input` ITSELF. Its
   opacity/position/z-index are what hide the real input while keeping it clickable
   through the label — change any of them and `.checked`, `:checked` counting and
   the form post all break while the page still LOOKS fine. Colours on the drawn
   pseudo-elements only.

   ⚠ background-COLOR, never the `background` shorthand. Bootstrap paints the tick
   and the radio dot as a background-IMAGE on ::after; the shorthand would erase it
   and leave a box that never appears to check. This estate has already shipped that
   exact bug once, on .custom-select.
   ============================================================================ */
body.xnet.x-force-dark .custom-control-label::before {
  background-color: var(--x-surface);
  border-color: var(--x-line);
}
body.xnet.x-force-dark .custom-control-input:checked ~ .custom-control-label::before {
  background-color: var(--x-primary);
  border-color: var(--x-primary);
}
body.xnet.x-force-dark .custom-control-input:disabled ~ .custom-control-label::before {
  background-color: var(--x-surface-2);
  border-color: var(--x-line-2);
}
body.xnet.x-force-dark .custom-switch .custom-control-label::after {
  background-color: var(--x-ink-2);
}
body.xnet.x-force-dark .custom-switch .custom-control-input:checked ~ .custom-control-label::after {
  background-color: var(--x-surface);
}
@media (prefers-color-scheme: dark) {
  body.xnet.x-allow-dark .custom-control-label::before {
    background-color: var(--x-surface);
    border-color: var(--x-line);
  }
  body.xnet.x-allow-dark .custom-control-input:checked ~ .custom-control-label::before {
    background-color: var(--x-primary);
    border-color: var(--x-primary);
  }
  body.xnet.x-allow-dark .custom-control-input:disabled ~ .custom-control-label::before {
    background-color: var(--x-surface-2);
    border-color: var(--x-line-2);
  }
  body.xnet.x-allow-dark .custom-switch .custom-control-label::after {
    background-color: var(--x-ink-2);
  }
  body.xnet.x-allow-dark .custom-switch .custom-control-input:checked ~ .custom-control-label::after {
    background-color: var(--x-surface);
  }
}


/* ============================================================================
   9i * ACTIVE NAV, PAGINATION CHIP AND THIRD-PARTY MAP CHROME.
   Found by the LIVE audit (96 authenticated renders), not by static capture.
   ============================================================================
   Three unrelated faults, one batch, because all three are foreground/background
   PAIRS that were only half-decided. Measured with the full 10-sheet cascade.

   --- A * THE ACTIVE NAV ITEM WAS BLUE-ON-BLUE IN BOTH THEMES -----------------
   .x-nav-item.is-active painted itself with --x-primary. That is the mid-blue
   ACCENT token (#5980a6 light / #6f9fce dark) whose bar is 3:1 because line 926
   uses it as a BORDER. As a text background it fails white in both themes:
       white on #5980a6 = 4.14      white on #6f9fce = 2.79     (need 4.5)
   --x-primary-700's own comment at line 52 reads "deep slate - ACTIVE NAV,
   strong CTAs". The token was named for this control and the control never used
   it. It is also DELIBERATELY NOT FLIPPED in dark (user-prefs.css:45), so one
   value serves both themes:
       white on #2c455d = 10.03 light AND dark.
   No new token, no flip, no widening: the rule now reaches for the token that
   was written for it.

   ! AND THE OTHER HALF OF THIS FAULT WAS MINE. Ten of the fourteen live failures
   were not the background at all - they were `.xnet .text-primary{color:
   var(--x-primary-ink) !important}`, which I landed to lift muted blue text off
   NEUTRAL surfaces. .text-primary also rides the active nav item, so that
   !important beat `.x-nav-item.is-active{color:#fff}` (both (0,2,0), mine
   important) and replaced white with an ink designed to sit on a PAGE, not on a
   filled chip: #2c455d on #5980a6 = 2.39, #9fc0e0 on #6f9fce = 1.47.
   ! THE GENERAL LESSON, and it is the same one this layer keeps re-learning from
   the other side: a FOREGROUND-ONLY !important override is unsafe on any element
   that also carries a background. I have been fixing half-applied pairs all
   programme and then shipped one. The pairing check must run in BOTH directions.
   The repair is specificity, not another !important war: (0,3,0) + !important
   outranks .text-primary and restores the pair the control declares for itself.

   --- B * THE CURRENT PAGE CHIP FAILS IN LIGHT --------------------------------
   gds.css:429 `.page-item.active .page-link{color:#fff;background:#0088cf}`
       white on #0088cf = 3.87  (need 4.5)  - /booking, /reservations
   ! THIS DELIBERATELY REVERSES A SCOPE DECISION RECORDED IN 9g ABOVE. That block
   is dark-only and says light is "a CONSISTENCY change with no defect behind
   it". That was true when it was written and is not true now: the live audit put
   a measured AA failure behind it. Only the ACTIVE chip moves; every other
   pagination state keeps its light bytes.
   ! IT INVALIDATES AN APPROVED BASELINE. op_list_booking was captured in light
   and booking/list.php is one of the 20. The next visual run SHOULD fail on it.
   That failure is genuinely this change and the baseline needs re-approval - I
   have not re-approved it here, because a baseline is QA's to re-bless, not the
   author's.

   --- C * LEAFLET SHIPS ALWAYS-WHITE CHROME --------------------------------
   inc_companyprofile.php:187 loads leaflet 1.9.4 from a CDN. Its attribution bar
   (#f8f8f8) and zoom controls (#fff) are hardcoded and carry NO dark variant, so
   in dark our `.xnet a:not(.x-btn){color:var(--x-primary-ink)}` puts a pale link
   on a white bar: 1.82, and the "+" glyph 1.93.
   The widget's chrome does not participate in the theme, so its ink must not
   either. Scoped to .leaflet-* only - this cannot reach anything we style.
   ============================================================================ */

/* A - the active item owns its own pair again. (0,3,0)+important beats
   .xnet .text-primary (0,2,0)+important. Gated/soon items are never .is-active,
   so inc_header.php's (0,3,1) :has() rules are untouched. */
.xnet .x-nav-item.is-active { color: #fff !important; }
.xnet .x-nav-item.is-active .feather,
.xnet .x-nav-item.is-active .x-nav-caret { color: #fff !important; }

/* B - the current page chip only. Colour only: no pointer-events, no layout. */
.xnet .page-item.active .page-link {
  background-color: var(--x-primary-700);
  border-color: var(--x-primary-700);
  color: #fff;
}

/* C - map chrome is light in both themes, so its ink is fixed in both themes.
   #0b4a75 on #f8f8f8 = 8.98 * #1d1f20 on #fff = 16.1 */
.xnet .leaflet-container a:not(.x-btn),
.xnet .leaflet-control-attribution a { color: #0b4a75; }
.xnet .leaflet-bar a,
.xnet .leaflet-control a span { color: #1d1f20; }

/* D - INLINE DEFINED TERM. 6 uses across 4 templates all hand-rolled the same
   idiom: a coloured word with a dotted underline, written as
   `color: darkblue; border-bottom: dotted 1px #000000`. Both literals are
   theme-blind - darkblue on the dark page surface measures 1.06, the worst
   single result in the live audit. currentColor makes the rule follow the ink,
   so one declaration serves both themes with no dark block at all.
   #2c455d on white = 10.03 * #9fc0e0 on #1e2124 = 8.54 */
.xnet .x-term { color: var(--x-primary-ink); border-bottom: 1px dotted currentColor; }


/* ============================================================================
   9j * FORM CONTROLS - 80 LIVE AA FAILURES, AND A LEDGER CLAIM OF MINE THAT HID THEM.
   ============================================================================
   Bootstrap 4.3.1 pins .form-control and .custom-select to background-color:#fff and no dark
   variant exists, while the .xnet layer re-tones the INK to --x-ink, which flips. In dark that
   is rgb(231,233,236) on rgb(255,255,255): measured 1.22 on real controls, 1.03 on disabled.
   Independent QA counted 80 visible failures across 181 controls on six ACTIVE modules.

   ⛔ I ASSERTED THE OPPOSITE IN THE LEDGER, and the assertion did real harm: I wrote that every
   input is "a pinned white box with dark text (#495057) in BOTH themes" and that this "explains
   why the live sweep reports 0 failures on inputs". Both halves were wrong. The ink is --x-ink,
   not #495057 - measured above. And the sweep is silent for an entirely different reason:
   live-contrast.js walks elements with their OWN TEXT CHILD NODES, and an input's value is a
   PROPERTY, not a text node, so all 181 controls are structurally invisible to it. I explained a
   real defect away with a fact I had not measured.

   ⛔ AND I CHECKED THE WRONG DENOMINATOR AGAIN - the third time today, on the rule I wrote
   myself. "No sheet of ours overrides it" was true of css/ and false of the cascade:
   vehicle/edit.php:80 and bookingrequest/styles.php:215 already override it in page-local
   <style> blocks, which are loaded exactly like a sheet.

   ⭐ THIS FIX IS NOT NEW. vehicle/edit.php:74-81 has carried it page-scoped for some time, with
   a note reading "the same two lines belong in the shared operator-xnet.css layer". Promoting it
   is doing what that note asked.

   LIGHT MODE IS BYTE-IDENTICAL: --x-surface IS #ffffff, exactly Bootstrap's value. Only dark
   moves, and only from broken to 12.20. */
.xnet .form-control,
.xnet .custom-select { background-color: var(--x-surface); }
.xnet .form-control:disabled, .xnet .form-control[readonly],
.xnet .custom-select:disabled { background-color: var(--x-surface-2); }


/* ============================================================================
   9k * PLACEHOLDER INK - 7 failures the instrument could not see until today.
   ============================================================================
   The walker was blind to form controls until this session (an input's value is a PROPERTY, not
   a text node), and the first run with sight found seven placeholder failures on the shipped
   surface - one of them on 18 of the 19 reachable pages.

   ⛔ MOST OF THEM HAD NO AUTHOR RULE AT ALL. Only .form-control carried one; every other input
   fell through to the BROWSER DEFAULT, rgb(117,117,117), which by definition cannot follow a
   theme. Measured on the grounds the sweep actually found: 2.89 on #2b3035, 3.80 on #e9e9ea,
   4.07 on #121212. An estate with a themed design system should not be leaving placeholder ink
   to the user agent.

   --x-ink-3 is the tertiary tier and the semantic match for deliberately de-emphasised text.
   It clears 4.5 on every ground measured: 4.98 / 4.83 / 4.62 light, 5.39 / 5.78 / 4.77 dark.
   ⚠ The thinnest is 4.62 on #e9e9ea - the search box's own grey - and that is stated rather than
   rounded away. --x-ink-2 would give 7.67 there, but it is the SECONDARY tier: a placeholder
   set in it reads as real content, which is the wrong trade for 3 points of margin.

   opacity:1 because Firefox lowers placeholder opacity by default, which would make the measured
   colour and the rendered colour disagree. */
.xnet input::placeholder,
.xnet textarea::placeholder { color: var(--x-ink-3); opacity: 1; }

/* ============================================================================
   9l. TABS — the Bootstrap `.nav-tabs` strip in the XNet dialect
   ----------------------------------------------------------------------------
   ⭐ APPROVED 2026-09-01 (Lionel), reversing the 2026-08-30 NOT-APPROVED. That
   ruling was made on "two page-local implementations". The measured number is
   ELEVEN: every one of setting/view, profile/view, route/add, route/edit,
   shuttleroute/add, shuttleroute/edit, combinedroute/edit, station/add,
   station/edit, vehicle/add and vehicle/edit carries its OWN copy of this
   dialect inside its OWN <style> block, and the shared layer defined nothing.

   ⛔ THE CANONICAL FORM IS THE ONE WITHOUT `padding`, AND THAT IS A DECISION, NOT
   A TRANSCRIPTION. The eleven copies are not identical. Nine of them (every
   route/shuttleroute/combinedroute/station/vehicle form) additionally declare
      padding:10px 14px; background:transparent;
   on `.nav-link`. Two of them (setting/view, profile/view) do not. Measured in a
   browser on 2026-09-01, in light, system-dark and forced-dark, the ONLY computed
   difference between the two dialects is the padding: 8px/16px (Bootstrap's own
   `.5rem 1rem`) against 10px/14px. `background:transparent` on the base
   `.nav-link` produced a ZERO-property delta — Bootstrap 4.3.1 sets no background
   there, so it was always a no-op; only the `.active` rule needs it.

   Three measurements decided it, in this order:
     1 ⛔ NINE OF THE ELEVEN CANNOT BE REACHED BY ANYBODY. route/shuttleroute/
       combinedroute/station/vehicle all sit under lt_vendor_platform_module
       `section.inventory`, status=disabled — on the QA tiers AND on lionel_liva.
       Every one of them answers 302 to /dashboard for every operator. Only
       setting/view and profile/view render at all, and both are the no-padding
       form. A "majority" of nine copies nobody can see is not evidence about
       what this component looks like.
     2 It is the only form whose equivalence can be PROVEN, because the only two
       pages that render are the two that carry it.
     3 A px padding opts the strip out of the shipped font-size preference:
       Bootstrap's `.5rem 1rem` scales with html[data-fontsize] (sm 14 / md 16 /
       lg 18px), a pinned 10px 14px does not.

   ⚠ SO THE NINE WILL MOVE BY 2px WHEN THEIR LANE DELETES ITS LOCAL COPY:
   `.nav-link` padding 10px/14px -> 8px/16px at the default font size. Nothing
   else changes. It is unobservable today (the surfaces do not render); if
   section.inventory ever ships, re-measure before banking it.

   ⭐ THIS IS A NO-OP UNTIL A LANE DELETES A LOCAL COPY, and that is deliberate.
   A page-local <style> is later in the cascade than this file at equal
   specificity, so where a local copy still exists it still wins — with identical
   values. Measured: exactly the eleven templates carry `nav nav-tabs` markup, so
   no page gains a rule it did not already have.
   ⛔ WITHOUT any rule at all the strip does NOT simply lose its border. It falls
   through to operator-modern.css's `--lv-*` treatment: the ul rule becomes
   #dcdcdc in light, the ACTIVE indicator and label become #0088cf (the legacy
   brand blue, measured elsewhere in this file at 3.87/4.18 — below AA in both
   themes) and margin-bottom -1px becomes 0. Removing the CLASS from the markup
   instead loses the ul border outright. Both are regressions; neither is what a
   migration lane should ship.

   SPECIFICITY. `.xnet .nav-tabs` (0,2,0) and `.xnet .nav-tabs .nav-link` (0,3,0)
   beat operator-modern.css `.nav-tabs .nav-link` (0,2,0) and
   liva-admin-design-system.css `.nav-tabs .nav-link.active` (0,2,0), both of
   which load earlier. Deliberately NOT resetting operator-modern's
   `flex-wrap:nowrap; overflow-x:auto` on `.nav-tabs` — the eleven local copies
   never reset it either, so the horizontal-scroll behaviour is inherited exactly
   as it is today.
   ========================================================================== */
.xnet .nav-tabs{ border-bottom:1px solid var(--x-line); }
.xnet .nav-tabs .nav-link{ border:0; border-bottom:2px solid transparent; margin-bottom:-1px; color:var(--x-ink-2); font-weight:500; }
.xnet .nav-tabs .nav-link:hover{ color:var(--x-ink); }
.xnet .nav-tabs .nav-link.active{ color:var(--x-primary-ink); border-bottom-color:var(--x-primary); background:transparent; }

/* ============================================================================
   9m. FIXED-LIGHT SUBSTRATE — .x-paper
   ----------------------------------------------------------------------------
   ⭐ APPROVED 2026-09-01, conditionally: "treat a QR code as content requiring a
   permanently light substrate, not an ordinary themed bg-white surface. Establish
   a semantic fixed-light/paper primitive IF the measured cases genuinely share
   that contract." Three candidates were measured. Two share it; one does not.

   THE CONTRACT: a region rendered in the LIGHT palette regardless of the
   operator's theme, because its audience is OUTSIDE the application — a phone
   camera, or a client reading a printed document. It is NOT "a white box".

   ⛔ BOTH QUALIFYING CASES ARE BROKEN TODAY, IN OPPOSITE DIRECTIONS, AND EACH IS
   HALF OF THE PAIR DEFECT THIS PROGRAMME EXISTS TO FIND. Measured on a leased
   tier, 2026-09-01, in light, system-dark (prefers-color-scheme) and forced-dark:

     profile/view.php · MFA setup QR — GROUND follows the theme, ink is pinned.
       Common\QrUtil renders black modules with no ground of its own, so the
       wrapper is the ground, and operator-modern.css re-themes `main .bg-white`
       to var(--lv-surface). Measured ground: rgb(255,255,255) light, and
       rgb(30,33,36) in BOTH dark paths. Black modules on #1e2124 is 1.31:1.
       ⚠ SCANNABILITY IS INFERRED FROM CONTRAST, NOT DECODED — no QR decoder was
       run. But a code whose modules and quiet zone differ by 1.31:1 has no
       light/dark discrimination for a camera to threshold on; the modules are
       effectively invisible. Stated as an inference on purpose.

     grouprequest/detail.php · client quote document — GROUND is pinned, INK
       follows the theme. #gq-preview-modal is the one modal grouprequest/styles
       does NOT re-theme, so Bootstrap's #fff stands in both themes while the
       body text resolves var(--x-ink). Measured worst ink: 1.22:1 for the body
       copy and 1.16:1 for the muted line, in BOTH dark paths, against 15.43 and
       6.69 in light — and the table headers take a dark band (rgb(43,48,53)) on
       white paper. The document is effectively blank in dark mode.

   ⛔ dashboard/view.php .cc-hero DOES NOT SHARE THE CONTRACT AND IS NOT INCLUDED.
   It is a purple brand gradient (#efeaff -> #e7eefc) with purple ink (#3a2f6b /
   #5b5183) and a #6c56d6 avatar: a PINNED PALETTE, not the light palette, and its
   audience is the operator inside the app. It is also already internally
   consistent — ground and ink are both pinned, so it has neither failure above.
   Forcing it in would have to repaint it white and destroy the brand. ⚠ And it
   renders for nobody anyway: lt_vendor_platform_module `copilot` is
   status=disabled on the QA tiers AND on lionel_liva. Left as recorded debt.

   ONE PRIMITIVE, NOT TWO. The QR needs only a light GROUND; the document needs
   the light PALETTE. The first requirement is a strict subset of the second, and
   one region that re-declares the light palette satisfies both — measured with
   this exact block injected: QR ground rgb(255,255,255) in all three theme arms,
   and the quote document 0 of 40 inks below AA in all three, worst 6.69, i.e.
   identical to its own light rendering.

   HOW IT WORKS, and why it is a token re-declaration rather than a colour list:
   every dark value that leaked into these two regions arrived through a token —
   var(--x-ink) directly, or a --lv-* name that user-prefs.css remaps onto the
   --x-* set. Re-declaring both families at their LIGHT values on the region makes
   every token-driven descendant land light by inheritance, without naming a
   single consumer. Same "one authority per question" shape as that remap.

   ⛔ WHAT IT DOES NOT COVER, AND DO NOT ASSUME IT DOES. Rules that hardcode a dark
   LITERAL under body.xnet.x-force-dark — the pagination `.page-link` set and the
   `.custom-control` switch set, both in this file — are not tokens and are not
   reverted inside a paper region. Neither appears in the two qualifying cases. A
   third caller that contains a paginated table or a custom switch must re-measure.

   ⛔ background CARRIES !important, AND IT HAS TO. The QR wrapper's competitor is
   `main .bg-white { background: var(--lv-surface) !important }` — you cannot beat
   an !important declaration without one. `.xnet .x-paper` is (0,2,0) against that
   rule's (0,1,1), so this wins on specificity once both are important. It is the
   minimum force: colour is left non-important, and nothing else is forced.
   ⭐ It is also the honest shape for the thing: a substrate whose entire contract
   is "this does not follow the theme" should not be overridable by a theme rule.

   USAGE (for the migration lanes that own those templates — lane P changed no
   markup): REPLACE `bg-white` with `x-paper`, do not add it alongside.
     profile/view.php:356       <span class="d-inline-block p-2 x-paper" …>
     grouprequest/detail.php    <div class="modal-content x-paper"> on
                                #gq-preview-modal (header, body and footer are all
                                white today, so the whole dialog is the paper).
   ========================================================================== */
.xnet .x-paper{
  color-scheme: light;
  --x-bg:#f2f2f3; --x-surface:#ffffff; --x-surface-2:#f5f5f8; --x-surface-3:#e9e9ea; --x-tint:#eef6ff;
  --x-ink:#1d1f20; --x-ink-2:rgba(29,31,32,.80); --x-ink-3:rgba(29,31,32,.64);
  --x-line:rgba(29,31,32,.16); --x-line-2:rgba(29,31,32,.08);
  --x-primary:#5980a6; --x-primary-600:#4a6b8a;
  --x-primary-ink:#2c455d; --x-primary-ink-hover:#1d3448; --x-tint-hover:#e4f0ff;
  --x-danger-ink:#8a2b2b; --x-success-ink:#1f5f3f; --x-warning-ink:#8a5a00; --x-info-ink:#2c455d;
  --x-success-bg:#e7f2ec; --x-warning-bg:#f6eeda; --x-danger-bg:#f7e2e2; --x-info-bg:#eef6ff;
  --x-shadow-sm:0 1px 2px rgba(43,43,45,.14);
  --x-shadow-md:0 3px 10px rgba(43,43,45,.14);
  --x-shadow-lg:0 12px 32px rgba(43,43,45,.20);
  /* the legacy family too — the quote document is Bootstrap markup, and every dark
     value that reached it did so through a --lv-* name. These are the light values
     liva-admin-design-system.css declares, not a re-point at the --x-* set. */
  --lv-ink:#2b3138; --lv-ink-2:#575757; --lv-muted:#757575;
  --lv-line:#dcdcdc; --lv-line-2:#ededed;
  --lv-surface:#ffffff; --lv-surface-2:#f8f8f8; --lv-surface-3:#f6f6f6;
  background: var(--x-surface) !important;
  color: var(--x-ink);
}

/* ============================================================================
   9n. LEGACY TABLE ROW HOVER — dark only
   ----------------------------------------------------------------------------
   ⛔ THE DEFECT, MEASURED ON /finance ON DEPLOYED BYTES, NOT INFERRED.
   operator-modern.css:195 paints the hovered row of a legacy Bootstrap table with
   `var(--lv-primary-tint)` = #e8f4fb. That token has NO dark value — it is one of
   ELEVEN legacy colour/shadow tokens (77 consuming declarations) that keep their
   LIGHT value on a dark page, while --lv-ink correctly follows the theme to
   #e7e9ec. Light ink on a light wash:

       dark, at rest    rgb(30,33,36)   / rgb(231,233,236)   13.30  ok
       dark, HOVERED    rgb(232,244,251)/ rgb(231,233,236)    1.09  ⛔ invisible
       light, hovered   rgb(232,244,251)/ rgb(33,37,41)      13.78  ok (control)

   The row's text disappears under the pointer. This is the recurring pair defect
   in its cleanest form: the ground stopped following the theme and the ink did
   not. ⭐ NOTE THE DIRECTION OF THIS FIX — it does not pin an ink to match a
   pinned ground; it lets the GROUND follow the theme so both halves move together,
   which is the version that cannot rot.

   ⛔ WHY NOT FIX THE TOKEN. Giving --lv-primary-tint a dark value would repaint
   TWELVE other declarations — .lds-btn--outline:hover, .lds-table tbody tr:hover,
   .lds-badge--info, .lds-alert--info, four focus-ring box-shadows, .rowitem.row:hover,
   ::selection and two anchornav states. That is a palette decision with visible
   product effect. This is the scoped alternative: the token is untouched and every
   one of those keeps its current value, verified before/after in both themes.

   VALUE = var(--x-surface-2), which is exactly what `.x-table tbody tr:hover`
   already uses, so a legacy table now hovers like a migrated one instead of
   inventing a third answer.

   SPECIFICITY. `body.xnet.x-force-dark .table tbody tr:hover` is (0,4,3) against
   operator-modern's (0,2,2), so it wins without !important. ⚠ A migrated table that
   carries BOTH `table` and `x-table` matches this too — harmless by measurement,
   not by luck: the value is identical to the one `.x-table` already sets.
   ⛔ DARK ONLY, BY BOTH ROUTES. Light is not broken (13.78) and is deliberately
   untouched; the forced and system-follow selectors are listed separately because
   one lives inside a media query and one does not.
   ========================================================================== */
body.xnet.x-force-dark .table tbody tr:hover { background: var(--x-surface-2); }
@media (prefers-color-scheme: dark) {
  body.xnet.x-allow-dark .table tbody tr:hover { background: var(--x-surface-2); }
}
