/* ============================================================
   DRYSTAR Change Order Hub  --  styles
   Same tokens as the field forms portal, deliberately: two apps
   behind one login should not look like two companies.
   ============================================================ */
:root{
  --bg:#0b0e13; --bg2:#10141b; --panel:#161b23; --panel2:#1b212b;
  --line:rgba(255,255,255,.1); --line2:rgba(255,255,255,.16);
  --text:#eceef2; --muted:#9aa3b1; --accent:#4a7ba6; --accent2:#6ba3d0; --steel:#aac4d8;
  --ok:#3f9d6b; --warn:#c9a227; --err:#cf5b4e;
  --display:"Sora",system-ui,-apple-system,sans-serif;
  --body:"Inter",system-ui,-apple-system,sans-serif;
  /* Tabular figures. Money in a column that does not line up reads as sloppy
     bookkeeping before anyone has checked whether it is right. */
  --num:"Inter",ui-monospace,SFMono-Regular,monospace;
}
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--text);font-family:var(--body);font-size:16px;line-height:1.6;-webkit-font-smoothing:antialiased}
h1,h2,h3{font-family:var(--display);font-weight:700;letter-spacing:-.01em;line-height:1.15}
a{color:var(--accent2);text-decoration:none}
img{display:block;max-width:100%}
.hidden{display:none!important}

.btn{font-family:var(--display);font-weight:600;font-size:14px;letter-spacing:.04em;border:1px solid transparent;border-radius:6px;padding:13px 22px;cursor:pointer;transition:all .18s;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:48px}
.btn-p{background:var(--accent);color:#fff}.btn-p:hover{background:var(--accent2)}
.btn-g{background:transparent;border-color:var(--line2);color:var(--text)}.btn-g:hover{border-color:var(--steel)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn-sm{min-height:34px;padding:6px 12px;font-size:12.5px}

label{display:block;font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
input[type=text],input[type=password],input[type=email],input[type=search]{
  width:100%;background:var(--panel);border:1px solid var(--line2);border-radius:6px;color:var(--text);
  font-family:var(--body);font-size:16px;padding:13px 14px;transition:border-color .18s}
input:focus{outline:none;border-color:var(--accent)}
.field{margin-bottom:18px}
.msg{font-size:13.5px;margin:-4px 0 14px;min-height:18px;color:var(--err)}
.msg.ok{color:var(--ok)}

/* ---------- sign in ---------- */
.gate{min-height:100svh;display:flex;align-items:center;justify-content:center;padding:24px;
  background:radial-gradient(1200px 600px at 50% -10%,rgba(74,123,166,.16),transparent 60%),var(--bg)}
.gate-card{width:100%;max-width:400px;background:rgba(16,20,27,.96);border:1px solid var(--line2);border-radius:12px;padding:38px 32px}
/* The real horizontal lockup, in white, on the dark card. Not the company
   name set in a font: see the note in lib/export.js. This is where the
   lockup belongs, because a person arriving at a sign in page has no other
   context. Inside the app the bar carries the mark alone (2026-09-04), and
   so does the favicon. Anything that LEAVES the building keeps the full
   lockup: the quotation, the export and the rendered PDF all do. */
.gate-card .mark{display:block;width:208px;height:auto;margin:0 auto 22px}
.gate-card h1{font-size:21px;text-align:center;margin-bottom:6px}
.gate-card .lede{text-align:center;color:var(--muted);font-size:14.5px;margin-bottom:26px}

/* ---------- top bar ---------- */
header{position:sticky;top:0;z-index:30;background:rgba(11,14,19,.94);backdrop-filter:blur(12px);border-bottom:1px solid var(--line)}
.bar{max-width:1440px;margin:0 auto;padding:0 24px;height:64px;display:flex;align-items:center;gap:16px}
/* Never shrinks. It is a flex item, so without this it gives up width like
   anything else, and the global img{max-width:100%} then follows the box
   down: at 375px the mark was squashed to a 1px sliver rather than dropping
   out cleanly, which looks like a broken image rather than a tight bar. */
.brand{display:flex;align-items:center;gap:14px;min-width:0;flex:0 0 auto}
/* The logo is also the way home. A link, not a click handler, so middle-click
   and new-tab behave like everywhere else on the web. */
a.brand{text-decoration:none;color:inherit}
/* A short approval shows what was asked under what was agreed. */
td.num .was{display:block;font-size:11px;color:var(--muted);white-space:nowrap}
/* The mark alone, not the lockup. The wordmark spelled DRYSTAR beside a
   screen that already says so, and it cost about 120px that the job name
   needed on a narrow bar. The mark is a solid shape rather than a gradient
   wordmark, so the sizing note that used to live here no longer applies: it
   does not wash out when it is made smaller. The full lockup still opens the
   sign in card, where there is room and no context yet. (Derek, 2026-09-04) */
.brand .logo{height:38px;width:auto;display:block;flex:0 0 auto}
@media (max-width:720px){
  .brand .logo{height:32px}
}
.bar .right{display:flex;align-items:center;gap:16px;margin-left:auto;flex:0 0 auto}
/* The project picker in the header. */
.pp{position:relative;flex:0 1 340px;min-width:0}
.pp-btn{width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px;text-align:left;background:var(--bg2);border:1px solid var(--line2);border-radius:8px;padding:11px 12px;color:var(--text);cursor:pointer;font:inherit}
.pp-btn:hover{border-color:var(--steel)}
.pp-cur{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pp-caret{flex:0 0 auto;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid var(--muted)}
.pp-btn[aria-expanded="true"] .pp-caret{border-top:0;border-bottom:6px solid var(--muted)}
.pp-panel{position:absolute;left:0;top:calc(100% + 6px);width:100%;min-width:360px;background:var(--panel);border:1px solid var(--line2);border-radius:10px;box-shadow:0 16px 40px rgba(0,0,0,.5);z-index:50;overflow:hidden}
.pp-search{padding:10px}
.pp-search input{width:100%}
.pp-list{max-height:min(60vh,520px);overflow-y:auto;padding:0 6px 8px}
.pp-h{font-family:var(--display);font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);padding:10px 8px 6px}
.pp-opt{padding:8px 10px;border-radius:6px;cursor:pointer;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pp-opt:hover,.pp-opt.active{background:var(--panel2)}
.pp-opt.current{color:var(--accent2)}
.pp-none{padding:10px 12px;color:var(--muted);font-size:13px}

/* The tool picker in the header, beside the project picker: what you are
   looking at on the left, what you are looking at it WITH on the right. The
   same single line button as the project picker, so the pair reads as one
   control rather than two widgets that happen to be adjacent. The panel is
   columns rather than a list because the grouping is what makes four tools
   legible as two kinds of thing. */
.tp{position:relative;flex:0 0 auto;min-width:0}
.tp-btn{display:flex;align-items:center;justify-content:space-between;gap:10px;text-align:left;background:var(--bg2);border:1px solid var(--line2);border-radius:8px;padding:11px 12px;color:var(--text);cursor:pointer;font:inherit;min-width:150px}
.tp-btn:hover{border-color:var(--steel)}
.tp-cur{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tp-caret{flex:0 0 auto;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid var(--muted)}
.tp-btn[aria-expanded="true"] .tp-caret{border-top:0;border-bottom:6px solid var(--muted)}
.tp-panel{position:absolute;left:0;top:calc(100% + 6px);width:max-content;max-width:min(720px,calc(100vw - 48px));background:var(--panel);border:1px solid var(--line2);border-radius:10px;box-shadow:0 16px 40px rgba(0,0,0,.5);z-index:50;padding:14px 18px 18px}
.tp-hint{margin:0 0 12px;text-align:center;font-family:var(--display);font-size:12px;color:var(--muted)}
.tp-cols{display:grid;grid-template-columns:repeat(2,minmax(210px,1fr));gap:10px 34px}
.tp-h{font-family:var(--display);font-size:13px;font-weight:700;color:var(--text);padding-bottom:8px;margin-bottom:6px;border-bottom:1px solid var(--line2)}
.tp-opt{display:block;padding:8px 10px;border-radius:6px;color:var(--muted);text-decoration:none}
.tp-opt:hover,.tp-opt:focus-visible{background:var(--panel2);color:var(--text);outline:none}
.tp-opt.current{color:var(--accent2);cursor:default}
.tp-label{display:block;font-size:14px;font-weight:600}
/* A tool that leaves the hub for another app says so, quietly. */
.tp-away{font-size:11px;margin-left:5px;opacity:.7}
.tp-note{display:block;font-size:12px;color:var(--muted);margin-top:2px}
@media (max-width:900px){
  .tp-cols{grid-template-columns:1fr}
  .tp-note{display:none}
}
@media (max-width:640px){
  .tp-btn{min-width:0}
}

/* Narrow. The picker used to be hidden outright under 720px, which left a
   phone showing a tool and no job at all. It takes the room instead, down to
   a floor that still shows a job number.

   This block HAS to sit after the .pp base rule rather than up beside the
   logo, which is where it was first written. Both rules are one class deep,
   so specificity is a tie and the later one wins: being inside a media query
   is not a tiebreak. Written above, it computed min-width 0 and the picker
   collapsed to 7px at 375, exactly the bug it was added to fix, while
   reading in the source as though it were doing the job. */
@media (max-width:720px){
  .pp{flex:1 1 auto;min-width:120px}
}
/* On a phone the bar can hold the mark, the job, the tool and a way out, and
   not the name of the person holding the phone. The gaps tighten and the
   tool label truncates before the job does, because which job you are on is
   the fact you cannot work without. */
/* A phone bar holds the mark, the job, the tool and a way out, and not the
   name of the person holding the phone. Even then 375px does not fit a full
   "Changes" beside a full job number, so the tool label is what gives: the
   page's own heading already says which tool this is, and nothing else on
   screen says which job. The gaps and the button padding tighten first so
   the truncation stays a word rather than a letter. */
@media (max-width:640px){
  .bar{gap:8px}
  .who{display:none}
  /* Basis 0, not auto. On auto the picker starts from its content width and
     only shrinks proportionally, so it kept 161px, the row overflowed and
     Sign out was clipped off the right edge while every box still measured
     as though it fitted. Basis 0 makes it take the remainder and nothing
     more, which is exactly what the last item in a fixed budget should do. */
  .pp{flex:1 1 0;min-width:112px}
  /* min-width:0 on all three, or the label refuses to shrink and Sign out
     is pushed off the right edge. A flex item will not go below its content
     unless it is told it may, and text-overflow cannot help until it can. */
  .tp{flex:0 1 auto;min-width:0}
  /* A floor, because the project picker grows and would otherwise take the
     whole remainder and leave the tool reading "C". Both keep a floor and
     the leftover goes to the job, which is the one nothing else says. */
  .tp-btn{width:100%;min-width:78px;padding:11px 8px;gap:6px}
  .tp-cur{min-width:0}
}
.who{font-size:13px;color:var(--muted)}
.who b{color:var(--text);font-weight:600}
.lo{background:none;border:0;color:var(--muted);font-size:13px;cursor:pointer;font-family:var(--display);letter-spacing:.04em;padding:6px}
.lo:hover{color:var(--text)}
.wrap{max-width:1440px;margin:0 auto;padding:26px 24px 90px}

.lead{margin-bottom:22px}
.lead .ey{font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--accent2);display:block;margin-bottom:8px}
.lead h1{font-size:clamp(22px,3.4vw,30px)}
/* The change's bar on the record screen: pinned under the 64px header. */
.co-bar{position:sticky;top:64px;z-index:20;background:rgba(11,14,19,.94);backdrop-filter:blur(12px);margin:0 -24px 22px;padding:12px 24px 0;border-bottom:1px solid var(--line);display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:10px 16px}
/* The three pages of a change, as a row of tabs along the bottom of the bar. */
.tabs{flex-basis:100%;display:flex;gap:4px;margin-top:6px}
.tabs button{background:transparent;border:0;border-bottom:2px solid transparent;color:var(--muted);font-family:var(--display);font-size:13px;font-weight:600;letter-spacing:.04em;padding:10px 14px 11px;cursor:pointer;transition:all .16s;margin-bottom:-1px}
.tabs button:hover{color:var(--text)}
.tabs button[aria-pressed="true"]{color:var(--text);border-bottom-color:var(--accent2)}
/* Save and Cancel live on the bar while editing; Edit takes their place
   when the change is locked. A new change is always editing. */
#app.readonly #save,#app.readonly #cancel{display:none}
#app:not(.readonly) #bar-edit{display:none}
.readonly #blockers{display:none}
#save-msg{margin:-10px 0 16px;min-height:0}
#save-msg:empty{display:none}
/* A bar button that cannot act yet: dimmed, still clickable, and it says why. */
.bar-actions .btn[aria-disabled="true"]{opacity:.45;cursor:help}
.msg.why{color:var(--steel)}
/* The Activity tab's timeline: newest first, one line per event. */
.timeline{display:flex;flex-direction:column}
.tl-row{display:grid;grid-template-columns:150px 1fr;gap:14px;padding:10px 0;border-bottom:1px solid var(--line)}
.tl-row:last-child{border-bottom:0}
.tl-when{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;padding-top:2px}
.tl-what b{font-weight:600}
.tl-who{display:block;font-size:12px;color:var(--muted);margin-top:2px}
.tl-note{font-size:12.5px;color:var(--muted);margin-top:4px}
@media (max-width:720px){.tl-row{grid-template-columns:1fr}}
.co-bar .lead-text{min-width:0}
.co-bar h1{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.co-bar .ey{margin-bottom:4px}
.bar-actions{display:flex;gap:8px;flex:0 0 auto;flex-wrap:wrap;justify-content:flex-end}
@media (max-width:720px){.co-bar{position:static;flex-direction:column;align-items:stretch}}
/* Read-only: a saved change opens locked. Edit unlocks it. Attachments and
   the Onward card stay live; they are not part of Save. */
.readonly .editable{opacity:.82}
.readonly .editable :is(input,select,textarea,button,.seg){pointer-events:none}
.readonly .editable input,.readonly .editable select,.readonly .editable textarea{background:transparent;border-color:transparent;border-bottom-color:var(--line)}
.readonly .editable .actions{display:none}
.readonly .editable #blockers{display:none}

/* ---------- tiles ----------
   Each tile is one specific way money leaks, and each is a filter as well
   as a count. A tile that says 3 and shows 4 rows on click would make the
   whole row of numbers untrustworthy, so both come from the same table. */
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;margin-bottom:22px}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px 18px;text-align:left;cursor:pointer;transition:all .18s;color:var(--text);font-family:var(--body)}
.tile:hover{background:var(--panel2);border-color:var(--line2)}
.tile[aria-pressed="true"]{border-color:var(--accent);background:var(--panel2);box-shadow:inset 0 0 0 1px var(--accent)}
.tile .n{font-family:var(--display);font-weight:700;font-size:28px;line-height:1.1;font-variant-numeric:tabular-nums}
.tile .k{font-size:11.5px;font-family:var(--display);font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:6px}
.tile .v{font-size:12.5px;color:var(--steel);margin-top:3px;font-variant-numeric:tabular-nums}
.tile.alarm .n{color:var(--err)}
.tile.accent .n{color:var(--accent2)}
.tile.warn .n{color:var(--warn)}
.tile.ok .n{color:var(--ok)}
.tile.zero .n{color:var(--muted)}

/* ---------- controls ---------- */
.controls{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:16px}
.controls input[type=search]{max-width:340px;-webkit-appearance:none;appearance:none}
.chip{background:transparent;border:1px solid var(--line2);border-radius:999px;color:var(--muted);font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.06em;padding:7px 14px;cursor:pointer;transition:all .16s}
.chip:hover{color:var(--text);border-color:var(--steel)}
.chip[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#fff}
.spacer{flex:1}
.stale{font-size:12.5px;color:var(--muted)}

/* ---------- the register ---------- */
.tablewrap{border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--panel)}
.scroll{overflow-x:auto}
table{width:100%;border-collapse:collapse;min-width:1080px}
/* Scoped to the register. The breakdown table has its own header rule and must
   NOT be sticky: inside a card it floats the labels down over the rows. */
.tablewrap thead th{font-family:var(--display);font-size:11px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);text-align:left;padding:11px 10px;background:var(--bg2);border-bottom:1px solid var(--line);white-space:nowrap;position:sticky;top:0;z-index:5}
tbody td{padding:11px 10px;border-bottom:1px solid var(--line);font-size:14px;vertical-align:top}
tbody tr:last-child td{border-bottom:0}
tbody tr:hover{background:var(--panel2)}
/* The whole row is the way into a change, not just the reference. */
tbody tr[data-id]{cursor:pointer}
tbody tr[data-id] .btn,tbody tr[data-id] a{cursor:pointer}
.num{text-align:right;font-family:var(--num);font-variant-numeric:tabular-nums;white-space:nowrap}
.ref{font-family:var(--display);font-weight:600;font-size:13.5px;white-space:nowrap}
.ref .their{display:block;font-family:var(--body);font-weight:400;font-size:12px;color:var(--muted)}
.proj{white-space:nowrap}
.proj .code{font-family:var(--display);font-weight:600;font-size:13px}
.proj .nm{display:block;font-size:12px;color:var(--muted);max-width:150px;overflow:hidden;text-overflow:ellipsis}
.subj{max-width:250px}
/* Two lines, then an ellipsis. A four-line subject pushed every other
   row apart and bought nothing: the full text is on the record. */
.subj .t{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.subj .gc{display:block;font-size:12px;color:var(--muted)}
/* A change with no project code at all. Not an error, and not something to
   paper over with a blank: the code is missing and that is worth seeing. */
.nocode{color:var(--warn)}

.pill{display:inline-block;font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;padding:3px 9px;border-radius:999px;border:1px solid var(--line2);color:var(--muted);white-space:nowrap}
.pill.accent{color:var(--accent2);border-color:rgba(107,163,208,.4);background:rgba(74,123,166,.12)}
.pill.ok{color:var(--ok);border-color:rgba(63,157,107,.4);background:rgba(63,157,107,.1)}
.pill.warn{color:var(--warn);border-color:rgba(201,162,39,.4);background:rgba(201,162,39,.1)}
.pill.alarm{color:#e8897e;border-color:rgba(207,91,78,.45);background:rgba(207,91,78,.12)}
.pill.muted{color:var(--muted)}

/* The deadline column. 'unknown' is styled as its own thing rather than as
   a blank, because a job whose window nobody has read out of the subcontract
   is not a job without a deadline. */
.due{white-space:nowrap;font-size:13px}
.due.blown{color:#e8897e;font-weight:600}
.due.hot{color:var(--warn);font-weight:600}
.due.warm{color:var(--steel)}
.due.ok{color:var(--muted)}
.due.met{color:var(--muted)}
.due.unknown{color:var(--muted);font-style:italic}

/* Who recorded it. A name, never a status: there is nothing to claim. */
.who-cell{white-space:nowrap;font-size:13px;color:var(--muted)}
.who-cell b{color:var(--text);font-weight:600}
.who-cell .nobody{color:var(--muted)}

/* Inside a job the project column would repeat the job on every row. */
.one-project .proj{display:none}
.proj a.code{color:var(--text)}
.proj a.code:hover{color:var(--accent2)}

/* ---------- the home screen: one card per job ----------
   A card carries the same three figures as the tiles inside the job and
   they come from the same function, so the two cannot disagree. The whole
   card is a link. */
.projects{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}
.pcard{display:block;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:18px 20px;color:var(--text);transition:all .18s}
.pcard:hover{background:var(--panel2);border-color:var(--steel)}
.pcard-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.pcard-head .code{font-family:var(--display);font-weight:700;font-size:18px;letter-spacing:-.01em}
.pcard-head .n{font-size:12px;color:var(--muted);white-space:nowrap}
.pcard-name{font-size:14px;color:var(--text);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pcard-gc{font-size:12px;color:var(--muted);margin-top:2px}
.pcard-figs{display:grid;grid-template-columns:1fr 1fr 1fr;grid-template-rows:auto auto;grid-auto-flow:column;gap:2px 10px;margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
.pcard-figs .fig b{display:block;font-family:var(--display);font-weight:700;font-size:22px;line-height:1.1;font-variant-numeric:tabular-nums}
.pcard-figs .fig span{display:block;font-size:11.5px;color:var(--steel);font-variant-numeric:tabular-nums;margin-top:2px}
.pcard-figs .fig.accent b{color:var(--accent2)}
.pcard-figs .fig.ok b{color:var(--ok)}
.pcard-figs .fig.zero b,.pcard-figs .fig.zero span{color:var(--muted)}
/* The label sits under its figure: column flow over two rows puts each
   em beneath the fig that precedes it in the markup. */
.pcard-figs em{font-style:normal;font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.projects .empty{grid-column:1/-1;background:var(--panel);border:1px solid var(--line);border-radius:10px}

/* The contract line on a card: signed for, worth now. */
.pcard-contract{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:baseline;font-size:12.5px;color:var(--steel);margin-top:8px;font-variant-numeric:tabular-nums}
.pcard-contract b{color:var(--text);font-weight:600}
.pcard-contract .arr{color:var(--muted)}
.pcard-contract .pot{color:var(--accent2)}
.pcard-contract .nc{color:var(--muted);font-size:11.5px}
.pcard-contract.unset{color:var(--muted);font-style:italic}

/* Inside a job: one row per contract it bills against. The base is a
   button that turns into an input; everything else is derived from it. */
.contracts{display:grid;gap:10px;margin-bottom:14px}
.contract{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 18px;display:grid;grid-template-columns:minmax(150px,1fr) 4fr;gap:8px 20px;align-items:center}
.cwho{display:flex;flex-direction:column;gap:2px;min-width:0}
.cnum{font-family:var(--display);font-weight:700;font-size:15px;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.clabel{font-size:12px;color:var(--muted)}
.clabel.dim{font-style:italic;opacity:.7}
.cwho-btn{background:transparent;border:1px solid transparent;border-radius:6px;padding:4px 8px;margin:-4px -8px;text-align:left;color:var(--text);cursor:pointer;font:inherit}
.cwho-btn:hover{border-color:var(--steel);background:var(--panel2)}
.cedit{gap:6px}
.cedit-in{background:var(--bg2);border:1px solid var(--accent);border-radius:6px;color:var(--text);font-size:13px;padding:5px 8px;width:100%}
.cedit-actions{display:flex;gap:6px;margin-top:2px}
.cfigs{display:grid;grid-template-columns:repeat(5,minmax(120px,1fr));gap:6px 14px}
.cfig{min-width:0}
.cfig b,.cfig .base{display:block;font-family:var(--display);font-weight:700;font-size:17px;line-height:1.2;font-variant-numeric:tabular-nums;color:var(--text);white-space:nowrap}
.cfig span{display:block;font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:3px}
.cfig.accent b{color:var(--accent2)}
.cfig.zero b{color:var(--muted)}
.cfig.total b{color:var(--steel)}
.cfig .base{background:none;border:0;border-bottom:1px dashed var(--line2);padding:0 0 1px;cursor:text;text-align:left;font-family:var(--display)}
.cfig .base:hover{border-bottom-color:var(--accent2)}
.cfig .base.unset{color:var(--muted);font-weight:600;font-style:italic}
.base-edit{width:100%;max-width:170px;background:var(--bg2);border:1px solid var(--accent);border-radius:6px;color:var(--text);font-family:var(--display);font-weight:700;font-size:16px;padding:2px 8px;font-variant-numeric:tabular-nums}
.cwarn{margin:0;font-size:12.5px;color:var(--warn)}
@media (max-width:820px){.contract{grid-template-columns:1fr}.cfigs{grid-template-columns:repeat(auto-fit,minmax(120px,1fr))}}

.empty{padding:60px 24px;text-align:center;color:var(--muted)}
.empty h3{font-size:16px;color:var(--text);margin-bottom:6px}
.loading{padding:60px 24px;text-align:center;color:var(--muted)}

.foot{margin-top:14px;font-size:12.5px;color:var(--muted);display:flex;gap:18px;flex-wrap:wrap}
.foot .tot{font-variant-numeric:tabular-nums}

@media (max-width:720px){
  .wrap{padding:20px 14px 70px}
  .bar{padding:0 14px}
}

/* ============================================================
   Record screen  (card, two-column, line table, totals panel)
   ------------------------------------------------------------
   Written for a pricing screen that was cancelled on 2026-08-25:
   the hub records a result priced elsewhere, it does not price.
   The layout carries over to the record screen unchanged, so this
   stays. The markup it styles does not exist yet.
   ============================================================ */
/* minmax(0,1fr), not 1fr: a grid track defaults to min-width:auto, so the
   left column could not shrink below the 900px min-width of the lines
   table inside it. Between 1080px and about 1310px that pushed the whole
   PAGE sideways, header and all, instead of scrolling the table inside
   the .scroll wrapper that is already there for it. */
/* One column. The breakdown is the widest thing on the page and it was
   competing with a 320px sidebar for room, which is what forced it to
   scroll sideways. Status and value goes underneath it instead. */
.price-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:22px;align-items:start}
/* Schedule of Values and Activity read as a wide table beside a narrow
   panel, so they get a second column on a wide screen; General is forms and
   stays one column. The panel sticks under the bar. */
@media (min-width:1100px){
  .tabpane[data-pane="sov"] .price-grid{grid-template-columns:minmax(0,1fr) 380px}
  .tabpane[data-pane="activity"] .price-grid{grid-template-columns:minmax(0,1fr) 420px}
  .tabpane[data-pane="sov"] aside,.tabpane[data-pane="activity"] aside{position:sticky;top:182px}
}
/* A sub-heading inside a card, for a second subject that does not earn a card. */
/* "The change": the words wide on the left, the three facts stacked on the
   right. One column below tablet width. */
.cd-grid{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(0,1fr);gap:0 24px}
.cd-words .field textarea{min-height:168px}
@media (max-width:820px){.cd-grid{grid-template-columns:1fr}}
.sub-h{font-family:var(--display);font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin:22px 0 12px;padding-top:16px;border-top:1px solid var(--line)}
/* Controls the bar presses on the person's behalf. Present, never shown. */
.proxied{display:none}
/* The two self-pushing destinations, one line each. The dot is the state:
   done, blocked (nothing to push yet), failed, or waiting. The button shows
   only while pressing it would do something. */
.sync{display:grid;grid-template-columns:10px minmax(0,1fr) auto;gap:12px;align-items:center;padding:12px 0;border-bottom:1px solid var(--line)}
.sync b{display:block;font-size:14px;font-weight:600}
.sync .push-state{margin:2px 0 0;font-size:12.5px}
.sync .dot{width:10px;height:10px;border-radius:50%;background:var(--line2)}
.sync:has(.push-state.done) .dot{background:var(--ok)}
.sync:has(.push-state.failed) .dot{background:var(--err)}
.sync:has(.push-state.blocked) .dot{background:transparent;border:1px solid var(--line2)}
.sync button:disabled{display:none}

.card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:20px;margin-bottom:18px}
.card h2{font-size:12px;font-family:var(--display);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--accent2);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--line)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.row3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
.row4{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:16px}
.row4+.row4{margin-top:16px}
@media (max-width:960px){.row4{grid-template-columns:1fr 1fr}}
@media (max-width:720px){.row2,.row3,.row4{grid-template-columns:1fr}}
select,textarea,input[type=date],input[type=number]{width:100%;background:var(--panel2);border:1px solid var(--line2);border-radius:6px;color:var(--text);font-family:var(--body);font-size:15px;padding:11px 12px}
select:focus,textarea:focus,input[type=date]:focus,input[type=number]:focus{outline:none;border-color:var(--accent)}
textarea{min-height:74px;resize:vertical}
input[type=number]{font-family:var(--num);font-variant-numeric:tabular-nums;text-align:right}

/* Two-state switch. A radio pair rather than a dropdown because the choice
   changes what the whole form means and should never be one click from wrong. */
.seg{display:inline-flex;border:1px solid var(--line2);border-radius:6px;overflow:hidden}
.seg button{background:transparent;border:0;color:var(--muted);font-family:var(--display);font-size:13px;font-weight:600;padding:10px 18px;cursor:pointer;transition:all .16s}
.seg button[aria-pressed="true"]{background:var(--accent);color:#fff}
.seg button:not(:last-child){border-right:1px solid var(--line2)}
.seg-sm button{font-size:12px;padding:7px 14px;letter-spacing:.06em}
.chip:disabled{opacity:.5;cursor:default}
/* Archived jobs, shown on request: dimmed, and labelled, so their history
   reads as history. */
.pcard.archived{opacity:.7}
.pcard.archived:hover{opacity:1}
.arch{display:inline-block;font-family:var(--display);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);border:1px solid var(--line2);border-radius:4px;padding:1px 6px;vertical-align:middle}
/* The list layout of the home screen: the card's figures as columns. */
.projects.aslist{display:block}
.plist{min-width:900px}
.plist td.ref .code{font-family:var(--display);font-weight:700;font-size:15px;color:var(--text)}
.plist td.ref .arch{margin-left:8px}
.plist td.subj a{color:var(--text)}
.plist td.fig b{display:block;font-family:var(--display);font-weight:700;font-size:16px;line-height:1.1;font-variant-numeric:tabular-nums}
.plist td.fig span{display:block;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.plist td.fig.accent b{color:var(--accent2)}
.plist td.fig.ok b{color:var(--ok)}
.plist td.fig.zero b,.plist td.fig.zero span{color:var(--muted)}
.plist tr.archived td{opacity:.7}
.plist tbody tr:hover td{background:var(--panel2)}

/* ---------- lines ---------- */
.lines{width:100%;border-collapse:collapse;min-width:900px}
.lines th{font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);text-align:left;padding:8px 8px;border-bottom:1px solid var(--line);white-space:nowrap}
.lines td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.lines tr:last-child td{border-bottom:0}
.lines input,.lines select{font-size:14px;padding:8px 9px}
.lines .amt{text-align:right;font-family:var(--num);font-variant-numeric:tabular-nums;font-size:14.5px;padding-top:16px;white-space:nowrap}
.lines .amt.credit{color:#e8897e}
.lines .del{background:none;border:0;color:var(--muted);cursor:pointer;font-size:18px;line-height:1;padding:12px 6px}
.lines .del:hover{color:var(--err)}
.w-code{width:210px}.w-desc{min-width:230px}.w-num{width:104px}.w-dir{width:104px}.w-uom{width:78px}

/* The routing note. This is rule 7 in the one place it actually bites: the
   moment somebody picks a code from the 8xx block. */
.lines tr.noterow td{padding-top:0;border-bottom:1px solid var(--line)}
.note{margin-top:0;font-size:12px;color:var(--warn);background:rgba(201,162,39,.08);border-left:2px solid rgba(201,162,39,.5);padding:7px 10px;border-radius:0 4px 4px 0}
.issues{margin-top:6px;font-size:12px;color:#e8897e}

/* ---------- totals ---------- */
/* Not sticky. It was sticky with no stacking context, so the Onward card
   below it scrolled up and painted its Push and Copy row buttons straight
   through the totals figures. In one column there is nothing to stick to. */
.totals .tcols{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:28px;align-items:start}
.totals .tinputs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:0 18px;align-items:start}
.totals .tmoney{border-left:1px solid var(--line);padding-left:26px}
@media (max-width:900px){.totals .tcols,.totals .tinputs{grid-template-columns:1fr}
  .totals .tmoney{border-left:0;padding-left:0}}
.tline{display:flex;justify-content:space-between;gap:12px;padding:7px 0;font-size:14px}
.tline dt{color:var(--muted)}
.tline dd{font-family:var(--num);font-variant-numeric:tabular-nums}
.tline.sub{border-top:1px solid var(--line);margin-top:6px;padding-top:12px;font-weight:600}
.tline.grand{border-top:1px solid var(--line2);margin-top:8px;padding-top:14px;font-size:19px;font-family:var(--display);font-weight:700}
.tline.grand dt{color:var(--text)}
.tline.credit dd{color:#e8897e}

/* Anything the GC never sees is marked, so nobody has to remember which is
   which when the quotation is being checked one last time before it goes. */
.internal{display:inline-block;font-size:9.5px;font-family:var(--display);font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);border:1px solid var(--line2);border-radius:3px;padding:1px 5px;margin-left:6px;vertical-align:1px}
.capwarn{margin-top:12px;font-size:12.5px;color:var(--warn);background:rgba(201,162,39,.08);border:1px solid rgba(201,162,39,.35);padding:10px 12px;border-radius:6px}
#blockers .blocker{margin:0 0 16px}
.blocker{margin-top:12px;font-size:12.5px;color:#e8897e;background:rgba(207,91,78,.08);border:1px solid rgba(207,91,78,.35);padding:10px 12px;border-radius:6px}
.terms{font-size:12.5px;color:var(--muted);margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
/* b is for figures, hence the tabular font. em is for a gap somebody has to
   go and close, so it takes the warning colour and stays in the body face. */
.terms b{color:var(--steel);font-weight:600;font-family:var(--num)}
.terms em{color:var(--warn);font-style:normal;font-weight:600}
.actions{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
.actions .btn{flex:1;min-width:130px}
.backlink{font-size:13px;color:var(--muted);margin-bottom:14px;display:inline-block}
.backlink:hover{color:var(--text)}

/* The reference and the next action both open the change. A pill that is a
   link still has to look like a pill, not like body copy that turned blue. */
.ref a{color:var(--text);text-decoration:none}
.ref a:hover{color:var(--accent2);text-decoration:underline}
a.pill{text-decoration:none}
a.pill:hover{filter:brightness(1.25)}

/* A disabled input has to look disabled. Hours are switched off on material and
   other lines, and an enabled-looking empty box invites somebody to type a
   number the budget would then take literally. */
.lines input:disabled,.lines select:disabled{opacity:.35;cursor:not-allowed;background:var(--bg2)}
/* The cost code box and its menu. */
.code-in{width:100%;min-width:220px}
.code-menu{position:absolute;z-index:40;max-height:320px;overflow-y:auto;background:var(--panel);border:1px solid var(--line2);border-radius:8px;box-shadow:0 12px 32px rgba(0,0,0,.45);padding:4px}
.code-opt{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:baseline;padding:8px 10px;border-radius:6px;cursor:pointer;font-size:13.5px}
.code-opt b{font-family:var(--num);font-weight:600;white-space:nowrap}
.code-opt span{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.code-opt em{font-style:normal;font-size:11px;color:var(--muted);letter-spacing:.06em;text-transform:uppercase}
.code-opt:hover,.code-opt.active{background:var(--panel2)}
.code-opt.active b{color:var(--accent2)}
.code-none{padding:10px 12px;color:var(--muted);font-size:13px}

/* Derived money. Read only by construction, not by a disabled attribute: there
   is no input to disable, because there is nothing to type. */
.derived{margin:18px 0 4px;padding-top:14px;border-top:1px solid var(--line)}
.derived .tline dt{display:flex;align-items:center;gap:6px}
.derived .tline dd{font-family:var(--num);font-variant-numeric:tabular-nums}

/* OH&P: two boxes for one number. Whichever is typed drives, the other follows,
   and the follower is dimmed so it is obvious which one is being believed. */
.ohp{display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:340px}
.ohp-box{position:relative;display:flex;align-items:center}
.ohp-box input{padding-right:26px}
.ohp-box .unit{position:absolute;right:10px;color:var(--muted);font-size:13px;pointer-events:none}
.ohp-box .unit.pre{position:absolute;left:11px;right:auto}
.ohp-box:has(.unit.pre) input{padding-left:22px;padding-right:12px}
.ohp-box.follows input{color:var(--muted)}

/* The OH&P percentage sits beside its label, because the GC reads the two
   together: a percentage is what gets checked against a subcontract cap. */
.derived .pct{font-family:var(--num);font-size:12.5px;color:var(--steel);font-variant-numeric:tabular-nums}
/* A gap the screen will close for you, rather than a warning. */
.hint.learn{color:var(--accent2)}

/* ---------- the export sheet ----------
   A native dialog, so focus trapping and Escape are the browser's job
   rather than three more listeners nobody maintains. */
.sheet-dialog{
  width:min(560px,calc(100vw - 32px));border:1px solid var(--line2);border-radius:12px;
  background:var(--panel);color:var(--text);padding:0;
}
.sheet-dialog::backdrop{background:rgba(6,8,12,.72);backdrop-filter:blur(3px)}
.sheet-form{padding:24px 26px 22px}
.sheet-head{display:flex;align-items:center;gap:14px;margin-bottom:20px}
.sheet-head h2{font-family:var(--display);font-size:19px;font-weight:700;margin:0;flex:1}
.sheet-head .x{background:none;border:0;color:var(--muted);font-size:26px;line-height:1;cursor:pointer;padding:0 4px}
.sheet-head .x:hover{color:var(--text)}
.sheet-dialog .field{margin-bottom:18px}
.sheet-dialog .seg{display:flex;width:100%}
.sheet-dialog .seg button{flex:1}
.sheet-dialog .actions{display:flex;gap:10px;margin-top:22px}
.sheet-dialog .actions .btn{flex:1}

/* What is about to come out, before it comes out. A count and a total is
   enough to catch the wrong job or the wrong list without opening the file. */
.ex-summary{
  display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:14px 16px;margin-bottom:12px;
}
.ex-summary .n{font-family:var(--display);font-size:15px;font-weight:600}
.ex-summary .n em{color:var(--muted);font-style:normal;font-weight:400}
.ex-summary .v{font-family:var(--num);font-variant-numeric:tabular-nums;font-size:20px;font-weight:600}
.ex-summary .v span{font-size:12px;color:var(--muted);display:block;text-align:right;font-family:var(--display);letter-spacing:.06em;text-transform:uppercase}
.ex-summary.none .v{color:var(--muted)}

.ex-warn{font-size:12.5px;padding:9px 12px;border-radius:6px;margin-bottom:8px;border:1px solid}
.ex-warn.alarm{color:#e8897e;background:rgba(207,91,78,.08);border-color:rgba(207,91,78,.35)}
.ex-warn.warn{color:var(--warn);background:rgba(201,162,39,.08);border-color:rgba(201,162,39,.35)}
.ex-warn.muted{color:var(--muted);background:transparent;border-color:var(--line)}

/* The internal toggle is deliberately a bit of friction: a checkbox that has
   to be found and ticked, and a red panel that appears when it is. */
.ex-internal{display:flex;gap:11px;align-items:flex-start;margin-top:18px;padding-top:16px;border-top:1px solid var(--line);cursor:pointer}
.ex-internal input{width:16px;height:16px;margin:3px 0 0;flex:0 0 auto;accent-color:var(--err)}
.ex-internal b{display:block;font-size:14px;font-weight:600;text-transform:none;letter-spacing:0;color:var(--text)}
.ex-internal em{display:block;font-style:normal;font-size:12.5px;color:var(--muted);margin-top:3px;line-height:1.5}
.ex-danger{
  margin-top:12px;font-size:12.5px;color:#e8897e;background:rgba(207,91,78,.1);
  border:1px solid rgba(207,91,78,.45);padding:11px 13px;border-radius:6px;
}

/* ---------- importing a payload ---------- */
.drop{
  border:1.5px dashed var(--line2);border-radius:9px;padding:22px 18px;text-align:center;
  margin-bottom:18px;transition:all .16s;cursor:pointer;
}
.drop:hover,.drop.over{border-color:var(--accent);background:rgba(74,123,166,.07)}
.drop p{margin:0;font-size:14px}
.drop .hint{margin-top:5px}
.linkish{background:none;border:0;color:var(--accent2);cursor:pointer;font:inherit;padding:0;text-decoration:underline}
#import textarea{font-family:var(--num);font-size:12.5px;line-height:1.5;resize:vertical}

/* What the payload will do, and what is wrong with it. Errors block, warnings
   do not, and the two never share a colour. */
.im-block{border:1px solid var(--line);border-radius:8px;padding:14px 16px;margin-bottom:12px;background:var(--panel2)}
.im-block h3{font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0 0 10px}
.im-line{display:flex;justify-content:space-between;gap:14px;font-size:13.5px;padding:3px 0}
.im-line dt{color:var(--muted)}
.im-line dd{margin:0;font-family:var(--num);font-variant-numeric:tabular-nums}
.im-msg{font-size:12.5px;padding:9px 12px;border-radius:6px;margin-bottom:8px;border:1px solid}
.im-msg.err{color:#e8897e;background:rgba(207,91,78,.08);border-color:rgba(207,91,78,.35)}
.im-msg.warn{color:var(--warn);background:rgba(201,162,39,.08);border-color:rgba(201,162,39,.35)}
.im-msg .where{display:block;font-family:var(--display);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;opacity:.75;margin-bottom:3px}
.im-cand{display:block;border:1px solid var(--line2);border-radius:7px;padding:10px 13px;margin-bottom:7px;color:var(--text);font-size:13.5px}
.im-cand:hover{border-color:var(--accent);background:var(--panel2)}
.im-cand b{font-family:var(--num)}
.im-cand span{display:block;color:var(--muted);font-size:12px;margin-top:2px}

/* ---------- pushing onward ---------- */
.onward{margin-top:18px}
.push{padding:14px 0;border-bottom:1px solid var(--line)}
.push:last-child{border-bottom:0;padding-bottom:0}
.push:first-of-type{padding-top:0}
.push-head{display:flex;align-items:flex-start;gap:12px}
.push-head>div{flex:1;min-width:0}
.push-head b{display:block;font-size:14px;font-weight:600}
.push-head em{display:block;font-style:normal;font-size:12px;color:var(--muted);margin-top:2px;line-height:1.45}
.push-head .btn{flex:0 0 auto;margin-top:2px}
.push.danger{border-bottom:0;margin-top:6px;padding-top:18px;border-top:1px dashed var(--line2)}
.btn-danger:not(:disabled){border-color:rgba(207,91,78,.5);color:#e8897e}
.btn-danger:not(:disabled):hover{background:rgba(207,91,78,.12);border-color:var(--err)}
/* Why a push is closed, in the same place the button is, so nobody has to hunt
   for the reason it did nothing. */
.push-state{font-size:12.5px;margin:9px 0 0;color:var(--muted)}
.push-state.done{color:var(--ok)}
.push-state.blocked{color:var(--warn)}
.push-state.failed{color:#e8897e}

/* ---------- attachments, and sending ----------

   The card is a list of documents with two decisions attached to each: what it
   is, and whether it goes to the general contractor. Both are made here, while
   somebody is looking at the file, rather than in a dialog full of filenames
   at the moment of sending. */

.msg.good{color:var(--ok)}
.msg.bad{color:var(--err)}

.attach td{vertical-align:middle}
.attach .w-kind{width:250px}
.attach .w-share{width:130px}
.attach .w-tick{width:34px}
.attach select{min-height:34px;padding:5px 8px;font-size:13px}
.attach b{font-weight:600}
/* The file name is the way to open the file. A button, not a link, because
   there is no URL to give it: the bytes are fetched as the caller. Stays
   clickable while the change is read-only - looking is not editing. */
.attach .filelink{background:none;border:0;padding:0;margin:0;color:var(--text);font:inherit;font-weight:600;text-align:left;cursor:pointer;text-decoration:underline;text-decoration-color:var(--line2);text-underline-offset:3px}
.attach .filelink:hover{color:var(--accent);text-decoration-color:var(--accent)}
.readonly .editable .attach .filelink{pointer-events:auto}
.attach .hint{margin-top:2px;font-size:11.5px}
.lines td.empty{padding:20px 8px;text-align:center;color:var(--muted)}

/* A file that cannot become pages inside the document. Said quietly on the
   card and loudly in the picker, because that is where it matters. */
.dim{color:var(--muted)}

.rowtools{white-space:nowrap;text-align:right}
.iconbtn{
  background:none;border:1px solid transparent;border-radius:5px;color:var(--muted);
  cursor:pointer;font-size:14px;line-height:1;padding:5px 7px;
}
.iconbtn:hover:not(:disabled){color:var(--text);border-color:var(--line2)}
.iconbtn:disabled{opacity:.3;cursor:default}

/* The send dialog is wider than the import one: the picker has to show a
   filename and what will happen to it on one line, and wrapping either of
   those is how somebody sends the wrong thing. */
.sheet-dialog.wide{width:min(720px,calc(100vw - 32px))}
.sheet-dialog .row{display:flex;gap:14px}
.sheet-dialog .row .field{flex:1;min-width:0}
.sheet-dialog .picker td{border-bottom:1px solid var(--line)}
.sheet-dialog .picker input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent)}
.sheet-dialog details summary{
  font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);cursor:pointer;padding:6px 0;
}
.sheet-dialog details summary:hover{color:var(--text)}
#sd-body{font-size:13px;line-height:1.55;resize:vertical}
#sd-notes .blocker,#sd-notes .note{margin-top:8px}

/* What has already gone out. A built-but-unsent package is the normal state
   between pressing Preview and pressing Send, so it must not read as a fault. */
.sent-row{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  font-size:12.5px;padding:8px 0;border-top:1px solid var(--line);
}
.sent-row:first-child{margin-top:10px}
.sent-row > span:first-child{font-family:var(--display);font-weight:600}
.sent-row.sent > span:first-child{color:var(--ok)}
.sent-row.built > span:first-child{color:var(--muted)}
.sent-row.failed > span:first-child{color:#e8897e}
.sent-row .hint{flex:1;min-width:0}
.sent-row .blocker{flex-basis:100%}

/* ---------- the submittals module ----------

   Almost everything here reuses the change register's own classes: .tiles,
   .tile, .pill, .pcard, .card, .lines, .timeline. What is below is only what
   the change side has no equivalent for. */

/* A tile whose figure is a count and whose second line is who is holding it,
   rather than a dollar amount. */
.tile .v{min-height:16px}

.pcard-figs.four{grid-template-columns:repeat(4,1fr)}

/* The register's own columns. */
.sec{white-space:nowrap;font-family:var(--num);font-size:13px}
.sec .nm,.proj .nm,.ref .nm{display:block;font-family:var(--body);font-size:11.5px;color:var(--muted);max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.typecell,.revcell{white-space:nowrap;font-size:13px;color:var(--muted)}
.limitmark{display:block;font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#e8897e;margin-top:3px;cursor:help}

/* The package screen. A list of facts, then the tables. */
.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px 22px;margin:0}
.fact dt{font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.fact dd{margin:4px 0 0;font-size:14px}
.pnotes{margin-top:16px;font-size:13.5px;color:var(--steel);line-height:1.6;white-space:pre-wrap}

.revs td,.finds td{vertical-align:top;font-size:13px}
.revlabel{font-family:var(--display);font-weight:700;white-space:nowrap}
/* The consultant's own code, in their own spelling. Never normalised: the
   codes differ per job and per reviewer, and tidying them would assert that
   two different words are the same word. */
.theircode{font-family:var(--num);font-size:12.5px;white-space:normal;max-width:190px}
.revs .nm,.finds .nm{display:block;font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.5;white-space:normal}
.summ,.said{max-width:280px;white-space:normal;line-height:1.55}
/* What a finding means for us, which is a different thing from what we will
   do about it. Kept visibly separate for that reason. */
.said.means{color:var(--steel);border-left:2px solid var(--line2);padding-left:10px}
.finds tr.closed td{opacity:.6}
.src{font-size:12px;color:var(--muted);max-width:170px;white-space:normal}
.fcount{font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin:0 0 12px}

/* ---------- obligations, and what counts as owed ----------

   The register answers two questions about a job: what have we raised, and
   what did the specification ask for in the first place. The second is only
   worth reading if its numbers are believable, which is what treatment is
   for: 116 lines read off Cameron's spec are not 116 owed submittals. */

.modes{margin-bottom:16px}

.coverage{margin-bottom:18px}
.cov-figs{display:flex;flex-wrap:wrap;gap:10px 38px;align-items:baseline}
.cov-fig b{display:block;font-family:var(--display);font-weight:700;font-size:26px;line-height:1.1;font-variant-numeric:tabular-nums}
.cov-fig span{display:block;font-family:var(--display);font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:3px}
.cov-fig.ok b{color:var(--ok)}
.cov-fig.warn b{color:var(--warn)}
.cov-fig.zero b{color:var(--muted)}
/* The obligations that are deliberately not chased, said out loud. Excluded
   from the figures above and one click away, so they read as a decision
   somebody made rather than as rows that quietly went missing. */
.cov-note{margin:12px 0 0;font-size:12.5px;color:var(--steel);line-height:1.6;max-width:76ch}
/* No obligation on file is NOT full coverage. Steveston is exactly this. */
.cov-none{margin:0;font-size:13px;color:var(--warn);background:rgba(201,162,39,.08);border:1px solid rgba(201,162,39,.35);padding:11px 13px;border-radius:6px;max-width:80ch;line-height:1.6}

.treatments{display:flex;gap:8px;flex-wrap:wrap}

.raised{white-space:normal;font-size:13px;max-width:170px}
.raised a{font-family:var(--display);font-weight:600;color:var(--text)}
.raised a:hover{color:var(--accent2)}
.raised .nothing{color:var(--warn);font-size:12.5px}
#reqwrap tr.gap td:first-child{box-shadow:inset 2px 0 0 var(--warn)}
/* A held-out obligation names the rows that took it over. Listing them is
   what makes the exclusion checkable rather than a claim. */
.kept{display:block;font-size:11.5px;color:var(--muted);margin-top:4px;line-height:1.7}
.kept code{font-family:var(--num);font-size:11px;background:var(--bg2);border:1px solid var(--line);border-radius:4px;padding:1px 5px;white-space:nowrap}
