:root {
  font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}

* { box-sizing: border-box; }

/* An element's own display rule (e.g. .loading-overlay's `display: flex`)
   otherwise beats the browser's default `[hidden] { display: none }` UA
   style, since both are author-origin-equivalent specificity — without
   this, "hidden" elements like the loading overlay or toast would still
   render (and, worse, still intercept clicks) whenever they also carry a
   class that sets `display`. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
}

.muted { color: var(--grey); font-size: 0.92em; }
.error-text { color: var(--red); font-size: 0.92em; }

/* The attachment chosen for a note, with the control that removes it
   again. Nothing is uploaded until the note is saved, so this is the only
   place somebody can see what they picked and change their mind. */
.note-attachment-chosen {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: -4px;
  font-size: 0.92em;
}

/* ---------------- Buttons ---------------- */
.btn {
  border: none;
  border-radius: var(--radius);
  padding: 9px 16px;
  font-size: 0.95em;
  cursor: pointer;
  font-weight: 600;
}
.btn-primary { background: var(--action); color: #fff; }
.btn-primary:hover { background: var(--action-hover); }
.btn-ghost { background: transparent; color: var(--navy); border: 1px solid var(--border); }
.btn-ghost:hover { background: var(--grey-light); }
.btn-danger { background: var(--red-bg); color: var(--red); }
.btn-success { background: var(--green-bg); color: var(--green); }
.btn-sm { padding: 5px 10px; font-size: 0.85em; }
.btn-block { width: 100%; }
/* Job Tracker's clickable Order ID (round 19) — a text-link look, not a
   boxed button, since it toggles an inline detail row rather than
   navigating or saving anything. */
.btn-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--navy);
  text-decoration: underline;
  cursor: pointer;
  font-weight: 600;
  font-size: 1em;
}
.btn-link:hover { color: var(--navy-dark); }

/* ---------------- Login screen ---------------- */
.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, var(--navy) 0%, var(--navy-dark) 100%);
}
.login-card {
  background: #fff;
  border-radius: 12px;
  padding: 40px;
  width: 360px;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0,0,0,0.25);
}
.login-logo { max-width: 160px; max-height: 70px; margin-bottom: 12px; }
.login-card h1 { font-size: 1.1em; margin: 0 0 2px; color: var(--navy); }
.login-card h2 { margin: 18px 0 4px; color: var(--ink); }
#login-error { margin-top: 12px; }

/* Seventeenth round: the landing page (company/contract tile picker) reuses
   .login-screen/.login-card's centred, branded layout, just wider — a
   fixed 360px is too narrow once there's more than a couple of tiles. */
.landing-card { width: min(640px, 92vw); }
.landing-tiles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin-top: 20px;
}
.landing-tile {
  background: var(--blue-light);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 28px 16px;
  font-size: 1.05em;
  font-weight: 600;
  color: var(--navy);
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.landing-tile:hover { box-shadow: 0 6px 16px rgba(0,0,0,0.12); transform: translateY(-1px); }

/* ---------------- Top bar ---------------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #fff;
  border-bottom: 1px solid var(--border);
  padding: 10px 24px;
}
.topbar-left { display: flex; align-items: center; gap: 12px; }
.topbar-logo { max-height: 34px; }
.topbar-title { font-weight: 700; color: var(--navy); }
.topbar-subtitle { font-size: 0.8em; color: var(--grey); }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.topbar-user { font-size: 0.9em; color: var(--grey); }

/* ---------------- Tabs ---------------- */
.tabs {
  display: flex;
  gap: 4px;
  background: #fff;
  border-bottom: 1px solid var(--border);
  padding: 0 20px;
  overflow-x: auto;
}
.tab-btn {
  background: none;
  border: none;
  padding: 12px 16px;
  font-size: 0.95em;
  cursor: pointer;
  color: var(--grey);
  border-bottom: 3px solid transparent;
  white-space: nowrap;
}
.tab-btn:hover { color: var(--navy); }
.tab-btn.active { color: var(--action); border-bottom-color: var(--action); font-weight: 600; }

/* ---------------- Content ---------------- */
/* Round 25 — 1100px was set when this ran in a browser tab with horizontal
   tabs across the top. It now has a 236px sidebar and is used on wide
   monitors, where it left roughly a third of the screen empty on every tab
   while tables scrolled sideways inside it.
   
   1700px is about where a data table stops being scannable left to right.
   Whereabouts goes wider still, below, because its weekly grid is seven days
   across and genuinely is that shape. */
.content { padding: 24px; max-width: 1700px; margin: 0 auto; }

/* Running text does not get wider with the container. A line of help text at
   1700px is harder to read than the same line at 1100, so the prose keeps a
   sane measure while the tables take the room. */
.content > .panel > p.muted,
.content .card > p.muted,
.content .card > p.warn-text { max-width: 105ch; }
.panel { display: none; }
.panel.active { display: block; }

.banner {
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 18px;
  font-size: 0.92em;
}
.banner-warning { background: var(--amber-bg); color: var(--amber); }
.banner-info { background: var(--blue-light); color: var(--navy); }
/* Data old enough that a figure taken from it could be wrong. The module had
   a warning banner but no error one — this is the first thing here that needs
   to say "do not rely on this" rather than "worth a look". */
.banner-error { background: var(--red-bg); color: var(--red); font-weight: 600; }

.card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}
.card h3 { margin-top: 0; color: var(--navy); }
.card h4 { color: var(--navy); margin: 20px 0 8px; font-size: 1em; }

/* Engineer Portal Updates (Round 13) — a navy left accent border so this
   card is visually distinct from the plain Job Notes card right below
   it, at a glance, not just by its own heading text. */
.card-engineer-portal {
  border-left: 4px solid var(--navy);
  background: #fbfcfe;
}

.cards-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  text-align: center;
}
.stat-card .stat-value { font-size: 1.7em; font-weight: 700; color: var(--navy); }
.stat-card .stat-label { font-size: 0.82em; color: var(--grey); margin-top: 4px; }

/* ---------------- Forms ---------------- */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
}
.form-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.88em;
  color: #333;
  font-weight: 600;
}
.form-grid input, .form-grid select, .form-grid textarea {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.95em;
  font-weight: 400;
  font-family: inherit;
}
.form-grid .full-width { grid-column: 1 / -1; }
/* Sixteenth round: an action button meant to sit INLINE beside a select in
   the same row (FX Job Status' Save, Workstack's Search) rather than below
   spanning the full width like every other form's submit button — without
   this, the button (a bare <div>, no label text above it) would sit at the
   top of its grid cell instead of level with the input boxes next to it. */
.form-grid .form-btn-cell { align-self: end; }
.field-hint { font-size: 0.82em; font-weight: 400; color: var(--grey); }
.checkbox-label { flex-direction: row !important; align-items: center; gap: 8px !important; }
.days-preview { font-size: 0.9em; color: var(--navy); font-weight: 600; }

/* A wrapping grid of checkboxes for multi-selecting Job Statuses (Export
   Report, Billing) — an ordinary <select multiple> requires ctrl/cmd-click
   to pick more than one option, which isn't obvious to a first-time user;
   plain checkboxes need no explanation. */
/* Round 30h — a tick grid sitting inside a .filter-row is a flex item, so
   it shrinks to its content and collapses to a single column however wide
   the card is. Told to take the room that is going. */
.filter-row .status-check-grid { flex: 1 1 320px; margin-top: 0; }
.status-check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 6px 14px;
  margin-top: 8px;
}
/* Round 32l — the Job Tracker's FX Job Status filter.

   Closed it is one row whatever the list grows to; open it is a short
   column per family rather than one long ragged run. The columns are
   wide enough for the longest status to sit on one line, because
   "Cancelled - Sent To / Will" breaking mid-phrase was half of what
   made the old flat grid hard to read. */
.fx-status-filter { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.fx-status-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 220px;
  justify-content: space-between;
  font-weight: 600;
}
.fx-status-caret { font-size: 0.8em; transition: transform 0.15s ease; }
.fx-status-toggle[aria-expanded="true"] .fx-status-caret { transform: rotate(90deg); }
.fx-status-panel {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  margin-top: 2px;
  width: 100%;
  /* The list is expected to grow past what fits, so it scrolls here
     rather than pushing Search off the bottom of the card again. */
  max-height: 320px;
  overflow: auto;
}
.fx-status-panel-head { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.fx-status-panel-head input[type='search'] { flex: 1 1 180px; min-width: 140px; }
.fx-status-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 14px 18px;
  align-items: start;
}
.fx-status-group { display: flex; flex-direction: column; gap: 4px; }
.fx-status-group-head {
  border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
  margin-bottom: 2px;
  color: var(--navy);
}

.status-check {
  /* .form-grid label (above) sets flex-direction: column for every label
     in scope, including these, at equal-or-higher CSS specificity — same
     situation .checkbox-label already had to work around below. Without
     !important here the checkbox renders stacked above its own text
     instead of sitting beside it. */
  display: flex;
  flex-direction: row !important;
  align-items: center;
  gap: 6px !important;
  font-size: 0.88em;
  font-weight: 400;
  color: #333;
}
.status-check input[type="checkbox"] { width: auto; }

/* Compact inputs/selects placed directly inside a table cell (the Synth
   Code lines' Amount Completed / Gang columns in Job Lookup) — the
   .form-grid input/select rules above assume a labelled form layout with
   room to breathe, which is too wide for a table cell. */
.cell-input {
  width: 100%;
  min-width: 90px;
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 4px 6px;
  font-size: 0.92em;
  font-family: inherit;
}

/* ---------------- Tables ---------------- */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92em;
}
.data-table th {
  text-align: left;
  background: var(--grey-light);
  padding: 8px 10px;
  color: #444;
  font-weight: 600;
}
.data-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.data-table tr:last-child td { border-bottom: none; }

/* ---------------- Status badges ---------------- */
.badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.8em;
  font-weight: 600;
}
.badge-pending { background: var(--amber-bg); color: var(--amber); }
.badge-approved { background: var(--green-bg); color: var(--green); }
.badge-rejected { background: var(--red-bg); color: var(--red); }
.badge-cancelled { background: var(--grey-light); color: var(--grey); }

/* Reinstatement status badges (Engineer Portal Updates, Round 13) — the
   same white/green/orange/red mapping as Engineer Portal's own
   REINSTATEMENT_TONES. White gets a border since it has no fill. */
.badge-reinstatement-white { background: #fff; color: var(--navy); border: 1px solid var(--border); }
.badge-reinstatement-green { background: var(--green-bg); color: var(--green); }
.badge-reinstatement-orange { background: var(--orange-bg); color: var(--orange); }
.badge-reinstatement-red { background: var(--red-bg); color: var(--red); }

/* ---------------- Toast / loading ---------------- */
.toast {
  position: fixed;
  bottom: 24px;
  right: 24px;
  background: var(--navy-dark);
  color: #fff;
  padding: 12px 18px;
  border-radius: 8px;
  font-size: 0.92em;
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  max-width: 320px;
  z-index: 50;
}
.toast.toast-error { background: var(--red); }

.loading-overlay {
  position: fixed;
  inset: 0;
  background: rgba(255,255,255,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 40;
}
.spinner {
  width: 34px; height: 34px;
  border: 4px solid var(--blue-light);
  border-top-color: var(--navy);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 640px) {
  .content { padding: 14px; }
  .topbar { padding: 10px 14px; }
}

/* ---------------- BT Job Console additions ---------------- */
.danger-heading { color: var(--red); }

input[type="file"] { display: block; margin-bottom: 10px; }

.import-progress { margin-top: 12px; }
.progress-bar {
  width: 100%;
  height: 8px;
  background: var(--grey-light);
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: 4px;
}
.progress-bar-fill {
  height: 100%;
  background: var(--navy);
  width: 0%;
  transition: width 0.2s ease;
}

.badge-new { background: var(--green-bg); color: var(--green); }
/* Round 30b — the PAID sticker on Green Sheet Check. Its own name rather
   than reusing .badge-approved, because the two mean different things and
   one of them will want a different colour one day. */
.badge-paid { background: var(--green-bg); color: var(--green); }
/* Keeps the sticker on the same line as the date input rather than
   wrapping under it in a narrow column. */
/* The input is width:100% like every other cell input, so on its own it
   left the sticker no room and it spilled under the Save button in the next
   column. Laid out as a row instead: the input gives way, the sticker never
   does, and the date stays wide enough to read. */
.gs-paid-wrap { display: flex; align-items: center; gap: 6px; }
.gs-paid-wrap .gs-date-input { flex: 1 1 auto; min-width: 118px; }
.gs-paid-wrap .badge { flex: 0 0 auto; }
.badge-moved { background: var(--amber-bg); color: var(--amber); }
.badge-dropped { background: var(--red-bg); color: var(--red); }

.delta-up { color: var(--green); font-weight: 600; }
.delta-down { color: var(--red); font-weight: 600; }
.delta-flat { color: var(--grey); }

.data-table.dense td, .data-table.dense th { padding: 5px 8px; font-size: 0.9em; }

/* ---------------- Gang member roster (Gangs tab) ---------------- */
.member-chip-list {
  list-style: none;
  padding: 0;
  margin: 0 0 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Round 25: each member's chip grew a status row (In Work / Absent /
   Annual Leave + date range + Save + History) underneath the name, so the
   old pill-shaped .member-chip became a small card instead. */
.member-chip-row {
  background: var(--grey-light);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 0.85em;
}
.member-chip-row-top {
  display: flex;
  align-items: center;
  gap: 6px;
}
.member-chip-name { font-weight: 600; }
.member-chip-remove {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--grey);
  font-size: 1em;
  line-height: 1;
  padding: 2px 4px;
  margin-left: auto;
}
.member-chip-remove:hover { color: var(--red); }
.member-status-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
.member-status-row select.cell-input { width: auto; }
.member-status-dates { display: flex; align-items: center; gap: 4px; }
.member-status-dates input[type="date"] { width: auto; }
.member-status-history {
  list-style: none;
  margin: 6px 0 0;
  padding: 6px 0 0;
  border-top: 1px dashed var(--border);
  font-size: 0.9em;
  color: var(--grey);
}
.member-status-history li { padding: 1px 0; }
.member-status-badge {
  display: inline-block;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.82em;
  font-weight: 600;
  color: #fff;
}
.badge-member-absent { background: var(--red); }
.badge-member-leave { background: #b8860b; }
.member-add-row { display: flex; gap: 6px; }
.member-add-row input { flex: 1; min-width: 120px; }

/* ---------------- Synth Code line completion history (Job Lookup) ---------------- */
.completion-cell { min-width: 320px; }
.completion-total {
  font-weight: 600;
  color: var(--navy);
  margin-bottom: 4px;
  display: block;
}
.completion-history {
  list-style: none;
  padding: 0;
  margin: 0 0 6px;
  font-size: 0.85em;
}
.completion-history li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 2px 0;
  border-bottom: 1px dashed var(--border);
}
.completion-history li:last-child { border-bottom: none; }
.completion-history .legacy-tag { color: var(--grey); font-style: italic; }
/* Super-user-only "delete this entry" button — same visual treatment as
   the Gangs member-chip remove button, reused here for a different list. */
.entry-delete-btn {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--grey);
  font-size: 1em;
  line-height: 1;
  padding: 2px 4px;
  flex: none;
}
.entry-delete-btn:hover { color: var(--red); }
.completion-add-row { display: flex; gap: 4px; align-items: center; }
.completion-add-row input[type="number"] { width: 70px; flex: none; }
.completion-add-row select { flex: 1; min-width: 90px; }

/* A card that deliberately breaks out of .content's normal 1100px
   max-width — used for the Job Lookup Synth Code lines card, whose
   Completed/Gang cell (history list + add row) needs more breathing room
   than the standard card width gives it. The margin-left:50% +
   translateX(-50%) pair re-centers a wider box regardless of the parent's
   own max-width, without affecting sibling cards. table-scroll is a
   fallback for narrow viewports so the wider table doesn't force the
   whole page to scroll sideways. */
/* Round 25: RETIRED, but kept so the markup using it need not change.

   It existed to break a card out of a 1100px column. The column is 1700px now
   and cards fill it, so a 1500px cap would make these cards NARROWER than the
   ordinary ones beside them - the opposite of what the class is named for.

   Neutralising it also drops the transform, which was quietly making each of
   these cards a containing block for anything positioned inside it. */
.wide-card {
  max-width: 100%;
  width: 100%;
  margin-left: 0;
  transform: none;
}
.table-scroll { overflow-x: auto; }

/* ---------------- Job Tracker (round 19: inline FX Job Status edit +
   expandable per-order detail row) ---------------- */
.inline-fx-status {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.inline-fx-status select { max-width: 160px; }
.job-tracker-detail-row td {
  background: var(--grey-light);
  padding: 14px 16px;
}
.job-tracker-detail-row h4:first-child { margin-top: 0; }

/* Round 29l — a job whose FX Job Status has reached a finished state
   (Completed or Cancelled, in either list's wording). Dimmed rather than
   hidden or disabled: the row is still fully usable, it just reads as
   settled so a long result list can be skimmed. The text colour is set on
   the cells and on the Order ID link, because the link carries its own
   colour and would otherwise stay at full strength on a faded row. */
.data-table tr.row-finished > td {
  background: var(--grey-light);
  color: var(--grey);
}
.data-table tr.row-finished > td .btn-link { color: var(--grey); }

/* Round 29l — the Billing Sheet's note panel, which is a row of the sheet
   itself sitting under the job it belongs to. Same treatment as the Job
   Tracker's detail row above, for the same reason: it has to read as part
   of the row above it rather than as a new table. */
.billing-notes-row > td {
  background: var(--grey-light);
  padding: 14px 16px;
}
.billing-notes-row h4 { margin: 0 0 4px; }
.billing-notes-row textarea {
  width: 100%;
  max-width: 640px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}
.billing-notes-row .data-table { background: var(--surface); margin-top: 10px; }

/* Round 30 — the payment sheet's own integrity checks. Each line says
   whether one thing about the file hangs together, so a sheet that does
   not add up announces itself rather than quietly showing a wrong total.
   OK is deliberately quiet and CHECK is deliberately not. */
/* Round 30c — where the account stands, which is the line people came to
   read. Given its own weight rather than sitting in the muted run-on above
   it, because 'outstanding' is the number this whole tab exists to produce. */
/* Round 30d — a money figure that went our way, or did not. Green and a
   plus for paid MORE than expected, red and a minus for less. The sign is
   written out by formatVariance() rather than left to the colour: a printed
   sheet is black and white, and colour alone is not something to hang a
   payment dispute on. */
/* nowrap because the column is narrow and the browser will happily break
   after the sign, leaving a lone minus on one line and the figure on the
   next — which reads as a different number. */
.money-up, .money-down { font-weight: 600; white-space: nowrap; }
.money-up { color: var(--green); }
.money-down { color: var(--red); }

.payment-position {
  margin: 10px 0 0;
  padding: 10px 12px;
  background: var(--grey-light);
  border-left: 3px solid var(--navy);
}
.payment-checks { list-style: none; margin: 10px 0 0; padding: 0; }
.payment-checks li {
  padding: 6px 10px;
  border-left: 3px solid var(--border);
  margin-bottom: 4px;
  background: var(--grey-light);
}
.payment-checks li strong { display: inline-block; min-width: 52px; }
.payment-checks li.check-ok { border-left-color: #1a6b38; }
.payment-checks li.check-ok strong { color: #1a6b38; }
.payment-checks li.check-bad { border-left-color: var(--danger); }
.payment-checks li.check-bad strong { color: var(--danger); }
.payment-checks li.check-bad { background: var(--danger-bg); }
.job-tracker-detail-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}
.job-tracker-detail-grid > div:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 900px) {
  .job-tracker-detail-grid { grid-template-columns: 1fr; }
}

/* ---------------- DFE Required flag (Job Lookup Synth Code lines) ---------------- */
.completion-total-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.dfe-required-badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.8em;
  font-weight: 700;
  background: var(--red-bg);
  color: var(--red);
}
.dfe-required-badge.done { opacity: 0.55; }
.dfe-done-check {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.85em;
  font-weight: 400;
  color: #333;
}
.dfe-done-check input { width: auto; }

/* ---------------- Whereabouts (weekly team job grid) ---------------- */
.whereabouts-week-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.whereabouts-week-controls input[type="date"] {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.95em;
  font-family: inherit;
}
#whereabouts-week-label {
  font-weight: 700;
  color: var(--navy);
  font-size: 1.05em;
  margin: 4px 0 0;
}
/* ---------------- Round 28: one grid, the way the sheet is ----------------

   This tab used to be a stack of cards, each holding its own tables, each
   table in its own horizontal scroller with its own copy of the dates.
   Scrolling sideways moved one gang and left the rest — and the dates —
   where they were, which is the one thing a spreadsheet never does.

   Now the whole week is a single scroller: the dates are written once at
   the top and pinned there, the gang and the field labels are pinned to
   the left edge, and everything scrolls together. That is "frozen panes",
   and it is most of why the sheet stays usable at ten gangs across seven
   days.

   GRIDLINES. The first cut of this had none — cells carried a transparent
   border that only appeared on hover — and a screenful came out white on
   white with nothing separating one cell from the next. A spreadsheet is
   legible because of its gridlines, not in spite of them, so every cell
   here draws one. --wa-grid is deliberately darker than --border, which
   is tuned for the edge of a card and disappears entirely when it has to
   do this job. */
.wa-grid-scroll {
  --wa-grid: #c3cad4;
  --wa-grid-strong: #8e99a8;
  overflow: auto;
  max-height: 78vh;
  border: 1px solid var(--wa-grid-strong);
  border-radius: 6px;
  background: var(--surface);
}
/* Tables butt straight up against each other: no gaps, no card edges, so
   a gang's block reads as part of one continuous sheet. */
.wa-grid-scroll table { margin: 0; border-collapse: separate; border-spacing: 0; width: 100%; }
.wa-grid-scroll th,
.wa-grid-scroll td {
  border-right: 1px solid var(--wa-grid);
  border-bottom: 1px solid var(--wa-grid);
}
.whereabouts-team-card { margin: 0; }
.whereabouts-slot { margin: 0; }

/* A gang starts a new block, and the eye needs to see that before it
   reads a word. Heavier than any line inside the block. */
.whereabouts-team-card + .whereabouts-team-card > :first-child th,
.whereabouts-team-card + .whereabouts-team-card > :first-child td { border-top: 3px solid var(--navy); }

/* The two frozen columns. The left offset of the label column is the gang
   column's width — if one changes, so must the other. */
.wa-gang-col { width: 200px; }
.wa-label-col { width: 136px; }
.wa-day-col { width: 158px; }
.whereabouts-table .wa-field-label,
.wa-day-status-row .wa-field-label,
.wa-gang-cell {
  position: sticky;
  background: var(--grey-light);
  z-index: 2;
}
.wa-gang-cell { left: 0; }
.whereabouts-table .wa-field-label,
.wa-day-status-row .wa-field-label { left: 200px; border-right: 2px solid var(--wa-grid-strong); }
/* The field labels must never wrap — a two-line label would push its whole
   row out of step with the same row in the gang above. The gang cell is
   the opposite case: names here run to "FNS - Civils / Grounded / Team 1 -
   Shelton & Marshall", and truncating that leaves two gangs looking
   identical. */
.whereabouts-table .wa-field-label,
.wa-day-status-row .wa-field-label {
  white-space: nowrap;
  font-size: 0.74em;
  font-weight: 600;
  color: var(--navy);
  padding: 2px 8px;
  text-align: left;
}
.wa-gang-cell { white-space: normal; overflow-wrap: anywhere; }

/* The pinned header has to sit above the pinned columns where they cross,
   or the corner cell is painted over as the grid scrolls under it. */
.wa-week-header th { position: sticky; top: 0; z-index: 3; background: var(--navy); color: #fff; border-bottom-color: var(--navy-dark); }
.wa-week-header .wa-week-head-corner,
.wa-week-header .wa-field-label { z-index: 4; background: var(--navy); color: #fff; }
.wa-week-head-date { font-weight: 700; font-size: 0.82em; }
.wa-week-head-name { font-size: 0.72em; opacity: 0.8; letter-spacing: 0.06em; }
.wa-week-head-day { text-align: left; padding: 4px 6px; }

/* Column A — the gang, its crew, and that job's own controls. */
.wa-gang-cell {
  vertical-align: top;
  text-align: left;
  padding: 6px 8px;
  border-right: 2px solid var(--wa-grid-strong);
}
.wa-gang-name { font-weight: 700; font-size: 0.84em; color: var(--navy); }
.wa-gang-crew { font-size: 0.74em; color: var(--grey); margin-top: 2px; }
.wa-gang-stickers { margin-top: 4px; }
.wa-gang-job { margin-top: 8px; font-size: 0.76em; font-weight: 600; }
.wa-gang-cell-status { border-top: none; }
.wa-add-slot-wrap { padding: 4px 8px 10px; position: sticky; left: 0; width: max-content; }

/* ---- CELL BACKGROUND ORDER ----------------------------------------

   Weakest first. Each rung is written to beat the one above it, and the
   comment is the contract: if you add a sixth thing that colours a cell,
   put it on this ladder rather than beside it.

     1. row banding      a reading guide
     2. the weekend      context
     3. a closed day     this gang cannot work
     4. permit status    THE signal — never let anything paint over it
     5. a contested cell someone else disagrees with you right now

   Today is deliberately NOT on the ladder: it marks itself with an inset
   edge rather than a fill, so it can sit underneath any of the above
   without taking a colour away from them. */

/* 1. Banding, so the eye can carry a row across seven columns without
      losing which field it is on. */
/* Weakest on the ladder ON PURPOSE: no .wa-grid-scroll in front of it,
   so it scores (0,2,3) against the (0,3,1) of every rung below. The first
   attempt at this ladder had it at (0,3,3) and it beat the permit colours
   outright — the decoration painting over the signal. */
.whereabouts-table tbody tr:nth-child(even) td { background: #fafbfc; }

/* 2. The weekend. A planner orients by it before reading anything, and in
      the sheet it is obvious because it is nearly always empty. */
.wa-grid-scroll .whereabouts-table td.wa-day-weekend,
.wa-grid-scroll .wa-day-status-row td.wa-day-weekend { background: #eceff3; }
.wa-grid-scroll .wa-week-header th.wa-day-weekend { background: var(--navy-dark); }

/* 3. A day the whole gang is out. */
.wa-grid-scroll .whereabouts-table td.wa-day-closed { background: #e4e7eb; opacity: 0.7; }

/* 4. Permit status — green granted, amber submitted, red refused, which
      is Chris's own colour code off the spreadsheet. This is the rung
      everything else exists to stay out of the way of. */
.wa-grid-scroll .whereabouts-table td.wa-permit-granted { background: #4caf50; }
.wa-grid-scroll .whereabouts-table td.wa-permit-submitted { background: #ff9800; }
.wa-grid-scroll .whereabouts-table td.wa-permit-refused { background: #e53935; }
.wa-grid-scroll .whereabouts-table td.wa-permit-granted .wa-cell,
.wa-grid-scroll .whereabouts-table td.wa-permit-submitted .wa-cell,
.wa-grid-scroll .whereabouts-table td.wa-permit-refused .wa-cell { color: #10243f; font-weight: 600; }

/* 5. A cell two people have both changed. Loudest, because it is the only
      one of these that needs a decision from the person looking at it. */
.wa-grid-scroll .whereabouts-table td .wa-cell.wa-cell-contested {
  outline: 2px solid #c2410c;
  outline-offset: -2px;
  background: #fff4e6;
}

/* Today, marking itself with an edge rather than a fill so it never takes
   a colour off the ladder above. */
.wa-grid-scroll td.wa-day-today { box-shadow: inset 2px 0 0 var(--navy), inset -2px 0 0 var(--navy); }
.wa-grid-scroll .wa-week-header th.wa-day-today { background: #2c4f8f; }

.whereabouts-table th { font-size: 0.82em; }

/* Round 27: the job-slot table and the day-status-row table (below) are
   two separate <table> elements, each rendered with its own <colgroup> —
   see renderWhereaboutsSlotTable()/renderWhereaboutsDayStatusRow() in
   app.js. Without forcing both to the SAME fixed column widths, each
   table auto-sizes its own columns from its own content, so the
   day-status row's tick-boxes/notes can end up visually a column or two
   off from the date they actually belong to even though the underlying
   data is correctly keyed to that date either way. table-layout: fixed
   plus matching .wa-label-col/.wa-day-col widths keeps every date column,
   across every table in a gang's card, pinned to the same pixel position. */
.whereabouts-table,
.wa-day-status-row {
  table-layout: fixed;
}
/* THE CELL IS THE BOX. The input draws nothing of its own — no border,
   no background — so what shows through is the table cell underneath,
   which is what carries the gridline, the banding, the weekend tint and,
   most importantly, the permit colour.

   The previous cut gave the input a white background, which painted over
   the permit colour on the cell behind it: green/amber/red survived only
   as a hairline round a white box. The single signal this tab exists to
   make unmissable had become the hardest thing on screen to see. */
.wa-cell {
  width: 100%;
  min-width: 0;
  border: none;
  border-radius: 0;
  padding: 3px 6px;
  font-size: 0.78em;
  line-height: 1.35;
  font-family: inherit;
  color: inherit;
  background: transparent;
  resize: vertical;
}
.wa-cell:focus {
  outline: 2px solid var(--navy);
  outline-offset: -2px;
  background: var(--surface);
}
.whereabouts-table td, .wa-day-status-row td { padding: 1px 2px; }
.whereabouts-slot-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}
.whereabouts-slot-actions .btn { font-size: 0.72em; padding: 2px 6px; }
.wa-add-slot-btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* Round 25 (generalized Round 26): greys out a whole day's cell, across
   every job-slot table, on any gang whose EVERY member is Absent/Annual
   Leave that day (see isGangClosedOnDate() in app.js — no longer only
   single-member gangs) — the already-saved value (if any) stays visible,
   it's just no longer editable. */
.wa-day-closed { background: var(--grey-light); opacity: 0.65; }
.wa-cell:disabled { cursor: not-allowed; }

/* Round 30j — a button that cannot be pressed has to look like it. The
   Payments tab showed a solid red "Apply these payments" beside the words
   "Every job the client has paid for is already recorded here", which is a
   button advertising work it was never going to do. Nothing else in the app
   had disabled styling either; this covers the lot. */
.btn:disabled,
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  filter: grayscale(0.6);
}

/* Round 25: the per-gang "who's out this week" strip shown once above the
   job-slot tables, one column per day — see renderWhereaboutsDayStatusRow()
   in app.js. */
.wa-day-status-row .wa-field-label {
  background: var(--grey-light);
  min-width: 90px;
  white-space: nowrap;
}
.wa-day-status-cell { font-size: 0.85em; vertical-align: top; }
.wa-day-status-cell.wa-day-status-out { background: #fff6e0; }
.wa-day-status-note { color: var(--red); font-weight: 600; margin-bottom: 4px; }
.wa-day-status-closed-note { color: var(--grey); font-weight: 400; font-style: italic; }
.wa-day-status-incoming { color: #1a6e3c; font-weight: 600; }
.wa-reassign-row { margin-bottom: 6px; }
.wa-reassign-row:last-child { margin-bottom: 0; }
.wa-reassign-check { display: flex; align-items: center; gap: 4px; font-weight: 400; white-space: nowrap; }
.wa-reassign-select { display: block; margin-top: 4px; width: 100%; min-width: 120px; }
.wa-reassign-job-preview { font-size: 0.92em; color: var(--grey); margin-top: 2px; }

/* Round 26: a small badge above the date on a day header that's an
   England & Wales bank holiday — see englandWalesBankHolidayFor() in
   config.js and renderWhereaboutsSlotTable() in app.js. */
.wa-bank-holiday-badge {
  display: inline-block;
  background: #1a6e3c;
  color: #fff;
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 0.75em;
  font-weight: 600;
  margin-bottom: 2px;
  white-space: nowrap;
}

/* Round 26: incoming-reassignment sticker (distinct colour from the
   absence badges — see .badge-member-absent/.badge-member-leave). */
.badge-member-incoming { background: #1a6e3c; }

/* Round 26: each gang's whole Whereabouts card can be collapsed — starts
   expanded, so the caret starts pointing down; collapsing it hides
   everything except the header line (name + stickers) — see
   toggleWhereaboutsTeamCollapsed() in app.js. */
.whereabouts-team-card-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 2px;
}
.wa-team-title { margin: 0; }
.wa-team-title .wa-caret { transform: rotate(90deg); }
.whereabouts-team-card.wa-team-collapsed .wa-team-title .wa-caret { transform: rotate(0deg); }
.whereabouts-team-card.wa-team-collapsed .whereabouts-team-card-body { display: none; }
/* The row a collapsed gang leaves behind — shown ONLY while collapsed, so
   the gang's name and its toggle never appear twice, and never disappear
   entirely. Without it, collapsing a gang hid the only button that could
   bring it back. */
.wa-team-collapsed-bar { display: none; }
.whereabouts-team-card.wa-team-collapsed .wa-team-collapsed-bar { display: table; }
.wa-collapsed-day {
  font-size: 0.74em;
  color: var(--grey);
  vertical-align: top;
  padding: 4px 6px;
}
.wa-collapsed-day-marked { color: #311b92; font-weight: 600; }

/* Round 26: the "Weekly Whereabouts" and "Show gangs" cards' own
   collapse toggle — same caret idea, but simpler (no per-item state, see
   the Whereabouts' own static cards, whose collapse was removed in round
   31k — see index.html). Starts expanded. */
.wa-collapse-toggle .wa-caret { transform: rotate(90deg); }
.wa-collapse-toggle.wa-collapse-toggle-collapsed .wa-caret { transform: rotate(0deg); }

/* Round 28: a job now starts OPEN, with every row showing. It used to
   start collapsed to its Order ID, which made a screenful of this tab look
   nothing like a screenful of the spreadsheet — where everything is always
   on display and that is the whole point of it. The "Job N" toggle still
   collapses one job when a planner wants it out of the way; it is just no
   longer what they are given first. renderWhereaboutsSlotTable() puts
   .wa-expanded on every slot as it renders. Purely visual: every field
   stays in the DOM and still gets saved whether or not it is showing. */
.whereabouts-slot .wa-detail-row { display: none; }
.whereabouts-slot.wa-expanded .wa-detail-row { display: table-row; }
.wa-slot-toggle {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.wa-slot-toggle:hover { text-decoration: underline; }
.wa-caret {
  display: inline-block;
  font-size: 0.8em;
  transition: transform 0.15s ease;
}
.whereabouts-slot.wa-expanded .wa-caret { transform: rotate(90deg); }

/* Job Lookup's "Job details" card (fifteenth round) — starts collapsed so
   the raw job fields are hidden until clicked, reusing the same
   toggle/caret look as the Whereabouts job-slot collapse above. Clicking
   the heading adds .expanded to #lookup-details-card (see
   toggleJobDetailsExpanded() in app.js); the table itself never leaves the
   DOM, so renderLookup() can keep refreshing its rows regardless of
   whether they're currently visible. */
#lookup-details-card .job-details-table { display: none; }
#lookup-details-card.expanded .job-details-table { display: table; }
#lookup-details-card.expanded .wa-caret { transform: rotate(90deg); }

/* Round 28 — a general collapsible card, generalised from the one above
   rather than copied: the Reports tab now carries several reports, each
   with two long tick grids, and all of them open at once was a page you
   had to scroll past rather than read.

   Everything inside the card except its <h3> is hidden while collapsed,
   so a report gains no extra markup to be collapsible and nothing has to
   be remembered when one is added. The content never leaves the DOM, so
   the live counts keep updating while a card is shut — a report can be
   opened to a figure that is already right rather than one that arrives
   a moment later.

   The hiding rule applies ONLY while collapsed. Hiding with `display:none`
   and then restoring with `display:revert` would have been the obvious
   pair and would have been wrong: revert goes back to the BROWSER's
   default, so .form-grid and .status-check-grid — both `display: grid`
   here — would have reopened as plain blocks with every filter in one
   column. With no rule at all in the expanded state, a card opens exactly
   as it looked before it could be collapsed. */
.collapsible-card:not(.expanded) > *:not(h3) { display: none; }
.collapsible-card .wa-caret { transform: rotate(0deg); }
.collapsible-card.expanded .wa-caret { transform: rotate(90deg); }

/* A day that's been soft-deleted shows a plain placeholder in its column
   instead of live inputs — the underlying values are still there in
   Graph (see deleteWhereaboutsDay), just not editable from this table. */
.wa-day-deleted-cell {
  text-align: center;
  color: var(--grey);
  background: var(--grey-light);
  font-size: 0.85em;
  font-style: italic;
}
.badge-active { background: var(--green-bg); color: var(--green); }
.badge-deleted { background: var(--red-bg); color: var(--red); }
.wa-history-deleted td { color: var(--grey); }
/* An Outcome other than N/A, shown on Job Lookup's Whereabouts history —
   deliberately a neutral highlight colour rather than red, since these
   outcomes range from good news (Job Completed) to bad (Gang Did Not
   Attend); the point is just to draw the eye, not imply a verdict. */
.badge-outcome-flag { background: var(--amber-bg); color: var(--amber); }
/* Job Tracker's computed "Type" column (round 20) — Flex/Civils worked out
   purely from a job's Synth Code line(s)' prefix, see
   synthCodeTypeLabel()/jobTrackerTypeCellHtml() in app.js. Two neutral,
   distinct colours (reusing tokens already used elsewhere for other
   badges) so the two types are easy to tell apart at a glance, without
   implying either one is "good" or "bad". */
.badge-type-flex { background: var(--blue-light); color: var(--navy); }
.badge-type-civils { background: var(--grey-light); color: var(--grey); }

/* Round 21: Job Lookup's "In Whereabouts" / "Not Programmed" sticker, next
   to the FX Job Status heading — see renderLookupWhereaboutsPresenceBadge()
   in app.js. Own classes rather than reusing .badge-active/.badge-deleted
   (same colour values, but a distinct name keeps intent clear at each call
   site — same reasoning as .badge-replanned above). Red for "Not
   Programmed" is deliberate, not a neutral colour, since the point of this
   sticker is to flag at a glance that a job hasn't been scheduled into
   Whereabouts yet. */
.badge-in-whereabouts { background: var(--green-bg); color: var(--green); }
.badge-not-programmed { background: var(--red-bg); color: var(--red); }
/* Round 27 — a job with no Synth Code lines, so nothing to price. Red, the
   same as Not Programmed: it is not an error, but a job that silently
   values at zero is the one thing here worth stopping for. */
.badge-no-synth { background: var(--red-bg); color: var(--red); }

/* ---------------- Grand total box (Billing tab) ---------------- */
.grand-total-box {
  text-align: center;
  background: var(--blue-light);
  border: 2px solid var(--navy);
}
.grand-total-box h3 { margin-top: 0; }
.grand-total-value {
  font-size: 2.4em;
  font-weight: 700;
  color: var(--navy);
  margin: 0;
}

/* ---------------- Whereabouts: gang filter (thirteenth round) ---------------- */
/* Reuses .status-check-grid/.status-check (defined above, for Export
   Report/Billing's status checkboxes) rather than a near-duplicate set of
   rules — visually it's the exact same "wrapping grid of checkboxes" shape. */
#whereabouts-gang-filter-card, .card:has(#whereabouts-gang-filter-body) h3 { display: flex; }

/* ---------------- Whereabouts: Permit colouring (thirteenth round) ---------------- */
/* Applied to a day's Order ID cell (always visible, even collapsed) based
   on that same day's Permit dropdown — see permitCellClass()/
   updateWhereaboutsPermitColor() in app.js and
   WHEREABOUTS_PERMIT_CLASS_BY_VALUE in config.js. Plain named-ish colours
   with black text, exactly as asked for — deliberately not reusing the
   --green/--amber/--red *_bg tokens used elsewhere for badges, since
   those are pale backgrounds meant to sit behind dark text in a small
   pill, not fill an entire table cell. */
/* Permit colours for anywhere outside the weekly grid. Inside it they are
   set on the ladder in the Round 28 block above, which is what decides
   what wins when more than one thing wants to colour a cell. */
.wa-permit-granted { background: #4caf50; color: #10243f; }
.wa-permit-submitted { background: #ff9800; color: #10243f; }
.wa-permit-refused { background: #e53935; color: #10243f; }

/* ---------------- Round 28: more than one planner at a time ---------------- */

/* A cell this planner has changed that somebody else has ALSO changed,
   to something different, since the page last agreed with SharePoint.
   The mark is an outline rather than a fill, because the permit colours
   above already own a cell's background and a disagreement must not be
   able to hide whether a permit has been granted.

   Deliberately not disabled and not read-only: the person looking at it
   can see both versions (theirs is in the tooltip) and is far better
   placed to decide than any rule here. Saving asks. */
.wa-cell-contested {
  outline: 2px solid #c2410c;
  outline-offset: -2px;
  background: #fff4e6;
}

/* The "not on BT work today" marker, on each gang's day-status row.
   Blank on a normal day and almost invisible, so a week of ordinary
   working days does not look like a week of empty boxes demanding to be
   filled in. Once it holds something it is the loudest thing in the
   column, because it changes what every job cell below it means. */
.wa-daymarker {
  width: 100%;
  font-size: 0.75rem;
  padding: 2px 4px;
  border: 1px dashed var(--border, #d1d5db);
  background: transparent;
  color: inherit;
}
.wa-daymarker::placeholder { opacity: 0.45; font-style: italic; }
.wa-daymarker-set {
  border: 1px solid #7e57c2;
  background: #ede7f6;
  color: #311b92;
  font-weight: 600;
}
/* The whole day-status cell picks it up too, so the column reads as "not
   a working day" at a glance rather than only on close inspection. */
.wa-day-status-marked { background: #f3edfb; }

/* The note under a repeated Order ID. Quiet by design: repeating an
   order across two days is ordinary practice, not a mistake, and styling
   it as a problem would train people to ignore it. */
.wa-repeat { margin-top: 2px; line-height: 1.2; }
.wa-repeat-label {
  font-size: 0.68em;
  color: var(--grey);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.wa-repeat-echo { font-style: italic; text-transform: none; letter-spacing: 0; }
.wa-repeat-select {
  display: block;
  width: 100%;
  margin-top: 1px;
  font-size: 0.68em;
  font-family: inherit;
  padding: 1px 2px;
  border: 1px dashed var(--border);
  background: transparent;
  color: inherit;
}

/* Who else has this week open. A quiet line under the week label — this
   appears without anyone asking for it, so it stays out of the way. */
.wa-presence {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  color: var(--muted, #6b7280);
}

/* A day's status badge on Job Lookup's Whereabouts history, once it's
   been Cancelled or Replanned via the Replan pop-up (thirteenth round) —
   .badge-cancelled already existed (reused from the leave system's
   status badges); .badge-replanned is new here. */
.badge-replanned { background: var(--blue-light); color: var(--navy); }

/* ---------------- Replan / Cancel pop-up (thirteenth round) ---------------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 60;
  padding: 20px;
}
.modal-box {
  background: #fff;
  border-radius: var(--radius);
  padding: 20px 24px;
  max-width: 520px;
  width: 100%;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  box-shadow: 0 20px 50px rgba(0,0,0,0.3);
}
.modal-box h3 { margin-top: 0; color: var(--navy); }
.replan-type-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin: 10px 0 16px;
}
#replan-cancelled-fields, #replan-newdate-fields, #replan-deleted-fields {
  border-top: 1px solid var(--border);
  padding-top: 12px;
  margin-top: 4px;
}
#replan-cancelled-fields label, #replan-newdate-fields label, #replan-deleted-fields label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.88em;
  font-weight: 600;
  color: #333;
  margin-bottom: 10px;
}
#replan-cancelled-fields textarea, #replan-newdate-fields textarea, #replan-deleted-fields textarea {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 0.95em;
  font-weight: 400;
  resize: vertical;
}
#replan-modal-close-btn { margin-top: 12px; }

/* ---------------------------------------------------------------------
   Dashboard value tiles and their breakdown
   ---------------------------------------------------------------------
   The two money tiles were a bare number with nothing to say what went
   into them, which is how "Complete Workstack Value" came to look wrong:
   it excludes JOB COMP, and JOB COMP is over half the rows.
   -------------------------------------------------------------------- */

.stat-card-action {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  border: 1px solid var(--border, #e2e5ea);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.stat-card-action:hover { border-color: var(--navy, #1f3864); }
.stat-card-action:focus-visible { outline: 2px solid var(--navy, #1f3864); outline-offset: 2px; }

.stat-note {
  margin-top: 6px;
  font-size: 0.82em;
  line-height: 1.35;
  color: var(--ink-muted, #5b6472);
}
/* The affordance. Red is this product's "do something" colour, and one
   small line of it is enough — the tile itself stays quiet. */
.stat-more {
  margin-top: 6px;
  font-size: 0.8em;
  font-weight: 600;
  color: var(--action, #c9241c);
}

.modal-box-wide { max-width: 760px; width: 100%; }

.vm-table { width: 100%; margin-top: 14px; }
.vm-status { font-weight: 600; }
.vm-rows, .vm-share { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.vm-value { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.vm-bar-cell { width: 45%; min-width: 120px; }

/* Navy, not red: these bars are data, and red here would read as a warning
   about whichever status happened to be biggest. */
.vm-bar {
  height: 14px;
  min-width: 2px;
  border-radius: 3px;
  background: var(--navy, #1f3864);
}

.vm-unpriced { margin-top: 12px; }

@media (max-width: 720px) {
  .vm-bar-cell { display: none; }
}

/* The "adding nothing to this total" block inside a value tile's breakdown.
   Set apart from the status table above it because it is a to-do list, not
   part of the figure — these are the lines someone has to go and fix. */
.vm-unpriced-block {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 2px solid var(--border, #e2e5ea);
}
.vm-unpriced-head {
  margin: 0 0 4px;
  color: var(--orange, #b25e09);
}
.vm-unpriced-intro { margin: 0 0 10px; }
.vm-copy { margin-top: 10px; }

/* Round 25 — the associated jobs summary line. The badges carry their own
   pill shape; this only gives them room to breathe and somewhere to wrap. */
.assoc-summary { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 12px; }

/* Round 25 — FX Job Status and FX ECD side by side in one card. Two columns
   where there is room, stacking when there is not; each field keeps its own
   Save button on the same line as its input rather than below it, which is
   what made two separate cards feel so tall. */
.fx-merged-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 14px 24px;
  align-items: start;
}
.fx-merged-field > label {
  display: block;
  font-size: 0.88em;
  font-weight: 600;
  color: #333;
  margin-bottom: 5px;
}
.fx-merged-row { display: flex; gap: 8px; align-items: center; }
.fx-merged-row select,
.fx-merged-row input {
  flex: 1 1 auto;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.95em;
  font-family: inherit;
}
.fx-merged-row .btn { flex: none; }
.fx-merged-field .muted { margin: 6px 0 0; font-size: 0.85em; }

/* ---------------- Whereabouts uses the width of the screen ----------------
   Round 25. The weekly grid is seven days across with a job, a status and
   comments in each — the widest thing in this app by some margin — and it was
   living inside the same 1100px column as a two-field form.

   Done by widening .content itself while that tab is open, rather than by
   making the cards break out of it. The breakout trick (.wide-card, above)
   works, but it needs margin-left:50% and a transform on every card, and a
   transform makes the card a containing block for anything positioned inside
   it. Widening the container has no such side effect, and the cards simply
   fill it.

   Only while the tab is ACTIVE, so every other tab keeps the narrower column
   that makes ordinary forms and tables readable. :has() is already used
   elsewhere in this file, so this is not a new dependency. */
.content:has(#panel-whereabouts.active) {
  max-width: 1900px;
}


/* Round 25 — the Job Tracker's "find within these results" row. */
.filter-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 0 0 12px;
}
.filter-row input[type="search"] {
  flex: 1 1 auto;
  max-width: 420px;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.95em;
  font-family: inherit;
}

/* ---------------- Round 29: the plan view ----------------

   One row per gang, one card per job, the permit colour down the card's
   left edge. The companion to the grid above, not a replacement: you look
   here and type there, and a card click carries you across.

   A STRIPE rather than a fill, decided by trying both against a real
   week. A wall of saturated colour reads instantly across a room and is
   wallpaper by Wednesday, and it leaves nothing louder for the cell two
   people are fighting over. The stripe keeps green/amber/red the only
   saturated thing on the page — the same rule the grid follows. */
.wa-view-switch {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.wa-view-opt { font-size: 0.8rem; color: var(--grey); display: flex; align-items: center; gap: 5px; }

/* Pinned, because the whole complaint was that clicking a card leaves you
   scrolled half way down the grid with the way back off the top of the
   screen. A switch you have to go looking for is not a switch. */
.wa-view-switch {
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--surface);
  padding: 6px 0;
}

/* The yard's week in one line. */
.wa-strip {
  display: grid;
  grid-template-columns: 210px repeat(7, 1fr);
  background: var(--surface);
  border: 1px solid var(--wa-grid-strong, #8e99a8);
  border-bottom: none;
  border-radius: 6px 6px 0 0;
  overflow: hidden;
}
.wa-strip > div { padding: 6px 10px; border-right: 1px solid #c3cad4; font-size: 0.76rem; }
.wa-strip > div:last-child { border-right: none; }
.wa-strip .wa-strip-lbl {
  color: var(--grey); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
}
.wa-strip b { font-size: 1.1rem; font-weight: 700; display: block; line-height: 1.15; }
.wa-strip .wa-day-weekend { background: #f0f2f5; }

.wa-plan-scroll { border-radius: 0 0 6px 6px; }
.wa-plan-table { table-layout: fixed; }
.wa-plan-gang-col { width: 210px; }
.wa-plan-day-col { width: 200px; }
.wa-plan-gang {
  position: sticky; left: 0; z-index: 2;
  width: 210px; min-width: 210px;
  background: var(--grey-light);
  text-align: left; vertical-align: top; padding: 8px 10px;
  border-right: 2px solid var(--wa-grid-strong, #8e99a8);
  white-space: normal;
}
.wa-plan-table thead th { position: sticky; top: 0; z-index: 3; background: var(--navy); color: #fff; }
.wa-plan-corner { z-index: 4; background: var(--navy); color: #fff; }
.wa-plan-head { text-align: left; padding: 5px 8px; }
.wa-plan-date { font-weight: 700; font-size: 0.82rem; display: block; }
.wa-plan-day { font-size: 0.7rem; opacity: 0.8; letter-spacing: 0.06em; }
.wa-plan-cell { width: 200px; min-width: 200px; vertical-align: top; padding: 4px; }
.wa-plan-load { margin-top: 6px; font-size: 0.74rem; color: var(--grey); }
.wa-plan-load b { color: var(--ink); }
.wa-plan-prob { color: var(--red); font-weight: 700; }

/* A job. The left border is the only place saturated colour appears. */
.wa-card {
  background: var(--surface);
  border: 1px solid #c3cad4;
  border-left: 4px solid #8e99a8;
  border-radius: 3px;
  padding: 5px 7px;
  margin-bottom: 4px;
  cursor: pointer;
}
.wa-card:last-child { margin-bottom: 0; }
.wa-card:hover { border-color: var(--navy); }
.wa-card:focus-visible { outline: 2px solid var(--navy); outline-offset: 1px; }
.wa-card-order { font-weight: 700; font-size: 0.82rem; }
.wa-card-exch { font-size: 0.76rem; }
.wa-card-loc, .wa-card-desc { font-size: 0.72rem; color: var(--grey); margin-top: 1px; }
.wa-sent {
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--green, #1e7e34); margin-left: 5px; font-weight: 700;
}
/* min-width:0 so a long tag can shrink inside the flex row rather than
   forcing the row wider than the card; max-width so none can exceed it. */
.wa-card-tags { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 3px; min-width: 0; }
.wa-card-tags > .wa-tag { max-width: 100%; }
.wa-tag {
  font-size: 0.64rem; padding: 1px 5px; border-radius: 9px;
  background: var(--grey-light); color: var(--grey); white-space: nowrap;
}
.wa-tag-tm { background: #eef2f9; color: var(--navy); font-weight: 600; }
/* Round 32b — the one tag that can run long. A job in for a month names
   every other day it is on, and .wa-tag's nowrap sent that straight out of
   the card instead of wrapping inside it. The short tags keep nowrap. */
.wa-tag-rep {
  background: #f4edfb; color: #5b2a86; font-weight: 600;
  white-space: normal; overflow-wrap: anywhere;
}
/* .wa-tag-replan lived here. Round 32a took the replan tag off the card:
   a cancelled day draws no card, so the tag could only ever reach one on a
   day that had been REUSED, where it named the job that was there before. */

/* The permit FILLS the card. Built as a stripe first and changed once
   Chris had the board in front of a real week: at a glance across ten
   gangs the stripe is too quiet to count, and counting is what the board
   is for.

   Written against .wa-card so it cannot be undercut by the weekend tint
   the way the grid's fills once were by row banding — see the CELL
   BACKGROUND ORDER ladder above for that lesson. The text goes dark
   rather than white: #ff9800 will not carry white text at this size, and
   three different text colours for three permit states is a rule nobody
   can hold in their head. */
.wa-plan-cell .wa-card.wa-permit-granted { background: #4caf50; border-color: #2e7d32; border-left-color: #2e7d32; }
.wa-plan-cell .wa-card.wa-permit-submitted { background: #ff9800; border-color: #e65100; border-left-color: #e65100; }
.wa-plan-cell .wa-card.wa-permit-refused { background: #e53935; border-color: #b71c1c; border-left-color: #b71c1c; }
/* Round 31g — no permit needed. Its own colour and deliberately not the
   green: a gang can go, but nothing was granted and nothing is on record
   with the council, and painting it green would lose that difference in
   the one place it costs something. Teal reads as "fine, nothing to
   chase" without claiming a permission nobody gave. */
.wa-plan-cell .wa-card.wa-permit-none-needed { background: #00897b; border-color: #00695c; border-left-color: #00695c; }
.wa-plan-cell .wa-card.wa-permit-granted,
.wa-plan-cell .wa-card.wa-permit-submitted,
.wa-plan-cell .wa-card.wa-permit-refused { color: #10243f; }
/* Teal is dark enough that the near-black used on the other three would
   not read on it. */
.wa-plan-cell .wa-card.wa-permit-none-needed { color: #ffffff; }
.wa-plan-cell .wa-card.wa-permit-none-needed .wa-card-loc,
.wa-plan-cell .wa-card.wa-permit-none-needed .wa-card-desc { color: rgba(255,255,255,0.85); }
/* The greys inside a filled card would vanish into it. */
.wa-plan-cell .wa-card.wa-permit-granted .wa-card-loc,
.wa-plan-cell .wa-card.wa-permit-granted .wa-card-desc,
.wa-plan-cell .wa-card.wa-permit-submitted .wa-card-loc,
.wa-plan-cell .wa-card.wa-permit-submitted .wa-card-desc,
.wa-plan-cell .wa-card.wa-permit-refused .wa-card-loc,
.wa-plan-cell .wa-card.wa-permit-refused .wa-card-desc { color: #10243f; opacity: 0.8; }
.wa-plan-cell .wa-card.wa-permit-granted .wa-tag,
.wa-plan-cell .wa-card.wa-permit-submitted .wa-tag,
.wa-plan-cell .wa-card.wa-permit-refused .wa-tag { background: rgba(255,255,255,0.75); color: #10243f; }
.wa-plan-cell .wa-card.wa-permit-granted .wa-sent,
.wa-plan-cell .wa-card.wa-permit-submitted .wa-sent,
.wa-plan-cell .wa-card.wa-permit-refused .wa-sent { color: #10243f; }

.wa-plan-marker {
  border-radius: 3px; padding: 5px 7px; margin-bottom: 4px;
  font-size: 0.74rem; font-weight: 700; text-align: center;
  background: #ede7f6; color: #311b92; border: 1px solid #b39ddb;
}
.wa-plan-closed { background: var(--grey-light); color: var(--grey); border-color: #c3cad4; }

/* The job you were last in, so coming back from the grid shows you where
   you were instead of leaving you to work it out. */
.wa-card-last { box-shadow: 0 0 0 2px var(--navy); }

/* Round 29b: adding a job from the board. Quiet until wanted — an empty
   day should look empty, which was the whole point of the plan view, so
   the "+" only comes forward on hover or keyboard focus. */
.wa-plan-add {
  display: block;
  width: 100%;
  margin-top: 2px;
  padding: 3px;
  border: 1px dashed transparent;
  border-radius: 3px;
  background: none;
  color: var(--grey);
  font: inherit;
  font-size: 0.7rem;
  cursor: pointer;
  opacity: 0;
}
.wa-plan-cell:hover .wa-plan-add,
.wa-plan-add:focus-visible { opacity: 1; border-color: var(--border); }
.wa-plan-add:hover { border-color: var(--navy); color: var(--navy); }
.wa-plan-cell-empty { vertical-align: middle; }

/* Round 30: the two states that are not a traffic light.

   "Waiting Permit" — no notice exists at all, so nobody has asked yet.
   Grey, because it is the absence of an answer rather than an answer.

   "Done — to register" — Works Stop and Work Stop - No Excavation, which
   Chris explains mean the job is finished but not yet registered with the
   council. Deliberately BLUE and deliberately outside the green/amber/red
   vocabulary: green would send a gang to a job that is already done, red
   would say somebody is blocked when nobody is, and amber would say we
   are waiting on the council when the council is waiting on us. */
.wa-plan-cell .wa-card.wa-permit-waiting {
  background: var(--surface);
  border-color: #c3cad4;
  border-left-color: #8e99a8;
}
.wa-plan-cell .wa-card.wa-permit-done {
  background: #1565c0;
  border-color: #0d47a1;
  border-left-color: #0d47a1;
  color: #fff;
}
.wa-plan-cell .wa-card.wa-permit-done .wa-card-loc,
.wa-plan-cell .wa-card.wa-permit-done .wa-card-desc,
.wa-plan-cell .wa-card.wa-permit-done .wa-sent { color: #fff; opacity: 0.85; }
.wa-plan-cell .wa-card.wa-permit-done .wa-tag { background: rgba(255,255,255,0.85); color: #0d47a1; }
.wa-plan-reg { color: #1565c0; font-weight: 700; }

/* Round 30c: a job somebody else has open. Dimmed and struck through
   rather than hidden — you should be able to see what is there, you just
   should not be the second person changing it. */
.wa-card-locked { opacity: 0.6; cursor: not-allowed; }
.wa-tag-locked { background: #fde68a; color: #78350f; font-weight: 700; }
.wa-weekend-check { display: flex; align-items: center; gap: 6px; font-size: 0.85em; }

/* Round 30e/f: the day cell's two quiet actions, and the per-gang tools. */
.wa-plan-actions { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.wa-plan-mark { font-style: italic; }
.wa-gang-tools { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
.wa-gang-detail {
  display: flex; align-items: center; gap: 4px;
  font-size: 0.7rem; color: var(--grey); white-space: nowrap; cursor: pointer;
}

/* Round 31: has the job pack gone out. Beside the job number, where Chris
   asked for it — it is a fact about THAT job, and reading down a column of
   order numbers is how a planner finds the ones still to send.

   Sent is the quiet state and To Send is the loud one, which is the way
   round that matters: a job already sent needs nothing from anybody, and a
   job still to send is a thing on somebody's list. */
.wa-sent-badge {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 9px;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  vertical-align: middle;
  white-space: nowrap;
}
.wa-sent-no { background: #78350f; color: #fff; }
.wa-sent-yes { background: rgba(255,255,255,0.8); color: #14532d; }
/* Round 31r — the BT PCP, beside the Sent badge on the same line as the
   order number. Outlined rather than filled: the Sent badge is a state
   that changes and wants the eye, this is a reference that does not. */
.wa-card-pcp {
  margin-left: 6px; padding: 0 5px; border-radius: 3px;
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.02em;
  border: 1px solid currentColor; opacity: 0.85;
}

/* Round 31h — the SLA date, directly under the job number it belongs to.
   Quiet: it is a deadline to be aware of, not the thing being decided. */
.wa-card-sla { font-size: 0.72rem; font-weight: 600; opacity: 0.85; letter-spacing: 0.02em; }
/* Round 31i — the "needs no permit" tick, greyed when BT has already
   answered. Faded, not hidden: somebody looking for it should find it and
   read why it is not theirs to set. */
.wa-no-permit-locked { opacity: 0.65; }
/* Round 31j — linesearch and utility drawing expiries on the card. Quiet
   until one of them has run out before the day the gang is due, which is
   a wasted visit and should look like one. */
/* Round 31l — the contract filter, and the line that owns up to it. The
   note is deliberately loud: the lesson from round 28's filter is that a
   board hiding work must say so where somebody will read it. */
/* Round 31o — the week's controls and the order search share one card.
   The search is given a fixed-ish column rather than half the width: it is
   a narrow thing (one input and three short columns) and the controls on
   the left are what people actually use. Stacks at narrow widths. */
.wa-week-head { display: flex; flex-wrap: wrap; gap: 8px 28px; align-items: flex-start; }
.wa-week-head-left { flex: 1 1 420px; min-width: 0; }
.wa-week-head-right { flex: 0 1 320px; min-width: 0; }
.wa-find-head { margin: 0 0 6px; font-size: 0.95rem; }
.wa-find-label { display: block; margin-bottom: 8px; }
.wa-find-label input { width: 100%; }
/* Capped and scrolling, so a search that finds twenty rows cannot push the
   board itself off the bottom of the screen. */
.wa-find-results { max-height: 200px; overflow: auto; }
.wa-find-results .data-table { font-size: 0.82rem; }

/* Round 31z — the one-click answers on the day-marker pop-up. */
.wa-marker-suggestions { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 4px; }
/* Round 32 — one line per reason, each removable on its own. */
/* Round 32f — the export size picker. Each option explains itself,
   because "Summary" and "Standard" mean nothing on their own. */
.wa-export-style { align-items: flex-start; gap: 8px; padding: 6px 0; }
.wa-export-style input { margin-top: 3px; }

.wa-marker-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.wa-marker-row input { flex: 1 1 auto; }

.wa-contract-filter { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 4px 0 8px; }
.wa-contract-filter-lbl { font-weight: 600; }
.wa-contract-boxes { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.wa-filter-note {
  margin: 0 0 8px; padding: 6px 10px; border-radius: 6px;
  background: #fff4e5; color: #7a4a00; border: 1px solid #ffcc80;
  font-size: 0.85rem; font-weight: 600;
}
.wa-card-searches { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.wa-search {
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.02em;
  padding: 0 4px; border-radius: 3px; opacity: 0.8;
  background: rgba(0,0,0,0.07);
}
.wa-search-expired { background: #b71c1c; color: #fff; opacity: 1; }
/* On a plain (unpermitted) card the white badge would vanish. */
.wa-card.wa-permit-waiting .wa-sent-yes { background: #dcfce7; color: #14532d; }

/* Round 31b: every day a job is on, when replanning it. A long job is
   twelve days across several weeks, so this is a list to tick rather than
   one day to choose — and the day you came from is already ticked, which
   is what makes deleting act on the right date without being asked. */
.replan-days-list {
  max-height: 220px;
  overflow: auto;
  margin: 6px 0 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
}
.replan-day { display: flex; align-items: center; gap: 6px; padding: 2px 0; font-size: 0.9em; }

/* Where each ticked day is going — one row per day, its own date and its
   own gang. Scrolls for the same reason the day list does: a three-week
   job should not push Save off the bottom of the pop-up. */
.replan-dest-rows {
  border-top: 1px solid var(--border);
  margin: 10px 0;
  padding-top: 10px;
  max-height: 240px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.replan-dest-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.88em; }
.replan-dest-from { flex: 1 1 200px; font-weight: 600; color: var(--navy); }
.replan-dest-row input[type="date"], .replan-dest-row select {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 7px;
  font-size: 0.95em;
}
.replan-dest-row select { max-width: 240px; }
/* Which slot it lands in, worked out as you change the row rather than
   discovered after saving. */
.replan-dest-slot { color: var(--grey); font-size: 0.92em; white-space: nowrap; }
.replan-day-done { opacity: 0.6; }

/* Round 33b — a task's attachments and the forms filled in against it.
   Both are lists inside a table cell, so they need their bullets and
   their margins taken off or the row grows a blank line at each end. */
.task-files,
.task-forms {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}
.task-files li,
.task-forms li { font-size: 12px; line-height: 1.5; }
.task-files a { color: var(--action); }

/* Round 33f — who a note was emailed to, under the note itself. */
.note-sent { font-size: 11px; margin-top: 3px; }

/* Round 33g — dropping a file onto a note.
   The dashed box is the affordance: a file input alone gives no clue that
   dragging is possible at all, which is why people drag at Outlook and
   nothing happens. */
.note-dropzone {
  border: 2px dashed var(--border);
  border-radius: 8px;
  padding: 14px 16px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: background 0.12s, border-color 0.12s;
}
.note-dropzone.is-over { border-color: var(--action); background: var(--grey-light); }
.note-dropzone .muted { font-size: 12px; line-height: 1.45; }
.note-file { display: inline-block; margin-right: 10px; }
.note-file-remove {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--red);
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
}
