@import url('./tokens.css');

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--ground);color:var(--ink);
  font:15px/1.45 var(--sans);-webkit-font-smoothing:antialiased;
  /* Numbers in this app are almost always compared down a column — prices,
     quantities, counts — and proportional digits make that impossible to scan.
     Set once here rather than remembered at thirty call sites. */
  font-variant-numeric:tabular-nums;
  padding-bottom:env(safe-area-inset-bottom);
}
h1,h2,h3{margin:0;font-weight:600;letter-spacing:-.01em}
h1{font-size:21px} h2{font-size:17px} h3{font-size:15.5px}
a{color:var(--accent)}
.ic{vertical-align:-4px;flex-shrink:0}
[hidden]{display:none!important}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ---------- controls ---------- */
.btn{
  display:inline-flex;align-items:center;gap:6px;
  min-height:34px;padding:7px 13px;
  font:inherit;font-size:13.5px;font-weight:500;
  color:var(--ink);background:var(--surface);
  border:1px solid var(--rule-2);border-radius:var(--r-sm);
  cursor:pointer;white-space:nowrap;
  transition:background .12s ease, border-color .12s ease, transform .06s ease;
}
.btn:hover{background:var(--surface-2);border-color:var(--rule-2)}
/* A press that moves is the cheapest way an interface feels answerable. */
.btn:active{transform:translateY(.5px)}
.btn-primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);
  box-shadow:var(--shadow-sm)}
.btn-primary:hover{filter:brightness(1.1)}
.btn-danger{background:var(--bad);border-color:var(--bad);color:#fff}
.btn-ghost{border-color:transparent;background:transparent}
.btn:disabled{opacity:.45;cursor:not-allowed}
input,select,textarea{
  font:inherit;font-size:15px;color:var(--ink);background:var(--surface);
  border:1px solid var(--rule-2);border-radius:var(--r-sm);padding:8px 11px;
  max-width:100%;
  transition:border-color .12s ease, box-shadow .12s ease;
}
input:hover,select:hover,textarea:hover{border-color:var(--ink-3)}
input:focus,select:focus,textarea:focus{outline:0;border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent)}
select{
  -webkit-appearance:none;appearance:none;padding-right:30px;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 15px) calc(50% + 1px), calc(100% - 10px) calc(50% + 1px);
  background-size:5px 5px, 5px 5px; background-repeat:no-repeat;
}
/* Safari gives date/time inputs an intrinsic min-width that beats width:100% */
input[type=date],input[type=time]{min-width:0;-webkit-appearance:none;appearance:none;max-width:100%}
label{display:block;font-size:12.5px;font-weight:500;color:var(--ink-2);margin-bottom:5px}

/* ---------- chrome ---------- */
.hdr{
  display:flex;align-items:center;gap:var(--s3);
  padding:10px var(--s4);background:var(--surface);
  border-bottom:1px solid var(--rule);position:sticky;top:0;z-index:40;
  padding-top:calc(10px + env(safe-area-inset-top));
}
.hdr .brand{font-weight:700;letter-spacing:-.02em;font-size:15px;flex-shrink:0}
/* A long organisation name must not turn the header into a five-line tower on
   a phone. It truncates; the full name is in the title attribute and on the
   Admin screen, which is where anyone reading it carefully already is. */
#orgName, #whoAmI{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
#orgName{flex:1 1 auto}
.hdr .sp{flex:1}
.who{font-size:12.5px;color:var(--ink-2)}
.role-chip{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  padding:2px 7px;border-radius:99px;background:var(--accent-soft);color:var(--accent)}

.tabs{display:flex;gap:2px;padding:0 var(--s4);background:var(--surface);
  border-bottom:1px solid var(--rule);overflow-x:auto;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tab{flex-shrink:0;padding:10px 14px;font-size:13.5px;font-weight:500;color:var(--ink-2);
  background:none;border:0;border-bottom:2px solid transparent;cursor:pointer;
  transition:color .12s ease}
.tab:hover{color:var(--ink)}
.tab[aria-selected="true"]{color:var(--accent);border-bottom-color:var(--accent)}

main{padding:var(--s5) var(--s4) var(--s7);max-width:1500px;margin:0 auto}
.panel{display:none}.panel.on{display:block}

/* ---------- browse: Miller columns ---------- */
.miller{display:flex;overflow-x:auto;background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r-lg);min-height:420px;
  box-shadow:var(--shadow-sm)}
.mcol{flex:0 0 232px;border-right:1px solid var(--rule);display:flex;flex-direction:column;min-width:0}
.mcol:last-child{border-right:0}
.mcol-h{font-size:10.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);padding:11px 13px 9px;border-bottom:1px solid var(--rule)}
.mcol-b{overflow-y:auto;flex:1;padding:4px 0}
.mrow{display:flex;align-items:center;gap:8px;width:100%;
  padding:8px 12px;min-height:var(--tap);font:inherit;font-size:13.5px;text-align:left;
  background:none;border:0;color:var(--ink-2);cursor:pointer}
.mrow:hover{background:var(--surface-2)}
.mrow[aria-current="true"]{background:var(--accent-soft);color:var(--ink);font-weight:600}
.mrow .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mrow .ct{font-size:11.5px;color:var(--ink-3);flex-shrink:0;text-align:right;
  font-variant-numeric:tabular-nums}
.mrow .ct i{font-style:normal;opacity:.7;margin-left:3px}
.empty{padding:var(--s5);color:var(--ink-3);font-size:13.5px;text-align:center}

/* ---------- tables ---------- */
/* A table that scrolls sideways must SAY it scrolls, or the last column simply
   looks amputated — which is exactly how the supplier comparison read. */
.twrap{overflow-x:auto;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r);box-shadow:var(--shadow-sm);
  background-image:linear-gradient(to right, var(--surface) 30%, transparent),
                   linear-gradient(to left, var(--surface) 30%, transparent),
                   linear-gradient(to right, rgba(0,0,0,.09), transparent 14px),
                   linear-gradient(to left, rgba(0,0,0,.09), transparent 14px);
  background-position:0 0, 100% 0, 0 0, 100% 0;
  background-repeat:no-repeat; background-size:36px 100%, 36px 100%, 14px 100%, 14px 100%;
  background-attachment:local, local, scroll, scroll}
table{border-collapse:collapse;width:100%;font-size:13.5px;min-width:640px}
th{font-size:10.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);text-align:left;padding:11px 13px;
  border-bottom:1px solid var(--rule-2);white-space:nowrap}
td{padding:10px 12px;border-bottom:1px solid var(--rule);vertical-align:top}
tbody tr:last-child td{border-bottom:0}
tbody tr:hover{background:var(--surface-2)}
td.num{font-size:13.5px;font-variant-numeric:tabular-nums;letter-spacing:.01em;
  white-space:nowrap;text-align:right}

.chip{display:inline-block;font-size:11px;font-weight:600;letter-spacing:.01em;
  padding:2.5px 8px;border-radius:99px;white-space:nowrap;line-height:1.35}
.chip.good{background:var(--good-soft);color:var(--good)}
.chip.warn{background:var(--warn-soft);color:var(--warn)}
.chip.bad{background:var(--bad-soft);color:var(--bad)}
.chip.muted{background:var(--surface-2);color:var(--ink-3)}
/* Filed by the reader, not by a person. Deliberately NOT green: green here
   means somebody looked at it, and a row that files itself has to be
   distinguishable on sight from one that was checked. */
.chip.auto{background:var(--accent-soft);color:var(--accent)}
.chip.auto svg{vertical-align:-2px}

/* the provenance affordance — every rate carries one */
.src{display:inline-flex;align-items:center;gap:4px;font-size:12px;
  color:var(--accent);background:none;border:0;padding:4px 2px;cursor:pointer;min-height:28px}
.src:hover{text-decoration:underline}
.src.none{color:var(--ink-3);cursor:default;font-style:italic}
.src.none:hover{text-decoration:none}

/* ---------- modal ---------- */
.mo{position:fixed;inset:0;background:rgba(10,12,16,.55);display:flex;
  align-items:center;justify-content:center;padding:var(--s4);z-index:900}
.md{background:var(--surface);border-radius:var(--r);box-shadow:var(--shadow);
  width:min(560px,100%);max-height:88vh;display:flex;flex-direction:column}
.md.wide{width:min(1240px,100%)}
.md h3{padding:var(--s4);border-bottom:1px solid var(--rule)}
.md-b{padding:var(--s4);overflow:auto;flex:1}
.md-f{display:flex;justify-content:flex-end;gap:var(--s2);
  padding:var(--s3) var(--s4);border-top:1px solid var(--rule)}

/* ---------- toast ---------- */
#toast{position:fixed;left:50%;bottom:calc(24px + env(safe-area-inset-bottom));
  transform:translateX(-50%) translateY(20px);opacity:0;pointer-events:none;
  background:var(--ink);color:var(--ground);padding:11px 18px;border-radius:99px;
  font-size:13.5px;max-width:90vw;z-index:1000;transition:opacity .18s,transform .18s}
#toast.show{opacity:1;transform:translateX(-50%)}
#toast.bad{background:var(--bad);color:#fff}
#toast.good{background:var(--good);color:#fff}

/* ---------- auth ---------- */
.auth{max-width:380px;margin:12vh auto;padding:var(--s5);background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r);box-shadow:var(--shadow)}
.auth .f{margin-bottom:var(--s3)}
.auth input{width:100%}
.msg{font-size:13px;padding:9px 11px;border-radius:var(--r-sm);margin-bottom:var(--s3)}
.msg.err{background:var(--bad-soft);color:var(--bad)}
.msg.ok{background:var(--good-soft);color:var(--good)}

/* ---------- drop zone ---------- */
.drop{border:2px dashed var(--rule-2);border-radius:var(--r);padding:var(--s7) var(--s4);
  text-align:center;color:var(--ink-2);background:var(--surface);cursor:pointer}
.drop.over{border-color:var(--accent);background:var(--accent-soft)}

.bar{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap;margin-bottom:var(--s3)}
.grow{flex:1;min-width:180px}
/* Everything in this app is text somebody else wrote — a supplier's product
   description, a category name, a filename. It arrives with no spaces to wrap
   at more often than you would think, and a single unbroken string is enough to
   push the whole page sideways on a phone. So the containers that hold that
   text break it rather than let it escape. */
.crumb, .empty, .note, .msg,
.prop, .prop b, .mf-hit, .fl,
.rev-desc, .rev-grp-h, .rev-row-nums, .rev-page-bar,
.mcol-h, .mcol .nm, td, th{overflow-wrap:anywhere}
.crumb{font-size:12px;color:var(--ink-3);margin-bottom:var(--s2)}
.crumb b{color:var(--ink-2);font-weight:500}

/* ---------- review workspace ---------- */
/* Lines left, the page right. The whole argument for this screen is that
   checking a number against its source should not be a navigation. */
.rev-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(340px,44%);
  gap:var(--s4);align-items:start}
.rev-lines{min-width:0;display:flex;flex-direction:column;gap:var(--s2)}
.rev-grp{padding-left:calc(var(--d,0) * 14px)}
/* A quote can nest its headings several deep. On a phone the indent has to
   stop earning its keep before it eats the line it is indenting. */
@media (max-width:700px){ .rev-grp{padding-left:calc(min(var(--d,0),2) * 8px)} }
.rev-grp-h{font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-3);margin:var(--s3) 0 var(--s2)}
.rev-row{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r);
  padding:var(--s3);cursor:pointer;display:flex;flex-direction:column;gap:6px}
.rev-row:hover{border-color:var(--rule-2)}
.rev-row:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.rev-row.sel{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
.rev-row.done{background:var(--surface-2)}
.rev-row-top{display:flex;gap:var(--s2);align-items:baseline;flex-wrap:wrap}
.rev-desc{font-size:14px;font-weight:500;flex:1;min-width:0;overflow-wrap:anywhere}
.rev-row-nums{display:flex;gap:var(--s3);align-items:baseline;flex-wrap:wrap;
  font-size:13px;color:var(--ink-2)}
.rev-row-nums b{font-size:14.5px;color:var(--ink)}
.rev-row-acts{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
.rev-row-acts .grow{min-width:0}

.rev-page{position:sticky;top:var(--s4);background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r);overflow:hidden}
.rev-page-bar{display:flex;gap:var(--s2);align-items:center;padding:var(--s2) var(--s3);
  border-bottom:1px solid var(--rule);font-size:13px}
.rev-page-canvas{max-height:72vh;overflow:auto;background:var(--surface-2)}
.rev-page-canvas canvas{display:block;width:100%}
.rev-page .crumb{padding:var(--s2) var(--s3);margin:0}

.note{background:var(--warn-soft);color:var(--warn);border-radius:var(--r-sm);
  padding:9px 12px;font-size:13px;margin:0 0 var(--s3);display:flex;gap:8px;align-items:flex-start}

/* filing dialog */
/* A column flex box that scrolls. `flex-shrink` MUST be off on the rows:
   the default is 1, so once the hits exceed max-height the browser squashes
   every row instead of scrolling -- down to the 44pt floor, which is less than
   the two lines a row actually needs, and the code line gets cut in half. Two
   hits fit and look perfect, twelve do not, which is exactly how this shipped. */
.mf-hits{max-height:min(320px,42vh);overflow-y:auto;margin:var(--s2) 0;display:flex;
  flex-direction:column;gap:4px;overscroll-behavior:contain}
.mf-hit{display:flex;flex-direction:column;gap:2px;text-align:left;width:100%;
  flex:0 0 auto;background:var(--surface-2);border:1px solid transparent;
  border-radius:var(--r-sm);padding:8px 10px;cursor:pointer;color:inherit;font:inherit;
  min-height:var(--tap)}
.mf-hit:hover{border-color:var(--rule-2)}
.mf-hit:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.mf-hit.on{border-color:var(--accent);background:var(--accent-soft)}
/* What you picked, directly under the box you typed in -- the old confirmation
   line sat BELOW the list, i.e. off screen the moment the list was full, so a
   successful click looked like nothing happening. */
.mf-chosen{display:flex;align-items:center;gap:6px;font-size:12.5px;margin:var(--s2) 0 0;
  padding:6px 9px;border-radius:var(--r-sm);background:var(--accent-soft);
  color:var(--ink);border:1px solid var(--accent)}
.mf-chosen.none{background:var(--surface-2);border-color:var(--rule);color:var(--ink-3)}
.mf-chosen b{overflow-wrap:anywhere}
.fl{display:block;font-size:12px;color:var(--ink-2);margin:var(--s2) 0}
.fl .in{margin-top:4px}
.in{width:100%;padding:9px 11px;border:1px solid var(--rule-2);border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink);font:inherit}
.mono{font-family:var(--mono)}

/* proposals */
.prop{display:flex;gap:var(--s3);align-items:flex-start;background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r);padding:var(--s3);margin-bottom:var(--s2)}
.prop > div:first-child{flex:1;min-width:0}
.prop .crumb{margin:4px 0 0}
.prop-acts{display:flex;gap:var(--s2);flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}

/* ---------- quantity bar ---------- */
/* Sits above the numbers because every number below it depends on what is
   typed here. */
.qty-bar{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap;
  background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r);
  padding:var(--s2) var(--s3);margin-bottom:var(--s3)}
.qty-bar label{font-size:12.5px;font-weight:500;color:var(--ink-2);margin:0}
.qty-bar input{width:110px;padding:7px 9px;border:1px solid var(--rule-2);
  border-radius:var(--r-sm);background:var(--surface);color:var(--ink);
  font:inherit;font-variant-numeric:tabular-nums;text-align:right}
.qty-unit{font-size:13px;color:var(--ink-2)}
.qty-flip{margin:0 0 var(--s3)}
.cmp-best{background:var(--good-soft)}
.btn-sm{padding:4px 9px;font-size:12px;min-height:0}
/* An editable value that still reads as a value. A full input in every row
   would turn the comparison into a form; a bare span would not say it can be
   changed. Empty reads "set" rather than a dash, because a dash looks like the
   answer is nothing when it is really that nobody has said. */
.leadv{background:none;border:1px dashed transparent;border-radius:var(--r-sm);
  padding:2px 6px;font:inherit;color:inherit;cursor:pointer;
  font-variant-numeric:tabular-nums;min-height:0}
.leadv:hover,.leadv:focus-visible{border-color:var(--rule-2);background:var(--surface-2)}
.leadv.none{color:var(--ink-3);font-size:12px;border-color:var(--rule)}
@media (max-width:700px){ .leadv{min-height:var(--tap);padding:6px 10px} }

.linkish{background:none;border:0;padding:0;font:inherit;color:var(--accent);
  cursor:pointer;text-align:left;overflow-wrap:anywhere}
.linkish:hover{text-decoration:underline}

/* ---------- pricing sheet ---------- */
.sheet-tbl input.sheet-qty{width:74px;padding:4px 6px;border:1px solid var(--rule-2);
  border-radius:var(--r-sm);background:var(--surface);color:var(--ink);font:inherit;
  text-align:right;font-variant-numeric:tabular-nums}
.sheet-tbl tfoot td{border-top:1.5px solid var(--rule-2);font-variant-numeric:tabular-nums}
tr.sheet-locked{background:var(--surface-2);color:var(--ink-2)}

/* The reader's suggested category, on the review row. Distinct from the amber
   "File this line…" action beside it: this is information, not a button. */
.chip.sug{background:var(--accent-soft);border-color:var(--accent);color:var(--ink);
  display:inline-flex;align-items:center;gap:4px;max-width:100%;overflow-wrap:anywhere}
.chip.sug svg{flex-shrink:0}

/* ---------- the quotes library ---------- */
.qt-note{display:flex;gap:var(--s2);align-items:flex-start;background:var(--warn-soft,var(--surface-2));
  border:1px solid var(--rule);border-radius:var(--r);padding:var(--s3);margin-bottom:var(--s3);
  font-size:13px;line-height:1.45}
.qt-note svg{flex-shrink:0;margin-top:1px}
.qt-list{display:flex;flex-direction:column;gap:var(--s2)}
.qt-row{display:flex;gap:var(--s3);align-items:flex-start;background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r);padding:var(--s3)}
.qt-row.archived{background:var(--surface-2);color:var(--ink-2)}
.qt-main{flex:1;min-width:0}
.qt-name{font-size:14.5px;overflow-wrap:anywhere;display:block}
.qt-meta{font-size:12px;color:var(--ink-3);margin-top:3px;overflow-wrap:anywhere}
.qt-state{flex:0 0 auto;max-width:210px;text-align:right}
.qt-state .crumb{margin-top:4px;display:block}
.qt-acts{flex:0 0 auto;display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap;
  justify-content:flex-end}
.qt-status{font-size:12px;color:var(--ink-2);max-width:260px;overflow-wrap:anywhere;text-align:right}

/* A sheet line and a quoted line, on a phone. Both are wide tables on a
   desktop; neither survives 390px as one, and what gets pushed off screen is
   the money and the source link. Same answer as the comparison's .cmp-card. */
.sheet-cards,.sup-lines{display:flex;flex-direction:column;gap:var(--s2)}
.sheet-card,.sup-line{border:1px solid var(--rule);border-radius:var(--r);
  padding:var(--s3);background:var(--surface)}
.sheet-card.locked{background:var(--surface-2);border-color:var(--rule-2)}
.sheet-card-row{display:flex;gap:var(--s2);align-items:flex-end;flex-wrap:wrap;
  margin-top:var(--s3)}
.sheet-qtyf{display:flex;flex-direction:column;gap:3px;font-size:11.5px;
  color:var(--ink-3);flex:1;min-width:0}
.sheet-qtyf input{width:100%;text-align:right}
.sheet-cardfoot{margin-top:var(--s3);border-top:1.5px solid var(--rule-2);
  padding-top:var(--s2);display:flex;flex-direction:column;gap:2px}
.sheet-cardfoot div{display:flex;justify-content:space-between;gap:var(--s3);
  font-size:13px;color:var(--ink-2)}
.sheet-cardfoot b{font-variant-numeric:tabular-nums;color:var(--ink)}
.sheet-cardfoot .tot{font-size:16px;margin-top:2px}
.sup-line-rate{font-size:19px;font-weight:600;letter-spacing:-.02em;margin:6px 0 4px}
.sup-line-rate span{font-size:11.5px;font-weight:400;color:var(--ink-3);margin-left:6px}

/* ---------- suppliers ---------- */
.sup-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--s3)}
.sup-card{display:flex;flex-direction:column;gap:5px;text-align:left;background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r);padding:var(--s3);cursor:pointer;
  color:inherit;font:inherit;min-height:var(--tap)}
.sup-card:hover{border-color:var(--rule-2)}
.sup-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.sup-name{font-size:14.5px;overflow-wrap:anywhere}
.sup-nums{display:flex;gap:var(--s2);flex-wrap:wrap;align-items:center;font-size:12.5px;
  color:var(--ink-2)}
.sup-nums b{color:var(--ink);font-size:14px}
.sup-stats{display:flex;gap:var(--s3);flex-wrap:wrap;margin:0 0 var(--s4)}
.sup-stat{background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r);
  padding:var(--s2) var(--s3);min-width:104px}
.sup-stat b{display:block;font-size:20px;letter-spacing:-.02em;line-height:1.15}
.sup-stat span{font-size:10.5px;font-weight:500;color:var(--ink-3);
  text-transform:uppercase;letter-spacing:.05em}
.sup-stat.good b{color:var(--good)}
.sup-stat.bad b{color:var(--bad)}
.sup-stat.warn b{color:var(--warn)}
.sup-h{font-size:13px;margin:var(--s4) 0 var(--s2)}

/* ---------- supplier comparison, on a phone ---------- */
.cmp-cards{display:flex;flex-direction:column;gap:var(--s2)}
.cmp-card{border:1px solid var(--rule);border-radius:var(--r);padding:var(--s3);
  background:var(--surface)}
.cmp-card.best{border-color:var(--good);background:var(--good-soft)}
.cmp-top{display:flex;gap:var(--s2);align-items:baseline;flex-wrap:wrap;margin-bottom:6px}
.cmp-sup{font-size:14.5px;overflow-wrap:anywhere;flex:1;min-width:0}
.cmp-rate{font-size:22px;font-weight:600;letter-spacing:-.02em;line-height:1.1}
.cmp-rate span{display:block;font-size:11.5px;font-weight:400;color:var(--ink-3);
  letter-spacing:0;margin-top:2px}
.cmp-facts{margin:var(--s3) 0 0;display:grid;grid-template-columns:1fr 1fr;gap:6px var(--s3)}
.cmp-f{display:flex;flex-direction:column;gap:1px;min-width:0}
.cmp-f span{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.cmp-f b{font-size:13px;font-weight:500;overflow-wrap:anywhere}
.cmp-src{margin-top:var(--s3);padding-top:var(--s2);border-top:1px solid var(--rule);
  display:flex;gap:var(--s2);flex-wrap:wrap;align-items:center}

/* ---------- phone ---------- */
@media (max-width:700px){
  main{padding:var(--s3)}
  /* Two A4-ish panes side by side is neither on a phone. The page pane drops
     below the lines; the per-line source button still opens it on its own. */
  .rev-split{grid-template-columns:1fr}
  .rev-page{position:static}
  .rev-page-canvas{max-height:56vh}
  .prop{flex-direction:column}
  .prop-acts{justify-content:flex-start}
  .sup-grid{grid-template-columns:1fr}
  /* Three columns on a 390px row leaves the buttons a sliver each. */
  .qt-row{flex-direction:column;gap:var(--s2)}
  .qt-state{max-width:none;text-align:left}
  .qt-acts{width:100%;justify-content:flex-start}
  .qt-status{text-align:left;max-width:none}
  .sup-stats{gap:var(--s2)}
  .sup-stat{flex:1 1 44%;min-width:0}
  /* Anything a finger has to hit clears 44pt. This is not only the buttons:
     the library picker, the project picker and the search box were 35-40px,
     and the quantity field -- the one control every figure on the comparison
     depends on -- was 39px. The scan that should have caught them looked at
     buttons on one screen; both it and these rules are now widened. */
  .in,
  select,
  input[type=text],
  input[type=search],
  input[type=number]{min-height:44px}
  .qty-bar input{flex:1;width:auto;min-width:0;min-height:44px;padding:9px 11px}
  .sheet-tbl input.sheet-qty{min-height:44px;padding:8px}
  .miller{display:block;min-height:0;border:0;box-shadow:none;background:none}
  .mcol{flex:none;width:auto;border-right:0;
    background:var(--surface);border:1px solid var(--rule);border-radius:var(--r)}
  .mcol.hide-phone{display:none}
  .mrow{min-height:var(--tap);font-size:15px}
  input,select,textarea{font-size:16px}   /* below 16px iOS zooms on focus */
  .btn{min-height:var(--tap);padding:10px 14px}
  .hdr{gap:var(--s2)}
  #orgName{display:none}      /* the brand and the person are what matter here */
  #whoAmI{max-width:9ch}
  /* HIG minimum. The tab bar is the app's primary navigation; at 35px it is
     the easiest thing in the whole interface to mis-tap. */
  .tab{min-height:var(--tap);display:flex;align-items:center}
  table{min-width:520px}
}

/* ── the classification, walked like folders ───────────────────────────────
   Search only helps when you already know the name. These two questions —
   "what is under here?" and "where should this go?" — need the shape of the
   tree, not a list of matches torn out of it. */
.ct{margin-top:10px;border:1px solid var(--line);border-radius:10px;overflow:hidden;
  background:var(--surface)}
.ct-crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:7px 9px;
  background:var(--surface-2);border-bottom:1px solid var(--line)}
.ct-crumb{background:none;border:0;padding:3px 5px;border-radius:6px;cursor:pointer;
  font:600 11.5px/1.2 inherit;color:var(--accent);min-height:26px}
.ct-crumb:hover{background:var(--accent-soft)}
.ct-crumb:last-of-type{color:var(--ink);cursor:default}
.ct-sep{display:inline-flex;color:var(--ink-3)}
/* Capped so the dialog cannot grow past the viewport on a long level — the
   list scrolls, the actions under it stay reachable. */
.ct-list{max-height:230px;overflow-y:auto;padding:4px}
.ct-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  background:none;border:0;border-radius:8px;padding:9px 10px;cursor:pointer;
  min-height:44px;color:var(--ink);font:13px/1.3 inherit}
.ct-row:hover{background:var(--surface-2)}
.ct-row.on{background:var(--accent-soft);color:var(--accent);font-weight:600}
.ct-nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ct-into{display:inline-flex;color:var(--ink-3);flex-shrink:0}
.ct-leaf{display:inline-flex;color:var(--ink-3);opacity:.45;flex-shrink:0}
.ct-add{color:var(--accent);font-weight:600;border-top:1px dashed var(--line);
  border-radius:0 0 8px 8px;margin-top:2px}
.ct-new{display:flex;gap:6px;padding:8px;border-top:1px dashed var(--line);flex-wrap:wrap}
.ct-new .in{flex:1;min-width:150px}
.ct-use{width:calc(100% - 16px);margin:0 8px 8px;justify-content:center}
/* An empty material offers its own way out, where you find out it is empty. */
.mat-orphan{display:flex;flex-direction:column;gap:6px;align-items:flex-start;
  padding:12px 0 4px;border-top:1px solid var(--line);margin-top:10px}

/* ── where a line came from, on the page itself ────────────────────────────
   The box is never a stored coordinate — it is the result of finding the
   line's own words in the PDF's text layer. So it is either exactly right or
   not drawn: a highlight over the wrong row is worse than none, because it
   gets checked, agrees with itself, and earns trust it has not got. */
.rev-page-stack{position:relative;line-height:0}
.rev-marks{position:absolute;inset:0;pointer-events:none}
.rev-mark{position:absolute;background:rgba(250,204,21,.34);
  box-shadow:0 0 0 1px rgba(202,138,4,.55) inset;border-radius:2px;
  animation:markIn .18s ease-out}
@keyframes markIn{from{background:rgba(250,204,21,.72)}to{background:rgba(250,204,21,.34)}}
/* Archived materials fold away rather than sit greyed among the live ones —
   archiving is meant to stop something looking like inventory you have. */
.mcol-arch{border-top:1px solid var(--line);margin-top:6px}
.mcol-arch>summary{cursor:pointer;padding:8px 10px;font:600 11px/1.2 inherit;
  letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3);min-height:36px;
  display:flex;align-items:center}
.mcol-arch>summary:hover{color:var(--ink)}
.mrow.arch .nm{opacity:.62;text-decoration:line-through
  var(--ink-3)}

/* ── filing: two paths, side by side ───────────────────────────────────────
   Picking an existing material and creating a new one are alternatives. Stacked,
   the second reads as a footnote to the first and every field is stretched to
   the full width of a wide dialog for the sake of the word "nr". */
.mf-intro{margin:0 0 14px}
.mf-cols{display:grid;gap:var(--s5)}
@media (min-width:900px){
  .mf-cols{grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);align-items:start}
  .mf-cols>.mf-col+.mf-col{border-left:1px solid var(--rule);padding-left:var(--s5)}
}
.mf-col{min-width:0}

/* ── the miller's empty half ───────────────────────────────────────────────
   One 232px column inside a 1400px card is 1170px of blank white that says
   nothing. The filler is not decoration: it names the next move. */
.mcol-hint{flex:1 1 auto;display:flex;flex-direction:column;min-width:0}
.mcol-hint .mcol-h{min-height:0}
.mcol-hint span{flex:1;display:grid;place-items:center;padding:var(--s5);
  color:var(--ink-3);font-size:13.5px;text-align:center;line-height:1.55}
/* A date wrapping to two lines doubles every row's height and makes the whole
   comparison look sparse and uneven. The table scrolls, and now says so, so
   there is no reason to buy width by wrapping. The supplier cell is exempt —
   its warnings are sentences and must wrap. */
.twrap td{white-space:nowrap}
.twrap td:first-child{white-space:normal}

/* ── what is about to be filed ─────────────────────────────────────────────
   Filing used to ask a person to commit a rate they could not see, and to
   retype a unit the reader had already read off the page. Both are shown, both
   are correctable, and correcting one fixes the RATE — not just this filing. */
.mf-rate{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--s3);
  background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r);
  padding:var(--s3);margin:0 0 var(--s4)}
.mf-rate-f{display:flex;flex-direction:column;gap:4px}
.mf-rate-f label{margin:0}
.mf-rate-f input{width:130px}
.mf-rate-f:nth-of-type(2) input{width:96px}
.mf-rate-note{flex:1 1 240px;margin:0;min-width:0;line-height:1.5}
/* A finished quote leaves the queue but stays reachable — the review workspace
   is the only way back into a document's lines. */
.rev-done{margin-top:var(--s4)}
.rev-done>summary{cursor:pointer;padding:8px 2px;font-size:12px;font-weight:600;
  color:var(--ink-3);min-height:36px;display:flex;align-items:center}
.rev-done>summary:hover{color:var(--ink)}
.rev-done .twrap{margin-top:var(--s2)}
/* The price at a glance, in the column where the materials are. A count told
   you something was priced; the price is what you came to see. */
.mrow .ct b{font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
.mrow .ct i{font-style:normal;color:var(--ink-3);opacity:1}
.mrow-n{display:inline-block;margin-left:7px;padding:1px 6px;border-radius:99px;
  background:var(--surface-2);color:var(--ink-3);font-size:10.5px;font-weight:600;
  vertical-align:1px}
.mrow[aria-current="true"] .mrow-n{background:var(--surface)}
/* Archived quotes fold away. Archiving one says it is done with; leaving it in
   the list contradicts that on the screen where it was said. */
.qt-arch{margin-top:var(--s3)}
.qt-arch>summary{cursor:pointer;padding:8px 2px;font-size:12px;font-weight:600;
  color:var(--ink-3);min-height:36px;display:flex;align-items:center}
.qt-arch>summary:hover{color:var(--ink)}
.qt-arch .qt-list{margin-top:var(--s2)}
/* What a material used to cost. Kept because "we paid X last year" is a real
   question; folded and plainly labelled because an archived price is not on
   offer, and never ranked beside the live ones. */
.mat-arch{margin-top:var(--s4)}
.mat-arch>summary{cursor:pointer;padding:8px 2px;font-size:12px;font-weight:600;
  color:var(--ink-3);min-height:36px;display:flex;align-items:center}
.mat-arch>summary:hover{color:var(--ink)}
.mat-arch .twrap{margin-top:var(--s2)}
.mat-arch table{min-width:420px}
