:root {
  /* Sinewave Cyan, taken from the logo artwork. Adam settled this on 15 Aug 2026: the
     brand notes had declared #1CABE2 while every logo file used #00AEEF, and the logo is
     the artefact people actually see. The brand skill was corrected to match.

     #00AEEF is bright — 2.53:1 on white — so it is a FILL and an accent, never text and
     never a text background. Two derived shades carry the jobs it cannot do:
       --cyan-text  #007FB1  exactly 4.5:1 on white, so links pass AA
       --cyan-deep  #00719D  for hover, and for a filled control that needs white on it
     Nothing else may be introduced: brand is cyan, dark grey and white. */
  --cyan: #00AEEF;
  --cyan-text: #007FB1;
  --cyan-deep: #00719D;
  /* Kept as an alias so the nine existing uses do not all have to change in one commit,
     and so a stylesheet loaded later cannot miss the rename. */
  --cyan-dark: #007FB1;
  --grey: #333F48;
  --bg: #f5f7f8;
  --surface: #ffffff;
  --surface-inset: #f6f7f8;

  /* Three rule weights doing three different jobs. --line-strong is STRUCTURAL — a totals
     row, a frozen-column edge, a group seam — and is deliberately not a text grey. The
     finance grids use it eight times and folding it into an ink token would flatten every
     seam in them. */
  --line: #dde3e7;
  --line-soft: #eef1f3;
  --line-strong: #b9c3cc;

  --ink-2: #6b7780;        /* secondary text, labels */
  --ink-3: var(--ink-3);        /* table headers, quiet notes */

  /* The informational family, tinted from the brand cyan rather than a foreign blue. */
  --cyan-bg: var(--cyan-bg);
  --cyan-bg-soft: #f4fbfe;
  --cyan-line: #cfe9f5;

  /* Semantic. Three families, three slots each, and nothing else — a status pill that is
     only grey is useless, but a fourth green or a second blue is drift. */
  --ok: #1e7d3c;      --ok-bg: #e4f7e9;      --ok-line: #b7e2c4;
  --warn: #9a7b00;    --warn-bg: #fff3d6;    --warn-line: #f0dfae;
  --danger: #c0392b;  --danger-bg: #fdeaea;  --danger-line: #f2c9c9;
}
* { box-sizing: border-box; }
body {
  margin: 0; font-family: 'Poppins', Calibri, system-ui, -apple-system, sans-serif;
  background: var(--bg); color: var(--grey); font-size: 15px;
}
main { max-width: 1600px; margin: 0 auto; padding: 24px 24px 60px; }
h1 { font-size: 1.5rem; margin: 0.4em 0; }
h2 { font-size: 1.1rem; margin: 1.4em 0 0.5em; }
/* Links are brand cyan, plain, no underline.
   Two goes at this were wrong. A derived #007FB1 was off-palette and read as a generic
   blue; underlining every link was worse and looked terrible across dense tables. Adam's
   call both times, and he is right: the palette is cyan #00AEEF, dark grey and white, and
   a link should just be cyan.
   Recorded so it is not "improved" again: #00AEEF is 2.53:1 on white, below the 4.5
   accessibility guideline. That is a known and accepted trade for brand fidelity. */
a { color: var(--cyan); text-decoration: none; }
a:hover { color: var(--cyan-deep); text-decoration: underline; }

.topbar {
  display: flex; align-items: center; gap: 24px; padding: 12px 20px;
  background: var(--grey); color: #fff;
}
.topbar .brand { color: #fff; font-weight: 700; font-size: 1.05rem; display: flex; align-items: center; gap: 8px; }
.topbar .brand span { color: var(--cyan); }
.brandlogo { height: 22px; width: auto; display: block; }
.topbar nav { display: flex; gap: 18px; flex: 1; }
.topbar nav a { color: #e8edf0; font-weight: 500; }
.topbar nav a:hover { color: var(--cyan); text-decoration: none; }
.topbar nav a.active { color: var(--cyan); }
.userbox { display: flex; gap: 10px; align-items: center; font-size: 0.9rem; color: #cfd6db; }
/* Second navigation tier: the pages of the module you are in. */
.subnav {
  display: flex; gap: 18px; padding: 9px 20px; background: #f2f5f7;
  border-bottom: 1px solid var(--line); font-size: 0.92rem;
}
.subnav a { color: var(--grey); font-weight: 500; }
.subnav a:hover { color: var(--cyan); text-decoration: none; }
.subnav a.active { color: var(--cyan); font-weight: 600; }
/* Collapsible module sections on the permissions matrix. */
.permgroup { margin: 14px 0; }
.permgroup > summary {
  cursor: pointer; font-weight: 600; font-size: 1.05rem; color: var(--grey);
  padding: 6px 0; list-style-position: inside;
}
.permgroup > summary:hover { color: var(--cyan); }
/* Request progress strip: status counts + clickable PO links into Procore. */
.progressline { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 6px 0 0; }
a.chip.po { background: var(--cyan); color: #fff; font-weight: 600; }
a.chip.po:hover { text-decoration: none; filter: brightness(1.08); }
a.status { text-decoration: none; }
a.status:hover { filter: brightness(1.08); text-decoration: none; }
.pill { background: var(--cyan); color: #fff; border-radius: 10px; padding: 0 7px; font-size: 0.8rem; }

.foot { text-align: center; color: var(--ink-3); font-size: 0.8rem; padding: 20px; }
.crumbs { font-size: 0.85rem; color: var(--ink-3); margin-top: 4px; }
.muted { color: #6b7780; }
.small { font-size: 0.82rem; }
.nowrap { white-space: nowrap; }
.error { color: var(--danger); }
.notice { background: #fff8e6; border: 1px solid #f0dfae; padding: 10px 14px; border-radius: 8px; }

.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; margin-top: 18px; }
.tile {
  background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 18px;
  display: flex; flex-direction: column; gap: 4px; transition: border-color 0.15s;
}
.tile:hover { border-color: var(--cyan); text-decoration: none; }
.tile.disabled { opacity: 0.55; }
.tile-code { font-weight: 700; font-size: 1.2rem; color: var(--grey); }
.tile-name { color: #6b7780; font-size: 0.88rem; }
.tile-count { color: var(--cyan-dark); font-size: 0.85rem; font-weight: 500; }
/* The operator's mark on a catalogue tile: a real logo file if one has been added to
   public/logos, otherwise the short name on its own colour. Fixed height either way, so a
   row of tiles lines up whichever kind each one is. */
.dnomark {
  display: flex; align-items: center; justify-content: center;
  height: 56px; border-radius: 8px; margin-bottom: 8px;
  color: #fff; font-weight: 700; font-size: 1.25rem; letter-spacing: 0.04em;
}
.dnomark.logo { background: #fff; border: 1px solid var(--line); padding: 6px 10px; }
/* Operators' logos run from 2.5:1 (SSEN) to 5.7:1 (ours). Capping the HEIGHT rather than
   filling the box keeps a squarer logo from towering over a long thin one. */
.dnomark.logo img { max-height: 40px; max-width: 100%; object-fit: contain; }
/* Transmission covers three owners (NGET, SPT, SHET), so it carries the word, not a mark. */
.dnomark.word { background: #fff; border: 1px dashed var(--line); color: var(--grey); font-size: 1.05rem; }

.searchbar { display: flex; gap: 10px; margin: 16px 0 10px; }
.searchbar input[type=search] { flex: 1; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 18px; }
.chip {
  background: #fff; border: 1px solid var(--line); border-radius: 16px;
  padding: 4px 12px; font-size: 0.85rem; color: var(--grey);
}
.chip.active { background: var(--cyan); border-color: var(--cyan); color: #fff; }
.chip:hover { text-decoration: none; border-color: var(--cyan); }

/* border-collapse: separate + no overflow clipping, so the per-supplier update
   popover can extend beyond the table edge instead of being cut off */
table.list { width: 100%; border-collapse: separate; border-spacing: 0; background: #fff; border: 1px solid var(--line); border-radius: 10px; }
table.list thead th:first-child { border-top-left-radius: 10px; }
table.list thead th:last-child { border-top-right-radius: 10px; }
table.list th { text-align: left; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-3); padding: 10px 12px; border-bottom: 1px solid var(--line); }
table.list td { padding: 9px 12px; border-bottom: 1px solid #eef1f3; vertical-align: middle; }
table.list tr:last-child td { border-bottom: none; }
table.list.compact td, table.list.compact th { padding: 6px 10px; font-size: 0.88rem; }
.totalrow td { background: var(--cyan-bg-soft); }
.code { font-family: ui-monospace, Menlo, monospace; font-size: 0.86rem; }
.tag { background: var(--cyan-bg); color: var(--cyan-dark); border-radius: 4px; padding: 1px 7px; font-size: 0.76rem; }
.tag.warn { background: #fdeaea; color: var(--danger); }
/* Sync run summaries. A removal or a conflict is the only thing on that screen anyone
   needs to act on, so it is the only thing that gets colour. */
.sum-warn { color: var(--warn); }
.sum-bad { color: var(--danger); }
.sum-muted { color: var(--ink-3); font-style: italic; }

/* Detail columns hold prose. `anywhere` breaks mid-word to make text fit any width, which
   on a squeezed column reads "approve / d feed" — so only break a word that genuinely
   cannot fit, and give the column a floor so it is never squeezed that far. */
.detailcell { max-width: 340px; min-width: 220px; overflow-wrap: break-word; }
/* Long identity codes DO need breaking anywhere — they have no spaces to break at. */
.detailcell .code, td .code { overflow-wrap: anywhere; }

/* The DNO updates review queue. Without widths the item description takes whatever it
   likes and everything else is crushed; these keep the decision buttons and the detail
   readable however long a description runs. */
table.list.flags { table-layout: fixed; }
table.list.flags col.c-when { width: 6.5rem; }
table.list.flags col.c-dno { width: 5rem; }
table.list.flags col.c-type { width: 7rem; }
table.list.flags col.c-detail { width: 24%; }
table.list.flags col.c-act { width: 11rem; }
table.list.flags td { vertical-align: top; }
table.list.flags td .code { display: inline-block; margin-right: 6px; }
/* The decision button and its quiet alternative stack, so neither is squeezed against the
   table edge however narrow the column runs. */
.flagactions form { display: block; margin: 0 0 4px; }
.flagactions .linklike { padding: 0; }

.status { border-radius: 4px; padding: 2px 8px; font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.s-draft { background: #eef1f3; color: #6b7780; }
.s-sent { background: var(--cyan-bg); color: var(--cyan-dark); }
.s-quoted { background: #fff3d6; color: #9a7b00; }
.s-approved { background: #e4f7e9; color: #1e7d3c; }
.s-ordered { background: #1e7d3c; color: #fff; }
.s-cancelled, .s-failed, .s-removed, .s-not_ordered { background: #fdeaea; color: var(--danger); }
.s-completed { background: #1e7d3c; color: #fff; }
.s-open { background: #eef1f3; color: #6b7780; }
.s-ok, .s-added { background: #e4f7e9; color: #1e7d3c; }
.s-changed, .s-running { background: #fff3d6; color: #9a7b00; }
.s-conflict { background: #fdeaea; color: var(--danger); }

.btn {
  display: inline-block; background: #fff; border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 16px; font-family: inherit; font-size: 0.9rem; font-weight: 500;
  color: var(--grey); cursor: pointer;
}
.btn:hover { border-color: var(--cyan); text-decoration: none; }
/* White on brand cyan. Adam's call, 15 Aug 2026 — I had switched the label to dark grey
   for contrast and he wants it white, which is how the button has always looked and how
   the brand reads. Recorded so it is not "corrected" again: white on #00AEEF is 2.53:1,
   below the 4.5 guideline, and the semibold weight plus the deeper hover are what carry it. */
.btn.primary { background: var(--cyan); border-color: var(--cyan); color: #fff; font-weight: 600; }
.btn.primary:hover { background: var(--cyan-deep); border-color: var(--cyan-deep); color: #fff; }
.btn.small { padding: 4px 10px; font-size: 0.82rem; }
.btn.danger-btn { border-color: var(--danger); color: var(--danger); }
.linklike { background: none; border: none; padding: 0; color: var(--cyan-dark); cursor: pointer; font: inherit; font-size: 0.86rem; }
.linklike:hover { text-decoration: underline; }
.linklike.danger { color: var(--danger); }
.inline { display: inline; }

input, select, textarea {
  font-family: inherit; font-size: 0.92rem; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--grey);
  width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--cyan); border-color: var(--cyan); }
label { display: flex; flex-direction: column; gap: 4px; font-size: 0.85rem; font-weight: 500; flex: 1; }
.row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; margin: 8px 0; }
.row > * { flex: 1; min-width: 120px; }
.row .btn { flex: 0; white-space: nowrap; }
.stack { display: flex; flex-direction: column; gap: 10px; }
.qty { width: 84px; }
.addform { display: flex; gap: 6px; align-items: center; }
.addform .qty { width: 76px; }

.card { background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 18px 20px; margin: 18px 0; }
.item-head { display: flex; justify-content: space-between; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
.item-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
@media (max-width: 800px) { .two-col { grid-template-columns: 1fr; } }

details.editbox { margin: 8px 0 16px; }
details.editbox summary, details.inline-edit summary { cursor: pointer; color: var(--cyan-dark); font-size: 0.88rem; }
details.inline-edit { position: relative; }
.popover {
  position: absolute; right: 0; z-index: 10; background: #fff; border: 1px solid var(--line);
  border-radius: 10px; padding: 12px; box-shadow: 0 6px 18px rgba(0,0,0,0.12); width: 300px;
  display: flex; flex-direction: column; gap: 10px;
}
.popover .row { margin: 0; }
/* explicit two-step remove: click "remove" -> confirm button appears */
details.confirm-remove { display: inline-block; position: relative; }
details.confirm-remove summary { list-style: none; cursor: pointer; }
details.confirm-remove summary::-webkit-details-marker { display: none; }
details.confirm-remove .confirmbox {
  position: absolute; right: 0; top: 130%; z-index: 20; background: #fff;
  border: 1px solid var(--danger); border-radius: 10px; padding: 10px 12px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.15); width: 240px; font-size: 0.85rem;
  display: flex; flex-direction: column; gap: 8px; text-align: left;
}
.confirmbox.wide { width: 320px; }
/* Category names run to "Substation & housings" and longer, and a box that clips the name
   makes renaming guesswork. */
.catname { width: 100%; min-width: 15rem; max-width: 26rem; }
/* Move-to-category bar above a section's item list: tick rows, pick a shelf, move. */
.movebar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 14px; margin-bottom: 12px;
}
/* the global label rule stacks caption above field; on this one-line bar it reads as a
   heading floating over the dropdown, so put the caption back beside it */
.movebar label { display: flex; flex-direction: row; align-items: center; gap: 6px; font-size: 0.9rem; flex: 0 1 auto; }
.movebar label select { max-width: 260px; }
.movebar .movecount { font-size: 0.88rem; color: #6b7780; }
.movebar .movecount.some { color: var(--grey); font-weight: 600; }
table.list th.tickcol, table.list td.tickcol { width: 2.2rem; padding-right: 0; }
.center { text-align: center; }
.voltselect { max-width: 200px; }
.voltrow { margin: -6px 0 16px; }
/* quote-lines editor: roomy description, top-aligned cells, growing comment box.
   Slightly smaller type so big prices fit and the row stays tidy. */
#quote-form td { vertical-align: top; padding-top: 12px; font-size: 0.88rem; }
#quote-form input, #quote-form select, #quote-form textarea { font-size: 0.85rem; padding: 7px 8px; }
#quote-form td:nth-child(2) { min-width: 230px; }
/* These three used to read [name^="price_"] and had NEVER matched anything: the fields are
   named q_<id>_price, so they start with "q_". The lead input was saved only by its .qty
   class; the price input has no width rule anywhere, so it rendered at the browser default
   inside the app's widest table and made the quote grid wider than it needed to be. */
#quote-form input[name$="_price"] { max-width: 130px; min-width: 110px; }
#quote-form input[name$="_lead"] { max-width: 58px; }
#quote-form select[name$="_leadunit"] { max-width: 86px; }
.commentcell { min-width: 200px; width: 22%; }
.comment-box { width: 100%; min-height: 36px; resize: vertical; overflow: hidden; line-height: 1.4; }
.row .btn { flex: 0 0 auto; white-space: nowrap; }

/* line-status row highlighting: light green = quoted, deeper green = approved/ordered */
table.list tr.row-quoted td { background: #f0faf2; }
table.list tr.row-approved td { background: #e2f6e8; }
table.list tr.row-ordered td { background: #d9f2e1; }
table.list tr.row-not_ordered td { background: #f6f6f6; color: var(--ink-3); }
/* --- request lines with several supplier quotes underneath each one --------------
   Each line is its own <tbody>: an item row, then one row per supplier's quote, then
   the "add a supplier quote" row. The grouping bar on the left is what makes it read
   as sub-items rather than a flat list of unrelated rows. */
table.list.quotes tbody.linegroup { border-top: 2px solid var(--line); }
table.list.quotes tbody.linegroup:first-of-type { border-top: none; }
/* The ITEM is a section band; its supplier quotes hang underneath as indented
   sub-rows with a connecting rail, so item vs quote reads at a glance. */
table.list.quotes tr.itemrow td { border-bottom: none; padding: 12px 8px; background: #eef3f6; }
table.list.quotes tr.itemrow.row-quoted td { background: #e8f4ec; }
table.list.quotes tr.itemrow.row-approved td { background: #d9eee0; }
table.list.quotes tr.itemrow .itemhead { display: flex; gap: 16px; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }
table.list.quotes tr.itemrow .itemhead > div:first-child { flex: 1 1 340px; }
table.list.quotes tr.quoterow td, table.list.quotes tr.addrow td { border-bottom: none; padding-top: 5px; padding-bottom: 5px; }
table.list.quotes tr.quoterow td:nth-child(2),
table.list.quotes tr.addrow td:nth-child(2) { padding-left: 34px; position: relative; }
table.list.quotes tr.quoterow td:nth-child(2)::before,
table.list.quotes tr.addrow td:nth-child(2)::before {
  content: "↳"; position: absolute; left: 12px; color: #9fb4c0;
}
table.list.quotes tr.quoterow td.pick { text-align: center; }
table.list.quotes tr.quoterow.chosen td { background: #f3fbf5; }
table.list.quotes tr.quoterow.chosen td:nth-child(2) { box-shadow: inset 3px 0 0 var(--cyan); }
table.list.quotes tr.addrow td { padding-bottom: 14px; }
table.list.quotes tr.addrow select, table.list.quotes tr.addrow input { opacity: 0.75; }
table.list.quotes tr.addrow:focus-within select, table.list.quotes tr.addrow:focus-within input { opacity: 1; }
table.list.quotes tr.addrow input[list] { min-width: 240px; }
.supchips { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-bottom: 4px; }
.supchips .chip, button.chip { cursor: pointer; font-size: 0.78rem; padding: 2px 9px; font-family: inherit; }
.supchips .chip:hover, button.chip:hover { background: var(--cyan); color: #fff; border-color: var(--cyan); }
/* per-item supplier sub-header: the column labels belong to the supplier rows, not to
   the item band above them */
table.list.quotes tr.subhead td {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-3);
  padding: 6px 8px 2px; border-bottom: none;
}
table.list.quotes tr.subhead td:nth-child(2) { padding-left: 34px; }
/* the RFQ panel sits at the top of Step 1 */
.rfqpanel { background: #f4fbfe; border: 1px solid #cfe9f5; border-radius: 8px; padding: 12px 16px; margin: 12px 0 16px; }
.rfqpanel table.list { background: #fff; }
/* a quote that is beaten on price or lead time is tinted so the trade-off is visible */
table.list.quotes td.dear, table.list.quotes td.dear input { color: var(--danger); }
table.list.quotes td.dear input { border-color: #f2c9c9; background: #fdf6f5; }
table.list.quotes td.slow, table.list.quotes td.slow input { color: var(--danger); }
table.list.quotes td.slow input { border-color: #f2c9c9; background: #fdf6f5; }
/* the supplier's own code for the item: quiet reference, editable in place */
table.list.quotes td.codecell { width: 110px; }
input.codeinput {
  width: 100px; font-family: ui-monospace, Menlo, monospace; font-size: 0.78rem;
  color: #6b7780; background: #fafbfc; border-color: var(--line); padding: 4px 7px;
}
input.codeinput:focus { color: var(--grey); background: #fff; }
/* the reversible "no choice" option is a small inline chip - the global label rule
   (block flex column, flex: 1) must not apply or it becomes a giant full-width box */
label.chip.nochoice {
  cursor: pointer; display: inline-flex; flex-direction: row; align-items: center;
  gap: 4px; flex: 0 0 auto; font-weight: 400; font-size: 0.85rem;
}
label.chip.nochoice input { width: auto; margin: 0; }
/* says what is filtering the list right now, with a way straight out of it */
.filterbar { background: #f4fbfe; border: 1px solid #cfe9f5; border-radius: 8px; padding: 8px 14px; margin: 0 0 10px; font-size: 0.9rem; }
.chip.good { background: #e2f6e8; border-color: #b7e2c4; color: var(--ok); }
.chip.warn { background: #fdeaea; border-color: #f2c9c9; color: var(--danger); }

/* the comparison verdict: one sentence, coloured by what it is telling you to do */
.verdict { border-left: 4px solid var(--cyan); background: #f4fbfe; padding: 10px 14px; border-radius: 0 8px 8px 0; margin: 6px 0 16px; }
.verdict.v-split { border-left-color: var(--ok); background: #f1faf3; }
.verdict.v-single { border-left-color: var(--cyan); }
.verdict.v-incomplete { border-left-color: var(--warn); background: #fff8e6; }

.pushgroup { border-top: 1px solid var(--line); padding: 12px 0; }
.pushline { display: block; font-weight: 400; padding: 3px 0; }
.pushline input { width: auto; margin-right: 8px; }
.searchbar label { flex: 0 0 auto; }
.sortlink { color: inherit; white-space: nowrap; }
/* form controls inside tables size to their content, not the full row width */
table.list td input, table.list td select { width: auto; }
table.list td select { max-width: 220px; }
table.list td input[type=number], table.list td input[type=date] { max-width: 110px; }
.timeline { list-style: none; padding: 0; }
.timeline li { padding: 6px 0; border-bottom: 1px solid #eef1f3; }

.login-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: var(--grey); }
.login-card { background: #fff; border-radius: 14px; padding: 36px 40px; width: 360px; }
.login-card h1 { font-size: 1.3rem; }
.login-card h1 span { color: var(--cyan); }
.login-card form { display: flex; flex-direction: column; gap: 14px; margin-top: 18px; }

/* unit selector sits with the item's actions */
.unitbox { display: flex; align-items: center; gap: 8px; }
.unitbox select { width: auto; min-width: 130px; }
/* manually-maintained sections read differently from the operator-driven ones */
.tile.manual { border-style: dashed; }
.tile.manual .tile-code { color: var(--cyan-dark); }

/* results count + previous/next */
.pager { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin: 12px 0 4px; flex-wrap: wrap; }
.pagerlinks { display: flex; gap: 8px; }

/* Shown under an item's reference where the operator publishes no commodity
   code, so a Sinewave reference is never mistaken for the operator's own
   (Adam, 3 Aug 2026). Deliberately quiet: it is a caveat, not a warning. */
.nocode { color: var(--ink-3); font-size: 0.78rem; font-style: italic; line-height: 1.3; display: inline-block; }
td.code .nocode { white-space: normal; max-width: 190px; }


/* Valuations: a % that went backwards vs prior month (value created never reduces). */
#valtable tr.valrow.backwards td { background: #fdeaea; }
#valtable tr.valrow.backwards .pctinput { outline: 2px solid var(--danger); }

/* Valuations table: 19 columns need to fit — tighter, whole pounds. */
/* The valuation view switcher: two tiles, estimate view first — it is the
   default entry surface (Adam, 11 Aug). */
.viewswitch { display: flex; gap: 10px; margin: 8px 0 14px; }
.viewswitch .vs-tile { display: flex; flex-direction: column; gap: 2px; padding: 10px 18px;
  border: 1px solid var(--line); border-radius: 10px; background: #fff;
  text-decoration: none; color: inherit; min-width: 190px; }
.viewswitch .vs-tile span { font-size: 12px; color: var(--ink-3); }
.viewswitch .vs-tile:hover { border-color: var(--cyan); }
.viewswitch .vs-tile.active { border-color: var(--cyan); box-shadow: inset 0 0 0 1px var(--cyan); background: var(--cyan-bg-soft); }

/* .valtable = the same compact grid the budget-view valuation table has, as a
   class so the estimate view matches it exactly (Adam, 11 Aug). */
#valtable, table.valtable { font-size: 11.5px; width: 100%; }
#valtable th, #valtable td, table.valtable th, table.valtable td { padding: 4px 6px; }
#valtable .pctinput, table.valtable .pctinput { width: 4.6em; }
#valtable tr.wbs-row td, table.valtable tr.wbs-row td { background: #eef2f6; border-top: 2px solid var(--line-strong); }

/* A valuation % outside 0-100 errors in the box itself. */
.pctinput.invalid { outline: 2px solid var(--danger); background: #fdeaea; }

/* Revenue recognition summary: ~20 columns, so it scrolls sideways and the three
   performance-obligation blocks are separated by a rule rather than by counting
   headers. Tighter padding keeps a month's projects on one screen height. */
table.list.valsummary td, table.list.valsummary th { padding: 7px 9px; font-size: 0.88rem; }
table.list.valsummary th.obgroup, table.list.valsummary td.obgroup { border-left: 2px solid var(--line-strong); }
table.list.valsummary thead th[colspan] { text-align: center; text-transform: none; font-size: 0.8rem; color: var(--grey); }
table.list.valsummary tfoot th { border-top: 2px solid var(--line-strong); background: #f7f9fb; color: var(--grey); text-transform: none; font-size: 0.88rem; }

/* Forecast commentary: a chip on the project row rather than a column, because most
   rows carry no note and an empty column across ninety projects is worse than none. */
.comment-pill { opacity: 0.45; text-decoration: none; }
.comment-pill.has-comment { opacity: 1; }

/* A save that was refused: nothing was written, the fields that caused it are marked, and
   what the person typed is still in the boxes. Introduced with the all-or-nothing quote
   save — before it, a bad value on line five left lines one to four saved with nothing on
   screen to say so. */
.save-refused {
  border-left: 4px solid #c0392b;
  background: #fdf3f2;
  padding: 10px 14px;
  margin: 0 0 12px;
  border-radius: 4px;
}
.field-error { color: #c0392b; margin-top: 3px; line-height: 1.3; max-width: 220px; }
input.field-bad, textarea.field-bad { border-color: #c0392b; background: #fdf3f2; }

/* An action reporting what it did. Added after "Invite each item's known suppliers" was
   reported as broken when it was working correctly — it had nothing to add and said so by
   saying nothing, which is indistinguishable from a dead button. The quiet variant is for
   "nothing changed, and here is why". */
.action-said {
  border-left: 4px solid var(--cyan);
  background: var(--cyan-bg-soft);
  padding: 9px 13px;
  margin: 10px 0 0;
  border-radius: 4px;
  max-width: 760px;
}
.action-said.quiet { border-left-color: var(--ink-3); background: #f4f6f7; }

/* ==========================================================================
   Responsive shell — every page adapts to the window it is in.
   ==========================================================================
   Added 15 Aug 2026. Before this the stylesheet had ONE media query in it, so
   the frame was effectively fixed: the top bar could not wrap, the sub-nav ran
   off the side, form rows stayed side by side at any width, and the page kept
   its desktop padding on a phone.

   Scope note, deliberate: this fixes the FRAME. A very wide table (the quote
   grid is ten columns) still widens the page rather than scrolling inside
   itself, and that is not fixed here on purpose — table.list carries
   "no overflow clipping" by design so the per-supplier popovers can overhang
   the table edge, and wrapping it in a scroll container would cut them off.
   Doing it properly means relocating those popovers first, which belongs with
   the shared list-view kit (Procore's own answer is a horizontal scroll
   indicator on the header). Until then the frame behaves and wide tables are
   the one thing that still pushes sideways. */

img, svg { max-width: 100%; height: auto; }

/* Small laptops and landscape tablets: reclaim the generous desktop padding. */
@media (max-width: 1100px) {
  main { padding: 18px 16px 48px; }
}

/* Portrait tablets and large phones: the frame stops pretending it is a desktop. */
@media (max-width: 820px) {
  body { font-size: 14px; }
  main { padding: 14px 12px 40px; }

  /* The bar wraps, and the tool links become a horizontal scroller rather than
     squashing or disappearing off the edge. */
  .topbar { flex-wrap: wrap; gap: 10px 16px; padding: 10px 12px; }
  /* Wrap rather than scroll. A handful of links hidden behind an invisible scroller is
     worse than two rows — you cannot see what you cannot see. Scrolling is kept for
     things that genuinely cannot wrap, like a wide table, and those get .scroll-x
     below so it is obvious there is more. */
  .topbar nav { order: 3; flex-basis: 100%; flex-wrap: wrap; gap: 10px 16px; }
  .userbox { margin-left: auto; }

  .subnav { flex-wrap: wrap; }

  /* A page header and its actions stack rather than fighting for one line. */
  .item-head { flex-direction: column; align-items: flex-start; gap: 10px; }
  .item-actions { width: 100%; flex-wrap: wrap; }

  /* Form rows go one per line. Without this the inputs shrink past usability. */
  .row { flex-direction: column; align-items: stretch; }
  .row > * { min-width: 0; }
  .row .btn { align-self: flex-start; }

  h1 { font-size: 1.28rem; }
  h2 { font-size: 1.05rem; }
  .card { padding: 14px; }

  /* Popovers are positioned against the desktop layout; on a narrow screen a
     fixed 240-320px box overhangs the edge. */
  .confirmbox, .confirmbox.wide { width: min(92vw, 320px); }
}

/* Phones. */
@media (max-width: 560px) {
  main { padding: 12px 10px 32px; }
  .card { padding: 12px; }
  .item-actions > * { width: 100%; }
  .item-actions .btn { width: 100%; }
}

/* --------------------------------------------------------------------------
   Scrolling that announces itself.
   --------------------------------------------------------------------------
   Reported directly: "Visually not clear the top bar scrolls." A container that
   scrolls with no cue is worse than one that clips, because nothing tells you
   there is more to see — the month tiles on project forecasting cut the FY total
   in half with no hint you could scroll to it.

   Two answers, applied deliberately:
     * Anything that CAN wrap, wraps. Navigation, tile rows, chip rows.
     * Anything that genuinely cannot wrap — a table with meaningful columns —
       scrolls inside .scroll-x, which paints a shadow at whichever edge has
       more content behind it. Pure CSS: the white gradients are anchored to the
       content (background-attachment: local) and the dark ones to the container,
       so a shadow appears only while there is something to scroll to.
   ------------------------------------------------------------------------ */
.scroll-x {
  overflow-x: auto;
  /* Matches the 10px radius of table.list inside it. Without this the wrapper stays square
     while the table's corner is rounded, so the wrapper's own white edge-gradient — the
     scroll cue — shows through the cut-away corner and the top right reads a pixel out. */
  border-radius: 10px;
  background:
    linear-gradient(to right, #fff 30%, rgba(255,255,255,0)) left center,
    linear-gradient(to left, #fff 30%, rgba(255,255,255,0)) right center,
    radial-gradient(farthest-side at 0 50%, rgba(15,30,45,0.16), rgba(15,30,45,0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(15,30,45,0.16), rgba(15,30,45,0)) right center;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

/* A panel is switched to fixed positioning only while it is open, so it escapes any
   scrolling ancestor rather than being clipped by it. Set from app.js; with JavaScript off
   the class never appears and the absolute positioning above still applies. */
.confirmbox.floating, .popover.floating { position: fixed; right: auto; top: auto; margin: 0; }

/* Several tables (the ESG ones especially) are transparent, and the scroll wrapper's edge
   gradients would show through them. */
.scroll-x > table { background: #fff; }

/* --------------------------------------------------------------------------
   From the responsive audit of all 60 views. Every rule here repairs several
   screens at once; none of them needs a view to be edited.
   ------------------------------------------------------------------------ */

/* The scroll cue belongs to anything that scrolls, not just the new wrapper. */
.esg-table-scroll, .matrix-scroll {
  background:
    linear-gradient(to right, #fff 30%, rgba(255,255,255,0)) left center,
    linear-gradient(to left, #fff 30%, rgba(255,255,255,0)) right center,
    radial-gradient(farthest-side at 0 50%, rgba(15,30,45,0.16), rgba(15,30,45,0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(15,30,45,0.16), rgba(15,30,45,0)) right center;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

/* The view switcher could not wrap, so two or three tiles at a 190px floor overflowed a
   narrowed window — one of the literal "not fitting the window" cases. */
.viewswitch { flex-wrap: wrap; }

/* .popover was missed when .confirmbox got a viewport-relative width; it kept a rigid 300px. */
.popover { width: min(92vw, 320px); }

/* Rows of controls that had no reason not to wrap. */
.searchbar, .addform, .unitbox { flex-wrap: wrap; }

/* A submenu that only opens on hover is unreachable on a touch screen: tapping the parent
   link simply navigates. On iPad that made Project forecasting, Pipeline forecasting and
   Cashflow unreachable from the navigation entirely. app.js toggles .open on tap; desktop
   hover is untouched. */
@media (hover: none) {
  .navdrop.open .navdrop-panel { display: block; }
}

@media (max-width: 560px) {
  /* Long identifiers and descriptions may go narrow rather than hold a table open. */
  .detailcell { min-width: 0; }
}

/* --------------------------------------------------------------------------
   The quote grid: an item and its suppliers must read as ONE block.
   --------------------------------------------------------------------------
   The item band was a very light tint (#eef3f6) sitting directly against white
   supplier rows, so at a glance you could not tell where one item ended and the
   next began — the item did not look like the heading of the rows beneath it.

   Three changes, together: a firmer band with a real top rule so each group
   starts visibly; the item name bigger and bold so it outranks its own sub-rows;
   and the supplier rows a step smaller with a continuous left rail, so they
   clearly hang off the item rather than sitting beside it. */

table.list.quotes tbody.linegroup { border-top: none; }

table.list.quotes tr.itemrow td {
  background: #dbe6ee;
  border-top: 2px solid #a8bfcf;
  padding: 13px 10px;
}
table.list.quotes tbody.linegroup:first-of-type tr.itemrow td { border-top: none; }
table.list.quotes tr.itemrow.row-quoted td { background: #d5ecdf; border-top-color: #9ccdb1; }
table.list.quotes tr.itemrow.row-approved td { background: #c6e6d3; border-top-color: #8bc4a3; }

/* The item name outranks everything in its group. */
table.list.quotes tr.itemrow .itemhead a {
  font-size: 1.04rem;
  font-weight: 700;
  color: var(--grey);
}
table.list.quotes tr.itemrow .itemhead a:hover { color: var(--cyan-dark); }
table.list.quotes tr.itemrow .itemhead .code { font-weight: 600; }

/* Supplier rows are subordinate: a step smaller, and joined to the item by a rail
   running down the left of the group so the block holds together visually. */
table.list.quotes tr.quoterow td, table.list.quotes tr.addrow td, table.list.quotes tr.subhead td {
  font-size: 0.83rem;
}
table.list.quotes tr.quoterow td:nth-child(2),
table.list.quotes tr.addrow td:nth-child(2),
table.list.quotes tr.subhead td:nth-child(2) {
  box-shadow: inset 2px 0 0 #cfdde7;
}
/* ...except the chosen supplier, whose cyan rail must win. */
table.list.quotes tr.quoterow.chosen td:nth-child(2) { box-shadow: inset 3px 0 0 var(--cyan); }

/* A clear gap before the next item, so groups do not run into one another. */
table.list.quotes tr.addrow td { padding-bottom: 18px; }


/* --------------------------------------------------------------------------
   Visual polish, from the review of all 60 screens against the ESG pages.
   --------------------------------------------------------------------------
   The single thing that made ESG read better: a headline figure is at least
   TWICE the size of its own caption. ESG runs 2.2x and 3.5x; the rest of the
   portal ran 1.36x, so the number was really just bold body text and the eye
   had nothing to land on. That, plus one box shape instead of three, plus
   headings that stop being top-heavy. */

/* Every card was visibly top-heavy: h2's 1.4em top margin collapsed through the
   card's own padding, giving ~43px above a heading against 18px below. One rule,
   every card on the site. */
.card > :first-child, .tile > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

/* h3 had no rule anywhere, so at browser default it rendered the same size as the
   h2 above it and the third level of hierarchy was invisible. */
h2 { font-weight: 600; }
h3 { font-size: 0.95rem; font-weight: 600; margin: 1.1em 0 0.4em; color: var(--grey); }

/* Headline figures earn their place. --cyan-text rather than --cyan: a figure is
   read, and #00AEEF at 2.53:1 is a fill colour, not a reading colour. */
.tile-code { font-size: 1.9rem; font-weight: 700; color: var(--cyan); line-height: 1.1; }
.tile-name { color: var(--ink-2); font-size: 0.85rem; margin-top: 4px; }

/* One box recipe. Three border colours, three radii and seven paddings across
   components that all present a bordered white box is why crossing from
   Procurement into Finance felt like a different application. */
.card, .tile, .viewswitch .vs-tile {
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
}
.two-col { gap: 16px; }


/* --------------------------------------------------------------------------
   One shape, one rhythm, one type scale — the last of the visual pass.
   --------------------------------------------------------------------------
   Before this the portal presented a bordered white box in seven different
   ways: three border colours, three corner radii and seven paddings. Crossing
   from Procurement into Finance visibly changed the shape of the furniture,
   which is the clearest sign it was built in three sittings rather than
   designed once. ESG was the only module with a coherent scale, so the numbers
   below are ESG's, not invented. */

.card, .tile, .viewswitch .vs-tile, .rfqpanel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.card { padding: 18px 20px; }
.tile { padding: 16px 18px; }

/* 18px between blocks, 16px inside a grid. Nothing else. */
.two-col, .tiles { gap: 16px; }
.card { margin-bottom: 18px; }

/* Type. The rule that made ESG read better than everything else: a headline
   figure is at LEAST twice its own caption. The rest of the portal ran 1.36x,
   which is why its numbers read as bold body text. */
h1 { font-size: 1.5rem; font-weight: 700; }
h2 { font-size: 1.1rem; font-weight: 600; }
.muted, .small.muted { color: var(--ink-2); }
