/* ============================================================================
   Cabinet Quoting — "Workbench" design system
   ----------------------------------------------------------------------------
   Dark graphite chrome around a bright paper working surface: the machine
   housing and the board you cut from. Every measurement and every rand is set
   in tabular monospace, because a cutting list is a table of measurements and
   digits that don't line up are digits you misread on a workshop floor.

   No web fonts — this has to work on a shop PC with no internet.
   ========================================================================== */

:root {
  /* Chrome — the machine housing */
  --chrome-900: #171b20;
  --chrome-800: #1f252c;
  --chrome-700: #2b333c;
  --chrome-line: #39424c;
  --chrome-ink: #e8ecef;
  --chrome-ink-soft: #97a3ae;

  /* Working surface — the board */
  --field: #eef0ec;
  --sheet: #ffffff;
  --sheet-sunk: #f7f8f6;
  --ink: #14181c;
  --ink-soft: #5f6a73;
  --ink-faint: #8a949c;
  --rule: #dfe3de;
  --rule-strong: #c4cac3;

  /* Signals */
  --accent: #1c5d4a;         /* pine — primary action, money confirmed */
  --accent-hover: #164a3b;
  --accent-wash: #e7f0eb;
  --measure: #1f6f8b;        /* drafting blue — computed & live values */
  --measure-wash: #e6f0f4;
  --pending: #a86a10;        /* amber — unsaved changes */
  --pending-wash: #fcf2e0;
  --alert: #97362c;          /* clay — destructive, bad dimensions */
  --alert-wash: #fbebe9;

  --radius: 8px;
  --radius-lg: 12px;
  --shadow-sm: 0 1px 2px rgba(20, 24, 28, 0.06);
  --shadow: 0 2px 8px rgba(20, 24, 28, 0.08), 0 1px 2px rgba(20, 24, 28, 0.05);
  --shadow-lift: 0 8px 28px rgba(20, 24, 28, 0.14);

  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono", "Roboto Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--field);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--measure); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--measure);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --------------------------------------------------------------- app chrome */

.topbar {
  background: var(--chrome-900);
  color: var(--chrome-ink);
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid #000;
}
.topbar-inner {
  max-width: 1560px;
  margin: 0 auto;
  padding: 0 22px;
  display: flex;
  align-items: center;
  gap: 26px;
  min-height: 52px;
}
.brand {
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #fff;
  white-space: nowrap;
}
.brand span { color: var(--chrome-ink-soft); font-weight: 500; }

/* The menu button and its backdrop belong to the phone layout only; on a wide
   screen the links are across the top and neither is drawn. */
.menu-btn { display: none; }
.menu-scrim { display: none; }
/* Folding a cupboard is a phone affordance; on a desktop the whole row is on
   screen at once and there is nothing to fold. */
.fold-btn { display: none; }
.topbar nav { display: flex; gap: 2px; flex: 1; flex-wrap: wrap; }
.topbar nav a {
  color: var(--chrome-ink-soft);
  font-size: 13px;
  font-weight: 500;
  padding: 7px 11px;
  border-radius: 6px;
  white-space: nowrap;
}
.topbar nav a:hover { background: var(--chrome-700); color: #fff; text-decoration: none; }
.topbar nav a.active { background: var(--accent); color: #fff; font-weight: 600; }

.topbar-right { display: flex; align-items: center; gap: 12px; }
.user-name { font-size: 12px; color: var(--chrome-ink-soft); }
.topbar-right button {
  background: transparent; color: var(--chrome-ink-soft);
  border: 1px solid var(--chrome-line); border-radius: 6px;
  padding: 5px 10px; font: inherit; font-size: 12px; cursor: pointer;
}
.topbar-right button:hover { color: #fff; border-color: var(--chrome-ink-soft); }

main.with-topbar { max-width: 1560px; margin: 0 auto; padding: 24px 22px 140px; }
main.narrow { max-width: 900px; }
main.centered { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }

/* ---------------------------------------------------------------- typography */

h1 { font-size: 21px; font-weight: 650; letter-spacing: -0.015em; margin: 0 0 3px; }
h2 { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; margin: 0 0 12px; }
h3 { font-size: 13px; font-weight: 650; margin: 0 0 8px; }
.subtitle { color: var(--ink-soft); font-size: 13px; margin: 0 0 18px; max-width: 78ch; }

/* Drafting labels — wide-tracked uppercase micro type, the vernacular of a
   dimensioned drawing. Used for every field label and column head. */
.eyebrow, label, th {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
label { display: block; margin-bottom: 5px; }

.page-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 20px; flex-wrap: wrap; margin-bottom: 20px;
}

/* Every number in this app is monospace and tabular. */
.mono, .num, td.num, input[type="number"], .money, .dim {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* -------------------------------------------------------------------- cards */

.card {
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 18px;
  margin-bottom: 18px;
  box-shadow: var(--shadow-sm);
}
.card.flush { padding: 0; overflow: hidden; }
.card-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 14px; padding: 14px 18px; border-bottom: 1px solid var(--rule);
  background: var(--sheet-sunk);
}
.card-head h2 { margin: 0; }
.card-head .hint { font-size: 12px; color: var(--ink-faint); font-weight: 400; text-transform: none; letter-spacing: 0; }

/* ------------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--rule); vertical-align: middle; }
th { background: var(--sheet-sunk); position: sticky; top: 0; z-index: 2; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--sheet-sunk); }
td.num, th.num { text-align: right; }

.table-scroll {
  overflow-x: auto;
  overflow-y: visible;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--sheet);
  /* A visible, always-present scrollbar — on a wide table you need to know
     there's more to the right without discovering it by accident. */
  scrollbar-color: var(--rule-strong) var(--sheet-sunk);
}
.table-scroll::-webkit-scrollbar { height: 12px; }
.table-scroll::-webkit-scrollbar-track { background: var(--sheet-sunk); border-radius: 0 0 var(--radius) var(--radius); }
.table-scroll::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: 6px; border: 3px solid var(--sheet-sunk); }
.table-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }

.scroll-hint {
  font-size: 11px; color: var(--ink-faint); margin: 6px 2px 0;
  display: flex; align-items: center; gap: 6px;
}

/* A long catalogue table in a box of its own.
 *
 * A wide table on a tall page puts its sideways scrollbar at the bottom of all
 * 174 rows, so reaching it meant scrolling to the foot of the page and losing
 * sight of the row being worked on. Capping the height the way the quote grid
 * is capped keeps the bar on screen at all times and keeps the column names
 * above the rows, which is the behaviour that already works on the quote. */
.sheet-scroll {
  max-height: calc(100vh - 260px);
  min-height: 220px;
  overflow: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--sheet);
  scrollbar-color: var(--rule-strong) var(--sheet-sunk);
}
.sheet-scroll::-webkit-scrollbar { height: 12px; width: 12px; }
.sheet-scroll::-webkit-scrollbar-track { background: var(--sheet-sunk); }
.sheet-scroll::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: 6px; border: 3px solid var(--sheet-sunk); }
.sheet-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }
.sheet-scroll > table { margin: 0; }

/* Setting something up is occasional; reading the list is constant. So the
   list comes first and the set-up panels fold away under it, which is what
   keeps the table's sideways scrollbar on screen instead of four cards down. */
details.setup-fold { padding: 0; }
details.setup-fold > summary {
  cursor: pointer; padding: 13px 18px; font-size: 14px;
  display: flex; align-items: baseline; gap: 2px; flex-wrap: wrap;
}
details.setup-fold > summary::marker { color: var(--ink-faint); }
details.setup-fold > summary:hover { background: var(--sheet-sunk); }
details.setup-fold[open] > summary { border-bottom: 1px solid var(--rule); }
details.setup-fold .setup-fold-body { padding: 16px 18px 18px; }
details.setup-fold .setup-fold-body > :first-child { margin-top: 0; }
/* A sticky <th> does nothing inside a table with collapsed borders — the
   browser has nowhere to hang it, so the column names scrolled away with the
   rows. The quote grid has always used separate borders for exactly this
   reason; these tables now do the same, drawing their rules per cell so they
   look as they did. */
/* Size to the content and let the box scroll, rather than squeezing eleven
   columns into the width of the card — which crushed a material's name down to
   two letters. */
table.sheet-sticky { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; }
table.sheet-sticky td > input[type="text"],
table.sheet-sticky td > input:not([type]) { min-width: 150px; }
table.sheet-sticky th,
table.sheet-sticky td { border-bottom: 1px solid var(--rule); }
table.sheet-sticky tbody tr:last-child td { border-bottom: none; }
table.sheet-sticky thead th {
  position: sticky; top: 0; z-index: 4;
  background: var(--sheet-sunk);
  border-bottom: 1px solid var(--rule-strong);
}
/* Inside a box that already scrolls sideways there is no reason to wrap. Four
   buttons squeezed into a 103px column stacked into four lines and made every
   row 158px tall, so three cupboard codes filled the window. Nothing wraps
   here; the row stays one line deep and the box scrolls to reach the rest. */
table.sheet-sticky th,
table.sheet-sticky td { white-space: nowrap; vertical-align: middle; }
table.sheet-sticky .row-actions { flex-wrap: nowrap; }
table.sheet-sticky thead th.num { text-align: right; }

/* -------------------------------------------------------------------- forms */

input, select, textarea {
  font: inherit;
  font-size: 13px;
  padding: 7px 9px;
  border: 1px solid var(--rule-strong);
  border-radius: 6px;
  background: var(--sheet);
  color: var(--ink);
  width: 100%;
  transition: border-color 0.12s, box-shadow 0.12s;
}
input:hover, select:hover, textarea:hover { border-color: var(--ink-faint); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--measure);
  box-shadow: 0 0 0 3px var(--measure-wash);
}
input[type="checkbox"] { width: auto; accent-color: var(--accent); }
select { cursor: pointer; }
textarea { resize: vertical; min-height: 64px; }

.field { margin-bottom: 14px; }
.field.tight { margin-bottom: 0; }

.grid { display: grid; gap: 14px; }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid.cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.grid.end { align-items: end; }

/* ------------------------------------------------------------------ buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; background: var(--accent); color: #fff; border: 1px solid var(--accent);
  padding: 8px 14px; border-radius: 6px; font: inherit; font-size: 13px; font-weight: 600;
  white-space: nowrap; transition: background 0.12s, border-color 0.12s;
}
.btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); text-decoration: none; color: #fff; }
.btn.secondary { background: var(--sheet); color: var(--ink); border-color: var(--rule-strong); }
.btn.secondary:hover { background: var(--sheet-sunk); border-color: var(--ink-faint); color: var(--ink); }
.btn.danger { background: var(--alert); border-color: var(--alert); }
.btn.danger:hover { background: #7d2c24; border-color: #7d2c24; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--ink-soft); }
.btn.ghost:hover { background: var(--sheet-sunk); color: var(--ink); }
.btn.small { padding: 5px 9px; font-size: 12px; }
.btn.icon { padding: 5px 8px; font-size: 13px; line-height: 1; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.row-actions { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* --------------------------------------------------------------------- pills */

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  background: var(--sheet-sunk); color: var(--ink-soft); border: 1px solid var(--rule);
}
.pill.draft { background: #f2f0ea; color: #7c6a3f; border-color: #e4dcc7; }
.pill.sent { background: var(--measure-wash); color: var(--measure); border-color: #c3dbe4; }
.pill.accepted { background: var(--accent-wash); color: var(--accent); border-color: #bfd9cd; }
.pill.paid { background: var(--accent-wash); color: var(--accent); border-color: #bfd9cd; }
.pill.unpaid { background: var(--pending-wash); color: var(--pending); border-color: #ecd9b2; }

/* ------------------------------------------------------- cupboard row editor */

/* One cupboard per row, scrolling sideways, with identity pinned left.
   Fewer, wider columns: each dropdown carries its own quantity, and the three
   dimensions share one cell, because they're read as one number anyway. */
/* The floor is the sum of what the columns below ask for. Set lower, the
   browser cannot honour the widths and falls back to sharing the space out by
   how much text is in each column — which gave the cost column 451px and left
   the Door picker 60, reading "N" where a board name should be. */
.cupboard-grid { min-width: 3490px; border-collapse: separate; border-spacing: 0; }
.cupboard-grid th, .cupboard-grid td {
  border-bottom: 1px solid var(--rule);
  padding: 7px 8px;
  white-space: nowrap;
  vertical-align: middle;
}
.cupboard-grid th { border-bottom: 1px solid var(--rule-strong); }
.cupboard-grid input, .cupboard-grid select { padding: 6px 8px; font-size: 13px; }

.cupboard-grid .w-code { width: 170px; }
.cupboard-grid .w-label { width: 140px; }
.cupboard-grid .w-exp { width: 130px; }
.cupboard-grid .w-size { width: 200px; }
/* A board name is the longest thing in the row and the one most worth reading:
   "Melawood Luca Polenta" needs the room more than a hinge name does. */
.cupboard-grid .w-mat { width: 190px; }
.cupboard-grid .w-pair { width: 180px; }
.cupboard-grid td.cost-cell, .cupboard-grid th.w-cost { width: 150px; }
/* The three tapes of one cupboard — carcase, doors, shelving — stacked in one
   cell rather than three columns. Three more 210px columns would have pushed
   the cost off the end of the grid on any screen; stacked they cost one
   column's width and read top to bottom in the order the heading names. */
.cupboard-grid .w-edge3 { width: 190px; }
.cupboard-grid .edge-trio { display: flex; flex-direction: column; gap: 3px; }
.cupboard-grid .edge-trio label { display: flex; align-items: center; gap: 4px; margin: 0; }
/* Three boxes that all read "follows the quote" are three boxes nobody can
   tell apart, so each carries its own letter. */
.cupboard-grid .edge-trio .edge-tag {
  flex: none; width: 10px; font-size: 10px; font-weight: 600;
  color: var(--ink-faint); text-align: center;
}
.cupboard-grid .edge-trio select { min-width: 0; flex: 1 1 auto; font-size: 11.5px; padding: 1px 6px; height: 21px; }
/* The four controls at the end of a row — the upgrade tick, the save state,
   Copy and the delete — were wrapping onto three lines inside a 130px column,
   which made every row in the grid 136px tall instead of 64px. On a
   forty-cupboard quote that is six cupboards on screen instead of thirteen.
   The grid scrolls sideways anyway, so the column can simply be wide enough. */
.cupboard-grid .w-act { width: 230px; }
.cupboard-grid .row-actions { flex-wrap: nowrap; }

/* Height × Depth × Length, laid out as it's spoken. */
.cupboard-grid .dims { display: flex; align-items: center; gap: 5px; }
.cupboard-grid .dims input {
  width: 58px; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.cupboard-grid .dims span { color: var(--ink-faint); font-size: 12px; }

/* A type and its quantity are one decision, so they share one cell. */
.cupboard-grid .pair { display: flex; gap: 5px; align-items: center; }
.cupboard-grid .pair select { flex: 1; min-width: 0; }
.cupboard-grid .pair .qty {
  width: 54px; flex: none; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}

/* Spinners steal roughly a third of a narrow number field and are useless for
   millimetres — the digits matter more. */
.cupboard-grid input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.cupboard-grid input[type="number"]::-webkit-outer-spin-button,
.cupboard-grid input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}

.rownum {
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  color: var(--ink-faint); width: 44px; text-align: center;
}

.cupboard-grid .sticky-col {
  position: sticky; left: 0; z-index: 3;
  background: var(--sheet);
  border-right: 1px solid var(--rule);
}
.cupboard-grid .sticky-col-2 {
  position: sticky; left: 44px; z-index: 3;
  background: var(--sheet);
  border-right: 1px solid var(--rule-strong);
  box-shadow: 4px 0 8px -4px rgba(20, 24, 28, 0.22);
}
/* The headings stay put.
 *
 * On a twenty-cupboard kitchen the columns run off the top of the screen and
 * you are left guessing whether a box is the hinge or the runner. The grid
 * therefore scrolls inside its own panel, and both heading rows — the groups
 * and the column names — stick to the top of it. The first two columns already
 * stick to the left, so the four corner cells sit above both. */
#cupboard-scroll {
  /* Tall enough to work in, short enough that the headings have something to
     stick to. Below that the page itself scrolls as before. */
  max-height: calc(100vh - 230px);
  overflow: auto;
}
.cupboard-grid thead th { position: sticky; z-index: 5; }
.cupboard-grid thead .colgroup-head th { top: 0; height: 26px; z-index: 6; }
.cupboard-grid thead tr:not(.colgroup-head) th { top: 26px; }
.cupboard-grid thead .sticky-col,
.cupboard-grid thead .sticky-col-2 { background: var(--sheet-sunk); z-index: 8; }
.cupboard-grid thead .colgroup-head .sticky-col,
.cupboard-grid thead .colgroup-head .sticky-col-2 { z-index: 9; }
.cupboard-grid thead .colgroup-head .sticky-col,
.cupboard-grid thead .colgroup-head .sticky-col-2 { background: var(--chrome-800); }
.cupboard-grid tbody tr:hover .sticky-col,
.cupboard-grid tbody tr:hover .sticky-col-2 { background: var(--sheet-sunk); }
.cupboard-grid tbody tr:hover { background: transparent; }
.cupboard-grid tfoot td { background: var(--sheet-sunk); }
.cupboard-grid tfoot .sticky-col,
.cupboard-grid tfoot .sticky-col-2 { background: var(--sheet-sunk); }

/* Column-family labels above the drafting labels, so 18 columns read as five
   ideas rather than eighteen. */
.colgroup-head th {
  background: var(--chrome-800); color: var(--chrome-ink-soft);
  font-size: 10px; letter-spacing: 0.12em; padding: 6px 8px;
  border-bottom: none; position: sticky; top: 0; z-index: 4;
}
.colgroup-head th.g-sep { border-left: 1px solid var(--chrome-line); }
.cupboard-grid thead tr:nth-child(2) th { top: 27px; }

/* Every cell that changes on save reserves its space up front. The cost cell
   used to grow a second line when the breakdown appeared, and the save badge
   changed width between "Saving…" and "Saved" — between them the row grew and
   the whole page shifted under the cursor while you were still typing. */
.cost-cell {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 13px; text-align: right; color: var(--measure); font-weight: 600;
  width: 150px;
}
.cost-cell .breakdown {
  /* Bounded, or the one long line of working inside it ("boards 2.1m² · hinges
     ×2 · …") sets the width of the whole column: the table lays out from its
     content, and this was taking 451px out of the grid while the board pickers
     were squeezed down to one letter. */
  max-width: 140px;
  display: block; font-size: 10.5px; color: var(--ink-faint); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-height: 14px;   /* held open whether or not there is a breakdown */
}
/* A fixed row height means nothing below a row can move when the row updates.
   78, not 64, since the edging cell stacks three boxes: a cupboard's carcase,
   doors and shelving tape read down the cell in the order the heading names
   them, which costs a quarter of a row's height and saves two columns. */
.cupboard-grid tbody tr.cup-row { height: 78px; }
.cupboard-grid tbody tr.cup-row > td { height: 78px; }

tr.row-dirty .sticky-col-2 { box-shadow: inset 3px 0 0 var(--pending), 4px 0 8px -4px rgba(20, 24, 28, 0.22); }
tr.row-saving .cost-cell { opacity: 0.5; }

.save-state {
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  display: inline-flex; align-items: center; gap: 5px;
  /* Fixed width: "Saving…", "Saved" and "Error" must not resize the cell. */
  width: 62px; white-space: nowrap; overflow: hidden;
}
.save-state.saved { color: var(--accent); }
.save-state.dirty { color: var(--pending); }
.save-state.error { color: var(--alert); }
.save-state .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* ------------------------------------------------------- live totals dock */

/* The number that matters, always on screen. */
.totals-dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  background: var(--chrome-900); color: var(--chrome-ink);
  border-top: 1px solid #000;
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.22);
}
.totals-dock-inner {
  max-width: 1560px; margin: 0 auto; padding: 10px 22px;
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
}
.dock-stat { display: flex; flex-direction: column; gap: 1px; }
.dock-stat .k {
  font-size: 9.5px; letter-spacing: 0.11em; text-transform: uppercase; color: var(--chrome-ink-soft); font-weight: 700;
}
.dock-stat .v { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 600; }
.dock-stat.grand .v { font-size: 20px; color: #fff; letter-spacing: -0.01em; }
.dock-spacer { flex: 1; }
.dock-actions { display: flex; gap: 8px; }
.dock-actions .btn.secondary { background: var(--chrome-700); color: var(--chrome-ink); border-color: var(--chrome-line); }
.dock-actions .btn.secondary:hover { background: var(--chrome-line); color: #fff; }

/* ------------------------------------------------------------ edging glyph */

/* Signature element. Four checkboxes called L1/L2/W1/W2 tell you nothing;
   a rectangle you click on its edges tells you exactly which edge gets tape. */
.edge-glyph {
  position: relative; width: 46px; height: 32px; margin: 0 auto;
  border: 1px dashed var(--rule-strong); border-radius: 2px; background: var(--sheet-sunk);
}
.edge-glyph input { position: absolute; opacity: 0; width: 0; height: 0; }
.edge-glyph .e {
  position: absolute; cursor: pointer; background: var(--rule-strong);
  transition: background 0.1s;
}
.edge-glyph .e:hover { background: var(--ink-faint); }
.edge-glyph .e.on { background: var(--accent); }
.edge-glyph .e-l1 { top: -1px; left: 4px; right: 4px; height: 4px; border-radius: 2px; }
.edge-glyph .e-l2 { bottom: -1px; left: 4px; right: 4px; height: 4px; border-radius: 2px; }
.edge-glyph .e-w1 { left: -1px; top: 4px; bottom: 4px; width: 4px; border-radius: 2px; }
.edge-glyph .e-w2 { right: -1px; top: 4px; bottom: 4px; width: 4px; border-radius: 2px; }
.edge-glyph-label { font-size: 9px; color: var(--ink-faint); text-align: center; margin-top: 3px; }

/* ------------------------------------------------------------------ notices */

.flash {
  background: var(--pending-wash); border: 1px solid #ecd9b2; color: #6f4a0d;
  padding: 10px 14px; border-radius: var(--radius); margin-bottom: 16px; font-size: 13px;
}
.flash.error { background: var(--alert-wash); border-color: #e6c2be; color: #7d2c24; }
.flash.ok { background: var(--accent-wash); border-color: #bfd9cd; color: var(--accent); }

.empty-state {
  color: var(--ink-faint); padding: 26px 18px; text-align: center; font-size: 13px;
}
.empty-state strong { display: block; color: var(--ink-soft); font-size: 14px; margin-bottom: 4px; }

.note-inline { font-size: 12px; color: var(--ink-faint); }

tr.warn-row td { background: var(--alert-wash) !important; color: var(--alert); font-weight: 600; }
tr.group-start td { border-top: 2px solid var(--rule-strong); }

/* ------------------------------------------------------------- totals table */

.totals-table td { padding: 5px 10px; }
.totals-table tr.sub td { border-top: 1px solid var(--rule-strong); font-weight: 600; }
.totals-table tr.grand td {
  font-weight: 700; font-size: 15px; border-top: 2px solid var(--ink); border-bottom: none; padding-top: 9px;
}

/* -------------------------------------------------------------- report deck */

.doc-deck { display: grid; grid-template-columns: repeat(auto-fill, minmax(255px, 1fr)); gap: 12px; }
.doc-card {
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 14px;
  background: var(--sheet); display: flex; flex-direction: column; gap: 8px;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.doc-card:hover { border-color: var(--rule-strong); box-shadow: var(--shadow); }
.doc-card .doc-name { font-weight: 650; font-size: 13.5px; }
.doc-card .doc-desc { font-size: 12px; color: var(--ink-faint); flex: 1; line-height: 1.45; }
.doc-card .doc-links { display: flex; gap: 6px; }

.filter-bar {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; align-items: end;
}

/* ---------------------------------------------------------------- print doc */

/* ==========================================================================
   Client-facing paperwork.

   Everything here sits on one modular grid, because the eye reads near-misses
   as sloppiness even when it can't name what's wrong. Two rules hold the page
   together:

   1. VERTICAL RHYTHM — every margin, padding and gap is a whole multiple of
      one base unit, and line-heights are multiples of it too. Nothing is 11px
      because 11 "looked right"; the page has a beat.

   2. A SIX-COLUMN GRID — every left edge on the page is a gridline of the same
      six-column grid. Label/value pairs split at 2 of 6; balanced pairs split
      at 3 of 6. Both are derived from one grid, so the axes relate to each
      other instead of merely coexisting.

   Numbers are tabular everywhere, so decimal points stack down a column.
   ========================================================================== */

:root {
  /* The beat. Every vertical measure below is a multiple of this. */
  --u: 8px;

  /* Six-column grid. --axis is the label/value split (2 of 6);
     --half is the balanced split (3 of 6). Both are gridlines of one grid. */
  --doc-cols: 6;
  --axis: 33.3333%;
  --half: 50%;
  --pad: calc(var(--u) * 8);   /* 64px — the content edge, left and right */

  /* Strictly monochrome, because the mark is: the logo samples as pure black
     on pure white with no colour anywhere. A gold or bronze accent was my
     invention and read as someone else's brand. The only colour on a client
     document is the red on the payment terms, which has to be noticed. */
  --doc-ink: #0F0F0F;          /* the logo's black */
  --doc-ink-soft: #55565A;
  --doc-ink-faint: #8B8D91;
  --doc-paper: #FFFFFF;
  --doc-rule: #DEDEDF;         /* neutral hairline, not warm stone */
  --doc-rule-strong: #0F0F0F;
  --doc-accent: #0F0F0F;       /* marks and rules are ink, not a colour */
  --doc-wash: #F5F5F6;         /* neutral, so it sits under a black mark */
  --doc-alert: #9A3B2E;
  --doc-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Cambria, Georgia, "Times New Roman", serif;
  --doc-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.paper {
  background: var(--doc-paper);
  max-width: 860px;
  margin: 0 auto;
  padding: calc(var(--u) * 7) var(--pad) 0;
  border: 1px solid var(--rule);
  border-radius: 2px;
  box-shadow: 0 12px 40px rgba(20, 18, 15, 0.09);
  font-family: var(--doc-sans);
  color: var(--doc-ink);
  font-size: 13px;
  /* 24px = 3 units. Body text lands on the grid. */
  line-height: calc(var(--u) * 3);
  position: relative;
  /* Keep the type colour even: no stretched words, no ragged hyphen breaks. */
  text-rendering: optimizeLegibility;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "kern" 1;
}

/* ------------------------------------------------------------- letterhead */

.lh { position: relative; margin-bottom: calc(var(--u) * 4); }
.lh-page {
  position: absolute; top: calc(var(--u) * -3); right: 0;
  font-size: 8.5px; line-height: calc(var(--u) * 1);
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--doc-ink-faint);
}

/* Equal side columns, so the centre block is genuinely centred rather than
   merely between two things. */
.lh-grid {
  display: grid;
  grid-template-columns: calc(var(--u) * 34) 1fr calc(var(--u) * 34);
  align-items: center;
  gap: calc(var(--u) * 3);
  min-height: calc(var(--u) * 19);
}
.lh-logo { display: flex; align-items: center; }
.logo-co { max-width: calc(var(--u) * 34); max-height: calc(var(--u) * 18); object-fit: contain; object-position: left center; }
.lh-co { text-align: center; }
.lh-name {
  font-family: var(--doc-serif);
  font-size: 15px; line-height: calc(var(--u) * 3);
  font-weight: 400; letter-spacing: 0.16em; text-transform: uppercase; color: var(--doc-ink);
}
.lh-addr {
  margin-top: var(--u);
  font-size: 10px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.06em; color: var(--doc-ink-soft);
}
.lh-addr span { display: block; }
/* Both marks in an identical box, right-aligned, so neither dominates. */
.lh-partners { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; gap: var(--u); }
.logo-pt { width: calc(var(--u) * 16); height: calc(var(--u) * 5); object-fit: contain; object-position: right center; opacity: 0.88; }

.lh-rule { height: 1px; background: var(--doc-rule); margin-top: calc(var(--u) * 3); }

/* --------------------------------------------------------- opening block */

.doc-open { margin: 0 0 calc(var(--u) * 5); }
.eyebrow-rule {
  margin: 0 0 calc(var(--u) * 1.5);
  /* Same size as the title beneath it, and bold: this line says what the quote
     is actually for, and at 9px grey it was the quietest thing on the page.
     Tracking comes right down — 0.3em is for small caps, not for 32px. */
  /* Six units, not five: a long name — "Quotation · Basement kitchenette &
     scullery" — wraps onto two lines, and at five they all but touched. */
  font-size: 32px; line-height: calc(var(--u) * 6);
  /* The same size as the client's name but not bold: at this size weight adds
     nothing and makes every document shout. */
  font-weight: 400; letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--doc-ink);
  text-wrap: balance;
}
.eyebrow-rule::after {
  content: ""; display: block; width: calc(var(--u) * 5); height: 1px;
  background: var(--doc-ink); margin-top: calc(var(--u) * 1.5);
}
.doc-title {
  font-family: var(--doc-serif);
  font-size: 32px; line-height: calc(var(--u) * 6);
  font-weight: 400; letter-spacing: -0.005em;
  margin: 0 0 var(--u); color: var(--doc-ink);
  text-wrap: balance;
}
.doc-meta { margin: 0; font-size: 11.5px; line-height: calc(var(--u) * 2); color: var(--doc-ink-soft); letter-spacing: 0.02em; }
.doc-meta .sep { padding: 0 var(--u); color: var(--doc-rule); }
/* The extra lines a client carries, under the meta line. One to a row, so a
   column of them reads down the page; the label column is wide enough for
   "Their order no:" and no wider. */
.doc-extra { margin: calc(var(--u) * 1.5) 0 0; }
.doc-extra p {
  margin: 0;
  font-size: 11.5px; line-height: calc(var(--u) * 2);
  color: var(--doc-ink-soft); letter-spacing: 0.02em;
}
.doc-extra .x-label {
  display: inline-block;
  min-width: calc(var(--u) * 24);
  padding-right: var(--u);
  color: var(--doc-ink-faint);
}

.doc-ref {
  margin: calc(var(--u) * 2) 0 0;
  font-size: 9.5px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--doc-ink-faint);
}

.doc-intro {
  font-family: var(--doc-serif);
  font-size: 16px; line-height: calc(var(--u) * 3);
  color: var(--doc-ink);
  margin: 0 0 calc(var(--u) * 4);
  padding-bottom: calc(var(--u) * 3);
  border-bottom: 1px solid var(--doc-rule);
  /* A comfortable measure: long lines are the commonest reason a page feels
     hard to read without looking obviously wrong. */
  max-width: none;
  /* The sentence and the section beneath it were running together. */
  margin-bottom: calc(var(--u) * 5);
  padding-bottom: calc(var(--u) * 4);
  text-wrap: pretty;
}

/* -------------------------------------------------------------- sections */

.block { margin: 0 0 calc(var(--u) * 4); }
.block-head {
  font-family: var(--doc-sans);
  font-size: 9px; line-height: calc(var(--u) * 2);
  font-weight: 700; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--doc-ink-faint);
  margin: 0 0 calc(var(--u) * 2);
  padding-bottom: var(--u);
  border-bottom: 1px solid var(--doc-rule);
}
.scope-head {
  font-size: 9px; line-height: calc(var(--u) * 2);
  font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--doc-ink); margin: 0 0 calc(var(--u) * 1.5);
}

/* Label/value pairs split on the shared axis. */
.spec-list { display: grid; grid-template-columns: var(--axis) 1fr; gap: 0; margin: 0; font-size: 12.5px; }
.spec-list dt,
.spec-list dd {
  padding: var(--u) 0;
  line-height: calc(var(--u) * 2);
  border-bottom: 1px solid var(--doc-wash);
  min-height: calc(var(--u) * 4);
}
.spec-list dt { color: var(--doc-ink-soft); letter-spacing: 0.02em; }
.spec-list dd { margin: 0; font-weight: 600; color: var(--doc-ink); }
.tbc { color: var(--doc-ink-faint); font-weight: 400; font-style: italic; }

/* Balanced pairs split on the other gridline. */
.two-col { display: grid; grid-template-columns: var(--half) 1fr; gap: 0 calc(var(--u) * 5); }
.two-col.scope { align-content: start; }
.scope-col { min-height: calc(var(--u) * 12); }
ul.checklist { list-style: none; padding: 0; margin: 0; font-size: 12.5px; }
ul.checklist li {
  padding: calc(var(--u) * 0.5) 0 calc(var(--u) * 0.5) calc(var(--u) * 2);
  line-height: calc(var(--u) * 2);
  position: relative; color: var(--doc-ink);
}
ul.checklist li::before {
  content: ""; position: absolute; left: 0; top: calc(var(--u) * 1.5);
  width: var(--u); height: 1px; background: var(--doc-ink);
}
ul.checklist li.tbc::before { display: none; }

/* ------------------------------------------------------------------ total */

/* The grand total must land on the same right edge as the money column above
   it — that column is what the eye has been tracking down the page, and a
   figure inset by its own padding reads as a mistake even at a glance. So the
   block carries no horizontal padding: label on the left content edge, figure
   on the right, exactly like the table. The rule above spans the same width as
   the table's rules, and whitespace does the work a tint was doing badly. */
.doc-total {
  margin: calc(var(--u) * 5) 0 calc(var(--u) * 4);
  padding: calc(var(--u) * 3) 0 0;
  border-top: 2px solid var(--doc-rule-strong);
}
.doc-total-inner { display: flex; justify-content: space-between; align-items: baseline; gap: calc(var(--u) * 3); }

/* The figure and the note it belongs to are one thing, so they are centred on
   each other. Right-aligning them independently left the number sitting off to
   one side of a wider line beneath it, which is what read as wrong. */
.doc-total-amount { display: inline-flex; flex-direction: column; align-items: center; }
.doc-total .lbl {
  font-size: 10px; line-height: calc(var(--u) * 2);
  font-weight: 700; letter-spacing: 0.28em; text-transform: uppercase; color: var(--doc-ink-soft);
}
.doc-total .val {
  font-family: var(--doc-serif);
  font-size: 30px; line-height: calc(var(--u) * 5);
  font-weight: 400; letter-spacing: -0.01em; color: var(--doc-ink);
  font-variant-numeric: tabular-nums;
}
.doc-validity {
  display: block; margin-top: var(--u); text-align: center;
  font-size: 10px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--doc-ink-faint);
  white-space: nowrap;
}

/* ------------------------------------------------------------ credentials */

.creds {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: var(--u) calc(var(--u) * 5);
  padding: calc(var(--u) * 2) 0 calc(var(--u) * 3);
  border-top: 1px solid var(--doc-rule); border-bottom: 1px solid var(--doc-rule);
  margin-bottom: calc(var(--u) * 4);
}
.creds span {
  font-size: 9.5px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--doc-ink-soft);
  /* No left padding: the words start on the same edge as every other line on
     the page. The mark hangs outside into the margin instead of pushing the
     text in, which is what made this block sit proud of the headings above
     and below it. */
  position: relative; padding-left: 0;
}
.creds span::before {
  content: "";
  position: absolute;
  left: calc(var(--u) * -2);
  /* Centred on the line by measurement, not by a guessed offset, so it stays
     centred at any size — including when the unit is re-scaled for print or
     for "fit on one page". */
  top: 50%;
  width: calc(var(--u) * 0.5); height: calc(var(--u) * 0.5);
  border: 1px solid var(--doc-ink);
  transform: translateY(-50%) rotate(45deg);
}

/* ------------------------------------------------------------------ terms */

.terms-block { margin-bottom: calc(var(--u) * 4); }
/* A line saying what a figure does not cover, under the table it belongs to. */
/* A heading gathering the rooms under it on a project quotation. */
.paper .group-row td {
  padding-top: calc(var(--u) * 2); font-size: 10px; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--doc-ink-soft); border-bottom: none;
}
.doc-aside {
  margin: calc(var(--u) * 2) 0 0; font-size: 12px; line-height: calc(var(--u) * 2.5);
  color: var(--doc-ink-soft);
}
/* Same axis as the specification, so the page has one secondary edge, not two. */
/* Half and half: the payment conditions are sentences, not labels, and were
   being squeezed into the narrow label column and broken mid-clause. */
.terms-grid { display: grid; grid-template-columns: var(--half) 1fr; gap: 0 calc(var(--u) * 4); font-size: 14.5px; }
.terms-grid > div + div { margin-top: calc(var(--u) * 3); }
.terms-grid .scope-head { margin-bottom: calc(var(--u) * 1); font-size: 10.5px; letter-spacing: 0.16em; }
.terms-grid p { margin: 0; line-height: calc(var(--u) * 2.5); color: var(--doc-ink-soft); }
.terms-grid p.hot { color: var(--doc-alert); font-weight: 600; }

.doc-thanks {
  font-family: var(--doc-serif);
  font-size: 13.5px; line-height: calc(var(--u) * 3);
  font-style: italic; color: var(--doc-ink-soft);
  margin: 0 0 calc(var(--u) * 4); max-width: 62ch; text-wrap: pretty;
}

/* --------------------------------------------------------------- signing */

.sign-block {
  display: grid; grid-template-columns: var(--half) 1fr;
  gap: 0 calc(var(--u) * 5); margin-bottom: calc(var(--u) * 6);
}
.sign-col { display: flex; flex-direction: column; }
/* The signature and the empty space above the client's line are the same
   height, so both rules sit on one line across the page. */
.sig { height: calc(var(--u) * 6); width: auto; align-self: flex-start; object-fit: contain; object-position: left bottom; }
.sign-space { height: calc(var(--u) * 6); }
.sign-line { border-top: 1px solid var(--doc-ink); margin-top: var(--u); padding-top: var(--u); }
.sign-name { margin: 0; font-size: 12px; line-height: calc(var(--u) * 2.5); font-weight: 600; color: var(--doc-ink); }
.sign-role {
  margin: calc(var(--u) * 1) 0 0; font-size: 9.5px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--doc-ink-faint);
}

/* Bleeds to the page edge: the offset is the padding, exactly. */
.doc-band {
  margin: 0 calc(var(--pad) * -1);
  background: var(--doc-ink); color: #F5F5F6;
  text-align: center; padding: calc(var(--u) * 2);
  font-size: 11px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.3em; text-transform: uppercase; font-weight: 500;
}

/* ------------------------------------------------------------------ tables */

.paper table { width: 100%; border-collapse: collapse; font-size: 12px; }
.paper th {
  background: transparent; text-align: left; position: static;
  border-bottom: 1px solid var(--doc-rule-strong);
  padding: 0 0 var(--u);
  font-size: 8.5px; line-height: calc(var(--u) * 2);
  font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; color: var(--doc-ink-faint);
}
.paper td {
  border-bottom: 1px solid var(--doc-wash);
  padding: calc(var(--u) * 1.5) 0;
  line-height: calc(var(--u) * 2);
  color: var(--doc-ink);
}
/* Money and quantities right-aligned and tabular, so decimals stack. */
.paper td.num, .paper th.num { text-align: right; font-variant-numeric: tabular-nums; }
.paper th + th, .paper td + td { padding-left: calc(var(--u) * 2); }
.paper tbody tr:last-child td { border-bottom: 1px solid var(--doc-rule); }

.stmt-table { width: 100%; border-collapse: collapse; margin-top: 0; font-size: 12.5px; }
.stmt-table th {
  border-bottom: 1px solid var(--doc-rule-strong);
  padding: 0 calc(var(--u) * 2) var(--u);
  font-size: 8.5px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--doc-ink-faint); text-align: right;
}
.stmt-table th:first-child { text-align: left; padding-left: 0; }
/* A heading belongs over its own column.
 *
 * Every heading was set to the right while only the money column's figures
 * were, so the invoice number and the date sat under headings standing well
 * away from them — the heading pushed to the far edge of a wide column and the
 * value at the near edge. These two carry their own class so the heading and
 * what it labels are set the same way, and the money stays right where money
 * belongs. */
.stmt-table th.ref, .stmt-table td.ref { text-align: left; }
.stmt-table td { padding: calc(var(--u) * 1.5) calc(var(--u) * 2); line-height: calc(var(--u) * 2); border-bottom: 1px solid var(--doc-wash); }
.stmt-table td:first-child { padding-left: 0; }
.stmt-table .stage { font-weight: 600; }
.stmt-table .out { color: var(--doc-alert); font-weight: 600; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; }
.stmt-table .paid { color: #3E6B4A; font-weight: 600; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; }
.stmt-table .amt { text-align: right; font-variant-numeric: tabular-nums; }
.stmt-table tr.tot td {
  font-weight: 700; border-top: 2px solid var(--doc-rule-strong); border-bottom: none;
  padding-top: calc(var(--u) * 2); font-size: 14px;
}

.stmt-head {
  color: var(--doc-ink); font-weight: 700;
  font-size: 9px; line-height: calc(var(--u) * 2);
  letter-spacing: 0.28em; text-transform: uppercase;
  margin: 0 0 calc(var(--u) * 2);
}

.invoice-note {
  font-family: var(--doc-serif);
  font-size: 13.5px; line-height: calc(var(--u) * 3);
  color: var(--doc-alert);
  margin: 0 0 calc(var(--u) * 4);
  padding-left: calc(var(--u) * 2);
  border-left: 2px solid var(--doc-alert);
  max-width: 62ch;
}

.paper-toolbar {
  max-width: 860px; margin: 0 auto calc(var(--u) * 2);
  display: flex; gap: var(--u); justify-content: flex-end;
}

/* -------------------------------------------------------------- cutting list */

.boardware {
  border: 2px solid #101315; padding: 0; background: #fff;
  font-family: var(--ui);
}
.boardware-head { padding: 14px 16px; border-bottom: 1px solid #101315; }
.boardware-head .title-row { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.boardware-head h2 { margin: 0; font-size: 17px; letter-spacing: 0.02em; color: #101315; text-transform: none; }
.boardware-meta { display: grid; grid-template-columns: 150px 1fr; gap: 2px 12px; font-size: 12.5px; margin-top: 10px; }
.boardware-meta dt { font-weight: 700; text-transform: uppercase; font-size: 10px; letter-spacing: 0.07em; color: #475056; padding-top: 2px; }
.boardware-meta dd { margin: 0; }
.boardware-banner {
  background: #d9e4d3; color: #7a3b1e; text-align: center;
  font-weight: 700; padding: 6px; font-size: 12.5px; letter-spacing: 0.03em;
}
.cut-table { border-collapse: collapse; font-size: 12px; width: 100%; min-width: 1180px; }
.cut-table th, .cut-table td { border: 1px solid #b9bfba; padding: 4px 7px; white-space: nowrap; }
.cut-table th { background: #eef0ec; position: sticky; top: 0; z-index: 2; }
.cut-table td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.cut-table input, .cut-table select { padding: 3px 5px; font-size: 12px; border-radius: 4px; }
.cut-table .w-n { width: 46px; }
.cut-table .w-sz { width: 82px; }
.cut-table .w-q { width: 58px; }
.cut-table .w-part { width: 165px; }
.cut-table .w-board { width: 175px; }
.cut-table .w-edge { width: 118px; }
.cut-table tr.sep td { background: var(--sheet-sunk); height: 8px; padding: 0; border-left: none; border-right: none; }

.cut-group-head td {
  background: var(--chrome-800); color: var(--chrome-ink);
  font-weight: 700; font-size: 11.5px; letter-spacing: 0.06em; text-transform: uppercase;
}

/* A cupboard whose code has not had its cutting list checked.
   Red on the heading, and said in words beside it — colour alone would be
   lost on a printed sheet, and this is read off a print at the saw. */
.cut-group-head.not-checked td {
  background: var(--alert);
  color: #fff;
}
.cut-unchecked {
  display: inline-block; font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em;
  background: #fff; color: var(--alert); border-radius: 3px;
  padding: 0 5px; margin-left: 7px; text-transform: uppercase;
}
.cut-ok { color: var(--accent); }

/* THE HOLD. A cutting list with a by-hand cupboard still unsigned.
   It PRINTS, deliberately: the buttons are shut, but a browser prints what is
   on the screen whether the program likes it or not, so a sheet that reaches
   the saw anyway arrives carrying the reason it should not have. */
.cut-hold {
  border: 3px solid var(--alert);
  background: var(--alert-wash);
  border-radius: 6px;
  padding: 12px 16px;
  margin: 0 0 14px;
}
.cut-hold strong {
  display: block; color: var(--alert);
  font-size: 15px; letter-spacing: 0.04em; text-transform: uppercase;
}
.cut-hold p { margin: 6px 0 0; color: var(--ink); font-size: 13.5px; }
.cut-hold p.why { color: var(--ink-faint); font-size: 12.5px; }
.cut-hold.done {
  border-color: var(--accent); background: var(--accent-wash, #eef5f0);
}
.cut-hold.done strong { color: var(--accent); }
.hand-check-cell { background: var(--alert-wash); }
.cut-check-cell { background: var(--measure-wash); }

.override-badge {
  display: inline-block; font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em;
  background: var(--pending-wash); color: var(--pending); border: 1px solid #ecd9b2;
  border-radius: 3px; padding: 0 4px; margin-left: 5px; text-transform: uppercase;
}

/* --------------------------------------------------------------- modal */

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(20, 24, 28, 0.55);
  display: flex; align-items: center; justify-content: center; z-index: 60; padding: 24px;
}
.modal {
  background: var(--sheet); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  max-width: 560px; width: 100%; padding: 22px; max-height: 90vh; overflow-y: auto;
}
.modal h2 { margin-top: 0; }
.modal .choice {
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 13px 14px;
  margin-bottom: 10px; cursor: pointer; display: flex; gap: 11px; align-items: flex-start;
}
.modal .choice:hover { border-color: var(--measure); background: var(--measure-wash); }
.modal .choice input { margin-top: 3px; }
.modal .choice .t { font-weight: 650; font-size: 13.5px; }
.modal .choice .d { font-size: 12px; color: var(--ink-soft); margin-top: 2px; line-height: 1.45; }

/* ------------------------------------------------------------------- login */

.login-card { width: 400px; max-width: 100%; }
.login-card .brand-mark {
  text-align: center; font-size: 12px; font-weight: 700; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--ink-faint); margin-bottom: 18px;
}

/* ------------------------------------------------------------------ toast */

.toast-stack { position: fixed; right: 20px; bottom: 76px; z-index: 70; display: flex; flex-direction: column; gap: 8px; }
.toast {
  background: var(--chrome-900); color: #fff; padding: 10px 15px; border-radius: var(--radius);
  font-size: 13px; box-shadow: var(--shadow-lift); animation: toast-in 0.18s ease-out;
}
.toast.err { background: var(--alert); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* A name typed without a space — a pasted reference, a filename — breaks
   rather than stretching a list table past the edge of the window. */
.card > summary { overflow-wrap: anywhere; min-width: 0; }

/* The catalogue tables — materials, hardware, cupboard codes — have eleven
   columns of editable boxes and are wider than a laptop, never mind a phone.
   They scroll sideways inside their card at every width rather than pushing
   the page off the side of the screen.

   A table that has been moved into a .sheet-scroll box is the exception: there
   the BOX scrolls, both ways, and it is capped to the height of the window so
   the sideways bar is always on screen. Letting the table scroll itself as well
   put that bar at the foot of all 174 rows, which is where it used to hide. */
#cat-table:not(.sheet-sticky) {
  display: block; overflow-x: auto; max-width: 100%; min-width: 100%;
  -webkit-overflow-scrolling: touch;
}
.sheet-scroll > table { display: table; overflow: visible; }

/* ------------------------------------------------------------- responsive */

@media (max-width: 900px) {
  .grid.cols-2, .grid.cols-3, .grid.cols-4, .grid.cols-5, .grid.cols-6 { grid-template-columns: 1fr; }
  .paper { padding: 26px 20px; }
  .paper .two-col, .paper .sign-row { grid-template-columns: 1fr; gap: 18px; }
  .totals-dock-inner { gap: 14px; padding: 8px 14px; }
  .dock-stat .v { font-size: 13px; }
  .dock-stat.grand .v { font-size: 17px; }
  main.with-topbar { padding: 18px 14px 150px; }
}





@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ------------------------------------------------------------------ print */

/* ------------------------------------------------------------ fit one page */

/* Everything scales off one unit and one font size, so squeezing a quote onto
   a single sheet is a matter of turning both down — the grid, the rhythm and
   every alignment hold, because they are all multiples of the same unit. */
.paper.fit-page {
  --u: 3.4pt;
  font-size: 8pt;
}
.paper.fit-page .doc-title { font-size: 16pt; }
.paper.fit-page .doc-intro { font-size: 8.5pt; }
.paper.fit-page .doc-total .val { font-size: 15pt; }
.paper.fit-page .lh-name { font-size: 8.5pt; }
.paper.fit-page .spec-list,
.paper.fit-page ul.checklist,
.paper.fit-page .terms-grid { font-size: 9pt; }
.paper.fit-page table { font-size: 7.5pt; }
.paper.fit-page .doc-thanks { display: none; }

/* Why "fit on one page" made words sit on top of each other.
 *
 * Everything on a document is spaced in units of --u, line-heights included,
 * which are written as multiples of it. Fit-page shrinks --u to 3.4pt and then
 * sets font sizes on particular blocks in pt — but those blocks keep their
 * unit-based line-heights, and the two no longer relate. The terms block ended
 * up as 9pt type on an 8.5pt line, the tables as 7.5pt on 6.8pt, the address
 * as 10pt on 9.1pt: a line shorter than the type it carries, which has to
 * collide.
 *
 * Rather than chase each one, every line inside a shrunk sheet is measured
 * against its own type. 1.32 is tight enough to keep the saving and can never
 * be shorter than the letters, whatever size an element ends up at and
 * whichever font the machine doing the printing substitutes. */
.paper.fit-page,
.paper.fit-page * { line-height: 1.32; }

/* The date and number strip runs the full width; at the smaller size its last
   pair was ending up hard against the edge of the sheet. */
.paper.fit-page .doc-stamp { padding-right: calc(var(--u) * 1.5); }
.paper.fit-page .stamp-pair { white-space: nowrap; }
/* Nothing here forces a height. The closing band became a fixed footer, so it
   no longer needs pushing to the bottom — and the min-height that used to do
   the pushing (261mm) was taller than the content area a 18/26mm margin
   leaves (253mm), which guaranteed a second, empty page every time. */

/* A cutting-list row that is worked out from the quote's extras — a kickplate,
   a ceiling filler, a custom panel. It has no cupboard and no recipe behind it,
   so there is nowhere to store an edit: it is shown, and plainly not editable,
   rather than offering boxes that cannot be saved. */
tr.from-extras td { background: var(--wash, #f4f5f6); }
tr.from-extras input, tr.from-extras select { border-style: dashed; color: var(--ink-soft); }

/* The colour, in a column of its own. Blank means "follow the cupboard", which
   the placeholder shows, so a pinned colour and an inherited one look
   different at a glance. */
.cut-table .w-colour { width: 190px; }
.cut-table .cl-colour { width: 100%; }
.cut-table .cl-colour::placeholder { font-style: italic; color: var(--ink-faint, #9a958a); }

/* The standing record at the foot of a job: every must-see thing from every
   step and every room. Quieter than the block at the top, which is about the
   work in front of you — this one is the history you go back to. */
/* The statement, edited where it is read. The boxes sit in the row rather than
   standing out of it, so the table still reads as a statement rather than as a
   form — and the two dates are narrow, because a date is a known width. */
.stage-table input[type="text"], .stage-table input:not([type]) { width: 100%; min-width: 90px; }
.stage-table input[type="date"] { width: 132px; }
.stage-table select { min-width: 104px; }
.stage-table td { vertical-align: middle; }
.stage-table .row-actions { white-space: nowrap; }

.must-see.standing { border-color: var(--rule); }
/* Nothing flagged yet. The card stays, so the place is always the same place,
   but it reads as quiet rather than as a warning with nothing in it. */
.must-see.standing.empty { background: transparent; }
.must-see.standing.empty .card-head { background: transparent; }
.must-see.standing .card-head { background: var(--sheet-sunk); }
.must-see.standing .flag-room h3 { color: var(--ink-soft); }

/* A form that exists only to be pointed at by a button elsewhere on the page,
   so nothing has to be nested inside anything. */
.hidden-form { display: none; }

/* One bulk box per edge, each with its own way to take the tape off. */
.bulk-fields .edge-bulk { position: relative; }
.bulk-fields .clear-edge { margin-top: 3px; font-size: 11px; color: var(--ink-soft); }
.bulk-fields .clear-edge input { margin-right: 4px; }

/* The two halves of the invoice register. Big enough to hit on a tablet, and
   each one says in a second line what kind of invoice it holds — "quote
   invoices" and "phased invoices" are not self-explanatory on their own. */
.reg-tabs { display: flex; gap: 10px; margin: 0 0 16px; flex-wrap: wrap; }
.reg-tab {
  flex: 1 1 240px; min-height: 56px;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  padding: 10px 16px; text-decoration: none;
  background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--radius);
  color: var(--ink-soft);
}
.reg-tab strong { font-size: 14px; color: var(--ink); }
.reg-tab span { font-size: 12px; }
.reg-tab.on { border-color: var(--accent); box-shadow: inset 0 -3px 0 var(--accent); }
.reg-tab.on strong { color: var(--accent); }

/* A rand figure never breaks across two lines: "R 194" above "100,00" reads as
   two different numbers at a glance. */
.reg-tabs ~ .card table td.num, .reg-tabs ~ .card table th.num { white-space: nowrap; }

/* A heading over a gathered set of rows, with that set's own money on it. */
tr.reg-group td {
  background: var(--wash, #f3f1ec);
  font-weight: 700; font-size: 12px; letter-spacing: 0.02em;
  border-top: 1px solid var(--rule-strong);
}
/* A quote in a phase that is not accepted: shown, but not counted. */
tr.not-agreed td { color: var(--ink-soft); }

/* A cancelled phase is history, not work. */
.phase-card.is-cancelled { opacity: 0.72; }
.phase-card.is-cancelled h2 { text-decoration: line-through; }

/* One payment stage on the editor. Six boxes and a remove button on one line,
   because the set is read down the page as a schedule — a card per stage turned
   three stages into three screens. */
.stage-tpl-row {
  display: grid;
  grid-template-columns:
    minmax(140px, 1.3fr) 138px 84px minmax(120px, 1fr) minmax(130px, 1.1fr) minmax(120px, 1fr) 120px 34px;
  gap: 10px; align-items: end;
  padding: 10px 0; border-bottom: 1px dashed var(--rule);
}
.stage-tpl-row:last-child { border-bottom: none; }
.stage-tpl-row .field { margin: 0; }
/* The labels are the same six words on every row; only the first row says them. */
.stage-tpl-row:not(:first-child) label { display: none; }
.stage-tpl-row [data-tpl-drop] { align-self: end; margin-bottom: 4px; }
.stage-tpl-row [data-tpl-pct]:disabled { background: var(--wash, #f4f5f6); color: var(--ink-soft); }

/* A quote whose price was agreed rather than costed. Marked plainly, because
   the cupboards underneath still look like they are driving the figure and
   they are not — mistaking the two is how a job gets re-priced by accident. */
.card.agreed-on {
  border-color: var(--measure, #2d7d9a);
  box-shadow: inset 4px 0 0 var(--measure, #2d7d9a);
}
.card.agreed-on > h2::after {
  content: "price agreed";
  margin-left: 10px; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; vertical-align: 2px;
  background: var(--measure-wash, #eaf4f8); color: var(--measure, #2d7d9a);
  border: 1px solid #c3dbe4;
}

/* Only on paper. Written here, above every print block, because a plain rule
   further down the file would beat the media query that turns it on — the same
   cascade trap the phone layout is kept last to avoid. */
.print-only { display: none; }
.sketch-print-head { margin-bottom: 8px; font-size: 13px; }

@media print {
  .topbar, .no-print, .totals-dock, .toast-stack, .paper-toolbar, .combo-list, .help-bubble { display: none !important; }
  body { background: #fff; }
  main { max-width: none !important; padding: 0 !important; }
  .card { border: none; box-shadow: none; padding: 0; }
  .table-scroll { overflow: visible !important; border: none; }
  /* On paper nothing scrolls and nothing sticks. */
  #cupboard-scroll { max-height: none !important; overflow: visible !important; }
  .sheet-scroll { max-height: none !important; min-height: 0 !important; overflow: visible !important; border: none; }
  .cupboard-grid thead th,
  table.sheet-sticky thead th { position: static !important; box-shadow: none !important; }

  /* ---- client paperwork, A4 -------------------------------------------- */

  /* The page margin IS the content edge, so the grid on paper is the same
     grid as on screen — nothing shifts between preview and print. */
  /* As little margin as the printer takes cleanly. 8mm is 0.32in — the custom
     margin that had to be typed into the print dialog every time. */
  @page { size: A4; margin: 8mm; }

  .paper {
    /* One base unit for print: 4.5pt. Every measure below is a multiple,
       exactly as on screen, so the rhythm survives the change of medium. */
    --u: 4.5pt;
    /* 8mm all round — the narrowest that still prints cleanly on the workshop
       printer, and what had to be set by hand in the print dialog before. */
    --pad: 8mm;
    border: none; box-shadow: none; border-radius: 0;
    /* Room at the foot for the fixed closing band, and nothing less.
     *
     * The band is pinned to the bottom of every printed page, so the flowed
     * content has to stop short of it. That gap used to be 5 units — but the
     * band's own height is four units of padding plus a line of 8pt type,
     * which is taller than five units, and "fit on one page" then shrinks the
     * unit while the 8pt stays 8pt. So the taller the band got relative to the
     * unit, the further the last lines of the document slid underneath it: the
     * signature block landed on top of the closing line.
     *
     * The band is now given an exact height in points, and that same figure
     * plus a little air is what the sheet keeps clear. Neither can shrink with
     * the unit, so they cannot drift apart again. */
    --band-h: 30pt;
    padding: 0 0 calc(var(--band-h) + 6pt); max-width: none; margin: 0;
    font-size: 9.5pt;
    line-height: calc(var(--u) * 3);
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
    orphans: 3;
    widows: 3;
  }

  .lh-page { top: calc(var(--u) * -3); font-size: 7pt; }
  /* The same unit multiples as on screen. Halving them here as well as
     re-scaling the unit shrank the logo twice over — it printed about 14mm
     wide, which reads as an afterthought on a document going to a client.
     At 20 units it is ~32mm, a proper letterhead. */
  .lh-grid {
    grid-template-columns: calc(var(--u) * 34) 1fr calc(var(--u) * 34);
    gap: calc(var(--u) * 3);
    min-height: 0;
  }
  .logo-co { max-width: calc(var(--u) * 34); max-height: calc(var(--u) * 18); }
  .logo-pt { width: calc(var(--u) * 16); height: calc(var(--u) * 5); }
  /* Images must actually be printed, not dropped as "background". */
  .logo-co, .logo-pt, .sig { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .lh-name { font-size: 10.5pt; line-height: calc(var(--u) * 3); }
  .lh-addr { font-size: 7.5pt; line-height: calc(var(--u) * 2); }

  .doc-title { font-size: 21pt; line-height: calc(var(--u) * 6); }
  .eyebrow-rule { font-size: 21pt; line-height: calc(var(--u) * 6); }
  .doc-meta, .doc-ref { font-size: 8pt; line-height: calc(var(--u) * 2); }
  /* The client's extra lines, at the same weight as the meta line they sit
     under, and never broken across a page from the name they belong to. */
  .doc-extra p { font-size: 8pt; line-height: calc(var(--u) * 2); }
  .doc-extra { break-inside: avoid; page-break-inside: avoid; }
  .doc-intro { font-size: 11pt; line-height: calc(var(--u) * 3); }

  .block-head, .scope-head { font-size: 7pt; line-height: calc(var(--u) * 2); }
  .spec-list { font-size: 9.5pt; }
  .spec-list dt, .spec-list dd { padding: calc(var(--u) * 0.75) 0; line-height: calc(var(--u) * 2); min-height: 0; }
  ul.checklist { font-size: 9.5pt; }
  ul.checklist li { line-height: calc(var(--u) * 2); }
  .scope-col { min-height: calc(var(--u) * 8); }

  .doc-total .val { font-size: 20pt; line-height: calc(var(--u) * 5); }
  .doc-total .lbl { font-size: 8pt; }
  .creds span { font-size: 7pt; line-height: calc(var(--u) * 2); }
  .terms-grid { font-size: 11pt; }
  .terms-grid .scope-head { font-size: 8.5pt; }
  .doc-thanks { font-size: 10pt; line-height: calc(var(--u) * 3); }
  .sig, .sign-space { height: calc(var(--u) * 6); }
  .paper table { font-size: 9pt; }
  .paper th { font-size: 7pt; }

  /* A real running footer, fixed to the foot of every printed sheet.
     In normal flow it was simply the last block, so when the page ran out it
     went over to the top of the next one — a closing line arriving above the
     content it was meant to close. The page keeps clear space for it. */
  .doc-band {
    position: fixed;
    /* The foot of the printed content area. A negative offset put it inside
       the paper margin, which the browser does not paint into — so it was
       pushed onto a page of its own instead. */
    bottom: 0;
    left: 0; right: 0;
    /* Exactly the height the sheet keeps clear for it — see --band-h above.
       Centred rather than padded, so the figure is the whole truth. */
    box-sizing: border-box;
    height: var(--band-h, 30pt);
    display: flex; align-items: center; justify-content: center;
    margin: 0 -8mm; padding: 0 calc(var(--u) * 2); font-size: 8pt;
    background: #0F0F0F !important; color: #F5F5F6 !important;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  /* Nothing that reads as one thing may be split across two pages. */
  .lh, .doc-open, .doc-total, .creds, .sign-block, .terms-block,
  .two-col, .scope-col, .doc-thanks, .invoice-note { break-inside: avoid; page-break-inside: avoid; }
  .block-head, .scope-head, .eyebrow-rule { break-after: avoid; page-break-after: avoid; }
  /* A4 at 96dpi is about 794px, which trips the narrow-screen rule that
     stacks two-column blocks. On paper there is always room for both, so the
     scope columns and the signing block stay side by side. */
  .paper .two-col, .paper .sign-block {
    grid-template-columns: var(--half) 1fr !important;
    gap: 0 calc(var(--u) * 5) !important;
  }
  .paper .terms-grid, .paper .spec-list {
    grid-template-columns: var(--axis) 1fr !important;
  }

  .paper thead { display: table-header-group; }
  .paper tr, .stmt-table tr { break-inside: avoid; page-break-inside: avoid; }
  /* A heading must never be the last thing on a page. */
  .block { break-before: auto; }

  /* ---- workshop paperwork ---------------------------------------------- */
  .cut-table, .order-table { min-width: 0; width: 100%; font-size: 7.5pt; }
  .cut-table th, .cut-table td,
  .order-table th, .order-table td { padding: 1mm 1.5mm; line-height: 1.35; }
  .cut-table .w-tick, .cut-table .c-act,
  .cut-table td:last-child, .cut-table th:last-child,
  .order-table .ol-act, .order-table td:last-child, .order-table th:last-child { display: none; }
  .order-table input, .order-table select { border: none; background: transparent; padding: 0; }
  .boardware { border: 1.5px solid #000; }
  .boardware-banner { background: #EDEAE3 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .cut-group-head td {
    background: #0F0F0F !important; color: #fff !important;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  tr.warn-row td { background: #fff !important; border: 1px solid #0F0F0F !important; font-weight: 700; }
}

/* The hold banner on a cutting list: louder on paper than on the screen, and
   never dropped from it. A browser prints what is on the screen whatever the
   program thinks, so a sheet that reaches the saw anyway arrives carrying the
   reason it should not have. */
@media print {
  .cut-hold { border-width: 4px; page-break-inside: avoid; }
  .cut-hold.done { display: none; }
}

/* ------------------------------------------------------------ stage cards */

/* One card per payment stage. The three percentage stages show a share of the
   quote; Design and Add On show the lines you typed, because that's work the
   quote never covered. */
.stage-card { border-left: 3px solid var(--pending); }
.stage-card.is-done { border-left-color: var(--accent); background: linear-gradient(90deg, var(--accent-wash) 0%, var(--sheet) 3%); }
.stage-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 14px; }
.stage-name {
  font-size: 15px; font-weight: 650; border: 1px solid transparent; background: transparent;
  padding: 3px 6px; width: auto; min-width: 230px; margin-right: 8px;
}
.stage-name:hover { border-color: var(--rule-strong); background: var(--sheet); }
.stage-amount {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 14px; font-weight: 600; color: var(--measure);
  padding: 7px 0;
}
.stage-amount.big { font-size: 22px; color: var(--ink); white-space: nowrap; }
.pair .suffix { color: var(--ink-faint); font-size: 13px; }

/* ------------------------------------------------------ department picker */

.cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 8px; margin-top: 8px; }
.cat-pick {
  display: flex; align-items: center; gap: 9px;
  border: 1px solid var(--rule); border-radius: 6px; padding: 9px 11px;
  cursor: pointer; text-transform: none; letter-spacing: 0; font-weight: 500;
  font-size: 13px; color: var(--ink); margin: 0; background: var(--sheet);
}
.cat-pick:hover { border-color: var(--measure); background: var(--measure-wash); }
.cat-pick .t { flex: 1; }
.cat-pick .a { font-size: 12px; color: var(--ink-faint); }
/* Already in another department — still pickable, but flagged so you don't
   double-count a cost without meaning to. */
.cat-pick.taken { opacity: 0.55; }
.cat-pick.taken .t::after { content: " (already used)"; color: var(--pending); font-size: 11px; }

/* Editable cutting list: keep the "was" hint from bulking the row. */
.cut-table.editable input { width: 100%; }
.cut-table .was { display: block; font-size: 9.5px; color: var(--ink-faint); margin-top: 1px; }
.cut-table .w-cust { width: 150px; }

/* ---------------------------------------------------- cutting recipe editor */

/* A part is described as "which measurement, then how much off it" — one idea,
   so the select and its offset live in one cell, sized to fit each other
   rather than each claiming the full width and overlapping. */
.recipe-table { min-width: 1180px; }
.recipe-table th, .recipe-table td { padding: 8px 10px; vertical-align: middle; }
.recipe-table .c-part { width: 170px; }
.recipe-table .c-dim { width: 230px; }
.recipe-table .c-prev { width: 130px; }
.recipe-table .c-qty { width: 120px; }
.recipe-table .c-split { width: 112px; }
.recipe-table .c-board { width: 165px; }
.recipe-table .c-edge { width: 86px; }
.recipe-table .c-act { width: 108px; white-space: nowrap; }
.recipe-table .c-part input { font-weight: 600; }
.recipe-table .c-qty input { text-align: center; font-family: var(--mono); }

.dim-rule { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.dim-rule .basis { flex: 1 1 118px; min-width: 0; }
.dim-rule .offset {
  flex: 0 0 66px; width: 66px; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
/* The rule read back in words, so you can check it without doing the sum. */
.rule-read { flex: 1 0 100%; font-size: 10.5px; color: var(--ink-faint); font-family: var(--mono); }

.recipe-table .prev-size {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 600; color: var(--measure);
}
.recipe-table .prev-warn {
  display: block; font-size: 10px; font-weight: 700; color: var(--alert); text-transform: uppercase; letter-spacing: 0.05em;
}
/* A part limited to a range of lengths is not an error — it simply isn't cut
   on a cupboard this size, which the preview has to say rather than imply. */
.recipe-table .prev-off {
  display: block; font-size: 10px; font-weight: 600; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: 0.05em;
}

/* "Only when the length is x to y", and "CNC over x" — the two rules that read
   the cupboard's Length off the quote. A blank box means no limit, so they are
   narrow and unobtrusive until something is typed into them. */
.recipe-table .c-when { width: 210px; }
/* The Qty box and where its number comes from, read as one thing. */
.qty-src { width: 100%; margin-top: 4px; font-size: 11px; padding: 4px 6px; }
.recipe-table .c-qty input[readonly] {
  background: var(--sheet-sunk); color: var(--ink-faint); cursor: not-allowed;
}
.recipe-table .c-when .basis { width: 100%; margin-bottom: 4px; }
.len-rule { display: flex; gap: 4px; align-items: center; }
/* Three of these stack in the "Only when" cell — length, height, depth. The
   label is fixed-width so the from/to boxes line up down the column, and a row
   that has been filled in is marked so a live limit is not missed at a glance. */
.len-rule + .len-rule { margin-top: 3px; }
.len-lead {
  width: 44px; flex: none; font-size: 10px; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-faint); font-weight: 700;
}
.len-rule.is-set .len-lead { color: var(--measure); }
.len-rule.is-set input { border-color: var(--measure); background: var(--measure-wash); }
.len-rule input {
  width: 58px; padding: 6px 6px; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.len-rule span { font-size: 11px; color: var(--ink-faint); }
.cnc-over { display: flex; gap: 4px; align-items: center; margin-top: 4px; }
.cnc-over span { font-size: 11px; color: var(--ink-faint); }
.cnc-over input {
  width: 58px; padding: 5px 6px; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.inline-check {
  display: inline-flex; align-items: center; gap: 5px; margin: 0;
  text-transform: none; letter-spacing: 0; font-weight: 500; font-size: 12px; color: var(--ink-soft);
}
.inline-check input { width: auto; }

.default-size {
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 9px 14px;
  background: var(--sheet); text-align: right;
}
.default-size .k { display: block; font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); font-weight: 700; }
.default-size .v { display: block; font-family: var(--mono); font-size: 15px; font-weight: 600; margin: 2px 0; }

/* Header help: a "?" that explains the column without a manual. */
th .th-label { vertical-align: middle; }
.help {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-left: 5px; padding: 0; vertical-align: middle;
  border: 1px solid var(--rule-strong); border-radius: 50%; background: var(--sheet);
  color: var(--ink-faint); font-size: 11px; font-weight: 700; font-family: var(--ui);
  cursor: help; line-height: 1;
}
/* A 15px circle is a fingernail on a phone. Bigger where there is no mouse. */
@media (pointer: coarse) {
  .help { width: 26px; height: 26px; font-size: 13px; }
  /* min-* rather than width, and stated strongly enough to beat the
     .inline-check rule that sets these back to auto. */
  input[type="checkbox"], input[type="radio"],
  .inline-check input[type="checkbox"], .inline-check input[type="radio"],
  td input[type="checkbox"], td input[type="radio"] {
    min-width: 22px; min-height: 22px;
  }
}
.help:hover, .help:focus-visible { background: var(--measure); border-color: var(--measure); color: #fff; }

.help-bubble {
  position: fixed; z-index: 80; max-width: 330px;
  background: var(--chrome-900); color: var(--chrome-ink);
  padding: 10px 13px; border-radius: var(--radius); font-size: 12.5px; line-height: 1.5;
  box-shadow: var(--shadow-lift); text-transform: none; letter-spacing: 0; font-weight: 400;
}

.modal .rule-change {
  display: flex; align-items: center; gap: 8px; margin: 8px 0 0;
  font-size: 12px; color: var(--ink-soft);
}
.modal .rule-change span { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); font-weight: 700; }
.modal .rule-change code {
  font-family: var(--mono); background: var(--sheet-sunk);
  border: 1px solid var(--rule); border-radius: 4px; padding: 2px 7px; font-size: 12px;
}

.doc-thanks { margin: 18px 0 0; font-size: 12.5px; }

/* --------------------------------------------------------------- combobox */

/* The real <select> stays in the DOM as the value; the visible field filters
   it. Works without JavaScript, better with it. */
.combo { position: relative; }
.combo > select { position: absolute; opacity: 0; pointer-events: none; width: 100%; height: 100%; }
.combo-field { width: 100%; cursor: text; }
.combo.open .combo-field { border-color: var(--measure); box-shadow: 0 0 0 3px var(--measure-wash); }
.combo-list {
  /* Fixed, not absolute: the cupboard grid scrolls horizontally, and an
     absolutely-positioned child of a scroll container is clipped by it — which
     would cut this list off exactly where it is needed most. Positioned in JS. */
  display: none; position: fixed; z-index: 9999;
  max-width: 460px; max-height: 320px; overflow-y: auto;
  background: var(--sheet); border: 1px solid var(--rule-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-lift); padding: 4px;
}
.combo.open .combo-list,
.combo-list.open { display: block; }
.combo-item {
  padding: 6px 9px; border-radius: 5px; font-size: 13px; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.combo-item:hover, .combo-item.active { background: var(--measure-wash); color: var(--measure); }
.combo-item.current { font-weight: 650; }
.combo-more { padding: 6px 9px; font-size: 11.5px; color: var(--ink-faint); }

/* ----------------------------------------------------------- filter boxes */

.filter-box { position: relative; display: flex; align-items: center; gap: 10px; }
.filter-box input { max-width: 340px; }
.filter-count { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }

/* ------------------------------------------------------- quote defaults */

.defaults-card { border-left: 3px solid var(--measure); }
.defaults-card .card-head { background: var(--measure-wash); }

/* ------------------------------------------------------------- bulk edit */

.cut-table .w-tick { width: 34px; text-align: center; }
.cut-table .w-tick input { width: auto; }
.cut-table .cut-group-head .inline-check span { color: var(--chrome-ink); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; font-size: 11.5px; }
.cut-table tr.picked td { background: var(--measure-wash); }

/* Appears only once something is ticked, so it never competes for attention
   with the list itself. */
.bulk-bar {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: end;
  border: 1px solid var(--measure); border-radius: var(--radius);
  background: var(--measure-wash); padding: 13px 15px; margin-bottom: 14px;
}
.bulk-bar[hidden] { display: none; }
.bulk-count { font-size: 13px; color: var(--measure); align-self: center; white-space: nowrap; }

/* A heading inside the bulk bar. The bar now carries every field a cupboard
   has, which is too many to read as one run of boxes, so they are grouped —
   size, boards, edging, hardware — each heading taking a line of its own. */
.bulk-fields .bulk-group {
  flex: 1 0 100%;
  margin: 10px 0 0;
  color: var(--ink-faint);
}
.bulk-fields .bulk-group:first-child { margin-top: 0; }
.bulk-fields .bulk-group .hint { text-transform: none; letter-spacing: normal; }
.bulk-fields .pair { display: flex; gap: 5px; align-items: center; }
.bulk-fields .pair select { flex: 1 1 auto; min-width: 0; }
.bulk-fields .pair .qty { width: 54px; flex: 0 0 54px; }
.bulk-count strong { font-family: var(--mono); font-size: 17px; }
.bulk-fields { display: flex; gap: 10px; flex-wrap: wrap; flex: 1; }
.bulk-fields .field { margin: 0; min-width: 118px; flex: 1 1 118px; }
.bulk-fields input { background: var(--sheet); }

/* ------------------------------------------------------- budget tracking */

.budget-bar { height: 7px; background: var(--sheet-sunk); border: 1px solid var(--rule); border-radius: 4px; overflow: hidden; }
.budget-bar span { display: block; height: 100%; }
tr.over-budget td { background: var(--alert-wash); }
tr.row-planned td { color: var(--ink-soft); font-style: italic; }

/* A note that needs to be read before the amount — used at the top of an
   invoice, so it must carry weight without shouting. */
.invoice-note {
  font-family: var(--doc-serif); font-size: 13.5px; line-height: 1.6;
  color: var(--doc-alert); margin: 0 0 26px; padding-left: 16px;
  border-left: 2px solid var(--doc-alert); max-width: 46em;
}

/* ------------------------------------------------------------- order list */

/* Read like the printed order, not like a form. Fields are borderless until
   you touch them, so the page scans as a list of what to buy and only becomes
   an editor where you put the cursor. */
.order-table { min-width: 1280px; }

/* The order list as a document, which is a different thing from the order list
   as a working screen. No minimum width — it fits the sheet — and columns wide
   enough for what actually goes in them: a board name, a round number and a
   unit, a supplier with an alternative under it, and the money on the right. */
/* Everything on order, as a working list.
 *
 * Two kinds of row: the thing to buy, and under it the jobs that want it. The
 * job rows are set in from the left and quieter, so the eye runs down the
 * things to buy and only drops into the detail where it needs to. */
.combined-orders { width: 100%; }
.combined-orders th, .combined-orders td { padding: 7px 10px; vertical-align: middle; }
.combined-orders tr.ol-cat td {
  background: var(--sheet-sunk); color: var(--ink-soft);
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  padding-top: 10px; padding-bottom: 6px;
}
.combined-orders tr.ol-line td { border-top: 1px solid var(--rule); }
.combined-orders tr.ol-part td { padding-top: 2px; padding-bottom: 2px; color: var(--ink-soft); font-size: 12.5px; }
.combined-orders .ol-from { padding-left: 24px; }
.combined-orders tr.ol-tot td { border-top: 2px solid var(--rule-strong); padding-top: 10px; }

.doc-order { width: 100%; table-layout: fixed; }
.doc-order th:nth-child(1), .doc-order td:nth-child(1) { width: 44%; }
.doc-order th:nth-child(2), .doc-order td:nth-child(2) { width: 16%; white-space: nowrap; }
.doc-order th:nth-child(3), .doc-order td:nth-child(3) { width: 22%; }
.doc-order th:nth-child(4), .doc-order td:nth-child(4) { width: 18%; white-space: nowrap; }
.doc-order td { vertical-align: top; word-break: break-word; }

/* The "which job is which" table under a combined order list.
 *
 * It borrowed the order table's widths, which are set for five columns with a
 * short quantity second. Here the second column is a client's name, held to 16%
 * and told not to wrap — so anything longer than "Smith" ran straight out of
 * its cell and over the quote number beside it. Its own widths, and names wrap
 * like names. */
.doc-jobs { width: 100%; table-layout: fixed; }
.doc-jobs th:nth-child(1), .doc-jobs td:nth-child(1) { width: 38%; }
.doc-jobs th:nth-child(2), .doc-jobs td:nth-child(2) { width: 30%; }
.doc-jobs th:nth-child(3), .doc-jobs td:nth-child(3) { width: 16%; }
.doc-jobs th:nth-child(4), .doc-jobs td:nth-child(4) { width: 16%; }
.doc-jobs td { vertical-align: top; overflow-wrap: anywhere; }
.order-table th, .order-table td { padding: 6px 8px; vertical-align: middle; }
.order-table .ol-item { width: 310px; }
.order-table .ol-qty { width: 78px; }
/* The size of a piece bought to size: two boxes and the × between them. */
.order-table .ol-size { width: 160px; white-space: nowrap; }
.order-table .ol-size input { width: 64px; text-align: right; display: inline-block; }
.order-table .ol-size span { opacity: .6; padding: 0 3px; }
.order-table .ol-unit { width: 78px; }
.order-table .ol-price { width: 108px; }
.order-table .ol-sup { width: 230px; }
.order-table .ol-act { width: 190px; }

/* An action cell carries more than one control — Save beside New password, a
   status dropdown beside Apply, Retire beside Remove. They sit on one line
   while the column is wide enough for them and drop to a second line when it
   is not. They used to be held on one line always, which at a narrow width
   simply laid them over the column beside them: a button that looks like it
   belongs to the row next to it is how the wrong thing gets pressed. */
td.act-cell, .order-table .ol-act { white-space: normal; }
td.act-cell > *, .order-table .ol-act > * { vertical-align: middle; }

.order-table input,
.order-table select {
  border-color: transparent;
  background: transparent;
  padding: 4px 6px;
  transition: border-color 0.1s, background 0.1s;
}
.order-table input:hover,
.order-table select:hover { border-color: var(--rule); background: var(--sheet); }
.order-table input:focus,
.order-table select:focus { border-color: var(--measure); background: var(--sheet); }
.order-table input::placeholder { color: var(--ink-faint); }

.order-table .ol-item input { font-weight: 600; }
.order-table .ol-sup input { margin-bottom: 2px; }
.order-table .ol-sup input.alt { font-size: 12px; color: var(--ink-soft); margin-bottom: 0; }
.order-table .ol-act select { width: 104px; display: inline-block; }
.order-table input[type="number"] { text-align: right; font-family: var(--mono); }

/* Row actions stay out of the way until the row is hovered. */
.order-table .ol-act .btn { opacity: 0; transition: opacity 0.1s; }
.order-table tr:hover .ol-act .btn,
.order-table .ol-act .btn:focus-visible { opacity: 1; }

.ol-note { display: block; font-size: 10.5px; color: var(--ink-faint); margin-top: 3px; white-space: normal; }
.ol-exp { font-weight: 600; }

/* ---------------------------------------------------- quote page details */

.crumbs { margin: 0 0 10px; font-size: 12.5px; }
.client-card { border-left: 3px solid var(--accent); }
.mini-k { display: block; font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); font-weight: 700; }
.mini-v { display: block; font-size: 13px; margin-top: 2px; }

.apply-panel { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule); }
.apply-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.tickgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 5px 12px; margin-bottom: 8px; }
.tickgrid .inline-check { font-size: 12.5px; }
@media (max-width: 900px) { .apply-cols { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ logo upload */

.logo-uploads { align-items: start; }
.logo-slot { display: flex; flex-direction: column; gap: 6px; }
.logo-preview {
  height: 84px; border: 1px dashed var(--rule-strong); border-radius: var(--radius);
  background: var(--sheet-sunk); display: flex; align-items: center; justify-content: center; padding: 8px;
}
.logo-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
.logo-preview .empty { font-size: 11px; color: var(--ink-faint); letter-spacing: 0.08em; text-transform: uppercase; }
.logo-slot input[type="file"] { font-size: 11.5px; padding: 5px; }

/* Two clearly separate choices, so it's obvious both must be made. */
.apply-box {
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 12px 14px; background: var(--sheet);
}
.pick-count {
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
  color: var(--measure); background: var(--measure-wash);
  border: 1px solid #c3dbe4; border-radius: 999px; padding: 1px 7px; margin-left: 7px;
  text-transform: none;
}
.pick-count.none { color: var(--alert); background: var(--alert-wash); border-color: #e6c2be; }

/* Actual cost typed straight into the Project Finance row. */
.pf-actual { width: 300px; }
.pf-actual-form { display: flex; align-items: center; gap: 5px; }
.pf-cur { font-size: 11px; color: var(--ink-faint); font-family: var(--mono); }
.pf-actual-form input { width: 92px; text-align: right; font-family: var(--mono); }
.pf-actual-form select { width: 104px; }

/* Tick a cupboard in the grid you're already looking at. */
/* The tick and the row number sit on the same line as the row's inputs. A row
   can be two lines tall when a select wraps, so both are centred against the
   cell rather than pinned to its top. */
.cupboard-grid .rownum {
  display: flex; align-items: center; gap: 6px; justify-content: flex-start;
  height: 100%; padding-left: 10px;
}
.cupboard-grid td.rownum { vertical-align: middle; }
.cupboard-grid .rownum input { margin: 0; flex: none; }
/* The three size boxes, sized to what actually goes in them. */
.cupboard-grid .dim-h, .cupboard-grid .dim-d, .cupboard-grid .dim-l { width: 68px; text-align: right; }
.cupboard-grid .cc-select { min-width: 150px; }
.cup-tick, .row-tick, .group-tick { cursor: pointer; }
.cupboard-grid .rownum span { line-height: 1; }
.cupboard-grid thead .sticky-col { text-align: center; vertical-align: middle; }
.cupboard-grid thead .sticky-col input { margin: 0; }
.cupboard-grid .rownum input { width: auto; margin: 0; }
.cupboard-grid tr.picked .sticky-col,
.cupboard-grid tr.picked .sticky-col-2,
.cupboard-grid tr.picked td { background: var(--measure-wash); }
#cup-bulk .bulk-fields .field { min-width: 132px; flex: 1 1 132px; }

.sub-head {
  font-size: 10px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-faint); margin: 18px 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--rule);
}
.sub-head:first-child { margin-top: 0; }

/* ------------------------------------------------------------ number stamp */

/* The serialised number, date and VAT number, set as one block against the
   right margin — a stamp, not a scatter of lines. */
/* No box. A ruled list, in the same letterspaced small caps as the rest of the
   page — the bordered panel read as a form field dropped onto an otherwise
   quiet document.

   It is set out beside the opening sentence rather than beside the heading, so
   the heading runs the full width of the page and a long room name stays on
   one line. The rule under that sentence closes both. */
.doc-stamp {
  /* In normal flow, right-aligned, one line. No float and no absolute
     positioning, so it cannot land on top of anything on any document. */
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0 calc(var(--u) * 3);
  margin: calc(var(--u) * 1.5) 0 calc(var(--u) * 3);
  padding-top: var(--u);
  border-top: 1px solid var(--doc-rule);
}
.stamp-pair { display: inline-flex; align-items: baseline; gap: var(--u); }
.stamp-pair .k {
  font-size: 8.5px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--doc-ink-faint); font-weight: 600; white-space: nowrap;
}
.stamp-pair .v {
  font-family: var(--doc-serif); font-size: 11px; font-weight: 400;
  color: var(--doc-ink); font-variant-numeric: tabular-nums; white-space: nowrap;
}

@media print {
  .stamp-pair .k { font-size: 6.5pt; }
  .stamp-pair .v { font-size: 8.5pt; }
  .doc-stamp { border-top: 1px solid #0F0F0F !important; }

  /* A drawing printed on its own sheet: the sheet, its name, and nothing else.
     Kept in this block rather than a new one lower down, so the phone layout
     stays the last thing in the file. */
  .sketch-tools, .sym-bar, .sketch-foot, .sk-bar, .sk-ask { display: none !important; }
  .print-only { display: block; }
  .sketch-card { border: none; box-shadow: none; }
  .sketch-wrap { padding: 0; background: none; }
  /* Across the width of the page is what makes the measurements readable. */
  #sketch { width: 100%; height: auto; border: 1px solid #999; }
}

.recipe-table .c-cnc { width: 96px; }

/* The discount, shown as its own step above the total so the client can see
   the list price, what came off, and what is left. */
.doc-discount { margin-top: calc(var(--u) * 4); }
.doc-discount .row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: var(--u) 0; border-bottom: 1px solid var(--doc-wash);
  font-size: 12.5px; line-height: calc(var(--u) * 2);
}
.doc-discount .row .money { font-variant-numeric: tabular-nums; font-weight: 600; }
.doc-discount .row.off { color: var(--doc-ink-soft); }

/* Upgrades: the price at the cheaper look-alikes, then each dearer choice. */
.doc-upgrades { margin-top: calc(var(--u) * 4); }
.doc-upgrades .row {
  display: flex; justify-content: space-between; align-items: baseline; gap: calc(var(--u) * 2);
  padding: var(--u) 0; border-bottom: 1px solid var(--doc-wash);
  font-size: 12.5px; line-height: calc(var(--u) * 2);
}
.doc-upgrades .row .money { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.doc-upgrades .row.base { font-weight: 600; border-bottom: 1px solid var(--doc-ink-soft); }
.doc-upgrades .row.basic { color: var(--doc-ink-soft); }
.doc-upgrades .amt { white-space: nowrap; }
.doc-upgrades .up-count { color: var(--doc-ink-soft); }
.doc-upgrades .up-head {
  margin-top: calc(var(--u) * 3); padding-bottom: var(--u);
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--doc-ink-soft);
}
.alt-select { min-width: 160px; }
.alt-warn { margin-top: calc(var(--u) / 2); font-size: 11px; color: var(--danger, #b3261e); }

.user-name { color: var(--chrome-ink); text-decoration: none; border-bottom: 1px solid transparent; }
.user-name:hover { border-bottom-color: var(--chrome-ink-soft); }
.plain-list { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.7; color: var(--ink-soft); }
.plain-list li + li { margin-top: 8px; }
.plain-list code { background: var(--sheet-sunk); border: 1px solid var(--rule); border-radius: 4px; padding: 1px 5px; font-size: 12px; }

/* ------------------------------------------------- grid scroll rail */

/* Fixed to the window, not sticky inside the card: the card sets
   overflow:hidden to keep its rounded corners, and that kills sticky outright.
   Its left edge and width are matched to the table in JS. */
.grid-rail {
  position: sticky;
  /* Above the navigation bar, which is z-index 40 and shares this top edge. */
  z-index: 45;
  overflow-x: auto;
  overflow-y: hidden;
  height: 17px;
  background: var(--sheet);
  border-bottom: 1px solid var(--rule-strong);
  box-shadow: 0 4px 10px -6px rgba(20, 24, 28, 0.35);
}
.grid-rail-inner { height: 1px; }
.grid-rail::-webkit-scrollbar { height: 13px; }
.grid-rail::-webkit-scrollbar-track { background: var(--sheet-sunk); }
.grid-rail::-webkit-scrollbar-thumb {
  background: var(--rule-strong); border-radius: 6px; border: 3px solid var(--sheet-sunk);
}
.grid-rail::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }


/* The rail is sticky within this wrapper, which holds only the rail and the
   grid card — so it rides at the top of the window for exactly as long as the
   grid is on screen, and stops when the grid does. No scroll listener, no
   fixed positioning, nothing an ancestor can quietly disable. */
.grid-sticky-wrap { position: relative; }
.grid-sticky-wrap > .grid-rail { position: sticky; }

/* ------------------------------------------- client-first navigation */

/* The way back from one client's work to the list of clients. */
.breadcrumb { margin: 0 0 10px; font-size: 13px; }
.breadcrumb a { color: var(--measure); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }

/* A whole row that behaves like the link it contains. */
.row-link { color: var(--ink); text-decoration: none; display: block; }
.row-link:hover { color: var(--measure); }
.row-link strong { font-weight: 600; }

/* The tick that adds a room to a project quotation. */
.quote-tick { cursor: pointer; }

/* Tick a cupboard or extra into the upgrades section. */
.upg-tick {
  display: inline-flex; align-items: center; gap: calc(var(--u) / 2);
  font-size: 12px; font-weight: 400; text-transform: none; letter-spacing: 0; white-space: nowrap; cursor: pointer;
}
.upg-tick input { width: auto; margin: 0; }

/* A quote whose price is agreed and fixed. */
.pill.postponed { background: var(--pending-wash, #f7eeda); color: var(--pending, #8a6d2f); border-color: #ecd9b2; }
/* Accepted and now being built. Distinct from accepted, because the whole
   reason for the status is telling work that has started from work that has
   only been agreed. */
.pill.in_progress { background: #e6f0f4; color: #1f6f8b; border-color: #c3dbe4; }
.pill.locked { background: var(--wash, #f3f1ec); color: var(--ink-soft, #6b665c); }

/* A row button that has just saved. */
.btn.ok { background: var(--accent, #2f6d54); border-color: var(--accent, #2f6d54); color: #fff; }

/* A column you can sort by. */
.sort-link { color: inherit; text-decoration: none; border-bottom: 1px dotted currentColor; }
th.sorted .sort-link { border-bottom-style: solid; font-weight: 700; }

/* Moving a quote up or down in your own order. */
.move-arrows { display: inline-flex; gap: 2px; margin-right: calc(var(--u) * 1); vertical-align: middle; }
.move-arrows form { display: inline; }
.btn.tiny { padding: 0 6px; font-size: 11px; line-height: 20px; min-height: 20px; }

/* ---- Tasks --------------------------------------------------------------- */
.task-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 calc(var(--u) * 2); }
.task-tabs a {
  padding: 6px 12px; border-radius: 999px; border: 1px solid var(--line, #e2ded4);
  color: inherit; text-decoration: none; font-size: 13px; background: var(--card, #fff);
}
.task-tabs a.on { background: var(--accent, #2f6d54); border-color: var(--accent, #2f6d54); color: #fff; }
.task-tabs .count, .task-group h2 .count {
  display: inline-block; min-width: 18px; padding: 0 5px; border-radius: 9px; font-size: 11px;
  background: rgba(0,0,0,.08); text-align: center; margin-left: 4px;
}
.task-add { margin-bottom: calc(var(--u) * 3); }
.task-body { min-width: 0; }
.task-group { margin: 0 0 calc(var(--u) * 3); }
.task-group h2 { font-size: 14px; margin: 0 0 6px; text-transform: none; }
.task-group.late h2 { color: var(--alert, #a33); }
.task-group.done { opacity: .75; }
.task-list { list-style: none; margin: 0; padding: 0; background: var(--card, #fff);
  border: 1px solid var(--line, #e2ded4); border-radius: 12px; }
.task { display: grid; grid-template-columns: 36px 1fr 36px; gap: 8px; align-items: start;
  padding: 10px 14px; border-top: 1px solid var(--line, #e2ded4); }
.task:first-child { border-top: none; }
.task-tick { margin: 0; }
.tick {
  width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--ink-faint, #9a958a);
  background: transparent; cursor: pointer; padding: 0; color: #fff; font-size: 13px; line-height: 18px;
}
.tick:hover { border-color: var(--accent, #2f6d54); }
.tick.on { background: var(--accent, #2f6d54); border-color: var(--accent, #2f6d54); }
.task-title { font-weight: 600; line-height: 22px; }
.task.is-done .task-title { text-decoration: line-through; color: var(--ink-faint, #9a958a); }
.task-meta { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12.5px; color: var(--ink-soft, #6b665c); }
.task-meta .due { font-weight: 600; }
.task-meta .due.late { color: var(--alert, #a33); }
.task-meta .has-notes { font-style: italic; }
.task-who {
  width: 30px; height: 30px; border-radius: 50%; background: var(--wash, #f3f1ec);
  display: grid; place-items: center; font-size: 11px; font-weight: 700; color: var(--ink-soft, #6b665c);
}
.task-edit summary { cursor: pointer; font-size: 12px; color: var(--ink-soft, #6b665c); margin-top: 4px; }
.task-edit[open] { background: var(--wash, #f7f5f0); border-radius: 8px; padding: 8px; margin-top: 6px; }
.task-edit textarea { width: 100%; }
.cat-row td { background: var(--wash, #f7f5f0); }
tr.is-selected td { background: var(--accent-wash, #e7f1ec); }

/* ---- Dashboard tiles ----------------------------------------------------- */
.dash-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px;
  margin: 0 0 calc(var(--u) * 3); }
.dash-tile {
  display: block; padding: 14px 16px; border-radius: 12px; text-decoration: none; color: inherit;
  background: var(--card, #fff); border: 1px solid var(--line, #e2ded4);
}
a.dash-tile:hover { border-color: var(--accent, #2f6d54); }
.dash-tile .k { display: block; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft, #6b665c); }
.dash-tile .v { display: block; font-size: 26px; font-weight: 700; line-height: 34px; }
.dash-tile .n { display: block; font-size: 12px; color: var(--ink-soft, #6b665c); }

/* ---- Task detail --------------------------------------------------------- */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 10px; margin-bottom: 12px; }
.photo-grid figure { margin: 0; }
.photo-grid img { width: 100%; height: 110px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line, #e2ded4); }
.photo-grid figcaption { font-size: 11px; color: var(--ink-soft, #6b665c); display: flex; align-items: center;
  justify-content: space-between; gap: 4px; }

/* ---- Orders -------------------------------------------------------------- */
/* The three figures at the top read as one strip, like the cost summary on a
   quote, rather than as three floating boxes. */
.order-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  background: var(--card, #fff); border: 1px solid var(--line, #e2ded4); border-radius: 12px;
  margin: 0 0 18px; overflow: hidden;
}
.order-stat { padding: 14px 18px; border-left: 1px solid var(--line, #e2ded4); }
.order-stat:first-child { border-left: none; }
.order-stat .k { display: block; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft, #6b665c); }
.order-stat .v { display: block; font-size: 22px; font-weight: 700; line-height: 30px; }
.order-stat .n { display: block; font-size: 12px; color: var(--ink-soft, #6b665c); }

.order-item { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.order-split { margin-top: 2px; }
.order-split summary {
  cursor: pointer; font-size: 12px; color: var(--ink-soft, #6b665c);
  list-style: none; display: inline-flex; align-items: center; gap: 4px;
}
.order-split summary::-webkit-details-marker { display: none; }
.order-split summary::before { content: "▸"; font-size: 10px; }
.order-split[open] summary::before { content: "▾"; }
.order-split summary:hover { color: var(--accent, #2f6d54); }
.order-split table.inner { margin: 6px 0 2px; width: auto; }
.order-split table.inner td { padding: 2px 14px 2px 0; font-size: 12px; border: none; white-space: nowrap; }
.order-split table.inner tr td:first-child { padding-left: 14px; }


/* ==========================================================================
 * THE PHONE LAYOUT — LAST ON PURPOSE
 *
 * Everything above is the program as it is on a desk. These two blocks are
 * the same program on a screen the width of a hand, and they come last so
 * that they win: CSS settles a tie by whichever rule was written later, and
 * a media query counts for nothing in that. Sitting halfway up the file,
 * half of this was being quietly undone by the component styles below it.
 * Nothing after this line but the phone.
 * ======================================================================= */

/* ---- on a phone -----------------------------------------------------------
 *
 * Not a shrunken desktop. A phone is held in one hand on a building site, and
 * the things that make the desktop layout work — a menu across the top, a
 * cupboard grid two and a half metres wide, two columns of form — are exactly
 * the things that make it unusable there. So on a narrow screen:
 *
 *   · the menu becomes a drawer behind one button;
 *   · every cupboard becomes a card, each field labelled, read down the screen
 *     instead of across a table nobody can reach the end of;
 *   · boxes are 16px, because anything smaller makes iOS zoom the page on tap
 *     and leave it zoomed;
 *   · everything you touch is at least 44px tall.
 *
 * Nothing is hidden. A measure taken on site needs the whole quote.
 */
@media screen and (max-width: 860px) {

  /* ---- the menu, behind one button --------------------------------------- */
  .topbar-inner { padding: 0 10px; gap: 10px; min-height: 56px; }
  .menu-btn {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 40px; height: 40px; padding: 0 8px; flex: none;
    background: none; border: 1px solid var(--chrome-line); border-radius: var(--radius);
    cursor: pointer;
    /* Above the drawer, which is its sibling: the button that opened it has to
       still be there to close it. */
    position: relative; z-index: 61;
  }
  .menu-btn span { display: block; height: 2px; background: var(--chrome-ink); border-radius: 2px; }
  .brand { font-size: 12px; letter-spacing: 0.12em; }
  .brand span { display: none; }
  .topbar-right { margin-left: auto; gap: 8px; }
  .user-name { max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* The drawer itself: off-screen until the button says otherwise. */
  .topbar nav {
    position: fixed; top: 0; left: 0; bottom: 0; width: 264px; max-width: 82vw;
    flex-direction: column; flex-wrap: nowrap; gap: 2px; flex: none;
    background: var(--chrome-800); border-right: 1px solid #000;
    padding: 64px 12px 20px; overflow-y: auto; z-index: 60;
    transform: translateX(-102%); transition: transform 0.18s ease-out;
    box-shadow: var(--shadow-lift);
  }
  body.menu-open .topbar nav { transform: none; }
  /* The bar stays above the drawer, so the same button that opened it is still
     there to close it. */
  body.menu-open .topbar { z-index: 70; }
  .topbar nav a { padding: 12px 14px; font-size: 15px; border-radius: var(--radius); }
  .menu-scrim {
    display: block; position: fixed; inset: 0; z-index: 50;
    background: rgba(20, 24, 28, 0.44);
  }
  .menu-scrim[hidden] { display: none; }

  main.with-topbar { padding: 14px 10px 150px; }
  main.with-topbar.narrow { padding-left: 10px; padding-right: 10px; }
  .page-head { flex-direction: column; align-items: flex-start; gap: 10px; }
  .page-head h1 { font-size: 21px; line-height: 27px; }
  .card { padding: 14px; }
  .grid.cols-2, .grid.cols-3, .grid.cols-4, .grid.cols-5, .grid.cols-6 { grid-template-columns: 1fr; }

  /* ---- fingers, not mice ------------------------------------------------- */
  /* 16px is not a taste — below it, iOS Safari zooms the page on focus and
     never zooms back, which is most of what "not phone friendly" feels like. */
  input, select, textarea { font-size: 16px; min-height: 44px; }
  /* A button whose words are longer than the screen wraps rather than sticks
     out of it. */
  .btn, button { min-height: 44px; font-size: 15px; padding: 10px 14px; white-space: normal; max-width: 100%; }
  .btn.small { min-height: 40px; font-size: 14px; }
  .btn.tiny { min-height: 32px; }
  input[type="checkbox"], input[type="radio"] { width: 22px; height: 22px; min-height: 0; }
  label { font-size: 13px; }

  /* ---- a cupboard reads down the screen, not across it -------------------- */
  /* The grid is 2540px wide by design; on a phone it becomes one card per
     cupboard. The heading row goes away and each cell carries its own label
     from data-label, so nothing loses its name in the change. */
  .cupboard-grid, .cupboard-grid tbody, .cupboard-grid tfoot,
  .cupboard-grid tr, .cupboard-grid td {
    display: block; width: auto; min-width: 0;
  }
  .cupboard-grid thead { display: none; }
  /* The column widths are what make the desktop grid readable and what make a
     card overflow a phone; on a card every field is simply as wide as the card. */
  .cupboard-grid .w-code, .cupboard-grid .w-label, .cupboard-grid .w-exp,
  .cupboard-grid .w-size, .cupboard-grid .w-mat, .cupboard-grid .w-pair,
  .cupboard-grid .w-edge3, .cupboard-grid .w-act { width: auto; }
  /* The fixed 64px row that keeps the desktop grid even would cut a card off
     at its first field, so the card is as tall as what is in it. */
  .cupboard-grid tbody tr.cup-row,
  .cupboard-grid tbody tr.cup-row > td { height: auto; }
  .cupboard-grid tbody tr.cup-row {
    border: 1px solid var(--rule); border-radius: var(--radius-lg);
    margin: 0 0 12px; padding: 10px 12px; background: var(--sheet);
    box-shadow: var(--shadow-sm);
  }
  .cupboard-grid th, .cupboard-grid td {
    white-space: normal; vertical-align: baseline;
  }
  .cupboard-grid td {
    display: grid; grid-template-columns: 92px minmax(0, 1fr);
    align-items: center; gap: 10px;
    border: none; padding: 5px 0; height: auto;
  }
  .cupboard-grid td::before {
    content: attr(data-label);
    font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ink-soft); line-height: 15px;
  }
  .cupboard-grid td:not([data-label])::before { content: none; }
  .cupboard-grid td:not([data-label]) { grid-template-columns: minmax(0, 1fr); }
  .cupboard-grid input, .cupboard-grid select { font-size: 16px; padding: 9px 10px; }
  /* The number badge sits at the top of the card like a heading. */
  /* Nothing is pinned to the left of a card, so the frozen columns give up
     their position, their edge and the shadow that hinted at more to come. */
  .cupboard-grid td.sticky-col, .cupboard-grid td.sticky-col-2 {
    position: static; left: auto; background: transparent;
    border-right: none; box-shadow: none; width: auto; text-align: left;
  }
  .cupboard-grid td.rownum {
    display: flex; align-items: center; gap: 10px;
    border-bottom: 1px solid var(--rule); margin-bottom: 6px; padding-bottom: 8px;
    font-weight: 700; color: var(--ink-soft);
  }
  .cupboard-grid td.cost-cell {
    border-top: 1px solid var(--rule); margin-top: 6px; padding-top: 8px;
  }
  .cupboard-grid td.w-act { padding-top: 8px; }

  /* ---- a card folds to its four useful lines ------------------------------ */
  .fold-btn {
    display: block; margin-left: auto; flex: none;
    width: 38px; height: 38px; min-height: 0; padding: 0;
    border: 1px solid var(--rule); border-radius: var(--radius);
    background: var(--sheet-sunk); cursor: pointer;
  }
  .fold-btn::before {
    content: "▾"; display: block; font-size: 14px; color: var(--ink-soft);
    line-height: 36px; text-align: center;
  }
  .cup-row.folded .fold-btn::before { content: "▸"; }
  .cupboard-grid tr.cup-row.folded > td { display: none; }
  .cupboard-grid tr.cup-row.folded > td.w-code,
  .cupboard-grid tr.cup-row.folded > td.w-label,
  .cupboard-grid tr.cup-row.folded > td.cost-cell { display: grid; }
  .cupboard-grid tr.cup-row.folded > td.rownum { display: flex; }
  .cupboard-grid tr.cup-row.folded > td.cost-cell { border-top: none; margin-top: 0; }
  .cupboard-grid tr.cup-row.folded .breakdown { display: none; }
  .cupboard-grid .row-actions { width: 100%; }
  /* A flex row's default min-width is its content, which is how three number
     boxes and a pair of selects push a card off the side of the screen. */
  .cupboard-grid .dims, .cupboard-grid .pair { min-width: 0; max-width: 100%; }
  .cupboard-grid .dims input { width: auto; flex: 1 1 0; min-width: 0; text-align: center; }
  .cupboard-grid .pair { flex-wrap: nowrap; }
  .cupboard-grid .pair select { flex: 1 1 0; min-width: 0; }
  .cupboard-grid .pair .qty { width: 62px; flex: none; }
  .cupboard-grid .combo, .cupboard-grid .combo-input { max-width: 100%; min-width: 0; }
  /* The card is already as wide as the screen; the panel must not clip it, and
     the sticky heading row and the top scroll bar have nothing left to do. */
  #cupboard-scroll { max-height: none; overflow: visible; }
  .grid-rail { display: none !important; }

  /* ---- every other table keeps its own sideways scroll -------------------- */
  .table-scroll { -webkit-overflow-scrolling: touch; }
  table { font-size: 14px; }
  /* A plain table in a card gets its own sideways scroll rather than carrying
     the whole page off the side of the screen. (The catalogue tables have the
     same rule at every width — see #cat-table below.) */
  .card > table:not(.cupboard-grid) {
    display: block; overflow-x: auto; max-width: 100%;
    -webkit-overflow-scrolling: touch;
  }
  .table-scroll table:not(.cupboard-grid) { display: table; }
  /* A name typed without a space — a filename, a pasted reference — must wrap
     rather than stretch the page. */
  td, th, .card, .note-inline, .subtitle, h1, h2, h3 { overflow-wrap: anywhere; }

  /* ---- the rest of the program -------------------------------------------- */
  .task { grid-template-columns: 38px minmax(0, 1fr); }
  /* The tick is a circle; a blanket 44px minimum would turn it into an egg.
     So the circle grows a little and the form around it carries the rest of
     the tap area — a thumb gets 44px, the drawing stays round. */
  .tick { width: 30px; height: 30px; min-height: 0; line-height: 26px; }
  .task-tick { display: flex; align-items: center; min-height: 44px; }
  .pf-actual { width: 100%; }
  /* A class beats a bare element selector whatever the order, so the boxes
     that carry their own type size are named here too — under 16px and a
     phone zooms the page in on them. */
  .stage-name, .stage-amount, input.stage-name { font-size: 16px; }
  .order-table .ol-sup input.alt { font-size: 16px; }
  .task-who { display: none; }
  .task-tabs { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
  .order-summary { grid-template-columns: 1fr; }
  .order-stat { border-left: none; border-top: 1px solid var(--rule); }
  .order-stat:first-child { border-top: none; }
  .dash-tiles { grid-template-columns: 1fr 1fr; }
  .apply-cols { grid-template-columns: 1fr; }
  .two-col, .sign-row { grid-template-columns: 1fr; }

  /* The running total stays in reach at the foot of the screen. */
  /* The bar is fixed to the foot of the screen, so it takes the screen's width
     rather than its contents'. */
  .totals-dock { left: 0; right: 0; width: 100%; max-width: 100%; }
  .totals-dock-inner { flex-wrap: wrap; gap: 8px 14px; padding: 8px 12px; max-width: 100%; }
  .dock-actions { flex-wrap: wrap; gap: 6px; }
  .dock-actions .btn { flex: 1 1 auto; text-align: center; }
  .dock-stat .k { font-size: 10px; }
  .dock-stat .v { font-size: 13px; }
  .dock-stat.grand .v { font-size: 17px; }

  /* ---- paperwork on a small screen ---------------------------------------- */
  /* An A4 sheet does not fit; the sheet gives up its fixed width so the words
     wrap, and the print rules are untouched — what prints is still A4. */
  /* The sheet narrows by changing the padding variable, not the padding: the
     dark band bleeds to the edge by negating exactly that variable, and a
     hard-coded padding would leave it hanging over the side. */
  .paper { width: auto; max-width: none; --pad: 14px; padding-top: 18px; }
  .doc-validity { white-space: normal; }
  .paper-toolbar { flex-wrap: wrap; gap: 8px; }
  .paper table { font-size: 11px; }
  /* The letterhead is logo · name · partner marks across the sheet. On a phone
     the three go one under the other, centred. This is screen only — what is
     printed is still the sheet as it was drawn. */
  .lh-grid { grid-template-columns: 1fr; gap: 10px; min-height: 0; text-align: center; }
  .lh-logo { justify-content: center; }
  .logo-co { max-width: 100%; object-position: center; }
  .lh-partners { flex-direction: row; align-items: center; justify-content: center; gap: 14px; }
  .logo-pt { object-position: center; }
  .lh-page { position: static; text-align: center; }
  /* A wide table on a sheet gets its own sideways scroll rather than pushing
     the sheet off the screen. */
  .paper .table-wrap, .paper table { max-width: 100%; }

  /* A five-figure amount must not push the payment stage off the screen. */
  .stage-head { flex-direction: column; gap: 6px; }
  .stage-amount.big { font-size: 19px; }
}

@media screen and (max-width: 420px) {
  .dash-tiles { grid-template-columns: 1fr; }
  .page-head h1 { font-size: 19px; line-height: 25px; }
  .cupboard-grid td { grid-template-columns: 1fr; gap: 3px; }
  .cupboard-grid td.rownum { grid-template-columns: none; }
  /* A folded card is a summary; it keeps its label beside the field so three
     of them fit on a screen instead of one and a half. */
  .cupboard-grid tr.cup-row.folded > td { grid-template-columns: 74px minmax(0, 1fr); gap: 8px; }
  .cupboard-grid tr.cup-row.folded > td.rownum { grid-template-columns: none; }
  .brand { display: none; }
  main.with-topbar { padding: 12px 8px 150px; }
}

/* ------------------------------------------------------------------- jobs */

/* Where a job is, at a glance. Each stage gets its own colour so the board
   reads as a row of states rather than a wall of words. */
.pill.stage-site_visit { background: var(--measure-wash); color: var(--measure); }
.pill.stage-design     { background: #efe8f6; color: #5b3b86; }
.pill.stage-quote      { background: var(--pending-wash); color: var(--pending); }
.pill.stage-decision   { background: #fdf3d6; color: #8a6a10; }
.pill.stage-ordering   { background: #e6f0f4; color: #1f6f8b; }
.pill.stage-workshop   { background: #eceee9; color: #4a5a46; }
.pill.stage-install    { background: var(--accent-wash); color: var(--accent); }

.hint.block { display: block; }
td.num.late { color: var(--alert); font-weight: 700; }

.stage-card { border-left: 3px solid var(--accent); }
.task-line {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 0; border-bottom: 1px solid var(--rule);
}
.task-line:last-child { border-bottom: none; }
.task-line.is-done { opacity: 0.55; }

.note-stream { margin-top: 14px; display: flex; flex-direction: column; gap: 10px; }
.job-note {
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 10px 12px;
  background: var(--sheet);
}
.job-note.flagged { border-color: var(--accent); background: var(--accent-wash); }
.job-note p { margin: 4px 0; }

.sketch-list { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px; }
/* The strip of drawings and plans at the foot of a job — every one on the job,
   whatever room or step it came from. */
.sketch-chips { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 12px; }

.sketch-chip {
  display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 8px 12px; background: var(--sheet); text-decoration: none;
}
.sketch-chip:hover { border-color: var(--rule-strong); }

/* Everything on the go, on an admin's dashboard. */
.all-work .work-stage { border-top: 1px solid var(--rule); padding: 10px 18px; }
.all-work .work-stage:first-of-type { border-top: none; }
.work-stage-head { display: flex; justify-content: space-between; align-items: baseline; }
.work-list { list-style: none; margin: 6px 0 0; padding: 0; }
.work-list li { padding: 4px 0; display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }

/* ---- the sketch pad ---- */
.sketch-tools {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  padding: 10px 14px; border-bottom: 1px solid var(--rule); background: var(--sheet-sunk);
}
.tool-group { display: flex; gap: 6px; align-items: center; }
.sketch-tools .tool { text-transform: none; }
/* Only the tool in your hand is filled in; the rest sit back. */
.sketch-tools .tool.on { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 700; }
.sketch-tools .swatch { width: 30px; min-width: 30px; padding: 0; height: 30px; border-radius: 50%; border: 2px solid var(--rule); }
.sketch-tools .swatch.on { border-color: var(--ink); }
.sketch-wrap { background: var(--sheet-sunk); padding: 10px; }
#sketch {
  display: block; width: 100%; height: auto; background: #fff;
  border: 1px solid var(--rule); border-radius: var(--radius);
  /* A finger on the sheet draws; it does not scroll the page away. */
  touch-action: none; cursor: crosshair;
}

/* The job file, printed for the fitting team. */
.photo-strip { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 1); margin: calc(var(--u) * 2) 0; }
.photo-strip img { width: calc(var(--u) * 22); height: auto; border: 1px solid var(--doc-rule); }

/* The heading block of the job file, and the sheet the sketch sits on. */
.doc-head { margin-bottom: calc(var(--u) * 3); }
.sketch-card { overflow: hidden; }

/* ------------------------------- finding a cupboard, and filling the screen */

.cup-head-tools { align-items: center; gap: 10px; }
.cup-find input {
  width: 230px; padding: 7px 10px; font-size: 13px;
}
.vis-hide {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* Full screen: the grid is the only thing on the page, so a long quote cannot
   be scrolled past. Everything else is taken out of the flow rather than
   merely covered, so nothing is left behind it to catch a stray scroll. */
body.grid-focus { overflow: hidden; }
body.grid-focus > *:not(.toast-stack) { display: none; }
/* Two things hang off <body> rather than off the page and have to be put back
   by hand, exactly as the bar below is: the search list a colour or hardware
   dropdown opens, and the help bubble. Without this every such dropdown in the
   grid opens a list that is there but invisible, which is indistinguishable
   from the dropdowns having disappeared.
   Written as separate rules rather than as more :not() clauses above, because
   each :not(.class) adds to that selector's weight — enough of them and it
   beats `body.grid-focus main.with-topbar` below and blanks the whole screen.
   It did exactly that the first time. */
body.grid-focus > .combo-list.open { display: block; }
body.grid-focus > .help-bubble { display: block; }
body.grid-focus .topbar { display: block; }
body.grid-focus main.with-topbar {
  display: block; position: fixed;
  top: 52px; left: 0; right: 0; bottom: 0;
  margin: 0; padding: 0; overflow: hidden; max-width: none;
}
body.grid-focus main.with-topbar > *:not(.grid-sticky-wrap) { display: none; }
body.grid-focus .grid-sticky-wrap { height: 100%; }
body.grid-focus #cupboards {
  display: flex; flex-direction: column;
  height: 100%; margin: 0; border-radius: 0; border-left: none; border-right: none;
}
body.grid-focus #cupboards .card-head { flex: none; }
body.grid-focus #cupboard-scroll {
  flex: 1 1 auto; max-height: none; height: auto;
}
/* The bulk bar and the sideways hint still work, they just stop taking room. */
body.grid-focus .scroll-hint { display: none; }

/* In full screen the whole point is to work down a long quote without losing
   your place, and scrolling right to reach the colours and hardware used to
   leave only a number and a code on the left — so you were choosing a colour
   for a cupboard you could no longer identify. The name and the size stay put
   as well, which is everything needed to know which cupboard is in front of
   you. Only here: at normal size the page is not filled and two pinned columns
   are enough.
   The offsets are the running total of the widths to their left (44 + 200 +
   160), so they are written against the same figures the columns are set to. */
/* Pinned columns cost width from the part that still scrolls, so in full
   screen these four are tightened: 44 + 150 + 130 + 160 = 484px pinned, which
   leaves a 1440 screen ~950px of colours and hardware to scroll through. At the
   original widths the pinned block was 614px and the first colour column could
   sit underneath it. */
body.grid-focus .cupboard-grid .w-code { width: 150px; }
body.grid-focus .cupboard-grid .w-label { width: 130px; }
body.grid-focus .cupboard-grid .w-size { width: 160px; }
body.grid-focus .cupboard-grid .sticky-col-2 { left: 44px; }
body.grid-focus .cupboard-grid .w-label {
  position: sticky; left: 194px; z-index: 3;
  background: var(--sheet); border-right: 1px solid var(--rule);
}
body.grid-focus .cupboard-grid .w-size {
  position: sticky; left: 324px; z-index: 3;
  background: var(--sheet); border-right: 1px solid var(--rule-strong);
  box-shadow: 4px 0 8px -4px rgba(20, 24, 28, 0.22);
}
/* The sizes are three boxes side by side; at 160px they need to be compact. */
body.grid-focus .cupboard-grid .w-size .dims { gap: 2px; }
body.grid-focus .cupboard-grid .w-size input { padding-left: 4px; padding-right: 4px; }

/* Eighteen columns asking for more width than the table has means the browser
   shares out what is there, and a colour ends up reading "Mel" — which is the
   other half of not being able to see what a cupboard has already been given.
   In full screen the grid may be as wide as it needs and scroll; these are
   floors, not fixed widths, so nothing is cramped to fit. */
body.grid-focus .cupboard-grid .w-mat { min-width: 200px; }
body.grid-focus .cupboard-grid .w-pair { min-width: 210px; }
body.grid-focus .cupboard-grid .w-exp { min-width: 140px; }
/* The one already carrying the shadow gives it up, or there are two. */
body.grid-focus .cupboard-grid .sticky-col-2 { box-shadow: none; }
body.grid-focus .cupboard-grid thead .w-label,
body.grid-focus .cupboard-grid thead .w-size { background: var(--sheet-sunk); z-index: 8; }
body.grid-focus .cupboard-grid thead .colgroup-head .w-label,
body.grid-focus .cupboard-grid thead .colgroup-head .w-size { background: var(--chrome-800); z-index: 9; }
body.grid-focus .cupboard-grid tfoot .w-label,
body.grid-focus .cupboard-grid tfoot .w-size { background: var(--sheet-sunk); }
body.grid-focus .cupboard-grid tbody tr:hover .w-label,
body.grid-focus .cupboard-grid tbody tr:hover .w-size { background: var(--sheet-sunk); }
body.grid-focus .grid-rail { display: none; }
body.grid-focus .totals-dock { display: none; }

@media screen and (max-width: 860px) {
  .cup-head-tools { width: 100%; flex-wrap: wrap; }
  .cup-find { flex: 1 1 100%; }
  .cup-find input { width: 100%; }
  /* On a phone the bar is 56px and the cards are already one per screen. */
  body.grid-focus main.with-topbar { top: 56px; }
  body.grid-focus #cupboard-scroll { overflow: auto; }
}

/* ------------------------------------------- working a job room by room */

/* The room picker is the first control on a job, because a job is worked one
   room at a time and showing five rooms at once is what made the page
   unusable. It sticks to the top so switching rooms never needs a scroll. */
.room-bar {
  position: sticky; top: 52px; z-index: 6;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin: 0 0 14px;
  background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: 0 1px 0 rgba(20, 24, 28, 0.04);
}
.room-bar label {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 700;
}
.room-bar select { min-width: 240px; font-size: 15px; font-weight: 600; }
/* Numbered dots for a quick hop between rooms once you know the order. */
.room-dots { display: flex; gap: 4px; }
.room-dots a {
  width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
  border: 1px solid var(--rule); color: var(--ink-soft); text-decoration: none;
  font-size: 12px; font-weight: 700;
}
.room-dots a.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ---- what the next steps must see ---- */

/* Flagged by an earlier step. Loud on purpose: the whole reason for the flag is
   that somebody will otherwise start work without reading it. */
.must-see { border-left: 3px solid var(--pending); }
.flag-list { display: flex; flex-direction: column; }
.flag-item {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 18px; border-top: 1px solid var(--rule);
}
.flag-item:first-child { border-top: none; }
.flag-item p { margin: 4px 0; }
.flag-kind {
  flex: none; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 700; padding: 3px 7px; border-radius: 999px;
  background: var(--pending-wash); color: var(--pending); border: 1px solid #ecd9b2;
}
.flag-item.kind-sketch .flag-kind { background: #efe8f6; color: #5b3b86; border-color: #ddd0ee; }
.flag-item.kind-task .flag-kind { background: var(--measure-wash); color: var(--measure); border-color: #c3dbe4; }
.pill.flag { background: var(--pending-wash); color: var(--pending); border-color: #ecd9b2; }
.flag-toggle { margin-top: 4px; }

/* ---- handing the job on ---- */

/* One button, and when it will not go, the reason by name. A refusal without an
   explanation is the thing that makes people work around a system. */
.hand-on {
  display: flex; gap: 16px; align-items: center; justify-content: space-between;
  flex-wrap: wrap; margin-top: 16px; padding: 14px 16px;
  border: 1px dashed var(--rule-strong); border-radius: var(--radius);
  background: var(--sheet-sunk);
}
.hand-on.is-ready { border-style: solid; border-color: var(--accent); background: var(--accent-wash); }
.btn.big { font-size: 15px; padding: 12px 20px; }
.blockers { font-size: 13px; color: var(--ink-soft); max-width: 560px; }
.blockers ul { margin: 6px 0; padding-left: 18px; }
.blockers li { margin: 2px 0; }

/* ---- a task on a job ---- */

.task-line.is-review { background: #fdf6e6; margin: 0 -18px; padding: 10px 18px; }
.task-line.is-flagged > div:nth-child(2) { border-left: 3px solid var(--pending); padding-left: 10px; }
.review-note {
  margin: 6px 0 0; font-size: 13px; padding: 7px 10px;
  background: var(--pending-wash); border-radius: var(--radius); color: #6d5620;
}
.lock-note { font-style: italic; }
.pill.review { background: #fdf3d6; color: #8a6a10; border-color: #ecd9b2; }
.pill.dropped { background: var(--wash, #f3f1ec); color: var(--ink-soft, #6b665c); text-decoration: line-through; }
.tick:disabled { opacity: 0.4; cursor: not-allowed; }
.locked-card { border-left: 3px solid var(--ink-soft); }
.read-back { margin: 0; }
.read-back dt {
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 700; margin-top: 10px;
}
.read-back dd { margin: 2px 0 0; white-space: pre-wrap; }

/* A small menu hung off a row, for the things that are not the main action. */
.drop { position: relative; }
.drop > summary { list-style: none; cursor: pointer; }
.drop > summary::-webkit-details-marker { display: none; }
.drop-body {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; width: 260px;
  background: var(--sheet); border: 1px solid var(--rule-strong); border-radius: var(--radius);
  padding: 12px; box-shadow: 0 8px 24px rgba(20, 24, 28, 0.14);
  display: flex; flex-direction: column; gap: 10px;
}
.drop-body form { display: flex; flex-direction: column; gap: 6px; }
.drop-body input { width: 100%; }
.drop-body .hint { margin: 0; }
.add-task > summary { list-style: none; cursor: pointer; margin-top: 12px; }
.add-task > summary::-webkit-details-marker { display: none; }

.task-group.review-group h2 { color: var(--pending); }
.task.is-review { background: #fdf6e6; }
.task.is-flagged .task-title { font-weight: 700; }
.sketch-row { display: flex; align-items: center; gap: 8px; }
.sketch-row.flagged .sketch-chip { border-color: var(--pending); background: var(--pending-wash); }
.flash.warn { background: var(--pending-wash); border-color: #ecd9b2; color: #6d5620; }
.note-inline.warn { color: var(--pending); font-weight: 600; }

/* ------------------------------------------- the drawing board, in detail */

/* The fittings palette. A glyph and its name together, because "◉" alone is a
   guess and a designer should not have to hover to find the water point. */
.sym-bar {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 10px 14px; border-bottom: 1px solid var(--rule); background: var(--sheet);
}
.sym-lead {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 700; margin-right: 4px;
}
.btn.sym {
  display: inline-flex; align-items: center; gap: 6px; text-transform: none;
  background: var(--sheet); border: 1px solid var(--rule); color: var(--ink);
}
.btn.sym:hover { border-color: var(--rule-strong); }
.btn.sym.on { background: var(--measure); border-color: var(--measure); color: #fff; font-weight: 700; }
.sym-glyph { font-size: 15px; line-height: 1; }
.sym-name { font-size: 12px; }
.sym-note { margin-left: auto; }
.swatch-row { display: flex; gap: 6px; }

/* The sheet, and the little bar that follows whatever is picked up. */
.sketch-wrap { position: relative; }
#sketch.picking { cursor: pointer; }
.sk-bar {
  position: absolute; z-index: 8; display: flex; gap: 4px; padding: 4px;
  background: var(--sheet); border: 1px solid var(--rule-strong); border-radius: var(--radius);
  box-shadow: 0 4px 14px rgba(20, 24, 28, 0.18);
}

.sketch-foot { padding: 10px 18px 14px; }
.keys > summary { cursor: pointer; font-size: 13px; color: var(--ink-soft); }
.key-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 6px 18px; margin-top: 10px; font-size: 13px; color: var(--ink-soft);
}
kbd {
  display: inline-block; min-width: 20px; text-align: center;
  padding: 1px 5px; border: 1px solid var(--rule-strong); border-bottom-width: 2px;
  border-radius: 4px; background: var(--sheet); font-family: var(--mono, monospace);
  font-size: 11px; color: var(--ink);
}

/* Asking the two numbers that always get asked: how high, and how far in. A
   panel rather than three prompt boxes, because this is used on a tablet while
   standing in somebody's kitchen. */
.sk-ask {
  position: fixed; inset: 0; z-index: 90; display: flex;
  align-items: center; justify-content: center; padding: 16px;
  background: rgba(20, 24, 28, 0.45);
}
.sk-ask-box {
  width: 100%; max-width: 520px; background: var(--sheet);
  border-radius: var(--radius); padding: 20px;
  box-shadow: 0 18px 50px rgba(20, 24, 28, 0.3);
}
.sk-ask-box h3 { margin: 0 0 14px; }
.sk-ask-box input, .sk-ask-box select { font-size: 16px; }

/* ---- the list a task is actually done by ---- */

/* A site visit is mostly numbers and answers, not ticks, so each line has
   somewhere to write the answer. A required line with nothing in it is marked,
   because that is precisely what is holding the step open. */
.ck-list { display: flex; flex-direction: column; border-top: 1px solid var(--rule); }
.ck-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 190px 32px;
  gap: 10px; align-items: center;
  padding: 8px 0; border-bottom: 1px solid var(--rule);
}
.ck-row.is-owed { background: #fdf6e6; margin: 0 -10px; padding: 8px 10px; }
.ck-row.is-answered .ck-label > span:first-child { color: var(--ink-soft); }
.ck-label { min-width: 0; }
.ck-answer input { width: 100%; }
.ck-answer .mono { font-size: 15px; }
.ck-must {
  margin-left: 6px; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  font-weight: 700; padding: 2px 6px; border-radius: 999px;
  background: var(--pending-wash); color: var(--pending); border: 1px solid #ecd9b2;
}
.ck-kind {
  margin-left: 6px; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-soft);
}

/* ----------------------------------------- the dashboard's two sections */

/* Jobs first, then what is on you. Two plain headings do more for finding
   things than any amount of card styling. */
.dash-head {
  margin: 26px 0 10px; font-size: 13px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-soft);
}
.work-stage.is-mine { background: var(--accent-wash); }
.work-stage-head { gap: 8px; }
.task-meta .who { color: var(--ink-soft); font-weight: 600; }

/* ------------------------------------------- one client, or all of them */

.task-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin: 0 0 14px;
  background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--radius);
}
.task-bar label {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 700;
}
.task-bar select { min-width: 220px; }
.task-bar .toggle { margin-left: auto; }

/* --------------------------------------- must-see, broken up by room */

.flag-room { border-top: 1px solid var(--rule); padding: 4px 0 10px; }
.flag-room:first-of-type { border-top: none; }
.flag-room h3 {
  margin: 12px 18px 2px; font-size: 12px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-soft);
}
.flag-item.kind-plan .flag-kind { background: #e6f0f4; color: #1f6f8b; border-color: #c3dbe4; }
.flag-item.kind-answer .flag-kind { background: #eceee9; color: #4a5a46; border-color: #d8ddd4; }

/* A flagged answer stands out in the list it is buried in. */
.ck-row.is-flagged { background: var(--pending-wash); margin: 0 -10px; padding: 8px 10px; }
.ck-flag { display: flex; align-items: center; }
.ck-flag .inline-check span { font-size: 11px; color: var(--ink-soft); }
.ck-row { grid-template-columns: minmax(0, 1fr) 190px 96px 32px; }

/* ------------------------------------------------ quoting from the job */

.quote-panel { border-left: 3px solid var(--pending); }
.quote-panel tr.not-ready td { opacity: 0.68; }

/* --------------------------------------- plans the client sent */

.plan-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.sketch-chip.is-pdf strong::before { content: "PDF "; color: var(--alert); font-size: 11px; }
.sketch-chip.is-image strong::before { content: "IMG "; color: var(--measure); font-size: 11px; }

/* --------------------------------------- the drawing board, in detail */

.tool-lead {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 700;
}
.sketch-tools .size-pick { min-width: 30px; font-weight: 700; }
.sketch-tools .size-pick.on { background: var(--measure); border-color: var(--measure); color: #fff; }
.sk-bar .btn { min-width: 34px; }

/* A drawing printed on its own sheet: the sheet, its name, nothing else. */
@media screen and (max-width: 860px) {
  /* The room picker is the one control that must never be hunted for. */
  .room-bar { top: 56px; padding: 8px 10px; }
  .room-bar select { min-width: 0; flex: 1 1 100%; font-size: 16px; }
  .hand-on { flex-direction: column; align-items: stretch; }
  .hand-on .btn.big { width: 100%; }
  .blockers { max-width: none; }
  .task-line { flex-wrap: wrap; }
  .task-line .row-actions { width: 100%; justify-content: flex-start; }
  /* A menu that hangs off the row has nowhere to hang on a phone. */
  .drop-body { position: static; width: auto; box-shadow: none; }
  .sym-note { margin-left: 0; flex: 1 1 100%; }
  .sym-name { display: none; }
  .btn.sym { min-width: 44px; justify-content: center; }
  .sym-glyph { font-size: 18px; }
  .flag-item { flex-direction: column; gap: 6px; }
  .key-grid { grid-template-columns: 1fr; }
  /* A size and its label side by side is 190px of nothing on a phone. */
  .ck-row { grid-template-columns: minmax(0, 1fr) 40px; }
  .ck-answer { grid-column: 1; grid-row: 2; }
  .ck-flag { grid-column: 1; grid-row: 3; }
  .ck-row > form { grid-column: 2; grid-row: 1 / span 3; align-self: start; }
  .ck-answer input { font-size: 16px; }
  .task-bar select { min-width: 0; flex: 1 1 100%; font-size: 16px; }
  .task-bar .toggle { margin-left: 0; }
  .plan-actions { flex-direction: column; align-items: stretch; }
  .plan-actions form { width: 100%; }
  /* Six boxes across is a desk layout. On a phone a stage is a small stack,
     with its labels back, because a column of unlabelled boxes says nothing. */
  .stage-tpl-row { grid-template-columns: 1fr 1fr; gap: 8px; padding: 12px 0; }
  .stage-tpl-row label { display: block; }
  .stage-tpl-row .field:first-child,
  .stage-tpl-row .field:nth-child(4),
  .stage-tpl-row .field:nth-child(5),
  .stage-tpl-row .field:nth-child(6),
  .stage-tpl-row .field:nth-child(7) { grid-column: 1 / -1; }
  .stage-tpl-row input, .stage-tpl-row select { font-size: 16px; }
  .stage-tpl-row [data-tpl-drop] { grid-column: 1 / -1; justify-self: start; min-height: 44px; }
  /* The register's two halves stack, each still a thumb's height. */
  .reg-tab { flex: 1 1 100%; min-height: 52px; }
}

/* Long site surveys use section rows so a kitchen form can stay readable on a tablet. */
.ck-section{
  grid-column:1/-1;
  margin:14px 0 4px;
  padding:10px 12px;
  border-radius:10px;
  background:var(--surface-2,#f5f3ee);
  border:1px solid var(--line,#ddd8cf);
  font-size:14px;
  letter-spacing:.02em;
}

/* ------------------------------------------------ client presentation mode
   The supplied Eden View presentation is image-led: a dark cover, room name
   band, then large renders/drawings. The live quote pages are appended only
   after every presentation image so the meeting flows the same way. */
.presentation-mode{max-width:1240px;margin:0 auto;padding-bottom:50px}
.presentation-slide{
  position:relative;
  width:100%;
  aspect-ratio:1.52 / 1;
  margin:22px auto;
  overflow:hidden;
  border-radius:3px;
  background:#0a0a0a;
  color:#fff;
  box-shadow:0 18px 46px rgba(20,24,28,.18);
}
.presentation-cover{
  display:flex;
  align-items:center;
  justify-content:center;
  text-align:center;
  background:radial-gradient(circle at 35% 25%,#454545 0,#242424 34%,#0c0c0c 74%);
}
.presentation-cover:after{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:.18;
  background-image:linear-gradient(120deg,transparent 0 48%,rgba(255,255,255,.06) 49%,transparent 50% 100%);
  background-size:28px 28px;
}
.presentation-cover>div{position:relative;z-index:2}
.presentation-cover h1{font-size:clamp(32px,5vw,68px);letter-spacing:.02em;margin:14px 0 8px;color:#fff}
.presentation-cover p{font-size:clamp(16px,2vw,25px);color:#ddd;margin:4px 0}
.presentation-cover .eyebrow{color:#d4d4d4;letter-spacing:.26em}
.presentation-cover-logo{position:relative;z-index:2;width:min(330px,34%);height:auto;margin-right:44px;filter:grayscale(1);background:#fff;padding:10px;border-radius:4px}
.presentation-version{font-size:13px!important;text-transform:uppercase;letter-spacing:.18em}
.presentation-band{
  position:absolute;z-index:5;top:0;left:0;right:0;height:58px;
  display:flex;align-items:center;justify-content:center;
  background:#050505;color:#fff;text-transform:uppercase;
  letter-spacing:.24em;font-size:clamp(13px,1.7vw,22px);font-weight:600;
}
.presentation-image{position:absolute;inset:58px 0 0;display:flex;align-items:center;justify-content:center;background:#111}
.presentation-image img{display:block;width:100%;height:100%;object-fit:contain;background:#111}
.presentation-logo{position:absolute;z-index:6;right:16px;bottom:14px;width:76px;max-height:58px;object-fit:contain;background:rgba(255,255,255,.88);padding:4px;border-radius:3px}
.presentation-caption{position:absolute;z-index:6;left:16px;bottom:14px;max-width:60%;padding:5px 9px;background:rgba(0,0,0,.62);font-size:11px;letter-spacing:.05em}
.presentation-change{display:flex;gap:8px;align-items:end;margin:8px 0 24px;padding:10px 12px;border:1px solid var(--rule);border-radius:10px;background:var(--sheet)}
.quote-slide{background:#f7f5f1;color:#202020}
.presentation-quote-body{position:absolute;inset:58px 0 0;padding:38px 54px 58px;overflow:auto}
.presentation-section-title{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:24px;padding-bottom:15px;border-bottom:1px solid #d5d0c7}
.presentation-section-title>span,.presentation-section-title>strong{font-size:clamp(24px,3.2vw,42px);font-weight:600}
.presentation-section-title h2{font-size:clamp(24px,3.1vw,40px);margin:4px 0 0}
.presentation-section-title .eyebrow{color:#777}
.presentation-intro{font-size:16px;line-height:1.55;margin:0 0 20px;max-width:90%}
.presentation-quote-table{width:100%;border-collapse:collapse;background:#fff;font-size:15px}
.presentation-quote-table th,.presentation-quote-table td{padding:12px 14px;border-bottom:1px solid #e4dfd7;text-align:left;vertical-align:top}
.presentation-quote-table th{background:#efebe5;font-size:12px;letter-spacing:.07em;text-transform:uppercase;color:#555}
.presentation-quote-table .num{text-align:right}
.presentation-quote-table tfoot th{font-size:14px;background:#e8e2d9;color:#222}
.presentation-quote-table.compact td,.presentation-quote-table.compact th{padding:9px 11px}
.presentation-extras{margin-top:18px;padding:14px 18px;background:#fff;border:1px solid #e0dbd2}
.presentation-extras ul{margin:8px 0 0;padding-left:20px;columns:2}
.presentation-total{display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding:16px 18px;background:#171717;color:#fff;font-size:17px}
.presentation-total strong{font-size:25px}
.presentation-empty{display:flex;align-items:center;justify-content:center;min-height:160px;text-align:center;color:#666;font-size:18px}
.presentation-empty.large{position:absolute;inset:58px 0 0;color:#ccc;background:#111}

@media(max-width:760px){
  .presentation-slide{aspect-ratio:auto;min-height:520px;border-radius:0}
  .presentation-cover{flex-direction:column;padding:70px 24px 40px;gap:20px}
  .presentation-cover-logo{width:220px;margin:0}
  .presentation-band{height:48px;letter-spacing:.14em}
  .presentation-image{inset:48px 0 0}
  .presentation-quote-body{inset:48px 0 0;padding:24px 18px 50px}
  .presentation-section-title{flex-direction:column;gap:8px}
  .presentation-quote-table{font-size:12px}
  .presentation-quote-table th,.presentation-quote-table td{padding:7px}
  .presentation-extras ul{columns:1}
  .presentation-change{flex-direction:column;align-items:stretch}
}

@media print{
  .presentation-mode{max-width:none;padding:0}
  .presentation-slide{break-after:page;page-break-after:always;box-shadow:none;margin:0;border-radius:0;width:100%;height:auto;aspect-ratio:1.52/1}
  .presentation-change{display:none!important}
  .presentation-quote-body{overflow:hidden}
}

/* v98 — site visit is worked as one survey per room */
.site-survey-panel{margin:0 18px 16px;border:1px solid var(--rule);border-radius:10px;overflow:hidden;background:var(--sheet)}
.site-survey-head{padding:13px 15px;background:var(--sheet-sunk);border-bottom:1px solid var(--rule)}
.site-survey-row{display:grid;grid-template-columns:minmax(220px,1fr) auto auto;gap:12px;align-items:center;padding:12px 15px;border-bottom:1px solid var(--rule)}
.site-survey-row:last-child{border-bottom:0}
@media(max-width:760px){.site-survey-row{grid-template-columns:1fr}.site-survey-panel{margin:0 10px 14px}}

/* v98 — job and client order list are one workflow */
.job-order-link .card-head{align-items:center}
.order-link-stats{display:grid;grid-template-columns:repeat(5,minmax(120px,1fr));gap:1px;background:var(--rule);border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.order-link-stats>div{background:var(--sheet);padding:13px 16px}.order-link-stats .k{display:block;color:var(--ink-faint);font-size:12px;margin-bottom:4px}.order-link-stats strong{font-size:20px}
.order-link-quotes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:10px;padding:14px 18px}.order-link-quotes>div{padding:10px 12px;border:1px solid var(--rule);border-radius:8px}.order-link-quotes .hint{display:block;margin-top:3px}
@media(max-width:800px){.order-link-stats{grid-template-columns:repeat(2,1fr)}}

/* ---------------------------------------------------------------- v98 client presentation
   Match the supplied Eden View deck: 1200 × 790-ish sheets, black room band,
   full-sheet render/plan, the company's own mark bottom-right, and the same
   concrete cover artwork. */
.eden-presentation{max-width:1200px;margin:0 auto;padding-bottom:60px}
.presentation-sheet-wrap{margin:0 0 26px}
.eden-presentation .presentation-slide{position:relative;width:100%;aspect-ratio:1200/790;min-height:0;margin:0;overflow:hidden;border-radius:0;background:#000;box-shadow:0 12px 36px rgba(0,0,0,.16)}
.eden-cover{background:#222 url('/presentation/cover-texture.jpeg') center center/cover no-repeat}
.eden-cover-title{position:absolute;left:0;right:0;bottom:58px;text-align:center;color:#f2f2f2;font-family:Georgia,'Times New Roman',serif;font-size:clamp(18px,2.2vw,30px);letter-spacing:.02em;text-shadow:0 1px 2px #000}
.eden-sheet{background:#000}
/* The sheet's own backing, as a picture rather than a background colour.
   The print dialog's "Background graphics" is unticked by default and strips
   every CSS background, which left the black surrounds missing and the summary
   sheet nearly blank on paper. A picture is content and prints either way. */
.presentation-slide>.sheet-fill{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;border-radius:inherit}
.eden-sheet-title{position:absolute;z-index:5;top:0;left:0;right:0;height:54px;display:flex;align-items:center;justify-content:center;background:#050505;color:#f7f7f7;font-family:Georgia,'Times New Roman',serif;font-size:clamp(17px,2vw,27px);letter-spacing:.22em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 30px}
.eden-sheet-media{position:absolute;inset:54px 0 0;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#000}
.eden-sheet-media img{width:100%;height:100%;display:block}
.eden-render-sheet .eden-sheet-media img{object-fit:cover}
.eden-plan-sheet .eden-sheet-media{background:#fff}
.eden-plan-sheet .eden-sheet-media img{object-fit:contain;background:#fff}
.presentation-pdf{display:block;width:100%;height:100%;border:0;background:#fff}
.eden-sheet-logo{position:absolute;z-index:7;right:17px;bottom:11px;width:92px;height:auto;max-height:70px;object-fit:contain;filter:none;background:transparent;padding:0;border-radius:0}
.eden-quote-sheet{background:#f8f7f4;color:#171717}
.eden-quote-body{position:absolute;inset:54px 0 0;padding:30px 44px 56px;overflow:auto;background:#f8f7f4}
.eden-quote-heading{display:flex;justify-content:space-between;align-items:flex-start;gap:30px;padding-bottom:15px;margin-bottom:18px;border-bottom:1px solid #cfcac1}
.eden-quote-heading h2{margin:3px 0 0;font-family:Georgia,'Times New Roman',serif;font-size:clamp(26px,3.2vw,42px);font-weight:500}
.eden-quote-heading>strong{font-size:clamp(24px,3vw,38px);white-space:nowrap}
.eden-breakdown{margin:14px 0 18px}.eden-breakdown h3,.eden-quote-items h3{font-family:Georgia,'Times New Roman',serif;font-size:20px;font-weight:500;margin:0 0 8px}
.eden-quote-items{margin-top:15px}
.presentation-comment{margin:9px 0 0;padding:12px 14px;border:1px solid var(--rule);border-radius:8px;background:var(--sheet)}
.presentation-comment form{display:grid;grid-template-columns:150px minmax(260px,1fr) auto;gap:10px;align-items:end}
.presentation-comment .field{margin:0}.presentation-comment button{align-self:end}
.presentation-sheet-notes{display:grid;gap:6px;margin-top:10px}.presentation-sheet-note{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:7px;background:var(--sheet-sunk)}
.presentation-sheet-note .pill{flex:0 0 auto}
@media(max-width:760px){
  .eden-presentation .presentation-slide{aspect-ratio:auto;min-height:520px}
  .eden-sheet-title{height:48px;font-size:16px;letter-spacing:.12em;padding:0 12px}
  .eden-sheet-media,.eden-quote-body{inset:48px 0 0}.eden-quote-body{padding:22px 16px 48px}
  .presentation-comment form{grid-template-columns:1fr}.eden-sheet-logo{width:70px}
}
/* The presentation prints landscape, edge to edge, one sheet per page.
 *
 * The rest of the paperwork — quotes, invoices, statements — is A4 portrait with
 * an 8 mm margin, and must stay that way. A page can only have one size, so the
 * presentation is given a page of its own by name and asks for that one. The
 * sheets were printing on the portrait page with a margin round them, which left
 * each one covering about half the paper.
 *
 * No margin at all: the sheet IS the page. That is the setting that had to be
 * chosen by hand in the print dialog every single time. */
@page eden-slide { size: A4 landscape; margin: 0; }
@media print{
  .eden-presentation{max-width:none;padding:0;page:eden-slide}
  /* One break per sheet. The wrapper and the sheet inside it each used to ask
     for one, which is two breaks for one sheet — and a blank page between every
     pair of slides. */
  .presentation-sheet-wrap{margin:0;break-after:auto;page-break-after:auto}
  .eden-presentation .presentation-slide{
    /* Exactly the page, so the image has the whole sheet of paper. */
    width:297mm;height:210mm;max-width:none;aspect-ratio:auto;min-height:0;margin:0;
    box-shadow:none;border-radius:0;break-after:page;page-break-after:always;break-inside:avoid;
  }
  /* Nothing after the last one, or the print ends on an empty page. */
  .eden-presentation .presentation-sheet-wrap:last-of-type .presentation-slide{break-after:auto;page-break-after:auto}
  .presentation-comment,.presentation-sheet-notes{display:none!important}
  .eden-quote-body{overflow:hidden}
}

/* v98 — the job starts with the full client quote breakdown, then the active quotes */
.job-quote-breakdown{padding:14px 18px;border-top:1px solid var(--rule);background:var(--sheet-sunk)}
.job-quote-breakdown h3{margin:0 0 8px;font-size:15px}.job-quote-breakdown table{max-width:620px;background:var(--sheet)}
.job-individual-quotes{padding:11px 18px 5px;border-top:1px solid var(--rule)}
.quote-subhead{display:flex;justify-content:space-between;align-items:center;gap:12px}

/* v99: proportions and black bands follow the supplied Eden View deck. */
.eden-presentation .eden-cover{background:#222 url('/presentation/cover-texture.jpeg') center center/cover no-repeat}
.eden-presentation .eden-cover-title{bottom:16.5%;font-size:clamp(18px,2.2vw,30px)}
.eden-presentation .eden-sheet-title{height:6.7%;font-size:clamp(14px,2vw,27px);padding:0 3%;box-sizing:border-box}
.eden-presentation .eden-sheet-media{inset:6.7% 0 6.7%;background:#000}
.eden-presentation .eden-sheet-media img{object-fit:contain;background:#000}
.eden-presentation .eden-plan-sheet .eden-sheet-media img{background:#fff}
.eden-presentation .eden-sheet-logo{right:2.2%;bottom:9.2%;width:11%;max-height:12%}
.eden-presentation .presentation-caption{position:static;max-width:none;padding:10px 16px;margin:0;background:#fff;color:#222;white-space:pre-wrap;font-size:14px;letter-spacing:normal}
.presentation-setup-list{display:grid;gap:22px}
.presentation-setup-item{display:grid;grid-template-columns:minmax(200px,1fr) 2fr;gap:24px;border-top:1px solid var(--rule);padding-top:22px}
.presentation-setup-preview img{width:100%;max-height:260px;object-fit:contain;background:#111}
.site-file-upload{display:grid;gap:10px;max-width:620px;width:100%}
.eden-presentation:fullscreen{overflow:auto;background:#282828;padding:20px;box-sizing:border-box}
.eden-presentation:fullscreen .presentation-slide{box-shadow:none}
@media(max-width:760px){.presentation-setup-item{grid-template-columns:1fr}.eden-presentation .presentation-slide{aspect-ratio:1200/790;min-height:0}.eden-presentation .eden-quote-sheet{aspect-ratio:auto;min-height:600px}}
@media print{.presentation-caption{display:none}.eden-presentation .eden-cover{print-color-adjust:exact;-webkit-print-color-adjust:exact}}
.presentation-player-controls{display:none}
.eden-presentation.is-presenting{position:fixed;inset:0;z-index:1000;max-width:none;background:#282828;padding:10px 20px 74px;overflow:auto;box-sizing:border-box}
.eden-presentation.is-presenting .page-head{display:none}
.eden-presentation.is-presenting .presentation-sheet-wrap{display:none;max-width:min(1200px,calc((100vh - 210px) * 1.519));margin:0 auto}
.eden-presentation.is-presenting .presentation-sheet-wrap.presentation-current{display:block}
.eden-presentation.is-presenting .presentation-player-controls{position:fixed;bottom:0;left:0;right:0;display:flex;gap:16px;align-items:center;justify-content:center;background:#fff;padding:12px;z-index:1100}
@media print{.eden-presentation.is-presenting{position:static;padding:0;overflow:visible}.eden-presentation.is-presenting .presentation-sheet-wrap{display:block;max-width:none}.presentation-player-controls{display:none!important}}

/* Survey answers and question editing. */
.survey-dimensions {display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px}
.survey-dimensions label {display:block;font-size:11px;line-height:1.35;color:var(--muted,#666)}
.survey-dimensions input {margin-top:5px;min-width:0;width:100%;box-sizing:border-box}
.survey-dimensions label>span {display:block;font-size:10px;margin-top:2px}
.ck-answer:has(.survey-dimensions) {min-width:0}
.ck-row:has(.survey-dimensions) {grid-template-columns:minmax(180px,1fr) minmax(280px,400px) 96px 40px}
.survey-answer-note {display:block;margin-top:8px;font-size:11px;color:var(--muted,#666)}
.survey-answer-note input {margin-top:3px}
.survey-question-editor {padding:8px 0;scroll-margin-top:90px}
.survey-question-editor>summary {cursor:pointer;font-weight:600}
.survey-question-editor:target {background:#f5f4ef;border-radius:8px;padding:10px}
.survey-edit-form {display:grid;grid-template-columns:minmax(180px,2fr) minmax(170px,1fr);gap:10px;margin-top:12px}
.survey-edit-form label {display:block;font-size:12px}
.survey-edit-form input:not([type=checkbox]),.survey-edit-form select {display:block;width:100%;box-sizing:border-box;margin-top:4px}
.survey-edit-form .inline-check {display:flex}
.survey-edit-form button {justify-self:start;align-self:end}
.survey-edit-link {font-size:11px;text-align:center}
.ck-answer select {width:100%;min-width:0}
@media(max-width:900px) {
  .ck-row:has(.survey-dimensions) {grid-template-columns:minmax(0,1fr) 40px}
  .ck-row:has(.survey-dimensions) .ck-answer {grid-column:1;grid-row:2}
  .ck-row:has(.survey-dimensions) .ck-flag {grid-column:1;grid-row:3}
}
@media(max-width:600px) {.survey-edit-form {grid-template-columns:minmax(0,1fr)}}

.survey-row-actions {display:flex;flex-direction:column;align-items:center;gap:5px}
.ck-label .survey-edit-form {grid-template-columns:minmax(0,1fr)}

.survey-dimensions label {display:grid;grid-template-rows:30px auto auto;align-content:start}

/* Faster job setup and design changes. */
.new-job-quotes {grid-column:1/-1}
.quote-import-row {display:grid;grid-template-columns:minmax(0,1fr) minmax(150px,240px);gap:12px;align-items:center;padding:8px 0;border-bottom:1px solid #ddd}
.quote-room-import {margin-top:12px}
.quote-room-import>button {margin-top:12px}
.job-stage-controls .stage-skip {grid-column:1/-1}
.presentation-replace-actions {display:flex;flex-wrap:wrap;gap:16px;align-items:end;grid-column:1/-1}
.presentation-replace-actions input[type=file] {max-width:100%}
.presentation-setup-item {min-width:0}
.job-quote-summary td form {display:flex;gap:6px;flex-wrap:wrap}
@media(max-width:650px){.quote-import-row{grid-template-columns:minmax(0,1fr)}.presentation-replace-actions{display:block}.presentation-replace-actions form{margin-top:12px}.presentation-replace-actions input{box-sizing:border-box;width:100%}}

.presentation-replace-actions .check-line {display:flex;align-items:center;gap:8px;text-transform:none;letter-spacing:normal;font-size:12px}
.presentation-replace-actions .check-line input {width:auto;flex:0 0 auto}
.stage-setup-question {border-bottom:1px solid #e1e4e8;padding-bottom:8px;margin-bottom:8px}

.height-group-setup { border-top:1px solid var(--line,#ddd); padding:12px 0; }
.height-group-setup summary { cursor:pointer; font-weight:600; }
.height-group-codes { max-height:320px; overflow:auto; }
.height-group-codes label { display:flex; align-items:center; gap:10px; white-space:normal; }
.height-group-codes input[type=checkbox] { width:auto; flex:none; }
/* Focused work: one queue and a separate record for each job. */
.work-section { margin:24px 0; }
.work-section h2 { font-size:18px; }
.work-item { display:flex; gap:20px; align-items:center; justify-content:space-between; background:var(--paper,#fff); border:1px solid var(--line,#ddd); border-radius:12px; padding:20px; margin:10px 0; }
.work-item h3 { margin:4px 0; font-size:17px; }
.work-item h3 a { color:inherit; }
.work-item p { margin:5px 0; }
.work-next { border-left:4px solid var(--accent,#496b55); }
.work-next h2 { margin:6px 0; }
.work-actions { display:flex; align-items:flex-end; flex-direction:column; gap:8px; flex-shrink:0; }
.work-actions form { margin:0; }
.work-job-link { font-size:12px; }
.work-filters { margin:16px 0; }
.work-filters form { padding-top:12px; }
.work-filters input { max-width:160px; }
.focus-tabs { display:flex; gap:6px; border-bottom:1px solid var(--line,#ddd); margin:18px 0; overflow-x:auto; }
.focus-tabs a { flex:none; padding:12px; font-weight:600; font-size:13px; text-decoration:none; color:var(--ink); }
.focus-tabs a.on { color:var(--accent); border-bottom:3px solid var(--accent); }
.focus-tabs .count {
  display:inline-block; min-width:18px; padding:0 5px; border-radius:9px; font-size:11px;
  background:rgba(0,0,0,.08); text-align:center; margin-left:4px; font-weight:700;
}
/* A row on a list that is retired: still readable, plainly not in play. */
.is-retired > td { opacity:.55; }
.is-retired input { font-style:italic; }
/* A name and its Save button on one line, inside a table cell. */
.inline-rename { display:flex; gap:6px; align-items:center; margin:0; }
.inline-rename input { min-width:0; flex:1 1 auto; }

/* A search box above a list: one line on a desk, stacked on a phone. */
.filter-row { display:flex; flex-wrap:wrap; gap:10px; align-items:flex-end; }
.filter-row .field { min-width:0; margin:0; }
.filter-row input { width:100%; }
.built-table td { vertical-align:top; }
.job-step-tabs { flex-wrap:wrap; }
/* Two marks, two meanings: the underline is the tab you are reading, the bold
   is where the job actually is. A tab that is both carries both. */
.job-step-tabs a.is-current { font-weight:800; }
.job-step-tabs a.is-current::before { content:"● "; color:var(--accent); font-size:10px; vertical-align:2px; }

/* What the job is waiting for, at the top of every step. */
.job-outstanding { border-left:4px solid var(--alert); }
.job-outstanding.is-ready { border-left-color:var(--accent); }
.job-outstanding h2 { font-size:17px; }
.job-important-first { border-left:4px solid var(--alert); }
.outstanding-list { margin:10px 0 0; padding-left:20px; display:flex; flex-direction:column; gap:5px; }
.outstanding-list li { overflow-wrap:anywhere; }
/* Every line of the to-do list is a way in, not a notice. The whole row is the
   target, so it can be hit with a thumb on a phone as well as a mouse. */
.to-do-list { list-style:none; padding-left:0; gap:6px; }
.to-do-line { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; padding:9px 12px; border:1px solid var(--line);
  border-radius:8px; background:var(--card); color:var(--ink); text-decoration:none; }
.to-do-line:hover, .to-do-line:focus-visible { border-color:var(--accent); background:var(--accent-wash); }
.to-do-what { font-weight:600; }
.to-do-go { margin-left:auto; white-space:nowrap; font-weight:700; color:var(--accent); }
.to-do-list li.is-mine .to-do-line { border-left:4px solid var(--accent); }
.shared-important { border-left:4px solid var(--alert); }
.shared-important p,.cupboard-note p,.meeting-record p { white-space:pre-wrap; overflow-wrap:anywhere; }
.meeting-links { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:8px; margin-top:12px; }
.meeting-link { display:flex; flex-direction:column; gap:5px; padding:10px; border:1px solid var(--line); border-radius:8px; color:var(--ink); }
.meeting-link.on { border-color:var(--accent); background:var(--accent-wash); }
.job-reference-files { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:12px; margin-top:14px; }
.job-reference-file { display:flex; flex-direction:column; border:1px solid var(--line); border-radius:8px; padding:8px; gap:6px; color:var(--ink); overflow-wrap:anywhere; }
.job-reference-file img { width:100%; height:145px; object-fit:contain; }
.job-reference-file span { font-size:12px; }
/* A PDF shows its first page in the tile, the same as a picture does, drawn by
   the browser's own PDF viewer — so nothing extra has to be installed on the
   machine this runs on. The tile is not scrollable: it is a thumbnail, and the
   whole document opens when you click it. If the browser will not draw it, the
   plain "PDF" label underneath shows through instead. */
.reference-pdf { display:block; height:140px; background:var(--field); overflow:hidden; position:relative; border-radius:6px; }
.reference-pdf object { width:100%; height:200%; border:0; pointer-events:none; transform:scale(1); transform-origin:top center; }
.reference-pdf-fallback { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font-weight:700; letter-spacing:1px; color:var(--muted); }
/* The tile is a link; the embedded page must not swallow the click. */
a.job-reference-file .reference-pdf { pointer-events:none; }
.cupboard-list { display:grid; gap:12px; }
.cupboard-room>.card-head { flex-wrap:wrap; gap:12px; }
.cupboard-room>.card-head>div:first-child { flex:1; min-width:150px; }
.cupboard-room>.card-head>.row-actions { flex-wrap:wrap; }
.cupboard-item { padding:14px; border:1px solid var(--line); border-radius:8px; overflow-wrap:anywhere; }
.cupboard-item.is-built { border-left:4px solid var(--accent); }
.cupboard-heading { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:12px; }
.cupboard-heading form { display:flex; align-items:center; gap:8px; }
.cupboard-note { padding:8px 10px; margin:8px 0; background:var(--field); border-radius:6px; }
.cupboard-item .row-actions input { flex:1; min-width:150px; }
.job-controls>section,.job-controls>details { margin-top:12px; }
.job-add-task>summary { font-weight:650; }
.step-checklists .focus-room-row { padding:12px 0; }
.survey-reference { margin:12px 0; }
@media(max-width:760px){.job-step-tabs { flex-wrap:nowrap; } .cupboard-heading { align-items:flex-start; } .meeting-links { grid-template-columns:1fr; } .job-reference-files { grid-template-columns:repeat(2,minmax(0,1fr)); } .job-reference-file img { height:105px; }}
.focus-room { display:flex; align-items:center; gap:10px; margin:16px 0; }
.focus-room select { max-width:360px; }
.focus-room-row, .finished-task, .focus-file { display:flex; align-items:center; gap:12px; justify-content:space-between; padding:12px 0; border-bottom:1px solid var(--line,#ddd); }
.focus-file { align-items:flex-start; }
.focus-file .hint { max-width:60%; }
.focus-blockers summary,.focus-flags summary,.extra-work summary,.add-task summary { cursor:pointer; font-weight:600; }
@media(max-width:640px) {
 .work-item { align-items:stretch; flex-direction:column; padding:16px; gap:12px; }
 .work-actions { flex-direction:row; align-items:center; flex-wrap:wrap; }
 .work-actions .btn { max-width:100%; }
 .focus-room-row,.finished-task,.focus-file { align-items:flex-start; flex-direction:column; }
 .focus-file .hint { max-width:100%; }
 .focus-tabs { margin:12px 0; }
 .focus-tabs a { padding:10px 8px; }
}
.nav-more { position:relative; }
.nav-more>summary { cursor:pointer; padding:8px 10px; color:var(--chrome-ink); font-size:12px; border-radius:6px; list-style:none; }
.nav-more>summary.active { background:var(--accent); color:#fff; }
.nav-more-menu { position:absolute; top:100%; left:0; background:var(--chrome-800); border:1px solid var(--chrome-700); border-radius:8px; min-width:210px; padding:8px; box-shadow:var(--shadow-lift); display:flex; flex-direction:column; z-index:80; }
.task-working-content { display:block; }
.task-working-content .ck-list { max-width:100%; }
.survey-job-editor>summary { cursor:pointer; font-weight:600; }
#task-photos>summary,#task-settings>summary { cursor:pointer; font-weight:600; margin-bottom:12px; }
@media(max-width:760px){ .nav-more-menu { position:static; min-width:0; border:0; padding:0 0 0 8px; box-shadow:none; } .nav-more>summary { padding:12px 14px; font-size:15px; } }

/* Opened in normal flow this 600px panel pushed itself off the right edge of
   the window. It now hangs below its button like the dropdown it is. */
.job-board-filters{position:relative}
.job-board-filters > .row-actions{
  max-width:min(600px,calc(100vw - 32px));padding:12px;
  position:absolute;right:0;top:calc(100% + 6px);z-index:30;
  background:var(--sheet);border:1px solid var(--rule);border-radius:var(--radius);
  box-shadow:0 10px 24px rgba(0,0,0,.14);
}
.job-focus-list{display:grid;gap:10px}.job-focus-list .work-item{margin:0}.job-focus-list .hint + .hint{margin-top:4px}

/* Department handover and meeting comment controls. */
.handoff { border-color: var(--accent); }
.handoff form { margin: 0; }
.presentation-comment-kind, .presentation-comment-text { min-width: 0; }
.task-next { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }

/* Where the job goes once the client meetings are done: meet again, send it on,
   or call it off — side by side, each saying what it will do. */
.after-meeting-ways { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:12px; margin-top:12px; }
.after-way { display:flex; flex-direction:column; gap:7px; padding:13px; border:1px solid var(--line); border-radius:9px; background:var(--field); }
.after-way strong { font-size:14px; }
.after-way .btn { align-self:flex-start; }
.after-way label { display:flex; flex-direction:column; gap:5px; font-size:13px; }
.after-way.is-stop { border-color:var(--rule-strong); }
.after-way.is-stop > summary { cursor:pointer; font-weight:600; list-style:none; }
.after-way.is-stop[open] > summary { margin-bottom:7px; }
/* Finishing a meeting without opening it first. */
.meeting-link-wrap { display:flex; flex-direction:column; gap:6px; }
.meeting-link-wrap .meeting-link { flex:1; }
.meeting-link-end { display:flex; }

/* The cutting lists, on the Orders tab, where the board order is placed. */
.cutting-under-orders .focus-room-row { border-top:1px solid var(--line); }
.cutting-under-orders .focus-room-row:first-of-type { border-top:0; }
.cutting-under-orders .row-actions { flex-wrap:wrap; gap:6px; }

/* A row that has just saved, so the eye can see it took without the page moving. */
tr.just-saved > td { background:var(--accent-wash); transition:background .25s ease; }

/* Changing a panel's thickness where it stands, rather than deleting it and
   typing it in again. */
.thickness-pick { display:flex; }
.thickness-pick select { font-size:12px; padding:4px 6px; }

/* Setting several quotes to the same status in one go. */
.quote-bulk { display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:9px 18px; border-top:1px solid var(--rule); background:var(--sheet-sunk); }
.quote-bulk select { max-width:200px; }
.quote-tick { margin-right:2px; }
/* The tick itself: a little larger than the browser's default, because it is
   pressed over and over when a row of quotes is being set all at once. */
.quote-pick { width:17px; height:17px; cursor:pointer; accent-color:var(--accent); }

/* A note taken on a slide: what it is, whether it is flagged for the whole job,
   and who is doing something about it. */
.presentation-sheet-note.is-important { border-left:4px solid var(--alert); }
/* A note is a row of a pill, the words, and the buttons. It used to be a flex
   row that could not wrap, so on a tablet or a phone the row simply grew wider
   than the screen and took the whole presentation page sideways with it — 142px
   off the edge of a phone. It wraps now, and the words are allowed to shrink:
   a flex item will not go below its content width unless it is told it may. */
.presentation-sheet-note { flex-wrap:wrap; }
.presentation-sheet-note .note-words { flex:1 1 220px; min-width:0; overflow-wrap:anywhere; }
.presentation-sheet-note .note-actions { display:flex; gap:6px; align-items:center; flex-wrap:wrap; margin-left:auto; }
/* "Hand it to…" hangs a 260px panel off the right of its button. Everywhere
   else that works; inside a presentation sheet the sheet clips its own overflow,
   so the right-hand 28px of the panel — the end of the date box and part of the
   button — was cut off and unreachable even on a 1440px screen. In here the
   panel sits in the flow under its button instead, where nothing can clip it. */
.presentation-sheet-note .drop { position:static; flex:1 1 100%; }
.presentation-sheet-note .drop > summary { display:inline-flex; width:auto; }
.presentation-sheet-note .drop-body {
  position:static; width:auto; max-width:100%; box-shadow:none; margin-top:6px;
}
/* A date box and a long list of people have an intrinsic least width of their
   own that width:100% cannot go under, so the panel is given the note's whole
   width to lay them out in rather than the few pixels left beside a button. */
.presentation-sheet-note .drop-body select,
.presentation-sheet-note .drop-body input { max-width:100%; min-width:0; }
.presentation-comment-important { grid-column:1/-1; }

/* The door gap: two small boxes in one narrow column of the cupboard grid. */
.cupboard-grid .w-doorgap { width: 104px; }
.cupboard-grid .w-doorgap .dim-pair { display: flex; gap: 4px; }
.cupboard-grid .w-doorgap input { width: 46px; text-align: right; }
/* The line naming the handle wraps inside the column rather than widening it.
   Without the cap the cell stretches to hold the sentence on one line, and a
   column of 104px becomes one of 273px — on a grid that is already wider than
   the screen. */
.cupboard-grid .w-doorgap .hint.block {
  max-width: 104px;
  white-space: normal;
  line-height: 1.25;
  margin-top: 3px;
}

/* The code stays put while the rest slides.
 *
 * Eleven columns are wider than the card, so reading a row means sliding
 * sideways — and the moment the Code column goes off the left you no longer
 * know which cupboard you are editing. It is pinned, the way the quote grid
 * pins its first two columns, and it sits above the rows it scrolls past. */
table.sheet-sticky th.lock-col,
table.sheet-sticky td.lock-col {
  position: sticky; left: 0; z-index: 3;
  background: var(--sheet);
  box-shadow: 1px 0 0 var(--rule);
}
table.sheet-sticky tbody tr:nth-child(even) td.lock-col { background: var(--sheet-sunk); }
/* The header's own corner has to beat both the sticky row and the sticky
   column, or the first column name is painted over as you scroll. */
table.sheet-sticky thead th.lock-col { z-index: 6; background: var(--sheet-sunk); }
@media print { table.sheet-sticky th.lock-col, table.sheet-sticky td.lock-col {
  position: static !important; box-shadow: none !important; } }

/* The tapes whose name looks like a given board, offered under its box. One
   click fills the box in; the names are long, so they wrap rather than push
   the table wider than the screen. */
.cut-suggest { display:flex; flex-wrap:wrap; gap:4px; margin-top:4px; max-width:320px; }
.cut-suggest .btn.small { font-size:11px; padding:1px 6px; line-height:1.5; }

/* ---- the cupboard codes list: can this code be cut? ------------------- */
/* Put beside the code rather than at the far right, because a code with no
   cutting list is priced on a quote and then puts nothing on the workshop's
   sheet — and that is worth seeing without scrolling the table sideways. */
.w-cut { width: 120px; white-space: nowrap; }
.cut-count, .cut-none {
  display: inline-block; font-size: 12px; padding: 2px 8px; border-radius: 10px;
  text-decoration: none; border: 1px solid transparent;
}
.cut-count { color: var(--ink-faint); background: var(--sheet-sunk); }
.cut-none { color: var(--alert); background: var(--alert-wash); border-color: var(--alert); }
.cut-count:hover, .cut-none:hover { border-color: currentColor; }
/* A count in a line of prose that filters the list when you press it. */
.link-btn {
  background: none; border: 0; padding: 0; font: inherit; color: var(--accent);
  text-decoration: underline; cursor: pointer;
}

/* ---- the cutting list build: a part is one row, not one screen ------- */
/* The split figures belong to the tick above them. Open on every row they
   made each part 170px tall — five parts over three screens — when most parts
   are not split at all. :has() does the work; where a browser has none the
   fields simply stay visible, which is how this looked before. */
.c-split .split-group + .split-group {
  margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--rule);
}
.c-split .split-fields { margin-top: 6px; display: grid; gap: 4px; }
.c-split .split-group:has(> label input[type="checkbox"]:not(:checked)) .split-fields { display: none; }
/* A part that is not split says so in one line rather than in seven boxes. */
.c-split .split-group:has(> label input[type="checkbox"]:not(:checked)) > label span { color: var(--ink-faint); }
/* Most parts are cut at every size. The three ranges fold away on those, with
   a "limits" button to open them and the reading underneath saying what they
   come to either way. */
.c-when.is-plain .when-fields { display: none; }
.when-toggle {
  background: none; border: 1px solid var(--rule); border-radius: 10px;
  font-size: 10.5px; color: var(--ink-faint); padding: 0 7px; cursor: pointer;
  text-transform: uppercase; letter-spacing: .04em;
}
.when-toggle:hover { border-color: var(--rule-strong); color: var(--ink-soft); }
.c-when:not(.is-plain) .when-toggle { display: none; }
/* "split down" on one line: wrapped, it put the column's two ticks three lines
   deep and set the height of the whole row. The column is widened to match. */
.c-split .inline-check span { white-space: nowrap; }

/* ---- cutting list setup: where you stand, at the top ----------------- */
.setup-tally { display: flex; gap: 18px; align-items: baseline; flex-wrap: wrap; }
.setup-tally span { font-size: 13px; color: var(--ink-soft); white-space: nowrap; }
.setup-tally strong { font-size: 18px; color: var(--ink); font-variant-numeric: tabular-nums; }
/* A tick whose words must not break over three lines beside a file box. */
.inline-check.nowrap span { white-space: nowrap; }
/* A colour a board was given before the list existed, or one renamed away. */
.cut-adrift { color: var(--alert); margin-top: 2px; }
/* Long explanations read better at a measure than at the width of a 1600px
   card. The cards' own heads are where these live. */
.card-head .hint { max-width: 78ch; display: inline-block; }
/* Whether the lists hold what the supplier's sheet lists. */
.sheet-gap { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.sheet-gap.ok { color: var(--ink-faint); }
.flash.warn { border-color: var(--pending); background: var(--pending-wash); color: var(--ink); }

/* The tape a board takes, beside the board itself rather than buried in a list
   of several hundred colours. */
.w-tape { width: 260px; }
#cut-colour-map td.w-tape select { min-width: 0; }

/* What a handle eats out of the front it is fitted to: millimetres and which
   way round, one cell on the Handles tab only. */
.w-cut-front { width: 92px; }
/* The millimetres and the direction are one setting, so they sit one above the
   other rather than side by side: together they read as "12mm, across", and the
   column stays narrow enough that this table still fits the page as the other
   hardware tabs do. .pair is laid out by the cupboard grid only, so this column
   lays out its own — without that the dropdown spills into the column beside
   it, where it reads as belonging to the wrong setting. */
.w-cut-front .pair { display: flex; flex-direction: column; gap: 4px; align-items: stretch; }
.w-cut-front .pair input { width: 100%; min-width: 0; text-align: right; }
.w-cut-front .pair select { width: 100%; min-width: 0; }
