:root {
  --ink: #16191d;
  --muted: #6b7280;
  --line: #e3e6ea;
  --bg: #f6f7f9;
  --card: #fff;
  --accent: #1f5eff;
  --good: #0a7c42;
  --good-bg: #eaf6ef;
  --warn: #8a5a00;
  --warn-bg: #fdf4e3;
  --bad: #b3261e;
  --bad-bg: #fdeceb;

  /* The three cell states. Everything that reports status — cells, row
     pills, dashboard boxes, filters — draws from these and nothing else, so
     a colour means one thing wherever it appears. */
  --s-ok-bg: #fff;
  --s-ok-line: var(--line);
  --s-auto-bg: #fdf6e3;
  --s-auto-line: #efe0b4;
  --s-auto-ink: #7a5c05;
  --s-error-bg: #fdeeed;
  --s-error-line: #f2cecb;
  --s-error-ink: var(--bad);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

main { max-width: 960px; margin: 0 auto; padding: 24px 20px 64px; }

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 22px; margin-bottom: 18px;
}
.card.narrow { max-width: 420px; margin: 60px auto; }

h1 { font-size: 22px; margin: 0 0 6px; }
h2 { font-size: 17px; margin: 0 0 10px; }
.muted { color: var(--muted); }
.empty { color: var(--muted); padding: 18px 0; }
.hint { font-size: 13px; color: var(--muted); margin: 4px 0 0; }
.ok { color: var(--good); }
.warn { color: var(--warn); }
.bad { color: var(--bad); }
code { background: #f0f1f3; padding: 1px 5px; border-radius: 4px; font-size: 13px; }

.topline { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin: 0 0 16px; flex-wrap: wrap; }
.topline-actions { display: flex; align-items: center; gap: 10px; }
.topline-actions button { margin-top: 0; }
.icon-btn { position: relative; width: 34px; height: 34px; padding: 0;
  border-radius: 8px; background: var(--card); color: var(--muted);
  border: 1px solid var(--line); font-size: 15px; line-height: 1; }
.icon-btn:hover { color: var(--ink); border-color: var(--accent); opacity: 1; }
.icon-btn[data-tip]::after {
  content: attr(data-tip); position: absolute; right: 0; top: calc(100% + 8px);
  width: 260px; padding: 9px 12px; background: var(--ink); color: #fff;
  border-radius: 7px; font: 400 12.5px/1.5 -apple-system, BlinkMacSystemFont,
  "Segoe UI", Roboto, sans-serif; text-align: left; opacity: 0;
  visibility: hidden; transition: opacity .12s; z-index: 50;
  pointer-events: none; box-shadow: 0 4px 16px rgba(0,0,0,.18); }
.icon-btn:hover::after, .icon-btn:focus::after { opacity: 1; visibility: visible; }

/* Where the review runs but the upload does not exist yet. Says so in place of
   the button rather than letting someone reach the confirmation and bounce. */
.not-ready { position: relative; display: inline-block; padding: 8px 14px;
  border: 1px dashed var(--line); border-radius: 8px; color: var(--muted);
  font-size: 13px; cursor: default; }
.not-ready[data-tip]::after {
  content: attr(data-tip); position: absolute; right: 0; top: calc(100% + 8px);
  width: 260px; padding: 9px 12px; background: var(--ink); color: #fff;
  border-radius: 7px; font: 400 12.5px/1.5 -apple-system, BlinkMacSystemFont,
  "Segoe UI", Roboto, sans-serif; text-align: left; opacity: 0;
  visibility: hidden; transition: opacity .12s; z-index: 50;
  pointer-events: none; box-shadow: 0 4px 16px rgba(0,0,0,.18); }
.not-ready:hover::after { opacity: 1; visibility: visible; }

.steps { display: flex; gap: 8px; list-style: none; padding: 0; margin: 0;
  font-size: 13px; color: var(--muted); flex-wrap: wrap; }
.steps li { border-radius: 999px; background: var(--card);
  border: 1px solid var(--line); }
.steps a, .steps span { display: block; padding: 4px 12px; border-radius: 999px;
  color: var(--muted); text-decoration: none; }
.steps a:hover { color: var(--ink); background: #eceef1; }
.steps li.now { background: var(--ink); border-color: var(--ink); }
.steps li.now a, .steps li.now span { color: #fff; }
.steps li.now a:hover { background: none; color: #fff; }
.steps span { color: #b3b8c0; cursor: default; }

label { display: block; font-weight: 550; margin: 14px 0 5px; }
input[type=text], input[type=tel], input[type=search], select, input[type=file] {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line);
  border-radius: 7px; font: inherit; background: #fff;
}
.field { margin-bottom: 6px; }
button, .primary {
  display: inline-block; margin-top: 14px; padding: 9px 16px;
  background: var(--ink); color: #fff; border: 0; border-radius: 7px;
  font: inherit; font-weight: 550; cursor: pointer; text-decoration: none;
}
button:hover { opacity: .9; }
button:disabled { opacity: .5; cursor: default; }
button.danger { background: var(--bad); }
button.link, .link {
  background: none; color: var(--accent); padding: 0; margin-left: 12px;
  font-weight: 450; text-decoration: none;
}

.flash { padding: 10px 14px; border-radius: 8px; margin-bottom: 14px; }
.flash.error, .note.error { background: var(--bad-bg); color: var(--bad); }
.flash.ok { background: var(--good-bg); color: var(--good); }
/* Something we could not read, on a screen that still works — a stale
   master list, say. Not an error and not a success; the same yellow
   the review grid uses for a value we changed on the user's behalf. */
.flash.warn { background: var(--warn-bg); color: var(--warn); }

/* --- Waiting on Onsite --------------------------------------------------- */
/* Deliberately covering and deliberately plain. The wait is real work (a large
   party library is dozens of paginated calls), and the only wrong thing to do
   with it is let the screen look idle. */
.waiting {
  position: fixed; inset: 0; z-index: 100; cursor: progress;
  background: rgba(255, 255, 255, 0.72);
  display: flex; align-items: center; justify-content: center;
  backdrop-filter: blur(1.5px);
}
/* An author-level `display` beats the browser's `[hidden] { display: none }`,
   so it has to opt back out — otherwise the overlay covers every page. */
.waiting[hidden] { display: none; }
.waiting-card {
  display: flex; align-items: center; gap: 12px;
  background: var(--card, #fff); color: var(--ink, #222);
  border: 1px solid var(--line, #e3e3e3); border-radius: 10px;
  padding: 14px 20px; box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12);
  font-size: 14px;
}
.spinner {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 2px solid var(--line, #ddd); border-top-color: var(--accent, #2b6cb0);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}
.note { padding: 10px 14px; border-radius: 8px; margin: 10px 0; font-size: 14px; }
.note.warning, .note.warn { background: var(--warn-bg); color: var(--warn); }
.note.info { background: #eef2fb; color: #2c4a86; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { text-align: left; font-weight: 550; color: var(--muted); font-size: 12px;
  text-transform: uppercase; letter-spacing: .04em; padding: 8px 10px 8px 0;
  border-bottom: 1px solid var(--line); }
td { padding: 9px 10px 9px 0; border-bottom: 1px solid var(--line);
  vertical-align: middle; }
.scroll { overflow-x: auto; }
.preview td { white-space: nowrap; max-width: 260px; overflow: hidden;
  text-overflow: ellipsis; }


.tallies { display: flex; gap: 12px; margin: 16px 0; flex-wrap: wrap; }
.tally { flex: 1; min-width: 120px; padding: 14px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--line); }
.tally strong { display: block; font-size: 26px; line-height: 1.1; }
.tally span { font-size: 13px; color: var(--muted); }
.tally.good { background: var(--good-bg); border-color: #cbe7d7; }
.tally.bad { background: var(--bad-bg); border-color: #f3cfcd; }

.group { border: 1px solid var(--line); border-radius: 8px; padding: 14px;
  margin: 12px 0; }
.group-head { display: flex; justify-content: space-between; gap: 12px;
  align-items: baseline; flex-wrap: wrap; }
.group .query { font-size: 16px; }
.resolve { display: flex; gap: 8px; align-items: center; margin-top: 10px;
  flex-wrap: wrap; }
.resolve select { flex: 2; min-width: 260px; }
.resolve .finder { flex: 1; min-width: 160px; }
.resolve button { margin-top: 0; white-space: nowrap; }
.inline-edit { display: flex; gap: 6px; }
.inline-edit input { min-width: 130px; }
.inline-edit button { margin-top: 0; padding: 5px 10px; font-size: 13px; }
.fixes td { font-size: 13px; }

.commit-statement {
  font-size: 17px; line-height: 1.5; padding: 16px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--line); margin: 12px 0;
}
.commit-statement .company-name { border-bottom: 2px solid var(--accent); }
/* The result screen's answer, before any detail. Did it go in, yes or no —
   a tally of committed against not-sent rows made the reader do arithmetic to
   find that out, and for an all-or-nothing endpoint the arithmetic was of a
   split that cannot happen. */
.verdict { padding: 16px 18px; border-radius: 10px; margin: 4px 0 6px; }
.verdict h1 { font-size: 24px; margin: 0 0 4px; }
.verdict p { margin: 0; font-size: 14px; line-height: 1.5; }
.verdict.good { background: var(--good-bg); color: var(--good); }
.verdict.warn { background: var(--warn-bg); color: var(--warn); }
.verdict.bad  { background: var(--bad-bg);  color: var(--bad); }
.verdict.good h1, .verdict.warn h1, .verdict.bad h1 { color: inherit; }
.actions-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }

/* Which Onsite an upload is about to be written into. Loud on live, because
   that is the one that cannot be undone. */
.env-line { font-size: 13px; margin: 0 0 10px; padding: 6px 10px;
  border-radius: 6px; display: inline-block; }
.env-line.live { background: var(--bad-bg); color: var(--bad); font-weight: 600; }
.env-line.test { background: var(--bg); color: var(--muted); }

/* A column of theirs nobody has ruled on. Red, because grey read as
   "nothing to see" and the whole failure is that it looks unimportant. */
.grid th.undecided { color: var(--bad); background: var(--bad-bg); }
.grid th.undecided .sample { color: var(--bad); opacity: .85; }
.grid th.ignored { color: var(--muted); text-decoration: line-through; }

/* A cell that is fine is still openable, and should not shout about it: no
   border, no mark, just the value — until the pointer is over it. */
.grid .cell-fix.quiet { border-color: transparent; background: none; }
.grid .cell-fix.quiet:hover { border-color: var(--line); background: var(--card); }
/* What the column's values look like. Said in the header so it reads without
   opening the picker, and phrased as a guess because that is what it is. */
.grid th .suggests { display: block; font-style: normal; font-size: 11px;
  font-weight: 500; margin-top: 2px; color: var(--accent); }
.grid option.suggested { font-weight: 600; }
/* A value from the column, under its heading: "cat" says nothing,
   "painting material" is what makes it recognisable. */
.grid th .sample b { font-weight: 500; opacity: .8; }
.grid th .sample { display: block; font-style: normal; font-weight: 400;
  font-size: 11px; margin-top: 2px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; max-width: 160px; }

/* A row left out by hand. Struck through rather than hidden: it is still part
   of the file the user is looking at, and they have to be able to see what
   they took out in order to put it back. */
.grid tr.excluded td { opacity: 0.55; }
.grid tr.excluded .txt { text-decoration: line-through; }
.grid td.c-pick, .grid th.c-pick { width: 30px; text-align: center; }
/* The same outline the tree uses for a picked row, rather than a tint:
   a background would fight the yellow and red the cells already carry. */
.grid tr.picked > td:not(.c-state) { box-shadow: inset 0 0 0 1px var(--accent); }

/* How much of the list you are looking at. A picker showing 20 of 300 with
   nothing to say so reads as a list of 20. */
.finder-note { font-size: 12px; color: var(--muted); margin: 6px 2px 0; }

.facts { display: grid; grid-template-columns: 120px 1fr; gap: 6px 14px;
  margin: 14px 0; font-size: 14px; }
.facts dt { color: var(--muted); }
.facts dd { margin: 0; }
.canonical { background: #12151a; color: #d7dde5; padding: 14px;
  border-radius: 8px; overflow-x: auto; font-size: 12.5px; line-height: 1.6; }

.status { font-size: 12px; padding: 2px 9px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--line); }
.status.committed { background: var(--good-bg); color: var(--good); border-color: #cbe7d7; }
.status.failed, .status.unknown, .status.partial {
  background: var(--bad-bg); color: var(--bad); border-color: #f3cfcd; }

.companies { list-style: none; padding: 0; }
.companies li { margin-bottom: 8px; }
.company-pick { display: block; width: 100%; text-align: left; margin: 0;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  padding: 14px 16px; }
.company-pick:hover { border-color: var(--accent); }
.company-pick strong { display: block; }
.company-pick .muted { font-size: 13px; font-weight: 400; }

/* --- Shell: logo bar, sidebar, content ------------------------------------
   The company is in the top bar on every screen because uploads cannot be
   undone and posting into the wrong one is the worst realistic mistake. */
.topbar {
  display: flex; align-items: center; gap: 24px;
  padding: 10px 20px; background: var(--card);
  border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 10px;
  text-decoration: none; color: var(--ink); font-weight: 650; }
/* The mark is not square (452×430), so height alone — setting both
   squashes it, and a border-radius on a transparent round mark
   clips its own edge. */
.brand img { height: 26px; width: auto; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 16px;
  font-size: 13.5px; }
.topbar-right a { color: var(--muted); text-decoration: none; }
.topbar-right a:hover { color: var(--ink); }
.topbar-right .who { color: var(--ink); font-weight: 550; }
.env { font-size: 11px; font-weight: 650; text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 9px; border-radius: 999px;
  background: var(--warn-bg); color: var(--warn); }

.body { display: flex; align-items: flex-start; }
.body.no-chrome { justify-content: center; }
.sidebar { width: 218px; flex: none; padding: 18px 12px; position: sticky; top: 0; }
.company-switch { position: relative; margin: 0 2px 4px; }
.company-current { display: flex; align-items: center; gap: 8px; width: 100%;
  margin: 0; padding: 9px 11px; font-size: 14px; font-weight: 600;
  text-align: left; border-radius: 8px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); }
.company-current:hover { border-color: var(--accent); opacity: 1; }
.company-current span { flex: 1; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.company-current em { font-style: normal; color: var(--muted); font-size: 11px; }

/* Opens downwards, from the control. */
.company-menu { position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  z-index: 70; background: var(--card); border: 1px solid var(--line);
  border-radius: 9px; box-shadow: 0 8px 28px rgba(0,0,0,.16); padding: 6px;
  max-height: 340px; overflow-y: auto; }
.company-menu[hidden] { display: none; }
.company-menu input[type=search] { width: 100%; padding: 7px 9px;
  font-size: 13px; margin-bottom: 4px; }
.company-menu ul { list-style: none; margin: 0; padding: 0; }
.company-menu li button { display: block; width: 100%; margin: 0;
  padding: 7px 10px; text-align: left; font-size: 13.5px; font-weight: 400;
  background: none; color: var(--ink); border: 0; border-radius: 6px;
  white-space: normal; }
.company-menu li button:hover { background: #eceef1; opacity: 1; }
.company-menu li button.on { background: var(--ink); color: #fff;
  font-weight: 600; }
.side-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); margin: 16px 10px 6px; font-weight: 650; }
.side-label:first-child { margin-top: 0; }
.sidebar ul { list-style: none; margin: 0; padding: 0; }
.sidebar li { margin-bottom: 1px; }
.sidebar a, .sidebar .soon {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 10px; border-radius: 7px; text-decoration: none;
  color: var(--ink); font-size: 14px;
}
.sidebar a:hover { background: #eceef1; }
.sidebar a.on { background: var(--ink); color: #fff; font-weight: 550; }
.sidebar .soon { color: #a3a9b3; cursor: default; }
.sidebar .soon em { font-style: normal; font-size: 10px; text-transform: uppercase;
  letter-spacing: .05em; background: var(--bg); border: 1px solid var(--line);
  color: var(--muted); padding: 1px 6px; border-radius: 999px; }

/* `min-width: 0` is what makes the wide review table scroll inside its
   own box. A flex item will not shrink below its content by default, so
   a thirty-column file stretched the whole page sideways instead —
   taking the step tabs, the topbar and the Upload button off screen
   with it, which is how the button became unreachable. */
main { flex: 1; min-width: 0; max-width: 900px; padding: 22px 24px 72px; }
.body.no-chrome main { max-width: 460px; }

.login-logo { height: 48px; width: auto; margin-bottom: 10px; }
.env-line { margin: 8px 0 0; }

/* --- Review: their file as a table ----------------------------------------
   The point of this screen is that a person recognises their own spreadsheet
   and can see, at a glance, which cells we could not make sense of. Status is
   carried by colour AND a mark, never colour alone. */
.lead { font-size: 15px; color: #444b55; margin: 6px 0 14px; max-width: 62ch; }
.primary.big { font-size: 15.5px; padding: 11px 20px; }
button.secondary, .secondary { background: var(--card); color: var(--ink);
  border: 1px solid var(--line); }
.review-head { display: flex; justify-content: space-between; gap: 24px;
  align-items: flex-start; flex-wrap: wrap; }
.tallies.compact { margin: 0; gap: 8px; }
.tallies.compact .tally { min-width: 104px; padding: 10px 14px; }
.tallies.compact .tally strong { font-size: 21px; }

.table-card { padding: 16px 0 0; }
.table-controls { display: flex; gap: 16px; flex-wrap: wrap;
  padding: 0 18px 14px; }
.filters { display: flex; gap: 2px; background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px; padding: 3px; }
.filters a { padding: 5px 11px; border-radius: 6px; font-size: 13px;
  color: var(--muted); text-decoration: none; white-space: nowrap; }
.filters a.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.filters a.on[href*="filter=auto"] { background: var(--s-auto-bg);
  color: var(--s-auto-ink); }
.filters a.on[href*="filter=errors"] { background: var(--s-error-bg);
  color: var(--s-error-ink); }

.grid-scroll { overflow-x: auto; border-top: 1px solid var(--line); }
table.grid { border-collapse: separate; border-spacing: 0; font-size: 13.5px;
  min-width: 100%; }
table.grid th { padding: 8px 12px; background: var(--bg); text-align: left;
  border-bottom: 1px solid var(--line); white-space: nowrap;
  font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--ink);
  font-weight: 600; vertical-align: top; }
table.grid th em { display: block; font-style: normal; font-size: 10.5px;
  color: var(--muted); font-weight: 400; text-transform: uppercase;
  letter-spacing: .05em; margin-top: 1px; }
table.grid th.not-uploaded { color: #a3a9b3; }
table.grid th .req { color: var(--bad); margin-left: 2px; }
table.grid td { padding: 0; border-bottom: 1px solid var(--line);
  max-width: 260px; }
table.grid td .txt { display: block; padding: 8px 12px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.c-num { color: var(--muted); font-variant-numeric: tabular-nums;
  text-align: right; padding: 8px 6px 8px 14px !important; width: 1%; }
.c-state { width: 1%; padding: 0 6px !important; text-align: center; }
.pill { display: inline-block; width: 18px; height: 18px; line-height: 18px;
  border-radius: 50%; font-size: 11px; font-weight: 700; }
.pill.ok { background: #eef1f4; color: #7b828c; }
.pill.auto { background: var(--s-auto-bg); color: var(--s-auto-ink);
  border: 1px solid var(--s-auto-line); }
.pill.error { background: var(--s-error-bg); color: var(--s-error-ink);
  border: 1px solid var(--s-error-line); }

td.cell.ok { background: var(--s-ok-bg); }
td.cell.auto { background: var(--s-auto-bg); }
td.cell.error { background: var(--s-error-bg); }
td.cell.not-uploaded .txt { color: #b3b8c0; }
td.cell.edited { box-shadow: inset 3px 0 0 var(--accent); }
tr.row-error .c-num { color: var(--bad); font-weight: 600; }
td.cell.focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* What they wrote, under what we made of it. Grey, because it is context
   rather than a second value. */
td.cell .was { display: block; padding: 0 12px 6px; font-style: normal;
  font-size: 11px; color: #9aa1ab; }

.cell-fix { display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 0; padding: 0; background: none; border: 0; border-radius: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.cell-fix:hover { background: rgba(0,0,0,.04); }
.cell-fix .mark { margin-left: auto; padding-right: 10px; font-weight: 700;
  font-size: 11px; }
td.cell.error .cell-fix .mark { color: var(--s-error-ink); }
td.cell.auto .cell-fix .mark { color: var(--s-auto-ink); }

tr.row-note td { padding: 4px 12px 8px; font-size: 12.5px; background: inherit; }
tr.row-note .warning { color: var(--s-auto-ink); }
tr.row-note .error { color: var(--s-error-ink); }

.pager { display: flex; justify-content: space-between; align-items: center;
  gap: 16px; padding: 12px 18px; flex-wrap: wrap; font-size: 13.5px; }
.pager .pages { display: flex; gap: 14px; align-items: center; }
.pager a { color: var(--accent); text-decoration: none; }

.reload { display: flex; align-items: center; gap: 12px; margin: -4px 0 24px 2px; }
.reload button { margin-top: 0; }
.reload .hint { margin: 0; }

/* The fix panel — one in the page, moved beside whichever cell was clicked. */
.panel { position: absolute; z-index: 40; width: 400px; max-width: calc(100vw - 32px);
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 32px rgba(0,0,0,.16); padding: 16px 18px 18px; }
.panel-close { position: absolute; top: 8px; right: 10px; margin: 0; padding: 2px 8px;
  background: none; color: var(--muted); font-size: 18px; line-height: 1; }
.panel-kind { margin: 0; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 650; }
.panel-value { margin: 2px 0 6px; font-size: 17px; word-break: break-word; }
.panel-why { margin: 0 0 12px; font-size: 13px; color: var(--s-error-ink); }
.panel-why:empty { display: none; }
.panel .hint { margin: 10px 0 0; }
.panel .clash td { padding: 7px 12px 7px 0; border-bottom: 1px solid var(--line); }
.panel .clash tr:last-child td { border-bottom: 0; }
.panel .choices { list-style: none; margin: 0 0 10px; padding: 0;
  max-height: 220px; overflow-y: auto; }
.panel .choices li + li { margin-top: 5px; }
.panel .choices label { display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px;
  cursor: pointer; font-size: 13.5px; }
.panel .choices label:hover { border-color: var(--accent); }
.panel .choices label:has(input:checked) { border-color: var(--accent);
  background: #f5f8ff; }
.panel .choices .name { flex: 1; }
.panel .choices .none { color: var(--muted); font-size: 13px; padding: 6px 2px; }
.panel .tag { font-size: 10.5px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px; }
.panel .score { font-size: 11.5px; color: var(--good); font-weight: 600; }
.panel input[type=text], .panel input[type=search] { width: 100%; }
.panel-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-top: 10px; }
.panel-actions button { margin-top: 0; font-size: 13.5px; }

.panel .revert { margin-top: 10px; border-top: 1px solid var(--line);
  padding-top: 10px; }
.panel .revert button { margin: 0; font-size: 13px; }
.do-this { background: var(--warn-bg); color: #6d4a06; border-radius: 8px;
  padding: 11px 13px; font-size: 13.5px; margin: 0 0 10px; }
.clash td { padding: 5px 10px 5px 0; font-size: 13.5px; }

/* The review screen gets the full window — a table squeezed into a
   reading-width column means horizontal scrolling to see your own file. */
main.wide { max-width: none; padding-right: 24px; }
main.wide .grid-scroll { max-height: calc(100vh - 220px); overflow: auto; }
main.wide table.grid thead th { position: sticky; top: 0; z-index: 2; }
main.wide table.grid .c-num,
main.wide table.grid .c-state { position: sticky; z-index: 1;
  background: var(--card); }
main.wide table.grid .c-num { left: 0; }
main.wide table.grid .c-state { left: 46px; }
main.wide table.grid thead .c-num,
main.wide table.grid thead .c-state { z-index: 3; background: var(--bg); }
main.wide tr.row-error .c-num, main.wide tr.row-error .c-state { background: #fffafa; }
main.wide tr.just-fixed .c-num, main.wide tr.just-fixed .c-state { background: var(--good-bg); }

/* Mode picker — two options side by side, and an icon rather than a
   paragraph. The difference between them is one sentence, and a sentence
   does not need a third of the screen. */
.mode-row { margin: 16px 0 4px; }
.mode-row > label { display: flex; align-items: center; gap: 7px; }
.segmented { display: inline-flex; background: var(--bg); padding: 3px;
  border: 1px solid var(--line); border-radius: 8px; gap: 2px; }
.segmented label { display: flex; align-items: center; gap: 7px; margin: 0;
  padding: 7px 14px; border-radius: 6px; cursor: pointer; font-weight: 450;
  font-size: 14px; color: var(--muted); white-space: nowrap; }
.segmented label.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.segmented input { width: auto; margin: 0; }

.info { position: relative; display: inline-flex; align-items: center;
  justify-content: center; width: 15px; height: 15px; border-radius: 50%;
  background: var(--muted); color: #fff; font-size: 10.5px; font-weight: 700;
  font-style: italic; cursor: help; flex: none; }
.info:hover, .info:focus { background: var(--accent); outline: none; }
.info[data-tip]::after {
  content: attr(data-tip); position: absolute; left: 50%; top: calc(100% + 8px);
  transform: translateX(-50%); width: 300px; padding: 9px 12px;
  background: var(--ink); color: #fff; border-radius: 7px; font: 400 12.5px/1.5
  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-style: normal; text-align: left; opacity: 0; visibility: hidden;
  transition: opacity .12s; z-index: 50; pointer-events: none;
  box-shadow: 0 4px 16px rgba(0,0,0,.18); }
.info:hover::after, .info:focus::after { opacity: 1; visibility: visible; }
.field .req { color: var(--bad); margin-left: 2px; }

/* --- Review: status bar, in-table mapping, confirmation -------------------
   One box of numbers, one action. Progress is "how many of the decisions this
   file needed are settled" — a bare "2 to fix" says nothing about how far
   through you are. */
.lead { font-size: 15px; color: #444b55; margin: 6px 0 14px; max-width: 62ch; }
.primary.big { font-size: 15.5px; padding: 11px 20px; }
button.secondary, .secondary { background: var(--card); color: var(--ink);
  border: 1px solid var(--line); }
.review-head { display: flex; justify-content: space-between; gap: 24px;
  align-items: flex-start; flex-wrap: wrap; }
.tallies.compact { margin: 0; gap: 8px; }
.tallies.compact .tally { min-width: 104px; padding: 10px 14px; }
.tallies.compact .tally strong { font-size: 21px; }

.table-card { padding: 16px 0 0; }
.table-controls { display: flex; gap: 16px; flex-wrap: wrap;
  padding: 0 18px 14px; }
.filters { display: flex; gap: 2px; background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px; padding: 3px; }
.filters a { padding: 5px 11px; border-radius: 6px; font-size: 13px;
  color: var(--muted); text-decoration: none; white-space: nowrap; }
.filters a.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.filters a.on[href*="filter=auto"] { background: var(--s-auto-bg);
  color: var(--s-auto-ink); }
.filters a.on[href*="filter=errors"] { background: var(--s-error-bg);
  color: var(--s-error-ink); }

.grid-scroll { overflow-x: auto; border-top: 1px solid var(--line); }
table.grid { border-collapse: separate; border-spacing: 0; font-size: 13.5px;
  min-width: 100%; }
table.grid th { padding: 8px 12px; background: var(--bg); text-align: left;
  border-bottom: 1px solid var(--line); white-space: nowrap;
  font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--ink);
  font-weight: 600; vertical-align: top; }
table.grid th em { display: block; font-style: normal; font-size: 10.5px;
  color: var(--muted); font-weight: 400; text-transform: uppercase;
  letter-spacing: .05em; margin-top: 1px; }
table.grid th.not-uploaded { color: #a3a9b3; }
table.grid th .req { color: var(--bad); margin-left: 2px; }
table.grid td { padding: 0; border-bottom: 1px solid var(--line);
  max-width: 260px; }
table.grid td .txt { display: block; padding: 8px 12px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.c-num { color: var(--muted); font-variant-numeric: tabular-nums;
  text-align: right; padding: 8px 6px 8px 14px !important; width: 1%; }
.c-state { width: 1%; padding: 0 6px !important; text-align: center; }
.pill { display: inline-block; width: 18px; height: 18px; line-height: 18px;
  border-radius: 50%; font-size: 11px; font-weight: 700; }
.pill.ok { background: #eef1f4; color: #7b828c; }
.pill.auto { background: var(--s-auto-bg); color: var(--s-auto-ink);
  border: 1px solid var(--s-auto-line); }
.pill.error { background: var(--s-error-bg); color: var(--s-error-ink);
  border: 1px solid var(--s-error-line); }

td.cell.ok { background: var(--s-ok-bg); }
td.cell.auto { background: var(--s-auto-bg); }
td.cell.error { background: var(--s-error-bg); }
td.cell.not-uploaded .txt { color: #b3b8c0; }
td.cell.edited { box-shadow: inset 3px 0 0 var(--accent); }
tr.row-error .c-num { color: var(--bad); font-weight: 600; }
td.cell.focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* What they wrote, under what we made of it. Grey, because it is context
   rather than a second value. */
td.cell .was { display: block; padding: 0 12px 6px; font-style: normal;
  font-size: 11px; color: #9aa1ab; }

.cell-fix { display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 0; padding: 0; background: none; border: 0; border-radius: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.cell-fix:hover { background: rgba(0,0,0,.04); }
.cell-fix .mark { margin-left: auto; padding-right: 10px; font-weight: 700;
  font-size: 11px; }
td.cell.error .cell-fix .mark { color: var(--s-error-ink); }
td.cell.auto .cell-fix .mark { color: var(--s-auto-ink); }

tr.row-note td { padding: 4px 12px 8px; font-size: 12.5px; background: inherit; }
tr.row-note .warning { color: var(--s-auto-ink); }
tr.row-note .error { color: var(--s-error-ink); }

.pager { display: flex; justify-content: space-between; align-items: center;
  gap: 16px; padding: 12px 18px; flex-wrap: wrap; font-size: 13.5px; }
.pager .pages { display: flex; gap: 14px; align-items: center; }
.pager a { color: var(--accent); text-decoration: none; }

.reload { display: flex; align-items: center; gap: 12px; margin: -4px 0 24px 2px; }
.reload button { margin-top: 0; }
.reload .hint { margin: 0; }

/* The fix panel — one in the page, moved beside whichever cell was clicked. */
.panel { position: absolute; z-index: 40; width: 400px; max-width: calc(100vw - 32px);
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 32px rgba(0,0,0,.16); padding: 16px 18px 18px; }
.panel-close { position: absolute; top: 8px; right: 10px; margin: 0; padding: 2px 8px;
  background: none; color: var(--muted); font-size: 18px; line-height: 1; }
.panel-kind { margin: 0; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 650; }
.panel-value { margin: 2px 0 6px; font-size: 17px; word-break: break-word; }
.panel-why { margin: 0 0 12px; font-size: 13px; color: var(--s-error-ink); }
.panel-why:empty { display: none; }
.panel .hint { margin: 10px 0 0; }
.panel .clash td { padding: 7px 12px 7px 0; border-bottom: 1px solid var(--line); }
.panel .clash tr:last-child td { border-bottom: 0; }
.panel .choices { list-style: none; margin: 0 0 10px; padding: 0;
  max-height: 220px; overflow-y: auto; }
.panel .choices li + li { margin-top: 5px; }
.panel .choices label { display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px;
  cursor: pointer; font-size: 13.5px; }
.panel .choices label:hover { border-color: var(--accent); }
.panel .choices label:has(input:checked) { border-color: var(--accent);
  background: #f5f8ff; }
.panel .choices .name { flex: 1; }
.panel .choices .none { color: var(--muted); font-size: 13px; padding: 6px 2px; }
.panel .tag { font-size: 10.5px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px; }
.panel .score { font-size: 11.5px; color: var(--good); font-weight: 600; }
.panel input[type=text], .panel input[type=search] { width: 100%; }
.panel-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-top: 10px; }
.panel-actions button { margin-top: 0; font-size: 13.5px; }

.panel .revert { margin-top: 10px; border-top: 1px solid var(--line);
  padding-top: 10px; }
.panel .revert button { margin: 0; font-size: 13px; }
.do-this { background: var(--warn-bg); color: #6d4a06; border-radius: 8px;
  padding: 11px 13px; font-size: 13.5px; margin: 0 0 10px; }
.clash td { padding: 5px 10px 5px 0; font-size: 13.5px; }

/* The review screen gets the full window — a table squeezed into a
   reading-width column means horizontal scrolling to see your own file. */
main.wide { max-width: none; padding-right: 24px; }
main.wide .grid-scroll { max-height: calc(100vh - 220px); overflow: auto; }
main.wide table.grid thead th { position: sticky; top: 0; z-index: 2; }
main.wide table.grid .c-num,
main.wide table.grid .c-state { position: sticky; z-index: 1;
  background: var(--card); }
main.wide table.grid .c-num { left: 0; }
main.wide table.grid .c-state { left: 46px; }
main.wide table.grid thead .c-num,
main.wide table.grid thead .c-state { z-index: 3; background: var(--bg); }
main.wide tr.row-error .c-num, main.wide tr.row-error .c-state { background: #fffafa; }
main.wide tr.just-fixed .c-num, main.wide tr.just-fixed .c-state { background: var(--good-bg); }

/* Mode picker — two options side by side, and an icon rather than a
   paragraph. The difference between them is one sentence, and a sentence
   does not need a third of the screen. */
.mode-row { margin: 16px 0 4px; }
.mode-row > label { display: flex; align-items: center; gap: 7px; }
.segmented { display: inline-flex; background: var(--bg); padding: 3px;
  border: 1px solid var(--line); border-radius: 8px; gap: 2px; }
.segmented label { display: flex; align-items: center; gap: 7px; margin: 0;
  padding: 7px 14px; border-radius: 6px; cursor: pointer; font-weight: 450;
  font-size: 14px; color: var(--muted); white-space: nowrap; }
.segmented label.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.segmented input { width: auto; margin: 0; }

.info { position: relative; display: inline-flex; align-items: center;
  justify-content: center; width: 15px; height: 15px; border-radius: 50%;
  background: var(--muted); color: #fff; font-size: 10.5px; font-weight: 700;
  font-style: italic; cursor: help; flex: none; }
.info:hover, .info:focus { background: var(--accent); outline: none; }
.info[data-tip]::after {
  content: attr(data-tip); position: absolute; left: 50%; top: calc(100% + 8px);
  transform: translateX(-50%); width: 300px; padding: 9px 12px;
  background: var(--ink); color: #fff; border-radius: 7px; font: 400 12.5px/1.5
  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-style: normal; text-align: left; opacity: 0; visibility: hidden;
  transition: opacity .12s; z-index: 50; pointer-events: none;
  box-shadow: 0 4px 16px rgba(0,0,0,.18); }
.info:hover::after, .info:focus::after { opacity: 1; visibility: visible; }
.field .req { color: var(--bad); margin-left: 2px; }

/* --- Review: status bar, in-table mapping, confirmation -------------------
   One box of numbers, one action. Progress is "how many of the decisions this
   file needed are settled" — a bare "2 to fix" says nothing about how far
   through you are. */
.action-bar { display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; padding: 14px 20px; }
.action-bar p { margin: 0; font-size: 13.5px; max-width: 62ch; }
.action-bar a { color: var(--accent); text-decoration: none; white-space: nowrap;
  margin-left: 10px; }
.action-bar button { margin-top: 0; }

.lead { font-size: 15px; color: #444b55; margin: 6px 0 14px; max-width: 62ch; }
.primary.big { font-size: 15.5px; padding: 11px 20px; }
button.secondary, .secondary { background: var(--card); color: var(--ink);
  border: 1px solid var(--line); }
.review-head { display: flex; justify-content: space-between; gap: 24px;
  align-items: flex-start; flex-wrap: wrap; }
.tallies.compact { margin: 0; gap: 8px; }
.tallies.compact .tally { min-width: 104px; padding: 10px 14px; }
.tallies.compact .tally strong { font-size: 21px; }

.table-card { padding: 16px 0 0; }
.table-controls { display: flex; gap: 16px; flex-wrap: wrap;
  padding: 0 18px 14px; }
.filters { display: flex; gap: 2px; background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px; padding: 3px; }
.filters a { padding: 5px 11px; border-radius: 6px; font-size: 13px;
  color: var(--muted); text-decoration: none; white-space: nowrap; }
.filters a.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.filters a.on[href*="filter=auto"] { background: var(--s-auto-bg);
  color: var(--s-auto-ink); }
.filters a.on[href*="filter=errors"] { background: var(--s-error-bg);
  color: var(--s-error-ink); }

.grid-scroll { overflow-x: auto; border-top: 1px solid var(--line); }
table.grid { border-collapse: separate; border-spacing: 0; font-size: 13.5px;
  min-width: 100%; }
table.grid th { padding: 8px 12px; background: var(--bg); text-align: left;
  border-bottom: 1px solid var(--line); white-space: nowrap;
  font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--ink);
  font-weight: 600; vertical-align: top; }
table.grid th em { display: block; font-style: normal; font-size: 10.5px;
  color: var(--muted); font-weight: 400; text-transform: uppercase;
  letter-spacing: .05em; margin-top: 1px; }
table.grid th.not-uploaded { color: #a3a9b3; }
table.grid th .req { color: var(--bad); margin-left: 2px; }
table.grid td { padding: 0; border-bottom: 1px solid var(--line);
  max-width: 260px; }
table.grid td .txt { display: block; padding: 8px 12px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.c-num { color: var(--muted); font-variant-numeric: tabular-nums;
  text-align: right; padding: 8px 6px 8px 14px !important; width: 1%; }
.c-state { width: 1%; padding: 0 6px !important; text-align: center; }
.pill { display: inline-block; width: 18px; height: 18px; line-height: 18px;
  border-radius: 50%; font-size: 11px; font-weight: 700; }
.pill.ok { background: #eef1f4; color: #7b828c; }
.pill.auto { background: var(--s-auto-bg); color: var(--s-auto-ink);
  border: 1px solid var(--s-auto-line); }
.pill.error { background: var(--s-error-bg); color: var(--s-error-ink);
  border: 1px solid var(--s-error-line); }

td.cell.ok { background: var(--s-ok-bg); }
td.cell.auto { background: var(--s-auto-bg); }
td.cell.error { background: var(--s-error-bg); }
td.cell.not-uploaded .txt { color: #b3b8c0; }
td.cell.edited { box-shadow: inset 3px 0 0 var(--accent); }
tr.row-error .c-num { color: var(--bad); font-weight: 600; }
td.cell.focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* What they wrote, under what we made of it. Grey, because it is context
   rather than a second value. */
td.cell .was { display: block; padding: 0 12px 6px; font-style: normal;
  font-size: 11px; color: #9aa1ab; }

.cell-fix { display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 0; padding: 0; background: none; border: 0; border-radius: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.cell-fix:hover { background: rgba(0,0,0,.04); }
.cell-fix .mark { margin-left: auto; padding-right: 10px; font-weight: 700;
  font-size: 11px; }
td.cell.error .cell-fix .mark { color: var(--s-error-ink); }
td.cell.auto .cell-fix .mark { color: var(--s-auto-ink); }

tr.row-note td { padding: 4px 12px 8px; font-size: 12.5px; background: inherit; }
tr.row-note .warning { color: var(--s-auto-ink); }
tr.row-note .error { color: var(--s-error-ink); }

.pager { display: flex; justify-content: space-between; align-items: center;
  gap: 16px; padding: 12px 18px; flex-wrap: wrap; font-size: 13.5px; }
.pager .pages { display: flex; gap: 14px; align-items: center; }
.pager a { color: var(--accent); text-decoration: none; }

.reload { display: flex; align-items: center; gap: 12px; margin: -4px 0 24px 2px; }
.reload button { margin-top: 0; }
.reload .hint { margin: 0; }

/* The fix panel — one in the page, moved beside whichever cell was clicked. */
.panel { position: absolute; z-index: 40; width: 400px; max-width: calc(100vw - 32px);
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 32px rgba(0,0,0,.16); padding: 16px 18px 18px; }
.panel-close { position: absolute; top: 8px; right: 10px; margin: 0; padding: 2px 8px;
  background: none; color: var(--muted); font-size: 18px; line-height: 1; }
.panel-kind { margin: 0; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 650; }
.panel-value { margin: 2px 0 6px; font-size: 17px; word-break: break-word; }
.panel-why { margin: 0 0 12px; font-size: 13px; color: var(--s-error-ink); }
.panel-why:empty { display: none; }
.panel .hint { margin: 10px 0 0; }
.panel .clash td { padding: 7px 12px 7px 0; border-bottom: 1px solid var(--line); }
.panel .clash tr:last-child td { border-bottom: 0; }
.panel .choices { list-style: none; margin: 0 0 10px; padding: 0;
  max-height: 220px; overflow-y: auto; }
.panel .choices li + li { margin-top: 5px; }
.panel .choices label { display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px;
  cursor: pointer; font-size: 13.5px; }
.panel .choices label:hover { border-color: var(--accent); }
.panel .choices label:has(input:checked) { border-color: var(--accent);
  background: #f5f8ff; }
.panel .choices .name { flex: 1; }
.panel .choices .none { color: var(--muted); font-size: 13px; padding: 6px 2px; }
.panel .tag { font-size: 10.5px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px; }
.panel .score { font-size: 11.5px; color: var(--good); font-weight: 600; }
.panel input[type=text], .panel input[type=search] { width: 100%; }
.panel-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-top: 10px; }
.panel-actions button { margin-top: 0; font-size: 13.5px; }

.panel .revert { margin-top: 10px; border-top: 1px solid var(--line);
  padding-top: 10px; }
.panel .revert button { margin: 0; font-size: 13px; }
.do-this { background: var(--warn-bg); color: #6d4a06; border-radius: 8px;
  padding: 11px 13px; font-size: 13.5px; margin: 0 0 10px; }
.clash td { padding: 5px 10px 5px 0; font-size: 13.5px; }

/* The review screen gets the full window — a table squeezed into a
   reading-width column means horizontal scrolling to see your own file. */
main.wide { max-width: none; padding-right: 24px; }
main.wide .grid-scroll { max-height: calc(100vh - 220px); overflow: auto; }
main.wide table.grid thead th { position: sticky; top: 0; z-index: 2; }
main.wide table.grid .c-num,
main.wide table.grid .c-state { position: sticky; z-index: 1;
  background: var(--card); }
main.wide table.grid .c-num { left: 0; }
main.wide table.grid .c-state { left: 46px; }
main.wide table.grid thead .c-num,
main.wide table.grid thead .c-state { z-index: 3; background: var(--bg); }
main.wide tr.row-error .c-num, main.wide tr.row-error .c-state { background: #fffafa; }
main.wide tr.just-fixed .c-num, main.wide tr.just-fixed .c-state { background: var(--good-bg); }

/* Mode picker — two options side by side, and an icon rather than a
   paragraph. The difference between them is one sentence, and a sentence
   does not need a third of the screen. */
.mode-row { margin: 16px 0 4px; }
.mode-row > label { display: flex; align-items: center; gap: 7px; }
.segmented { display: inline-flex; background: var(--bg); padding: 3px;
  border: 1px solid var(--line); border-radius: 8px; gap: 2px; }
.segmented label { display: flex; align-items: center; gap: 7px; margin: 0;
  padding: 7px 14px; border-radius: 6px; cursor: pointer; font-weight: 450;
  font-size: 14px; color: var(--muted); white-space: nowrap; }
.segmented label.on { background: var(--card); color: var(--ink);
  font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.segmented input { width: auto; margin: 0; }

.info { position: relative; display: inline-flex; align-items: center;
  justify-content: center; width: 15px; height: 15px; border-radius: 50%;
  background: var(--muted); color: #fff; font-size: 10.5px; font-weight: 700;
  font-style: italic; cursor: help; flex: none; }
.info:hover, .info:focus { background: var(--accent); outline: none; }
.info[data-tip]::after {
  content: attr(data-tip); position: absolute; left: 50%; top: calc(100% + 8px);
  transform: translateX(-50%); width: 300px; padding: 9px 12px;
  background: var(--ink); color: #fff; border-radius: 7px; font: 400 12.5px/1.5
  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-style: normal; text-align: left; opacity: 0; visibility: hidden;
  transition: opacity .12s; z-index: 50; pointer-events: none;
  box-shadow: 0 4px 16px rgba(0,0,0,.18); }
.info:hover::after, .info:focus::after { opacity: 1; visibility: visible; }
.field .req { color: var(--bad); margin-left: 2px; }

/* --- Review: status bar, in-table mapping, confirmation -------------------
   One box of numbers, one action. Progress is "how many of the decisions this
   file needed are settled" — a bare "2 to fix" says nothing about how far
   through you are. */
.status-bar { display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap; padding: 16px 20px; }
.status-counts { display: flex; gap: 10px; }
.stat { min-width: 96px; padding: 10px 14px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--line); }
.stat strong { display: block; font-size: 22px; line-height: 1.15; }
.stat .of { font-size: 14px; color: var(--muted); font-weight: 500; }
.stat span { font-size: 12.5px; color: var(--muted); }
.stat.ok { background: var(--s-ok-bg); border-color: var(--s-ok-line); }
.stat.auto { background: var(--s-auto-bg); border-color: var(--s-auto-line); }
.stat.auto strong { color: var(--s-auto-ink); }
.stat.error { background: var(--s-error-bg); border-color: var(--s-error-line); }
.stat.error strong { color: var(--s-error-ink); }
.stat em { display: block; font-style: normal; font-size: 11px;
  color: var(--muted); margin-top: 2px; }
.status-action { display: flex; align-items: center; gap: 16px; margin-left: auto;
  flex-wrap: wrap; justify-content: flex-end; }
.status-action p { margin: 0; font-size: 13px; max-width: 34ch; }
.status-action a { color: var(--accent); text-decoration: none; white-space: nowrap; }
.status-action button { margin-top: 0; }
.lead { font-size: 15px; color: #444b55; margin: 6px 0 14px; max-width: 62ch; }
.primary.big { font-size: 15.5px; padding: 11px 20px; }
button.secondary, .secondary { background: var(--card); color: var(--ink);
  border: 1px solid var(--line); }

/* Mapping lives in the header, under each column's own name. */
tr.map-row th { padding: 0 8px 8px; background: var(--bg); font-weight: 400;
  border-bottom: 1px solid var(--line); }
tr.map-row em { font-style: normal; font-size: 10.5px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em; }
.map-pick { width: 100%; min-width: 132px; padding: 4px 7px; font-size: 12px;
  border-radius: 6px; border: 1px solid var(--line); background: var(--card);
  color: var(--muted); }
.map-pick:hover { border-color: var(--accent); color: var(--ink); }
.map-pick:not([value=""]) { color: var(--ink); }

.drop-form { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.drop-form input[type=file] { flex: 1; min-width: 260px; }
.drop-form button { margin-top: 0; }

.modal { position: fixed; inset: 0; background: rgba(16,19,23,.45);
  display: flex; align-items: center; justify-content: center; z-index: 60;
  padding: 20px; }
/* An author-level `display` beats the browser's `[hidden] { display: none }`,
   so anything given a display value has to opt back in explicitly or the
   hidden attribute silently does nothing. */
.modal[hidden] { display: none; }
.modal-card { background: var(--card); border-radius: 12px; padding: 24px;
  width: 520px; max-width: 100%; max-height: 90vh; overflow-y: auto;
  box-shadow: 0 12px 48px rgba(0,0,0,.28); }
.modal-card.wide { width: 940px; }
.modal-card h2 { font-size: 19px; margin: 0 0 10px; }
.modal-card .note { font-size: 13.5px; }
.commit-statement { font-size: 15.5px; line-height: 1.55; padding: 14px;
  border-radius: 8px; background: var(--bg); border: 1px solid var(--line);
  margin: 0 0 12px; }
.modal-actions { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.modal-actions button { margin-top: 0; }

/* A field with nothing feeding it. The whole column is empty, which is the
   point of ordering the table by Onsite's fields rather than by theirs — laid
   out their way there is no column for it to be missing from. */
/* Nothing feeds this field. Red only when the field is required — an empty
   optional column is an ordinary thing for a file to do. */
table.grid th.missing { color: var(--muted); }
table.grid th.missing-required,
table.grid tr.map-row th.missing-required { background: var(--s-error-bg);
  color: var(--s-error-ink); }
table.grid th.spacer, table.grid td.spacer { border-left: 2px solid var(--line); }
tr.map-row th em { font-style: normal; font-size: 10.5px; color: #b3b8c0;
  text-transform: uppercase; letter-spacing: .05em; }

/* --- The BOQ outline -------------------------------------------------------
   Not a table. A BOQ line is a description with a few numbers attached, and a
   description runs to 300 characters — pouring that into a fixed-width column
   beside eight short numeric ones gave clipped text, a 1400px minimum width
   and a page you had to scroll sideways to read. The description takes the
   whole line; the numbers sit under it on a strip, aligned down the page so a
   rate is still scannable.
*/
.tree-controls { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; padding: 10px 14px; }
.tree-tools { display: flex; gap: 6px; align-items: center; }
.ghost { background: none; border: 1px solid var(--line); color: var(--muted);
  border-radius: 7px; padding: 5px 10px; font-size: 12.5px; text-decoration: none;
  display: inline-block; }
.ghost:hover { color: var(--ink); border-color: var(--accent); }
.ghost.on { background: var(--ink); border-color: var(--ink); color: #fff; }

.outline { border-top: 1px solid var(--line); }

/* One line of the BOQ, as a grid.

   The numbers *are* tabular and belong in tracks — a rate has to line up with
   the rate above it, and a flex strip with minimum widths cannot promise that,
   because a longer value simply pushes the next one along. Only the
   description is not tabular, so it gets a wide track and everything that
   belongs to it — tags, notes, warnings — sits beneath it in the same track.

   That is the whole shape: columns for what is short, one wide column for
   prose, and prose's overflow underneath rather than beside. */
.line { display: grid;
  grid-template-columns:
    56px                 /* select and status, in the margin */
    96px                 /* the serial we generated */
    minmax(240px, 1fr)   /* the description */
    104px                /* unit */
    92px                 /* quantity */
    108px                /* rate */
    128px                /* amount, ours */
    118px;               /* amount, theirs */
  align-items: start; gap: 2px 8px; padding: 9px 14px 10px;
  border-bottom: 1px solid var(--line); }
.line:last-child { border-bottom: 0; }
/* Beneath the description, sharing its width and everything to its right. */
.line > .extras { grid-column: 3 / -1; }
.line > .theirs-rows { grid-column: 3 / -1; }

.line.head, .line.foot { background: var(--bg); font-size: 12px;
  font-weight: 600; color: var(--ink); }
.line.head em, .line.foot em { display: block; font-style: normal;
  font-size: 10.5px; color: var(--muted); font-weight: 400;
  text-transform: uppercase; letter-spacing: .05em; }
.line.head .unit, .line.head .num { color: var(--muted);
  text-transform: uppercase; font-size: 10.5px; letter-spacing: .05em; }
.line.foot { border-top: 2px solid var(--line); border-bottom: 0;
  font-weight: 400; color: var(--muted); }

.gutter { display: flex; align-items: center; gap: 6px; padding-top: 2px; }
.serial { color: var(--muted); font-size: 12.5px; white-space: nowrap;
  font-variant-numeric: tabular-nums; padding-top: 3px; }
.serial[data-tip] { cursor: help; }
.twisty { background: none; border: 0; color: var(--muted); font-size: 11px;
  width: 14px; padding: 0; cursor: pointer; }
.twisty[aria-expanded="false"] { transform: rotate(-90deg); display: inline-block; }
.twisty-gap { display: inline-block; width: 14px; }

/* Two lines of whatever width the line actually has. The browser measures;
   a character count cannot, and cut every description short of its own line. */
.desc { font-size: 13.5px; display: -webkit-box; -webkit-line-clamp: 2;
  line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  border-radius: 5px; padding-left: calc(var(--d, 0) * 18px);
  min-width: 0; }
.desc.edit { cursor: text; }
.desc.edit:hover { background: #f4f6f8; }
.line.group .desc, .line.section .desc { font-weight: 400; }
.line.item .desc { font-weight: 600; color: #0f5132; }
.line.group { background: #eceff3; }
.line.section { background: #f7f8fa; }
.line.just-fixed { background: var(--good-bg); }

.unit, .num { min-width: 0; }
.num { text-align: right; }
.amount { text-align: right; font-variant-numeric: tabular-nums;
  font-size: 13.5px; padding-top: 1px; }
/* Their own figure is no longer a column of its own — it is identical to ours
   on every line that is right, so the column earned its width only on the rare
   line where it differed. Red says the two disagree; the figure is on hover. */
.amount.mismatch { color: var(--bad); font-weight: 600; }

/* Everything that belongs to the line but is not one of its numbers. */
.extras { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px;
  margin-top: 5px; font-size: 12.5px; color: var(--muted);
  padding-left: calc(var(--d, 0) * 18px); }
.note-text { color: var(--muted); font-size: 12px; max-width: 460px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: help; }

/* A value. Red is what Onsite will refuse, yellow is what we changed from the
   file — on the value, not on the line, or you know a line is wrong without
   knowing which part of it. */
.v { display: block; padding: 1px 5px; border-radius: 4px; color: var(--ink);
  font-variant-numeric: tabular-nums; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.extras .v { display: inline-block; }
.v.edit { cursor: text; }
.v.edit:hover { background: #eef1f4; box-shadow: inset 0 0 0 1px var(--line); }
.v.auto { background: var(--s-auto-bg); color: var(--s-auto-ink);
  box-shadow: inset 0 0 0 1px var(--s-auto-line); }
.v.error { background: var(--s-error-bg); color: var(--s-error-ink);
  font-weight: 600; box-shadow: inset 0 0 0 1px var(--s-error-line); }
.editing { background: #fff !important; outline: 2px solid var(--accent);
  outline-offset: 1px; box-shadow: none !important; -webkit-line-clamp: unset;
  white-space: normal; overflow: visible; }

/* The unit is chosen, not typed: Onsite refuses the whole file over one it
   cannot match, and offers its list only after the upload has failed. It fills
   its track and no more — left to size itself it took the description's room. */
.u { width: 100%; max-width: 100%; font: inherit; font-size: 12.5px;
  color: var(--ink); padding: 1px 4px; border: 1px solid transparent;
  border-radius: 4px; background: transparent; }
.u:hover { border-color: var(--line); background: #fff; }
.u.error { background: var(--s-error-bg); color: var(--s-error-ink);
  font-weight: 600; border-color: var(--s-error-line); }
.u.auto { background: var(--s-auto-bg); color: var(--s-auto-ink);
  border-color: var(--s-auto-line); }

.tag { padding: 1px 7px; border-radius: 4px; background: #eef1f4; color: #7b828c;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
  white-space: nowrap; }
.tag[data-full] { cursor: help; }
.tag.warn { background: var(--s-auto-bg); color: var(--s-auto-ink);
  border: 1px solid var(--s-auto-line); }
.tag.bad { background: var(--s-error-bg); color: var(--s-error-ink);
  border: 1px solid var(--s-error-line); text-transform: none;
  letter-spacing: 0; font-size: 11.5px; white-space: normal; }

/* Their rows, under ours, only when asked for. */
.theirs-rows { grid-column: 3 / -1; margin-top: 8px; padding: 8px 10px;
  border-left: 2px solid var(--line); background: #fbfcfd; border-radius: 0 6px 6px 0; }
.their-row { font-size: 11.5px; color: var(--muted); display: flex;
  flex-wrap: wrap; gap: 2px 12px; padding: 2px 0; }
.their-row b { color: #a3a9b3; font-weight: 400; min-width: 26px;
  font-variant-numeric: tabular-nums; }
.their-row i { font-style: normal; color: #b3b8c0; margin-right: 4px; }

/* Which of their columns feeds what. Out of the way until wanted — it is
   checked once and then not thought about again. */
.mapping { border-bottom: 1px solid var(--line); }
.mapping summary { padding: 11px 14px; cursor: pointer; font-size: 13px;
  color: var(--muted); }
.mapping summary b { color: var(--ink); }
.mapping .cols { display: inline-block; padding: 2px 8px; margin-right: 8px;
  border: 1px solid var(--line); border-radius: 6px; font-size: 12px; }
.map-body { display: flex; flex-wrap: wrap; gap: 10px 18px; padding: 4px 14px 14px; }
.map-one { display: flex; flex-direction: column; gap: 3px; font-size: 12px;
  color: var(--muted); }
.map-one > span { font-weight: 500; color: var(--ink); }
.map-one i { font-style: normal; color: var(--muted); font-weight: 400; }
.map-one .req { color: var(--bad); }
.map-one.missing-required > span { color: var(--bad); }
.map-pick, .map-pick-col { font: inherit; font-size: 12.5px; padding: 4px 6px;
  border: 1px solid var(--line); border-radius: 6px; background: #fff;
  max-width: 210px; }
.unused-note { flex-basis: 100%; margin: 4px 0 0; font-size: 12.5px;
  color: var(--muted); display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap; }
.unused-col { display: inline-flex; align-items: center; gap: 6px;
  background: #fff; border: 1px solid var(--line); border-radius: 7px;
  padding: 3px 4px 3px 9px; font-size: 12.5px; }

/* Their file, beside ours. Real columns of the same table, so the headings sit
   over the values and the halves cannot drift apart however long a description
   runs — nothing to scroll-sync and get wrong. Rendered only when asked for,
   so there is no display toggle here. */
table.grid.compare-on td.compare, table.grid.compare-on th.compare {
  background: #fcfcfd; vertical-align: top; font-size: 12px;
  color: var(--muted); max-width: 240px; }
table.grid.compare-on .c-theirs { border-left: 2px solid var(--line); }
table.grid.compare-on th.c-theirs { border-left: 2px solid var(--line); }
/* One line per row of their file that went into this line, stacked in place so
   a merge is visible as a merge. */
table.grid.compare-on td.compare span { display: block; padding: 4px 10px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
table.grid.compare-on td.c-theirs span { text-align: right; color: #a3a9b3;
  font-variant-numeric: tabular-nums; }

/* Columns of theirs feeding nothing. Named rather than shown, because the
   outline is what we are about to send — but named loudly, since a column we
   failed to recognise is otherwise invisible. */
.note.unused { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.unused-col { display: inline-flex; align-items: center; gap: 6px;
  background: var(--card); border: 1px solid var(--line); border-radius: 7px;
  padding: 3px 4px 3px 9px; font-size: 12.5px; }
.unused-col select { font-size: 12px; max-width: 150px; }

/* Selecting lines to restructure. The bar only exists while something is
   selected — a permanent toolbar of actions that need a selection reads as
   broken every time you look at it without one. */
.tree tr.picked > td:not(.c-state) { box-shadow: inset 0 0 0 1px var(--accent); }
/* Floating, not in the flow: appearing in the flow pushed the whole table
   down the moment you ticked something, so the row you had just picked moved
   out from under the pointer. */
.selection-bar { position: fixed; bottom: 22px; left: 50%;
  transform: translateX(-50%); z-index: 60; display: flex;
  align-items: center; gap: 8px; flex-wrap: wrap; max-width: calc(100vw - 60px);
  padding: 9px 14px; border-radius: 10px; background: var(--ink); color: #fff;
  box-shadow: 0 8px 30px rgba(0,0,0,.3); font-size: 13px; }
.selection-bar[hidden] { display: none; }
.selection-bar .count { color: #fff; }
.selection-bar .label { color: #b9bfc9; font-size: 12.5px; }
.selection-bar .sep { width: 1px; height: 18px; background: #3a4049; }
.selection-bar button { background: #2b313a; border: 1px solid #3f4650;
  color: #fff; border-radius: 7px; padding: 5px 10px; font-size: 12.5px; }
.selection-bar button:hover { background: #39414c; border-color: #556; }
.selection-bar button.clear { background: none; border-color: transparent;
  color: #b9bfc9; margin-left: auto; }

/* The rest of a clamped cell. Dark, roomy and immediate — a BOQ description is
   two or three sentences and needs to be read, not squinted at. */
.tip { position: fixed; z-index: 100; max-width: 520px; padding: 10px 13px;
  border-radius: 8px; background: var(--ink); color: #fff; font-size: 12.5px;
  line-height: 1.55; box-shadow: 0 8px 30px rgba(0,0,0,.28);
  pointer-events: none; white-space: pre-wrap; }
.tip[hidden] { display: none; }
[data-full] { cursor: help; }

/* Where a BOQ is going, beside the steps rather than buried in the table.
   Not a "BOQ name" — Onsite has no such object; a BOQ is the billing
   activities of a work order, and that is what gets chosen. */
/* Side by side: project then BOQ, left to right in the order they are chosen.
   Wrapping put one under the other and made them look unrelated. */
.target { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  font-size: 13px; margin-left: 18px; min-width: 0; }
.target select { flex: 0 1 auto; min-width: 0; }
.target select:disabled { background: var(--bg); color: #b3b8c0; }
.target select { font: inherit; font-size: 12.5px; padding: 5px 8px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
  max-width: 260px; color: var(--ink); }
.target.unset select[name="workorder_id"] { border-color: var(--s-auto-line);
  background: var(--s-auto-bg); }
.target .why { font-size: 12px; color: var(--muted); }
.target .why.bad { color: var(--bad); }
.target .why.ok { color: var(--good); }

/* Units Onsite will refuse, grouped by what the file says. Fifty lines saying
   MT is one decision, not fifty — and remembering it means the next file from
   this client needs no decision at all. */
.unit-fixes { padding: 12px 14px; border-bottom: 1px solid var(--line);
  background: var(--s-error-bg); }
.unit-fixes .lead { margin: 0 0 9px; font-size: 13px; color: var(--ink); }
.unit-fix { display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: 12.5px; }
.unit-fix .was { font-weight: 600; color: var(--bad); min-width: 84px;
  background: #fff; border: 1px solid var(--s-error-line); border-radius: 5px;
  padding: 2px 8px; }
.unit-fix .count { color: var(--muted); min-width: 66px; }
.unit-fix .arrow { color: var(--muted); }
.unit-fix select { font: inherit; font-size: 12.5px; padding: 4px 7px;
  border: 1px solid var(--line); border-radius: 6px; background: #fff;
  min-width: 130px; }
.unit-fix .remember { color: var(--muted); display: flex; align-items: center;
  gap: 4px; cursor: help; }
.unit-fix button { font: inherit; font-size: 12.5px; padding: 4px 12px;
  border: 1px solid var(--ink); border-radius: 6px; background: var(--ink);
  color: #fff; }

/* A line left out of the upload. Still here, still restorable — the file is
   untouched and Onsite cannot delete what has already been sent. */
.line.excluded > * { opacity: .5; }
.line.excluded .desc { text-decoration: line-through; }
.line.excluded .extras { opacity: 1; }
.tag.out { background: #eceff3; color: #6b7280; border: 1px solid var(--line); }
.linkish { background: none; border: 0; color: var(--accent); font: inherit;
  font-size: 12px; padding: 0 2px; cursor: pointer; text-decoration: underline; }
.selection-bar button.danger { background: #4a2320; border-color: #6b3430; }
.selection-bar button.danger:hover { background: #5d2b27; }

/* Openers for the panels that used to sit above the outline. Their counts ride
   on the button, so the problems are visible without opening anything. */
.topline-actions .ghost { display: inline-flex; align-items: center; gap: 6px; }
.topline-actions .ghost b { color: var(--ink); font-variant-numeric: tabular-nums; }
.topline-actions .ghost em { font-style: normal; font-size: 11px;
  color: var(--muted); }
.topline-actions .ghost.bad { border-color: var(--s-error-line);
  background: var(--s-error-bg); color: var(--bad); }
.topline-actions .ghost.bad b { color: var(--bad); }

/* Inside the dialog the mapping pickers get room to lay out in a grid rather
   than a wrapped row. */
.modal-card .map-body { display: grid; padding: 0;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px 18px; }
.modal-card .unused-note { grid-column: 1 / -1; }
.modal-card .unit-fix { margin-top: 8px; }
.modal-card .unit-fix select { flex: 1 1 auto; min-width: 160px; }

/* --- The wide table: what stays put while you scroll ----------------------
   Appended deliberately at the end. The review rules above exist in three
   near-identical copies from earlier edits, and the last one wins — so a fix
   written into one of them may or may not take effect depending which. This
   block is last, and therefore certain.

   Two things were wrong. Sticking every `th` pinned both header rows to the
   same line, so the mapping pickers sat on top of the column names; sticking
   the `thead` instead keeps the pair together. And the frozen left-hand
   columns still assumed the layout from before the select-all checkbox was
   added, so they overlapped each other once a wide file was scrolled
   sideways. */
main.wide table.grid thead { position: sticky; top: 0; z-index: 3; }
main.wide table.grid thead th { position: static; }

main.wide table.grid .c-pick,
main.wide table.grid .c-num,
main.wide table.grid .c-state {
  position: sticky; z-index: 1; background: var(--card);
}
main.wide table.grid .c-pick  { left: 0; }
main.wide table.grid .c-num   { left: 30px; }
main.wide table.grid .c-state { left: 76px; }
main.wide table.grid thead .c-pick,
main.wide table.grid thead .c-num,
main.wide table.grid thead .c-state { z-index: 4; background: var(--bg); }

/* The frozen cells have to carry the row's own colour, or a red row turns
   white for its first three columns as soon as it is scrolled past. */
main.wide tr.row-error .c-pick,
main.wide tr.row-error .c-num,
main.wide tr.row-error .c-state { background: #fffafa; }
main.wide tr.just-fixed .c-pick,
main.wide tr.just-fixed .c-num,
main.wide tr.just-fixed .c-state { background: var(--good-bg); }
main.wide tr.excluded .c-pick,
main.wide tr.excluded .c-num,
main.wide tr.excluded .c-state { background: var(--bg); }

/* --- The side panel, as Onsite draws it -----------------------------------
   A dark navy column with the company at the top, dashed rules between
   groups, and the open item boxed rather than merely tinted. Deliberately a
   cooler blue than the app's own indigo: this is the upload tool sitting
   beside Onsite, and looking *exactly* like it would suggest it is the same
   screen, which it is not.

   Appended at the end, where it is certain to win — the review rules above
   exist in three near-identical copies from earlier edits. */
:root {
  --side-bg: #17204a;          /* cooler and a shade lighter than Onsite's */
  --side-ink: #e8ecf7;
  --side-dim: #97a3c7;
  --side-line: rgba(255, 255, 255, .12);
  --side-on-bg: #223065;
  --side-on-line: #5b7cfa;
}

.body { align-items: stretch; }
.sidebar {
  width: 232px; padding: 16px 12px 24px;
  background: var(--side-bg); color: var(--side-ink);
  position: sticky; top: 0; align-self: stretch; min-height: 100vh;
}

.sidebar .side-label {
  color: var(--side-dim); margin: 18px 12px 6px;
  border-top: 1px dashed var(--side-line); padding-top: 14px;
}
.sidebar .side-label:first-of-type { border-top: 0; padding-top: 0; }

.sidebar li a,
.sidebar li .soon {
  display: block; padding: 9px 12px; border-radius: 9px;
  color: var(--side-ink); font-size: 14px; font-weight: 500;
  text-decoration: none; border: 1px solid transparent;
}
.sidebar li a:hover { background: rgba(255, 255, 255, .07); }
/* Boxed, not just filled: at a glance the outline is what reads as "you are
   here" down a column of similar words. */
.sidebar li a.on {
  background: var(--side-on-bg); border-color: var(--side-on-line);
  font-weight: 600;
}
.sidebar li .soon { color: var(--side-dim); }

/* The company control belongs to the dark panel now, not to the page. */
.sidebar .company-current {
  background: rgba(255, 255, 255, .06); border-color: var(--side-line);
  color: var(--side-ink);
}
.sidebar .company-current:hover { border-color: var(--side-on-line); }
.sidebar .company-current em { color: var(--side-dim); }

/* --- Side panel: icons, and no group headings -----------------------------
   The headings said Upload / Library / Settings above lists of two things
   each, which is three words explaining five. The dashed rule that separated
   them does the same job without them, and the icons carry the recognition
   the headings were standing in for. */
.sidebar .side-label { display: none; }
.sidebar .nav-group {
  border-top: 1px dashed var(--side-line); padding-top: 10px; margin-top: 10px;
}
.sidebar .nav-group:first-of-type { border-top: 0; padding-top: 0; margin-top: 14px; }

.sidebar li a,
.sidebar li .soon {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; font-size: 15px;
}
/* Onsite's are small — they sit beside the word rather than competing
   with it. 19px read as a row of buttons. */
.nav-icon { width: 16px; height: 16px; flex: none; opacity: .8; }
.sidebar li a.on .nav-icon { opacity: 1; }
.sidebar li .soon .nav-icon { opacity: .5; }
.sidebar li .soon em { margin-left: auto; }

/* --- Side panel: the size the words are actually read at ------------------
   16px with an 18px icon. The labels are the thing being scanned, so they
   lead: the icon sits beside the word at a size that supports it rather than
   competing, and every label starts at the same x however wide its icon
   draws — a flexed row will otherwise let a narrower glyph pull its text
   left and the column stops looking like a column. */
.sidebar li a,
.sidebar li .soon {
  justify-content: flex-start; text-align: left;
  font-size: 16px; gap: 12px; padding: 10px 12px;
}
.sidebar li a span,
.sidebar li .soon > span { flex: 1; min-width: 0; }
.nav-icon { width: 18px; height: 18px; }

/* --- The three steps should not move under you ----------------------------
   Two `main` rules were in play: an early one with `margin: 0 auto` from
   before there was a sidebar, and the flex one that replaced it. The auto
   margin survived, so Upload and Result were centred in whatever space was
   left while Review filled the width — and stepping between the three slid
   the whole page sideways.

   Left edge fixed for all three. Review is still allowed to be wider, because
   a table of thirty columns has to be, but it now grows to the right rather
   than dragging everything with it. */
.body main { margin-left: 0; margin-right: 0; max-width: 1040px; }
.body main.wide { max-width: none; }
.body main > .topline { margin-left: 0; }

/* --- An empty line still has to be clickable ------------------------------
   A BOQ heading whose name never came through renders an empty cell, and an
   empty `-webkit-box` is zero pixels tall — so the one line the screen was
   shouting about ("Every line needs a name") was the one line nobody could
   click to fix. The row looked editable and was not.

   A placeholder gives it both a hit area and a sentence saying what to do.
   `::before` content is not part of `textContent`, so nothing here can be
   saved as the value by accident. */
.desc.edit:empty,
.desc.edit:has(> br:only-child) {
  min-height: 1.5em; -webkit-line-clamp: none; display: block;
}
.desc.edit:empty::before {
  content: "Add a name for this line";
  color: var(--bad); font-weight: 500; opacity: .85;
}
.desc.edit.editing:empty::before { content: ""; }

/* The same for anything short and editable that happens to be blank: a dash
   is a target, an empty box is not. */
.line .unit:empty::before,
.line .num:empty::before { content: "—"; color: var(--muted); }

/* --- The mapping panel: one question, one direction -----------------------
   It used to be two blocks — Onsite's fields each picking one of their
   columns, then their leftover columns each picking an Onsite field. Opposite
   questions in identical furniture, so the bold label reads as the subject and
   the subject flipped halfway down the page; and it read as being asked the
   same thing twice, as though the second block were a double-check on the
   first rather than the columns the first had not accounted for.

   One list now, laid out left to right in the direction it is read: their
   column, an arrow, what it becomes. The arrow is what makes the direction
   impossible to mistake, so it is drawn rather than implied. */
.map-toolbar { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; padding: 2px 0 8px; font-size: 12.5px; }
.map-toolbar .bad { color: var(--bad); }
.map-toolbar .only-left { display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); cursor: pointer; }

.map-list { display: flex; flex-direction: column; }
.map-line { display: grid; grid-template-columns: minmax(0, 1fr) 18px 240px;
  align-items: center; gap: 10px; padding: 6px 2px;
  border-bottom: 1px solid var(--line); }
.map-line:last-child { border-bottom: 0; }
.map-line.head { border-bottom: 1px solid var(--ink); padding-bottom: 5px;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); position: sticky; top: 0; background: #fff; z-index: 2; }
.map-line .from { display: flex; flex-direction: column; gap: 1px;
  min-width: 0; font-size: 12.5px; }
.map-line .from b { font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-line .from i { font-style: normal; font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-line .from i.faint { opacity: .6; }
.map-line .arrow { color: var(--muted); text-align: center; font-size: 13px; }
.map-line select { width: 100%; font: inherit; font-size: 12.5px;
  padding: 5px 7px; border: 1px solid var(--line); border-radius: 6px;
  background: #fff; }

/* Undecided is the state that holds the upload, so it is the loud one — and
   it stays loud on the row, not only on the dropdown, because the row is what
   you scan down. */
.map-line.undecided { background: var(--s-error-bg); }
.map-line.undecided .from b { color: var(--bad); }
.map-line.undecided select { border-color: var(--bad); }
.map-line.settled select { color: var(--ink); }

/* Hiding the settled ones on a sixty-column file. The header row is not a
   decision and stays. */
.map-list.only-undecided .map-line.settled { display: none; }

/* What this mapping leaves empty, in front of the button that saves it. Not a
   warning to dismiss — a statement of what the file does not carry. */
.note.left-off { background: var(--s-auto-bg); border-radius: 8px;
  padding: 9px 12px; margin: 12px 0 0; font-size: 12.5px; line-height: 1.5; }

/* A company this account may not upload into. Shown, not hidden — a company
   that simply is not in the list reads as the tool being broken. */
.companies .company-pick.off { cursor: not-allowed; opacity: .55;
  background: transparent; border-style: dashed; }
.companies .company-pick.off:hover { border-color: var(--line); }
.company-menu .menu-note { margin: 4px 0 0; padding: 8px 10px;
  border-top: 1px solid var(--line); font-size: 11.5px; line-height: 1.45;
  color: var(--muted); }

/* Jumping to a page by the rows it holds. */
.pager .page-jump { display: inline-flex; align-items: center; gap: 6px;
  margin: 0 2px; }
.pager .page-jump select { font: inherit; font-size: 12.5px; padding: 3px 6px;
  border: 1px solid var(--line); border-radius: 6px; background: #fff;
  color: var(--ink); max-width: 230px; }
.pager .page-jump button { font: inherit; font-size: 12.5px; padding: 3px 9px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; }

/* --- The mapping panel, second pass ---------------------------------------
   Onsite's field on the left and their column on the right: the row reads
   "Item Code ← A · Material name", which is the direction people check in —
   they know what Onsite wants and are looking for where it comes from.

   Everything above the list is one sticky block. On a sixty-column file the
   Save button sat a thousand pixels down, and so did the sentence saying what
   the mapping leaves empty; a statement you have to scroll back to is not one
   you read before pressing the button. */
#columns-modal .modal-card { padding: 0; }
#columns-modal .map-sticky { position: sticky; top: 0; z-index: 4;
  background: var(--card); padding: 18px 24px 0;
  border-radius: 12px 12px 0 0; border-bottom: 1px solid var(--line); }
#columns-modal .map-list { padding: 0 24px 20px; }
#columns-modal .modal-head { display: flex; align-items: flex-start;
  justify-content: space-between; gap: 20px; cursor: move;
  touch-action: none; }
#columns-modal .modal-head .head-text { min-width: 0; }
#columns-modal .modal-head h2 { margin: 0 0 6px; }
#columns-modal .modal-head .note { margin: 0; }
#columns-modal .modal-actions { margin: 0; flex: none; }
#columns-modal .map-line.head { position: static; }
#columns-modal .note.left-off { margin: 10px 0 0; }
#columns-modal .map-toolbar { padding-bottom: 6px; }
/* Dragged out of the way. The dialog is tall enough to cover the table it is
   describing, and shoving it aside beats closing and reopening it. */
#columns-modal .modal-card.moved { margin: 0; }

/* --- The BOQ outline: every Onsite column ---------------------------------
   The outline used to draw only the fields this file happened to fill, with
   Item code, GST Percent, HSN Code, Cost Code and Notes as tags beneath the
   description — and only once they had a value. So a BOQ missing its HSN
   codes had nowhere to type one, and a column Onsite wants was invisible on
   the screen where you fill it in.

   Every column now gets a track. The line grows past the width of the card and
   the outline scrolls sideways, which is the trade: a roomier description is
   worth less than a column you can use. */
.outline { overflow-x: auto; }
.line { grid-template-columns:
    56px 96px minmax(240px, 1fr) 104px 92px 108px 128px 118px
    var(--extra-cols, ); 
  min-width: var(--line-min, auto); }
.line > .extra-col { font-size: 12.5px; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; }
.line.head > .extra-col { color: var(--ink); }
.line > .extra-col.edit { cursor: pointer; border-radius: 5px;
  padding: 1px 4px; margin: -1px -4px; }
.line > .extra-col.edit:hover { background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line); }
/* An empty column still has to be clickable, or drawing it achieves nothing. */
.line > .extra-col.edit:empty::before { content: "—"; color: var(--muted); }
.line > .extra-col.auto { background: var(--s-auto-bg); }
.line > .extra-col.error { background: var(--s-error-bg); color: var(--bad); }
/* The description and its overflow stop before the new columns rather than
   running under them, so the tags do not sit beneath a rate. */
.line > .extras, .line > .theirs-rows { grid-column: 3 / 9; }

/* GST beside the rate it is charged on, rather than out past the amount. One
   track fewer than before: their Amount folded into ours. */
.line { grid-template-columns:
    56px 96px minmax(240px, 1fr) 104px 92px 108px 84px 128px
    var(--extra-cols, );
  min-width: var(--line-min, auto); }
.line > .extras, .line > .theirs-rows { grid-column: 3 / 9; }

/* --- The BOQ outline: one treatment per role ------------------------------
   The row had grown four font sizes and three colours across it — 10.5px grey
   caps for UNIT/QTY/RATE, 12px dark for Item, 13.5px for Amount, 12.5px for
   the columns added later — so it read as several tables stitched together
   rather than one. Values are values; headings are headings. */
.line > .serial, .line > .desc, .line > .unit, .line > .num,
.line > .amount, .line > .extra-col { font-size: 13px; }
.line > .serial { color: var(--ink); }
.line > .extra-col { color: var(--ink); }

/* Every heading identical, and on one line, so they share a baseline however
   the tracks are sized. */
.line.head > .serial, .line.head > .desc, .line.head > .unit,
.line.head > .num, .line.head > .amount, .line.head > .extra-col {
  font-size: 10.5px; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em; align-self: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.line.head { align-items: center; }
.line.head > .serial[data-tip], .line.head > .amount[data-tip] { cursor: help; }

/* A cell with something wrong with it. The problem is stated on the value it
   is about, not under the item name — clicking opens the panel with the
   message and the ways out. */
.line .fixable { cursor: pointer; position: relative; }
.line .v.error, .line > .extra-col.error, .line > .desc.error {
  background: var(--s-error-bg); color: var(--bad); border-radius: 5px; }
.line .fixable::after { content: ""; position: absolute; right: 2px; top: 2px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--bad); }
.line > .desc.fixable::after { right: -2px; }
