:root{--bg:#f4f6f9;--card:#fff;--ink:#1f2937;--muted:#656e7a;--line:#e5e7eb;--brand:#1e40af;--accent:#0ea5e9;--soft:#f8fafc;--panel:#f7f6f4;--fs:14px;--green:#16a34a;--amber:#d97706;--red:#dc2626}
*{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);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(--brand);background:var(--brand);color:#fff;font-size:14px;cursor:pointer}
.btn:hover{text-decoration:none;opacity:.92}.btn.secondary{background:#fff;color:var(--brand)}.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(--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);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}.ff-check label{margin: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);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);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);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:#dbe3f0;
  --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);border-color:var(--line);box-shadow:var(--shadow-sm)}
.btn.secondary:hover{border-color:var(--brand);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 */
.form-control{background:var(--field);border:1px solid var(--field-line);border-radius:var(--radius-sm);
  padding:10px 12px;color:var(--ink);transition:border-color .15s,box-shadow .15s,background .15s}
.form-control::placeholder{color:var(--muted);opacity:.7}
.form-control:focus{outline:none;border-color:var(--brand);background:var(--card);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 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)}
.filter-bar .form-control{background:var(--card)}
:focus-visible{outline:2px solid var(--brand);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){
  table.grid,table.dt{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}
}
@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)}
@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}
.side-close{display:none;background:none;border:0;color:var(--muted);font-size:18px;cursor:pointer;line-height:1}
.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(--brand);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(--brand)}
.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);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);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. */
.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:var(--radius);
  padding:16px 18px;box-shadow:var(--shadow-sm);overflow:hidden}
.kpi .k, .kpi .k-lab{display:block;font-size:11.5px;font-weight:700;color:var(--muted);
  text-transform:uppercase;letter-spacing:.4px;line-height:1.35;padding-right:26px}
.kpi .v, .kpi .k-val{display:block;font-size:26px;font-weight:800;letter-spacing:-.5px;line-height:1.1;
  margin:6px 0 3px;font-variant-numeric:tabular-nums}
.kpi .v a, .kpi .k-val a{color:var(--ink)}
.kpi .d, .kpi .k-sub{display:block;font-size:12.5px;font-weight:600;color:var(--muted);line-height:1.35}
.kpi .kic{position:absolute;right:16px;top:14px;font-size:17px;opacity:.55;line-height:1}
/* A card that is a link behaves like one. */
a.kpi{text-decoration:none;color:var(--ink);transition:transform .12s,box-shadow .16s}
a.kpi:hover{transform:translateY(-2px);box-shadow:var(--shadow);text-decoration:none}
/* Semantic tone: a 3px rail down the left edge, plus a tinted value. Kept
   separate from the accent colour so "won" and "lost" read at a glance. */
.kpi.tone-ok, .kpi.tone-warn, .kpi.tone-bad, .kpi.tone-info{padding-left:21px}
.kpi.tone-ok::before, .kpi.tone-warn::before, .kpi.tone-bad::before, .kpi.tone-info::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:3px}
.kpi.tone-ok::before{background:var(--ok)}
.kpi.tone-warn::before{background:var(--warn)}
.kpi.tone-bad::before{background:var(--bad)}
.kpi.tone-info::before{background: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}
.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}
  .side-close{display:block}
  .topbar-slim .nav-toggle{display:block}
  .tb-brand{display:block}
  .app .container{margin:18px auto;padding:0 16px}
}
@media(max-width:560px){
  .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);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);text-decoration:underline}
.dt-wrap table.dt th[aria-sort=ascending] a,
.dt-wrap table.dt th[aria-sort=descending] a{color:var(--brand)}
.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:.85}
.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(--brand);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}
}

/* ---- 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(--brand) 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(--brand); 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}
  .btn.small{min-height:36px}
}

/* ============================================================================
   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}}
