/* AQUAffection Admin — Sales Tracker design language */
:root {
  --navy1: #142F5C;
  --navy2: #1b3e78;
  --cyan: #1CA3D6;
  --cyan-dark: #158ab8;
  --canvas: #eef2f7;
  --card: #ffffff;
  --ink: #16233b;
  --muted: #64748b;
  --line: #dbe3ee;
  --ok: #1a9e63;
  --warn: #d97706;
  --bad: #d64545;
  --shadow: 0 2px 10px rgba(20, 47, 92, .10);
  --radius: 12px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; overflow-x: clip; max-width: 100%; }
img { max-width: 100%; }
body { font: 15px/1.45 -apple-system, "Segoe UI", Roboto, Arial, sans-serif; background: var(--canvas); color: var(--ink); }
h1, h2, h3 { line-height: 1.2; }
a { color: var(--cyan-dark); text-decoration: none; }
a:hover { text-decoration: underline; }
[hidden] { display: none !important; }

/* ---------- header ---------- */
.app-header {
  display: flex; align-items: center; gap: 14px;
  background: linear-gradient(100deg, var(--navy1), var(--navy2));
  color: #fff; padding: 10px 18px; position: sticky; top: 0; z-index: 50;
}
.header-logo { height: 36px; }
.header-sep { width: 1px; height: 26px; background: rgba(255,255,255,.35); }
.header-title { font-size: 18px; font-weight: 600; letter-spacing: .3px; }
.header-spacer { flex: 1; }
.header-user { font-size: 13px; opacity: .85; }
.off-badge { background: #fff3cd; color: #7a5d00; border: 1px solid #e6cc6a; border-radius: 999px; padding: 3px 11px; font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.nav-toggle { display: none; background: none; border: 0; color: #fff; font-size: 22px; cursor: pointer; padding: 2px 6px; }

/* ---------- layout ---------- */
.app-body { display: flex; min-height: calc(100vh - 54px); }
.sidebar {
  width: 216px; flex: 0 0 216px; background: #fff; border-right: 1px solid var(--line);
  padding: 14px 10px; position: sticky; top: 54px; height: calc(100vh - 54px); overflow-y: auto;
}
.nav-group { font-size: 11px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin: 16px 8px 6px; }
.nav-group:first-child { margin-top: 2px; }
.nav-item {
  display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px;
  color: var(--ink); font-weight: 500; cursor: pointer; border: 0; background: none; width: 100%; text-align: left; font-size: 14.5px;
}
.nav-item:hover { background: #f1f6fb; text-decoration: none; }
.nav-item.active { background: #e2f3fb; color: #0d6d95; }
.nav-item .ico { width: 20px; text-align: center; }
.nav-badge { margin-left: auto; background: var(--bad); color: #fff; font-size: 11px; border-radius: 10px; padding: 1px 7px; font-weight: 600; }
.main { flex: 1; padding: 22px 26px 60px; min-width: 0; }

/* ---------- cards / page ---------- */
.page-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 16px; }
.page-head h1 { font-size: 22px; margin: 0; }
.page-head .spacer { flex: 1; }
/* The router puts a Back control on every drilled-into screen. Sits first so it reads as
   "leave this screen", ahead of the actions that act on it. */
.btn-back { flex: 0 0 auto; padding-left: 12px; padding-right: 14px; }
@media (max-width: 640px) { .page-head .btn-back { order: -1; } }
.card { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 20px; margin-bottom: 16px; }
.card h2 { font-size: 15px; margin: 0 0 12px; color: var(--navy1); }
.grid { display: grid; gap: 16px; margin-bottom: 16px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); }

/* KPI tiles */
.kpi { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px; cursor: pointer; }
.kpi .n { font-size: 26px; font-weight: 700; color: var(--navy1); }
.kpi .l { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.kpi.accent .n { color: var(--cyan-dark); }
.kpi.bad .n { color: var(--bad); }

/* ---------- buttons / forms ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 7px; border: 0; border-radius: 9px;
  padding: 9px 16px; font-size: 14px; font-weight: 600; cursor: pointer; background: #e8edf4; color: var(--ink);
}
.btn:hover { filter: brightness(.97); }
.btn-primary { background: var(--cyan); color: #fff; box-shadow: 0 2px 6px rgba(28,163,214,.35); }
.btn-primary:hover { background: var(--cyan-dark); filter: none; }
.btn-danger { background: #fbe9e9; color: var(--bad); }
.btn-ghost { background: rgba(255,255,255,.14); color: #fff; }
.btn-sm { padding: 5px 11px; font-size: 13px; border-radius: 7px; }
.btn-block { width: 100%; justify-content: center; }
.btn[disabled] { opacity: .5; cursor: default; }

label.f { display: block; margin-bottom: 12px; font-size: 13px; font-weight: 600; color: #33415c; }
label.f > input, label.f > select, label.f > textarea, .login-card label > input {
  display: block; width: 100%; margin-top: 5px; padding: 9px 11px; font-size: 14.5px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink); font-family: inherit;
}
label.f > textarea { min-height: 84px; resize: vertical; }
/* Date (and kin) fields at the same width as every other box. iOS Safari sizes these to their
   own intrinsic width and ignores width:100% until the native appearance is switched off — the
   jobcard deadline box was the visible casualty. min-height keeps the tap target once the
   native chrome is gone. */
label.f > input[type=date], label.f > input[type=month], label.f > input[type=time], label.f > input[type=datetime-local] {
  -webkit-appearance: none; appearance: none; width: 100%; min-height: 42px;
}
label.f > input:focus, label.f > select:focus, label.f > textarea:focus, .login-card input:focus {
  outline: 2px solid var(--cyan); outline-offset: 0; border-color: var(--cyan);
}
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 0 18px; }
.form-error { background: #fbe9e9; color: var(--bad); border-radius: 8px; padding: 9px 12px; font-size: 13.5px; margin-bottom: 12px; }
.hint { font-size: 12.5px; color: var(--muted); }

/* ---------- tables ---------- */
.tbl-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 14px; }
.tbl th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); padding: 8px 10px; border-bottom: 2px solid var(--line); white-space: nowrap; }
.tbl td { padding: 9px 10px; border-bottom: 1px solid #edf1f7; vertical-align: top; }
.tbl tr.rowlink { cursor: pointer; }
.tbl tr.rowlink:hover { background: #f4f8fc; }
.tbl .num { text-align: right; font-variant-numeric: tabular-nums; }

/* badges */
.badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.b-issued { background: #ede9fe; color: #6d28d9; }
.b-assigned { background: #e0f2fe; color: #0369a1; }
.b-inprogress { background: #fef3c7; color: #b45309; }
.b-completed { background: #d9f2e5; color: #157347; }
.b-deleted { background: #f1f5f9; color: #64748b; }
.b-ok { background: #d9f2e5; color: #157347; }
.b-warn { background: #fef3c7; color: #b45309; }
.b-bad { background: #fbe9e9; color: #c03535; }
.b-muted { background: #eef2f7; color: #5a6b85; }
/* planned = scheduled work we control · unplanned = reactive call-out */
.b-planned { background: #e2f3fb; color: #0e7fac; }
.b-unplanned { background: #fdf0e6; color: #b45309; }

/* ---------- search bar ---------- */
.toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.toolbar input[type=search], .toolbar select {
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 8px; font-size: 14px; background: #fff;
}
.toolbar input[type=search] { flex: 1 1 220px; min-width: 0; }
.toolbar input:focus, .toolbar select:focus { outline: 2px solid var(--cyan); border-color: var(--cyan); }

/* ---------- login ---------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(120deg, var(--navy1), var(--navy2)); padding: 20px; }
/* A button that reads as a link. It has to be a <button>, not an <a>: there is nowhere to go —
   it swaps one panel for another — and an anchor with no href is unreachable by keyboard. */
.linkish { display: block; margin: 14px auto 0; background: none; border: 0; padding: 6px;
  color: var(--cyan-dark); font: inherit; font-size: 13.5px; cursor: pointer; text-decoration: underline; }
.linkish:hover { color: var(--navy1); }
/* the quiet counterpart of .form-error — a statement, not a failure */
.form-note { background: #eef7fc; border: 1px solid #cfe6f5; color: #24506b;
  border-radius: 8px; padding: 9px 11px; font-size: 13px; line-height: 1.4; margin: 10px 0 4px; }
.login-card { background: #fff; border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,.25); padding: 34px 34px 28px; width: 100%; max-width: 400px; text-align: center; }
.login-logo { height: 54px; margin-bottom: 8px; }
.login-card h1 { margin: 0; font-size: 24px; color: var(--navy1); }
.login-sub { color: var(--muted); margin: 4px 0 22px; font-size: 14px; }
.login-card form { text-align: left; }
.login-card label { display: block; margin-bottom: 14px; font-size: 13px; font-weight: 600; color: #33415c; }

/* ---------- modal / toast ---------- */
.modal-back { position: fixed; inset: 0; background: rgba(15,26,47,.55); display: flex; align-items: flex-start; justify-content: center; padding: 40px 16px; z-index: 100; }
.modal { background: #fff; border-radius: 14px; box-shadow: 0 16px 60px rgba(0,0,0,.3); width: 100%; max-width: 640px; padding: 22px 24px 0; max-height: calc(100vh - 80px); overflow-y: auto; overscroll-behavior: contain; }
.modal.wide { max-width: 900px; }
.modal h2 { margin: 0 0 16px; font-size: 18px; color: var(--navy1); }
/* action buttons stay pinned to the bottom of the (scrolling) modal, always visible */
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; position: sticky; bottom: 0; margin: 18px -24px 0; padding: 14px 24px; background: #fff; border-top: 1px solid var(--line); border-radius: 0 0 14px 14px; z-index: 2; }

/* Phone browsers measure vh against the TALL viewport — the one you get with the toolbars hidden.
   A modal sized in vh is therefore taller than what you can actually see, and its footer, Cancel
   and Save included, sits underneath Safari's bottom bar or Android's navigation. dvh measures what
   is on screen right now and shrinks as the toolbars appear.
   Kept behind @supports so anything too old to know dvh keeps the vh sizing above rather than
   losing its height rule altogether. */
@supports (height: 100dvh) {
  .modal { max-height: calc(100dvh - 80px); }
}
.update-bar { position: fixed; left: 50%; transform: translateX(-50%); bottom: 20px; z-index: 300; display: flex; align-items: center; gap: 12px; background: var(--navy1); color: #fff; padding: 10px 12px 10px 18px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,.28); font-size: 14px; max-width: calc(100vw - 24px); }
.update-bar .btn { background: #fff; color: var(--navy1); border: 0; }
.update-x { background: transparent; border: 0; color: rgba(255,255,255,.7); font-size: 20px; line-height: 1; cursor: pointer; padding: 0 4px; }
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--navy1); color: #fff; padding: 11px 20px; border-radius: 10px; box-shadow: var(--shadow); z-index: 200; font-size: 14px; animation: toastin .2s ease-out; }
.toast.err { background: var(--bad); }
@keyframes toastin { from { opacity: 0; transform: translate(-50%, 8px); } }

/* ---------- detail pages ---------- */
.dl { display: grid; grid-template-columns: 190px 1fr; gap: 6px 14px; font-size: 14px; }
.dl dt { color: var(--muted); font-size: 13px; padding-top: 1px; }
.dl dd { margin: 0; min-width: 0; word-wrap: break-word; }
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 120px), 1fr)); gap: 10px; }
.photo-grid img { width: 100%; height: 110px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); cursor: pointer; }
.sig-pad { border: 1.5px dashed var(--line); border-radius: 10px; background: #fff; touch-action: none; width: 100%; height: 160px; }

/* leave calendar */
.cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal .dow { font-size: 11px; text-transform: uppercase; color: var(--muted); text-align: center; padding: 4px 0; }
.cal .day { min-height: 74px; background: #fff; border: 1px solid #e8edf4; border-radius: 8px; padding: 4px 6px; font-size: 12px; }
.cal .day.dim { opacity: .4; }
.cal .day .d { font-weight: 600; color: var(--muted); font-size: 11px; }
.cal-nav { display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 12px; }
.cal-people { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 12px; font-size: 12.5px; color: var(--ink); }
.cal-people span { display: inline-flex; align-items: center; }
.cal-people .dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 5px; }
.cal .day.clash { background: #3f4652; border-color: #2b313b; }
.cal .day.clash .d { color: #fff; }
.cal .clash-n { font-size: 9.5px; font-weight: 700; color: #fbbf24; float: right; }
.cal .ev { margin-top: 3px; border-radius: 5px; padding: 1px 5px; font-size: 11px; background: #e0f2fe; color: #0a5c80; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal .ev.sick { background: #fbe9e9; color: #b23131; }

/* timeline */
.tline { border-left: 2px solid var(--line); margin-left: 8px; padding-left: 18px; }
.tline .ev { position: relative; margin-bottom: 12px; }
.tline .ev::before { content: ''; position: absolute; left: -24px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--cyan); }
.tline .t { font-size: 12px; color: var(--muted); }

/* ---------- print (jobcard PDF on letterhead) ---------- */
#print-root { display: none; }
@media print {
  @page { size: A4; margin: 6mm 10mm; }
  body.printing #app-shell, body.printing #login-screen, body.printing #invite-screen,
  body.printing #toast-root, body.printing #modal-root { display: none !important; }
  body.printing #print-root { display: block; }
  body { background: #fff; }
  /* KNOWN, NOT FIXED: thead/tfoot repeat the letterhead correctly on every page, but on the LAST
     page the footer strip sits directly under the content instead of at the foot of the sheet.
     `height:100%` on the table does not help — Chrome will not stretch the final page fragment —
     and `position:fixed` does not repeat per page in this engine (see the report pipeline, which
     hit the same wall). The real fix is the one the report builders use: lay the document out as
     real 210x297mm sheets and paginate in-page. Measured 2026-08-04. */
}
.print-table { width: 100%; border-collapse: collapse; }
.print-table thead td { padding: 0 0 4mm; }
.print-table tfoot td { padding: 4mm 0 0; }
.lh-img { width: 100%; display: block; }
.pjc { font: 11.5pt/1.45 -apple-system, "Segoe UI", Roboto, Arial, sans-serif; color: #16233b; }
.pjc h1 { font-size: 17pt; color: #142F5C; margin: 0 0 2mm; }
.pjc .pjc-sub { color: #556; font-size: 10pt; margin-bottom: 5mm; }
.pjc-grid { display: grid; grid-template-columns: 42mm 1fr; gap: 1.2mm 5mm; font-size: 10.5pt; margin-bottom: 4mm; }
.pjc-grid dt { color: #667; }
.pjc-grid dd { margin: 0; font-weight: 500; }
.pjc h2 { font-size: 11.5pt; color: #142F5C; border-bottom: 1.5px solid #C9CED6; padding-bottom: 1mm; margin: 5mm 0 2mm; }
.pjc-notes { white-space: pre-wrap; font-size: 10.5pt; }
.pjc-sigrow { display: flex; gap: 14mm; flex-wrap: wrap; }
.pjc-sig { margin-top: 3mm; }
.pjc-sig img { max-height: 24mm; max-width: 62mm; display: block; }
.pjc-sigline { height: 18mm; width: 62mm; }
.pjc-sig .who { font-size: 10pt; color: #445; border-top: 1px solid #99a; padding-top: 1mm; min-width: 62mm; margin-top: 1mm; }
/* Photos paginate as table ROWS. See the comment in jcPrint(): a CSS grid row that meets the page
   boundary is sliced through the middle of the pictures, because break-inside on a grid item is
   widely ignored. A table row is honoured by every print engine, so a row either fits on the page
   or moves to the next one whole. */
.pjc-photos { width: 100%; table-layout: fixed; border-collapse: collapse; margin: 1mm -1.5mm 0; }
.pjc-photos tr { page-break-inside: avoid; break-inside: avoid; }
.pjc-photos td { width: 33.333%; padding: 0 1.5mm 3mm; vertical-align: top; }
/* `contain`, not `cover`. These are the evidence that the work was done — cropping to fill a tidy
   box quietly removes part of what the technician photographed. The panel keeps the shape. */
/* 46mm, not 50. At 50 a row came to 53mm and the fourth row missed the bottom of the page by about
   three millimetres — so nine photos printed and the last two went to a page of their own under a
   half-empty sheet. 46mm fits four rows with room to spare and costs almost nothing in size. */
.pjc-photos img { width: 100%; height: 46mm; object-fit: contain; background: #F4F7FA;
  border: 0.3mm solid #C9CED6; border-radius: 2mm; display: block; }
/* a heading must not be the last thing on a page — house rule, and it reads as a missing section */
/* in a batch, every jobcard after the first starts on a fresh sheet */
/* the tick column: as narrow as the box, and a tap target that works on a phone */
#jc-list td:first-child, #jc-list th:first-child { width: 34px; padding-right: 0; }
#jc-list .jc-pick { width: 18px; height: 18px; cursor: pointer; }
#jc-batch { margin: -2px 0 8px; }

.pjc-next { page-break-before: always; break-before: page; }
.pjc-keep { page-break-after: avoid; break-after: avoid; }
.pjc h2 { page-break-after: avoid; break-after: avoid; }
.pjc-sigrow, .pjc-sig { page-break-inside: avoid; break-inside: avoid; }
.pjc-badge { display: inline-block; border: 0.4mm solid #1CA3D6; color: #0d6d95; border-radius: 3mm; padding: 0.5mm 3mm; font-size: 10pt; font-weight: 600; vertical-align: middle; margin-left: 3mm; }

/* ---------- address autocomplete + trip stops ---------- */
.stop-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.stop-badge { flex: 0 0 42px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; }
.stop-wrap { position: relative; flex: 1; }
.stop-input { width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; font: inherit; }
.stop-x { flex: 0 0 auto; border: 0; background: #fbe9e9; color: #b23131; border-radius: 6px; width: 28px; height: 28px; cursor: pointer; font-size: 16px; }
.ac-box { position: absolute; top: 100%; left: 0; right: 0; z-index: 200; background: #fff; border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow); max-height: 300px; overflow-y: auto; margin-top: 2px; }
/* inside a modal the results flow in-line so the box grows to fit them — nothing is clipped and there's no tiny inner scrollbar to fight */
.modal .ac-box { position: static; max-height: 52vh; box-shadow: none; margin-top: 6px; }
.ac-item { padding: 8px 11px; font-size: 13px; line-height: 1.35; cursor: pointer; border-bottom: 1px solid #f1f5f9; display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.ac-item:hover { background: #e2f3fb; }
.ac-item > span[style*="float"] { float: none !important; margin-left: auto; white-space: nowrap; flex: 0 0 auto; }

.pick-photos { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.copy-btn { border: 1px solid var(--line); background: #f6f9fc; border-radius: 5px; padding: 1px 6px; font-size: 12px; cursor: pointer; color: var(--cyan-dark); vertical-align: 1px; }
.copy-btn:hover { background: #e2f3fb; }

/* SANS 241 verdict */
.sans-box { border-radius: 10px; padding: 10px 14px; margin: 0 0 14px; font-size: 13.5px; }
.sans-box ul { margin: 6px 0 0 18px; padding: 0; }
.sans-box.sans-ok { background: #ecfdf3; border: 1px solid #a7e3c0; color: #14713f; }
.sans-box.sans-bad { background: #fdf2f2; border: 1px solid #f0b8b8; color: #a12c2c; }
.sans-fail-row { background: #fdf2f2; }
.sans-fail-row td { font-weight: 600; }

/* ---------- design & costing ---------- */
.dtabs { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 12px; }
.dtab { border: 1px solid var(--line); background: #fff; border-radius: 8px 8px 0 0; padding: 8px 14px; font-size: 13.5px; font-weight: 500; cursor: pointer; color: var(--ink); }
.dtab:hover { background: #f1f6fb; }
.dtab.active { background: var(--navy1); color: #fff; border-color: var(--navy1); }
.dtab-n { background: rgba(28,163,214,.25); border-radius: 8px; padding: 0 6px; font-size: 11px; }
.dtab.active .dtab-n { background: rgba(255,255,255,.25); }
#wb-canvas { width: 100%; border: 1px solid var(--line); border-radius: 10px; background: #fff; touch-action: none; display: block; }
.wb-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.wb-mode.active { background: var(--cyan); color: #fff; border-color: var(--cyan); }
.wb-colors { display: inline-flex; gap: 5px; }
.wb-color { width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
.wb-color.active { border-color: var(--ink); box-shadow: 0 0 0 2px #fff inset; }
.pin-badge { background: #fdf2f2; color: #d64545; border-radius: 6px; padding: 1px 6px; font-size: 11.5px; font-weight: 700; }

/* ---------- design packages ---------- */
.pkg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; }
.pkg-card { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: #fff; cursor: pointer; transition: box-shadow .15s, transform .05s; }
.pkg-card:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
.pkg-thumb { height: 130px; display: flex; align-items: center; justify-content: center; background: #f1f6fb; overflow: hidden; }
.pkg-thumb img { width: 100%; height: 100%; object-fit: cover; }
.pkg-thumb span { font-size: 42px; opacity: .6; }
.pkg-thumb.lg { width: 150px; height: 150px; border-radius: 12px; border: 1px solid var(--line); flex: 0 0 auto; }
.pkg-meta { padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
.pkg-detail { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; }
.pkg-pic { display: flex; flex-direction: column; gap: 8px; align-items: center; }
.pkg-fields { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 10px; }

/* second row of design tabs (element sections) sits under the main views */
.dtabs-sub { margin-top: -8px; margin-bottom: 14px; padding-left: 6px; }
.dtabs-sub .dtab { font-size: 12.5px; padding: 6px 11px; background: #f8fafc; }
.dtabs-sub .dtab.active { background: var(--cyan); border-color: var(--cyan); }

/* whiteboard stage: canvas + quick-drop tray */
.wb-stage { display: flex; gap: 12px; align-items: flex-start; }
.wb-stage #wb-canvas { flex: 1; min-width: 0; }
.wb-snap { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
/* fitting symbol palette */
.wb-side { flex: 0 0 210px; display: flex; flex-direction: column; gap: 10px; }
.wb-side .wb-tray { flex: 0 0 auto; width: 100%; max-height: 340px; }
.sym-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.sym-tab { border: 1.5px solid var(--line); background: #fff; border-radius: 7px; padding: 3px 7px; font-size: 11.5px; font-weight: 600; cursor: pointer; }
.sym-list { display: flex; flex-direction: column; gap: 4px; }
.sym-item { display: flex; align-items: center; justify-content: space-between; gap: 6px; text-align: left; background: #fff; border: 1px solid var(--line); border-radius: 7px; padding: 6px 8px; font-size: 12px; cursor: pointer; line-height: 1.25; }
.sym-item:hover { border-color: var(--cyan); background: #f1f6fb; }
.sym-item.sel { border-color: var(--cyan); background: #e2f3fb; box-shadow: 0 0 0 2px rgba(28,163,214,.25); }
.sym-back { background: none; border: 0; color: var(--cyan-dark); font-size: 12px; cursor: pointer; padding: 2px 0 6px; }
.wb-tray { flex: 0 0 190px; border: 1px solid var(--line); border-radius: 10px; background: #f8fafc; padding: 10px; max-height: 640px; overflow-y: auto; }
.wb-tray-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-weight: 600; font-size: 13px; margin-bottom: 8px; }
.wb-tray-items { display: flex; flex-direction: column; gap: 6px; }
.tray-item { display: flex; align-items: center; gap: 7px; background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 6px 7px; cursor: grab; user-select: none; }
.tray-item:hover { border-color: var(--cyan); }
.tray-item.sel { border-color: var(--cyan); box-shadow: 0 0 0 2px rgba(28,163,214,.25); }
.tray-item:active { cursor: grabbing; }
.tray-ic { font-size: 16px; flex: 0 0 auto; }
.tray-lbl { flex: 1; min-width: 0; font-size: 12.5px; line-height: 1.25; overflow: hidden; }
.tray-lbl small { display: block; color: var(--muted); font-size: 10.5px; }
.tray-x { flex: 0 0 auto; border: 0; background: transparent; color: var(--muted); font-size: 16px; line-height: 1; cursor: pointer; padding: 0 2px; }
.tray-x:hover { color: var(--bad); }
.wb-tray-hint, .wb-tray-empty { font-size: 11px; color: var(--muted); margin-top: 8px; }
.wb-addtabs { display: flex; gap: 6px; }
.cat-aqf { transition: box-shadow .2s, border-color .2s; }
.cat-aqf.cat-saved { border-color: var(--ok); box-shadow: 0 0 0 2px rgba(26,158,99,.25); }
@media (max-width: 780px) { .wb-stage { flex-direction: column; } .wb-tray { flex: 1 1 auto; width: 100%; max-height: 220px; } }

/* ---------- employee onboarding ---------- */
.onb-progress { height: 10px; background: #eef2f7; border-radius: 6px; overflow: hidden; margin-top: 10px; }
.onb-progress-bar { height: 100%; background: var(--ok); transition: width .2s; }
.onb-step { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid #eef2f7; }
.onb-step:last-child { border-bottom: 0; }
.onb-ico { flex: 0 0 24px; font-size: 17px; text-align: center; }
.onb-body { flex: 1; min-width: 0; }
.onb-label { font-weight: 500; font-size: 14.5px; }
.onb-tag { font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--cyan-dark); background: #e2f3fb; border-radius: 4px; padding: 1px 5px; margin-left: 6px; vertical-align: 1px; }
.onb-act { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 8px; }
.onb-meta { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.onb-active { background: #f2fbff; border-radius: 8px; padding: 12px 10px; }
.onb-active .onb-label { color: var(--navy1); }
.onb-locked { opacity: .5; }
.onb-done .onb-label, .onb-na .onb-label { color: var(--muted); }
.btn-xs { padding: 2px 8px; font-size: 12px; }

/* ---------- chips (multi-select) ---------- */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.chip { display: inline-flex; align-items: center; gap: 6px; background: #e2f3fb; color: #0d6d95; border-radius: 999px; padding: 3px 6px 3px 11px; font-size: 13px; font-weight: 500; }
.chip-x { border: 0; background: rgba(13,109,149,.15); color: #0d6d95; border-radius: 50%; width: 18px; height: 18px; cursor: pointer; font-size: 13px; line-height: 1; }
.audit-tick { color: var(--ok); font-weight: 700; }
.audit-flag { color: var(--warn); font-weight: 700; }

/* ---------- claim approval cards ---------- */
.claim-card { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; margin-bottom: 12px; }
.claim-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.claim-amt { font-size: 17px; font-weight: 700; color: var(--navy1); white-space: nowrap; }
.claim-meta { display: flex; flex-wrap: wrap; gap: 12px; font-size: 13px; color: var(--muted); margin: 6px 0; }
.claim-route { font-size: 13px; margin: 4px 0; }
.claim-notes { font-size: 13.5px; margin: 6px 0; white-space: pre-wrap; }
.claim-files { display: flex; flex-wrap: wrap; gap: 10px; margin: 8px 0; font-size: 13px; }
.claim-thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
.claim-thumb { display: block; width: 96px; height: 96px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: #f8fafc; }
.claim-thumb img { width: 100%; height: 100%; object-fit: cover; }
img.claim-thumb { object-fit: cover; }
.claim-thumb.pdf { display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 22px; color: var(--muted); text-decoration: none; }
.claim-thumb.pdf span { font-size: 10px; padding: 0 4px; text-align: center; overflow: hidden; max-height: 24px; }
.claim-actions { display: flex; gap: 8px; margin-top: 8px; }
/* claim detail: info left, document pictures right */
.claim-detail { display: flex; gap: 18px; flex-wrap: wrap; }
.claim-detail-info { flex: 1 1 300px; min-width: 0; }
.claim-detail-docs { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.doc-thumb { width: 100%; max-height: 260px; object-fit: contain; border: 1px solid var(--line); border-radius: 8px; background: #f8fafc; }
.doc-pdf { display: inline-block; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: #f8fafc; }
/* claims analysis */
.an-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 12px; }
.an-kpi { background: #f6f9fc; border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
.an-kpi .l { font-size: 12px; color: var(--muted); }
.an-kpi .n { font-size: 20px; font-weight: 700; color: var(--navy1); margin-top: 2px; }
.an-chart { margin-top: 8px; }
.an-bar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.an-bar-lbl { flex: 0 0 92px; font-size: 12.5px; color: var(--muted); }
.an-bar { flex: 1; display: flex; height: 20px; background: #eef2f7; border-radius: 5px; overflow: hidden; }
.an-seg { height: 100%; }
.an-seg.cash { background: #1CA3D6; }
.an-seg.fuel { background: #d97706; }
.an-bar-val { flex: 0 0 100px; text-align: right; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.an-legend { display: flex; gap: 14px; font-size: 12px; color: var(--muted); margin-top: 8px; }
.an-legend .k { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 4px; vertical-align: -1px; }
.an-legend .k.cash { background: #1CA3D6; }
.an-legend .k.fuel { background: #d97706; }
.an-seg { display: flex; align-items: center; overflow: hidden; }
.an-seg-lbl { color: #fff; font-size: 10.5px; font-weight: 600; padding: 0 5px; white-space: nowrap; }
.line-chart { width: 100%; max-width: 760px; height: auto; display: block; }
.an-decomp { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr)); gap: 12px; margin: 8px 0 4px; }
.an-dc { background: #f6f9fc; border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
.an-dc .l { font-size: 12px; color: var(--muted); }
.an-dc .n { font-size: 18px; font-weight: 700; color: var(--navy1); margin: 2px 0; }
.an-dc .s { font-size: 11.5px; color: var(--muted); }
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--cyan-dark); }
.sort-ar { font-size: 10px; margin-left: 3px; opacity: .4; }
th.sortable:hover .sort-ar { opacity: .8; }
.claim-totals { margin-top: 14px; border-top: 2px solid var(--line); padding-top: 12px; }
.claim-totals .ct-grand { display: flex; flex-wrap: wrap; gap: 18px; font-size: 14px; margin-bottom: 10px; }
.claim-totals .ct-grand strong { color: var(--navy1); }
/* The bare totals table stays narrow — four short money columns do not need the page.
   Note the child selector: it must not reach the claim list nested inside an expanded person row,
   which was being squeezed to 380px along with its parent. */
.claim-totals > table { max-width: 380px; }
/* …but once a row carries a reference and a Mark paid button it needs the room. Nothing in these
   cells may wrap: "Mark paid" broken over two lines reads as two buttons, and a payment reference
   split down the middle is a reference somebody will mistype into the bank. */
.claim-totals > table.ct-wide { max-width: none; width: 100%; }
.claim-totals th, .claim-totals td { white-space: nowrap; }
.claim-totals .btn { white-space: nowrap; }
.claim-totals code { white-space: nowrap; font-size: 13px; letter-spacing: .04em; font-weight: 600; color: var(--navy1); }
.claim-totals table table { max-width: none; width: 100%; }
/* the person column is the only one that should give way when the screen is narrow */
.claim-totals > table td:first-child, .claim-totals > table th:first-child { white-space: normal; width: 99%; }
.claim-totals .ct-caret { display: inline-block; width: 12px; opacity: .55; font-size: 11px; }
/* Account picker inside an expanded person row. Coding saves on change, so the control itself has
   to report back — a green edge for saved, red for refused and reverted. */
.claim-totals select.ct-acct { min-width: 190px; font-size: 12.5px; padding: 3px 6px; }
.ct-saved { outline: 2px solid var(--ok, #1a7f37); outline-offset: 1px; transition: outline-color .4s; }
.ct-bad { outline: 2px solid var(--bad, #b3261e); outline-offset: 1px; }
/* The approver's VAT line on a pending cash claim */
.claim-vat { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0 2px; font-size: 13px; }
.claim-vat label { font-weight: 600; color: var(--navy1); }
.claim-vat select { font-size: 12.5px; padding: 3px 6px; }

/* ---------- leave mini calendar ---------- */
.hl-bal { background: #eef6fb; border: 1px solid #cfe6f5; border-radius: 8px; padding: 9px 12px; font-size: 13.5px; margin: 4px 0 10px; }
/* leave date range picker */
.rp { border: 1px solid var(--line); border-radius: 10px; padding: 10px; margin: 4px 0 10px; max-width: 360px; }
.rp-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.rp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.rp-dow { text-align: center; font-size: 10.5px; text-transform: uppercase; color: var(--muted); padding-bottom: 2px; }
.rp-cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; font-size: 13px; border-radius: 7px; cursor: pointer; user-select: none; }
.rp-cell:not(.rp-empty):hover { background: #e2f3fb; }
.rp-empty { cursor: default; }
.rp-we { color: #a2adba; }
.rp-hol { color: var(--warn); font-weight: 600; }
.rp-in { background: #d3ecfa; border-radius: 0; }
.rp-start, .rp-end { background: var(--cyan); color: #fff; font-weight: 700; }
.rp-start { border-radius: 7px 0 0 7px; }
.rp-end { border-radius: 0 7px 7px 0; }
.rp-start.rp-end { border-radius: 7px; }
.rp-readout { margin-top: 8px; font-size: 13px; font-weight: 600; color: var(--navy1); text-align: center; }
.hl-cal { margin: 6px 0 4px; }
.hl-cal-key { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.hl-cal-key .k { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 4px; vertical-align: -1px; }
.hl-cal-key .k.work { background: #1CA3D6; }
.hl-cal-key .k.weekend { background: #e2e8f0; }
.hl-cal-key .k.holiday { background: #d97706; }
.hl-cal-m { margin-bottom: 8px; }
.hl-cal-mh { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 4px; }
.hl-cal-days { display: flex; flex-wrap: wrap; gap: 4px; }
.hl-d { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 6px; font-size: 12.5px; font-weight: 600; }
.hl-d.work { background: #1CA3D6; color: #fff; }
.hl-d.weekend { background: #eef2f7; color: #94a3b8; font-weight: 400; }
.hl-d.holiday { background: #d97706; color: #fff; }
.hl-cal-sum { font-size: 13px; margin-top: 6px; }
.hl-hol-note { color: var(--warn); }

/* ---------- sites map ---------- */
.map-split { display: flex; gap: 16px; align-items: stretch; }
.map-split > .card { margin-bottom: 16px; }
.map-detail { flex: 0 0 320px; max-width: 340px; overflow-y: auto; max-height: calc(100vh - 190px); }
.map-detail-h { font-size: 13px; color: var(--navy1); margin: 14px 0 6px; }
.map-detail .dl { grid-template-columns: 120px 1fr; font-size: 13.5px; }
@media (max-width: 860px) {
  .map-split { flex-direction: column; }
  .map-detail { flex: 1 1 auto; max-width: none; max-height: none; }
  .map-detail .dl { grid-template-columns: 110px 1fr; }
}
.map-wrap { height: calc(100vh - 210px); min-height: 420px; border-radius: var(--radius); overflow: hidden; }
.map-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin-top: 10px; }
.map-legend .dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 5px; vertical-align: -1px; }

/* ---------- jobcards per person per month (small multiples) ----------
   One panel per person on a shared scale. The panels are the data; everything else — captions,
   the peak value, the month ends — is ink, never the series colour. */
.ja-panels { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px 16px; }
.ja-panel { margin: 0; }
.ja-panel figcaption {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 13px; color: var(--ink); margin-bottom: 2px;
}
.ja-panel figcaption span { font-size: 12px; color: var(--muted); white-space: nowrap; }
.ja-panel svg { width: 100%; height: 80px; display: block; overflow: visible; }
.ja-panel svg rect[fill="transparent"] { cursor: default; }
/* Key person vs support on the assign picker. The key chip is the only one that carries colour —
   there is exactly one of them, and it is the answer to "whose job is this". */
.chip.chip-key { background: #e2f3fb; border-color: #9fd6ee; color: #0d6d95; font-weight: 600; }
.chip .chip-make {
  border: 0; background: none; color: var(--cyan-dark); font-size: 11.5px; cursor: pointer;
  padding: 0 2px; text-decoration: underline;
}
.chip .chip-make:hover { color: var(--navy1); }

.ja-pick { margin-top: 14px; }
.ja-pick summary { cursor: pointer; font-size: 13px; color: var(--muted); padding: 4px 0; }
.ja-pick summary:hover { color: var(--ink); }
.ja-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 4px 14px; margin-top: 8px; }
.ja-pick-grid label { display: flex; align-items: center; gap: 7px; font-size: 13.5px; cursor: pointer; padding: 3px 0; }
.ja-pick-grid input { flex: 0 0 auto; }
.ja-table { margin-top: 16px; }
.ja-table summary { cursor: pointer; font-size: 13px; color: var(--muted); padding: 4px 0; }
.ja-table summary:hover { color: var(--ink); }
.ja-table table.tbl { font-size: 12.5px; }
.ja-table .tbl td, .ja-table .tbl th { padding: 5px 7px; }
@media (max-width: 640px) { .ja-panels { grid-template-columns: 1fr; } }

/* ---------- stacked tables on a phone ----------
   A six-column table on a 390px screen has two bad options: squeeze every column until nothing is
   readable, or scroll sideways so half the row is always off-screen — and a row you cannot see the
   end of is a row you cannot act on, which matters when the last column holds Approve and Decline.
   So below 700px each ROW becomes a card and each CELL a labelled line, using the data-label the
   table renderers put on every cell.
   Opt-in by class: only the screens that have been looked at on a phone get it, rather than every
   table in the app changing shape at once. */
.stack-sort { display: none; }
@media (max-width: 700px) {
  .stack-sm .tbl-wrap { overflow-x: visible; }
  .stack-sm table.tbl, .stack-sm table.tbl tbody, .stack-sm table.tbl tr, .stack-sm table.tbl td { display: block; width: auto; }
  .stack-sm table.tbl thead { display: none; }
  .stack-sm table.tbl tr {
    border: 1px solid var(--line); border-radius: 10px; background: #fff;
    padding: 8px 12px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(16,32,64,.05);
  }
  .stack-sm table.tbl td {
    border: 0; padding: 5px 0; display: flex; gap: 10px; align-items: flex-start;
    text-align: left; min-height: 0;
  }
  .stack-sm table.tbl td + td { border-top: 1px solid #f2f5f9; }
  .stack-sm table.tbl td::before {
    content: attr(data-label); flex: 0 0 38%; max-width: 38%;
    color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: .4px;
    padding-top: 3px; word-break: break-word;
  }
  /* action and tick-box columns have no heading — no label, and the buttons get the full width */
  .stack-sm table.tbl td[data-label=""]::before { display: none; }
  .stack-sm table.tbl td[data-label=""] { justify-content: flex-start; flex-wrap: wrap; gap: 8px; padding-top: 8px; }
  .stack-sm table.tbl td.num { text-align: left; }
  /* the "nothing here" row is a single cell spanning the table — it must not sprout a label */
  .stack-sm table.tbl td[colspan]::before { display: none; }
  .stack-sm .stack-sort {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
    font-size: 13px; color: var(--muted);
  }
  .stack-sm .stack-sort select { max-width: 55vw; }
}

/* ---------- mobile ---------- */
@media (max-width: 860px) {
  .nav-toggle { display: block; }
  .sidebar { position: fixed; left: 0; top: 54px; bottom: 0; z-index: 60; transform: translateX(-105%); transition: transform .18s ease; box-shadow: 4px 0 18px rgba(0,0,0,.15); height: auto; }
  .sidebar.open { transform: none; }
  .main { padding: 14px 12px 60px; }
  .dl { grid-template-columns: 1fr; gap: 2px; }
  .dl dt { margin-top: 8px; }
  .header-user { display: none; }
  /* The header ran 43px wider than a 375px screen even before the All-apps link was added,
     so it scrolled sideways under the thumb. The logo already says which app this is —
     the word next to it is what has to go. */
  .header-sep, .header-title { display: none; }
  /* and close the gaps: four items with 14px between them and 18px of padding still ran
     17px over a 375px screen. */
  .app-header { gap: 8px; padding: 10px 12px; }
}

/* A read-only field is machine-set (e.g. the Google Maps route distance on a km claim) — it must
   look unavailable, or people try to type in it and think the app is broken. The first selector
   is there to out-specify `label.f > input`, which sets a white background on every form field. */
label.f > input[readonly], input[readonly] { background: #eef2f7; color: #41506b; cursor: not-allowed; }

/* duplicate warning when adding a catalogue part — the catalogue has been grown by import several
   times, so a near-duplicate is the likeliest way to corrupt a price list */
.dup-warn { border: 1px solid #f0c36d; background: #fff8e6; border-radius: 8px; padding: 9px 11px; margin: 4px 0 10px; font-size: 13px; color: #7a5d00; }
.dup-row { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; border-top: 1px dashed #eadfba; }
.ac-newpart { border-top: 1px dashed var(--line); color: var(--navy1); }
.ac-vary { float: right; margin-left: 8px; padding: 1px 7px; font-size: 11px; }

/* trip map — numbered stops and small via points, matching the drag-to-reroute behaviour */
.tm-pin { width: 24px; height: 24px; border-radius: 50%; background: var(--cyan); color: #fff; border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.35); font: 700 12px/20px Arial; text-align: center; cursor: grab; }
.tm-pin.via { width: 16px; height: 16px; background: #fff; border: 3px solid var(--cyan); }
/* The status dot on the sites map. Google's own pin is a red teardrop that says nothing; ours
   carries the site's stage, in the same colours as the legend under the map. */
.gm-dot { width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.4); cursor: pointer; }
#tm-map { cursor: grab; }
/* The pin picker overlays a fixed marker at the centre of the box, so the box is its frame.
   Leaflet made its own container relative; Google does not, and without this the pin escapes to
   the nearest positioned ancestor and floats somewhere else entirely on the form. */
#se-pickmap { position: relative; }
/* The two half-day ticks, sitting under the leave calendar. Side by side on a desktop, stacked on a
   phone, each a comfortable tap target rather than a bare checkbox. */
/* The channel picker on a logger. A bounded, scrolling box for the same reason the site pickers
   have one — a busy site has dozens of channels and an unbounded list pushes the form off screen. */
.lg-ch-list { max-height: 220px; overflow-y: auto; border: 1px solid var(--line); border-radius: 8px;
  margin-top: 6px; background: #fff; }
.lg-ch { display: flex; align-items: center; gap: 10px; padding: 7px 10px; font-size: 13px;
  border-bottom: 1px solid #f1f5f9; }
.lg-ch:last-child { border-bottom: 0; }
.lg-ch > span:first-child { flex: 1 1 auto; min-width: 0; }
.lg-ch.on { background: #eef7fc; }

/* A leave block that can be corrected says so by behaving like a control: a pointer, and it lifts
   very slightly under the cursor. Deliberately subtle — the calendar is read far more often than it
   is edited, and turning every block into a shouting button would spoil the month at a glance. */
.ev-fix { cursor: pointer; }
.ev-fix:hover { filter: brightness(1.12); outline: 2px solid rgba(255,255,255,.85); outline-offset: -2px; }
.ev-fix:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }

/* A panel of its own, not two checkboxes lost in a paragraph. Half days are asked for often enough
   that the option has to be findable without reading the hint text — it was there before this and
   people still typed the total by hand. */
.hl-halves {
  display: grid; gap: 8px; margin: 12px 0 4px; padding: 12px 14px;
  border: 1px solid #cfe6f5; border-radius: 10px; background: #f2f8fc;
}
.hl-halves-title { font-weight: 700; font-size: 13.5px; color: var(--navy1); }
.hl-halves label { display: flex; align-items: center; gap: 10px; font-size: 14px;
  font-weight: 600; color: #33415c; cursor: pointer; min-height: 34px; }
.hl-halves input { width: auto; flex: 0 0 auto; transform: scale(1.15); }
.hl-halves .hint { margin: 0; }

/* A jobcard waiting on a deletion decision. Tinted rather than badged alone, so it is visible while
   scanning a long list rather than only when the Status column is read. */
tr.row-del-pending > td { background: #fdf3f2; }
tr.row-del-pending:hover > td { background: #fbe9e9; }

/* Raising the same job at several sites. Set apart from the fields above it so it reads as a
   deliberate extra step rather than another thing to fill in — most jobcards are for one site. */
.extra-sites { border-top: 1px dashed var(--line); margin-top: 4px; padding-top: 12px; }

/* Uploading, and saving: a bar that says what is happening and how far along it is.
   Sits above the modal layer, because the thing being uploaded is usually inside one, and clear of
   the phone's bottom furniture like everything else down there. */
.up-bar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  z-index: 400; width: min(420px, calc(100vw - 32px));
  background: var(--navy1); color: #fff; padding: 12px 16px 13px;
  border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,.32); font-size: 13.5px;
}
.up-label { font-weight: 600; margin-bottom: 8px; }
.up-track { height: 6px; border-radius: 3px; background: rgba(255,255,255,.22); overflow: hidden; }
.up-fill { height: 100%; background: var(--cyan); border-radius: 3px; transition: width .22s ease; }
/* The automatic save bar has nothing to measure — a fetch reports no progress — so it shows motion
   rather than a percentage. A bar creeping to 90% and stopping is a lie about how far along it is. */
.busy-run { width: 35%; animation: busyslide 1.1s ease-in-out infinite; }
@keyframes busyslide { 0% { margin-left: -35%; } 100% { margin-left: 100%; } }
.up-note { color: rgba(255,255,255,.72); font-size: 12px; margin-top: 6px; min-height: 15px; }

/* What is wrong at a site, at the top of its page. Deliberately loud — it only appears when there
   IS something, so it can afford to be, and a block that shows up only sometimes does not become
   scenery the way a permanent banner does. */
.risk-card { border-left: 4px solid var(--bad); background: #fdf3f2; }
.risk-list { list-style: none; margin: 6px 0 0; padding: 0; }
.risk-list li { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid rgba(0,0,0,.06); font-size: 13.5px; }
.risk-list li:first-child { border-top: 0; }
.risk-what { font-weight: 700; color: var(--bad); flex: 0 0 auto; }
.risk-warn .risk-what { color: #a06000; }
.risk-detail { flex: 1 1 240px; min-width: 0; color: #33415c; }
@media (max-width: 760px) {
  .risk-list li { align-items: flex-start; }
  .risk-detail { flex-basis: 100%; }
}

/* How the claimed distance was arrived at, said in words on the form itself. Quiet when the map's
   figure was taken as it stands; marked when a person changed it, because that is the version an
   approver has to think about. */
.km-basis { margin: 8px 0 4px; padding: 9px 11px; border-radius: 8px; font-size: 13px; line-height: 1.45;
  background: #f4f7fa; border: 1px solid var(--line); color: #33415c; }
.km-basis.changed { background: #fffaf0; border-color: #f0d9a8; }

.stop-site { max-width: 190px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font-size: 16px; background: #fff; }
/* The place list for a trip stop. A fixed height with its own scrollbar, because the list runs to
   several hundred entries — the native dropdown it replaced could be given neither.
   It has its own class rather than .ac-box: inside a modal .ac-box is deliberately static so long
   result lists flow in-line, which for this one would shove the whole form down the page. */
.stop-pick { position: relative; display: inline-block; }
.sp-box {
  position: absolute; top: 100%; left: 0; z-index: 300; width: 280px; max-width: 80vw;
  max-height: 260px; overflow-y: auto; background: #fff; border: 1px solid var(--line);
  border-radius: 8px; box-shadow: var(--shadow); margin-top: 2px;
}
.sp-group {
  position: sticky; top: 0; background: #f6f9fc; border-bottom: 1px solid var(--line);
  padding: 5px 10px; font-size: 11.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}

/* Searchable picker. A native <select> holding hundreds of options is unusable on a phone — iOS
   shows a wheel with no search — so these render as a text input that opens the keyboard.
   font-size MUST stay at 16px: iOS zooms the whole page when you focus anything smaller. */
.combo { position: relative; display: block; }
label.f > .combo > input.combo-q {
  display: block; width: 100%; margin-top: 5px; padding: 10px 34px 10px 11px; font-size: 16px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink); font-family: inherit;
}
label.f > .combo > input.combo-q:focus { outline: 2px solid var(--cyan); border-color: var(--cyan); }
.combo-x {
  position: absolute; right: 4px; top: 22px; border: 0; background: none;
  font-size: 22px; line-height: 1; color: var(--muted); cursor: pointer; padding: 4px 8px;
}
.combo-hits { top: 100%; }
.combo-hits .ac-item { padding: 12px; }        /* a comfortable tap target on a phone */

/* Inside a modal, .ac-box above is deliberately static so a long result list flows in-line and
   nothing gets clipped. For the SITE picker on a claim that reads badly: several hundred sites push
   the rest of the form far down the page and the list has no edge of its own. So this one keeps its
   own box — fixed height, its own scrollbar, floating over the form rather than growing it.
   Placed after the .modal .ac-box rule on purpose: same specificity, so order decides. */
.modal .combo-hits {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 300;
  max-height: 260px; overflow-y: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  box-shadow: var(--shadow); margin-top: 2px;
}

/* the fitting picker shows the same mark that lands on the board, beside its name */
.sym-item.sym-fit { display: flex; align-items: center; gap: 8px; }
.sym-glyph { flex: 0 0 24px; }

/* ---------- typing on a phone ----------
   iOS Safari zooms the whole page in when you focus a field whose text is smaller than 16px, and
   it does not zoom back out afterwards — so one tap on a search box leaves the layout skewed and
   the person pinching to recover. Every control in this app was 14–15px, which is why tapping
   almost anything did it.

   16px is a threshold, not a taste: at or above it Safari leaves the page alone. Note that
   maximum-scale / user-scalable=no in the viewport tag is NOT an alternative — iOS deliberately
   ignores those so that pages stay zoomable for people who need it, and taking zoom away would be
   the wrong fix anyway. Making the text big enough to read is the right one. */
@media (max-width: 860px) {
  label.f > input, label.f > select, label.f > textarea,
  .login-card label > input, .login-card input,
  /* .toolbar input[type=search] is spelled out because the rule it has to beat is written that
     way too, and a bare `.toolbar input` loses on specificity */
  .toolbar input, .toolbar select, .toolbar input[type=search],
  .stop-input, .stop-site,
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=search], input[type=date], input[type=month], input[type=time],
  input[type=tel], input[type=url], select, textarea {
    font-size: 16px;
  }
  /* the same reasoning for the date pickers, which iOS renders with its own control */
  input[type=date], input[type=month] { min-height: 40px; }

  /* A date field would not fill its line the way every other field does — the jobcard deadline sat
     narrower than the boxes above and below it. `width: 100%` is already set on every field in a
     form; iOS and Android both ignore it on a date input, because the platform substitutes its own
     control and sizes it to its contents. Turning the native appearance off hands the sizing back
     to the CSS, and min-width stops the intrinsic content width winning anyway.
     The picker still opens as the platform's own — only the box on the form changes. */
  input[type=date], input[type=month], input[type=time], input[type=datetime-local] {
    -webkit-appearance: none; appearance: none;
    width: 100%; min-width: 100%; max-width: 100%;
  }
  /* iOS centres the text inside that control and leaves the calendar glyph floating; line it up
     with the text in the fields around it */
  label.f > input[type=date], label.f > input[type=month],
  label.f > input[type=time], label.f > input[type=datetime-local] {
    text-align: left;
  }

  /* Tap targets. A finger is about 9mm across and cannot aim the way a mouse pointer can, so
     controls that are comfortable with a cursor are a lottery with a thumb. Buttons here were
     25–34px tall and the menu items 36px; 44px is the size Apple and Google both settle on.
     Only the height is forced — the padding is left alone so nothing reflows. */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 36px; padding-left: 14px; padding-right: 14px; }
  .btn-xs { min-height: 32px; }
  .nav-item { min-height: 44px; display: flex; align-items: center; }
  /* row actions sit side by side; give them room to breathe so neighbours are not hit by mistake */
  td .btn-sm + .btn-sm, td .btn-sm + .btn-xs { margin-left: 6px; }
}

/* ---------- suggested jobcards on a claim ----------
   Offered, never applied: linking a claim to the wrong job puts the cost against the wrong client,
   and only the person who drove there knows which job it was for. Each card carries the reason it
   is being offered, so the choice can be judged rather than guessed. */
.jc-suggest { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.jc-suggest .jc-sug {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  border: 1px solid var(--line); background: #f7fafd; border-radius: 9px;
  padding: 7px 11px; cursor: pointer; font: inherit; font-size: 13px; text-align: left; min-width: 150px;
}
.jc-suggest .jc-sug:hover { border-color: var(--cyan); background: #eaf6fd; }
.jc-suggest .jc-sug strong { color: var(--navy1); }
.jc-suggest .jc-sug .hint { font-size: 11.5px; }

/* the derived rate, shown while a litre price is being typed */
.rate-calc { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline;
  background: #f2f8fc; border: 1px solid #cfe6f5; border-radius: 9px; padding: 9px 12px;
  margin: 4px 0 10px; font-size: 13.5px; }
.rate-calc strong { color: var(--navy1); }
.rate-calc:empty { display: none; }

/* The itemised approved list is still there, folded away — the person totals are what the run is
   worked from, but an approver occasionally wants the flat list to sort and scan. */
.ap-detail { margin-top: 16px; }
.ap-detail summary { cursor: pointer; font-size: 13px; color: var(--muted); padding: 4px 0; }
.ap-detail summary:hover { color: var(--ink); }

/* How a trip's kilometres are shared between departments, shown while the claim is being built */
.dept-split { display: flex; flex-wrap: wrap; gap: 5px 16px; align-items: baseline;
  background: #f2f8fc; border: 1px solid #cfe6f5; border-radius: 9px;
  padding: 8px 12px; margin: 2px 0 10px; font-size: 13px; }
.dept-split strong:first-child { color: var(--navy1); }
.dept-split:empty { display: none; }
/* the same split, on the submitted claim an approver is reading */
.claim-split { display: flex; flex-wrap: wrap; gap: 5px 16px; align-items: baseline;
  background: #f2f8fc; border: 1px solid #cfe6f5; border-radius: 9px;
  padding: 8px 12px; margin: 6px 0 2px; font-size: 13px; }
.claim-split strong:first-child { color: var(--navy1); }
.leg-dept { font-size: 11px; font-weight: 600; color: var(--navy1);
  background: #e8f2fa; border-radius: 6px; padding: 1px 6px; white-space: nowrap; }
.stop-row .stop-dept { flex: 0 1 200px; min-width: 0; font-size: 13px; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; }
.stop-row .stop-shared { flex: 0 1 200px; font-size: 12px; font-style: italic; }

/* A stop on a phone.
   Four controls — the address, the place picker, the department and the remove button — plus a
   label, all on one flex line. On a 360px screen that leaves each of them a few centimetres wide,
   the address unreadable and the two dropdowns squeezed against each other. So below the tablet
   width each stop becomes its own small card and the controls take the full width one under the
   other, in the order they are filled in: where, which place, whose budget. */
@media (max-width: 760px) {
  .stop-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "badge remove" "addr addr" "pick pick" "dept dept";
    gap: 7px 8px; align-items: center;
    padding: 10px; margin-bottom: 10px;
    border: 1px solid var(--line); border-radius: 10px; background: #fbfdff;
  }
  .stop-badge { grid-area: badge; flex: none; }
  .stop-wrap { grid-area: addr; }
  .stop-pick { grid-area: pick; display: block; }
  .stop-site { max-width: none; width: 100%; }
  .stop-row .stop-dept { grid-area: dept; flex: none; width: 100%; }
  .stop-row .stop-shared { grid-area: dept; flex: none; }
  .stop-x { grid-area: remove; justify-self: end; }
  /* the place list matches the box that opens it, rather than a fixed 280px sticking out of it */
  .sp-box { width: 100%; max-width: none; }

  /* ---- Cancel and Save, clear of the phone's own furniture ----
     Two separate things were hiding them. Safari draws a bar across the bottom of the screen and
     iPhones reserve a strip for the home indicator; Android does the same for gesture navigation.
     The browser does not subtract either from the page, so a footer pinned to bottom:0 sits behind
     them. env(safe-area-inset-bottom) is that strip's height — zero on a desktop, and zero on iOS
     as well until viewport-fit=cover is set in the viewport tag, which it now is.
     The modal is also given room top and bottom so it cannot run under the browser chrome. */
  .modal-back { padding: 12px 10px calc(12px + env(safe-area-inset-bottom, 0px)); }
  .modal { max-height: calc(100vh - 24px); padding: 18px 16px 0; }
  .modal-actions {
    margin: 16px -16px 0;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  }
  /* full-width buttons: a thumb reaching the bottom corner of a phone is not accurate, and Cancel
     sitting next to Save at 90px wide is how the wrong one gets pressed */
  .modal-actions .btn { flex: 1 1 0; justify-content: center; text-align: center; }
  .modal-actions .btn + .btn { margin-left: 0; }

  /* the same strip hides these, and a toast that cannot be read is no message at all */
  .toast { bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }
  .update-bar { bottom: calc(20px + env(safe-area-inset-bottom, 0px)); }
}
@supports (height: 100dvh) {
  @media (max-width: 760px) {
    .modal { max-height: calc(100dvh - 24px); }
  }
}

/* The FNB payment file, in the payment run */
.bankfile { background: #f2f8fc; border: 1px solid #cfe6f5; border-radius: 10px;
  padding: 10px 14px; margin: 8px 0 14px; display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; }
.bankfile .bf-head { flex: 1 1 100%; display: flex; gap: 10px; align-items: baseline; }
.bankfile .bf-head strong { color: var(--navy1); }
.bf-missing { flex: 1 1 100%; display: flex; flex-direction: column; gap: 3px;
  background: #fdf2f2; border-left: 3px solid var(--bad); border-radius: 6px; padding: 8px 12px; margin-top: 4px; font-size: 13px; }

/* Warning that a client or site being typed already exists. Shown while typing rather than after
   saving, because after saving it is already a second record somebody has to merge. */
.dupe-warn { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
  background: #fffaf0; border: 1px solid #f0d9a8; border-radius: 9px;
  padding: 9px 12px; margin: 0 0 12px; font-size: 13px; }
.dupe-warn strong:first-child { color: #8a5a00; width: 100%; }
.dupe-warn > span:not(.hint) { font-weight: 600; }
.dupe-warn .hint { width: 100%; }
.dupe-warn:empty { display: none; }

/* What a site's SLA covers — a tick, a name, a count and an interval per item.
   ONE row per line on a fixed five-column grid, not a multi-column grid of flex rows. The previous
   layout gave each row fixed-width boxes (74px + 148px + 138px) inside a column only ~290px wide,
   so the controls overflowed their cell, ran across the neighbouring column and off the card — the
   item names were squeezed to zero width and the boxes could not reliably be clicked at all.
   A shared template also means the quantities and intervals line up down the page, which is what
   makes the list readable as a schedule rather than ten separate rows. */
.sla-items { display: flex; flex-direction: column; gap: 2px; }
.sla-head, .sla-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) 84px 146px 148px 112px;
  gap: 10px; align-items: center;
}
.sla-head { font-size: 11px; text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted); padding-bottom: 4px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.sla-row { padding: 4px 0; font-size: 14px; }
.sla-row + .sla-row { border-top: 1px solid #f2f5f9; }
.sla-row input[type=checkbox] { width: auto; margin: 0; }
.sla-name { min-width: 0; cursor: pointer; font-weight: 500; }
.sla-row input[type=number], .sla-row input[type=date], .sla-row select {
  width: 100%; min-width: 0; padding: 6px 8px; font-size: 13.5px;
  border: 1px solid var(--line); border-radius: 7px; background: #fff; font-family: inherit;
}
.sla-row input:disabled, .sla-row select:disabled { background: #f4f6fa; color: var(--muted); }
.sla-due { font-size: 13px; color: var(--ink); white-space: nowrap; }
/* Who to call at the site, and when it was last serviced — the two things you want before driving there. */
.sla-facts { display: flex; flex-direction: column; gap: 3px; font-size: 13.5px;
  background: #f4f8fc; border: 1px solid #dce8f3; border-radius: 9px; padding: 9px 12px; margin: 0 0 12px; }
.sla-plan-due { margin-left: auto; }
/* On a phone the five columns cannot fit, so the controls stack under the name they belong to
   rather than being shaved to nothing. */
@media (max-width: 700px) {
  .sla-head { display: none; }
  .sla-row { grid-template-columns: auto minmax(0, 1fr); gap: 6px 10px; padding: 9px 0; }
  .sla-row > :nth-child(n+3) { grid-column: 2; }
  .sla-row > span:empty { display: none; }
}

/* Photos being gathered for a jobcard, before it is created. Shown as thumbnails with a remove
   button: a count alone ("3 photos ready") cannot tell you which three, and the blurred one is
   always noticed straight away. Attached photos are NOT shown here — those belong to the record. */
.pick-strip { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.pick-strip:empty { display: none; }
.pick-thumb { position: relative; display: inline-block; }
.pick-thumb img { width: 74px; height: 74px; object-fit: cover; border-radius: 9px;
  border: 1px solid var(--line); display: block; }
.pick-thumb button {
  position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; line-height: 1;
  border: 0; border-radius: 50%; background: var(--bad); color: #fff; font-size: 15px;
  font-weight: 700; cursor: pointer; padding: 0; box-shadow: 0 1px 4px rgba(0,0,0,.3);
}

/* The SLA read the other way round: what happens monthly, what happens annually. */
.sla-plan { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px; font-size: 13.5px; }
.sla-plan > strong { color: var(--navy1); }
.sla-plan-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.sla-plan-row .badge { flex: 0 0 auto; }
.sla-row .sla-freq { flex: 0 0 148px; padding: 5px 7px; font-size: 13px;
  border: 1px solid var(--line); border-radius: 7px; background: #fff; }

/* An action bar INSIDE a card. Distinct from .modal-actions, which is a modal footer and is
   position:sticky with a z-index — reused in a card it floated over the card's own content and
   covered the last two rows of the SLA list, so those items could not be clicked at all. */
.card-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 14px;
  padding-top: 12px; border-top: 1px solid var(--line); }

/* A contact rendered as something you can act on: name, company, number, address. Wraps rather than
   overflowing, because a long email beside a long name is the normal case, not the exception. */
.contact-line { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px; }
.contact-line .sep { color: var(--line); }

/* The SLA items on a jobcard being raised: tick, name, how many, and the interval as context. */
.jc-sla-items { margin-bottom: 12px; }
.jc-sla-row { grid-template-columns: auto minmax(0, 1fr) 84px 110px; }
@media (max-width: 700px) { .jc-sla-row { grid-template-columns: auto minmax(0, 1fr); } }

/* A photo on an open jobcard, with the control to withdraw it. The button only exists while the
   job is open and only for the people on it — a completed jobcard is a signed record. */
.jc-photo { position: relative; display: block; }
.jc-photo > button {
  position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; line-height: 1;
  border: 0; border-radius: 50%; background: var(--bad); color: #fff; font-size: 15px;
  font-weight: 700; cursor: pointer; padding: 0; box-shadow: 0 1px 4px rgba(0,0,0,.3);
}

/* A chart column you can open.
   The highlight is on the COLUMN, not the panel: hovering anywhere in the chart used to tint every
   month at once, which told you nothing about which one you were about to click. Now the band under
   the cursor fills and its bar darkens, so the month being opened is unmistakable. */
.ja-hit { cursor: pointer; }
.ja-col-open:hover .ja-hit { fill: rgba(42,109,181,.13); }
.ja-col-open:hover path { fill: #17427A; }
.ja-col-open:hover text { fill: #17427A; font-weight: 700; }

/* ---------- training: screen ---------- */
.trn-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.trn-scan-thumb { display: block; max-width: 100%; max-height: 180px; margin: 0 auto 12px; border-radius: 8px; border: 1px solid var(--line); }
.trn-prog { height: 10px; border-radius: 5px; background: #e8edf5; overflow: hidden; margin-top: 14px; }
.trn-prog-bar { height: 100%; background: var(--brand, #142F5C); border-radius: 5px; transition: width .3s; }

/* live scanner: camera view with an ID-card-shaped guide the person lines the card up in.
   The guide IS the status: white = searching, amber pulse = reading this very frame (hold
   still), green = captured. The pill says it in words and the bar walks with the engine. */
.trn-live { position: relative; background: #000; border-radius: 10px; overflow: hidden; }
.trn-live video { display: block; width: 100%; max-height: 55vh; object-fit: cover; }
.trn-live-guide { position: absolute; left: 6%; right: 6%; top: 50%; transform: translateY(-50%);
  aspect-ratio: 1.585; max-height: 90%; margin: 0 auto; border: 3px solid rgba(255,255,255,.92);
  border-radius: 12px; box-shadow: 0 0 0 2000px rgba(0,0,0,.35); pointer-events: none;
  transition: border-color .2s; }
.trn-live-guide.trn-g-steady { border-color: #ff5a5a; }
.trn-live-guide.trn-g-reading { border-color: #ffb020; animation: trn-pulse 1.1s ease-in-out infinite; }
.trn-live-guide.trn-g-locked { border-color: #2ecc71; animation: none; }
@keyframes trn-pulse {
  0%, 100% { box-shadow: 0 0 0 2000px rgba(0,0,0,.35), 0 0 0 0 rgba(255,176,32,.65); }
  50% { box-shadow: 0 0 0 2000px rgba(0,0,0,.35), 0 0 0 9px rgba(255,176,32,0); }
}
.trn-live-pill { position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  max-width: 92%; padding: 7px 14px; border-radius: 999px; background: rgba(10,18,32,.78);
  color: #fff; font-size: 14px; font-weight: 600; text-align: center; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.trn-live-pill.trn-pill-hot { background: rgba(173,110,8,.9); }
.trn-live-prog { position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
  background: rgba(255,255,255,.18); pointer-events: none; }
.trn-live-prog > div { height: 100%; width: 0%; background: #ffb020; transition: width .25s; }

/* ---------- training: print (register + certificates, on the letterhead table) ---------- */
.trn-reg { font: 10.5pt/1.5 "Segoe UI", Roboto, Arial, sans-serif; color: #16233b; }
.trn-reg h1 { font-size: 16pt; color: #142F5C; margin: 0 0 4mm; }
.trn-reg-meta { width: 100%; border-collapse: collapse; margin-bottom: 5mm; }
.trn-reg-meta td { border: 1px solid #c9d3e0; padding: 1.6mm 2.5mm; }
.trn-reg-meta td:nth-child(odd) { color: #556; width: 26mm; background: #f2f5fa; }
.trn-reg-tbl { width: 100%; border-collapse: collapse; }
.trn-reg-tbl th { background: #142F5C; color: #fff; text-align: left; padding: 2mm 2.5mm; font-weight: 600; }
.trn-reg-tbl td { border: 1px solid #c9d3e0; padding: 2mm 2.5mm; height: 9mm; }
.trn-reg-tbl td.num { text-align: right; }
.trn-reg-tbl tr { page-break-inside: avoid; }
.trn-reg-foot { display: flex; gap: 16mm; margin-top: 12mm; page-break-inside: avoid; }

.trn-sign { flex: 1; text-align: center; font-size: 9.5pt; color: #556; }
.trn-sign-line { border-bottom: 1px solid #16233b; height: 12mm; margin-bottom: 2mm; }

/* The certificate is the house AQF template as full-page artwork (cert-bg.png, extracted from
   Training\AQF AI Certificate.pdf with the variable text removed) with only the person, session,
   date and facilitator overlaid in position. Positions are percentages of the A4 art (595x842pt).
   Printed with @page margin 0 (injected by trnCertPrintRun) so the swooshes bleed to the edge. */
.trn-cert2 { position: relative; width: 210mm; height: 296.5mm; overflow: hidden;
  page-break-after: always; page-break-inside: avoid; font-family: Georgia, 'Times New Roman', serif; }
.trn-cert2-last { page-break-after: auto; }
.trn-cert2-bg { position: absolute; inset: 0; width: 100%; height: 100%; }
.trn-cert2 .ov { position: absolute; left: 0; width: 100%; text-align: center; z-index: 1; }
.trn-cert2 .trn-c2-name { top: 36.2%; height: 7%; display: flex; align-items: flex-end; justify-content: center; color: #16233b; }
.trn-cert2 .trn-c2-id { top: 44.5%; font-size: 8.5pt; color: #667; font-family: "Segoe UI", Arial, sans-serif; }
.trn-cert2 .trn-c2-course { top: 53.7%; height: 7.4%; display: flex; align-items: center; justify-content: center;
  font-style: italic; color: #00395E; padding: 0 10mm; box-sizing: border-box; }
.trn-cert2 .trn-c2-date { top: 65.2%; font-size: 12pt; font-family: "Segoe UI", Arial, sans-serif; color: #111; }
.trn-cert2 .trn-c2-trainer { top: 74.4%; height: 3.4%; left: 11.8%; width: 28%; display: flex;
  align-items: flex-end; justify-content: center; font-size: 12pt; font-family: "Segoe UI", Arial, sans-serif; color: #16233b; }

/* A warning inside a form that informs rather than blocks — the duplicate-jobcard notice.
   Amber, not red: raising a second similar jobcard is often correct, so this must read as
   "check this" and never as "you have done something wrong". */
.note-warn { border-left: 3px solid var(--warn); background: #fffbeb; color: #4a3a1a;
  padding: 10px 12px; border-radius: 6px; margin: 0 0 12px; font-size: 13px; line-height: 1.5; }
.note-warn a { color: #92400e; }
.note-warn .hint { color: #6b5730; }

/* ---------- leak-report QR stickers (print) ----------
   Six to an A4 sheet inside the existing 6mm/10mm page margin: two columns of 93mm, three rows of
   90mm. The QR itself lands at 38mm, which a phone reads comfortably at arm's length — below about
   25mm it starts needing a steady hand, and these are scanned one-handed.
   print-color-adjust is not optional here. Browsers drop background colour when printing unless
   told otherwise, and without it the navy band prints as white paper with white text on it. */
.lk-sheet { display: grid; grid-template-columns: 1fr 1fr; gap: 4mm; }
.lk-sticker {
  height: 90mm; background: #fff; overflow: hidden;
  border: 0.4mm dashed #9fb0c8; border-radius: 4mm;
  display: flex; flex-direction: column;
  break-inside: avoid; page-break-inside: avoid;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.lk-top {
  background: #142F5C; padding: 3mm 4mm; text-align: center;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.lk-logo { height: 9mm; }
.lk-body { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3mm 4mm 4mm; text-align: center; }
.lk-head { font-size: 15pt; font-weight: 700; color: #142F5C; line-height: 1.15; }
.lk-sub { font-size: 8.5pt; color: #556; margin: 1.2mm 0 2.5mm; line-height: 1.3; }
.lk-qr svg { display: block; width: 38mm; height: 38mm; }
.lk-where { font-size: 8.5pt; font-weight: 600; color: #16233b; margin-top: 2.5mm; line-height: 1.25; }
.lk-code { font-size: 7.5pt; color: #8494a8; letter-spacing: .08em; font-family: ui-monospace, Consolas, monospace; }

/* The other firm’s leave on the shared calendar — same block, hatched so it reads as “not ours”. */
.ev-partner { opacity: .82; background-image: repeating-linear-gradient(45deg, rgba(255,255,255,.22) 0 3px, transparent 3px 6px); cursor: default; }
