/* Quantise pilot UI — clean, dependency-free. Shell = sidebar modules + content. */
:root {
  --bg: #f1f4f8;
  --card: #ffffff;
  --ink: #1b2430;
  --muted: #68737f;
  --line: #e4e9f1;
  --accent: #0b63c5;
  --accent-ink: #ffffff;
  --sidebar: #10203a;
  --sidebar-ink: #c9d4e3;
  --ok: #1e7d3e;
  --warn: #a15c00;
  --bad: #b3261e;
}

* { box-sizing: border-box; }
html, body {
  /* Overscrolling past the top must NOT trigger the browser's pull-to-refresh — a
     full reload closed whatever line items were open (user report 2026-07-07, iOS
     Safari). overscroll-behavior covers Chrome/Android; iOS Safari's native gesture
     ignores it, so the DOCUMENT never scrolls at all — <main> (and the login view)
     are the scroll containers instead, which also keeps the sidebar fixed. */
  height: 100%;
  overflow: hidden;
  overscroll-behavior-y: none;
}
body {
  margin: 0;
  font: 15px/1.5 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
}
#view-login { height: 100%; overflow-y: auto; }

.brand { font-weight: 700; font-size: 19px; letter-spacing: 0.2px; }
.brand .dot { color: var(--accent); }
.brand.big { font-size: 24px; margin-bottom: 18px; }

/* ---------- shell ---------- */
.shell { display: flex; height: 100%; }
.sidebar {
  width: 220px;
  flex-shrink: 0;
  background: var(--sidebar);
  color: var(--sidebar-ink);
  display: flex;
  flex-direction: column;
  padding: 20px 14px;
}
.sidebar .brand { color: #fff; }
.brand-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 8px 18px;
}
.avatar {
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 700;
  background: var(--accent);
  color: #fff;
  flex-shrink: 0;
}
.avatar:hover { filter: brightness(1.15); }

.acc-grid {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 8px 14px;
  font-size: 14.5px;
}
#modules { display: flex; flex-direction: column; gap: 2px; }
.module {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--sidebar-ink);
  background: none;
  border: none;
  border-radius: 8px;
  cursor: pointer;
}
.module:hover { background: rgba(255, 255, 255, 0.07); }
.module.active { background: var(--accent); color: #fff; }
/* #47C (founder 2026-08-20, iPad 11"): at tablet width the sidebar gives its
   width to the data — condensed, still labelled; desktop untouched */
@media (max-width: 1080px) {
  .sidebar { width: 150px; padding: 16px 8px; }
  .module { font-size: 13px; padding: 9px 10px; }
  .acc-grid { grid-template-columns: 110px 1fr; }
}

/* #106 §3.9 sub-module accordion: the group header carries a rotating chevron; the
   sub-items sit indented and a shade lighter, revealed only when the group is
   expanded. The header lights subtly when a child is the active screen. */
.modgroup { display: flex; flex-direction: column; gap: 2px; }
.group-head::after {
  content: "";
  width: 6px; height: 6px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
  opacity: 0.8;
}
.modgroup.expanded .group-head::after { transform: rotate(45deg); }
.group-head.active { color: #dbe7fa; }
.submodules { display: none; flex-direction: column; gap: 1px; margin: 1px 0 3px 10px;
              border-left: 1px solid rgba(255, 255, 255, 0.1); padding-left: 6px; }
.modgroup.expanded .submodules { display: flex; }
.submodule {
  text-align: left;
  width: 100%;
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 500;
  color: #9db8dd;
  background: none;
  border: none;
  border-radius: 7px;
  cursor: pointer;
}
.submodule:hover { background: rgba(255, 255, 255, 0.06); color: #cfe0f5; }
.submodule.active { background: var(--accent); color: #fff; }

/* origin tag beside the job hash in My Jobs — Auto (muted) vs Manual (amber) */
.origin-tag {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0 6px;
  border-radius: 999px;
  vertical-align: 1px;
}
.origin-tag.auto { color: #6b7a90; border: 1px solid #d3dbe6; background: #f4f6fa; }
.origin-tag.manual { color: #8a5a00; border: 1px solid #e7c98c; background: #fdf1dd; }
.soon {
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  padding: 1px 8px;
  border-radius: 999px;
  color: #7f97b8;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: none;
}
.sidebar-foot {
  /* directly UNDER the last menu item (user, 2026-07-07) — new modules push it down,
     it never floats detached at the bottom of the pane */
  margin-top: 10px;
  padding: 12px 8px 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.sidebar-foot .ghost { color: #9db8dd; }

main {
  /* no max-width (user, 2026-07-07): the UI fills the window edge to edge at any
     size — on an ultrawide, main takes every pixel the sidebar doesn't */
  flex: 1;
  min-width: 0;                  /* flex item may shrink below content width */
  /* no top padding: the scrollport must start at 0 so sticky bars pin FLUSH to the
     top — each view section carries the breathing room instead */
  padding: 0 28px 26px;
  overflow-y: auto;              /* THE app scroller (see html/body note above) */
  overscroll-behavior-y: none;
  /* keyboard focus (Tab into a grid input) scrolls the target BELOW the pinned
     action bar instead of underneath it (review: the bar painted over the very
     strip the browser scrolls focused elements into) */
  scroll-padding-top: 150px;
}
main > section { padding-top: 26px; }

@media (max-width: 760px) {
  .shell { flex-direction: column; }
  .sidebar { width: 100%; flex-direction: row; align-items: center; gap: 10px; padding: 10px 12px; }
  .sidebar .brand { font-size: 16px; }
  .brand-row { padding: 0 6px; gap: 8px; }
  #modules { flex-direction: row; flex-wrap: wrap; }
  .module { width: auto; padding: 7px 10px; }
  .sidebar-foot { margin: 0 0 0 auto; border: none; padding: 0; }
  main { padding: 16px; }
}

/* ---------- cards & common ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 22px 26px;
  margin-bottom: 22px;
  box-shadow: 0 1px 2px rgba(16, 32, 58, 0.05), 0 10px 28px -18px rgba(16, 32, 58, 0.14);
}
.card.narrow { max-width: 380px; margin: 60px auto; }
h2 { margin: 0 0 12px; font-size: 17px; }
h3 { margin: 22px 0 8px; font-size: 15px; }
.muted { color: var(--muted); }
.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 13px; }
.hidden { display: none !important; }
.error { color: var(--bad); }
.total { font-size: 18px; font-weight: 700; }
/* #114 (user): the §3.9(2) component split stacks one figure per line */
.total .total-row { display: flex; justify-content: space-between; gap: 18px;
  max-width: 360px; font-size: 15px; font-weight: 400; }
.total .total-row .total-money { font-variant-numeric: tabular-nums; }
.total .total-grand { font-weight: 700; font-size: 17px;
  border-top: 1px solid #c9d4e4; margin-top: 3px; padding-top: 3px; }
.total .total-advisory { font-size: 13px; margin-top: 3px; }
.total .total-advisory-note { font-size: 11.5px; font-weight: 400; }

.stages { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.stages .card { margin-bottom: 0; }
@media (max-width: 900px) { .stages { grid-template-columns: 1fr; } }
/* Action bars stay reachable (user rule 2026-07-07 → design principle #11): the stage
   cards carry every job action — PDFs, view toggles, Edit/Save/Cancel, Generate —
   so they PIN to the top of the viewport while the line items scroll underneath.
   Condensed so the pinned bar costs as little height as possible. */
#stage-cards {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(241, 244, 248, 0.82);   /* frosted: rows ghost underneath */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 10px 0 12px;
  margin: -10px 0 12px;
  box-shadow: 0 8px 10px -10px rgba(16, 32, 58, 0.35);
}
.stages .card.stage { padding: 12px 16px; }
.stages .card.stage h2 { font-size: 15px; margin-bottom: 4px; }
.stages .card.stage p.muted { font-size: 12.5px; margin: 0 0 8px; }
.stages .card.stage .total { font-size: 16px; margin: 2px 0 6px; }
@media (max-width: 900px) {
  /* stacked cards would pin half a phone screen — mobile is view/approve (principle
     9), so the bar scrolls normally there (and no scroll offset is needed) */
  #stage-cards { position: static; box-shadow: none; }
  main { scroll-padding-top: 0; }
}

label { display: block; margin-bottom: 12px; color: var(--muted); font-size: 13px; }
input {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 9px 13px;
  font-size: 15px;
  color: var(--ink);
  border: 1px solid #d4dce8;
  border-radius: 10px;
}
input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(11, 99, 197, 0.14);
}
/* the `hidden` ATTRIBUTE must always win: the global display:block above was
   overriding it and leaking the native file-picker into the dropzone */
input[hidden] { display: none !important; }

button, .button {
  display: inline-block;
  padding: 9px 18px;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: 9px;
  cursor: pointer;
  text-decoration: none;
  box-shadow: 0 2px 6px -2px rgba(11, 99, 197, 0.5);
}
button:disabled { opacity: 0.45; cursor: default; }
/* the quote button's RESTING state (user 2026-07-08): quote generated and nothing
   unsaved — green, full-strength and non-clickable. Disabled here means "done",
   never "broken", so it opts out of the dimmed disabled look above. */
#gen-quote.generated:disabled {
  background: var(--ok);
  color: #fff;
  opacity: 1;
  box-shadow: 0 2px 6px -2px rgba(30, 125, 62, 0.5);
}
/* button hierarchy: filled = commits/spends · outline = opens artefacts · ghost = navigates */
a.button { background: #fff; color: var(--accent); border-color: #b9d2ee; box-shadow: none; }
a.button:hover { background: #f4f8fd; }
/* the filled-button glow belongs ONLY to filled buttons — every specialised button
   (sidebar modules, pills, menus, overlays) opts back out */
.module, .hide-toggle, .kebab, .menu-item, .avatar, #overlay-close { box-shadow: none; }
button.ghost, .ghost {
  background: none;
  color: var(--accent);
  border: none;
  padding: 6px 8px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: none;
}

#dropzone {
  border: 1.5px dashed #c3cfdf;
  border-radius: 12px;
  padding: 30px 20px 24px;
  text-align: center;
  margin: 14px 0;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
#dropzone.drag { border-color: var(--accent); background: #eaf3fd; }
#dropzone svg { display: block; margin: 0 auto 10px; }
.dz-lead { font-size: 15px; margin: 0; }
.dz-sub { margin: 2px 0 0; font-size: 13px; }
.dz-browse { color: var(--accent); font-weight: 600; text-decoration: underline;
             text-underline-offset: 3px; }
.dz-types { display: flex; gap: 8px; justify-content: center; margin-top: 16px;
            flex-wrap: wrap; }
.ftype { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
         font-weight: 600; color: #5a6b84; background: #f2f5fa;
         border: 1px solid #e2e8f2; border-radius: 999px; padding: 3px 11px; }
.ftype b { color: var(--accent); font-size: 10px; letter-spacing: 0.06em; }

/* the jobs LEDGER (design review 2026-07-07): structured columns, UK dates,
   dotted status pills, icon actions — the id demoted to a mono sub-line */
table.ledger { width: 100%; border-collapse: collapse; font-size: 14px; }
.ledger th { text-align: left; font-size: 11px; text-transform: uppercase;
             letter-spacing: 0.09em; color: #8a97ab; font-weight: 600;
             padding: 6px 10px 10px; border-bottom: 1px solid var(--line); }
.ledger td { padding: 12px 10px; border-bottom: 1px solid #eef2f7;
             vertical-align: middle; }
.ledger tbody tr { cursor: pointer; }
.ledger tbody tr:hover td { background: #f5f8fc; }
.ledger tr:last-child td { border-bottom: none; }
.jobname { font-weight: 600; }
.jobsub { font-size: 11.5px; color: #99a5b8; margin-top: 1px;
          font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.when { color: var(--muted); white-space: nowrap; }
.acts { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.icb { width: 30px; height: 30px; padding: 0; border-radius: 8px; border: none;
       background: none; box-shadow: none; display: inline-flex; align-items: center;
       justify-content: center; cursor: pointer; color: var(--muted); }
.icb:hover { background: #edf2f9; color: var(--accent); }

/* #106 §3.9: My Jobs per-row action buttons — View BoM / Quote / Open. Quote is the
   primary (blue); a disabled View BoM (manual job / not ready) reads as unavailable. */
.rowbtn {
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 600;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.rowbtn:hover:not(:disabled) { background: #f4f6fa; }
.rowbtn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.rowbtn.primary:hover { filter: brightness(0.94); }
.rowbtn.disabled, .rowbtn:disabled { opacity: 0.45; cursor: not-allowed; }

/* #106 §3.9 step 2b: the frozen top-pane job picker in the BoM / Quote modules —
   stays reachable while the line items scroll (<main> is the scroller). */
.picker-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  margin-bottom: 12px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(16, 32, 64, 0.06);
}
.picker-label { font-weight: 600; font-size: 14px; color: var(--ink); white-space: nowrap; }
#job-picker {
  flex: 1;
  max-width: 420px;
  padding: 7px 10px;
  font-size: 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  cursor: text;    /* search-combo (input + datalist): type to narrow */
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 11px 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: var(--line);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%;
                background: currentColor; flex-shrink: 0; }
.chip.ready { background: #e2f2e7; color: var(--ok); }
.chip.failed { background: #fbe4e2; color: var(--bad); }
.chip.extracting, .chip.pending { background: #fdf1dd; color: var(--warn); }
.chip.awaiting_confirmation { background: #e4edfb; color: var(--accent); }

.pkg { border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; margin: 10px 0; }
/* #51 scan-check verdict badges on the review screen */
.scan-badge {
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
/* structural moved green → PURPLE (#77, user): green now means "found/complete"
   in the pack checklist, so the two vocabularies never collide */
.scan-badge.ok { background: #ece6f9; color: #6b46c1; }
.scan-badge.arch { background: #e4edfb; color: var(--accent); }
.scan-badge.na { background: #fbe4e2; color: var(--bad); }
.scan-badge.pending { background: var(--line); color: var(--muted); }
.scan-summary-row { display: flex; gap: 8px; align-items: center; padding: 3px 0; font-size: 13.5px; }
/* the grid goes inert while a save PATCH is in flight — clicks must not race the save */
#grid-tables.busy { pointer-events: none; opacity: 0.65; }
/* quote lines removed from the total (edit-mode RED — user, 2026-07-07: a removed
   line reads as struck, not faded; absent from read-only as before). Children that
   declare their own colour (.rate-suffix, .comp-line, muted spans) must go red too —
   a direct colour beats inheritance, so name them (review: two-tone removed rows). */
tr.row-hidden td, tr.row-hidden input.cell-input,
tr.row-hidden .rate-suffix, tr.row-hidden .comp-line,
tr.row-hidden td .muted { color: var(--bad); }
tr.row-hidden .badge, tr.row-hidden .scan-badge { opacity: 0.55; }
.hide-toggle {                       /* a clear rounded-pill control, not bare text */
  padding: 2px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  background: var(--line);
  border: 1px solid #d3d9e0;
  border-radius: 999px;
  margin-left: 6px;
  cursor: pointer;
}
.hide-toggle:hover { background: #d7dde4; }
tr.row-hidden .hide-toggle { opacity: 1; color: var(--ink); }   /* the control stays ink, not red, on removed rows */
/* #116 (user): labour/plant line identity — one wash per component, THE SAME two
   colours the Quote PDF and xlsx shade with. Background only (ink stays ink, and
   removed-row red still wins: row-hidden colours text, not background). Apricot is
   deliberately NOT the completeness-flag yellow (#fceabb on the PDF/xlsx) and teal
   not the ok-green — every wash in the system stays unique. */
tr.row-labour td { background: #fde9d6; }
tr.row-plant td { background: #dcefec; }
/* the #96.1 sticky action lane paints td:last-child white at higher specificity —
   on a washed row the lane wears the wash instead (still opaque, so wide rows
   still scroll under it exactly as before) */
#grid-tables table.edit-grid tbody tr.row-labour td:last-child { background: #fde9d6; }
#grid-tables table.edit-grid tbody tr.row-plant td:last-child { background: #dcefec; }
/* hover deepens a washed row's OWN wash — its identity must never flicker to the
   generic hover blue (#f7fafd); the :last-child twins keep the pinned lane in step */
#grid-tables tbody tr.row-labour:not(.row-hidden):hover td { background: #fbdfc4; }
#grid-tables tbody tr.row-plant:not(.row-hidden):hover td { background: #cfe9e5; }
#grid-tables table.edit-grid tbody tr.row-labour:not(.row-hidden):hover td:last-child { background: #fbdfc4; }
#grid-tables table.edit-grid tbody tr.row-plant:not(.row-hidden):hover td:last-child { background: #cfe9e5; }
.badge.kind-labour { background: #fde9d6; color: #a05a1c; }
.badge.kind-plant { background: #dcefec; color: #11706a; }
.total .total-row.split-labour, .total .total-row.split-plant {
  border-radius: 5px; padding: 1px 6px; margin: 0 -6px;  /* wash without breaking column alignment */
}
.total .total-row.split-labour { background: #fde9d6; }
.total .total-row.split-plant { background: #dcefec; }
/* #117 §3.9: New Job tabs (auto upload vs hand-built manual quote) */
.nj-tabs { display: flex; gap: 8px; margin: 4px 0 14px; }
.nj-tab {
  padding: 7px 16px; border: 1px solid #cfd8e3; border-radius: 999px;
  background: #fff; color: var(--ink); font-weight: 600; font-size: 13.5px;
  cursor: pointer;
}
.nj-tab:hover { background: #f4f8fd; }
.nj-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* #117: a manual job's BoM stage is honest — present, greyed, inert */
#stage-bom.stage-manual { opacity: 0.6; }
a.icon-btn.disabled { pointer-events: none; opacity: 0.35; }
/* #117: the add-section bar + hand-added section stubs in the quote grid */
.add-sec-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
               margin: 20px 0 8px; }
.add-sec-lead { font-weight: 600; }
.add-sec-select, .add-sec-custom { width: auto; min-width: 180px; }
.add-sec-err { font-size: 12.5px; }
.sec-new-note { margin: 2px 0 6px; font-size: 12.5px; }
.blank-canvas { font-size: 14.5px; margin: 14px 0; }
.pkg-head { display: block; margin-bottom: 6px; color: var(--ink); font-size: 14px; }
.pkg-file { display: block; margin: 3px 0 3px 8px; font-size: 13.5px; color: var(--ink); }
.pkg-file input { display: inline; width: auto; margin-right: 4px; }
.pkg-head input { display: inline; width: auto; margin-right: 6px; }

/* #81 address block: a half-width, LEFT-aligned column — full-width fields all ran
   to the right margin. Uniform 12px gaps (street's comes from its label's bottom
   margin; sibling margins collapse in the card's block flow); the postcode keeps
   its 130px. Phones get the full width back. */
#addr-street, #addr-line2, #m-addr-street, #m-addr-line2, .addr-row { width: 50%; }
.addr-row { display: flex; gap: 10px; margin-top: 12px; }
.addr-row input { flex: 1; margin-top: 0; }
.addr-row input#addr-postcode { flex: 0 0 130px; }
.addr-note { font-size: 12.5px; margin-top: 6px; }
#upload-btn { margin-top: 18px; }   /* clear break after the address block — the
                                       button sat tight under the postcode row */
#m-create-btn { margin-top: 18px; } /* #117.2 (founder): same break on the manual
                                       tab — Create sat tight under the postcode */
@media (max-width: 760px) {
  #addr-street, #addr-line2, .addr-row { width: 100%; }
}

/* #79 export row: "Export" + file-type icon buttons (PDF / Excel) under the stage
   actions — generic file badges, not brand logos (trademark) */
.export-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.export-label { font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
                text-transform: uppercase; }
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 5px 8px; border: 1px solid var(--line); border-radius: 10px;
  background: #fff; line-height: 0; text-decoration: none; cursor: pointer;
}
.icon-btn:hover { border-color: var(--accent);
                  box-shadow: 0 2px 8px -3px rgba(11, 99, 197, 0.35); }

/* #77 pack checklist: one visual language on both surfaces —
   green tick = found, red cross = missing, neutral dot = nothing chosen yet.
   #81: boxed in light grey so the list reads as one unit against the card. */
.pack-checklist {
  background: #f4f6f9;
  border: 1px solid #e4e9f0;
  border-radius: 10px;
  padding: 10px 14px 8px;
  margin: 0 0 14px;
}
.pack-checklist ul {
  list-style: none;
  padding-left: 2px;
  margin: 6px 0 10px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 3px 18px;
}
.pack-checklist li { font-size: 13px; color: var(--muted); }
.pack-checklist li.check-found { color: var(--ok); }
.pack-checklist li.check-missing { color: var(--bad); }
.pack-checklist li.optional { font-style: italic; }
#add-files-btn { margin: 0 6px; }

/* #75 CAD/BIM v1: a quieter sibling of the PDF dropzone — optional, honest label */
#cad-dropzone {
  border: 2px dashed #c4cedd;
  border-radius: 12px;
  text-align: center;
  padding: 14px 12px 10px;
  margin: 12px 0 8px;
  cursor: pointer;
}
#cad-dropzone.drag { border-color: var(--accent); background: #f2f7fd; }
#cad-dropzone svg { display: block; margin: 0 auto 8px; }   /* #81: same cloud as PDF */
#cad-dropzone .dz-lead { margin: 0 0 6px; }
.cad-retain { display: flex; gap: 8px; align-items: flex-start; font-size: 13.5px;
              margin: 0 0 10px; }
.cad-retain input { display: inline; width: auto; margin-top: 3px; }

/* #74 pack questions: the optional scan-derived Q&A block on the review card.
   Answer inputs use the global input style; rows just need breathing room. */
#pack-questions { margin: 12px 0 4px; }
#pack-questions .pack-question { display: block; margin: 10px 0; font-size: 13.5px;
                                 color: var(--ink); }

.downloads { display: flex; gap: 12px; margin-top: 14px; align-items: center; flex-wrap: wrap; }

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--muted); font-weight: 600; white-space: nowrap; }
td.num, th.num { text-align: right; white-space: nowrap;
                 font-variant-numeric: tabular-nums; }
.total, input.cell-input { font-variant-numeric: tabular-nums; }
#grid-tables tbody tr:not(.row-hidden):hover td,
#bom-tables tbody tr:hover td { background: #f7fafd; }

/* ONE badge spec (design review): same height/weight/casing everywhere —
   colour is the only variable */
.badge {
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
  margin-left: 6px;
  vertical-align: middle;
}
.badge.documented { background: #e7f0fb; color: #2563ae; }
.badge.standard_inclusion { background: #e6f3ea; color: var(--ok); }
.badge.review, .badge.critic_flag { background: #fdf3e0; color: var(--warn); }
.badge.edited { background: #efe9fb; color: #6c3fc9; }
.badge.consolidated { background: #eef2f6; color: #4a5a6a; }
.badge.broken { background: #fdecec; color: #b2352f; }

/* three-way choice modal (#62) — Cancel always means "no changes" */
#choice-overlay {
  position: fixed; inset: 0; background: rgba(20, 26, 34, .45); z-index: 60;
  display: flex; align-items: center; justify-content: center;
}
#choice-overlay.hidden { display: none; }
.choice-box {
  background: #fff; border-radius: 10px; padding: 20px 22px; max-width: 440px;
  width: calc(100vw - 48px); box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
}
#choice-text { white-space: pre-line; margin: 0 0 14px; }

.banner {
  background: #fdf1dd;
  color: var(--warn);
  border: 1px solid #f2ddb2;
  border-radius: 8px;
  padding: 10px 14px;
  margin: 10px 0;
  font-size: 14px;
}
.cell-input {
  display: inline-block;
  width: 90px;
  padding: 4px 7px;
  margin: 0;
  font-size: 13.5px;
  text-align: right;
}
.rate-suffix { margin-left: 3px; font-size: 12.5px; color: var(--muted); font-weight: 600; }
.expander { padding: 0 6px; font-size: 15px; }
.child-row td { background: #fafbfc; }
.child-item { padding-left: 26px; }
td.muted { color: var(--muted); }

/* ---------- job-row ⋮ menu ---------- */
.kebab-wrap { position: relative; margin-left: auto; }
.kebab { font-size: 18px; line-height: 1; padding: 2px 10px; color: var(--muted); }
.kebab:hover { color: var(--ink); }
.row-menu {
  position: absolute;
  right: 0;
  top: 26px;
  z-index: 30;
  min-width: 130px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(16, 32, 58, 0.14);
  padding: 4px;
}
.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
.menu-item:hover { background: #f2f6fb; }
.menu-item.danger { color: var(--bad); }
.menu-item.danger:hover { background: #fbe4e2; }

/* ---------- call-off vector diagram panes ---------- */
.vector-pane { display: flex; flex-wrap: wrap; gap: 14px; margin: 12px 0 6px; }
.vector-panel {
  position: relative;
  flex: 0 1 240px;
  max-width: 300px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 10px 6px;
  background: #fff;
}
.vector-panel svg { display: block; width: 100%; height: 170px; }
.vector-caption { font-size: 12.5px; text-align: center; padding-top: 4px; }
.vector-size {
  font-size: 12px;
  font-weight: 700;
  color: #8a5a17;
  text-align: center;
  padding-bottom: 6px;
}
.vector-panel .enlarge {
  position: absolute;
  top: 2px;
  right: 2px;
  font-size: 15px;
  padding: 2px 8px;
  color: var(--muted);
}
.vector-panel .enlarge:hover { color: var(--accent); }

/* fullscreen viewer */
#vector-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(11, 18, 30, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
}
#overlay-body { width: min(92vw, 1100px); max-height: 92vh; text-align: center; }
#overlay-body svg {
  width: 100%;
  height: calc(92vh - 110px);          /* leaves the caption inside the viewport */
  background: #fff;
  border-radius: 10px;
}
.overlay-caption { color: #dbe4f0; padding-top: 12px; font-size: 15px; }
#overlay-close {
  position: fixed;
  top: 18px;
  right: 22px;
  z-index: 101;
  font-size: 18px;
  padding: 8px 14px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999px;
}
#overlay-close:hover { background: rgba(255, 255, 255, 0.25); }

.comp-line { font-size: 12px; white-space: nowrap; }

/* #91 selection state (user spec 2026-07-10): the file count wears a soft green
   pill, clearing is an explicit soft-red pill under "browse files" (selections
   ACCUMULATE across picks now — the native input's replace-on-pick wiped batch
   one). Both override the filled-button base style. */
.count-pill { display: inline-block; padding: 2px 10px; border-radius: 999px;
  background: #e3f4e8; color: #176b3a; font-weight: 600; font-size: 12.5px; }
button.clear-pill { display: inline-block; margin-top: 8px; padding: 3px 14px;
  border-radius: 999px; border: 1px solid #f0c4be; background: #fdeceb;
  color: #b3261e; font-size: 12.5px; font-weight: 600; box-shadow: none; }
button.clear-pill:hover { background: #f9dcd9; }
/* #91: the Job name / Job address leads read in ink, not muted grey (user) */
label.label-ink { color: var(--ink); }

/* #104: line/section notes — internal (screen-only, muted italic under the line)
   + external (prints). The ✎ pill fills amber while a line carries notes. */
button.note-btn { display: inline-block; margin-left: 6px; padding: 2px 9px;
  border-radius: 999px; border: 1px solid #cdd6e4; background: #f4f7fb;
  color: #4a5a72; font-size: 12.5px; box-shadow: none; }
button.note-btn:hover { background: #e8eef7; }
button.note-btn.has-note { background: #fdf3d7; border-color: #e4cf8e; color: #7a5d12; }
.note-int { color: var(--muted); font-style: italic; white-space: normal; }
/* #98.4: the whole-order uplift field sits on the job-total row, right-aligned */
.order-uplift { float: right; font-size: 13px; font-weight: 400;
  color: var(--muted); display: inline-flex; align-items: center; gap: 6px; }
.order-uplift-input { width: 70px; }
/* #103/#104 (user): long qty-bridge text painted over the £/unit column — the
   shared .comp-line nowrap suits the money stacks, not sentence-length lines */
.comp-line.bridge-line { white-space: normal; }
.note-int.hidden { display: none; }
/* #120: delete-account request modal — same backdrop contract as the others.
   #121 (founder live test): the GLOBAL input{display:block;width:100%} rule was
   fattening each checkbox to a full-width block (text wrapped underneath) and the
   uncapped box height pushed the buttons below the fold — checkbox inputs revert
   to their natural size (the options-menu fix, same disease) and the box caps at
   the viewport with its own scroll, so Submit/Cancel always stay on screen. */
#delreq-overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, .45);
  z-index: 60; display: flex; align-items: center; justify-content: center; }
#delreq-overlay.hidden { display: none; }

/* ---------- #47 Purchase: basket, popup, module ---------- */
#po-overlay, #po-new-overlay, #po-line-overlay {
  position: fixed; inset: 0; background: rgba(15, 23, 42, .45);
  z-index: 60; display: flex; align-items: center; justify-content: center; }
#po-overlay.hidden, #po-new-overlay.hidden, #po-line-overlay.hidden {
  display: none; }
/* #47C: New lives under the Purchase caption (right-of-search hid on tablets) */
#po-new { margin-top: 10px; }
/* #47.4: the manual-line product picker — hits list under the search box */
#po-prod-hits { margin-top: 6px; }
#po-prod-hits .po-hit { display: block; width: 100%; text-align: left;
  font-size: 12.5px; padding: 6px 10px; margin-top: 4px;
  color: var(--ink);   /* #47D: buttons inherit the primary white — re-ink */
  background: #f4f6f9; border: 1px solid #e4e9f0; border-radius: 8px;
  cursor: pointer; }
#po-prod-hits .po-hit:not(:disabled):hover { border-color: var(--accent); }
#po-prod-hits .po-hit:disabled { color: var(--muted); cursor: default; }
.po-box { max-width: 420px; width: calc(100% - 32px); text-align: left;
  max-height: calc(100vh - 48px); overflow-y: auto; }
.po-box .po-field select, .po-box .po-field input { display: block; width: 100%;
  margin-top: 4px; text-align: left; }
/* #47.2: the PO row carries the inline Add — staging, not committing */
.po-box .po-row { display: flex; gap: 8px; margin-top: 4px; align-items: stretch; }
.po-box .po-field .po-row select { margin-top: 0; flex: 1; min-width: 0; }
.po-box .po-row button { flex: 0 0 auto; }
/* the staged list — local only until OK commits it (pack-checklist boxing) */
#po-staged { list-style: none; margin: 12px 0 0; padding: 0; }
#po-staged li { display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; padding: 6px 10px; margin-top: 6px;
  background: #f4f6f9; border: 1px solid #e4e9f0; border-radius: 8px; }
#po-staged .po-unstage { margin-left: auto; background: none; border: none;
  cursor: pointer; color: var(--muted); font-size: 14px; line-height: 1;
  padding: 0 2px; }
#po-staged .po-unstage:hover { color: var(--bad); }
/* #47.3: PO-detail edit mode — compact right-aligned qty/rate inputs */
#po-lines .cell-input { max-width: 110px; text-align: right; }
.po-basket { background: none; border: none; color: var(--accent);
  cursor: pointer; padding: 2px 4px; line-height: 1; }
.po-basket:disabled { color: #b9c4d2; cursor: default;
  /* clicks/hover fall through to the cell, which carries the caption (#47.1) */
  pointer-events: none; }
.po-basket:not(:disabled):hover { filter: brightness(1.25); }
/* #47.1: the greyed basket explains itself — a disabled button swallows its
   native title, so the CELL pops the reason on hover (or a click's flash).
   Anchored to open LEFTWARD: the basket column is the row's last. */
td.po-cell { position: relative; }
td.po-cell[data-tip]::after {
  content: attr(data-tip);
  position: absolute; right: 30px; top: 50%; transform: translateY(-50%);
  width: max-content; max-width: 250px; padding: 6px 10px;
  background: #253347; color: #f4f7fb; font-size: 11.5px; font-weight: 400;
  line-height: 1.45; text-align: left; white-space: normal;
  border-radius: 8px; box-shadow: 0 4px 14px -4px rgba(15, 30, 50, .45);
  opacity: 0; pointer-events: none; transition: opacity .12s ease; z-index: 30;
}
td.po-cell[data-tip]:hover::after,
td.po-cell.tip-open::after { opacity: 1; }
.po-chip { display: inline-block; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; padding: 1px 10px;
  border-radius: 999px; }
.po-chip.po-new { color: #0b63c5; border: 1px solid #b9d4f2; background: #eaf2fc; }
.po-chip.po-ordered { color: #8a5a00; border: 1px solid #e7c98c; background: #fdf1dd; }
.po-chip.po-closed { color: #1e7d3e; border: 1px solid #b9dfc6; background: #e9f7ee; }
.po-chip.po-cancelled { color: #68737f; border: 1px solid #d3dbe6; background: #f4f6fa; }
#po-list .po-row { cursor: pointer; }
#po-list .po-row:hover td { background: #f7fafd; }
#po-search { max-width: 240px; margin: 0; }
#po-detail-chip { margin-right: 4px; }
.delreq-box { max-width: 480px; width: calc(100% - 32px); text-align: left;
  max-height: calc(100vh - 48px); overflow-y: auto; }
.delreq-box #delreq-reasons { display: grid; gap: 8px; margin: 12px 0 4px; }
.delreq-box #delreq-reasons label { display: flex; gap: 9px; align-items: center;
  font-weight: 400; color: var(--ink); font-size: 14px; margin: 0; }
.delreq-box #delreq-reasons input[type="checkbox"] { display: inline-block;
  width: auto; margin: 0; flex: 0 0 auto; }
.delreq-box .notes-label { display: block; margin: 12px 0 2px; font-weight: 600; }
.delreq-box textarea { width: 100%; margin-top: 4px; }
/* #122: Products comparison + the grid's supplier selector */
.prod-search { max-width: 240px; margin: 0; }
/* #122.2: Filter ▾ expander — a full-width second row inside the frosted pane
   holding the three labelled dropdowns (Product Category / Supplier Name /
   Price). Wrap is scoped to the Products bar; other header panes keep one row. */
#view-products .dims-bar { flex-wrap: wrap; }
.prod-filters { flex-basis: 100%; display: flex; align-items: flex-end;
  gap: 14px; flex-wrap: wrap; }
.prod-ctl { display: flex; flex-direction: column; gap: 3px; }
.prod-ctl-label { font-size: 11px; color: var(--muted); }
/* #122.3 (founder): each dropdown sizes to its LONGEST entry — auto width;
   checkpoint #5: bounded so one very long supplier name can't blow the bar
   past the viewport (the select clips internally beyond the cap) */
.prod-ctl .cell-input { margin: 0; width: auto; max-width: 340px; text-align: left; }
#prod-clear { align-self: flex-end; }
.prod-global { display: flex; align-items: center; gap: 6px; margin: 0;
  white-space: nowrap; color: var(--ink); font-size: 13px; }
.prod-global input[type="checkbox"] { display: inline-block; width: auto; margin: 0; }
.prod-set-cell { white-space: nowrap; }
.prod-set { width: 90px; display: inline-block; margin-right: 6px; }
.prod-sup { min-width: 120px; }
/* #122.4: the selector lives in the Supplier COLUMN now — fill the cell */
.sup-choice { margin: 0; width: 100%; max-width: 220px; display: block;
  text-align: left; }
.sup-tag { font-size: 11.5px; }
/* a line WITH a supplier wears the found/complete green, bold (founder 2026-08-20) */
td.sup-cell.sup-set { color: var(--ok); font-weight: 700; }
td.sup-cell { font-size: 12.5px; overflow-wrap: anywhere; }

.acc-danger { color: var(--bad); border-color: #e6b8b4; }
.acc-danger:hover:not(:disabled) { background: #fbe4e2; }
.acc-danger:disabled { opacity: .55; cursor: default; }

#notes-overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, .45);
  display: flex; align-items: center; justify-content: center; z-index: 60; }
#notes-overlay.hidden { display: none; }
.notes-box { max-width: 480px; width: calc(100% - 32px); text-align: left; }
.notes-box .notes-label { display: block; margin: 10px 0 2px; font-weight: 600; }
.notes-box .notes-label .muted { font-weight: 400; }
.notes-box textarea { width: 100%; margin-top: 4px; }

/* #94 grey lines: the quote couldn't price these — visible, muted, priceable in
   Edit (their money arrives from the user, flagged non-standard). */
.row-unpriced td { color: var(--muted); }
.row-unpriced .badge { color: inherit; }
.desc-input { width: 100%; min-width: 140px;     /* #96.1: 220px blew the table
                                                    past the viewport on grey rows */
  text-align: left; }   /* #99.1 (user): a DESCRIPTION types left-to-left like
                           prose — it had inherited the money cells' right-align */

/* #105(1): the typed steel-spec picker on grey steel rows (section + length) */
.member-spec { display: flex; gap: 4px; margin-top: 4px; align-items: center; }
.member-spec .spec-select { max-width: 160px; text-align: left; }
.member-spec .spec-len { width: 76px; }

/* #96.1 (user): the Edit-mode action lane (remove/add) pins to the right edge —
   wide rows scroll UNDER it instead of pushing it off-screen */
#grid-tables table.edit-grid thead th:last-child,
#grid-tables table.edit-grid tbody td:last-child {
  position: sticky; right: 0; background: #fff;
  box-shadow: -6px 0 8px -6px rgba(20, 35, 60, 0.18);
}
#grid-tables table.edit-grid tbody tr:not(.row-hidden):hover td:last-child {
  background: #f7fafd;               /* hover tint parity for the pinned lane */
}

/* #98 uplift columns (quote grid, edit mode only) — margin, never printed */
.uplift-pct-input, .uplift-abs-input { width: 70px; }
.uplift-bulk, .waste-bulk { width: 70px; display: inline-block; margin-top: 3px; }
th.uplift-h, th.waste-h { white-space: nowrap; }
/* §3.9(2) breakage/wastage column — advisory allowance, never in the job total */
.wastage-pct-input { width: 70px; }
.waste-hint { font-size: 11px; font-variant-numeric: tabular-nums; }

/* #99 Options dropdown (quote exports) */
.options-wrap { position: relative; display: inline-block; }
.options-menu { position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
  background: #fff; border: 1px solid #d4dce8; border-radius: 10px;
  box-shadow: 0 10px 26px -10px rgba(20, 35, 60, 0.4); padding: 12px 14px;
  min-width: 250px; text-align: left; }
.options-menu label { display: flex; align-items: center; gap: 8px; margin: 7px 0;
  color: var(--ink); font-size: 13.5px; }
.options-menu input[type="checkbox"] { display: inline-block; width: auto; margin: 0; }
.options-menu .opt-hint { font-size: 12px; margin: 0 0 8px; }

/* #99 (user): pills in edit cells stack UNDER the value instead of overlapping
   the unit text — fixed columns are narrower than the old auto layout */
table.edit-grid td.num { white-space: normal; }
table.edit-grid td .badge { display: inline-block; margin: 3px 0 0; }

/* #98.1 live preview: the "→ £new" under Line £ + the running total strip */
.uplift-preview { font-size: 12px; color: var(--accent);
                  font-variant-numeric: tabular-nums; }
.uplift-preview-strip { font-size: 13px; font-weight: 600; color: var(--accent);
                        font-variant-numeric: tabular-nums; }

/* #98.2 (user): section tables ALIGN — every section is its own <table>, and
   auto layout sized columns per section's content, so column edges jumped while
   scrolling. Fixed layout + ONE set of per-view column widths = one ruler for
   every section, BoM and Quote alike. Long content wraps (user: a taller row
   beats a horizontal scroll); below the min-width the whole table scrolls as
   one inside .table-wrap. This also supersedes the #96.4 padding hack: with
   real column widths the money and uplift columns hold their ground beside the
   pinned action lane by construction. */
#grid-tables table, #bom-tables table { table-layout: fixed; min-width: 780px; }
#grid-tables table.edit-grid { min-width: 980px; }
#grid-tables td, #bom-tables td { overflow-wrap: break-word; }
#grid-tables td .cell-input, #bom-tables td .cell-input { max-width: 100%; }
/* BoM: expander | Drawing Item | Spec | Qty | Unit | Reference | badges */
#bom-tables th:nth-child(1) { width: 4%; }
#bom-tables th:nth-child(2) { width: 27%; }
#bom-tables th:nth-child(3) { width: 29%; }
#bom-tables th:nth-child(4) { width: 6%; }
#bom-tables th:nth-child(5) { width: 7%; }
#bom-tables th:nth-child(6) { width: 14%; }
#bom-tables th:nth-child(7) { width: 13%; }
/* Quote read-only: Product | Qty | Unit | £/unit | Line £ | Supplier | badges |
   basket (#122.4 supplier column; #47 the order column at the very end).
   #47C: the 780px floor predated Supplier + basket — the money columns were
   quietly squeezed ~15%. The read quote gets its own 900px floor and the
   ruler re-weights toward the pill-carrying columns, so short pills hold one
   line even at the floor (landscape iPad 11" fits without scrolling once the
   sidebar condenses; portrait scrolls the table, as the edit grid does). */
#grid-tables table.quote-ro { min-width: 900px; }
/* #47D (founder, iPad): the ORDER column pins right in the read quote — the
   basket is never half-cut at the container's edge mid-scroll (the #96.1
   lane pattern, wash + hover twins included) */
#grid-tables table.quote-ro thead th:last-child,
#grid-tables table.quote-ro tbody td:last-child {
  position: sticky; right: 0; background: #fff; }
#grid-tables table.quote-ro tbody tr:not(.row-hidden):hover td:last-child {
  background: #f7fafd; }
#grid-tables table.quote-ro tbody tr.row-labour td:last-child { background: #fde9d6; }
#grid-tables table.quote-ro tbody tr.row-plant td:last-child { background: #dcefec; }
#grid-tables table.quote-ro tbody tr.row-labour:not(.row-hidden):hover td:last-child { background: #fbdfc4; }
#grid-tables table.quote-ro tbody tr.row-plant:not(.row-hidden):hover td:last-child { background: #cfe9e5; }
table.quote-ro th:nth-child(1) { width: 30%; }
table.quote-ro th:nth-child(2) { width: 7%; }
table.quote-ro th:nth-child(3) { width: 8%; }
table.quote-ro th:nth-child(4) { width: 11%; }
table.quote-ro th:nth-child(5) { width: 13%; }
table.quote-ro th:nth-child(6) { width: 13%; }
table.quote-ro th:nth-child(7) { width: 13%; }
table.quote-ro th:nth-child(8) { width: 5%; }
/* #47A (founder 2026-08-20): mixed-length £/unit notes (timber's stacked
   per-length prices) were nowrap inside a fixed-width column — wide ones
   painted ACROSS into Line £. Taller row beats horizontal bleed (house
   rule): in the read-only quote they wrap in place instead. */
table.quote-ro td .comp-line { white-space: normal; }
/* #47B (founder 2026-08-20): the SYSTEMATIC no-breach rule for read-mode
   tables. A cell may stack pills/notes after its number (uplift, waste,
   edited, amended…) — nothing may ever paint across a neighbour: the cell
   wraps (money tokens carry no spaces, so they hold whole) and pills wrap
   as WHOLE UNITS onto the next line. #47C (founder, iPad 11"): pills never
   break internally — words stacking inside a 999-radius pill read as cut
   off; instead the quote table's floor is sized so every short pill holds
   one line (sentence-style flags wear .badge-note and wrap as prose).
   Edit mode keeps its own ruler + horizontal scroll — untouched. */
table.quote-ro td.num, table.quote-pre td.num, #bom-tables td.num {
  white-space: normal; }
.badge.badge-note { white-space: normal; }
/* Quote pre-quote (no £ columns yet): Product | Qty | Unit | badges */
table.quote-pre th:nth-child(1) { width: 58%; }
table.quote-pre th:nth-child(2) { width: 12%; }
table.quote-pre th:nth-child(3) { width: 13%; }
table.quote-pre th:nth-child(4) { width: 17%; }
/* Quote edit: Product | Qty | Unit | £/unit | Line £ | Uplift % | Uplift £ |
   Waste % (§3.9(2)) | Supplier (#122.4) | actions.
   #47D (founder-caught): the ruler was never re-cut for the Supplier column —
   ten columns shared nine widths, the sticky action lane collapsed to ZERO
   and its pill/Remove/✎ painted over the supplier selector. Ten real cuts. */
table.edit-grid th:nth-child(1) { width: 22%; }
table.edit-grid th:nth-child(2) { width: 5%; }
table.edit-grid th:nth-child(3) { width: 6%; }
table.edit-grid th:nth-child(4) { width: 10%; }
table.edit-grid th:nth-child(5) { width: 11%; }
table.edit-grid th:nth-child(6) { width: 6%; }
table.edit-grid th:nth-child(7) { width: 6%; }
table.edit-grid th:nth-child(8) { width: 6%; }
table.edit-grid th:nth-child(9) { width: 13%; }
table.edit-grid th:nth-child(10) { width: 15%; }

/* #95: the OPEN line-items view's toggle inverts — you can see which pane is on
   show. Beats the ghost style. */
button.ghost.active-view { background: var(--accent); color: #fff;
  border-color: transparent; }

/* #95 add-product footer: search dropdown + staged rows */
.add-line-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 6px 0; }
.search-wrap { position: relative; flex: 1; min-width: 260px; }
/* #99.1 (user): FIXED so the list escapes .table-wrap's overflow clip — it was
   hiding behind the notes block; left/top/width are set from the input's rect */
.search-hits { position: fixed; z-index: 200;
  background: #fff; border: 1px solid #d4dce8; border-radius: 8px;
  box-shadow: 0 8px 20px -8px rgba(20, 35, 60, 0.35); max-height: 320px;
  overflow-y: auto; }
.search-hit { padding: 7px 12px; font-size: 13.5px; cursor: pointer; }
.search-hit:hover { background: #eef4fc; }
/* #114: the section map ranks instead of gating — out-of-range hits are tagged */
.search-hit .off-section-tag { font-size: 11.5px; font-style: italic; }
.add-line-form .qty-input { width: 90px; }
.add-line-form .price-input { width: 110px; }
/* §3.9(2): line-type + buying-unit selects on the add form */
.add-line-form .kind-select { width: 110px; }
.add-line-form .unit-select { width: 80px; }
/* #115: live £ preview beside the add form — names what the typed price buys */
.add-line-form .add-preview { font-style: italic; white-space: nowrap; }
.row-staged-add td { color: var(--muted); font-style: italic; }

/* #95.1: search-hit count header + taller scrollable dropdown */
.search-count { padding: 5px 12px; font-size: 12px; border-bottom: 1px solid #edf1f7; }

/* #96: whole-section remove/restore (red like row-hidden while removed) + the
   per-section note (Quote PDF only) */
h3.sec-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
button.sec-remove-btn { font-size: 12.5px; padding: 3px 10px; }
h3.sec-head.sec-removed span, .table-wrap.sec-removed td,
.table-wrap.sec-removed input.cell-input, .table-wrap.sec-removed .comp-line,
.table-wrap.sec-removed td .muted, .sec-note.sec-removed textarea {
  color: var(--bad); }
.table-wrap.sec-removed .badge, .table-wrap.sec-removed .scan-badge { opacity: 0.55; }
.sec-note { display: flex; align-items: flex-end; gap: 8px; margin: 6px 0 14px; }
.sec-note-input { flex: 1; min-height: 44px; resize: vertical; font: inherit;
  font-size: 13.5px; padding: 7px 10px; border: 1px solid #d4dce8;
  border-radius: 8px; color: var(--ink); background: #fff; }
.sec-note-count { font-size: 12px; white-space: nowrap; }
p.sec-note-ro { margin: 4px 0 14px; font-style: italic; }

/* ---------- Suppliers module (#118 §3.9.3) ---------- */
.sup-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 10px 0 14px; }
.sup-prefill-lead { font-size: 13px; }
button.sup-chip { font-size: 12.5px; padding: 4px 10px; }
#sup-list tbody tr.sup-row { cursor: pointer; }
#sup-list tbody tr.sup-row:hover { background: #f7fafd; }
#sup-card input { display: block; width: 50%; }
#sup-card .addr-row input { width: auto; }
/* #118.1 (founder): one vertical beat on the card — every group lead gets the same
   air above it, and standalone inputs sit on the .addr-row 12px rhythm */
#sup-card label.label-ink { display: block; margin-top: 16px; }
#sup-card > input { margin-top: 12px; }
#sup-card .addr-row input { margin-top: 0; }
.sup-terms-lead { margin: 16px 0 0; }
/* #118.1: credit terms is a plain number with "days" beside it (30/60/90 account) */
#sup-card .sup-days-wrap { flex: 1; display: flex; align-items: center; gap: 8px; }
#sup-card .sup-days-wrap input { flex: 1; }
.sup-days { color: var(--muted); font-size: 14px; }
/* #118.1: currency is a dropdown (GBP-only until multi-currency is real) */
#sup-card select { display: block; padding: 9px 13px; font-size: 15px;
  color: var(--ink); border: 1px solid #d4dce8; border-radius: 10px;
  background: #fff; }
#sup-card .addr-row select { flex: 0 0 110px; margin-top: 0; }
.sup-actions { display: flex; gap: 10px; margin-top: 18px; }
button.sup-delete { color: var(--bad); margin-left: auto; }
@media (max-width: 900px) {
  #sup-card input { width: 100%; }
  #sup-card .addr-row { width: 100%; }
}

/* checkpoint #4: keyboard-openable supplier rows — the name is a real button */
button.sup-open { background: none; border: none; box-shadow: none; padding: 0;
  color: var(--ink); font: inherit; font-weight: 600; cursor: pointer; }
button.sup-open:hover, button.sup-open:focus { text-decoration: underline; }

/* ---------- #119 Dimensions pane ---------- */
.dims-sec { font-size: 12px; }
.dims-input { width: 110px; margin-top: 0; text-align: right; }
#dims-body td.num, #dims-body th.num { text-align: right; }
/* #119.2/.3 (founder): HOUSE STYLE — header/action panes wear the frosted pale
   blue (rgba 241,244,248,.82 + blur) so they read apart from white content
   bodies; actions sit on the RIGHT. Applies to the dims-bar, both stage panes
   and the module picker bar; new header panes follow suit. */
.dims-bar { position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 14px;
  background: rgba(241, 244, 248, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 12px 26px; margin: -22px -26px 12px;
  border-radius: 12px 12px 0 0;
  box-shadow: 0 8px 10px -10px rgba(16, 32, 58, 0.35); }
.dims-bar h2 { margin: 0; }
/* #121.2 (founder): header CAPTIONS live INSIDE the pane (principle #12) —
   title + explainer stack on the left, actions keep the right edge */
.dims-bar-left { flex: 1 1 auto; min-width: 0; margin-right: auto; }
.dims-bar .dims-caption { margin: 4px 0 0; font-size: 13px; max-width: 720px; }
#stage-cards.tab-hidden { display: none; }


/* #119.3 (founder): the stage panes wear the frosted header-pane house style —
   copy/breakdown on the LEFT, actions on the RIGHT (Recalculate's position) */
.stages { grid-template-columns: 1fr; }   /* #119.3: panes run edge to edge */
.stages .card.stage { background: rgba(241, 244, 248, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: flex-start; gap: 18px; }
.stage-left { flex: 1 1 auto; min-width: 0; }
.stage-right { display: flex; flex-direction: column; align-items: flex-end;
  gap: 6px; flex: 0 0 auto; max-width: 62%; }
.stage-right .downloads { display: flex; flex-wrap: wrap;
  justify-content: flex-end; align-items: center; gap: 6px; margin: 0; }
.stage-right .export-row { justify-content: flex-end; margin: 4px 0 0; }
.picker-bar { background: rgba(241, 244, 248, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
@media (max-width: 900px) {
  .stages .card.stage { flex-direction: column; }
  .stage-right { align-items: flex-start; max-width: 100%; }
  .stage-right .downloads, .stage-right .export-row { justify-content: flex-start; }
}

/* checkpoint #6 (founder): sticky section headers on the quote/BoM grids — the
   column names (Product / Qty / Unit / £/unit / Line £ / Supplier) stay in
   sight through a long section, and at each section boundary the next
   section's own header row pushes the previous one out. Sticky pins against
   the nearest scroll container, so this only engages on wraps app.js has
   MEASURED as needing no horizontal scroll (.no-hscroll — see
   syncStickyHeads); a wrap that scrolls sideways keeps static headers.
   --stagebar-h = the pinned stage bar's real height (same source), so the
   header row pins flush beneath it, frosted per principle #12. */
#grid-tables .table-wrap.no-hscroll,
#bom-tables .table-wrap.no-hscroll,
#dims-body .table-wrap.no-hscroll { overflow-x: visible; }
#grid-tables .no-hscroll thead th,
#bom-tables .no-hscroll thead th,
#dims-body .no-hscroll thead th {
  position: sticky;
  /* below whichever chrome is pinned above the table (the stage bar on the
     quote/BoM tabs, the Dimensions pane's own bar there) PLUS the section
     heading pinned above this column row (--sechead-h, set per box; 0 where
     a box has no section headings — the Dimensions pane) */
  top: calc(var(--stagebar-h, 132px) + var(--dimsbar-h, 0px) + var(--sechead-h, 0px));
  z-index: 20;              /* above the pinned right lane's cells, below the bars */
  background: rgba(241, 244, 248, 0.92);   /* frosted: rows ghost underneath */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
/* Founder (2026-09-01, live pass): the SECTION heading pins too — stacked above
   the column row, so mid-scroll you always know WHICH section you're in; the
   next section's heading (equally sticky, solid ground) covers it at the
   boundary. Page-level mode only, via :has — in wrap-mode the heading sits
   naturally above its viewport-capped card. */
#grid-tables > h3:has(+ .table-wrap.no-hscroll),
#bom-tables > h3:has(+ .table-wrap.no-hscroll) {
  position: sticky;
  top: calc(var(--stagebar-h, 132px) + var(--dimsbar-h, 0px));
  z-index: 21;                      /* over the column row, under the bars (30/40) */
  background: rgb(241, 244, 248);   /* SOLID: the superseded heading hides fully */
  box-shadow: 0 6px 8px -8px rgba(16, 32, 58, 0.25);
}
/* the corner cell of the pinned-right lane keeps the frosted header wash (its
   own rule paints it white for the horizontal-scroll case, inert here) */
#grid-tables .no-hscroll table.quote-ro thead th:last-child,
#grid-tables .no-hscroll table.edit-grid thead th:last-child {
  background: rgba(241, 244, 248, 0.92);
}
/* Founder follow-up (2026-09-01): a wrap that DOES scroll sideways — the quote
   grid on tablet/narrow windows — must not surrender its headers: cap its
   height to the viewport so the wrap becomes its table's own scroller in BOTH
   axes; the header row then pins at the wrap's top (and the ORDER lane keeps
   its right pin) while the page scrolls between section cards. */
#grid-tables .table-wrap:not(.no-hscroll),
#bom-tables .table-wrap:not(.no-hscroll),
#dims-body .table-wrap:not(.no-hscroll) {
  max-height: calc(100vh - var(--stagebar-h, 0px) - var(--dimsbar-h, 0px) - 72px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
#grid-tables .table-wrap:not(.no-hscroll) thead th,
#bom-tables .table-wrap:not(.no-hscroll) thead th,
#dims-body .table-wrap:not(.no-hscroll) thead th {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(241, 244, 248, 0.92);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
#grid-tables .table-wrap:not(.no-hscroll) table.quote-ro thead th:last-child,
#grid-tables .table-wrap:not(.no-hscroll) table.edit-grid thead th:last-child {
  background: rgba(241, 244, 248, 0.92);   /* the two-axis corner frosts too */
}
