/* ============================================================
   enviroHUB — Application Stylesheet
   Enviroair design system — desktop portal
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');

:root {
  --ea-green-900: #2f4a23;
  --ea-green-800: #3d6230;
  --ea-green-700: #4a763b;
  --ea-green-600: #578842;
  --ea-green-500: #6a9d54;
  --ea-green-400: #88b573;
  --ea-green-300: #adcc9c;
  --ea-green-200: #d0e2c5;
  --ea-green-100: #e8f0e2;
  --ea-green-50:  #f4f8f1;

  --ea-charcoal-900: #1f2022;
  --ea-charcoal-800: #2c2d2f;
  --ea-charcoal-700: #3a3b3d;
  --ea-charcoal-600: #4f5052;
  --ea-charcoal-500: #6e6f72;
  --ea-charcoal-400: #909195;
  --ea-charcoal-300: #b6b7ba;
  --ea-charcoal-200: #d5d6d8;
  --ea-charcoal-100: #ececed;
  --ea-charcoal-50:  #f6f6f7;

  --ea-sky-600:   #2b80b8;
  --ea-sky-100:   #e2f1f9;
  --ea-sky-50:    #f1f8fc;
  --ea-amber-600: #c6822a;
  --ea-amber-100: #fcefd6;
  /* --ea-amber-50 was referenced by several chips before it existed. An undefined custom
     property makes the whole declaration invalid, so those chips rendered with no background
     at all rather than a pale one. Defined 2026-08-10 — the chips gain their intended tint. */
  --ea-amber-50:  #fef8ec;
  --ea-red-600:   #c8362d;
  --ea-red-100:   #fbe4e2;
  --ea-red-50:    #fdf3f2;
  --ea-white:     #ffffff;
  --ea-paper:     #fafaf8;

  --bg-page:    var(--ea-paper);
  --bg-surface: var(--ea-white);
  --bg-subtle:  var(--ea-charcoal-50);
  --bg-muted:   var(--ea-charcoal-100);
  --fg-primary:   var(--ea-charcoal-900);
  --fg-secondary: var(--ea-charcoal-600);
  --fg-tertiary:  var(--ea-charcoal-500);
  --fg-muted:     var(--ea-charcoal-400);
  --fg-brand:     var(--ea-green-700);
  --fg-danger:    var(--ea-red-600);
  --accent:       var(--ea-green-600);
  --border-subtle:  var(--ea-charcoal-100);
  --border-default: var(--ea-charcoal-200);

  --font-display: 'Manrope', system-ui, -apple-system, sans-serif;
  --font-body:    'Manrope', system-ui, -apple-system, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, monospace;

  --shadow-sm: 0 1px 2px rgba(31,32,34,0.06), 0 1px 3px rgba(31,32,34,0.05);
  --shadow-md: 0 2px 4px rgba(31,32,34,0.06), 0 4px 12px rgba(31,32,34,0.06);
  --ring-brand: 0 0 0 3px rgba(87,136,66,0.32);

  --hub-sidebar-w: 224px;
  --hub-topbar-h: 80px;
}

/* ============================================================
   Reset
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg-primary);
  background: var(--bg-page);
  -webkit-font-smoothing: antialiased;
}
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; }
a { color: var(--fg-brand); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ============================================================
   Screen visibility
   ============================================================ */
.hub-page   { display: none; }
.hub-page.active { display: flex; }
.hub-screen { display: none; }
.hub-screen.active { display: block; }

/* ============================================================
   LOGIN
   ============================================================ */
.login-screen {
  min-height: 100vh;
  width: 100%;
  align-items: center;
  justify-content: center;
  background: var(--bg-page);
  padding: 24px;
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: 12px;
  padding: 40px;
  box-shadow: var(--shadow-md);
}

.login-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 32px;
}

.login-logo-mark {
  width: 48px; height: 48px;
  border-radius: 10px;
  display: block; flex-shrink: 0;
}

.login-logo-name {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--fg-primary);
}
.login-logo-name span { color: var(--ea-green-600); }

.login-card h1 {
  font-size: 20px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 4px;
}

.login-card p { font-size: 13px; color: var(--fg-secondary); margin-bottom: 28px; }

.login-error {
  background: var(--ea-red-100); color: var(--ea-red-600);
  font-size: 13px; font-weight: 600;
  padding: 10px 14px; border-radius: 6px; margin-bottom: 16px;
  display: none;
}
.login-error.show { display: block; }

/* ============================================================
   FORM CONTROLS
   ============================================================ */
.field-group {
  display: flex; flex-direction: column; gap: 5px; margin-bottom: 16px;
}
.field-group label {
  font-size: 12px; font-weight: 600; color: var(--fg-secondary); letter-spacing: 0.02em;
}
.field-hint {
  font-size: 11px; color: var(--text-muted); line-height: 1.5; margin: 0;
}
/* Job picker dropdown. The markup and behaviour have existed since the resource, defect
   and follow-up screens were built; the styles never did, so the results rendered as a
   plain unstyled block pushing the rest of the modal down. Added 2026-09-06 with the ITP
   screen, which uses the same picker. */
.job-picker-results {
  max-height: 240px; overflow-y: auto;
  border: 1px solid var(--border-default); border-radius: 6px;
  background: var(--bg-surface); margin-top: 4px;
  box-shadow: 0 4px 14px rgba(0,0,0,.08);
}
.job-picker-item {
  padding: 8px 12px; cursor: pointer; font-size: 13px; line-height: 1.4;
  border-bottom: 1px solid var(--border-subtle, var(--border-default));
}
.job-picker-item:last-child { border-bottom: none; }
.job-picker-item:hover { background: var(--ea-green-50, #f4f8f1); }
.field-input, .field-select, .field-textarea {
  width: 100%; height: 38px; padding: 0 12px;
  border: 1px solid var(--border-default); border-radius: 6px;
  background: var(--bg-surface); color: var(--fg-primary);
  font-size: 14px; outline: none;
  transition: border-color 120ms, box-shadow 120ms;
}
.field-input:focus, .field-select:focus, .field-textarea:focus {
  border-color: var(--ea-green-600); box-shadow: var(--ring-brand);
}
.field-textarea { height: auto; min-height: 80px; padding: 10px 12px; resize: vertical; }

/* ============================================================
   TOOLTIPS — `data-tip`, and it is the Hub-wide way
   ============================================================

   ⚠️ **A browser's own tooltip appears below and right of the pointer, and the pointer covers
   it.** James, 2026-09-17: *"when hovering over the row buttons, the pop-up text is obscured by
   the mouse pointer… make the new hover behaviour (pop-up text) a hub-wide design rule."*
   A `title` tooltip cannot be moved, so we draw our own, ABOVE the control — the one place the
   cursor never is.

   **Use it like this:**

       <button data-tip="Remove this line">×</button>
       <button data-tip-right data-tip="Remove this block">×</button>

   - `data-tip` is the text. Escape it like any other attribute.
   - `data-tip-right` anchors the bubble to the control's right edge instead of its left. Use it
     for anything sitting near the right-hand edge of its container, or the bubble hangs off.
   - It appears on **focus** as well as hover, so it is reachable from the keyboard.
   - ⚠️ **Do not use both `title` and `data-tip`** on one element: the browser's own tooltip will
     appear underneath ours and cover it, which is the fault this exists to fix.
   - The control needs to be able to hold an absolutely positioned child, so anything using it
     gets `position: relative`. That is set here; do not set it again.
   - ⚠️ **Never inside a box with `overflow: hidden`** — the bubble is clipped and simply does
     not appear. Check the cell or card it lives in. */
[data-tip] { position: relative; }
[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 7px); left: 0;
  background: var(--ea-charcoal-800); color: #fff;
  font-size: 11px; font-weight: 500; font-style: normal; line-height: 1.4;
  letter-spacing: 0; text-transform: none; text-align: left;
  padding: 5px 8px; border-radius: 4px;
  white-space: normal; width: max-content; max-width: 280px;
  z-index: 60; pointer-events: none;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .2);
}
[data-tip-right][data-tip]:hover::after,
[data-tip-right][data-tip]:focus-visible::after { left: auto; right: 0; }

/* ============================================================
   BUTTONS
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 36px; padding: 0 16px;
  border-radius: 6px; border: 1px solid transparent;
  font-size: 13px; font-weight: 600; letter-spacing: -0.01em;
  transition: background-color 120ms, color 120ms, border-color 120ms, box-shadow 120ms; white-space: nowrap; cursor: pointer;
}
.btn svg { flex-shrink: 0; }
.btn-primary  { background: var(--ea-green-600); color: #fff; }
.btn-primary:hover { background: var(--ea-green-700); }
.btn-secondary { background: var(--bg-surface); color: var(--fg-primary); border-color: var(--border-default); }
.btn-secondary:hover { background: var(--bg-subtle); }
.btn-ghost { background: transparent; color: var(--fg-secondary); }
.btn-ghost:hover { background: var(--bg-subtle); color: var(--fg-primary); }
.btn-sm  { height: 30px; padding: 0 12px; font-size: 12px; }
.btn-lg  { height: 42px; padding: 0 20px; font-size: 14px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ============================================================
   APP SHELL
   ============================================================ */
.app-shell {
  width: 100%; height: 100vh; overflow: hidden;
  flex-direction: row;
}

/* Sidebar */
.sidebar {
  width: var(--hub-sidebar-w); flex-shrink: 0;
  background: var(--ea-charcoal-800);
  display: flex; flex-direction: column;
  overflow-y: auto;
}

.sidebar-logo {
  height: var(--hub-topbar-h);
  padding: 0 18px;
  display: flex; align-items: center; gap: 10px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
  flex-shrink: 0;
}
.sidebar-logo-mark {
  width: 38px; height: 38px;
  border-radius: 8px; flex-shrink: 0; display: block;
}
.sidebar-logo-name {
  font-size: 17px; font-weight: 700; letter-spacing: -0.02em; color: #fff;
}
.sidebar-logo-name span { color: var(--ea-green-400); }

.sidebar-nav { flex: 1; padding: 12px 8px; }

.nav-section-label {
  font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: rgba(255,255,255,0.3); padding: 10px 10px 4px;
}

.nav-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 10px; border-radius: 7px;
  border: none; background: transparent;
  color: rgba(255,255,255,0.65);
  font-size: 13px; font-weight: 500;
  cursor: pointer; text-align: left;
  transition: background 120ms, color 120ms; margin-bottom: 1px;
}
.nav-item svg { flex-shrink: 0; stroke: currentColor; }
.nav-item:hover { background: rgba(255,255,255,0.07); color: #fff; }
.nav-item.active { background: rgba(87,136,66,0.25); color: var(--ea-green-300); }
.nav-item.active svg { stroke: var(--ea-green-400); }
.nav-badge {
  margin-left: auto; background: var(--ea-green-500); color: #fff;
  border-radius: 20px; font-size: 10px; font-weight: 700;
  min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
}

.sidebar-footer {
  padding: 12px; border-top: 1px solid rgba(255,255,255,0.07);
}
.sidebar-user {
  display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 7px;
}
.sidebar-user-avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--ea-green-700);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff; flex-shrink: 0;
}
.sidebar-user-info { flex: 1; min-width: 0; }
.sidebar-user-name {
  font-size: 12px; font-weight: 600; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar-user-role { font-size: 11px; color: rgba(255,255,255,0.45); }
.sidebar-logout {
  width: 28px; height: 28px; border-radius: 6px;
  background: transparent; border: none; color: rgba(255,255,255,0.4);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0; transition: background 120ms, color 120ms;
}
.sidebar-logout:hover { background: rgba(255,255,255,0.1); color: #fff; }

/* Main */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }

.topbar {
  height: var(--hub-topbar-h); flex-shrink: 0;
  background: var(--bg-surface); border-bottom: 1px solid var(--border-subtle);
  display: flex; align-items: center; padding: 0 28px; gap: 12px;
}
.topbar-title-group { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.topbar-title { font-size: 22px; font-weight: 800; letter-spacing: -0.03em; line-height: 1.2; }
.topbar-subtitle { font-size: 13px; color: var(--fg-secondary); margin-top: 6px; }
.topbar-subtitle:empty { display: none; }
.topbar-actions { display: flex; align-items: center; gap: 8px; }

.content { flex: 1; overflow-y: auto; padding: 28px; }

/* ============================================================
   DASHBOARD
   ============================================================ */
.stat-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 16px; margin-bottom: 28px;
}
.stat-card {
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: 10px; padding: 20px 24px; box-shadow: var(--shadow-sm);
}
.stat-card-label {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg-tertiary); margin-bottom: 8px;
}
.stat-card-value {
  font-family: var(--font-display);
  font-size: 36px; font-weight: 800; letter-spacing: -0.03em;
  color: var(--fg-primary); line-height: 1; margin-bottom: 4px;
}
.stat-card-sub { font-size: 12px; color: var(--fg-tertiary); }
.stat-card.danger  .stat-card-value { color: var(--ea-red-600); }
.stat-card.warning .stat-card-value { color: var(--ea-amber-600); }
.stat-card.ok      .stat-card-value { color: var(--ea-green-600); }
.stat-card.danger  { border-color: var(--ea-red-200, #fecaca); }
.stat-card.warning { border-color: var(--ea-amber-200, #fde68a); }
.stat-card.clickable { cursor: pointer; transition: box-shadow 120ms, border-color 120ms; }
.stat-card.clickable:hover { box-shadow: var(--shadow-md, 0 4px 12px rgba(0,0,0,.1)); border-color: var(--border-default); }
.stat-card-overdue { font-size: 12px; color: var(--ea-red-600); font-weight: 600; margin-top: 2px; }

/* ============================================================
   PAGE HEADER
   ============================================================ */
.page-header { margin-bottom: 24px; }
.page-header h1 {
  font-size: 22px; font-weight: 800; letter-spacing: -0.03em;
}
.page-header p { font-size: 13px; color: var(--fg-secondary); margin-top: 2px; }

/* Section header */
.section-header {
  display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px;
}
.section-header h2 { font-size: 14px; font-weight: 700; letter-spacing: -0.01em; }

/* ============================================================
   CARD
   ============================================================ */
.card {
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: 10px; box-shadow: var(--shadow-sm);
}

/* ============================================================
   RECENT ACTIONS LIST
   ============================================================ */
.action-list { list-style: none; }
.action-item {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 14px 20px; border-bottom: 1px solid var(--border-subtle);
}
.action-item:last-child { border-bottom: none; }
.action-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ea-sky-600); margin-top: 5px; flex-shrink: 0;
}
.action-dot.overdue  { background: var(--ea-red-600); }
.action-dot.complete { background: var(--ea-green-600); }
.action-body { flex: 1; min-width: 0; }
.action-desc {
  font-size: 13px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.action-meta { font-size: 12px; color: var(--fg-muted); margin-top: 2px; font-family: var(--font-mono); }

/* ============================================================
   TOOLBAR
   ============================================================ */
.toolbar {
  display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap;
}
.search-wrap { position: relative; flex: 1; min-width: 200px; max-width: 320px; }
.search-wrap svg {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  pointer-events: none; stroke: var(--fg-muted);
}
.search-input {
  width: 100%; height: 34px; padding: 0 12px 0 34px;
  border: 1px solid var(--border-default); border-radius: 6px;
  background: var(--bg-surface); color: var(--fg-primary); font-size: 13px;
  outline: none; transition: border-color 120ms, box-shadow 120ms;
}
.search-input:focus { border-color: var(--ea-green-600); box-shadow: var(--ring-brand); }

.filter-tabs {
  display: flex; gap: 2px;
  background: var(--bg-subtle); border: 1px solid var(--border-subtle);
  border-radius: 7px; padding: 3px;
}
.filter-tab {
  padding: 5px 12px; border: none; border-radius: 5px;
  background: transparent; font-size: 12px; font-weight: 600;
  color: var(--fg-secondary); cursor: pointer; transition: background 120ms, color 120ms, box-shadow 120ms; white-space: nowrap;
}
.filter-tab:hover { color: var(--fg-primary); }
.filter-tab.active { background: var(--bg-surface); color: var(--fg-primary); box-shadow: var(--shadow-sm); }

/* ============================================================
   TABLE
   ============================================================ */
.table-wrap {
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: 10px; box-shadow: var(--shadow-sm); overflow: hidden;
}
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
  padding: 11px 16px; text-align: left;
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg-muted); background: var(--bg-subtle);
  border-bottom: 1px solid var(--border-subtle); white-space: nowrap;
}
tbody tr { border-bottom: 1px solid var(--border-subtle); transition: background 80ms; }
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--bg-subtle); }
tbody td { padding: 11px 16px; vertical-align: middle; }
/* ── Tenders register ───────────────────────────────────────────────────────
   ⚠️ **One table row per INVITATION, with the tender spanning them.**

   The first attempt stacked each builder and its date inside one cell. James, 2026-09-14:
   *"the table is now messy as columns dont align. Who invited should be a separate column
   then when closing should be the column to the right of that so all dates align."* He is
   right, and a stacked cell cannot be fixed with padding — "Watts & Hughes Construction" is
   wider than "Hawkins", so its date starts further right and nothing lines up. Only real
   columns align, so `rowspan` carries the project, client and status down the invitations.

   Consequences worth knowing before editing:
   - Cells that span must be `vertical-align: top`, or a three-invitation tender centres its
     own name against the middle builder.
   - Borders are drawn per CELL, not per row: a row inside a tender has none, and the last
     row of each tender carries the divider. `tbody tr` gets its border removed here for that.
   - Row hover has to cover the whole tender, so it is keyed on a class the renderer puts on
     every row of one group rather than on `tr:hover`. */
.tnd-table tbody tr { border-bottom: none; }
.tnd-table tbody tr:hover { background: transparent; }
.tnd-table tbody td { vertical-align: top; padding: 10px 16px; }
.tnd-table tbody tr.tnd-last > td { border-bottom: 1px solid var(--border-subtle); }
/* ⚠️ **The spanning cells need the line too, or it stops halfway across.**
   The divider is drawn on the last row of a tender, but with `rowspan` the project, client and
   status cells belong to the group's FIRST row — so on any tender with more than one
   invitation those three columns had no border and the line ran under the last three columns
   only. A rowspan cell's bottom edge is already at the bottom of the group, so bordering it
   lands the missing segment exactly where the rest of the line is. */
.tnd-table tbody td[rowspan] { border-bottom: 1px solid var(--border-subtle); }
/* No line under the last tender — the card's own edge is the line there. Keyed on the whole
   group rather than on `:last-child`, because the cells to suppress are in its first row. */
.tnd-table tbody tr.tnd-end > td { border-bottom: none; }
.tnd-table tbody tr.tnd-hot > td { background: var(--bg-subtle); }
/* Dates line up digit for digit, which is the whole point of the column. */
.tnd-table td.tnd-when {
  font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 12.5px;
}
.tnd-table td.tnd-who { font-weight: 600; }
.tnd-table td.tnd-state { white-space: nowrap; }
.tnd-table .tnd-left { color: var(--fg-muted); font-size: 11.5px; margin-left: 8px; }

/* ── One tender, as a screen ────────────────────────────────────────────────
   It was a modal through stage 1. A tender now carries invitations, a notebook, a document
   register, the findings and the work breakdown, with the supplier responses, the pricing
   worksheet and the letter still to come — and a pricing worksheet inside a 94vw box with a
   scrolling body is the wrong shape. James asked for a screen with tabs; the roadmap's
   principle 7 already said something that wants the whole viewport should be one. */
/* ⚠️ **Kept tight on purpose.** James, 2026-09-17, on a lower-resolution screen: this row and
   the tab strip under it were spending 30px of height on white space that bought nothing, and
   every pixel here is a line of the worksheet below. It carries one line of small controls —
   it does not need report spacing. */
.tnd-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-bottom: 7px; margin-bottom: 8px;
  border-bottom: 1px solid var(--border-subtle);
}
.tnd-head .td-secondary { font-size: 12.5px; }
#screen-tender .filter-tabs { margin-bottom: 10px; }

/* ── Where the tender is up to ──────────────────────────────────────────────
   ⚠️ The tabs were always in the order the job runs and never said so. A tick on what is
   finished, a dot on the step that wants you, and the ones that cannot be started yet go
   QUIET — never disabled, because a step you cannot start is still worth reading, and a
   control that refuses to do anything explains nothing about why.
   Nothing is added to any screen: an earlier attempt put a next-action line on the front page
   and made it busier, which was the opposite of the point. */
#tnd-detail-tabs .filter-tab { display: inline-flex; align-items: center; gap: 5px; }
#tnd-detail-tabs .filter-tab.tnd-step-waiting:not(.active) { color: var(--fg-muted); }
.tnd-step-tick { color: var(--ea-green-600); font-size: 11px; font-weight: 700; line-height: 1; }
.tnd-step-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--ea-amber-600); display: inline-block;
}

/* The work breakdown.
   ⚠️ Two levels, and the indent is doing real work: a SECTION organises the estimate and the
   letter, a BLOCK is what one supplier is asked to price. Four suppliers may price one block
   while one supplier prices two, so the blocks have to read as the countable things. */
.tnd-sec {
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  margin-bottom: 12px; overflow: hidden;
}
.tnd-sec-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; background: var(--bg-subtle);
  border-bottom: 1px solid var(--border-subtle);
}
.tnd-sec-head h5 { margin: 0; font-size: 13.5px; font-weight: 650; }
/* ⚠️ A block is ONE LINE, and the line opens it. Everything known about a block — the notes,
   the specified supplier's small print, the sentence lifted out of the document — used to be
   stacked inside its row, which is what you want while settling that one block and noise on
   every other row at the same time. Read at a glance, edit on click. */
.tnd-blk {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px 8px 30px; min-height: 38px;
  border-bottom: 1px solid var(--border-subtle);
  cursor: pointer; transition: background 120ms;
}
.tnd-blk:hover { background: var(--bg-subtle); }
/* The "no blocks yet" line is a message, not a row — it opens nothing. */
.tnd-blk-empty { cursor: default; }
.tnd-blk-empty:hover { background: transparent; }
.tnd-sec .tnd-blk:last-child { border-bottom: none; }
.tnd-blk-name {
  font-weight: 600; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tnd-blk-right { margin-left: auto; display: flex; align-items: center; gap: 6px; flex: none; }
/* The specified-supplier chip was dropped on 2026-09-17 with the block modal. It is the one
   thing on that modal worth bringing back — the Tauranga coil-coating safeguard — and when it
   returns it belongs on the sheet, not behind a form. */

/* Proposed, and not yet looked at by a person. Same amber and the same meaning as an
   unconfirmed closing date: read it, then keep it or bin it. Nothing downstream should treat
   an unaccepted row as part of the breakdown. */
/* ── The pricing worksheet ──────────────────────────────────────────────────
   Shaped like Bruce's spreadsheet on purpose: matching what he already knows is what makes
   moving off it survivable.

   ⚠️ It first shipped as a card per section, and on Rotorua's real HVAC book — 24 sections
   holding 61 lines — that was 5,737px of page for 61 rows of figures, two sections on the
   first screen. One continuous sheet with the section headings inside it measures 2,178px
   and shows eight. Both measured against that tender before this was written; do not go
   back to a card per section. */

/* The band: one line. Every figure, the three rates, and the two buttons. */
/* ⚠️ **The band locks to the top with the column header beneath it.** James, 2026-09-17: *"we
   dont need the fixed HVAC total row at the bottom but we do need to lock the other header info
   (totals, rates mark-up etc)"* — which is the right way round: the totals and the rates are
   what you check a figure against while typing it, and the trade total at the foot is what you
   read once at the end.
   ⚠️ No margin under it. A transparent gap between two sticky things is a letterbox that the
   rows scroll through, which is exactly the fault this replaced. */
/* ── the strip above the band: trade tabs on the left, Back up and Excel on the right ──
   ⚠️ **The buttons are NOT inside the tab pill box.** A solid button dropped into a pill bar
   reads as a fourth tab that happens to look different. They sit beside it on the same row,
   which is also why the row survives a single-trade tender with no tabs at all.
   Its own margin because `#screen-tender .filter-tabs` no longer reaches it — the pills are a
   child here, not the row itself. */
#screen-tender .wsx-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px;
}
#screen-tender .wsx-strip .filter-tabs { margin-bottom: 0; }
.wsx-strip-right {
  margin-left: auto; display: flex; align-items: center; gap: 7px; font-size: 12.5px;
}

.wsx-band {
  position: sticky; top: var(--wsx-stick-top, 0px); z-index: 6;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 7px 10px 8px; margin-bottom: 0;
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
/* ⚠️ **Each figure is a TILE.** James, 2026-09-17. These are the numbers you check a line
   against while typing it, so they are read at a glance and out of the corner of the eye — a
   row of small grey text is not. Stacked label over value, boxed, and about a tenth larger than
   the run of text they replaced. */
.wsx-fig {
  display: flex; flex-direction: column; gap: 1px; white-space: nowrap;
  padding: 3px 9px; border-radius: var(--radius-sm);
  background: var(--ea-charcoal-50); border: 1px solid var(--border-subtle);
}
.wsx-fig-l { font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--fg-tertiary); }
.wsx-fig-v { font-size: 14.5px; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.15; }
/* The one figure the whole screen exists to produce. */
.wsx-fig-total { background: var(--ea-green-50); border-color: var(--ea-green-300); }
.wsx-fig-total .wsx-fig-l { color: var(--ea-green-800); }
.wsx-fig-big { font-size: 17px; font-weight: 800; color: var(--ea-green-800); }
.wsx-sep { width: 1px; align-self: stretch; background: var(--border-subtle); }
.wsx-spacer { margin-left: auto; }
.wsx-note { font-size: 11.5px; color: var(--fg-tertiary); white-space: nowrap; }
.wsx-rate { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--fg-tertiary); white-space: nowrap; }
/* Typed into often enough to be worth a real target — slightly larger than the cells on the
   sheet, because a rate is set once for a whole trade and getting it wrong moves every line. */
.wsx-rate input {
  width: 62px; text-align: right; font-variant-numeric: tabular-nums;
  padding: 4px 7px; font-size: 13px; font-family: inherit; color: var(--fg-primary);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-surface);
}

/* Folded shut — twenty of them pushed the first figure below the fold — but the count is on
   the summary, so a closed panel still says how much is waiting. */
.wsx-checks {
  border: 1px solid var(--ea-amber-400, #fbbf24); background: var(--ea-amber-50, #fffbeb);
  border-radius: var(--radius-md); margin-bottom: 6px; font-size: 12.5px;
}
.wsx-checks > summary { padding: 5px 10px; cursor: pointer; font-weight: 650; }
.wsx-checks-body { padding: 0 10px 7px 22px; }
.wsx-checks-body > div { padding: 1px 0; }

/* ⚠️ The sheet scrolls in its OWN frame, not with the page. A sticky header inside an
   overflow box sticks to that box — build it the other way round and the header parks itself
   over the first rows at rest. Scrolling here also keeps the totals and the rates in view
   while you work down sixty lines, which is what a frozen pane is for. */
/* ⚠️ **ONE scrollbar, the page's.** The sheet used to scroll inside its own frame, which put a
   second bar down the middle of the screen and — worse — laid it over the last column, so the
   duplicate and remove buttons sat behind it and could not be clicked. James, 2026-09-17: *"the
   copy and remove row buttons are hidden behind the vertical scroll bar… I have double scroll
   bars, need to reformat so there is only one."*
   ⚠️ `overflow-x: auto` would not do: CSS computes the other axis to `auto` as soon as one is
   not `visible`, so asking for a sideways bar brings the vertical one back. The table is a
   fixed layout at `width: 100%`, so it never needs to scroll sideways anyway. */
.wsx-wrap {
  overflow: visible;
  border: 1px solid var(--border-default); border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--bg-surface);
}
/* ⚠️ **FIXED LAYOUT. The columns are decided by the header, never by the content.** Left to
   size itself the browser reads every cell in the table, including the wide `colspan` heading
   rows, and hands the spare width to whichever column it likes — on 2026-09-17 that blew the
   Item column out to five times its width and pushed every description into the middle of the
   sheet. A worksheet whose columns move when you type in it is not a worksheet.
   ⚠️ Anything longer than its column now clips rather than widening it, so every cell that can
   hold long text says how it handles the overflow. */
.wsx {
  width: 100%; table-layout: fixed;
  border-collapse: separate; border-spacing: 0; font-size: 12px; line-height: 1.25;
}

/* Gridlines both ways. This is the thing that makes it read as a sheet rather than a list. */
.wsx th, .wsx td { border-right: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle); }
.wsx th:last-child, .wsx td:last-child { border-right: none; }

/* ⚠️ Pinned directly BENEATH the band, whose height is measured after every redraw and written
   into `--wsx-band-h`. Guessing it in CSS would be wrong the moment the band wraps onto two
   lines, which it does on a narrow window. */
.wsx thead th {
  position: sticky; top: var(--wsx-band-h, 0px); z-index: 4;
  background: var(--bg-subtle); text-align: left;
  font-weight: 600; font-size: 10px; text-transform: uppercase; letter-spacing: .03em;
  /* ⚠️ **The column titles WRAP to two lines.** James, 2026-09-17: "Material Cost becomes
     Material, with 'cost' below". The columns are a fixed width, so a title that cannot wrap
     is a title that gets cut off — and a header reading "LABOUR RAT" is worse than one that
     takes a second line. Aligned to the bottom so a one-word title sits on the same baseline
     as the second line of a two-word one. */
  color: var(--fg-secondary); padding: 4px 6px; white-space: normal;
  line-height: 1.15; vertical-align: bottom;
  border-bottom: 1px solid var(--border-default);
}
.wsx thead th.num { text-align: right; width: 76px; }

.wsx td { padding: 0 4px; height: 22px; }
.wsx input, .wsx select {
  width: 100%; border: 1px solid transparent; background: transparent;
  padding: 1px 3px; font: inherit; color: inherit; border-radius: 2px;
}
.wsx input:hover, .wsx select:hover { border-color: var(--border-subtle); }
.wsx input:focus, .wsx select:focus {
  border-color: var(--ea-green-600, #578842); background: var(--bg-surface); outline: none;
}
.wsx td.num input { text-align: right; font-variant-numeric: tabular-nums; }
/* Computed columns are not typed into, and must not look as if they could be. */
.wsx td.calc {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--fg-secondary); padding-right: 8px;
}
.wsx td.calc.sell { color: var(--fg-primary); font-weight: 600; }
/* A line with no quantity is not in any total. Saying so quietly beats it looking priced.
   ⚠️ **Dim the number, never the cell.** This was `opacity: .35`, which fades a cell's BORDERS
   along with its contents — so on a sheet that is mostly not priced yet the gridlines vanished
   from Hours, Material, Labour and Sell and nowhere else, because `.calc` is only on those
   four. Reported 2026-09-17 on a tender with 30 of 35 lines unpriced. Colour dims the figure
   and leaves the grid alone. */
.wsx tr.unpriced td.calc,
.wsx tr.unpriced td.calc.sell { color: var(--fg-muted); font-weight: 400; }
.wsx .wsx-sup { color: var(--fg-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 110px; }

/* Item is narrow and Description takes whatever is left — the only column with no width of
   its own, because it is the only one whose content has no bound. */
.wsx-w-item { width: 64px; } .wsx-w-desc { width: auto; }
/* The Supplier column was removed 2026-09-17 — James: "why is the Supplier column necessary?"
   It was only ever filled by choosing a supplier's price, and each price now names its own
   supplier on its row, so it was the same fact twice. Its 100px went back to the description. */
/* "How arrived at" — counted / scaled / allowance — was removed 2026-09-17. James: "I dont
   see the value of this field". It was in no total and no screen read it back. */
.wsx-w-blk  { width: 110px; }

/* ⚠️ **Every column except the description is given a width.** Left to itself the browser
   handed the description almost the whole table and squeezed the rest, so a supplier called
   "Industrial Sheetmetals" broke over two lines while a thousand pixels sat empty beside it.
   James, 2026-09-17: "adjust column widths so there is less wrapping". The description is the
   only column that takes what is left, because it is the only one whose content has no bound. */
.wsx thead th:nth-child(5),  .wsx thead th:nth-child(6)  { width: 58px; }
.wsx thead th:nth-child(7)  { width: 88px; }
.wsx thead th:nth-child(8)  { width: 58px; }
.wsx thead th:nth-child(9)  { width: 64px; }
.wsx thead th:nth-child(10) { width: 58px; }
.wsx thead th:nth-child(11), .wsx thead th:nth-child(12) { width: 88px; }
.wsx thead th:nth-child(13) { width: 96px; }
.wsx thead th:nth-child(14) { width: 62px; }
/* A heading is a name, not prose. Wrapping one reads as two rows and breaks the scan down the
   left-hand edge, which is how the structure of the sheet is read at all — so a long one clips,
   now that the table no longer widens itself to fit. */
/* ⚠️ A heading clips rather than widening the table — but NOT the cell that holds the row's
   buttons, because `overflow: hidden` there would swallow their tooltips. */
.wsx tr.wsx-sec td, .wsx tr.wsx-blk td {
  white-space: nowrap; overflow: hidden;
}
.wsx tr.wsx-blk td.wsx-end, .wsx tr.wsx-sec td.wsx-end { overflow: visible; }
/* ⚠️ **A supplier's price lines up with a supplier's LINE.** The two rows say the same kind of
   thing — this supplier, this price, choose it or not — so they start in the same place. The
   indent was missing here, and a leftover rule from the old markup zeroed the radio's margin so
   the name sat hard against the button while a line's did not. James, 2026-09-17. */
.wsx-alt-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: 10px; }
/* Every cell that can hold long text says how it overflows, because a fixed table clips rather
   than growing. A description reads to the edge of its column and stops. */
.wsx td.wsx-cell-top, .wsx td.wsx-cell-opt { overflow: hidden; }
.wsx-inline { max-width: 100%; }
.wsx-inline input { min-width: 0; }

/* ── the grip ──────────────────────────────────────────────────────────────────
   The order of the sheet is the order of the estimate and the letter, and nothing else
   records it. One grip on every heading, block and line.
   ⚠️ Visible at rest on a heading, on hover on a line. A heading is dragged rarely and has to
   be findable; a line is dragged often by somebody who already knows where the grip is, and a
   column of dots down sixty rows is noise. */
.wsx-w-grip { width: 16px; }
/* ⚠️ **Hidden with COLOUR, not opacity — and this rule broke its own warning.** The note two
   rules below has said since it was written that opacity on a table cell fades its BORDERS
   too, and the grip column was doing exactly that: `opacity: 0` at rest took the gridlines
   with it, so the left-hand column had no row lines and no column line down sixty rows while
   every other column did. James, 2026-09-17: *"add row and column lines to left hand column
   (the one wit the drag handles)"* — they were never missing, they were being faded out with
   the dots. Transparent text leaves the cell's borders alone. */
.wsx-grip {
  width: 16px; text-align: center; padding: 0 !important; cursor: move;
  color: transparent; user-select: none; font-size: 10px; letter-spacing: -2px;
  transition: color .1s;
}
.wsx tbody tr:hover .wsx-grip,
.wsx tr.wsx-sec .wsx-grip, .wsx tr.wsx-blk .wsx-grip { color: var(--fg-muted); }
/* ⚠️ **`move`, not `grab`.** Chrome on Windows draws the open-hand grab cursor from a small
   bitmap and it comes out visibly pixelated at any normal display scaling — James, 2026-09-17:
   "the hand mouse pointer that i get for drag to move is really pixelated". `move` is a system
   cursor, crisp at every scale, and says the same thing. */
.wsx-grip:active { cursor: move; }
/* ⚠️ Colour, not opacity. Opacity on a table cell fades its BORDERS too, and this sheet is
   read by its gridlines — the same fault that made the calculated columns lose their grid. */
.wsx tr.wsx-dragging td { color: var(--fg-muted); }
/* ── where it can land ─────────────────────────────────────────────────────────
   A drop is a GAP between peers, not a row. The first version let anything land after whatever
   row you released over, so a heading could be dropped into the middle of somebody's lines.
   James, 2026-09-17: "only valid insertion points should show."

   Every gap the dragged thing is allowed into is marked faintly the moment the drag starts, so
   the question "where may this go" is answered before the mouse moves. The one under the
   cursor goes solid. A line ON the gap, never a box around a row — the answer is a position. */
.wsx tr.wsx-gap-top td { box-shadow: inset 0  2px 0 0 var(--ea-green-200, #bbf7d0); }
.wsx tr.wsx-gap-bot td { box-shadow: inset 0 -2px 0 0 var(--ea-green-200, #bbf7d0); }
.wsx tr.wsx-drop-top td { box-shadow: inset 0  3px 0 0 var(--ea-green-600) !important; }
.wsx tr.wsx-drop-bot td { box-shadow: inset 0 -3px 0 0 var(--ea-green-600) !important; }

/* A section is a row IN the sheet carrying its own subtotal, not a card around one. That is
   a header and a subtotal row collapsed into one, on each of twenty-four sections. */
.wsx tr.wsx-sec td {
  background: var(--ea-green-50); font-weight: 650;
  border-top: 1px solid var(--border-default); height: 24px;
}
.wsx-sec-name { color: var(--fg-primary); }
/* The "3 lines" tally came off headings 2026-09-17 (James). The lines are underneath it. */
/* A section with no lines shows no subtotal. Rows of "$0" down the top of the sheet read as
   a section that was priced and came to nothing, which is a different thing entirely. */
.wsx tr.wsx-sec-empty td { background: var(--bg-subtle); }
.wsx tr.wsx-sec-empty .wsx-sec-name { color: var(--fg-secondary); font-weight: 600; }

/* ⚠️ Visible at rest. It was `opacity: 0` until the row was hovered, which on a heading with
   nothing under it made the only way to start pricing invisible — found 2026-09-17 when a new
   heading and its two blocks appeared to have gone missing from the sheet. Same rule as the
   remove cross on the work breakdown: a control nobody can see is a control nobody finds. */
.wsx-add {
  margin-left: 10px; border: none; background: none; padding: 0;
  font: inherit; font-size: 11px; font-weight: 500; color: var(--fg-brand); cursor: pointer;
  opacity: .55; transition: opacity .1s;
}
.wsx tr.wsx-sec:hover .wsx-add, .wsx-add:hover, .wsx-add:focus-visible { opacity: 1; }

/* A block with nothing pricing it yet reads as a block heading like any other — the prompt to
   price it hangs off the name. Deliberately quiet: it is work that exists and has no number,
   which is a different thing from a line priced at zero, and it must never read as money. */
.wsx-waiting-hint {
  margin-left: 9px; font-size: 11px; font-weight: 400; font-style: italic;
  color: var(--fg-tertiary); cursor: pointer;
}
.wsx-waiting-hint:hover { color: var(--fg-brand); text-decoration: underline; }
/* The name IS the heading, and renaming it is typing in it — for a block and, since
   2026-09-17, for a section too. No border until hovered, so a column of headings does not read
   as a column of form fields. */
.wsx-blkname, .wsx-secname {
  width: auto; min-width: 140px; max-width: 320px;
  font: inherit; color: inherit;
}
.wsx-blkname { font-weight: 650; }
.wsx-secname { font-weight: 650; }

/* Type a description, or paste a list of them. The last row of every section, so it reads as
   the end of the list rather than as a control bolted on beside it. */
.wsx tr.wsx-addrow td { background: transparent; border-bottom: 1px solid var(--border-subtle); }
/* The foot of a section: no longer somewhere you type, still somewhere you can DROP. Tall
   enough to take the 3px drop bar and to be an easy target, short enough to read as the gap
   between two headings rather than as a row. */
.wsx tr.wsx-endsec td { height: 7px; padding: 0; }
.wsx tr.wsx-addrow input {
  width: 100%; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: 12px; color: var(--fg-primary);
  padding: 2px 0; border-bottom: 1px solid transparent;
}
.wsx tr.wsx-addrow input::placeholder { color: var(--fg-secondary); opacity: .7; }
.wsx tr.wsx-addrow input:focus { border-bottom-color: var(--ea-green-600); }
.wsx tr.wsx-addrow input:disabled { opacity: .5; }

/* Copy a line. Sits beside the remove on the same end cell and follows the same hover rule the
   rest of the grid already uses. */
.wsx-dup { font-size: 12px !important; }

/* ── the four levels of the sheet ───────────────────────────────────────────
   James, 2026-09-17, giving the shape:

       Sections (as they are now)
           Blocks (eg Fans)
               Lines (or Suppliers if there are multiple suppliers)
                   Options (for that line, eg "External Duct Coating")

   ⚠️ The first version drew a HEADER ROW above each supplier's lines, which put the name on one
   row and its money on the next and invented a level the estimate does not have. The supplier
   IS the line — Bruce writes "MIS" on a line with its price, then "MIS (External Duct Coating)"
   beneath it.

   Depth is shown by indenting the description, because that is the column the eye runs down.
   Nothing else moves: the money stays in its columns, which is the point of a sheet. */
.wsx td.wsx-cell-top { padding-left: 10px; }
.wsx td.wsx-cell-opt { padding-left: 26px; }

/* ⚠️ **The documents NAME this one.** The Tauranga Fire Station safeguard: three condensers
   priced at $27k–$29k against Daikin's $30k, none of them mentioning the coil coating the
   drawing required — so the cheapest was not the cheapest. It sits to the right of the
   description, where the eye lands after reading what the line is, and it never hides: the
   whole value of the mark is being seen while comparing prices, not after. */
.wsx-spec {
  flex: 0 0 auto; margin-left: 7px; font-size: 10px; font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase; white-space: nowrap;
  padding: 1px 6px; border-radius: 20px;
  background: var(--ea-charcoal-100); color: var(--ea-charcoal-700);
}
/* Beside the note, and behaving exactly like it: hidden until the row is hovered, lit for good
   once the line carries a specification. The chip in the description does the reading.
   ⚠️ **Charcoal, not sky.** The note marker went blue on 2026-09-17 and two blue icons sitting
   side by side say nothing to each other. Three marks, three meanings, three colours: blue is
   what we know, amber is what we are not carrying, and this is what the documents insist on. */
.wsx-notecell .wsx-specbtn { font-size: 12px; }
.wsx-notecell .wsx-specbtn.has-note { opacity: 1 !important; color: var(--ea-charcoal-700) !important; }

/* The supplier names the line when there is a choice to make. It sits BESIDE the description
   box, not above it — every input here is width:100%, so anything written before one pushed the
   box onto a second line and the row read as two. */
/* ⚠️ **A TICK IS NOT A TEXT BOX.** Every input on this sheet is `width: 100%`, which is right
   for the cells you type in and catastrophic for a radio: moved out of the 24px marker column
   and into the description cell, each one claimed three hundred pixels and shoved the supplier,
   the description and the chip into the middle of the sheet. Reported twice on 2026-09-17, the
   second time after a table-layout fix that was treating the symptom.
   They size to themselves and never grow. */
/* ⚠️ Written as `.wsx input[type=…]` and not as `.wsx-pick`, because `.wsx input` is a class
   plus an element and a bare class does not outrank it — the obvious-looking fix would have
   been overridden and the bug would have looked unfixed. */
.wsx input[type="radio"], .wsx input[type="checkbox"] { width: auto; flex: 0 0 auto; }
.wsx-inline { display: flex; align-items: center; gap: 0; min-width: 0; }
/* ⚠️ `.wsx-fit` is excluded here rather than fighting this rule below it. Each `:not()` counts
   as a class, so this selector is (0,3,1) and a plain `.wsx-inline input.wsx-fit` is (0,2,1) —
   it loses, and the chip stays pinned to the far edge looking unfixed. Second time today. */
.wsx-inline input:not([type="radio"]):not([type="checkbox"]):not(.wsx-fit) {
  flex: 1 1 auto; min-width: 40px;
}
/* A box carrying a chip sizes to its own text, so the chip sits against the words rather than
   out at the edge of the column. Only those rows — the rest keep the whole cell as a target. */
.wsx-inline input.wsx-fit { flex: 0 1 auto; width: auto; min-width: 3ch; max-width: 100%; }
.wsx-supname {
  flex: 0 0 auto; font-weight: 600; margin-right: 7px; white-space: nowrap;
  max-width: 45%; overflow: hidden; text-overflow: ellipsis;
}

/* ⚠️ **A radio to toggle BETWEEN, a tick to turn on and off.** James: "the first level of
   options (supplier) should be a toggle between whereas the lower level should be turn
   on/off." The radio only appears where there is more than one supplier under a block — one
   supplier is a line, not an alternative — and it sits in the marker column with the ticks, in
   the tick's place, so the column reads straight down. */

/* An option is quieter than the line it hangs off — it is an addition to that price, not a
   price of its own. */
.wsx tr.wsx-opt td.wsx-cell-opt input { font-style: italic; }
.wsx-opt-add {
  border: none; background: none; cursor: pointer; color: var(--fg-tertiary);
  font-size: 14px; line-height: 1; padding: 0 3px; vertical-align: middle;
  opacity: 0; transition: opacity .1s;
}
.wsx tbody tr:hover .wsx-opt-add, .wsx-opt-add:focus-visible { opacity: 1; }

/* Picking the supplier whose price this is. One inline row under the block, like the note. */
.wsx tr.wsx-offerpick td { background: var(--bg-subtle); padding: 6px 10px; height: auto; }
.wsx tr.wsx-offerpick select { font: inherit; font-size: 12px; padding: 2px 6px; margin-right: 8px; }


/* ── In this price, or not ──────────────────────────────────────────────────
   A line counts when it is ticked AND has a quantity. Bruce switches an offer off by emptying
   the quantity column, which destroys the take-off and makes an unchosen $1.1m offer look
   exactly like work nobody has got to. Here the number and the quantity both stay. */
/* ⚠️ Quiet. A tick on every line of a sixty-row sheet in the brand colour reads as sixty
   decisions demanding attention; grey lets the money stay the loudest thing on the row.
   James, 2026-09-17: "make the checkboxes less prominent (grey perhaps?)". */
.wsx-inc {
  margin: 0 2px 0 0; vertical-align: middle; cursor: pointer;
  accent-color: var(--fg-muted); transform: scale(.88);
}
.wsx-inc:hover, .wsx tbody tr:hover .wsx-inc { accent-color: var(--fg-secondary); }
.wsx-inc:disabled { cursor: default; opacity: .4; }
/* The toggle between supplier options stands in the tick's place on those rows — one control
   per row, one column. Round means choose between; square means on or off. */
.wsx-pick {
  margin: 0 2px 0 0; vertical-align: middle; cursor: pointer;
  accent-color: var(--ea-green-600);
}
/* ⚠️ Colour and italics, never opacity — opacity fades the gridlines with the text.
   ⚠️ **A step lighter again on 2026-09-17.** James: the options and supplier options that are
   not selected "are cluttering up the sheet a bit too much". They have to stay legible — an
   unchosen price is the fallback and the negotiating position — but they are not what the eye
   should land on, and at the muted grey they read almost as loudly as the price in use. */
.wsx tr.wsx-out td { color: var(--ea-charcoal-300); font-style: italic; }
.wsx tr.wsx-out input, .wsx tr.wsx-out select { color: var(--ea-charcoal-300); font-style: italic; }
.wsx tr.wsx-out .wsx-inc { font-style: normal; }

/* ── Supplier prices, in the sheet ──────────────────────────────────────────
   Every quote is a row beside the item it prices, which is how Bruce's book already reads:
   the chosen supplier carries the quantity and the others sit there at nothing. The card that
   used to hold these below the sheet is gone. */

/* The block is a heading and its prices are the options under it. Before this the chosen price
   was an ordinary line and the rest sat beneath it, so three prices for one thing read as an
   item with two footnotes rather than as three options. */
/* ⚠️ **Green shading marks a SECTION heading and nothing else.** James, 2026-09-17. The block
   heading and the chosen row both carried it too, so three different things on the sheet read
   as the same kind of thing and the section — the one that carries a subtotal — stopped
   standing out. A block heading is set apart by weight and a rule above it instead. */
.wsx tr.wsx-blk td {
  height: 21px; border-top: 1px solid var(--border-default);
  font-weight: 650; padding-left: 8px;
}
/* "3 prices — pick the one in our price" came off block headings 2026-09-17 (James). Each of
   those prices is a row underneath with a radio button on it. */
/* The block's own controls, on its heading — who prices it, the way in to its detail, and the
   way to remove it. All of this used to live on the Work breakdown tab. */
.wsx-blk-act { margin-left: 10px; display: inline-flex; align-items: center; gap: 6px; }
/* The coverage dropdown came off the block heading 2026-09-17 — it read "Supplier price" on
   every block, saying what the supplier offers underneath now say by existing. Still set from
   the block's own detail; only "Excluded" is worth a mark on the row. */
.wsx-blk-btn {
  border: none; background: transparent; cursor: pointer; color: var(--fg-secondary);
  font-size: 12px; line-height: 1; padding: 2px 5px; border-radius: 3px; opacity: .5;
}
.wsx tr.wsx-blk:hover .wsx-blk-btn { opacity: 1; }
.wsx-blk-btn:hover { background: var(--border-subtle); }
.wsx-add-x { color: var(--fg-secondary) !important; font-size: 13px !important; }

/* The chosen one is a real checked radio in the same list as the others. No fill — the radio
   says which it is, and a colour saying it as well is the same fact twice. */
.wsx-has-quotes { white-space: nowrap; }
.wsx-has-quotes input[type="radio"] { width: auto; margin: 0 6px 0 0; vertical-align: -1px; cursor: default; }
.wsx-has-quotes input:not([type="radio"]) { width: calc(100% - 22px); }

/* A price we did not take. Quiet, and with nothing in the calculated columns — it is a price
   on file, not money in the tender. Never `opacity`: it would take the gridlines with it. */
/* ⚠️ A price we are NOT using is grey and italic — James, 2026-09-17. No fill: shading is the
   section heading's job now, and a row that is quiet should read quiet rather than read as
   another kind of heading. */
.wsx tr.wsx-alt td { color: var(--ea-charcoal-300); font-style: italic; height: 20px; }
/* ⚠️ Lighter at rest, but READABLE the moment you look at it. An unchosen price is the
   fallback and the negotiating position — it has to be quiet, not unreadable. */
.wsx tr.wsx-alt:hover td,
.wsx tr.wsx-alt:hover td input { color: var(--fg-secondary); }
.wsx tr.wsx-out:hover td,
.wsx tr.wsx-out:hover td input { color: var(--fg-secondary); }
.wsx tr.wsx-alt .wsx-alt-amt input { font-style: italic; }
/* The price is typed on the row where it is read. It looks like the sheet's other numbers, so
   it reads as a figure rather than as a form dropped into the middle of an estimate. */
.wsx-alt-amt { font-variant-numeric: tabular-nums; }
.wsx tr.wsx-alt .wsx-alt-amt input {
  text-align: right; font-variant-numeric: tabular-nums;
  color: inherit; font-weight: 500;          /* inherit: a price not being used stays grey */
}
.wsx tr.wsx-alt .wsx-alt-amt input::placeholder { font-weight: 400; opacity: .55; font-style: italic; }

/* Asked, and nothing back yet. No radio — choosing a price that does not exist would set
   "this one is in ours" against nothing and move no money. */
.wsx tr.wsx-alt-none .wsx-alt-name { color: var(--ea-charcoal-300); }
.wsx-alt-dot { display: inline-block; width: 13px; text-align: center; color: var(--fg-muted); }

/* Used in two places: the marker column on a line, and the row of a supplier price that came
   back through a request. The marker-column copy takes its hide-until-hover behaviour from
   `.wsx-notecell button`, which is why the padding here is the only thing it needs. */
.wsx-qx {
  border: none; background: transparent; cursor: pointer;
  font-size: 13px; line-height: 1; padding: 0 3px; border-radius: 3px; color: var(--fg-muted);
}
.wsx tr.wsx-alt:hover .wsx-qx { color: var(--fg-secondary); }
.wsx-qx:hover { background: var(--border-subtle); }
.wsx-alt-x {
  font-size: 10.5px; color: var(--ea-amber-600); padding-left: 20px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 260px;
}
/* A line that HAS an exclusion keeps its marker visible, like a line that has a note. It is a
   MARK, not a button: hiding it until hover would only tell you about the row you are already
   looking at, which is the one row you did not need telling about. */
.wsx-notecell .wsx-qx.has-note,
.wsx-end .wsx-qx.has-note { color: var(--ea-amber-600) !important; opacity: 1 !important; }

/* ⚠️ **The trade total row at the foot is GONE — do not restyle it, it is not drawn.**
   Pinned to the bottom for a few hours on 2026-09-17, then unpinned, then removed outright the
   same day. James: *"we dont need the HVAC total row at the bottom as all that info is on the
   fixed header"*. Its four figures are tiles in the band, and the band is pinned, so they are
   on screen at the foot of the sheet as well as the top. Its rules are deleted rather than left
   behind, so nobody reads this file and concludes the row still exists. */

/* The note marker leads the row. It belongs on the left with the thing it annotates, not
   out past the money at the far right (James, 2026-09-15). */
/* Three markers, together on the left: the note, what the line does not cover, and whether the
   documents name it. The tick and the toggle moved into the description cell on 2026-09-17,
   because the Item column sat between the decision and the thing being decided; these stayed,
   because they are marks to scan rather than decisions to make. */
.wsx-w-note { width: 58px; }
.wsx-notecell { text-align: center; padding: 0 !important; white-space: nowrap; }
/* ⚠️ **Hidden until the row is hovered — asked for, 2026-09-17.** James: *"Note button and
   exclusion button and duplicate button and remove button hide unless hovered over."* Four of
   the same control repeated down every row of a dense sheet is noise; you only ever act on the
   row under the cursor, so that is the only row that needs them.

   This is NOT the same as the `+ block` / `+ line` buttons on a heading, which are visible at
   rest: those are the only way to start something, on a row that may be empty. A per-row button
   that repeats on every line is a different case, and a MARKER is different again — see below,
   a line carrying a note or an exclusion stays marked whether hovered or not.

   `opacity` is safe here because it is on the BUTTON, which has no borders. Never on the cell:
   that is what made the gridlines vanish from the calculated columns. */
.wsx-notecell button,
.wsx-end button {
  border: none; background: none; color: var(--fg-tertiary); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 0 3px; transition: opacity .1s;
  vertical-align: middle; opacity: 0;
}
.wsx-notecell button { display: inline-flex; align-items: center; }
.wsx-notecell .wsx-note svg { display: block; }
.wsx tbody tr:hover .wsx-notecell button,
.wsx tbody tr:hover .wsx-end button,
.wsx-notecell button:focus-visible, .wsx-end button:focus-visible { opacity: 1; }
/* ⚠️ A line that HAS a note stays marked whether or not the row is hovered. The whole point
   of the mark is to be visible while scanning — one that appears on hover only tells you
   about the row you are already looking at. */
.wsx-notecell .wsx-note.has-note { color: var(--ea-sky-600, #0284c7) !important; opacity: 1 !important; }

.wsx-end { white-space: nowrap; text-align: right; }
.wsx-end .wsx-del:hover { color: var(--fg-danger); }

/* ⚠️ **A note and an exclusion ARE their own field — there is no editor to open.** They used
   to be a textarea row with Cancel and Save, which was three moves for one sentence. Borderless
   until hovered, so a run of them reads as text rather than as a column of form fields; the
   colour says which is which, blue for a note and amber for an exclusion. */
.wsx-underline {
  display: block; width: 100%; margin-top: 1px;
  font-size: 10.5px; font-style: italic; line-height: 1.4;
  padding: 0 3px; border: 1px solid transparent; border-radius: 2px;
  background: transparent; font-family: inherit;
}
.wsx-underline::placeholder { color: var(--fg-muted); font-style: italic; }
.wsx-underline:hover { border-color: var(--border-subtle); }
.wsx-underline:focus { border-color: var(--ea-green-600); background: var(--bg-surface); outline: none; }
.wsx-underline.wsx-noteline { color: var(--ea-sky-600, #0284c7); }
.wsx-underline.wsx-ours { color: var(--ea-amber-600); font-weight: 500; }

/* ⚠️ **Hover is BLUE, and green is reserved for a section heading.** The hover used to be the
   same green-50 the headings are painted in, so running the mouse down the sheet turned
   ordinary rows into things that looked like headings. James, 2026-09-17: "on hover rows
   currently shade green which confuses them with section headers." One colour, one meaning:
   green says "this is a heading", blue says "this is the row under your cursor". */
.wsx tbody tr:hover td { background: var(--ea-sky-50); }
.wsx tbody tr.wsx-sec:hover td { background: var(--ea-sky-100); }

/* ── The pricing matrix — who is being asked to price what ──────────────────
   Blocks down, suppliers across. A square IS a line in that supplier's request; there is no
   plan sitting beside the requests. Kept dense for the same reason as the worksheet: Rotorua
   has 27 blocks, and a grid you have to scroll to compare is not a comparison. */
.mx-wrap { overflow: auto; max-height: 60vh; border: 1px solid var(--border-default); border-radius: var(--radius-md); }
.mx { border-collapse: separate; border-spacing: 0; font-size: 12px; width: 100%; }
.mx th, .mx td { border-right: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle); }
.mx th:last-child, .mx td:last-child { border-right: none; }

/* Both headers stick: the supplier names when scrolling down, the block names when scrolling
   across. A grid where you lose one of them is a grid of unlabelled squares. */
.mx thead th {
  position: sticky; top: 0; z-index: 3; background: var(--bg-subtle);
  padding: 5px 7px; text-align: center; vertical-align: bottom;
  border-bottom: 1px solid var(--border-default);
}
.mx-col { width: 96px; min-width: 96px; max-width: 96px; overflow: hidden; }
/* Supplier names are long and there are a lot of columns. Wrap to two lines and hyphenate
   rather than clip — a clipped name in a column header is a column you cannot identify, and
   "GREEN SOLUTION" and "GREEN SOLUTIONS LTD" are not obviously the same supplier. */
.mx-col-name {
  font-size: 11px; font-weight: 650; line-height: 1.15;
  white-space: normal; overflow-wrap: anywhere; hyphens: auto;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Wraps too. "1/1 back · chased 2×" clipped to "1/1 back · CHAS" loses the one number that
   says whether this supplier is being left alone. */
.mx-col-sub { font-size: 10px; color: var(--fg-tertiary); margin-top: 1px;
              white-space: normal; line-height: 1.2; }
.mx-add { width: 40px; }

.mx-rowhead {
  position: sticky; left: 0; z-index: 2; background: var(--bg-surface);
  text-align: left; padding: 4px 9px; min-width: 240px; max-width: 320px;
  border-right: 1px solid var(--border-default);
}
/* On a laptop screen the add-supplier "+" sat 9px past the edge of the grid (audit
   2026-09-24, 1366 wide). The grid is meant to scroll across once there are many suppliers;
   this only stops the "+" being the thing that falls off when there are few. Must come after
   the rule above or it loses. */
@media (max-width: 1500px) { .mx-rowhead { min-width: 190px; } }
.mx thead .mx-rowhead { z-index: 4; background: var(--bg-subtle);
  font-size: 10px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-secondary); }
.mx-why  { display: block; font-size: 10.5px; color: var(--fg-tertiary); }
.mx-spec { display: inline-block; margin-left: 6px; font-size: 10px; color: var(--fg-brand); }

/* The two gaps this stage exists to find, on the row rather than only in a card above it. */
.mx-rowhead.mx-gap  { box-shadow: inset 3px 0 0 var(--ea-red-600); }
.mx-rowhead.mx-gap  .mx-why { color: var(--ea-red-600); font-weight: 600; }
.mx-rowhead.mx-thin { box-shadow: inset 3px 0 0 var(--ea-amber-600); }

.mx tr.mx-sec td {
  background: var(--ea-green-50); font-weight: 650; padding: 3px 9px;
  position: sticky; left: 0; border-top: 1px solid var(--border-default);
}
.mx-sec-trade { margin-left: 8px; font-weight: 400; font-size: 10.5px; color: var(--fg-tertiary); }

/* A block we price ourselves or have excluded still gets a row. It is not asked of anybody,
   and the point is that you can see that rather than wonder. */
.mx tr.mx-row-off .mx-rowhead { color: var(--fg-tertiary); background: var(--bg-subtle); }

.mx-cell {
  width: 78px; height: 26px; text-align: center; cursor: pointer;
  font-size: 13px; line-height: 1; color: var(--fg-secondary);
}
.mx-cell:hover { background: var(--ea-green-100); }
.mx-cell:empty:hover::after { content: '+'; color: var(--fg-tertiary); }

/* Ticked but not sent reads as an intention: outlined, not filled. Sent reads as a fact. */
.mx-plan     { background: var(--bg-surface); color: var(--fg-tertiary); box-shadow: inset 0 0 0 1px var(--ea-green-300); }
.mx-sent.mx-asked    { background: var(--ea-sky-100);   color: var(--ea-sky-600); }
.mx-sent.mx-quoted   { background: var(--ea-green-100); color: var(--ea-green-700); font-weight: 700; }
.mx-sent.mx-declined { background: var(--ea-charcoal-100); color: var(--fg-muted); }
.mx-chosen   { box-shadow: inset 0 0 0 2px var(--ea-green-600); }

.mx-key { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 9px; font-size: 11px; color: var(--fg-tertiary); }
.mx-key span { display: flex; align-items: center; gap: 5px; }
.mx-sw { width: 13px; height: 13px; border-radius: 3px; display: inline-block; background: var(--bg-surface); }

.tnd-proposed {
  background: var(--ea-amber-50, #fffbeb);
  box-shadow: inset 3px 0 0 var(--ea-amber-400, #fbbf24);
}

/* ── Building the breakdown by hand ─────────────────────────────────────────
   James, 2026-09-17: type it, drag it, delete it. With nothing proposing the breakdown any
   more, every block on that screen is typed by a person, and a modal for each one is the
   difference between two minutes and twenty. */

/* The handle sits in the indent a block row already has, so a list with no handles — All
   trades, where reordering across two separately priced jobs has no meaning — keeps exactly
   the same layout rather than shifting when you switch tabs. */
.tnd-blk, .tnd-sec-head { position: relative; }
.tnd-grip {
  cursor: grab; user-select: none; flex: none;
  color: var(--fg-secondary); opacity: .4;
  font-size: 13px; line-height: 1; letter-spacing: -1px;
  padding: 4px 3px; border-radius: 4px;
  transition: opacity 120ms, background 120ms;
}
.tnd-blk .tnd-grip { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); }
.tnd-grip:hover  { opacity: 1; background: var(--border-subtle); }
.tnd-grip:active { cursor: grabbing; }
.tnd-dragging    { opacity: .4; }

/* The gap gets the line, not the row. Highlighting the row under the cursor cannot say whether
   the thing lands above or below it, which is the only question being asked mid-drag. */
.tnd-drop-before { box-shadow: inset 0  2px 0 var(--ea-green-600); }
.tnd-drop-after  { box-shadow: inset 0 -2px 0 var(--ea-green-600); }

/* Visible at rest, not revealed on hover. A control nobody can see until they happen to point
   at it is a control most people never find. */
.tnd-x {
  border: none; background: transparent; cursor: pointer; flex: none;
  color: var(--fg-secondary); opacity: .35;
  font-size: 12px; line-height: 1; padding: 4px 6px; border-radius: 4px;
  transition: opacity 120ms, background 120ms, color 120ms;
}
.tnd-blk:hover .tnd-x { opacity: .6; }
.tnd-x:hover { opacity: 1; background: var(--ea-red-100, #fee2e2); color: var(--ea-red-800, #991b1b); }

/* The type-a-block row. Deliberately not a button and not a modal — it is the last line of the
   list, and it looks like one. */
.tnd-blk-add { cursor: default; }
.tnd-blk-add:hover { background: transparent; }
.tnd-add-input {
  width: 100%; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: 12.5px; color: var(--fg-primary);
  padding: 2px 0; border-bottom: 1px solid transparent;
}
.tnd-add-input::placeholder { color: var(--fg-secondary); opacity: .8; }
.tnd-add-input:focus { border-bottom-color: var(--ea-green-600); }
.tnd-add-input:disabled { opacity: .5; }

/* The type-a-heading box sits below the last heading, outside any of them, because that is
   where a new heading goes. Same box as the block one so there is nothing extra to learn. */
.tnd-sec-add { padding: 4px 14px 2px; }

/* Who prices a block, as the chip itself rather than four moves through a modal. A native
   select keeps the keyboard and the screen reader working; the chip classes just dress it. */
.tnd-cov {
  appearance: none; -webkit-appearance: none;
  border: none; cursor: pointer; flex: none;
  font: inherit; font-size: 11px; line-height: 1.6;
  padding: 1px 18px 1px 8px;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 8px top 52%, right 4px top 52%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.tnd-cov:hover  { filter: brightness(.96); }
.tnd-cov:focus  { outline: 2px solid var(--ea-green-600); outline-offset: 1px; }
/* The options themselves are drawn by the operating system, so they get plain colours —
   inheriting a chip's background makes half of them unreadable on Windows. */
.tnd-cov option { background: var(--bg-card, #fff); color: var(--fg-primary); }

/* Dropping a block into a different heading. The whole heading is outlined, because "into this
   one, at the end" is a different answer from the line that means "between these two rows". */
.tnd-drop-into { box-shadow: inset 0 0 0 2px var(--ea-green-600); }

.td-secondary { color: var(--fg-secondary); }
.td-mono { font-family: var(--font-mono); font-size: 12px; color: var(--fg-secondary); }

/* ── Timesheet week record — people down, eight weeks across ────────────────
   The row hover that suits a list works against a grid: it repaints behind every
   coloured cell at once and the colours are the whole point. Suppressed here. */
.ts-record-table { table-layout: fixed; }
.ts-record-table thead th { text-align: center; padding: 9px 6px; }
.ts-record-table thead th.ts-rec-name { text-align: left; padding-left: 16px; width: 190px; }
.ts-record-table thead th.ts-rec-now { color: var(--fg-brand); background: var(--ea-green-50); }

/* Xero payroll summary, per week. Quiet until hovered — there are eight of them and they
   must not compete with the cells underneath, which are what the screen is for. */
.ts-rec-sum {
  display: block; margin: 5px auto 0; padding: 2px 8px;
  font: inherit; font-size: 10px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--fg-muted);
  background: transparent; border: 1px solid var(--border-default); border-radius: 20px;
  cursor: pointer; transition: color 80ms, border-color 80ms, background 80ms;
}
.ts-rec-sum:hover { color: var(--fg-brand); border-color: var(--ea-green-400); background: var(--bg-surface); }
.ts-rec-sum:focus-visible { outline: none; box-shadow: var(--ring-brand); }
.ts-record-table tbody td { padding: 6px; text-align: center; }
.ts-record-table tbody td.ts-rec-name {
  text-align: left; padding-left: 16px; font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.ts-record-table tbody tr:hover { background: transparent; }

.ts-rec-cell {
  display: block; border-radius: 6px; padding: 7px 4px;
  font-family: var(--font-mono); font-size: 12px; font-weight: 600;
  border: 1px solid transparent; transition: box-shadow 80ms, transform 80ms;
}
.ts-rec-cell.is-open { cursor: pointer; }
.ts-rec-cell.is-open:hover { box-shadow: var(--shadow-sm); transform: translateY(-1px); }

/* Coloured by who the week is waiting on, not by raw status — that is the question
   the record answers. */
.ts-rec-closed  { background: var(--ea-green-100); color: var(--ea-green-800); border-color: var(--ea-green-200); }
.ts-rec-you     { background: var(--ea-amber-100); color: var(--ea-amber-600); border-color: #f0dcb4; }
.ts-rec-missing { background: var(--ea-red-100);   color: var(--ea-red-600);   border-color: #f3cdc9; }
.ts-rec-open    { background: var(--bg-subtle);    color: var(--fg-tertiary);  border-color: var(--border-subtle); }
.ts-rec-na      { background: transparent;         color: var(--ea-charcoal-300); }

/* A week that banked or drew flexi carries a dot, so the movement the old
   "Flex this week" column showed is still visible without a column of its own. */
.ts-rec-flex::after {
  content: ''; display: block; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: .55; margin: 3px auto 0;
}

/* Rate table modal — compact, editable grid. Fixed layout so the column
   widths set on the <th>s are honoured (the name column takes the rest). */
.mcd-rates-table { table-layout: fixed; }
.mcd-rates-table th { padding: 7px 8px; }
.mcd-rates-table td { padding: 3px 8px; vertical-align: middle; }
.mcd-rates-table .field-input { height: 30px; padding: 0 8px; font-size: 12px; width: 100%; }
.mcd-rates-table td.mcd-drag, .mcd-rates-table th:first-child { width: 22px; padding-left: 4px; padding-right: 4px; font-size: 13px; }
.mcd-rates-table .btn-sm { padding: 4px 10px; }
/* The actions column carries three buttons since the Tasks checklist was added. The table is
   `table-layout: fixed`, so without an explicit width this column takes whatever is left over
   and the last button is pushed off the row — which is what happened to Delete. */
.mcd-rates-table td.mcd-rate-actions,
.mcd-rates-table th.mcd-rate-actions { width: 232px; white-space: nowrap; }
.mcd-rates-table td.mcd-rate-actions .btn-sm { padding: 4px 8px; }
.mcd-rates-table td.mcd-rate-actions .btn-sm + .btn-sm { margin-left: 3px; }
.mcd-rates-table td.mcd-rate-actions strong { font-variant-numeric: tabular-nums; }

/* Pin the draft modal to the top so switching tabs (different content heights)
   doesn't re-centre the modal and shift the header/tabs. */
#modal-mcd-draft { align-items: flex-start; }

/* Draft modal tabs */
.mcd-tabs { display: flex; gap: 2px; border-bottom: 2px solid var(--border-subtle); margin-bottom: 16px; flex-wrap: wrap; }
.mcd-tab { padding: 8px 14px; font-size: 13px; font-weight: 500; color: var(--fg-secondary); background: transparent; border: none; border-bottom: 2px solid transparent; margin-bottom: -2px; cursor: pointer; transition: color .15s, border-color .15s; }
.mcd-tab:hover { color: var(--fg-primary); }
.mcd-tab.active { color: var(--ea-green-700); border-bottom-color: var(--ea-green-600); font-weight: 600; }
.mcd-tabpanel { display: none; }
.mcd-tabpanel.active { display: block; }
.mcd-dragging { opacity: .4; }
.mcd-drag:active { cursor: grabbing; }

/* Clause builder (draft editor) */
.mcd-clause-shdr { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--fg-secondary); margin: 0 0 6px; }
.mcd-clause-cat { font-size: 11px; font-weight: 600; color: var(--fg-tertiary); margin: 8px 0 4px; }
.mcd-clause-item { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 6px; cursor: pointer; font-size: 13px; }
.mcd-clause-item input { margin-top: 2px; flex-shrink: 0; }
.mcd-clause-ref { font-family: var(--font-mono); font-size: 11px; color: var(--fg-secondary); }
.mcd-clause-body { font-size: 12px; color: var(--fg-secondary); }
.mcd-clause-lock { color: var(--ea-green-600); font-weight: 700; }
.mcd-clause-always, .mcd-clause-choose { font-size: 11px; color: var(--fg-tertiary); }
.mcd-clause-group { border-left: 2px solid var(--border-subtle); padding-left: 10px; margin: 4px 0 8px; }
.mcd-clause-grouptitle { font-size: 13px; margin-bottom: 4px; }
.mcd-clause-variant { display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer; margin-bottom: 2px; }

/* Expandable clause rows (accordion) */
.mcd-clause-row { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 6px; }
.mcd-clause-row > input, .mcd-clause-row > .mcd-clause-lock { margin-top: 3px; flex-shrink: 0; }
.mcd-clause-variantrow { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 4px; }
.mcd-clause-variantrow > input { margin-top: 3px; flex-shrink: 0; }
.mcd-clause-main { flex: 1; min-width: 0; }
.mcd-clause-toggle { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  background: none; border: none; padding: 0; cursor: pointer; font-size: 13px; color: var(--fg-primary); font-family: inherit; }
.mcd-clause-caret { margin-left: auto; color: var(--fg-tertiary); flex-shrink: 0; transition: transform .12s; }
.mcd-clause-toggle.open .mcd-clause-caret { transform: rotate(90deg); }
.mcd-clause-full { font-size: 12px; color: var(--fg-secondary); margin: 5px 0 2px; padding: 8px 10px;
  background: var(--bg-subtle); border-radius: 6px; white-space: pre-wrap; line-height: 1.5; }

/* Clause library admin cards */
.mcd-clause-card { border: 1px solid var(--border-subtle); border-radius: 8px; padding: 12px; margin-bottom: 10px; }
.mcd-clause-card .field-input, .mcd-clause-card .field-textarea { height: auto; padding: 6px 8px; font-size: 12px; }
.mcd-clause-card textarea.field-textarea { min-height: 56px; }
.mcd-clause-cardrow { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; align-items: center; }
.mcd-clause-cardrow label { display: flex; align-items: center; gap: 5px; font-size: 12px; white-space: nowrap; }
.td-truncate { max-width: 320px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── Maintenance → Status grid ────────────────────────────────
   Wide screens: exactly the widths this table always had (300 / 450 / 5×130 / 150 = 1550px),
   stretched to fill the screen. James likes it that way — do not change the wide layout.
   Below 1832px wide the 1550px no longer fits beside the sidebar, and .table-wrap clips
   rather than scrolls, so the last quarters and 12A column simply vanished (Chris's PC,
   2026-09-24). There the quarter and 12A columns go compact and Client/Site share what is
   left, wrapping instead of truncating. The horizontal scroll is a last resort only — on a
   screen too small even for the compact widths. */
.maint-status-table { table-layout: fixed; width: 100%; }
.maint-status-table .ms-client { width: 300px; }
.maint-status-table .ms-site   { width: 450px; }
.maint-status-table .ms-q      { width: 130px; text-align: center; }
.maint-status-table .ms-12a    { width: 150px; }
@media (max-width: 1831px) {
  .maint-status-wrap { overflow-x: auto; }
  .maint-status-table { min-width: 960px; }
  .maint-status-table .ms-client,
  .maint-status-table .ms-site   { width: auto; }
  .maint-status-table .ms-q      { width: 104px; padding-left: 6px; padding-right: 6px; }
  .maint-status-table .ms-12a    { width: 132px; padding-left: 10px; padding-right: 10px; }
  .maint-status-table tbody td:nth-child(n+3) { padding-left: 6px; padding-right: 6px; }
  .maint-status-table tbody td:last-child     { padding-left: 10px; padding-right: 10px; }
  .maint-status-table td.td-truncate { max-width: none; white-space: normal; overflow: visible; }
}

/* ── Maintenance → Contracts and Follow-ups — same rule as Status (2026-09-24) ──
   Both tables set fixed pixel widths inline: Contracts 1475px, Follow-ups 1250px. Wide screens
   keep them exactly. Below the width where they stop fitting beside the sidebar (+282px) they
   scrolled sideways, with the scroll bar at the bottom of a long list — so on a laptop the
   money, agreement and date columns (Contracts) and Job / Status (Follow-ups) were simply not
   seen. There the table lays itself out by content instead: every column fits, long names and
   descriptions wrap rather than being cut off. !important because the widths are inline. */
@media (max-width: 1756px) {
  .maint-contracts-table { table-layout: auto !important; }
  /* Headings wrap too: "Agreement No.", "Contract date", "Annual value" on one line were what
     held the table ~90px too wide at 1280 and squeezed Client and Site to a sliver. */
  .maint-contracts-table th { width: auto !important; white-space: normal; vertical-align: bottom; }
  .maint-contracts-table th, .maint-contracts-table td { padding-left: 8px; padding-right: 8px; }
  .maint-contracts-table td.td-truncate { max-width: none; white-space: normal; overflow: visible; }
}
@media (max-width: 1531px) {
  .fu-table { table-layout: auto !important; }
  .fu-table th { width: auto !important; }
  .fu-table th, .fu-table td { padding-left: 8px; padding-right: 8px; }
  .fu-table td.td-truncate { max-width: none; white-space: normal; overflow: visible; }
}

/* ============================================================
   STATUS CHIPS
   ============================================================ */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; line-height: 1.4; white-space: nowrap;
}
/* ⚠️ A chip is a STATUS PILL — `text-transform: uppercase` and `white-space: nowrap`. It is
   not a message box, and using one for a sentence SHOUTS THE SENTENCE AND THEN CLIPS IT
   (James, 2026-08-21: "I can't read the whole message as it does not wrap"). For any message
   longer than a word or two, use `.notice` below. */
.chip-open     { background: var(--ea-sky-100);   color: var(--ea-sky-600); }
.chip-overdue  { background: var(--ea-red-100);   color: var(--ea-red-600); }
.chip-complete { background: var(--ea-green-100); color: var(--ea-green-700); }
/* A failure that has been put right. Green, because it is closed — but outlined and a shade
   off a clean pass, because "this failed and was rectified" is not "this passed first time"
   and the two must stay tellable apart at a glance. */
.chip-rectified{ background: var(--ea-green-50);  color: var(--ea-green-700); box-shadow: inset 0 0 0 1px var(--ea-green-300); }
.chip-manual   { background: var(--ea-charcoal-100); color: var(--ea-charcoal-600); }
.chip-meeting  { background: var(--ea-sky-100);   color: var(--ea-sky-600); }
.chip-vehicle  { background: var(--ea-amber-100); color: var(--ea-amber-600); }
.chip-project  { background: var(--ea-green-100); color: var(--ea-green-700); }
/* Vehicle compliance status */
.chip-ok       { background: var(--ea-green-100); color: var(--ea-green-700); }
.chip-due_soon { background: var(--ea-amber-100); color: var(--ea-amber-600); }
.chip-unknown  { background: var(--ea-charcoal-100); color: var(--ea-charcoal-600); }

/* ============================================================
   NOTICE — a sentence the reader has to act on
   ============================================================

   Use this, never a chip, for anything longer than a word or two. Sentence case, wraps,
   full line-height. A chip is `uppercase` + `nowrap`, so a sentence in one arrives SHOUTED
   AND CUT OFF — which is exactly what a GST warning did on the card receipt modal.

     <div class="notice notice-warn">…</div>   something to check
     <div class="notice notice-error">…</div>  something that failed or is wrong
     <div class="notice">…</div>               neutral, just so you know                   */
.notice {
  display: block;
  padding: 9px 12px;
  margin-bottom: 12px;
  border-radius: 8px;
  border-left: 3px solid var(--border-default);
  background: var(--bg-subtle);
  color: var(--fg-secondary);
  font-size: 12px;
  line-height: 1.5;
  white-space: normal;          /* the whole point */
  text-transform: none;         /* the other whole point */
}
.notice + .notice { margin-top: -4px; }
.notice-warn  { border-left-color: var(--ea-amber-600); background: var(--ea-amber-100); color: var(--ea-amber-600); }
.notice-error { border-left-color: var(--ea-red-600);   background: var(--ea-red-100);   color: var(--ea-red-600); }

/* ============================================================
   TABLE ACTIONS + DATE UTILITY CLASSES
   ============================================================ */
.tbl-action { padding: 4px 8px; border: 1px solid var(--border-subtle) !important; }
.tbl-action:hover { border-color: var(--border-default) !important; }
.tbl-action--danger { color: var(--ea-medgas-red); }
.tbl-action--danger:hover { color: var(--ea-medgas-red); background: var(--ea-medgas-red-100); border-color: var(--ea-medgas-red) !important; }
.tbl-date { white-space: nowrap; }
.tbl-date--overdue { color: var(--ea-medgas-red); font-weight: 500; }
.tbl-date--soon    { color: var(--ea-amber-600); }

/* ============================================================
   EMPTY STATE
   ============================================================ */
.empty-state {
  text-align: center; padding: 56px 24px; color: var(--fg-muted);
}
.empty-state svg { stroke: var(--ea-charcoal-300); margin-bottom: 12px; display: block; margin-left: auto; margin-right: auto; }
.empty-state p { font-size: 13px; }

/* ============================================================
   LOADING
   ============================================================ */
.loading {
  display: flex; align-items: center; justify-content: center;
  padding: 48px; color: var(--fg-muted); font-size: 13px; gap: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--border-default);
  border-top-color: var(--ea-green-600);
  border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0;
}

/* ============================================================
   MODAL
   ============================================================ */
.modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(31,32,34,0.4);
  display: flex; align-items: center; justify-content: center; padding: 24px;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 160ms, visibility 0s 160ms;
}
.modal-overlay.open {
  opacity: 1; visibility: visible; pointer-events: auto;
  backdrop-filter: blur(2px);
  transition: opacity 160ms, visibility 0s 0s;
}

.modal {
  background: var(--bg-surface); border-radius: 12px;
  box-shadow: 0 8px 24px rgba(31,32,34,0.15);
  width: 100%; max-width: 480px;
  max-height: calc(100vh - 48px);
  display: flex; flex-direction: column;
  transform: scale(0.97) translateY(8px); transition: transform 160ms;
}
.modal-overlay.open .modal { transform: scale(1) translateY(0); }

.modal-header {
  padding: 20px 24px 0;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
}
.modal-header h3 { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; }
.modal-close {
  width: 28px; height: 28px; border-radius: 6px;
  background: transparent; border: none; color: var(--fg-muted);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0; transition: background 120ms, color 120ms;
}
.modal-close:hover { background: var(--bg-subtle); color: var(--fg-primary); }
/* Wide modals — for screens that carry a lot at once.
   James, 2026-09-14 on the tendering module: *"this tender module will be managing a lot of
   data so the modals will need to be large to maximise use of screen to reduce the need for
   scrolling."* Height was never the constraint — `.modal` is already `100vh - 48px` with a
   scrolling body — the width was, at 480px by default.
   `94vw` keeps the overlay visible at the edges so it still reads as a modal. */
.modal-wide   { max-width: min(1180px, 94vw); }
.modal-medium { max-width: min(760px, 94vw); }

/* Two tracks side by side inside a wide modal, one below the other when there is no room.
   The point is to stop a long list pushing everything else off the bottom. */
.modal-split {
  display: grid; grid-template-columns: 1fr; gap: 24px; align-items: start;
}
@media (min-width: 900px) {
  .modal-split { grid-template-columns: 1.15fr 1fr; gap: 28px; }
  .modal-split > * { min-width: 0; }
}

.modal-body { padding: 20px 24px; overflow-y: auto; flex: 1; min-height: 0; }
.modal-footer { padding: 0 24px 20px; display: flex; justify-content: flex-end; gap: 8px; }

.cfg-tabs { display: flex; border-bottom: 1px solid var(--border-subtle); padding: 0 16px; gap: 4px; }
.cfg-tab {
  padding: 10px 14px; font-size: 13px; font-weight: 500;
  background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--fg-secondary); cursor: pointer; margin-bottom: -1px;
  transition: color 120ms, border-color 120ms;
}
.cfg-tab:hover { color: var(--fg-primary); }
.cfg-tab.active { color: var(--ea-green-700, #15803d); border-bottom-color: var(--ea-green-700, #15803d); }

.cfg-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; border-bottom: 1px solid var(--border-subtle);
}
.cfg-row-name { flex: 1; font-size: 13px; font-weight: 500; min-width: 120px; }
.cfg-row-code { font-size: 11px; color: var(--fg-tertiary); font-family: monospace; white-space: nowrap; }
.cfg-toggle-label { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--fg-secondary); cursor: pointer; white-space: nowrap; flex-shrink: 0; }
.cfg-col-active  { width: 72px; }
.cfg-col-ego     { width: 110px; }
.cfg-col-isleave { width: 82px; }
/* Fixed width so space is always reserved — visibility:hidden keeps layout stable */
.cfg-leave-extra { display: flex; align-items: center; gap: 10px; flex-shrink: 0; width: 200px; }
.cfg-leave-select { font-size: 12px; padding: 3px 6px; border: 1px solid var(--border-subtle); border-radius: 4px; background: var(--surface-raised, #fff); color: var(--fg-primary); max-width: 130px; }
.cfg-lt-status { font-size: 12px; color: var(--fg-secondary); white-space: nowrap; min-width: 80px; }

/* ============================================================
   TOAST
   ============================================================ */
.toast-container {
  position: fixed; bottom: 24px; right: 24px;
  display: flex; flex-direction: column; gap: 8px; z-index: 200;
}
.toast {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-radius: 8px;
  background: var(--ea-charcoal-800); color: #fff;
  font-size: 13px; font-weight: 500;
  box-shadow: 0 4px 12px rgba(31,32,34,0.2);
  animation: slideIn 200ms ease-out; max-width: 320px;
}
.toast.success { background: var(--ea-green-700); }
.toast.error   { background: var(--ea-red-600); }
@keyframes slideIn {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ============================================================
   GOOGLE SIGN-IN
   ============================================================ */
.google-signin-wrap {
  display: flex; justify-content: center; margin-top: 4px;
}

/* ============================================================
   TOGGLE SWITCH
   ============================================================ */
.toggle-switch {
  display: inline-flex; align-items: center; cursor: pointer;
}
.toggle-switch input { display: none; }
.toggle-slider {
  width: 32px; height: 18px;
  background: var(--ea-charcoal-200);
  border-radius: 9px; position: relative;
  transition: background 150ms;
}
.toggle-slider::before {
  content: '';
  position: absolute;
  width: 13px; height: 13px;
  background: #fff;
  border-radius: 50%;
  top: 2.5px; left: 2.5px;
  transition: transform 150ms;
  box-shadow: 0 1px 2px rgba(0,0,0,0.18);
}
.toggle-switch input:checked + .toggle-slider { background: var(--ea-green-600); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(14px); }
.toggle-switch input:disabled + .toggle-slider { opacity: 0.35; }
.toggle-switch:has(input:disabled) { cursor: not-allowed; }

/* ============================================================
   PERMISSIONS TABLES
   ============================================================ */
/* Permissions by role (2026-09-28): roles on the left, the selected role's permissions and the
   people below on the right. Replaced the two wide grids (eGO 19 columns, Hub 38). */
.pr-layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 16px; align-items: start; }
.pr-roles-card { padding: 8px; position: sticky; top: 12px; }
.pr-role { padding: 9px 11px; border-radius: 8px; cursor: pointer; border: 1px solid transparent; margin-bottom: 2px; }
.pr-role:hover { background: var(--bg-subtle); }
.pr-role.active { background: var(--ea-green-50); border-color: var(--ea-green-200); }
.pr-role-name { font-weight: 800; display: flex; align-items: center; gap: 8px; }
.pr-role-sum, .pr-person-sum { font-size: 11px; color: var(--fg-tertiary); margin-top: 2px; }
.pr-role-people { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.pr-new-role { width: 100%; margin-top: 6px; }
.pr-muted { font-size: 11px; color: var(--fg-muted); }
.pr-pill {
  display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 8px;
  border-radius: 10px; font-size: 11px; font-weight: 700; white-space: nowrap;
  background: var(--bg-muted); color: var(--fg-secondary);
}
.pr-pill-contractor { background: var(--ea-amber-100); color: #8a5a1c; }
.pr-pill-plus  { background: var(--ea-green-100); color: var(--ea-green-700); }
.pr-pill-minus { background: var(--ea-red-100);   color: var(--ea-red-600); }
.pr-pill-warn  { background: var(--ea-amber-100); color: #8a5a1c; }
.pr-extra-x { border: none; background: none; color: inherit; font-size: 13px; line-height: 1; cursor: pointer; padding: 0 0 0 2px; }
.pr-right { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.pr-role-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border-subtle); flex-wrap: wrap; }
.pr-role-title { font-size: 16px; font-weight: 800; letter-spacing: -0.01em; }
.pr-rename { width: 240px; height: 32px; }
.pr-contractor-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-secondary); cursor: pointer; }
.pr-grow { flex: 1; }
.pr-count { font-family: var(--font-mono); font-size: 11px; color: var(--fg-tertiary); }

.pr-apps { display: grid; grid-template-columns: 1fr 1fr; }
.pr-app { padding: 12px 16px 16px; min-width: 0; }
.pr-app + .pr-app { border-left: 1px solid var(--border-subtle); }
.pr-app-head { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 14px; margin-bottom: 10px; }
.pr-app-head .pr-count { margin-left: auto; }
.pr-app-off { padding: 12px 2px; color: var(--fg-tertiary); }
.pr-area { border: 1px solid var(--border-subtle); border-radius: 8px; margin-bottom: 6px; }
.pr-area-off { background: var(--bg-subtle); }
.pr-area-head { display: flex; align-items: center; gap: 10px; padding: 7px 10px; }
.pr-area-name { font-weight: 700; flex: 1; }
.pr-area-off .pr-area-name { color: var(--fg-tertiary); }
.pr-area-body { display: grid; grid-template-columns: 1fr 1fr; gap: 3px 14px; padding: 0 10px 9px 52px; }
.pr-opt { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--fg-secondary); padding: 2px 0; }
.pr-opt-nested { padding-left: 16px; }
.pr-opt-dis { color: var(--fg-muted); }

.pr-section-head { font-size: 14px; font-weight: 800; padding: 12px 16px; border-bottom: 1px solid var(--border-subtle); }
.pr-people-table { width: 100%; border-collapse: collapse; }
.pr-people-table th { text-align: left; font-size: 11px; font-weight: 700; color: var(--fg-secondary); padding: 8px 16px; border-bottom: 1px solid var(--border-default); }
.pr-people-table td { padding: 8px 16px; border-bottom: 1px solid var(--border-subtle); vertical-align: middle; }
.pr-person-name { font-weight: 700; display: flex; align-items: center; gap: 8px; }
.pr-role-select { height: 30px; min-width: 150px; }
.pr-extras { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.pr-people-table td.pr-extras { display: table-cell; }
.pr-extras .pr-pill { margin: 1px 2px 1px 0; }
.pr-row-actions { text-align: right; white-space: nowrap; }
.pr-extra-row td { background: var(--bg-subtle); }
.pr-extra-pick { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.pr-extra-pick select { height: 30px; }
.pr-extra-pick select:first-child { min-width: 260px; }

/* Shared by other screens — were defined among the old grid's rules, kept here. */
.field-input-sm { height: 28px; font-size: 12px; flex: 1; min-width: 0; padding: 0 8px; }
.btn-xs { height: 26px; padding: 0 8px; font-size: 11px; }

/* ============================================================
   TIMESHEETS HUB
   ============================================================ */
.ts-hub-toolbar {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px;
}
.btn-group { display: flex; }
.btn-group .btn { border-radius: 0; border-right-width: 0; }
.btn-group .btn:first-child { border-radius: 6px 0 0 6px; }
.btn-group .btn:last-child  { border-radius: 0 6px 6px 0; border-right-width: 1px; }
.btn-group .btn.active { background: var(--ea-green-600); color: #fff; border-color: var(--ea-green-600); }

/* Status badges */
.badge {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
}
.badge-default { background: var(--ea-charcoal-100); color: var(--ea-charcoal-600); }
.badge-warning { background: #fef3c7; color: #92400e; }
.badge-danger  { background: #fee2e2; color: #991b1b; }
.badge-success { background: var(--ea-green-100); color: var(--ea-green-800); }

/* ============================================================
   LEAVE REQUESTS
   ============================================================ */
.leave-info-grid { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.leave-info-row { display: flex; gap: 12px; font-size: 13px; align-items: flex-start; }
.leave-info-label { font-weight: 600; color: var(--fg-secondary); min-width: 120px; flex-shrink: 0; }
.leave-dates-list { display: flex; flex-direction: column; gap: 6px; border: 1px solid var(--border-subtle); border-radius: 8px; padding: 10px 14px; background: var(--bg-subtle); }
.leave-date-row { display: flex; align-items: center; gap: 10px; font-size: 13px; cursor: pointer; }
.leave-date-row input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--ea-green-600); flex-shrink: 0; }

/* ============================================================
   UTILITIES
   ============================================================ */
.text-muted   { color: var(--fg-muted); }
.text-danger  { color: var(--ea-red-600); }
.text-success { color: var(--ea-green-600); }
.font-mono    { font-family: var(--font-mono); font-size: 12px; }

/* ============================================================
   H&S MODULE
   ============================================================ */

/* Sub-navigation tabs */
.hs-tabs {
  display: flex;
  gap: 2px;
  border-bottom: 2px solid var(--border-subtle);
  margin-bottom: 20px;
}
.hs-tab {
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-secondary);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.hs-tab:hover  { color: var(--fg-primary); }
.hs-tab.active { color: var(--ea-green-700); border-bottom-color: var(--ea-green-600); font-weight: 600; }

/* Sub-screen visibility */
.hs-tab-content           { display: none; }
.hs-tab-content.active    { display: block; }

/* QA tabs — identical style to H&S tabs */
/* Sub-tabs within a screen. Shared by QA and the Mail Assistant rather than duplicated —
   two tab bars that look almost alike is worse than one that looks the same. */
.qa-tabs, .ma-tabs { display: flex; gap: 2px; border-bottom: 2px solid var(--border-subtle); margin-bottom: 20px; }
.qa-tab, .ma-tab  { padding: 8px 16px; font-size: 13px; font-weight: 500; color: var(--fg-secondary); background: transparent; border: none; border-bottom: 2px solid transparent; margin-bottom: -2px; cursor: pointer; transition: color 0.15s, border-color 0.15s; }
.qa-tab:hover, .ma-tab:hover  { color: var(--fg-primary); }
.qa-tab.active, .ma-tab.active { color: var(--ea-green-700); border-bottom-color: var(--ea-green-600); font-weight: 600; }
.qa-tab-content           { display: none; }
.qa-tab-content.active    { display: block; }

/* Hub → Employees → the person window (2026-11-01). Two columns, three for short fields;
   one column on a narrow window. */
/* The tabs stay in view while a long tab scrolls — found on the first look, the Personal tab
   scrolled them off the top. -20px cancels the modal body's top padding. */
#modal-person .qa-tabs { position: sticky; top: -20px; z-index: 2; background: var(--bg-surface, #fff); margin: -20px 0 16px; padding-top: 20px; }
#modal-person textarea.field-input { min-height: 84px; resize: vertical; }
.pp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.pp-grid.pp-grid3 { grid-template-columns: 2fr 2fr 1.4fr; }
@media (max-width: 720px) { .pp-grid, .pp-grid.pp-grid3 { grid-template-columns: 1fr; } }
.pp-grid > .field-group { min-width: 0; }
.pp-sec { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ea-green-700);
  margin: 18px 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-subtle); }
.pp-switch { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 10px 0; border-bottom: 1px solid var(--border-subtle); }
.pp-switch:last-child { border-bottom: none; }
.pp-switch strong { display: block; font-size: 13px; font-weight: 600; }
.pp-switch span { display: block; font-size: 12px; color: var(--fg-secondary); margin-top: 2px; max-width: 560px; }
.pp-switch .toggle-switch { flex-shrink: 0; }
.pp-ro { font-size: 14px; padding: 8px 0; color: var(--fg-primary); }
.pp-note { font-size: 12.5px; color: var(--fg-secondary); background: var(--ea-charcoal-50, #f6f6f5); border-radius: 8px; padding: 10px 12px; margin: 4px 0 14px; }
.maint-tab-content        { display: none; }
.maint-tab-content.active { display: block; }

.acc-tab-content          { display: none; }
.acc-tab-content.active   { display: block; }

/* Training matrix */
.hs-matrix-table { border-collapse: collapse; font-size: 12px; min-width: 100%; }
.hs-matrix-table th,
.hs-matrix-table td { border: 1px solid var(--border-subtle); padding: 6px 10px; white-space: nowrap; }
.hs-matrix-table th { background: var(--bg-subtle); font-weight: 600; position: sticky; top: 0; z-index: 1; text-align: center; }
.hs-matrix-table th.hs-matrix-staff { position: sticky; left: 0; z-index: 2; min-width: 130px; text-align: left; }
.hs-matrix-table td.hs-matrix-cert  { font-size: 11px; position: sticky; left: 0; background: var(--bg-page); border-right: 2px solid var(--border-default); max-width: 220px; white-space: normal; }
.hs-matrix-table td.hs-matrix-cell  { text-align: center; cursor: pointer; }
.hs-matrix-table td.hs-matrix-cell:hover { background: var(--bg-subtle); }
.hs-matrix-cell-empty { font-size: 11px; color: var(--fg-muted); }

/* Risk chips */
.chip-risk-very_low  { background: var(--ea-green-100); color: var(--ea-green-800); }
.chip-risk-low       { background: var(--ea-green-100); color: var(--ea-green-800); }
.chip-risk-medium    { background: #fef3c7; color: #92400e; }
.chip-risk-moderate  { background: #fef3c7; color: #92400e; }
.chip-risk-high      { background: #fee2e2; color: #991b1b; }
.chip-risk-critical  { background: #991b1b; color: #fff; font-weight: 700; }

/* Incident type label */
.hs-type-label { font-size: 11px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--fg-secondary); }

/* Corrective actions list in detail */
.hs-ca-list { display: flex; flex-direction: column; gap: 8px; }
.hs-ca-item { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; background: var(--bg-subtle); border-radius: 6px; font-size: 13px; }
.hs-ca-item-desc { flex: 1; }
.hs-ca-item-meta { font-size: 11px; color: var(--fg-secondary); margin-top: 3px; }

/* Step builder rows */
.hs-step-input { width: 100%; padding: 5px 7px; font-size: 12px; border: 1px solid var(--border-default); border-radius: 4px; background: var(--bg-page); color: var(--fg-primary); resize: vertical; min-height: 34px; }
.hs-step-input:focus { outline: none; border-color: var(--ea-green-500); }
.hs-risk-sel { width: 100%; padding: 5px 4px; font-size: 11px; border: 1px solid var(--border-default); border-radius: 4px; background: var(--bg-page); color: var(--fg-primary); }

/* Involved parties */
.hs-party-item { display: flex; align-items: center; gap: 8px; padding: 7px 10px; background: var(--bg-subtle); border-radius: 5px; font-size: 13px; }
.hs-party-item .chip { flex-shrink: 0; }
.hs-party-name { flex: 1; }

/* ============================================================
   HAZARD REGISTER
   ============================================================ */
.haz-section-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin: 8px 0 4px; border-top: 1px solid var(--border-subtle); padding-top: 12px; }
.haz-risk-chip-display { height: 38px; display: flex; align-items: center; }
.haz-row td { vertical-align: top; padding-top: 10px; padding-bottom: 10px; }
.haz-row td:nth-child(3) { line-height: 1.4; }

/* ============================================================
   PRE-QUAL
   ============================================================ */

/* System selector tabs */
.pq-system-tabs { display: flex; gap: 4px; margin-bottom: 16px; }
.pq-sys-tab { padding: 7px 18px; border: 1px solid var(--border-default); border-radius: 6px; background: var(--bg-page); color: var(--fg-secondary); font-size: 13px; font-weight: 500; cursor: pointer; transition: all .15s; }
.pq-sys-tab:hover { border-color: var(--ea-green-500); color: var(--fg-primary); }
.pq-sys-tab.active { background: var(--ea-green-500); border-color: var(--ea-green-500); color: #fff; }

/* System info card */
.pq-info-card { padding: 16px 20px 12px; margin-bottom: 12px; display: flex; align-items: flex-start; gap: 24px; flex-wrap: wrap; }
.pq-info-grid { display: flex; gap: 32px; flex: 1; flex-wrap: wrap; }
.pq-info-item { display: flex; flex-direction: column; gap: 2px; }
.pq-info-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.pq-info-value { font-size: 13px; color: var(--fg-primary); }
.pq-info-value.pq-overdue { color: var(--danger); font-weight: 600; }

/* Score summary card */
.pq-score-card { padding: 16px 20px; margin-bottom: 12px; }
.pq-score-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.pq-score-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.pq-score-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pq-score-pct { font-size: 13px; color: var(--fg-secondary); }

/* Progress bar */
.pq-progress-bar { height: 8px; border-radius: 4px; overflow: hidden; display: flex; background: var(--bg-subtle); margin-bottom: 8px; }
.pq-bar-met     { background: var(--ea-green-500); transition: width .3s; }
.pq-bar-partial { background: #f59e0b; transition: width .3s; }
.pq-bar-gap     { background: var(--danger); transition: width .3s; }
.pq-bar-na      { background: var(--border-default); transition: width .3s; }

/* Score legend */
.pq-score-legend { display: flex; align-items: center; gap: 12px; font-size: 11px; flex-wrap: wrap; }
.pq-leg { display: flex; align-items: center; gap: 4px; color: var(--fg-secondary); }
.pq-leg::before { content: ''; display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.pq-leg-green::before { background: var(--ea-green-500); }
.pq-leg-amber::before { background: #f59e0b; }
.pq-leg-red::before   { background: var(--danger); }
.pq-leg-grey::before  { background: var(--border-default); }
.pq-leg-note { margin-left: auto; color: var(--text-muted); font-size: 11px; }

/* Questions table */
.pq-table tbody tr.pq-row { cursor: pointer; }
.pq-table tbody tr.pq-row:hover { background: var(--bg-subtle); }
.pq-table tbody tr.pq-group-row { background: var(--bg-subtle); pointer-events: none; }
.pq-table tbody tr.pq-group-row td { padding: 6px 12px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.pq-ref { display: inline-block; padding: 2px 7px; background: var(--bg-subtle); border-radius: 4px; font-size: 11px; font-weight: 600; font-family: var(--font-mono, monospace); color: var(--fg-secondary); border: 1px solid var(--border-default); }
.pq-ns-label { font-size: 10px; color: var(--text-muted); line-height: 1.4; }
.pq-q-col { width: 200px; }
.pq-name-cell { max-width: 200px; white-space: normal; }
.pq-notes-cell { font-size: 12px; max-width: 360px; }
.pq-ai-cell { text-align: right; }
.pq-ai-btn { font-weight: 700; letter-spacing: 0.03em; }
.pq-notes-text { color: var(--fg-secondary); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.pq-notes-empty { color: var(--text-muted); font-style: italic; }
.pq-upd-cell { font-size: 11px; color: var(--text-muted); white-space: nowrap; }

/* Edit question modal */
.pq-edit-desc { font-size: 13px; color: var(--fg-secondary); line-height: 1.55; margin-bottom: 16px; background: var(--bg-subtle); padding: 10px 12px; border-radius: 6px; border-left: 3px solid var(--border-default); }

/* Gap review modal */
.pq-review-modal-body { max-height: calc(85vh - 100px); overflow-y: auto; }
.pq-review-summary { margin-bottom: 16px; }
.pq-review-alerts { background: var(--bg-subtle); border-radius: 6px; padding: 12px 16px; margin-bottom: 20px; }
.pq-review-alerts-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 8px; }
.pq-review-alerts ul { margin: 0; padding-left: 16px; font-size: 13px; line-height: 1.65; color: var(--fg-secondary); }
.pq-review-h { font-size: 13px; font-weight: 600; margin: 0 0 8px; }
.pq-review-h-gap { color: var(--danger); }
.pq-review-h-partial { color: #b45309; margin-top: 20px; }
.pq-review-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }
.pq-review-item { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: 6px; border-left: 3px solid transparent; font-size: 13px; }
.pq-review-gap     { background: #fef2f2; border-color: var(--danger); }
.pq-review-partial { background: #fffbeb; border-color: #f59e0b; }
.pq-review-ref { flex-shrink: 0; font-family: var(--font-mono, monospace); font-size: 11px; font-weight: 600; padding: 2px 6px; background: rgba(0,0,0,.06); border-radius: 3px; margin-top: 2px; }
.pq-review-desc { color: var(--fg-secondary); line-height: 1.45; font-size: 12px; }
.pq-review-footer { font-size: 12px; color: var(--text-muted); margin-top: 12px; }

/* Contractor review checklist */
.ct-rev-item { padding: 10px 0; border-bottom: 1px solid var(--border-subtle); }
.ct-rev-q { font-size: 13px; font-weight: 500; margin-bottom: 6px; }
.ct-rev-opts { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 6px; }
.ct-rev-opts label { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; color: var(--fg-secondary); cursor: pointer; }
.ct-rev-opts input { accent-color: var(--ea-green-600); cursor: pointer; }
.ct-rev-note { font-size: 12px; padding: 6px 8px; }

/* H&S dashboard data-integrity alerts */
.hs-data-alerts { background: #fffbeb; border: 1px solid #f59e0b; border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; }
.hs-data-alerts-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ea-amber-700, #b45309); margin-bottom: 6px; }
.hs-data-alerts ul { margin: 0; padding-left: 18px; }
.hs-data-alerts li { font-size: 13px; color: var(--fg-secondary); line-height: 1.5; }

/* Structured pre-qual notes (bullet list + plain lines) */
.pq-notes-line { line-height: 1.45; }
.pq-notes-list { margin: 3px 0 0; padding-left: 16px; }
.pq-notes-list li { margin: 1px 0; line-height: 1.4; }
.pq-notes-line + .pq-notes-list { margin-top: 2px; }

/* ============================================================
   MSDS register
   ============================================================ */
.msds-status-chip { display:inline-flex; align-items:center; gap:4px; padding:2px 8px; border-radius:99px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; white-space:nowrap; }
.msds-status-current  { background:var(--ea-green-100); color:var(--ea-green-800); }
.msds-status-due_soon { background:#fef3c7; color:#92400e; }
.msds-status-expired  { background:#fee2e2; color:#991b1b; }
.msds-status-unknown  { background:var(--bg-subtle); color:var(--text-muted); }

.msds-dropzone { border:2px dashed var(--border-default); border-radius:8px; padding:24px 16px; display:flex; flex-direction:column; align-items:center; gap:8px; cursor:pointer; transition:border-color .15s, background .15s; text-align:center; color:var(--text-muted); font-size:13px; }
.msds-dropzone:hover, .msds-dropzone.drag-over { border-color:var(--ea-blue-500); background:var(--ea-blue-50); }
.msds-dropzone.has-file { border-color:var(--ea-green-500); background:var(--ea-green-50); color:var(--ea-green-800); }
.msds-last-refreshed { font-size:12px; color:var(--text-muted); }

/* ============================================================
   Drop zone — THE file upload control for the whole Hub
   ============================================================

   ⚠️ Every file upload in the Hub is a drop zone with click-to-browse. Never a bare
   <input type="file">, never a "Choose file" button. Use `.hub-dropzone` with
   `HubDrop.attach()` in app.js — see documentation/envirohub.md → "File uploads".

   The look is lifted from the maintenance equipment-schedule zone, which is the one
   James signed off: a dashed border that turns Enviroair green on hover or drag, and a
   status line underneath naming what was picked.

   It replaces two habits that had both grown up in here — five copies of the same
   twelve lines of inline style pasted into index.html, and a `.msds-dropzone` class
   that highlighted BLUE, which is not a brand colour. New zones use this one; the
   existing ones predate it and are left alone rather than rewritten while they work. */
.hub-dropzone {
  border: 2px dashed var(--border-subtle);
  border-radius: 8px;
  padding: 18px 16px;
  text-align: center;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.hub-dropzone:hover,
.hub-dropzone.drag-over { border-color: var(--accent); background: var(--bg-subtle); }
.hub-dropzone.has-file  { border-color: var(--accent); background: var(--bg-subtle); }
/* pointer-events:none throughout — a drag that lands on a child element must still be a
   drag onto the zone, and a click on the text must still open the picker. */
.hub-dropzone-label  { font-size: 12px; color: var(--fg-secondary); pointer-events: none; }
.hub-dropzone-browse { color: var(--accent); }
.hub-dropzone-hint   { color: var(--fg-tertiary); margin-left: 4px; }
.hub-dropzone-status { font-size: 11px; margin-top: 4px; color: var(--fg-secondary); pointer-events: none; }
.hub-dropzone-status.is-error { color: var(--fg-danger); }

/* ============================================================
   Policies & procedures
   ============================================================ */
.policy-type-chip { display:inline-block; padding:2px 8px; border-radius:99px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; }
.policy-type-Policy    { background:#e0f2fe; color:#075985; }
.policy-type-Procedure { background:#f3e8ff; color:#6b21a8; }
.policy-link-btn { display:inline-flex; align-items:center; gap:4px; font-size:12px; font-weight:500; padding:3px 8px; border-radius:4px; background:var(--bg-subtle); border:1px solid var(--border-default); color:var(--text-primary); cursor:pointer; text-decoration:none; transition:background .12s; }
.policy-link-btn:hover { background:var(--bg-hover); }

/* Path picker */
.path-picker-row { display:flex; align-items:center; gap:8px; }
.path-picker-display { flex:1; min-width:0; font-size:13px; color:var(--text-primary); background:var(--bg-subtle); border:1px solid var(--border-default); border-radius:6px; padding:7px 10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.path-picker-display:empty::before { content:'Not set'; color:var(--text-muted); }
.path-picker-breadcrumb { display:flex; align-items:center; flex-wrap:wrap; gap:2px; padding:0 0 12px; font-size:12px; }
.crumb { background:none; border:none; padding:2px 4px; cursor:default; color:var(--text-muted); font-size:12px; }
.crumb-link { cursor:pointer; color:var(--ea-blue-600); text-decoration:underline; }
.crumb-link:hover { color:var(--ea-blue-700); }
.crumb-active { font-weight:600; color:var(--text-primary); }
.crumb-sep { color:var(--text-muted); padding:0 1px; }
.path-picker-list { display:flex; flex-direction:column; gap:2px; max-height:320px; overflow-y:auto; margin:0 -24px; padding:0 24px 24px; }
.path-picker-item { display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:6px; background:none; border:none; cursor:pointer; text-align:left; font-size:13px; color:var(--text-primary); transition:background .12s; }
.path-picker-item:hover { background:var(--bg-hover); }
.path-picker-item svg { flex-shrink:0; color:var(--text-muted); }
.path-picker-empty { padding:24px 0; text-align:center; font-size:13px; color:var(--text-muted); }

/* SDS parse status */
.msds-parse-status { display:flex; align-items:center; gap:8px; margin-top:14px; font-size:13px; color:var(--text-muted); }
.spinner-sm { width:14px; height:14px; border-width:2px; }
.msds-parse-note { margin-top:12px; font-size:12px; color:var(--ea-orange-700,#b45309); background:var(--ea-orange-50,#fff7ed); border:1px solid var(--ea-orange-200,#fed7aa); border-radius:6px; padding:8px 10px; }

/* MSDS bulk import */
.msds-bulk-status { padding:4px 0 12px; font-size:13px; }
.msds-bulk-hint { margin:0 0 12px; font-size:12px; color:var(--text-muted); }
.msds-bulk-table td { vertical-align:middle; padding:6px 12px; }
.field-input-sm { padding:5px 8px; font-size:12px; height:auto; }
.msds-bulk-table .bulk-name-input { min-width:160px; }
.btn-danger { background:var(--ea-red-600,#dc2626); color:#fff; border-color:var(--ea-red-600,#dc2626); }
.btn-danger:hover { background:var(--ea-red-700,#b91c1c); }
/* MSDS PDF modal checklist */
.msds-pdf-checklist { display:flex; flex-direction:column; gap:2px; max-height:360px; overflow-y:auto; margin:0 -24px; padding:0 24px 4px; }
.msds-pdf-check-row { display:flex; align-items:center; gap:10px; padding:7px 10px; border-radius:6px; cursor:pointer; font-size:13px; }
.msds-pdf-check-row:hover { background:var(--bg-hover); }
.msds-pdf-check-row input[type="checkbox"] { flex-shrink:0; accent-color:var(--ea-green-600); width:15px; height:15px; cursor:pointer; }
.msds-pdf-cb-name { flex:1; min-width:0; }
.msds-pdf-cb-chip { font-size:11px; font-weight:600; padding:2px 7px; border-radius:99px; text-transform:capitalize; flex-shrink:0; }
.chip-green { background:var(--ea-green-100,#dcfce7); color:var(--ea-green-800,#166534); }
.chip-amber { background:var(--ea-amber-100,#fef3c7); color:var(--ea-amber-800,#92400e); }
.chip-red   { background:#fee2e2; color:#991b1b; }

.sortable-th:hover { background:var(--bg-hover); }
.sort-active { color:var(--text-primary); }
.btn-xs { padding:3px 6px; font-size:11px; }

/* MSDS duplicate confirm */
.msds-dup-names { background:var(--bg-subtle); border:1px solid var(--border-default); border-radius:8px; padding:12px 14px; display:flex; flex-direction:column; gap:8px; }
.msds-dup-row { display:flex; align-items:baseline; gap:10px; }
.msds-dup-label { font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted); width:52px; flex-shrink:0; }
.msds-dup-value { font-size:13px; font-weight:500; color:var(--text-primary); }

/* Resource scope toggle */
.res-scope-toggle { display:flex; gap:0; border:1px solid var(--border-default); border-radius:8px; overflow:hidden; width:fit-content; }
.res-scope-btn { padding:7px 16px; font-size:13px; font-weight:500; background:none; border:none; cursor:pointer; color:var(--text-muted); transition:background .1s,color .1s; }
.res-scope-btn.active { background:var(--ea-green-600); color:#fff; }
.res-scope-btn:not(.active):hover { background:var(--bg-hover); }

/* Resource job selected display */
.res-job-selected { margin-top:6px; font-size:13px; font-weight:500; color:var(--text-primary); padding:6px 10px; background:var(--bg-subtle); border-radius:6px; border:1px solid var(--border-default); }

/* Show archived toggle */
.res-archived-toggle { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-muted); cursor:pointer; white-space:nowrap; }
.res-archived-toggle input { accent-color:var(--ea-green-600); cursor:pointer; }

/* Version bump toggle */
.version-bump-toggle { display:flex; gap:10px; }
.version-bump-opt { display:flex; align-items:center; gap:8px; padding:9px 14px; border-radius:8px; border:1px solid var(--border-default); cursor:pointer; font-size:13px; flex:1; transition:border-color .12s, background .12s; }
.version-bump-opt:has(input:checked) { border-color:var(--ea-green-600); background:var(--ea-green-50,#f0fdf4); }
.version-bump-opt input[type="radio"] { accent-color:var(--ea-green-600); flex-shrink:0; }
.version-bump-label { font-weight:500; }
.version-bump-hint { margin-left:auto; font-size:11px; font-family:var(--font-mono); color:var(--text-muted); }

/* AI assistant modal */
.res-ai-messages { flex:1; overflow-y:auto; padding:16px 20px; display:flex; flex-direction:column; gap:12px; background:var(--bg-subtle); }
.res-ai-msg { display:flex; }
.res-ai-msg--user { justify-content:flex-end; }
.res-ai-msg--ai   { justify-content:flex-start; }
.res-ai-msg-bubble { max-width:88%; padding:10px 14px; border-radius:12px; font-size:13px; line-height:1.55; }
.res-ai-msg--user .res-ai-msg-bubble { background:var(--ea-green-600); color:#fff; border-bottom-right-radius:3px; }
.res-ai-msg--ai   .res-ai-msg-bubble { background:#fff; border:1px solid var(--border-default); border-bottom-left-radius:3px; color:var(--text-primary); }
.res-ai-typing { display:flex; align-items:center; gap:5px; padding:14px 16px; }
.res-ai-typing span { width:7px; height:7px; border-radius:50%; background:var(--text-muted); animation:ai-bounce .9s infinite both; }
.res-ai-typing span:nth-child(2) { animation-delay:.15s; }
.res-ai-typing span:nth-child(3) { animation-delay:.3s; }
@keyframes ai-bounce { 0%,80%,100%{transform:translateY(0)} 40%{transform:translateY(-6px)} }
.res-ai-input-row { display:flex; gap:8px; padding:12px 16px; border-top:1px solid var(--border-default); flex-shrink:0; background:#fff; }
.res-ai-textarea { flex:1; resize:none; font-size:13px; padding:8px 10px; }
.res-ai-footer { display:flex; justify-content:flex-end; gap:8px; padding:10px 16px; border-top:1px solid var(--border-default); flex-shrink:0; background:#fff; }

/* PDF viewer modal */
.modal-pdf { padding:0; }
.modal-pdf .modal-header { padding:16px 20px; border-bottom:1px solid var(--border-default); }

/* Instance option picker tiles */
.inst-option-tile { display:flex; flex-direction:column; align-items:center; gap:10px; padding:20px 12px; border:1px solid var(--border-default); border-radius:10px; background:var(--bg-surface); cursor:pointer; text-align:center; transition:border-color .15s,background .15s; }
.inst-option-tile:hover { border-color:var(--color-primary); background:var(--bg-subtle); }
.inst-option-tile svg { color:var(--color-primary); }
.inst-opt-label { font-size:14px; font-weight:600; color:var(--text-primary); }
.inst-opt-desc  { font-size:12px; color:var(--text-muted); line-height:1.4; }

/* ── Accounting: source document beside the detail ───────────────────────────
   The job on these screens is reconciling against Xero, so the invoice or
   statement sits next to the figures read off it. Collapsible, because the
   split is tight on a laptop and sometimes only the numbers are wanted. */
/* The document has to be READABLE, not merely present — a shrunken invoice is
   no use for checking a figure. So with the preview open the modal takes
   almost the whole window and the PDF fills its half top to bottom, rather
   than sitting in a fixed 640px box inside a 1240px dialog. */
.acc-doc-modal { width: 96vw; height: 92vh; }
.acc-doc-body { display: flex; gap: 18px; align-items: stretch; overflow: hidden; }
.acc-doc-preview { flex: 0 0 52%; min-width: 0; display: flex; flex-direction: column; }
.acc-doc-preview iframe {
  flex: 1 1 auto; width: 100%; min-height: 0; display: block;
  border: 1px solid var(--border-default); border-radius: 8px; background: var(--bg-muted);
}
/* Each column scrolls on its own — the detail must not push the PDF off screen. */
.acc-doc-detail { flex: 1 1 auto; min-width: 0; overflow-y: auto; }

/* Collapsed: the pane goes, and the modal returns to an ordinary dialog rather
   than leaving the detail stretched across a full-screen box. */
.acc-doc-modal.preview-hidden .acc-doc-preview { display: none; }
.acc-doc-modal.preview-hidden { max-width: 820px; width: 100%; height: auto; }
.acc-doc-modal.preview-hidden .acc-doc-body { display: block; overflow-y: auto; }

/* margin-left:auto pushes the toggle to the right of the title; the close
   button keeps its place because .modal-header is space-between. */
.acc-preview-toggle { margin-left: auto; flex-shrink: 0; }

/* Side by side stops being readable well before the modal stops fitting. */
@media (max-width: 1100px) {
  .acc-doc-modal { height: auto; }
  .acc-doc-body { flex-direction: column; overflow-y: auto; }
  .acc-doc-preview { flex: 1 1 auto; width: 100%; }
  .acc-doc-preview iframe { height: 70vh; flex: 0 0 auto; }
}
/* ── the maintenance agreement, beside the contract record ─────────────────────
   The third use of the accounting split, and the same job again: a document on one side and
   the figures somebody read off it on the other. What it adds is a line above and below the
   frame — above, which document this is; below, what else is in the folder. A contract screen
   has to be able to say "this is the agreement in force", and where it cannot, say so. */
/* Kept deliberately thin. Every pixel these two strips take is a pixel off the document, and a
   contract read at half width has none to spare — so they are one line each, no more. */
.maint-doc-head, .maint-doc-foot {
  flex: 0 0 auto; font-size: 12px; line-height: 1.3; color: var(--text-muted);
  padding: 5px 9px; border: 1px solid var(--border-subtle); background: var(--bg-subtle);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.maint-doc-head { border-radius: 8px 8px 0 0; border-bottom: none; font-weight: 600; color: var(--text-primary); }
.maint-doc-foot { border-radius: 0 0 8px 8px; border-top: none; }
.maint-doc-foot:empty { display: none; }
#maint-doc-preview iframe { border-radius: 0; }
.maint-doc-open { flex: 0 0 auto; font-size: 12px; padding-top: 6px; }
/* Nothing filed yet is an ordinary state, not an error — most of the register is still waiting
   on somebody to say which version is in force. It should read as information, not a fault. */
.maint-doc-none {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 24px; line-height: 1.6; font-size: 13px;
  color: var(--text-muted); background: var(--bg-muted);
  border: 1px dashed var(--border-default); border-radius: 0 0 8px 8px;
}
.maint-preview-toggle { margin-left: auto; flex-shrink: 0; }
#maint-edit-modal.preview-hidden .acc-doc-preview { display: none; }
#maint-edit-modal.preview-hidden { max-width: 560px; width: 100%; height: auto; }
#maint-edit-modal.preview-hidden .acc-doc-body { display: block; overflow-y: auto; }

/* ── the invitation email, beside the record made from it ──────────────────────
   Borrows the accounting split wholesale — it is the same job: a document on the left and the
   figures somebody read off it on the right. What is different is the strip above and below
   the frame, because an email has a sender and a date that the body does not repeat, and
   attachments that are not in the body at all. */
.tnd-inv-modal .acc-doc-preview iframe { background: #fff; }
.tnd-inv-preview-toggle { margin-left: auto; flex-shrink: 0; }
.tnd-inv-modal.preview-hidden .acc-doc-preview { display: none; }
.tnd-inv-modal.preview-hidden { max-width: 760px; width: 100%; height: auto; }
.tnd-inv-modal.preview-hidden .acc-doc-body { display: block; overflow-y: auto; }

.tnd-mailhead {
  border: 1px solid var(--border-subtle); border-bottom: none;
  border-radius: 8px 8px 0 0; background: var(--bg-subtle);
  padding: 8px 10px; font-size: 12.5px; flex: 0 0 auto;
}
.tnd-mailhead-line { display: flex; align-items: baseline; gap: 6px; }
.tnd-mailhead-subj { font-weight: 650; margin-top: 2px; }
/* The frame joins the strip above it rather than floating under it — one object, not two. */
.tnd-mailhead + iframe { border-top-left-radius: 0; border-top-right-radius: 0; }

.tnd-mailfoot { flex: 0 0 auto; margin-top: 8px; }
.tnd-mailatts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 12px; }
/* ⚠️ Amber, not grey. Held-back pictures are a decision the screen made on your behalf, and it
   has to be visible enough that nobody concludes the email simply looks like that. */
.tnd-mailblocked {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 8px; padding: 6px 9px; font-size: 12px;
  border: 1px solid var(--ea-amber-400, #fbbf24); background: var(--ea-amber-50, #fffbeb);
  border-radius: var(--radius-md);
}
.tnd-mailpick { margin-top: 8px; }

.acc-doc-open {
  display: block; margin-top: 6px; font-size: 12px; text-align: right;
  color: var(--color-primary); text-decoration: none;
}
.acc-doc-open:hover { text-decoration: underline; }

/* ============================================================
   ACCOUNTING — RULES TAB
   ============================================================ */

/* The explainer above each list. Rules are tried in order and the first match
   wins; that is the one thing someone editing them has to know, so it is on the
   screen rather than in a document nobody opens. */
.acc-rules-intro {
  font-size: 12.5px; line-height: 1.65; color: var(--fg-secondary);
  background: var(--bg-subtle); border: 1px solid var(--border-subtle);
  border-radius: 8px; padding: 12px 14px; margin: 0 0 18px;
}
.acc-rules-intro strong { color: var(--fg-primary); font-weight: 600; }

.acc-rules-heading {
  font-size: 13px; font-weight: 600; color: var(--fg-primary);
  margin: 22px 0 2px; letter-spacing: 0.01em;
}
.acc-rules-sub { font-size: 12px; color: var(--fg-tertiary); margin: 0 0 10px; }

/* The match conditions, rendered one per line as plain English. */
.acc-cond { font-size: 12px; line-height: 1.6; color: var(--fg-secondary); }
.acc-cond em { font-style: normal; font-weight: 600; color: var(--fg-primary); }

/* A rule that is switched off still has to be readable — it is the thing you
   came to turn back on — so this dims it without greying it out. */
.acc-rule-off td { opacity: 0.55; }

/* Test results, inside the editor. Deliberately in the modal rather than a
   screen of its own: the point is to change the rule and test again. */
.acc-test-panel {
  border: 1px solid var(--border-subtle); border-radius: 8px;
  padding: 12px 14px; margin-bottom: 16px; background: var(--bg-subtle);
  font-size: 12.5px; line-height: 1.6;
}
.acc-test-headline { font-weight: 600; color: var(--fg-primary); margin: 0 0 8px; }
.acc-test-warn { color: var(--ea-amber-800, #92400e); }
.acc-test-list { max-height: 220px; overflow-y: auto; margin: 8px 0 0; }
.acc-test-row {
  padding: 6px 0; border-top: 1px solid var(--border-subtle);
  display: flex; gap: 8px; align-items: flex-start;
}
.acc-test-row .acc-test-subject { flex: 1 1 auto; min-width: 0; }
.acc-test-row .td-secondary { display: block; }

.acc-rule-error {
  background: var(--ea-red-100); color: var(--ea-red-600);
  border-radius: 8px; padding: 10px 12px; font-size: 12.5px; line-height: 1.5;
  margin-bottom: 16px;
}

/* ============================================================
   RULES SCREEN
   ============================================================ */

.rules-section { margin-bottom: 28px; }
.rules-section__head {
  display: flex; align-items: baseline; gap: 10px; margin: 0 0 4px;
}
.rules-section__head h3 {
  font-size: 14px; font-weight: 600; color: var(--fg-primary); margin: 0;
}
.rules-section__count { font-size: 12px; color: var(--fg-tertiary); }
.rules-section__sub { font-size: 12px; color: var(--fg-tertiary); margin: 0 0 12px; }

/* A rule reads as a paragraph, not a row of fields — that is the whole point of the
   builder. The precise conditions sit underneath in small type so the plain-English
   version is never the only record of what it actually does. */
.rule-card {
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: 10px; padding: 14px 16px; margin-bottom: 10px; cursor: pointer;
  transition: border-color 120ms, box-shadow 120ms;
}
.rule-card:hover { border-color: var(--border-default); box-shadow: var(--shadow-sm); }
.rule-card.is-off { opacity: 0.6; }
.rule-card__top { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.rule-card__name { font-size: 13.5px; font-weight: 600; color: var(--fg-primary); }
.rule-card__order {
  font-size: 11px; color: var(--fg-tertiary); background: var(--bg-subtle);
  border-radius: 5px; padding: 2px 6px; white-space: nowrap;
}
.rule-card__summary { font-size: 12.5px; line-height: 1.65; color: var(--fg-secondary); margin: 0; }
.rule-card__cond {
  font-size: 11.5px; line-height: 1.6; color: var(--fg-muted);
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-subtle);
}
.rule-card__cond em { font-style: normal; color: var(--fg-secondary); font-weight: 500; }
.rule-card__spacer { margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* ── the builder conversation ── */
.rb-transcript { margin-bottom: 16px; }
.rb-msg {
  border-radius: 10px; padding: 10px 13px; margin-bottom: 10px;
  font-size: 12.5px; line-height: 1.65;
}
.rb-msg--you { background: var(--bg-subtle); color: var(--fg-secondary); }
.rb-msg--ai  { background: var(--ea-green-50); color: var(--fg-primary); }
.rb-msg__who {
  display: block; font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--fg-tertiary); margin-bottom: 4px;
}

/* The proposal — what he approves instead of a form. */
.rb-proposal {
  border: 1px solid var(--border-default); border-radius: 10px;
  padding: 14px 16px; margin-bottom: 10px; background: var(--bg-surface);
}
.rb-proposal__name { font-size: 14px; font-weight: 600; color: var(--fg-primary); margin: 0 0 8px; }
.rb-proposal__summary { font-size: 13px; line-height: 1.7; color: var(--fg-secondary); margin: 0 0 12px; }
.rb-proposal__guards { margin: 0; padding-left: 18px; }
.rb-proposal__guards li {
  font-size: 12px; line-height: 1.6; color: var(--fg-secondary); margin-bottom: 4px;
}
.rb-proposal__label {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--fg-tertiary); margin: 0 0 6px;
}
.rb-warn {
  background: var(--ea-amber-100, #fef3c7); color: var(--ea-amber-800, #92400e);
  border-radius: 8px; padding: 10px 12px; font-size: 12.5px; line-height: 1.55;
  margin-top: 12px;
}

/* Whether a rule confirms the document before acting is the most important thing about it —
   James, 2026-08-08 — so it gets its own line on the card rather than hiding among the
   conditions. Green because it is the safe state; its absence is what deserves a second look. */
.rule-card__confirm {
  display: flex; align-items: center; gap: 6px; margin-top: 8px;
  font-size: 11.5px; line-height: 1.5; color: var(--ea-green-700);
}
.rule-card__confirm svg { flex: 0 0 auto; }

/* ── Inbox review ────────────────────────────────────────────────────────────
   A queue worked through in one sitting, so the card is built for pace: the
   subject reads first, the answer buttons are one row, and a card that has been
   answered visibly recedes rather than disappearing — so progress is felt and a
   mistake is still reachable. */

.mr-progress { display: flex; align-items: center; gap: 12px; margin: 0 0 18px; }
.mr-progress__bar {
  flex: 1; height: 6px; border-radius: 999px;
  background: var(--bg-muted); overflow: hidden;
}
.mr-progress__fill {
  height: 100%; background: var(--ea-green-600);
  border-radius: 999px; transition: width .25s ease;
}
.mr-progress__text { font-size: 12px; color: var(--fg-secondary); white-space: nowrap; }
.mr-progress__text strong { color: var(--fg-primary); font-weight: 600; }

.mr-card {
  border: 1px solid var(--border-subtle); border-radius: 8px;
  background: var(--bg-surface); padding: 12px 14px; margin-bottom: 8px;
}
.mr-card--done { background: var(--bg-subtle); border-color: var(--border-subtle); }

.mr-card__head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 5px;
}
.mr-card__meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--fg-tertiary); min-width: 0;
}
.mr-card__head > :last-child { margin-left: auto; }
.mr-card__date { font-variant-numeric: tabular-nums; white-space: nowrap; }
.mr-card__from {
  color: var(--fg-secondary); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22rem;
}

.mr-chip {
  font-size: 10.5px; letter-spacing: .02em; padding: 1px 6px; border-radius: 999px;
  background: var(--ea-green-50); color: var(--ea-green-700);
  border: 1px solid var(--ea-green-200); white-space: nowrap;
}
.mr-chip--muted {
  background: var(--bg-muted); color: var(--fg-tertiary); border-color: var(--border-subtle);
}

/* .mr-card__subject is defined once, further down — it became the open/close control. */
.mr-card--done .mr-card__subject { font-weight: 400; color: var(--fg-secondary); }

.mr-card__picks { display: flex; flex-wrap: wrap; gap: 4px; }
.mr-pick { font-size: 11.5px; }
.mr-pick--on {
  background: var(--ea-green-600); color: #fff; border-color: var(--ea-green-600);
}

.mr-card__answer { font-size: 12.5px; color: var(--fg-secondary); }
.mr-card__answer strong { color: var(--ea-green-700); font-weight: 600; }
.mr-card__by { color: var(--fg-muted); font-size: 11px; margin-left: 6px; }

/* Where a message sits now — from Gmail, shown with the body. There is deliberately no
   location in the list itself: the scan log cannot supply one. */
.mr-now { color: var(--ea-green-700); font-weight: 600; }

/* ── what the assistant thought ──────────────────────────────────────────────
   A disagreement is the only thing worth looking at twice, so it is the only
   state that carries weight. Agreement is deliberately quiet — a screen where
   every row shouts teaches you to stop reading it. */

.mr-judge {
  margin-top: 10px; padding: 9px 11px; border-radius: 6px;
  border: 1px solid var(--border-subtle); border-left: 3px solid var(--border-default);
  background: var(--bg-subtle); font-size: 12.5px;
}
.mr-judge--agrees  { border-left-color: var(--ea-green-500); }
.mr-judge--differs { border-left-color: var(--ea-amber-600); background: var(--ea-amber-100); }
.mr-judge--unseen  { border-left-color: var(--ea-sky-500); }
.mr-judge--err     { border-left-color: var(--ea-medgas-red); color: var(--ea-medgas-red); }

.mr-judge__head {
  display: flex; align-items: baseline; gap: 8px;
  color: var(--fg-primary); line-height: 1.5;
}
.mr-judge__head strong { font-weight: 600; }
.mr-judge__conf {
  margin-left: auto; flex: 0 0 auto;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  padding: 1px 6px; border-radius: 999px;
  background: var(--bg-muted); color: var(--fg-tertiary);
}
.mr-judge__conf--high   { background: var(--ea-green-100); color: var(--ea-green-900); }
.mr-judge__conf--medium { background: var(--ea-amber-100); color: #8a5a17; }
.mr-judge__conf--low    { background: var(--bg-muted);     color: var(--fg-secondary); }

.mr-judge__why { margin-top: 4px; color: var(--fg-secondary); line-height: 1.55; }

/* Training screen. Two sections, both of which should empty — the headings carry a count
   because "how many are left" is the only number that matters on a queue. */
.mr-sect {
  display: flex; align-items: baseline; gap: 8px;
  margin: 22px 0 2px; font-size: 15px; font-weight: 600; color: var(--fg-primary);
}
.mr-sect:first-child { margin-top: 4px; }
.mr-sect__n {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  padding: 1px 7px; border-radius: 999px;
  background: var(--bg-muted); color: var(--fg-secondary);
}
.mr-sect__blurb {
  margin: 0 0 10px; max-width: 68ch;
  color: var(--fg-secondary); font-size: 13px; line-height: 1.55;
}

/* The quiet count sits in the toolbar, deliberately understated: it is reassurance that the
   assistant is working, not a thing to act on. */
.mr-quiet { color: var(--fg-secondary); font-size: 13px; }
.mr-quiet strong { color: var(--fg-primary); font-weight: 600; }

/* The full category list, only shown when "Something else" is pressed. */
.mr-card__picks--all {
  margin-top: 6px; padding-top: 10px;
  border-top: 1px dashed var(--border-subtle);
}

/* "None of these" sits at the end of the grid and is not a category — it opens a sentence.
   Dashed, so it reads as an escape hatch rather than the twenty-third option. */
.mr-pick--none {
  border-style: dashed !important;
  color: var(--fg-secondary);
}

/* What it would DO, as opposed to what it thinks. Set apart from the reason above it with a
   rule rather than a box — it is the same thought continued, not a separate panel. */
.mr-prop {
  display: flex; align-items: baseline; gap: 8px;
  margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--border-subtle);
  color: var(--fg-primary); line-height: 1.5;
}
.mr-prop__tag {
  flex: 0 0 auto;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  padding: 1px 6px; border-radius: 999px;
  background: var(--ea-green-100); color: var(--ea-green-900);
}
/* Held back for want of confidence — the same standing as "do nothing", so it reads quietly
   rather than as a second opinion. */
.mr-prop--held             { color: var(--fg-secondary); }
.mr-prop--held .mr-prop__tag { background: var(--bg-muted); color: var(--fg-tertiary); }

/* Amber wash needs a readable body colour in dark mode, where it is a dark tint. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mr-judge--differs { background: #2c2417; }
}
:root[data-theme="dark"] .mr-judge--differs { background: #2c2417; }

/* Subject doubles as the open/close control, so it has to look pressable without becoming
   a button — it is still the thing you read first. */
.mr-card__subject {
  display: flex; align-items: baseline; gap: 7px; width: 100%;
  background: none; border: 0; padding: 0; margin-bottom: 10px;
  font: inherit; font-size: 13.5px; line-height: 1.45; font-weight: 500;
  color: var(--fg-primary); text-align: left; cursor: pointer;
}
.mr-card__subject:hover { color: var(--ea-green-700); }
.mr-card__subject:focus-visible { outline: 2px solid var(--ea-green-600); outline-offset: 3px; border-radius: 3px; }
.mr-card__caret {
  flex: 0 0 auto; color: var(--fg-muted); font-size: 15px; line-height: 1;
  transition: transform .15s ease; transform-origin: 45% 55%;
}
.mr-card__caret--open { transform: rotate(90deg); color: var(--ea-green-600); }
.mr-card--open { border-color: var(--border-default); }

.mr-body-panel {
  border: 1px solid var(--border-subtle); border-radius: 6px;
  background: var(--bg-subtle); padding: 10px 12px; margin: 0 0 10px;
}
.mr-body-panel--err { color: var(--ea-amber-600); font-size: 12.5px; }
.mr-body-files {
  font-size: 11.5px; color: var(--fg-tertiary);
  padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid var(--border-subtle);
}
.mr-body-text {
  margin: 0; font-family: inherit; font-size: 12.5px; line-height: 1.6;
  color: var(--fg-secondary); white-space: pre-wrap; word-break: break-word;
  max-height: 22rem; overflow-y: auto;
}
.mr-body-more { font-size: 11px; color: var(--fg-muted); margin-top: 8px; }


/* ============================================================================
   SHARED COMPONENT - expanding list rows
   Canonical copy: ai-brain layer-2-skills/enviroair-design/components.css
   Copied here verbatim rather than restated, so the two cannot drift into
   saying different things. Change it there first.
   ============================================================================ */
/* ── Expanding list rows ─────────────────────────────────────────────────────
   A dense, scannable list where each row opens in place to show its detail.

   ⚠️ **Use this instead of showing everything at once.** A list where every row carries its
   summary, its errors and its buttons is a list you scroll rather than read — twelve documents
   become three screens, and nobody finds the one they wanted. The row shows what identifies it;
   opening shows the rest.

   ⚠️ **And instead of a modal per row.** A modal loses your place in the list, which matters
   when the reason you are looking is to compare one row against another.

   The original is eGO's `.action-card`, which predates this file. It is the same pattern with
   the same behaviour; this is the version to copy from now.

   Markup:

     <div class="ea-rows">
       <div class="ea-row ea-row-open" onclick="…">
         <span class="ea-chev">›</span>
         <div class="ea-row-body">
           <div class="ea-row-title">What it is</div>
           <div class="ea-row-meta">chips, dates, the one line that identifies it</div>
         </div>
         <div class="ea-row-side">status chip</div>
       </div>
       <div class="ea-row-panel">… the detail, and the buttons that act on it …</div>
     </div>

   Rules:
   - The whole row is the hit target, not a chevron somebody has to aim at.
   - A button inside a row must stop the click reaching the row, or acting on something also
     collapses it under the person's hand.
   - Open rows are remembered by id, not by position. A list that re-renders — and these all
     re-render, on every save — will otherwise open a different row than the one that was open.
   - One row open at a time is usually right for reading; several at once is right for
     comparing. Choose per screen and say which in a comment.
   ────────────────────────────────────────────────────────────────────────── */

.ea-rows { display: flex; flex-direction: column; }

.ea-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 10px;
  cursor: pointer;
  border-bottom: 1px solid var(--border-subtle);
  transition: background 120ms;
}
.ea-row:hover { background: var(--bg-subtle); }
.ea-row-open { background: var(--bg-subtle); }

.ea-row-body  { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ea-row-title { font-size: 13px; font-weight: 600; line-height: 1.35; }
.ea-row-meta  {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--fg-secondary);
}
.ea-row-side  { display: flex; align-items: center; gap: 6px; flex: none; }

/* Points right when shut, down when open. The rotation is the only thing that says a row can
   be opened at all, so it is not decoration. */
.ea-chev {
  flex: none; width: 14px; color: var(--fg-muted);
  font-size: 15px; line-height: 1.25;
  transition: transform 120ms;
}
.ea-row-open .ea-chev { transform: rotate(90deg); }

.ea-row-panel {
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border-subtle);
  padding: 2px 12px 12px 34px;          /* indented to the row's title, not to its chevron */
  font-size: 12.5px;
}
.ea-row-panel p { margin: 0 0 8px; }

/* A row the list is working on — reading a document, fetching a price. The row says so and
   stays saying so; a toast that fades is not progress on something that takes a minute. */
.ea-row-busy { color: var(--ea-green-700, #15803d); }

/* A row that is superseded, inactive or otherwise no longer the live one. Kept visible,
   because a superseded document is evidence, and faded so it is not mistaken for current. */
.ea-row-faded { opacity: .55; }

/* ── File drop zone ──────────────────────────────────────────────────────────
   Drag files onto it, or press browse. That is the whole component.

   ⚠️ **Resist explaining it.** The first version of the tender document uploader listed the
   whole Dropbox folder with a tick beside every file and three paragraphs saying why. James:
   *"its trying to be too clever and the modal is way to cluttered with mountains of text."*
   A drop zone that needs instructions is the wrong drop zone.

   ⚠️ **Cancel the default on dragover AND drop**, or the browser navigates away to the file and
   the screen is gone.

   Markup:
     <div class="ea-drop">
       <div class="ea-drop-main">Drag files here</div>
       <div class="ea-drop-sub">or <button …>browse</button></div>
     </div>
   Add `.ea-drop-over` while a drag is over it.
   ────────────────────────────────────────────────────────────────────────── */

.ea-drop {
  border: 2px dashed var(--border-default, #d4d4d8);
  border-radius: var(--radius-md);
  padding: 26px 18px;
  text-align: center;
  background: var(--bg-subtle);
  transition: border-color 120ms, background 120ms;
}
.ea-drop-over {
  border-color: var(--ea-green-600, #16a34a);
  background: var(--ea-green-50, #f0fdf4);
}
.ea-drop-main { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.ea-drop-sub  { font-size: 12.5px; color: var(--fg-secondary); }

/* ── revisions ──────────────────────────────────────────────────────────────
   A revision is one trade's estimate at a point in time. James, 2026-09-17: a copy with an
   automatic number and date, the previous one locked, and every changed field highlighted.

   ⚠️ The marker lives in the band because the band is locked to the top of the sheet — the one
   place it cannot scroll away from the numbers it qualifies. */
.wsx-rev {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  padding: 2px 8px; border-radius: 20px;
  background: var(--ea-charcoal-50); border: 1px solid var(--border-subtle);
}
.wsx-rev-n { font-size: 11.5px; font-weight: 700; }
.wsx-rev-d { font-size: 10.5px; color: var(--fg-tertiary); }
.wsx-revpick {
  font: inherit; font-size: 11px; padding: 1px 4px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle); background: var(--bg-surface); color: var(--fg-primary);
}
/* ⚠️ Looking at a locked revision has to be unmistakable — every control on the sheet still
   looks live, and the refusal comes from the server. The band says so before you try. */
.wsx-rev-locked { background: var(--ea-amber-100); border-color: var(--ea-amber-600); }
.wsx-rev-lock {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ea-amber-600);
}

/* A field that moved since the previous revision. A tint on the cell, not on the text: the
   figure has to stay as readable as every other figure on the sheet. */
.wsx td.wsx-moved { background: var(--ea-amber-50, #fef8ec); box-shadow: inset 0 0 0 1px var(--ea-amber-100); }
.wsx tbody tr:hover td.wsx-moved { background: var(--ea-amber-100); }

/* A line that has no predecessor — it was added in this revision. */
.wsx tr.wsx-added td:first-child { box-shadow: inset 3px 0 0 0 var(--ea-green-500); }

/* ⚠️ **The ROW carries the mark as well as the cell.** Four of the watched fields have no column
   of their own — which supplier is in the price, whether the line is specified, its note and its
   exclusion — so tinting cells alone left a changed row looking untouched. James, 2026-09-17:
   "selecting a different supplier option was not highlighted, and my new note was not
   highlighted." An edge on the row catches every change; the cell tint still says which one,
   where there is a cell to say it in. */
.wsx tr.wsx-changed td:first-child { box-shadow: inset 3px 0 0 0 var(--ea-amber-600); }

/* A note or an exclusion whose words moved. */
.wsx-underline.wsx-moved-text {
  background: var(--ea-amber-50, #fef8ec);
  box-shadow: inset 0 0 0 1px var(--ea-amber-100);
}

/* ⚠️ **A line that has GONE, listed where it used to be.** Nothing disappears silently from a
   price. Struck through and grey so it reads as a record of a deletion rather than as work. */
.wsx tr.wsx-gone td {
  color: var(--ea-charcoal-300); text-decoration: line-through; height: 19px;
  background: var(--bg-subtle);
}
.wsx tr.wsx-gone td.calc {
  text-decoration: none; font-size: 10px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--ea-amber-600);
}
.wsx-gone-name { padding-left: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* An option that went with its line sits in from the line it belonged to, as it did on the sheet. */
.wsx-gone-name.wsx-gone-opt { padding-left: 26px; }
/* ⚠️ The strikethrough is on the whole row, so the button has to opt out of it — a struck-through
   arrow reads as a control that has itself been cancelled. */
.wsx tr.wsx-gone td.wsx-end { text-decoration: none; }
.wsx-end .wsx-back { font-size: 14px; }
.wsx-end .wsx-back:hover { color: var(--ea-green-700, #15803d); }

/* ============================================================
   OLD JOBS — WorkflowMax archive, job detail modal
   ============================================================ */
.wfm-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 32px; }
.wfm-kv { display: flex; gap: 12px; padding: 4px 0; font-size: 13px; }
.wfm-kv > span { flex: 0 0 90px; color: var(--fg-tertiary); }
.wfm-kv > div { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.wfm-desc { margin-top: 14px; padding: 12px 14px; background: var(--bg-subtle); border-radius: 8px; font-size: 13px; }
.wfm-section { margin-top: 22px; }
.wfm-h { font-size: 13px; font-weight: 700; margin: 0 0 8px; }
.wfm-section td { vertical-align: top; }
.wfm-note { padding: 10px 0; border-bottom: 1px solid var(--border-subtle); font-size: 13px; overflow-wrap: anywhere; }
.wfm-note:last-child { border-bottom: none; }
.wfm-note-head { color: var(--fg-tertiary); margin-bottom: 4px; }

/* ── Maintenance report review (Maintenance → Reports, screen-mreport) ──────────────────────
   The editor beside an A4 preview. Both columns scroll on their own, so a long list of items
   never pushes the preview out of sight. */
.mrp-tab-count:not(:empty) { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 4px;
  border-radius: 9px; background: var(--ea-amber-100, #fef3c7); color: #8a5a16; font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; }
.mrp-layout { display: grid; grid-template-columns: minmax(400px, min(560px, 42%)) 1fr; gap: 16px;
  height: calc(100vh - var(--hub-topbar-h) - 110px); min-height: 480px; }
.mrp-editor { overflow-y: auto; padding-right: 6px; }
.mrp-preview { display: flex; flex-direction: column; min-width: 0; background: var(--ea-charcoal-100);
  border-radius: 8px; overflow: hidden; }
.mrp-preview iframe { flex: 1; width: 100%; border: 0; background: var(--ea-charcoal-100); }
.mrp-preview-status { font-size: 12px; color: var(--fg-tertiary); padding: 6px 12px; min-height: 26px; }
.mrp-banner { font-size: 13px; padding: 9px 12px; border-radius: 6px; margin-bottom: 10px; }
.mrp-banner--warn { background: #fef3c7; color: #7a4d0b; }
.mrp-banner--info { background: var(--ea-green-50); color: var(--ea-green-800); }
.mrp-banner--danger { background: #fee2e2; color: #991b1b; }
.mrp-banner .btn { margin-left: 8px; }
.mrp-h { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--fg-tertiary);
  margin: 16px 0 8px; }
.mrp-h:first-child { margin-top: 0; }
.mrp-card { border: 1px solid var(--border-subtle); border-left: 4px solid var(--ea-amber-600, #d97706); border-radius: 6px;
  padding: 10px 12px; margin-bottom: 10px; background: #fff; }
.mrp-card--not_checked { border-left-color: var(--ea-charcoal-300); }
.mrp-card--open_followup { border-left-color: var(--ea-charcoal-700); }
.mrp-card--asset_note { border-left-color: var(--ea-charcoal-200); }
.mrp-card.mrp-out { background: var(--bg-subtle); }
.mrp-card-head { display: flex; align-items: flex-start; gap: 10px; }
.mrp-card-head .mrp-unit { font-weight: 700; font-size: 14px; }
.mrp-card-head .mrp-where { font-size: 12px; color: var(--fg-tertiary); }
.mrp-card-head .cfg-toggle-label { margin-left: auto; white-space: nowrap; }
.mrp-orig { font-size: 12.5px; font-style: italic; color: var(--fg-secondary); margin: 6px 0; overflow-wrap: anywhere; }
.mrp-check { font-size: 12.5px; background: #fef3c7; color: #7a4d0b; border-radius: 4px; padding: 6px 8px; margin: 6px 0; }
.mrp-why { font-size: 12.5px; color: var(--fg-tertiary); margin: 6px 0 0; }
.mrp-new { font-size: 11px; font-weight: 700; color: #991b1b; margin-left: 6px; }
.mrp-card .field-group { margin: 8px 0 0; }
.mrp-card .field-group label { font-size: 11.5px; }
.mrp-card .field-textarea { min-height: 54px; font-size: 13px; }
.mrp-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.mrp-photo { width: 104px; font-size: 11px; color: var(--fg-tertiary); }
.mrp-photo-img { width: 104px; height: 78px; border-radius: 4px; background: var(--bg-subtle) center / cover no-repeat;
  border: 1px solid var(--border-subtle); cursor: zoom-in; }
.mrp-photo.mrp-out .mrp-photo-img { opacity: .35; }
.mrp-photo label { display: flex; align-items: center; gap: 4px; margin-top: 3px; cursor: pointer; }
.mrp-photo-cap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mrp-hint { font-size: 12px; color: var(--fg-tertiary); margin-top: 6px; }
.mrp-send-from { font-size: 12px; color: var(--fg-tertiary); }
.mrp-send-error { font-size: 13px; color: #991b1b; margin-top: 8px; }
.mrp-send-error:empty { display: none; }
@media (max-width: 1100px) { .mrp-layout { grid-template-columns: 1fr; height: auto; } .mrp-preview { height: 80vh; } }

/* ── SSSP builder (H&S → SSSPs, screen-sssp) — shares the report screen's editor + preview
   layout (.mrp-*) and adds its own form pieces. */
/* The editor alone, full width with a comfortable cap; the PDF opens in its own tab on Preview
   (James, 2026-10-07 — the side-by-side preview flickered and squeezed the form). */
.ss-layout { height: calc(100vh - var(--hub-topbar-h) - 110px); min-height: 480px; }
.ss-layout .mrp-editor { height: 100%; max-width: 1200px; }
#screen-sssp .mrp-preview-status { padding: 0; min-height: 0; }
.ss-checks { background: #fef3c7; color: #7a4d0b; border-radius: 6px; padding: 9px 12px; font-size: 13px; margin-bottom: 12px; }
.ss-checks ul { margin: 4px 0 0; padding-left: 18px; }
.ss-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
.ss-grid > .field-group { min-width: 0; }
.ss-row { display: grid; grid-template-columns: 22px 1fr 1fr; gap: 8px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border-subtle); font-size: 13px; }
.ss-row--2 { grid-template-columns: 1fr 1fr; }
.ss-row .field-input, .ss-row .field-select { height: 32px; font-size: 13px; padding: 0 8px; }
.ss-pick { display: flex; gap: 10px; align-items: flex-start; padding: 8px 10px; border: 1px solid var(--border-subtle); border-radius: 6px; margin-bottom: 6px; cursor: pointer; font-size: 13px; background: #fff; }
.ss-pick.on { border-color: var(--ea-green-600); background: var(--ea-green-50); }
.ss-pick input[type=checkbox] { margin-top: 3px; accent-color: var(--ea-green-600); }
.ss-pick .ss-ref { font-family: var(--font-mono); font-size: 11px; color: var(--fg-tertiary); }
.ss-sub { font-size: 12px; color: var(--fg-tertiary); }
.ss-note { margin-top: 6px; }
.ss-custom { border: 1px solid var(--border-subtle); border-radius: 6px; padding: 10px; margin-bottom: 8px; background: #fff; }
.ss-ppe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 4px 12px; }
.ss-ppe-item { display: flex; align-items: center; gap: 6px; font-size: 13px; padding: 3px 6px; border-radius: 4px; cursor: pointer; font-weight: 400; margin: 0; }
.ss-ppe-item input { accent-color: var(--ea-green-600); }
.ss-ppe-item.is-suggested { background: #fef3c7; }
.ss-sec-h { font-size: 13px; font-weight: 600; margin: 18px 0 8px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ss-hint { font-size: 12px; color: var(--fg-tertiary); margin: -4px 0 8px; }
.ss-list-row { display: grid; gap: 8px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border-subtle); }
.ss-list-row .field-input, .ss-list-row .field-select { height: 32px; font-size: 13px; padding: 0 8px; min-width: 0; }
.ss-del { background: none; border: 0; color: var(--fg-tertiary); cursor: pointer; font-size: 16px; line-height: 1; padding: 4px; }
.ss-del:hover { color: #991b1b; }
.ss-sig-canvas { width: 100%; height: 160px; border: 1px dashed var(--border-default); border-radius: 6px; background: #fff; touch-action: none; cursor: crosshair; display: block; }
