:root{--bg:#f4f6f9;--card:#fff;--ink:#1f2937;--muted:#656e7a;--line:#e5e7eb;--brand:#0f5f5c;--accent:#0ea5e9;--soft:#f8fafc;--panel:#f7f6f4;--fs:14px;--green:#16a34a;--amber:#d97706;--red:#dc2626;--field-line:#9aa3af;--focus:#0f5f5c;--brand-ink:#0f5f5c;--btn-bg:#0f5f5c;--on-brand:#ffffff}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--ink);font-size:var(--fs)}
a{color:var(--brand-ink);text-decoration:none}a:hover{text-decoration:underline}
.topbar{display:flex;align-items:center;gap:18px;background:var(--brand);color:#fff;padding:10px 20px;flex-wrap:wrap}
.topbar .brand{color:#fff;font-weight:700;font-size:18px}
.topbar nav{display:flex;gap:16px;flex:1;flex-wrap:wrap}
.topbar nav a{color:#dbeafe;font-size:14px}.topbar nav a:hover{color:#fff}
.topbar .user{font-size:13px;color:#dbeafe}
.logout{background:none;border:none;color:#fff;text-decoration:underline;cursor:pointer;font-size:13px;margin-left:8px}
.container{max-width:1200px;margin:22px auto;padding:0 20px}
.msg{padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px}
.msg-success{background:#dcfce7;color:#166534}.msg-warning{background:#fef3c7;color:#92400e}.msg-error{background:#fee2e2;color:#991b1b}
.msg-info{background:#eff6ff;color:#1e40af}
h1{font-size:22px;margin:0 0 4px}h2{font-size:17px;margin:24px 0 10px}
.sub{color:var(--muted);margin:0 0 18px;font-size:14px}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px}
.tile{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:16px}
.tile .n{font-size:28px;font-weight:700}.tile .l{color:var(--muted);font-size:13px;margin-top:4px}
.tile.red .n{color:var(--red)}.tile.amber .n{color:var(--amber)}.tile.green .n{color:var(--green)}
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
.card{background:var(--card);border:1px solid var(--line);border-left:6px solid var(--line);border-radius:10px;padding:14px}
.card.RED{border-left-color:var(--red)}.card.AMBER{border-left-color:var(--amber)}.card.GREEN{border-left-color:var(--green)}
.badge{display:inline-block;padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600}
.badge.RED{background:#fee2e2;color:var(--bad)}.badge.AMBER{background:#fef3c7;color:var(--warn)}.badge.GREEN{background:#dcfce7;color:var(--ok-ink)}
/* Legacy badges used --red/--amber/--green as INK on a strong tint, which
   measured 2.86:1 (amber), 3.00 (green) and 3.95 (red) — all below the 4.5
   WCAG AA needs for text this size. Those colours are correct as fills and
   wrong as ink. Re-pointed at the darker inks the modern .pill already uses;
   the badges now measure 4.51, 6.49 and 5.30 and look the same at a glance. */
.btn{display:inline-block;padding:7px 14px;border-radius:8px;border:1px solid var(--btn-bg);background:var(--btn-bg);color:var(--on-brand);font-size:14px;cursor:pointer}
.btn:hover{text-decoration:none;opacity:.92}.btn.secondary{background:#fff;color:var(--brand-ink)}.btn.danger{background:var(--red);border-color:var(--red)}
.btn.small{padding:4px 10px;font-size:13px}
table.grid{width:100%;border-collapse:collapse;background:var(--card);border-radius:10px;overflow:hidden}
table.grid th,table.grid td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--line);font-size:14px}
table.grid th{background:var(--soft);color:var(--muted);font-weight:600}
form.stack label{display:block;font-size:13px;color:var(--muted);margin:12px 0 4px;font-weight:600}
.form-control{width:100%;padding:8px 10px;border:1px solid var(--field-line);border-radius:8px;font-size:14px}
.panel{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:18px}
.muted{color:var(--muted)}
.tabs{display:flex;gap:4px;flex-wrap:wrap;border-bottom:2px solid var(--line);margin-top:14px}
.tabs a{padding:9px 15px;font-size:14px;color:var(--muted);font-weight:600;border:2px solid transparent;border-bottom:none;border-radius:8px 8px 0 0;margin-bottom:-2px}
.tabs a.active{color:var(--brand-ink);background:var(--card);border-color:var(--line)}
.tab-body{border-top-left-radius:0}.tab-sub{font-size:15px;margin:20px 0 10px}
.master-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}
.filter-bar{display:flex;gap:8px;margin:10px 0 18px;flex-wrap:wrap}.filter-bar .form-control{max-width:320px}.filter-bar select.form-control{max-width:200px}
.kv{display:grid;grid-template-columns:200px 1fr;gap:6px 12px;font-size:14px}.kv dt{color:var(--muted)}.kv dd{margin:0}
.pill-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px;align-items:center}
.hierarchy{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:12px 14px}.hierarchy .child{margin-left:18px;font-size:14px;margin-top:4px}
.addr-card{border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin-bottom:8px}
.note-item{border-left:3px solid var(--brand);padding:6px 12px;margin-bottom:10px;background:var(--soft);border-radius:0 8px 8px 0}
.inline-add{display:grid;grid-template-columns:1fr 1fr;gap:6px 16px;align-items:end;background:var(--soft);border:1px solid var(--line);border-radius:10px;padding:14px}
.inline-add .ff-wide,.inline-add button{grid-column:1/-1}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px 16px}.form-grid .ff-wide{grid-column:1/-1}
.ff label{display:block;font-size:12px;color:var(--muted);font-weight:600;margin-bottom:3px}
.ff-check{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.ff-check label{margin:0}
/* Help text under a checkbox field drops to its own full-width line instead of
   being squeezed to the right of the box. */
.ff-check > small{flex-basis:100%;width:100%;margin:2px 0 0}
.timeline{border-left:2px solid var(--line);margin-left:8px;padding-left:4px}
.tl-item{display:flex;align-items:baseline;gap:10px;padding:8px 0 8px 14px;position:relative;font-size:14px}
.tl-item::before{content:"";position:absolute;left:-7px;top:14px;width:10px;height:10px;border-radius:50%;background:var(--brand)}
.tl-date{color:var(--muted);min-width:90px;font-size:13px}.tl-text{flex:1}
/* Dropdown panel shared by the searchable select and the free-text combo.
   Colours come from the theme, never hard-coded: a fixed white panel with a
   pale blue hover ignored the theme builder entirely, and the browser's own
   <datalist> popup it replaces was worse — narrow, dark, and unstyleable.
   left:0;right:0 pins the panel to the FULL width of the field above it. */
.ss-wrap{position:relative}
.ss-list{position:absolute;z-index:50;left:0;right:0;top:100%;max-height:264px;overflow-y:auto;
  background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:var(--radius-sm,10px);
  margin-top:4px;box-shadow:0 10px 28px rgba(15,23,42,.16)}
.ss-item{padding:9px 12px;font-size:14px;cursor:pointer;color:var(--ink);line-height:1.35}
.ss-item:hover,.ss-item.ss-on{background:color-mix(in srgb,var(--brand) 10%,var(--card))}
.ss-item.ss-none{color:var(--muted);cursor:default}
.ss-item.ss-new{color:var(--brand-ink);font-weight:600;border-top:1px solid var(--line)}
.ss-item b{background:color-mix(in srgb,var(--accent) 26%,transparent);font-weight:700;border-radius:3px}
/* The caret makes it read as a dropdown rather than a plain text box. */
.ss-wrap::after{content:"";position:absolute;right:12px;top:50%;width:0;height:0;pointer-events:none;
  margin-top:-2px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid var(--muted)}
.ss-wrap > input.form-control{padding-right:30px}
.readonly-field{background:var(--soft);color:var(--muted)}
.row-actions{display:flex;gap:6px}

/* ---- Operations & Finance module styles ---- */
.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin-top:16px}
.master-card{display:flex;flex-direction:column;gap:6px;padding:16px 18px;border:1px solid var(--line,#e2ddd6);border-radius:10px;background:var(--panel,#f7f6f4);text-decoration:none;color:inherit}
.master-card:hover{border-color:var(--brand);text-decoration:none}
.master-card strong{font-size:15px}
.kv-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px 22px}
.kv-grid>div{display:flex;flex-direction:column;gap:2px;font-size:14px}
.kv-grid .k{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted,#808080);font-weight:600}
.kv-grid .kv-wide{grid-column:1/-1}
.panel-split{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.panel-split>.panel{margin:0}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:8px 0 20px}
.stat-card{background:var(--panel,#f7f6f4);border:1px solid var(--line,#e2ddd6);border-radius:10px;padding:16px}
.sc-num{font-size:24px;font-weight:800;line-height:1}
.sc-lbl{font-size:12px;color:var(--muted,#808080);margin-top:6px}
.bar{height:8px;border-radius:999px;background:#e5e7eb;overflow:hidden;margin:6px 0 2px;max-width:220px}
.bar-fill{height:100%;background:var(--brand);border-radius:999px}
.chk{display:inline-flex;align-items:center;gap:6px;font-weight:400}
.btn.small.danger{background:var(--red);border-color:var(--red)}
@media(max-width:720px){.panel-split{grid-template-columns:1fr}}

/* ---- Cascading (dependent) master dropdowns ---- */
.cascade{display:flex;flex-direction:column;gap:6px}
.cascade .cascade-sel{width:100%}

/* ---- Quick-add modal + inline add links ---- */
.addlink{font-size:12px;font-weight:600;color:var(--brand-ink);text-decoration:none;margin-left:6px}
.addlink:hover{text-decoration:underline}
.modal-back{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:flex-start;justify-content:center;z-index:50;padding:40px 16px;overflow:auto}
.modal{background:#fff;border-radius:12px;padding:22px 24px;max-width:460px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,.3)}
.modal h3{margin:0 0 14px}
.modal .ff{margin-bottom:10px}
.qa-field{display:none}
.qa-field .ff{margin-bottom:10px}

/* ---- Allocate-job info panel + deliverables ---- */
.info-panel{background:var(--panel-2,#f1efec)}
.info-role{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted,#808080);font-weight:700;margin-bottom:4px}
.info-party strong{font-size:15px}
table.mini{width:100%;border-collapse:collapse;margin-top:8px;font-size:13px}
table.mini th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted,#808080);padding:4px 8px;border-bottom:1px solid var(--line,#e2ddd6)}
table.mini td{padding:4px 8px;border-bottom:1px solid var(--line,#e2ddd6)}
.checkgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:4px 14px;margin-top:4px}
.checkgrid .chk{font-weight:400;font-size:13.5px;padding:3px 0}

/* ---- Breadcrumbs ---- */
.crumbs{font-size:12.5px;color:var(--muted,#808080);margin:0 0 10px}
.crumbs a{color:var(--brand-ink);text-decoration:none}
.crumbs a:hover{text-decoration:underline}
.skill-box{border:1px solid var(--line,#e2ddd6);border-radius:8px;padding:10px 12px;background:var(--card)}

/* ============================================================
   2026 UI refresh — professional surfaces, buttons & fields.
   Kept as a layer so it restyles every existing screen while
   the theme builder's CSS variables still drive all colours.
   ============================================================ */
:root{
  --radius:14px; --radius-sm:10px; --field:#f4f7fc; --field-line:#9aa3af;
  --shadow:0 1px 2px rgba(18,32,60,.05), 0 10px 30px rgba(18,32,60,.07);
  --shadow-sm:0 1px 2px rgba(18,32,60,.06);
}
body{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
.container{max-width:1180px;margin:26px auto;padding:0 22px}

/* Top bar — sticky, subtle depth, pill-style nav hovers */
.topbar{padding:10px 18px;gap:16px;box-shadow:0 2px 16px rgba(0,0,0,.14);position:sticky;top:0;z-index:40;flex-wrap:nowrap}
.topbar .brand{font-size:17px;letter-spacing:.01em;flex:0 0 auto}
.nav-wrap{display:flex;flex:1;align-items:center;gap:14px;justify-content:space-between;min-width:0}
.topbar nav{display:flex;gap:6px;flex-wrap:wrap;min-width:0}
.topbar nav a{padding:6px 11px;border-radius:8px;opacity:.92;white-space:nowrap;transition:background .15s,opacity .15s}
.topbar nav a:hover{background:rgba(255,255,255,.16);opacity:1;text-decoration:none}
.topbar .user{display:flex;align-items:center;gap:8px;white-space:nowrap;flex:0 0 auto}
/* hamburger — hidden on desktop */
.nav-toggle{display:none;margin-left:auto;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.25);
  color:#fff;width:40px;height:38px;border-radius:9px;font-size:18px;cursor:pointer;line-height:1}
@media (max-width:900px){
  .topbar{flex-wrap:wrap}
  .nav-toggle{display:block}
  .nav-wrap{display:none;flex-basis:100%;flex-direction:column;align-items:stretch;gap:4px;margin-top:8px}
  .nav-wrap.open{display:flex}
  .topbar nav{flex-direction:column;gap:2px;width:100%}
  .topbar nav a{padding:11px 12px;border-radius:9px;font-size:15px;background:rgba(255,255,255,.06)}
  .topbar .user{flex-wrap:wrap;gap:10px;width:100%;padding:10px 4px 4px;border-top:1px solid rgba(255,255,255,.18);margin-top:6px}
  .topbar .user .who{flex-basis:100%;opacity:.85}
}

h1{font-weight:800;letter-spacing:-.02em}
h2{font-weight:700;letter-spacing:-.01em}

/* Cards & panels — softer radius + real elevation */
.panel{border-radius:var(--radius);box-shadow:var(--shadow)}
.card,.tile{border-radius:var(--radius);box-shadow:var(--shadow-sm)}
.master-card{border-radius:var(--radius);box-shadow:var(--shadow-sm);background:var(--soft);transition:transform .12s,box-shadow .16s,border-color .16s}
.master-card:hover{transform:translateY(-2px);box-shadow:var(--shadow);border-color:var(--brand)}
.stat-card,.tile{background:var(--soft);border-radius:var(--radius);box-shadow:var(--shadow-sm);transition:transform .12s,box-shadow .16s}
.stat-card:hover,.tile:hover{transform:translateY(-2px);box-shadow:var(--shadow)}
.sc-num,.tile .n{letter-spacing:-.01em;font-variant-numeric:tabular-nums}

/* Buttons — gentle gradient sheen + coloured shadow */
.btn{border-radius:var(--radius-sm);font-weight:600;padding:9px 16px;
  background-image:linear-gradient(180deg,rgba(255,255,255,.17),rgba(255,255,255,0));
  box-shadow:0 4px 13px color-mix(in srgb,var(--brand) 30%,transparent);
  transition:filter .15s,transform .08s,box-shadow .15s}
.btn:hover{filter:brightness(1.06);opacity:1;text-decoration:none}
.btn:active{transform:translateY(1px)}
.btn.secondary{background:var(--card);background-image:none;color:var(--brand-ink);border-color:var(--line);box-shadow:var(--shadow-sm)}
.btn.secondary:hover{border-color:var(--brand-ink);filter:none}
.btn.danger{box-shadow:0 4px 13px color-mix(in srgb,var(--red) 30%,transparent)}
.btn.small{padding:5px 11px;box-shadow:none;background-image:none}
.btn.small:not(.secondary):not(.danger){box-shadow:0 2px 8px color-mix(in srgb,var(--brand) 26%,transparent)}

/* Form fields — soft ground + focus ring */
/*  font-family is NAMED, not left to the browser. A <textarea> defaults to
    monospace while <input> and <select> do not, so every multi-line field in the
    application — job descriptions, reasons, decision notes, complaint text, 128
    of them — was typeset in a different typeface from the single-line field
    beside it. Nobody chose that; it is the browser default showing through a
    rule that never said. Measured in Chromium: inputs Arial, textareas
    monospace. */
.form-control{background:var(--field);border:1px solid var(--field-line);border-radius:var(--radius-sm);
  padding:10px 12px;color:var(--ink);font-family:inherit;
  transition:border-color .15s,box-shadow .15s,background .15s}
.form-control::placeholder{color:var(--muted);opacity:1}
.form-control:focus{outline:none;border-color:var(--focus);background:var(--card);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--focus) 20%,transparent)}
select.form-control{cursor:pointer}
.readonly-field{background:var(--soft)}
.inline-add,.hierarchy,.note-item{border-radius:var(--radius-sm)}

/* Tables — bordered container, header, gentle row hover */
table.grid{border:1px solid var(--line);box-shadow:var(--shadow-sm)}
table.grid th{letter-spacing:.03em;text-transform:uppercase;font-size:11.5px}
table.grid tr:hover td{background:color-mix(in srgb,var(--brand) 5%,transparent)}

.badge{padding:3px 10px}
.crumbs a:hover{color:var(--brand-ink)}
.filter-bar .form-control{background:var(--card)}
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}

/* Tabs — single scrollable row with an accent bar on the active tab */
.tabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin;padding-bottom:1px}
.tabs a{white-space:nowrap}
.tabs a.active{box-shadow:inset 0 3px 0 var(--brand)}
.tab-body{border-radius:0 var(--radius) var(--radius) var(--radius);box-shadow:var(--shadow)}
.master-head h1{margin-bottom:2px}
/* keep wide tables from pushing the page sideways on small screens: each
   scrolls inside its own box instead of stretching (or freezing) the page */
@media (max-width:640px){
  /* GATE 4 — A DECISION BUTTON HAS TO BE HITTABLE WITH A THUMB.
     `.btn.small` is 4-5px of vertical padding, which is right beside a table row on
     a laptop and about 24px tall on a phone. Approve and Reject on a hiring request
     are an operational workflow a manager does in the field, and a 24px target next
     to its opposite is how the wrong one gets pressed. Raised to a real touch target
     on small screens only; the desktop density is untouched. */
  .btn.small{min-height:34px;padding-top:7px;padding-bottom:7px}
  /* Fallback for any table JS did not convert: scroll inside its own box
     rather than stretching the page. Data tables get the card layout below. */
  table.grid,table.dt,table.tbl{display:block;overflow-x:auto;white-space:nowrap;-webkit-overflow-scrolling:touch;max-width:100%}
  .tbl-scroll{-webkit-overflow-scrolling:touch}
  /* the widest register (quote lines) must not force a 1500px page — let it scroll */
  table.dt.qlines{min-width:0;width:max-content}
  .crumbs{flex-wrap:wrap}

  /* Professional mobile tables: each row becomes a labelled card — Field: value
     — so a wide register never scrolls sideways. Built by initResponsiveTables()
     in app.js, which tags the header row .rt-head and copies each column name
     onto its cells as data-label. */
  table.rtable,table.rtable{display:block;overflow:visible;white-space:normal;
    background:none;border:0;box-shadow:none;width:100%}
  table.rtable thead,table.rtable thead,.rt-head{display:none}
  /*  B10-CL-4 — a bare `.rt-head` (0,1,0) loses to `table.rtable tr` (0,1,1)
      below, which turns every row into a card. On a register whose header is
      a plain <tr> rather than a <thead>, the header therefore became a card
      too: a ~45px strip of run-together column names above the first record
      (verified on /candidates). Qualifying the selector (0,2,1) lets the hide
      win without touching the card layout itself.  */
  table.rtable tr.rt-head{display:none}
  table.rtable tbody,table.rtable tr,table.rtable tbody,table.rtable tr{display:block;width:auto}
  table.rtable tr,table.rtable tr{background:var(--card);border:1px solid var(--line);
    border-radius:10px;box-shadow:var(--shadow-sm);margin:0 0 10px;overflow:hidden}
  table.rtable tr:hover td,table.rtable tr:hover td{background:transparent}
  table.rtable td,table.rtable td{display:flex;gap:12px;justify-content:space-between;
    align-items:baseline;padding:8px 12px;border:0;border-bottom:1px solid var(--line);
    white-space:normal;text-align:right;font-size:13.5px;min-width:0;overflow-wrap:anywhere}
  table.rtable tr td:last-child,table.rtable tr td:last-child{border-bottom:0}
  table.rtable td[data-label]::before,table.rtable td[data-label]::before{
    content:attr(data-label);flex:0 0 40%;text-align:left;font-weight:600;color:var(--muted);
    text-transform:none;letter-spacing:0;font-size:12px}
  table.rtable td:not([data-label]),table.rtable td:not([data-label]){justify-content:flex-start;text-align:left}
  table.rtable td[data-label]:empty::after,table.rtable td[data-label]:empty::after{content:'—';color:var(--muted)}

  /* A pill is meant for a short label. When one is (mis)used to carry a long
     message it must wrap rather than push the whole page sideways; short pills
     are one line as before. (`.container` raises specificity so this wins over
     the base `.pill{white-space:nowrap}` defined later in the file.) */
  .container .pill{white-space:normal;overflow-wrap:break-word;max-width:100%}

  /* A two-column grid does not fit a phone — stack it to one column. Targets
     the inline `grid-template-columns:1fr 1fr` used across the detail panels. */
  .container [style*="grid-template-columns:1fr 1fr"]{grid-template-columns:1fr!important}
  /* Safety net: a panel whose inner content is still too wide scrolls inside
     its own box instead of pushing the whole page sideways. */
  .container .panel{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* ============================================================
   Sidebar shell + role dashboard (2026 rebuild).
   Desk roles get a grouped left rail and a slim top bar.
   Theme-builder variables still drive every colour; only new
   semantic status vars are added (nothing renamed).
   ============================================================ */
:root{
  --ok:#15803d;--ok-bg:#ecfdf5;--warn:#b45309;--warn-bg:#fffbeb;
  /* A darker green for ink on the STRONG green tint #dcfce7, where --ok
     itself only reaches 4.57. Kept separate so --ok stays what it is. */
  --ok-ink:#166534;
  --bad:#b91c1c;--bad-bg:#fef2f2;--info:#0369a1;--info-bg:#eff6ff;
  --side-w:238px;
}
body.app{background:var(--bg)}
.shell{display:flex;min-height:100vh;align-items:stretch}

/* ---- Left rail ---- */
.side{width:var(--side-w);flex:0 0 var(--side-w);background:var(--card);border-right:1px solid var(--line);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;z-index:60}
.side-brand{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px;border-bottom:1px solid var(--line)}
/* ---- Collapsible rail --------------------------------------------------
   A register with a dozen columns needs the width more than the menu does.
   Collapsed, the rail keeps its icons so navigation is still one click —
   hiding it entirely would trade a horizontal scroll for a lost menu. */
.side-collapse{display:none;background:none;border:1px solid var(--line);border-radius:7px;
  color:var(--muted);cursor:pointer;font-size:13px;line-height:1;padding:5px 8px;flex:0 0 auto}
.side-collapse:hover{border-color:var(--brand);color:var(--brand-ink)}
@media(min-width:961px){ .side-collapse{display:block} }

:root.nav-collapsed .side{width:60px;flex-basis:60px}
:root.nav-collapsed .side-brand{padding:14px 8px;justify-content:center}
:root.nav-collapsed .sb-logo b, :root.nav-collapsed .sb-logo img{display:none}
:root.nav-collapsed .side-nav{overflow-x:hidden}
/* Hide the label, keep the icon. :not(.s-ic) so the icon span survives. */
:root.nav-collapsed .s-item{justify-content:center;padding-left:0;padding-right:0;gap:0}
:root.nav-collapsed .s-item > span:not(.s-ic){display:none}
/* Group headings become a rule, so the grouping is still legible at 60px. */
:root.nav-collapsed .s-grp{font-size:0;padding:8px 0 0;margin:6px 10px 0;border-top:1px solid var(--line);height:1px;overflow:hidden}
:root.nav-collapsed .s-grp-c{display:none}
:root.nav-collapsed .s-find, :root.nav-collapsed .s-find-none{display:none}
/* Folded-away sections must still show at 60px, or the icon rail loses items
   the user cannot then find — there is no heading to click at that width. */
:root.nav-collapsed .s-sec[hidden]{display:block}
:root.nav-collapsed .side-foot .sf-meta, :root.nav-collapsed .side-foot b{display:none}
:root.nav-collapsed .side-foot{justify-content:center;padding:10px 6px}
/* The label reappears as a tooltip on hover. */
:root.nav-collapsed .s-item{position:relative}
:root.nav-collapsed .s-item:hover::after{
  content:attr(data-label);position:absolute;left:calc(100% + 8px);top:50%;transform:translateY(-50%);
  background:var(--ink);color:var(--card);font-size:12px;font-weight:600;white-space:nowrap;
  padding:5px 9px;border-radius:6px;z-index:80;pointer-events:none;box-shadow:0 4px 14px rgba(0,0,0,.2)}

/* §m — peek. The rail stays narrow so a wide register has the whole screen;
   clicking it opens the full menu OVER the page rather than pushing it aside,
   and clicking anywhere else puts it back. Nothing reflows, so the table you
   were reading does not jump while you find the menu item. */
@media(min-width:961px){
  :root.nav-collapsed.nav-peek .side{width:250px;position:fixed;top:0;bottom:0;left:0;z-index:90;
    box-shadow:6px 0 26px rgba(0,0,0,.16)}
  /* Taking the rail out of the flow would let the page slide 60px to the left.
     The shell keeps that 60px, so nothing under the cursor moves. */
  :root.nav-collapsed.nav-peek .shell{padding-left:60px}
  :root.nav-collapsed.nav-peek .side-brand{padding:14px 16px;justify-content:space-between}
  :root.nav-collapsed.nav-peek .sb-logo b, :root.nav-collapsed.nav-peek .sb-logo img{display:block}
  :root.nav-collapsed.nav-peek .s-item{justify-content:flex-start;padding-left:12px;padding-right:12px;gap:10px}
  :root.nav-collapsed.nav-peek .s-item > span:not(.s-ic){display:inline}
  :root.nav-collapsed.nav-peek .s-item:hover::after{display:none}
  :root.nav-collapsed.nav-peek .s-grp{font-size:10.5px;padding:14px 12px 5px;margin:0;border-top:0;height:auto}
  :root.nav-collapsed.nav-peek .side-foot{justify-content:flex-start;padding:12px 16px}
  :root.nav-collapsed.nav-peek .side-foot .sf-meta, :root.nav-collapsed.nav-peek .side-foot b{display:block}
}

.sb-logo{display:flex;align-items:center;gap:10px;color:var(--ink);font-weight:800;font-size:16px;text-decoration:none;min-width:0}
.sb-logo:hover{text-decoration:none}
.sb-logo b{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sb-mono{width:32px;height:32px;flex:0 0 32px;border-radius:9px;background:linear-gradient(135deg,var(--brand),var(--accent));
  color:#fff;display:grid;place-items:center;font-weight:800;font-size:15px}
/*  D5 (Gate 6) — the navigation drawer's close control was an 18px glyph in a
    20px box: well under a thumb. Inspectors are phone-first in the field, so the
    one control that gets them out of the menu has to be hittable. The glyph keeps
    its size and colour; only the TARGET grows, centred, so it looks the same and
    can actually be pressed. 44px is the accessibility floor, not an aspiration. */
.side-close{display:none;background:none;border:0;color:var(--muted);font-size:18px;cursor:pointer;line-height:1;
  min-width:44px;min-height:44px;align-items:center;justify-content:center;padding:0}
.side-nav{flex:1;overflow-y:auto;padding:8px 10px 16px}
/* A group heading is a real button now: the whole rail folds down to eight
   headings instead of forty items, which was the difference between a menu you
   read and a wall you scroll. */
.s-grp{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;
  font-size:10.5px;letter-spacing:.7px;text-transform:uppercase;color:var(--muted);
  padding:14px 12px 5px;font-weight:700;background:none;border:0;cursor:pointer;
  font-family:inherit;text-align:left}
.s-grp:hover{color:var(--ink)}
.s-grp-c{font-size:14px;line-height:1;transform:rotate(90deg);transition:transform .15s ease;opacity:.6}
.s-grp.shut .s-grp-c{transform:rotate(0deg)}
.s-grp:focus-visible{outline:2px solid var(--focus);outline-offset:-2px;border-radius:8px}
.s-sec[hidden]{display:none}
/* An author display:flex beats the browser's own [hidden] rule, so hiding a
   heading by setting the attribute did nothing on screen — the filter left
   headings standing over nothing. Both need saying explicitly. */
.s-grp[hidden]{display:none}

/* Type-to-find. Forty destinations is past the point where grouping alone
   helps; three letters gets there faster than any hierarchy. */
.s-find{position:relative;margin:2px 2px 6px}
.s-find-in{width:100%;padding:8px 30px 8px 11px;border:1px solid var(--line);border-radius:9px;
  background:var(--card);color:var(--ink);font-size:13.5px;font-family:inherit}
.s-find-in:focus{outline:none;border-color:var(--focus)}
.s-find-in::-webkit-search-cancel-button{display:none}
.s-find-x{position:absolute;right:4px;top:50%;transform:translateY(-50%);background:none;border:0;
  color:var(--muted);cursor:pointer;font-size:12px;padding:5px 7px;line-height:1}
.s-find-x:hover{color:var(--ink)}
.s-find-none{margin:2px 12px;font-size:12.5px;color:var(--muted)}
.side-nav .s-item[hidden]{display:none}
/* While filtering, the fold state is set aside — you want every match visible,
   not matches hidden inside a section you happened to have closed. */
.side-nav.filtering .s-sec{display:block}
.s-item{display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:10px;color:var(--ink);
  font-weight:500;text-decoration:none;margin:1px 0;font-size:14px}
.s-item:hover{background:var(--soft);text-decoration:none}
.s-item.on{background:color-mix(in srgb,var(--brand) 12%,transparent);color:var(--brand-ink);font-weight:700}
.s-ic{width:20px;text-align:center;font-size:15px;flex:0 0 20px}
.side-foot{border-top:1px solid var(--line);padding:12px 14px;display:flex;gap:10px;align-items:center}
.sf-av{width:34px;height:34px;flex:0 0 34px;border-radius:50%;background:var(--brand);color:#fff;
  display:grid;place-items:center;font-weight:700;font-size:12.5px}
.sf-id{min-width:0}.sf-id b{display:block;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sf-id small{color:var(--muted);font-size:11.5px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---- Main column + slim top bar ---- */
.main{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar-slim{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:12px;
  background:color-mix(in srgb,var(--card) 92%,transparent);backdrop-filter:saturate(140%) blur(6px);
  border-bottom:1px solid var(--line);padding:10px 20px}
.topbar-slim .nav-toggle{display:none;background:var(--soft);border:1px solid var(--line);color:var(--ink);
  width:38px;height:36px;border-radius:9px;font-size:17px;cursor:pointer;line-height:1;margin:0}
.tb-brand{display:none;font-weight:800;font-size:16px;color:var(--ink)}
.tb-brand:hover{text-decoration:none}
.tb-spacer{flex:1}
.tb-chip{background:var(--soft);border:1px solid var(--line);border-radius:20px;padding:5px 11px;font-size:12px;font-weight:600;color:var(--ink);white-space:nowrap}
.tb-user{display:flex;align-items:center;gap:12px}
.tb-user a{font-size:13px;font-weight:600}
/* The signature link used to be a bare ✍️ with no label, so nobody found it.
   It now reads as a control; the word drops away on a narrow screen. */
.tb-sig{display:inline-flex;align-items:center;gap:5px;background:var(--soft);border:1px solid var(--line);
  border-radius:20px;padding:4px 11px;text-decoration:none;color:var(--ink)}
.tb-sig:hover{border-color:var(--brand);color:var(--brand-ink);text-decoration:none}
@media(max-width:900px){.tb-sig span{display:none}}
.tb-logout{color:var(--muted)}
.app .container{max-width:1200px;margin:22px auto;padding:0 24px;width:100%}
.scrim{display:none;position:fixed;inset:0;background:rgba(15,23,42,.42);z-index:55}
.scrim.on{display:block}

/* ---- KPI stat cards ----------------------------------------------------
   ONE card design for every register and dashboard. Two sets of class names
   are honoured on purpose: screens were written with both `k/v/d` and the
   longer `k-lab/k-val/k-sub`, and a screen using the unsupported pair used to
   render as three lines of plain body text with no hierarchy at all. Both
   names now resolve to the same rules, so no screen can be left unstyled and
   either name is safe to write. */
/* ============================================================
   ONE KPI CARD — the colour-headed, big-number card proven on
   the Recruitment Command Centre, promoted to the whole product.

   Three designs existed: this one (global .kpi, grey label on
   top), the Recruitment one (a coloured header band), and the
   dashboard's .qcard / .tile (label BELOW the number). Same
   job, three looks, so moving between modules felt like moving
   between products.

   DESIGN ONLY. No markup changed, no metric changed, no card
   added or removed. Every existing class name still works:
   .k/.k-lab is the band, .v/.k-val the number, .d/.k-sub the
   note, .kic the icon.

   Header colours: the Recruitment palette rotates five hues.
   Three of them did not carry white text safely —
   teal 2.92:1, green 3.35:1, orange 2.67:1 against the 4.5:1
   that 10.5px bold needs. Rolling those out unchanged would
   have spread one screen's contrast fault across the product,
   so each was darkened along its own hue until it cleared:
   teal #3aa6a6 -> #2d8181 (4.60), green #4a9d5b -> #3e844c
   (4.56), orange #e08a3c -> #a6662c (4.61). Blue and purple
   already passed and are untouched. At a glance the palette
   reads the same; it is simply legible.
   ============================================================ */
.kpi-row{display:grid;gap:16px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}
.kpi{position:relative;background:var(--card);border:1px solid var(--line);border-radius:13px;
  padding:0;overflow:hidden;display:flex;flex-direction:column;
  box-shadow:0 1px 2px rgba(16,24,40,.05),0 8px 22px -18px rgba(16,24,40,.28)}
/* The label becomes a solid header band. */
.kpi .k, .kpi .k-lab, .kpi .l{display:block;background:var(--kh,var(--brand));color:#fff;
  font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;
  line-height:1.3;padding:8px 13px;padding-right:34px}
.kpi .v, .kpi .k-val{display:block;font-size:32px;font-weight:800;letter-spacing:-.5px;line-height:1;
  padding:15px 14px 0;margin:0;font-variant-numeric:tabular-nums}
.kpi .v a, .kpi .k-val a{color:inherit}
.kpi .d, .kpi .k-sub, .kpi .dd{display:block;font-size:11.5px;font-weight:600;color:var(--muted);
  line-height:1.35;padding:6px 14px 14px;margin-top:auto}
/* The icon sits in the band, not over the number. */
.kpi .kic{position:absolute;right:11px;top:6px;font-size:15px;opacity:.85;line-height:1;color:#fff}
a.kpi{text-decoration:none;color:var(--ink);transition:transform .1s ease,box-shadow .1s ease}
a.kpi:hover{transform:translateY(-1px);text-decoration:none;
  box-shadow:0 2px 4px rgba(16,24,40,.08),0 12px 26px -16px rgba(16,24,40,.36)}
/* Rotating header hues, contrast-corrected (see note above). */
.kpi-row .kpi:nth-child(5n+1),.qcards .qcard:nth-child(5n+1),.stats .tile:nth-child(5n+1){--kh:#3b6fb0}
.kpi-row .kpi:nth-child(5n+2),.qcards .qcard:nth-child(5n+2),.stats .tile:nth-child(5n+2){--kh:#2d8181}
.kpi-row .kpi:nth-child(5n+3),.qcards .qcard:nth-child(5n+3),.stats .tile:nth-child(5n+3){--kh:#3e844c}
.kpi-row .kpi:nth-child(5n+4),.qcards .qcard:nth-child(5n+4),.stats .tile:nth-child(5n+4){--kh:#7d5ba6}
.kpi-row .kpi:nth-child(5n+5),.qcards .qcard:nth-child(5n+5),.stats .tile:nth-child(5n+5){--kh:#a6662c}
/* Semantic tone now colours the BAND, so meaning is carried by the same
   element everywhere rather than by a rail on one design and a tint on
   another. The value keeps its tint so a number reads at a glance. */
.kpi.tone-ok{--kh:var(--ok)} .kpi.tone-warn{--kh:var(--warn)}
.kpi.tone-bad{--kh:var(--bad)} .kpi.tone-info{--kh:var(--info)}
.kpi.tone-ok .k-val, .kpi.tone-ok .v{color:var(--ok)}
.kpi.tone-bad .k-val, .kpi.tone-bad .v{color:var(--bad)}
.kpi.tone-warn .k-val, .kpi.tone-warn .v{color:var(--warn)}


.up{color:var(--ok)}.down{color:var(--bad)}

/* ---- Action / queue cards (money desk, coordinator queue) ---- */
.qcards{display:grid;gap:14px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}
.qcard{display:block;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:16px;box-shadow:var(--shadow-sm);text-decoration:none;color:var(--ink);
  transition:transform .12s,box-shadow .16s}
.qcard:hover{transform:translateY(-2px);box-shadow:var(--shadow);text-decoration:none}
.qcard .qic{width:38px;height:38px;border-radius:10px;display:grid;place-items:center;font-size:18px;margin-bottom:10px}
.qcard .qn{font-size:24px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
.qcard .ql{color:var(--muted);font-size:13px;margin-top:3px;font-weight:600}

/*  Placed AFTER the original .qcard rules on purpose. When this block sat with
    the rest of the KPI card design further up the file, the originals won on
    source order: `display:block` killed the flex ordering that lifts the label
    into the band, and `color:var(--muted)` kept the band text grey — measured
    at 1.7-2.0:1 on the new backgrounds, worse than what it replaced. Same
    specificity means the later rule wins, so position is the fix, not !important. */
/* ---- The dashboard's .qcard, same design, markup untouched ----
   Its children are ordered icon, number, label; `order` lifts the
   label into the band without touching a single view. */
.qcard{padding:0!important;overflow:hidden;display:flex;flex-direction:column;border-radius:13px;
  box-shadow:0 1px 2px rgba(16,24,40,.05),0 8px 22px -18px rgba(16,24,40,.28)}
.qcard .ql{order:1;background:var(--kh,var(--brand));color:#fff;font-size:10.5px;font-weight:800;
  text-transform:uppercase;letter-spacing:.6px;line-height:1.3;padding:8px 13px;padding-right:34px;margin:0}
.qcard .qn{order:2;font-size:32px;font-weight:800;line-height:1;padding:15px 14px 14px;letter-spacing:-.5px}
.qcard .qic{order:0;position:absolute;right:11px;top:6px;width:auto;height:auto;margin:0;
  background:none!important;font-size:15px;opacity:.85;color:#fff;z-index:1}
.qcard{position:relative}
.qcard.tone-ok{--kh:var(--ok)} .qcard.tone-warn{--kh:var(--warn)}
.qcard.tone-bad{--kh:var(--bad)} .qcard.tone-info{--kh:var(--info)}
.qcard:hover{transform:translateY(-1px)}

/* ---- The plain .tile, same design ---- */
.tile{padding:0;overflow:hidden;display:flex;flex-direction:column;border-radius:13px;
  box-shadow:0 1px 2px rgba(16,24,40,.05),0 8px 22px -18px rgba(16,24,40,.28)}
.tile .l{order:1;background:var(--kh,var(--brand));color:#fff;font-size:10.5px;font-weight:800;
  text-transform:uppercase;letter-spacing:.6px;line-height:1.3;padding:8px 13px;margin:0}
.tile .n{order:2;font-size:32px;font-weight:800;line-height:1;padding:15px 14px 14px;letter-spacing:-.5px}
.tile.red{--kh:var(--bad)} .tile.amber{--kh:var(--warn)} .tile.green{--kh:var(--ok)}
.tone-ok .qic{background:var(--ok-bg);color:var(--ok)}
.tone-warn .qic{background:var(--warn-bg);color:var(--warn)}
.tone-bad .qic{background:var(--bad-bg);color:var(--bad)}
.tone-info .qic{background:var(--info-bg);color:var(--info)}

/* ---- Status pills ---- */
.pill{display:inline-block;padding:3px 9px;border-radius:20px;font-size:11.5px;font-weight:700;white-space:nowrap}
.pill.p-ok{background:var(--ok-bg);color:var(--ok)}
.pill.p-warn{background:var(--warn-bg);color:var(--warn)}
.pill.p-bad{background:var(--bad-bg);color:var(--bad)}
.pill.p-info{background:var(--info-bg);color:var(--info)}
.pill.p-mut{background:var(--soft);color:var(--muted)}

/* ---- Two-column dashboard row + horizontal bars ---- */
.dash-2col{display:grid;gap:16px;grid-template-columns:2fr 1fr;margin-bottom:16px}
.ctitle{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.ctitle h3{margin:0;font-size:15px}
.ctitle a{font-size:12.5px;font-weight:600}
.hbars{display:flex;flex-direction:column;gap:11px}
.hbar{display:grid;grid-template-columns:120px 1fr 58px;align-items:center;gap:10px;font-size:13px}
.hbar .track{background:var(--soft);border-radius:6px;height:12px;overflow:hidden}
.hbar .fill{height:100%;border-radius:6px;background:var(--brand)}
.hbar .val{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}
.dl{display:flex;align-items:center;gap:16px}

/* ---- Money-desk / dashboard table ---- */
table.dt{width:100%;border-collapse:collapse;font-size:13.5px}
table.dt th,table.dt td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line)}
table.dt th{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--muted);font-weight:700}
table.dt tbody tr:hover{background:var(--soft)}
table.dt td.num,table.dt th.num{text-align:right;font-variant-numeric:tabular-nums}

/* ---- Editable grids (quote lines, site list, any data-entry table) --------
   A table of inputs was unreadable: fields collapsed to the column width, so a
   dropdown showed one character and a text box could not be read while typing.
   Give the cells room, stop the table squeezing them, and make the controls
   full height. The wrapper still scrolls sideways, which is what should give. */
table.dt.qlines{min-width:1500px;table-layout:auto}
table.dt.qlines th,table.dt.qlines td{padding:8px 8px;vertical-align:top}
table.dt.qlines .form-control{min-width:100%;height:38px;font-size:13.5px;padding:7px 9px}
table.dt.qlines textarea.form-control{height:auto}
table.dt.qlines select.form-control{padding-right:26px;text-overflow:ellipsis}
table.dt.qlines td.num{vertical-align:middle;font-weight:600}
/* Row-remove buttons stay put rather than stretching with the row. */
table.dt.qlines .btn.small{white-space:nowrap}
/* The pick-many boxes (inspection types, executing offices) scroll rather than
   pushing the rest of the form off the screen. */
.pickbox{max-height:190px;overflow-y:auto;border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:8px;background:var(--card);margin:4px 0 0}
.pickbox .ff-check{display:inline-flex;align-items:center;gap:6px;margin:3px 10px 3px 0;font-size:13px;white-space:nowrap}
/* A disabled field must look disabled, not merely be unresponsive. */
.form-control:disabled{background:var(--soft);color:var(--muted);cursor:not-allowed}
/* Same for a button. A solid blue button that does nothing when clicked reads
   as a broken app; a greyed one reads as "not yet". */
.btn:disabled,.btn[disabled]{background:var(--soft);color:var(--muted);border-color:var(--line);
  box-shadow:none;cursor:not-allowed;opacity:.85}
.btn:disabled:hover,.btn[disabled]:hover{background:var(--soft);color:var(--muted)}

/* ---- Summary count chips above list tables ---- */
.chip-row{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 6px}
.chip-row .ct{display:inline-flex;align-items:center;gap:7px;background:var(--card);border:1px solid var(--line);
  border-radius:20px;padding:5px 12px;font-size:12.5px;font-weight:600;color:var(--ink)}
.chip-row .dot{width:8px;height:8px;border-radius:50%;display:inline-block}
.chip-row .dot-warn{background:var(--warn)}.chip-row .dot-bad{background:var(--bad)}.chip-row .dot-ok{background:var(--ok)}

@media(max-width:1000px){
  .kpi-row{grid-template-columns:repeat(2,1fr)}
  .qcards{grid-template-columns:repeat(2,1fr)}
  .dash-2col{grid-template-columns:1fr}
}
@media(max-width:820px){
  .side{position:fixed;left:0;top:0;transform:translateX(-100%);transition:transform .2s ease;box-shadow:0 0 40px rgba(0,0,0,.2)}
  .side.open{transform:none}
  /*  D5 — inline-flex, not block, so the 44px box centres the glyph instead of
      leaving it in the top-left corner of a larger hit area. */
  .side-close{display:inline-flex}
  .topbar-slim .nav-toggle{display:block}
  .tb-brand{display:block}
  .app .container{margin:18px auto;padding:0 16px}

  /*  R-19 — THE TOP BAR IS A TOUCH TARGET TOO.
      A 390px sweep of all 156 openable screens found controls under the 44px
      floor on 152 of them — which sounded like 152 problems and was three:
      the jump-to button (32px), the search submit (34px) and the menu toggle
      (36px) sit in the top bar, so they appear on every page at once. Every
      other control in the application already measured 44px or more, which is
      why fixing the shared bar fixes the whole sweep.
      Deferred item D-04 is closed by this: the "36px secondary buttons" it
      described measure 44–52px today; the top bar was what was left. */
  .topbar-slim .tb-cmdk,
  .topbar-slim .nav-toggle,
  .topbar-slim form button[type="submit"]{min-height:44px;min-width:44px}
  .topbar-slim .tb-cmdk{padding-top:0;padding-bottom:0}
  /*  inline-flex so the glyph centres inside the larger box rather than
      sitting at its top-left, the same correction D5 made for .side-close. */
  .topbar-slim form button[type="submit"]{display:inline-flex;align-items:center;justify-content:center}
}
@media(max-width:560px){
  /* EVERY page could be dragged 6px sideways on a phone — against the
     blueprint's "no sideways scrolling" rule. The cause is here, not in the
     navigation drawer: the top bar is a non-wrapping flex row with 20px side
     padding, and its right-hand cluster (the user chip and Logout) has no room
     to give, so it ran past the edge. Trimming the padding and letting the
     cluster shrink puts it back inside. min-width:0 is what actually permits a
     flex item to shrink below its content width. */
  .topbar-slim{padding:10px 12px;gap:8px 10px;flex-wrap:wrap}
  .tb-user{gap:8px 10px;flex-wrap:wrap;justify-content:flex-end}
  .tb-user a{font-size:12.5px}
  .tb-spacer{flex-basis:100%;height:0}   /* pushes the user cluster onto its own row */
  .kpi-row{grid-template-columns:1fr}
  .qcards{grid-template-columns:repeat(2,1fr)}
  .tb-chip{display:none}
  table.dt{display:block;overflow-x:auto;white-space:nowrap}
}

/* ---- Inspector phone-first bottom tab bar (mobile only) ---- */
.botnav{display:none}
@media(max-width:820px){
  body.inspector .botnav{display:flex;position:fixed;bottom:0;left:0;right:0;z-index:50;
    background:var(--card);border-top:1px solid var(--line);box-shadow:0 -4px 16px rgba(0,0,0,.08);
    padding:6px 6px calc(6px + env(safe-area-inset-bottom))}
  body.inspector .botnav .bn{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;
    padding:6px 0;color:var(--muted);font-weight:700;text-decoration:none;font-size:11px;border-radius:12px}
  body.inspector .botnav .bn span{font-size:21px;line-height:1}
  body.inspector .botnav .bn em{font-style:normal}
  body.inspector .botnav .bn.on{color:var(--brand-ink);background:color-mix(in srgb,var(--brand) 10%,transparent)}
  body.inspector .app .container{padding-bottom:78px}
  body.inspector .topbar-slim .nav-toggle{display:none} /* bottom bar replaces the drawer */
}

/* ---- A field that stopped a save -------------------------------------------
   A message at the top of a long form, with nothing marked on the form itself,
   reads as "the button does not work". These ring the boxes that need filling
   and clear themselves the moment the person types. */
.field-bad{border-color:var(--bad) !important;box-shadow:0 0 0 3px rgba(185,28,28,.12) !important;background:rgba(185,28,28,.03)}
.ff-bad > label{color:var(--bad);font-weight:600}
.ff-bad > label::after{content:" — needed";font-weight:500;opacity:.8}

/* ---- The shared register (lib/datatable.php) --------------------------------
   One set of rules for forty-two lists. Sorting, paging, choosing columns and
   acting on several rows at once look the same on every screen, so learning one
   register teaches all of them.

   .sr-only is here rather than in a screen: a caption naming the table, a label
   on every checkbox and a name for the search box are what make a register
   usable without sight, and none of them should be visible to anyone else. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

.dt-wrap{background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden;
  box-shadow:var(--shadow-sm)}
.dt-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:space-between;
  padding:10px 12px;background:var(--soft);border-bottom:1px solid var(--line)}
.dt-bar-right{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-left:auto}
.dt-bar select{padding:5px 8px;font-size:12.5px;border:1px solid var(--line);border-radius:8px;
  background:var(--card);color:inherit}
.dt-search{display:flex;align-items:center;gap:6px}
.dt-search input[name=q]{padding:6px 10px;font-size:13px;border:1px solid var(--line);
  border-radius:8px;background:var(--card);color:inherit;min-width:190px}

/* Horizontal scrolling belongs to the table, never to the page. A register with
   twenty columns must not push the whole layout sideways on a phone. */
.dt-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}

.dt-wrap table.dt{margin:0}
.dt-wrap table.dt th a{color:inherit;text-decoration:none;display:inline-block}
.dt-wrap table.dt th a:hover{color:var(--brand-ink);text-decoration:underline}
.dt-wrap table.dt th[aria-sort=ascending] a,
.dt-wrap table.dt th[aria-sort=descending] a{color:var(--brand-ink)}
.dt-wrap table.dt tbody tr:last-child td{border-bottom:0}

.dt-bulk{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 12px;
  border-top:1px solid var(--line);background:var(--soft)}
.dt-pages{display:flex;align-items:center;gap:10px;justify-content:center;padding:10px 12px;
  border-top:1px solid var(--line)}
.dt-pages .btn.disabled{opacity:.45;pointer-events:none}

/* Column chooser: a <details> so it works with JavaScript switched off — the
   list is a plain form that posts and comes back. */
.dt-cols{position:relative;display:inline-block}
.dt-cols>summary{list-style:none;cursor:pointer}
.dt-cols>summary::-webkit-details-marker{display:none}
.dt-colbox{position:absolute;right:0;top:calc(100% + 6px);z-index:30;min-width:210px;max-height:320px;
  overflow:auto;display:flex;flex-direction:column;gap:6px;padding:12px;background:var(--card);
  border:1px solid var(--line);border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.16)}
.dt-colbox label{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:500;cursor:pointer}
.dt-colbox button{margin-top:4px;align-self:flex-start}

@media(max-width:640px){
  .dt-bar{gap:8px}
  .dt-search input[name=q]{min-width:0;flex:1}
  .dt-bar-right{width:100%;margin-left:0}
}
/* An older rule turns table.dt into a scrolling block on small screens. Inside
   the component .dt-scroll already does that, and a table forced to display:block
   loses its column widths and its <caption> association. Put it back. */
@media(max-width:560px){
  .dt-wrap table.dt{display:table;overflow:visible}
}

/* ---- Global record search in the top bar (lib/search.php) -------------------
   Deliberately NOT the same shape as the box in the rail: that one filters the
   menu, this one searches records, and two identical boxes doing different
   things is worse than not having the second one. */
.tb-search{display:flex;align-items:center;gap:0;margin-left:14px;flex:0 1 340px;min-width:0}
.tb-search input{flex:1;min-width:0;padding:6px 11px;font-size:13px;color:inherit;
  border:1px solid var(--line);border-right:0;border-radius:8px 0 0 8px;background:var(--card)}
.tb-search input::placeholder{color:var(--muted);opacity:1}
.tb-search button{padding:6px 11px;font-size:13px;line-height:1.35;cursor:pointer;color:inherit;
  border:1px solid var(--line);border-radius:0 8px 8px 0;background:var(--soft)}
.tb-search button:hover{background:color-mix(in srgb,var(--brand) 12%,transparent)}
.tb-search input:focus{outline:2px solid var(--focus);outline-offset:-1px;position:relative;z-index:1}
@media(max-width:820px){ .tb-search{flex-basis:auto;margin-left:8px} .tb-search input{width:110px} }
/* On a phone the box would crowd out the brand, so only the magnifier is left.
   It submits an empty term, which lands on the search page — and that page has
   a full-width box with focus already in it. Hiding search entirely on the
   device most likely to be used one-handed in a plant would be the wrong call. */
@media(max-width:560px){
  .tb-search input{display:none}
  .tb-search button{border-radius:8px;font-size:15px}
}

/* ---- "Go to anything" command palette (layout_top.php) ---------------------
   A jump list over every screen the user may open, from a top-bar button that
   stays reachable on a phone (unlike the hamburger drawer). Purely additive. */
.tb-cmdk{display:inline-flex;align-items:center;gap:6px;margin-left:10px;padding:6px 10px;
  border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--muted);
  font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}
.tb-cmdk:hover{border-color:var(--brand);color:var(--brand-ink);background:color-mix(in srgb,var(--brand) 8%,transparent)}
.tb-cmdk-k{font-size:10.5px;border:1px solid var(--line);border-radius:5px;padding:0 5px;
  background:var(--soft);color:var(--muted);font-family:inherit}
@media(max-width:820px){ .tb-cmdk-t{display:none} .tb-cmdk-k{display:none} .tb-cmdk{margin-left:8px;padding:6px 9px;font-size:15px} }

html.cmdk-open{overflow:hidden}
.cmdk{position:fixed;inset:0;z-index:200;display:flex;align-items:flex-start;justify-content:center}
.cmdk[hidden]{display:none}
.cmdk-back{position:absolute;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(2px)}
.cmdk-box{position:relative;margin-top:9vh;width:min(640px,94vw);max-height:78vh;display:flex;flex-direction:column;
  background:var(--card);border:1px solid var(--line);border-radius:14px;overflow:hidden;
  box-shadow:0 24px 60px rgba(15,23,42,.32)}
.cmdk-in-wrap{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--line)}
.cmdk-in-ic{font-size:18px}
.cmdk-in{flex:1;border:0;background:transparent;font-size:16px;color:var(--ink);outline:0;min-width:0}
.cmdk-in::placeholder{color:var(--muted)}
.cmdk-esc{font-size:11px;border:1px solid var(--line);border-radius:5px;padding:1px 6px;background:var(--soft);color:var(--muted)}
.cmdk-list{overflow-y:auto;padding:6px 6px 8px}
.cmdk-grp{font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);font-weight:700;padding:10px 10px 5px}
.cmdk-row{display:flex;align-items:center;gap:11px;padding:8px 10px;border-radius:9px;cursor:pointer}
.cmdk-row.on,.cmdk-row:hover{background:color-mix(in srgb,var(--brand) 11%,transparent)}
.cmdk-ic{width:28px;height:28px;flex:0 0 28px;border-radius:8px;background:var(--soft);display:grid;place-items:center;font-size:15px}
.cmdk-tx{min-width:0;flex:1;display:flex;flex-direction:column}
.cmdk-lb{font-size:14px;font-weight:600;color:var(--ink);display:flex;align-items:center;gap:7px}
.cmdk-tag{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:var(--brand-ink);
  background:color-mix(in srgb,var(--brand) 13%,transparent);border-radius:20px;padding:1px 7px}
.cmdk-ar{font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cmdk-pin{border:0;background:transparent;color:var(--line);font-size:16px;cursor:pointer;padding:2px 4px;line-height:1;flex:0 0 auto}
.cmdk-pin:hover{color:var(--muted)}
.cmdk-pin.on{color:var(--amber,#d97706)}
.cmdk-none{padding:24px 14px;text-align:center;color:var(--muted);font-size:13.5px}
.cmdk-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 14px;border-top:1px solid var(--line);
  font-size:11.5px;color:var(--muted)}
.cmdk-foot kbd{font-size:11px;border:1px solid var(--line);border-radius:4px;padding:0 5px;background:var(--soft);margin:0 1px}
.cmdk-foot-r{opacity:.85}
@media(max-width:560px){ .cmdk-box{margin-top:0;width:100vw;max-height:100vh;height:100%;border-radius:0} }

/* ---- The chain strip (lib/chain.php) ----------------------------------------
   Where this record sits, from enquiry to payment. A stage with nothing in it
   is drawn dashed and greyed rather than hidden: an EMPTY stage is the
   interesting one, and hiding it is how the gaps stayed invisible. */
.chain{display:flex;gap:6px;flex-wrap:wrap;align-items:stretch;margin:14px 0}
.chain-step{display:flex;flex-direction:column;gap:1px;min-width:104px;flex:1 1 104px;
  padding:8px 10px;border:1px solid var(--line);border-radius:10px;background:var(--card);
  text-decoration:none;color:inherit;position:relative}
.chain-step:hover{border-color:var(--brand);text-decoration:none}
.chain-ic{font-size:14px;line-height:1}
.chain-lb{font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700}
.chain-v{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chain-none{border-style:dashed;background:transparent;opacity:.6;cursor:default}
.chain-none .chain-v{color:var(--muted);font-weight:400}
.chain-here{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 8%,var(--card));
  box-shadow:inset 0 0 0 1px var(--brand)}
.chain-all{background:var(--soft)}
@media(max-width:640px){ .chain-step{min-width:88px;flex-basis:88px} }

/* ============================================================================
   DESIGN SYSTEM — the single source of truth
   ----------------------------------------------------------------------------
   Everything below is ADDITIVE. Not one existing rule or variable name is
   changed, because 170 screens depend on them and a redesign that breaks a
   working system is not an improvement.

   WHY THIS EXISTS, from measuring the app rather than guessing at it:

     colour      16 hardcoded values across 2,785 inline styles — already
                 disciplined, and NOT the problem
     type        40 distinct inline font sizes, including 10.5, 11.5, 12.5,
                 13.5 and 14.5px. Half-pixel sizes are drift, not decisions
     spacing     32 distinct values, most of them off any grid — 2, 3, 5, 6, 9,
                 10, 14, 18, 22, 26px

   So the drift is in TYPE and SPACE, and the mechanism is the inline style
   attribute: a screen that cannot express "slightly smaller, slightly tighter"
   in a class invents a number instead. Utilities below give every one of those
   2,785 cases a named class to use, which is what actually stops the drift.

   HOW TO USE IT
     - never write a raw px font-size or margin in a view again
     - reach for a utility class; if none fits, the scale is wrong — fix the
       scale here rather than adding one more number in one more screen
   ========================================================================= */
:root{
  /* ---- Type scale. Six steps, no half-pixels, each one a decision --------- */
  --t-xs:11px;      /* pills, table meta, timestamps                          */
  --t-sm:12px;      /* helper text under a field, muted captions             */
  --t-md:13px;      /* secondary body, dense table cells                     */
  --t-base:14px;    /* body — the size everything else is judged against     */
  --t-lg:16px;      /* card titles, sub-headings                            */
  --t-xl:18px;      /* section headings                                      */
  --t-2xl:22px;     /* page title                                            */
  --t-3xl:28px;     /* the one number on a KPI tile                          */

  --lh-tight:1.25;  /* headings                                              */
  --lh-base:1.55;   /* body — long text needs the air                        */

  --fw-normal:400; --fw-medium:600; --fw-bold:700;

  /* ---- Space scale. 4px grid, no exceptions ------------------------------ */
  --s-1:4px;  --s-2:8px;  --s-3:12px; --s-4:16px;
  --s-5:20px; --s-6:24px; --s-8:32px; --s-10:40px; --s-12:48px;

  /* ---- Semantic colour roles --------------------------------------------
     Named for the JOB, not the hue. "--danger" survives a rebrand; "--red"
     becomes a lie the first time somebody changes it. The literal --red,
     --green and --amber above are kept because existing screens use them. */
  --primary:var(--brand);
  --success:var(--green);
  --danger:var(--red);
  --warning:var(--amber);
  --info:var(--accent);
  --disabled:#9aa3af;

  /* ---- Interaction states, so hover and focus are never re-invented ------ */
  --hover:color-mix(in srgb, var(--brand) 6%, transparent);
  --selected:color-mix(in srgb, var(--brand) 10%, transparent);
  --focus-ring:0 0 0 3px color-mix(in srgb, var(--focus) 35%, transparent);
}

/* ---- Focus. One visible ring everywhere, because a keyboard user who cannot
   see where they are is locked out of the software. :focus-visible so it does
   not fire on a mouse click. ------------------------------------------------ */
:where(a,button,input,select,textarea,[tabindex]):focus-visible{
  outline:2px solid var(--focus); outline-offset:2px; box-shadow:var(--focus-ring); border-radius:4px}

/* ---- Type utilities ---------------------------------------------------- */
.t-xs{font-size:var(--t-xs)}   .t-sm{font-size:var(--t-sm)}
.t-md{font-size:var(--t-md)}   .t-base{font-size:var(--t-base)}
.t-lg{font-size:var(--t-lg)}   .t-xl{font-size:var(--t-xl)}
.t-2xl{font-size:var(--t-2xl)} .t-3xl{font-size:var(--t-3xl)}
.fw-medium{font-weight:var(--fw-medium)} .fw-bold{font-weight:var(--fw-bold)}
.t-mut{color:var(--muted)} .t-danger{color:var(--danger)} .t-success{color:var(--success)}
.t-warning{color:var(--warning)} .t-num{font-variant-numeric:tabular-nums}

/* ---- Space utilities. Only the scale exists — there is no .mt-13px ------ */
.mt-0{margin-top:0} .mt-1{margin-top:var(--s-1)} .mt-2{margin-top:var(--s-2)}
.mt-3{margin-top:var(--s-3)} .mt-4{margin-top:var(--s-4)} .mt-5{margin-top:var(--s-5)}
.mt-6{margin-top:var(--s-6)} .mt-8{margin-top:var(--s-8)}
.mb-0{margin-bottom:0} .mb-1{margin-bottom:var(--s-1)} .mb-2{margin-bottom:var(--s-2)}
.mb-3{margin-bottom:var(--s-3)} .mb-4{margin-bottom:var(--s-4)} .mb-6{margin-bottom:var(--s-6)}
.p-2{padding:var(--s-2)} .p-3{padding:var(--s-3)} .p-4{padding:var(--s-4)} .p-6{padding:var(--s-6)}

/* ---- Layout utilities. These replace the commonest inline style in the app:
   display:flex with a gap and wrapping. --------------------------------------*/
.row{display:flex;align-items:center;gap:var(--s-2);flex-wrap:wrap}
.row-top{display:flex;align-items:flex-start;gap:var(--s-2);flex-wrap:wrap}
.row-between{display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);flex-wrap:wrap}
.col{display:flex;flex-direction:column;gap:var(--s-2)}
.gap-1{gap:var(--s-1)} .gap-3{gap:var(--s-3)} .gap-4{gap:var(--s-4)}
.grow{flex:1} .nowrap{white-space:nowrap} .wrap-anywhere{overflow-wrap:anywhere}

/* ---- Empty state. A blank panel saying "No data" tells somebody nothing they
   did not already know. This gives every empty list the same three things: what
   is missing, why, and the one button that fixes it. ------------------------ */
.empty{text-align:center;padding:var(--s-10) var(--s-6);color:var(--muted)}
.empty .empty-ic{font-size:32px;line-height:1;margin-bottom:var(--s-3);opacity:.65}
.empty .empty-t{font-size:var(--t-lg);font-weight:var(--fw-medium);color:var(--ink);margin-bottom:var(--s-2)}
.empty .empty-d{font-size:var(--t-md);max-width:46ch;margin:0 auto var(--s-4)}

/* ---- Skeleton loading, so a slow screen shows its shape rather than white -- */
.skel{background:linear-gradient(90deg,var(--soft) 25%,var(--line) 37%,var(--soft) 63%);
  background-size:400% 100%;animation:skel 1.3s ease-in-out infinite;border-radius:var(--radius-sm)}
.skel-line{height:12px;margin-bottom:var(--s-2)} .skel-line:last-child{width:60%}
@keyframes skel{0%{background-position:100% 50%}100%{background-position:0 50%}}

/* ---- Respect a person who has asked for less motion. Not decoration: for
   some people animation causes nausea, and it is one line to honour. -------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important}
}

/* ---- Touch targets. 44px is the size a finger actually hits; below it people
   mis-tap and blame the software. Applied only on touch devices so the desktop
   stays dense. -------------------------------------------------------------- */
@media (pointer:coarse){
  .btn,.tabs a,.dt th a,select.form-control,input.form-control{min-height:44px}
  /*  B10-CL-6 — .btn.small was deliberately held at 36px when B7 wrote this
      block. Measured at 360px it is 3,042 of the 3,046 interactive elements
      still under the 44px floor this same block sets for every other
      control, and it is the row action on every register (Allocate, Open,
      Edit). Raised to the block's own standard; desktop is untouched
      because the whole rule is inside @media (pointer:coarse).  */
  .btn.small{min-height:44px}
  /*  …and the Disruptions filter chips, the other 4.  */
  .dz-chip{min-height:44px;display:inline-flex;align-items:center}
  /*  B6 — a panel tab is how a form is navigated on a phone, so it is a primary
      touch target and was 33px. Centring rather than padding keeps the bar the
      same height on a mouse, where the rule does not apply.  */
  .tabbtn,details.fold>summary{min-height:44px;display:flex;align-items:center}
  /*  …and let a fold's title and its explanation wrap onto separate lines
      rather than squeezing the title into a narrow column beside it.  */
  details.fold>summary{flex-wrap:wrap;gap:0 8px}
  details.fold>summary .sub{flex:1 1 100%;margin-left:18px}
}

/* ============================================================================
   FORMS — one system, replacing five
   ----------------------------------------------------------------------------
   MEASURED, not guessed:

     .ff          1,059 uses — but had NO base rule, only ".ff label"
     .form-grid     114 uses — grid-template-columns:1fr 1fr, and NOT ONE
                    media query anywhere in this file overriding it
     .field / .fgrid / .stack   13 uses between them — three more systems
     <label> with no class      1,004 uses, each screen styling it ad hoc

   What that produced on a 375px phone, measured in a real browser:

     /call-new   two columns of 144.5px; 22 of 25 fields under 160px wide
     /quote-new  two columns; narrowest field 64px

   A 64px date field is not a form. This is the single worst thing in the
   application and it is one rule.

   THE RULE: a form is a grid of fields that fit their content and collapse on
   their own. Columns are a consequence of width, never a fixed number.
   ========================================================================= */

/* Fields fit content and reflow by themselves. minmax(220px,1fr) means two
   columns on a laptop, one on a phone, three on a wide monitor — with no
   breakpoint to maintain and no field ever crushed to 64px. */
.form-grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--s-3) var(--s-4)}
/* A field that needs the full width says so, at any size. */
.form-grid .ff-wide,.form-grid .ff-full{grid-column:1/-1}
/* Below this, two columns cannot be honest whatever the minmax says. */
@media(max-width:560px){
  .form-grid{grid-template-columns:1fr}
  .form-grid .ff-wide,.form-grid .ff-full{grid-column:auto}
}

/* .ff finally has a base rule, so every field in every form is spaced the
   same whether or not it sits in a grid. */
.ff{margin-bottom:var(--s-3);min-width:0}   /* min-width:0 — grid children
       otherwise refuse to shrink below their content and force a page to
       scroll sideways on a phone */
.form-grid .ff{margin-bottom:0}             /* the grid owns the gap */
.ff label{font-size:var(--t-sm);line-height:1.35}
.ff .form-control{width:100%}
.ff-help{font-size:var(--t-sm);color:var(--muted);margin-top:var(--s-1);line-height:1.4}
.ff-err{font-size:var(--t-sm);color:var(--bad);margin-top:var(--s-1);font-weight:var(--fw-medium)}

/* Two controls that are one decision — pick from a list on the left OR type on
   the right (company-from-master vs a new name). Side by side where there is
   room, stacked on a phone so neither is ever a cramped half-width box. */
.pair-2{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-2) var(--s-3);align-items:start}
@media (max-width:560px){.pair-2{grid-template-columns:1fr}}

/* Fold-away section — the secondary detail a screen keeps one tap under, so the
   default view stays about what to do, not everything at once. Styled to read
   as a panel whose header is the toggle. */
details.fold{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow-sm);margin-top:var(--s-4);overflow:hidden}
details.fold>summary{cursor:pointer;list-style:none;padding:var(--s-3) var(--s-4);
  font-weight:var(--fw-semibold);display:flex;align-items:center;gap:var(--s-2);color:var(--ink)}
details.fold>summary::-webkit-details-marker{display:none}
details.fold>summary::before{content:"›";color:var(--muted);font-weight:700;font-size:1.15em;
  transition:transform .15s;line-height:1}
details.fold[open]>summary::before{transform:rotate(90deg)}
details.fold>summary .sub{font-weight:var(--fw-regular);color:var(--muted);font-size:var(--t-sm)}
details.fold>.fold-body{padding:0 var(--s-4) var(--s-4)}

/* "Where it is now" band — the one-glance status + next step at the top of a
   record, so a person is oriented before they read anything else. */
.nowband{border:1px solid var(--line);border-left:3px solid var(--brand);border-radius:var(--radius);
  background:var(--card);box-shadow:var(--shadow-sm);padding:var(--s-4);margin-top:var(--s-4)}
.nowband .step{font-size:var(--t-lg,17px);font-weight:var(--fw-semibold)}
.nowband .next{margin:var(--s-2) 0 0}
.nowband .cta{margin-top:var(--s-3);display:flex;gap:var(--s-2);flex-wrap:wrap}

/* Required. One marker, in one place, so a person can see before they start
   what the form will refuse to accept without. */
.ff-req > label::after{content:" *";color:var(--bad);font-weight:var(--fw-bold)}
.form-req-note{font-size:var(--t-sm);color:var(--muted);margin-bottom:var(--s-4)}
.form-req-note b{color:var(--bad)}

/* Sections. A long form that is one undifferentiated run of fields is why
   people give up halfway; grouping is what makes 40 fields feel like four
   decisions. */
.form-sec{margin:var(--s-6) 0 var(--s-3);padding-bottom:var(--s-2);border-bottom:1px solid var(--line)}
.form-sec:first-child{margin-top:0}
.form-sec h3{font-size:var(--t-lg);margin:0 0 2px;font-weight:var(--fw-medium)}
.form-sec p{font-size:var(--t-sm);color:var(--muted);margin:0}

/* The action row. Primary left, so the eye lands on it where reading stops;
   sticky on a phone so "Save" is never a scroll away at the bottom of a long
   form. */
.form-actions{display:flex;gap:var(--s-2);align-items:center;flex-wrap:wrap;
  margin-top:var(--s-5);padding-top:var(--s-4);border-top:1px solid var(--line)}
.form-actions .spacer{flex:1}
@media(max-width:560px){
  .form-actions{position:sticky;bottom:0;background:var(--card);z-index:5;
    margin:var(--s-4) calc(var(--s-4) * -1) calc(var(--s-4) * -1);
    padding:var(--s-3) var(--s-4);box-shadow:0 -2px 12px rgba(18,32,60,.08)}
  .form-actions .btn{flex:1;text-align:center}
}

/* Inputs: same height whatever the element, because a select 2px shorter than
   the text field beside it is the kind of thing nobody names and everybody
   feels. */
.form-control{min-height:38px}
textarea.form-control{min-height:76px;line-height:1.5}
.form-control:disabled,.form-control[readonly]{background:var(--soft);color:var(--muted);cursor:not-allowed}
select.form-control{appearance:none;background-image:
  linear-gradient(45deg,transparent 50%,var(--muted) 50%),
  linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 15px) 52%,calc(100% - 10px) 52%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:30px}

/* Editable tables — locations on a quotation, line items, rate rows.
   Nine columns of inputs cannot fit a 375px screen at any font size; the
   quotation's PIN-code box measured 66px wide. Crushing them is the wrong
   answer, so the table scrolls sideways instead and every field keeps a width
   a thumb can actually hit. :has() does the selecting; where it is not
   supported nothing changes, which is exactly today's behaviour. */
@media(max-width:760px){
  .table-scroll,
  table:has(.form-control){display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
  table:has(.form-control) .form-control{min-width:130px}
  table:has(.form-control) input[name*="pin"],
  table:has(.form-control) input[type="number"]{min-width:90px}
}

/* ============================================================================
   BOARDS (kanban) — sideways on a desk, stacked on a phone
   ----------------------------------------------------------------------------
   A board is columns because a desk is wide. A phone is not, and asking
   somebody to scroll sideways through seven stages to find one card — on the
   device they actually carry to site — is how a board stops being used.
   Same markup, same order; below 820px each stage becomes a section you scroll
   past normally, with its own heading and count.
   ========================================================================= */
.board-wrap{overflow-x:auto}
.board{display:flex;gap:var(--s-3);min-width:min-content;align-items:flex-start}
.board-col{min-width:250px;max-width:250px;padding:var(--s-3)}

@media(max-width:820px){
  .board-wrap{overflow-x:visible}
  .board{display:block;min-width:0}
  /* Full width, and a gap between stages rather than beside them. */
  .board-col{min-width:0;max-width:none;width:100%;margin-bottom:var(--s-3)}
  /* A stage with nothing in it earns one line, not a whole empty card — with
     seven stages stacked, the empty ones are what you scroll past to get
     anywhere. */
  .board-col:has(.board-empty){padding:var(--s-2) var(--s-3)}
}

/* The bulk bar is two rows: the buttons, then whatever control the action being
   reached for needs. Two rows rather than one because revealing a control on the
   same row moved the buttons, and a button that moves under a thumb is a mis-tap
   waiting to happen. */
.dt-bulk-wrap{border-top:1px solid var(--line);background:var(--soft)}
.dt-bulk-wrap .dt-bulk{border-top:none;background:transparent}
.dt-bulk-ctls{display:flex;gap:var(--s-2);flex-wrap:wrap;padding:0 12px 10px}
.dt-bulk-ctls:empty{display:none}
.dt-bulk-ctls .bulk-ctl[hidden]{display:none}
@media(max-width:560px){
  .dt-bulk-ctls .form-control{width:100%}
  .dt-bulk-ctls .bulk-ctl{flex:1 1 100%}
}

/* ---- Top-of-app punch bar (web-first check-in/out) --------------------- */
.punchbar{position:sticky;top:0;z-index:25;display:flex;align-items:center;gap:10px;
  flex-wrap:wrap;padding:8px 16px;background:var(--soft);border-bottom:1px solid var(--line);
  font-size:14px}
.punchbar.pb-none{color:var(--muted);font-size:13px}
.punchbar .pb-badge{font-size:15px;line-height:1}
.punchbar .pb-label{font-weight:600}
.punchbar .pb-job,.punchbar .pb-status{font-size:13px}
.punchbar .pb-status b{color:var(--ink)}
.punchbar .pb-photo{font-size:13px;display:inline-flex;align-items:center;gap:4px}
.punchbar .pb-msg{font-size:12.5px}
.punchbar .btn.small{padding:5px 12px}
@media(max-width:560px){.punchbar{padding:8px 12px}.punchbar .pb-label{display:none}}

/* ── Section tabs ──────────────────────────────────────────────────────────
   Turn a long vertical screen into a few short ones. The tabs are chips that
   WRAP onto as many rows as needed, so every tab is visible at once — you never
   scroll sideways to find one, on a phone or a desktop. Built by
   initSectionTabs() in app.js from any [data-tabs] block; with JS off the
   panels just stack (nothing hidden). */
.tabbar{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 16px}
.tabbtn{flex:0 0 auto;padding:8px 14px;border:1px solid var(--line,#e5e7eb);background:var(--card,#fff);
  border-radius:22px;font:inherit;font-size:13px;font-weight:600;color:var(--muted,#656e7a);cursor:pointer;
  white-space:nowrap;display:inline-flex;align-items:center;gap:7px;box-shadow:var(--shadow-sm,0 1px 2px rgba(18,32,60,.06));
  transition:background .12s,color .12s,border-color .12s}
.tabbtn:hover{color:var(--ink,#1f2937);border-color:var(--field-line,#9aa3af)}
.tabbtn.on{background:var(--brand,#1e40af);color:#fff;border-color:var(--brand,#1e40af)}
.tabbtn:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.tabcnt{font-size:10.5px;font-weight:700;background:var(--warn-bg,#fffbeb);color:var(--warn,#b45309);
  border-radius:20px;padding:1px 7px;line-height:1.7}
.tabbtn.on .tabcnt{background:rgba(255,255,255,.22);color:#fff}

/* ── Wizard foot: Back · step · Next / Save on the last step ─────────────── */
.fs-nav{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin:20px 0 0;padding-top:16px;border-top:1px solid var(--line,#e5e7eb)}
.fs-nav-right{margin-left:auto;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.fs-nav-right .fs-actions{display:flex;align-items:center;gap:8px;margin:0!important}
.fs-step{font-size:12.5px;font-weight:600}
@media (max-width:560px){
  .fs-nav{gap:8px}
  .fs-nav .fs-back{order:1}
  .fs-nav .fs-step{order:2;flex:1 1 auto;text-align:center}
  .fs-nav-right{order:3;margin-left:0;width:100%;justify-content:flex-end}
}

/* ── Bare-controls fallback ─────────────────────────────────────────────────
   Some older, function-rendered panels wrote <input>/<select>/<textarea>
   without the .form-control class, so they rendered as raw browser defaults and
   looked unfinished. Give any UNCLASSED text-like control the same look as
   .form-control, so the whole app is consistent without editing every file.
   Anything that already carries a class keeps its own styling; inline width /
   flex sizing is preserved. */
.container input:not([class]):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=submit]):not([type=button]):not([type=file]):not([type=image]):not([type=range]):not([type=color]),
.container select:not([class]),
.container textarea:not([class]){
  background:var(--field);border:1px solid var(--field-line);border-radius:var(--radius-sm);
  padding:9px 11px;color:var(--ink);font-family:inherit;font-size:14px;max-width:100%;box-sizing:border-box;
  transition:border-color .15s,box-shadow .15s,background .15s}
.container select:not([class]){cursor:pointer}
.container input:not([class])::placeholder,.container textarea:not([class])::placeholder{color:var(--muted);opacity:1}
.container input:not([class]):focus,.container select:not([class]):focus,.container textarea:not([class]):focus{
  outline:none;border-color:var(--focus);background:var(--card);box-shadow:0 0 0 3px color-mix(in srgb,var(--focus) 20%,transparent)}

/* Unstyled .tbl tables get the same look as .grid (and the mobile card layout
   below, via initResponsiveTables which now also targets table.tbl). */
table.tbl{width:100%;border-collapse:collapse;background:var(--card);border:1px solid var(--line);
  border-radius:10px;box-shadow:var(--shadow-sm);overflow:hidden}
table.tbl th,table.tbl td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--line);font-size:13.5px}
table.tbl th{background:var(--soft);color:var(--muted);font-weight:600;font-size:11.5px;text-transform:uppercase;letter-spacing:.03em}
table.tbl tr:hover td{background:color-mix(in srgb,var(--brand) 5%,transparent)}

/* Field-finding #9/#10 — in-page popup (embed modal): a screen opened on top of the current page. */
.embed-backdrop{position:fixed;inset:0;z-index:1000;background:rgba(15,23,42,.55);
  display:flex;align-items:flex-start;justify-content:center;padding:4vh 16px;overflow:auto}
.embed-dialog{background:var(--card,#fff);border-radius:14px;width:min(920px,100%);max-height:92vh;
  display:flex;flex-direction:column;box-shadow:0 24px 60px -12px rgba(15,23,42,.5);overflow:hidden}
.embed-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:11px 16px;border-bottom:1px solid var(--line,#e5e7eb);font-weight:600;font-size:15px;flex:none}
.embed-x{border:0;background:none;font-size:22px;line-height:1;cursor:pointer;color:var(--muted,#64748b);
  padding:2px 8px;border-radius:8px}
.embed-x:hover{background:var(--soft,#f1f5f9);color:var(--ink,#0f172a)}
.embed-frame{border:0;width:100%;height:74vh;background:var(--bg,#fff)}
/* The bare screen inside the iframe: trim the page's own outer margins so it sits flush in the modal. */
body.embed{background:var(--bg,#fff)}
body.embed .container.embed-main{max-width:none;margin:0}
@media (max-width:640px){ .embed-dialog{width:100%;max-height:96vh} .embed-frame{height:80vh} }

/* ==========================================================================
   R-19 · THE 390px PASS — rules that must not sit inside another media block.
   These were first written beside .dt-scroll, which turned out to be nested
   inside @media(max-width:820px); a phone rule hidden inside a tablet rule is
   a rule nobody can reason about. They live at the end of the file, at depth
   zero, where what they apply to is exactly what they say.
   ========================================================================== */
/*  R-19 — .tbl-wrap WAS NEVER DEFINED.
    Thirteen views wrap a table in it, plainly meaning "let this scroll". The
    rule did not exist, so on a phone those tables pushed the whole page
    sideways instead — /service-scope dragged every page 51px. A class that
    does nothing is worse than no class: the author believed it was handled,
    and so did every reviewer after them. Defined here with the same behaviour
    as its two siblings above, which is what the markup already assumed. */
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
/*  A list beside a detail pane. Two columns on a desk; stacked on a phone,
    where 260px of fixed column leaves 130px for the thing you came to read. */
@media(max-width:760px){ .dt-split{grid-template-columns:1fr!important} }
/*  R-19 — THE 1fr TRAP, ON A PHONE.
    `grid-template-columns:repeat(2,1fr)` reads as "two equal columns", but 1fr
    is minmax(AUTO,1fr) and that auto floor is min-content — so a column holding
    a <select> with long option text refuses to shrink and the grid pushes the
    whole page sideways. /service-scope dragged every page 51px exactly that way.
    There are 50 of these grids written inline across 24 views, so the overflow
    was latent in all of them and visible in whichever happened to hold the
    widest content. Matching the inline style is the only way to reach all 50
    without editing 24 files, and one column is the right answer at 390px
    regardless — the blueprint asks for a phone layout, not a squeezed desk one. */
@media(max-width:760px){
  [style*="grid-template-columns:repeat"]{grid-template-columns:1fr!important}
  /*  …and a child that says grid-column:1/3 must stop saying it.
      Collapsing the template alone was not enough, and the reason is worth
      recording: a field written to span both columns keeps asking for line 3,
      so the grid OBLIGES by creating an implicit second track, and the page
      goes on overflowing. Measured: the template read 1fr while the tracks
      still resolved to 189px + 205px. In one column everything spans the row. */
  [style*="grid-template-columns:repeat"] > [style*="grid-column"]{grid-column:1/-1!important}
}

/*  R-19 · the last six screens. Four configuration screens carry their own
    private button families — the recruitment workflow designer (.rp-btn), the
    qualification taxonomy (.qtx-btn/.qtx-tab), the password reveal (.pw-eye)
    and the channel switcher (.ch-mode-btn) — written before .btn settled at
    44px and never brought in line. Measured on a phone: 23px to 35px.
    Raised here rather than in four screens, and only at phone width, so the
    dense desk layouts those screens were designed for are untouched. */
@media(max-width:760px){
  .rp-btn, .qtx-btn, .qtx-tab, .pw-eye, .ch-mode-btn{
    min-height:44px; display:inline-flex; align-items:center; justify-content:center}
  /*  An icon-only control needs the width too, or it stays a 26px dot. */
  .rp-btn.ghost, .pw-eye{min-width:44px}
}


/* ---- UAT 1.3.5 / 1.6.1 / 1.7.1 — the one-step-back control ---------------
   Drawn by the layout for every screen, beside the breadcrumb. Styled as
   navigation chrome rather than a page action, so it never competes with the
   screen's own buttons, and sized for a thumb on a phone (44px tap target)
   because the people who get stranded are the ones using it in the field. */
.crumb-back{display:inline-flex;align-items:center;gap:6px;margin:0 10px 0 0;
  padding:6px 11px;border:1px solid var(--line);border-radius:999px;
  background:var(--card);color:var(--ink);text-decoration:none;
  font-size:13px;font-weight:600;white-space:nowrap;vertical-align:middle}
/*  --brand-ink for text and --focus for the focus ring, never the raw brand:
    on a pale workspace colour the raw brand reads at about 2:1, which is why
    the stylesheet tests forbid it. I reached for --brand here and the suite
    caught all three places. */
.crumb-back:hover{background:var(--soft);border-color:var(--brand);color:var(--brand-ink)}
.crumb-back:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
@media (max-width:760px){
  .crumb-back{min-height:44px;padding:0 14px;margin-bottom:8px}
  .crumbs{display:block}
}

/* ---- UAT 1.7.3 — the approvals workbench tab strip ------------------------
   The four approval screens live in three different areas; this strip is what
   makes them read as one place. Scrolls rather than wraps on a phone so the
   tabs never push the page sideways. */
.appr-tabs{display:flex;gap:6px;overflow-x:auto;padding:2px 0 8px;margin:0 0 12px;
  border-bottom:1px solid var(--line);scrollbar-width:thin}
.appr-tab{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;
  padding:7px 13px;border:1px solid var(--line);border-bottom:none;
  border-radius:9px 9px 0 0;background:var(--soft);color:var(--muted);
  text-decoration:none;font-size:13px;font-weight:600;white-space:nowrap}
.appr-tab:hover{background:var(--card);color:var(--ink)}
.appr-tab.on{background:var(--card);color:var(--brand-ink);border-color:var(--line);
  box-shadow:inset 0 -2px 0 var(--brand)}
.appr-tab:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
@media (max-width:760px){.appr-tab{min-height:44px}}
