:root{
  --navy:#15263C; --blue:#1C9FE0; --ink:#111; --line:#e3e7ee; --bg:#f4f6fa;
  --ok:#1f9d55; --warn:#b7791f; --bad:#c0392b; --muted:#6b7688;
}
*{box-sizing:border-box}
body{margin:0;font-family:Inter,'Segoe UI',system-ui,Arial,sans-serif;color:var(--ink);background:var(--bg)}
.topbar{display:flex;justify-content:space-between;align-items:center;background:var(--navy);color:#fff;padding:10px 20px}
.brandwrap{display:flex;flex-direction:column;line-height:1}
.brand{font-family:'Oswald','Arial Narrow',Impact,sans-serif;font-weight:700;font-size:26px;letter-spacing:1px;text-transform:uppercase;display:flex;align-items:center;gap:3px}
.brand .tri{display:inline-block;width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-bottom:18px solid var(--blue);margin:0 1px 2px}
.tagline{font-size:10px;letter-spacing:2px;color:#b9c4d6;margin-top:3px;font-weight:600}
.tagline span{color:var(--blue)}
.who label{font-size:12px;color:#b9c4d6;margin-right:8px}
.who select{padding:6px 8px;border-radius:6px;border:1px solid #2c3e5c;background:#0f1c30;color:#fff}
.badge{margin-left:10px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;background:var(--blue);color:#04283b;padding:3px 8px;border-radius:20px;font-weight:700}
.tabs{display:flex;gap:4px;background:#fff;padding:0 16px;border-bottom:1px solid var(--line)}
.tabs button{background:none;border:none;padding:14px 16px;font-size:14px;color:var(--muted);cursor:pointer;border-bottom:3px solid transparent}
.tabs button.active{color:var(--navy);border-bottom-color:var(--blue);font-weight:700}
main{padding:20px;max-width:1200px;margin:0 auto}
h2{margin:0 0 4px;color:var(--navy)}
.sub{color:var(--muted);font-size:13px;margin:0 0 16px}

/* lookahead board */
.weeks{display:flex;gap:16px;overflow-x:auto;padding-bottom:8px}
.week{min-width:300px;flex:1;background:#fff;border:1px solid var(--line);border-radius:10px;padding:12px}
.week h3{margin:0 0 10px;font-size:13px;color:var(--navy);text-transform:uppercase;letter-spacing:.5px}
.card{border:1px solid var(--line);border-left:4px solid var(--muted);border-radius:8px;padding:10px;margin-bottom:10px;background:#fff}
.card.ready{border-left-color:var(--ok)}
.card.blocked{border-left-color:var(--bad)}
.card.uncommitted{border-left-color:var(--warn)}
.card.done{opacity:.6;border-left-color:var(--muted)}
.card .code{font-size:11px;color:var(--muted);font-weight:700}
.card .name{font-weight:600;margin:2px 0}
.card .meta{font-size:12px;color:var(--muted);display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.pill{font-size:11px;padding:2px 7px;border-radius:20px;font-weight:700}
.pill.ready{background:#e6f6ec;color:var(--ok)}
.pill.blocked{background:#fdecea;color:var(--bad)}
.pill.uncommitted{background:#fdf3e2;color:var(--warn)}
.pill.done{background:#eef1f5;color:var(--muted)}
.card .commit{margin-top:8px;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.card .commit input{padding:4px;border:1px solid var(--line);border-radius:6px;font-size:12px}

table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden}
th,td{text-align:left;padding:10px 12px;font-size:13px;border-bottom:1px solid var(--line);vertical-align:top}
th{background:#f7f9fc;color:var(--navy);text-transform:uppercase;font-size:11px;letter-spacing:.5px}
tr:last-child td{border-bottom:none}
.status{font-weight:700;font-size:11px;text-transform:uppercase}
.status.open{color:var(--bad)} .status.in_progress{color:var(--warn)} .status.cleared{color:var(--ok)}
.tag{background:#eef1f5;color:var(--navy);font-size:11px;padding:2px 7px;border-radius:6px;text-transform:uppercase;font-weight:700}

button.btn, label.btn{background:var(--blue);color:#04283b;border:none;padding:8px 14px;border-radius:7px;font-weight:700;cursor:pointer;font-size:13px;display:inline-flex;align-items:center;justify-content:center}
button.btn.ghost, label.btn.ghost{background:#fff;border:1px solid var(--line);color:var(--navy)}
button.link{background:none;border:none;color:var(--blue);cursor:pointer;font-weight:600;font-size:12px;padding:0}
.panel{background:#fff;border:1px solid var(--line);border-radius:10px;padding:16px;margin-bottom:16px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}
.field label{display:block;font-size:12px;color:var(--muted);margin-bottom:3px}
.field input,.field select,.field textarea{width:100%;padding:8px;border:1px solid var(--line);border-radius:7px;font-size:13px;font-family:inherit}
.row-actions{display:flex;gap:10px}
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--navy);color:#fff;padding:10px 18px;border-radius:8px;opacity:0;transition:opacity .25s;font-size:13px;pointer-events:none}
.toast.show{opacity:1}
.empty{color:var(--muted);font-size:13px;padding:20px;text-align:center}

/* sync status + authorship */
.who{display:flex;align-items:center;gap:10px}
#syncPill{font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px}
#syncPill.synced{background:#123a2a;color:#57d38d}
#syncPill.syncing{background:#3a3212;color:#e0c04a}
#syncPill.offline{background:#3a1e1e;color:#ef8f8f}
.by{font-size:11px;color:var(--muted)}
.pend{color:var(--warn);font-weight:700}
.intbar{background:#eef3fb;border-bottom:1px solid var(--line);padding:7px 20px;font-size:12px;color:var(--navy)}
.intbar b{color:var(--navy)}.intbar .muted{color:var(--muted);margin-left:6px}
.pub{color:var(--blue);font-weight:700}
.muted{color:var(--muted)}

/* drawing-anchored capture */
.sheetwrap{position:relative;display:inline-block;border:1px solid var(--line);border-radius:8px;overflow:hidden;max-width:100%;background:#fff;cursor:crosshair}
.sheetwrap img{display:block;max-width:100%;height:auto}
.pin{position:absolute;transform:translate(-50%,-100%);cursor:pointer;font-size:22px;line-height:1;filter:drop-shadow(0 1px 1px rgba(0,0,0,.4))}
.pin.deficiency{color:#c0392b}.pin.rfi{color:#b7791f}.pin.observation{color:#2E7DD1}
.pin.photo{color:#1f9d55}.pin.inspection{color:#6b4fbb}
.pin.closed{opacity:.45}
.pindot{position:absolute;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;border:2px solid #fff}
.pinsheetnote{font-size:12px;color:var(--muted);margin:6px 0 12px}
.plancols{display:flex;gap:20px;flex-wrap:wrap}.plancol{flex:1;min-width:320px}
.pinrow{border:1px solid var(--line);border-left:4px solid var(--muted);border-radius:8px;padding:8px 10px;margin-bottom:8px;font-size:13px}
.pinrow.deficiency{border-left-color:#c0392b}.pinrow.rfi{border-left-color:#b7791f}
.pinrow.observation{border-left-color:#2E7DD1}.pinrow.photo{border-left-color:#1f9d55}.pinrow.inspection{border-left-color:#6b4fbb}

/* ---- T16 Field Mode ---- */
.field-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:12px; margin:14px 0 20px; }
.field-card { display:flex; align-items:center; gap:14px; padding:18px 16px; border:1px solid var(--line,#d8dee6);
  border-radius:12px; background:#fff; cursor:pointer; text-align:left; font:inherit; }
.field-card:hover { border-color:var(--navy,#12385e); box-shadow:0 2px 8px rgba(0,0,0,.08); }
.field-card-icon { font-size:28px; }
.field-card-title { font-weight:700; font-size:16px; color:var(--navy,#12385e); }
.field-card-sub { font-size:13px; color:var(--muted,#6b7683); }
.field-h3 { margin:18px 0 8px; font-size:14px; text-transform:uppercase; letter-spacing:.04em; color:var(--muted,#6b7683); }
.field-tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:10px; margin-bottom:20px; }
.field-tile { border:1px solid var(--line,#d8dee6); border-radius:10px; padding:12px; background:#fff; text-align:center; }
.field-tile-n { font-size:26px; font-weight:800; color:var(--navy,#12385e); }
.field-tile-l { font-size:12px; color:var(--muted,#6b7683); margin:2px 0 6px; }
.field-more { margin-top:8px; }
.field-more summary { cursor:pointer; font-weight:600; color:var(--navy,#12385e); padding:8px 0; }
.field-more-row { display:flex; flex-wrap:wrap; gap:8px; padding:8px 0; }

/* T19 Big Room */
.board-head{display:flex;justify-content:space-between;align-items:center;background:var(--navy);color:#fff;padding:12px 18px;border-radius:8px;margin-bottom:12px;font-size:18px}
.live-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--ok);margin:0 5px 0 12px;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
.bweeks{display:flex;gap:12px;overflow-x:auto;align-items:flex-start}
.bweek{flex:1;min-width:220px;background:#fff;border:1px solid var(--line);border-radius:8px;padding:10px}
.bweek h3{margin:0 0 8px;color:var(--navy);font-size:16px;text-transform:uppercase;letter-spacing:.5px}
.bcard{background:#fff;border:1px solid var(--line);border-radius:6px;padding:10px 12px;margin-bottom:8px;box-shadow:0 1px 2px rgba(0,0,0,.06)}
.bcard .bname{font-weight:700;font-size:15px;color:var(--ink)}
.bcard .bmeta{font-size:12px;color:var(--muted);margin-top:2px}
.bcard.proposed{background:#fff8e8;border-color:#e5c66a}
.bcard.done{opacity:.55}
.btn.small{padding:4px 10px;font-size:12px}
/* proposals on the lookahead */
.card.proposed{background:#fff8e8;border-color:#e5c66a}
.card.bounced{background:#fdf0ef;border-color:#e0a9a3}
.pill.proposed{background:#b7791f;color:#fff}
.pill.bounced{background:#c0392b;color:#fff}
.propose-card{border:2px dashed var(--blue);touch-action:pan-x}
/* T20 QC checklist */
.qc-item{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line)}
.qc-item:last-child{border-bottom:0}
.qc-item-text{flex:1;min-width:260px;font-size:14px}
.qc-item.pass{background:#f2faf5}.qc-item.fail{background:#fdf0ef}.qc-item.na{opacity:.6}
.qc-item-actions{display:flex;gap:6px}
.qbtn{border:1px solid var(--line);background:#fff;border-radius:6px;padding:8px 14px;font-size:13px;cursor:pointer;font-weight:600}
.qbtn.pass.on{background:var(--ok);color:#fff;border-color:var(--ok)}
.qbtn.fail.on{background:var(--bad);color:#fff;border-color:var(--bad)}
.qbtn.na.on{background:var(--muted);color:#fff;border-color:var(--muted)}
.qc-note{flex-basis:100%;border:1px solid var(--bad);border-radius:6px;padding:8px 10px;font-size:13px}

/* T25 variance + notifications */
.vtag{font-size:10px;font-weight:800;border-radius:4px;padding:1px 6px;margin-left:6px;vertical-align:middle}
.vtag.slip{background:var(--bad);color:#fff}
.vtag.exp{background:var(--ok);color:#fff}
.bmove{margin-top:6px;display:flex;gap:10px;align-items:center}
.bcard.dragging{opacity:.4}
.bweek.dropover{outline:3px dashed var(--blue);outline-offset:-3px;background:#eef6fd}
#bellBtn{background:none;border:none;cursor:pointer;font-size:17px;position:relative;margin-right:10px;color:#fff}
#bellCount{display:none;position:absolute;top:-6px;right:-8px;background:var(--bad);color:#fff;font-size:10px;font-weight:800;border-radius:10px;padding:1px 5px;align-items:center}
#notifPanel{position:fixed;top:54px;right:16px;width:380px;max-width:92vw;max-height:70vh;overflow:auto;background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,.25);z-index:99}
.np-head{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}
.np-item{padding:10px 14px;border-bottom:1px solid var(--line)}
.np-item.unread{background:#f4f9ff}
.np-item.slip{border-left:4px solid var(--bad)}
.np-item.expedite{border-left:4px solid var(--ok)}
.np-item.escalation{border-left:4px solid #7b1fa2;background:#faf4fd}
.np-title{font-weight:700;font-size:13px}
.np-body{font-size:12px;white-space:pre-wrap;margin-top:3px}
.np-meta{font-size:11px;color:var(--muted);margin-top:3px}

/* T26 site photos */
.pcap{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:8px}
.pcap input[id$="Cap"]{flex:1;min-width:200px}
.pstrip{display:flex;gap:10px;overflow-x:auto;padding:4px 0}
.pshot{margin:0;flex:0 0 150px}
.pshot img{width:150px;height:110px;object-fit:cover;border-radius:8px;border:1px solid var(--line)}
.pshot-pending{width:150px;height:110px;border-radius:8px;border:1px dashed var(--muted);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--muted)}
.pshot figcaption{font-size:11px;color:var(--ink);max-width:150px}
.pshot figcaption span{display:block;color:var(--muted);font-size:10px}

/* T27 — end-of-day review + sign */
.eod-banner{background:#fff7e6;border:1px solid #f0c36d;border-radius:10px;padding:10px 14px;margin:10px 0;font-size:14px}
.eod-banner .link{margin-left:8px}
.sr-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;font-size:13px;margin:8px 0}
.sr-note{font-size:13px;margin:8px 0}
.sr-warn{color:#a15c00}
.sr-cert{font-size:12px;color:var(--muted);border-top:1px solid var(--line);padding-top:8px}
.np-go{margin-left:8px}

/* T28 — Field Mode: talk-to-text primary, phone/tablet primary */
.micb{border:1px solid var(--line);background:#fff;border-radius:50%;width:38px;height:38px;font-size:17px;cursor:pointer;vertical-align:middle;margin-left:6px}
.micb.rec{background:#c0392b;border-color:#c0392b;animation:micpulse 1.1s infinite}
@keyframes micpulse{50%{box-shadow:0 0 0 8px rgba(192,57,43,.25)}}
#fmBtn{background:none;border:none;font-size:20px;cursor:pointer;opacity:.6}
#fmBtn.on{opacity:1;filter:drop-shadow(0 0 4px var(--blue))}
.fh-status{display:flex;gap:14px;align-items:center;flex-wrap:wrap;font-size:15px;padding:10px 2px}
.fh-unread{background:#c0392b;color:#fff;border-radius:12px;padding:2px 10px;font-weight:700}
.fh-cap{margin:6px 0 12px}
.ftiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.ftile{background:#fff;border:1px solid var(--line);border-radius:16px;min-height:104px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;cursor:pointer;box-shadow:0 1px 3px rgba(21,38,60,.08)}
.ftile:active{transform:scale(.97)}
.ft-ic{font-size:30px}
.ft-l{font-weight:700;font-size:16px;color:var(--navy)}
.ft-s{font-size:12px;color:var(--muted)}
.fh-voice{position:fixed;inset:auto 0 0 0;background:var(--navy);color:#fff;padding:18px;z-index:60;border-radius:16px 16px 0 0}
.fh-voice-live{font-size:18px;min-height:48px;margin-bottom:10px}
/* T34 — persistent bottom nav in field mode */
#fnav{position:fixed;left:0;right:0;bottom:0;z-index:55;display:flex;background:var(--navy);box-shadow:0 -2px 10px rgba(0,0,0,.3);padding-bottom:env(safe-area-inset-bottom)}
#fnav button{flex:1;min-height:60px;background:none;border:none;color:#c8d6ea;font-size:12px;font-weight:700;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;cursor:pointer}
#fnav button span{font-size:22px;line-height:1}
#fnav button.active{color:#fff;background:rgba(255,255,255,.12)}
.fmode main{padding-bottom:86px}
/* T34 — voice-entry approval cards (Today §3) */
.va-list{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}
.va-card{border:1px solid var(--blue);border-left:4px solid var(--blue);border-radius:10px;padding:10px;background:#f4f8fd}
.va-text{width:100%;font-size:15px;border:1px solid var(--line);border-radius:8px;padding:8px;margin-bottom:8px}
.va-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.va-co{flex:1;min-width:160px}
.va-ok{background:var(--ok)!important;color:#fff}
/* fmode: bigger everything, no desktop tab strip */
.fmode .tabs{display:none}
/* padding-bottom must be restated: the 86px reserve above is on the same
   specificity, so this shorthand was silently resetting it to 10px and the fixed
   bottom nav was sitting on the last 76px of every field screen. */
.fmode main{padding:10px;padding-bottom:86px}
.fmode .btn{min-height:48px;font-size:16px;padding:12px 18px}
.fmode input,.fmode select,.fmode textarea{font-size:16px;min-height:44px}
.fmode textarea{min-height:64px}
.fmode .micb{width:48px;height:48px;font-size:21px}
.fmode .qc-item{min-height:52px;font-size:16px}
.fmode .pcap label.btn{font-size:18px}
.fmode .day{min-width:44px;min-height:44px}
/* documents + editors: stack on narrow screens */
@media (max-width: 740px){
  .grid{grid-template-columns:1fr !important}
  .mini{display:block;overflow-x:auto}
  .field-tiles{grid-template-columns:repeat(2,1fr)}
}

/* T29 — voice notes: live transcript, route by voice or tap */
#vnBtn{background:var(--blue);color:#fff;border:none;border-radius:18px;padding:6px 12px;font-size:15px;cursor:pointer;display:flex;align-items:center;gap:4px}
#vnBtn .vn-lab{font-size:13px;font-weight:700}
#vnPanel{position:fixed;inset:0;background:rgba(10,16,26,.55);z-index:100;display:flex;flex-direction:column;justify-content:flex-end;align-items:center}
.vn-sheet{background:#fff;border-top:3px solid var(--blue);box-shadow:0 -8px 30px rgba(0,0,0,.35);padding:14px;border-radius:16px 16px 0 0;width:100%;max-width:720px;max-height:94vh;overflow:auto}
.fmode .vn-sheet{border-radius:0;max-width:none;flex:1;max-height:none}
.vn-head{display:flex;gap:10px;align-items:center;margin-bottom:8px}
#vnText{width:100%;font-size:17px;min-height:84px;border:1px solid var(--line);border-radius:10px;padding:10px}
.vn-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}
.vn-dest{background:#fff;color:var(--navy);border:1px solid var(--line)}
.vn-dest.sel{background:var(--blue);color:#fff;border-color:var(--blue)}
#notesPanel{position:fixed;top:54px;right:16px;width:420px;max-width:94vw;max-height:72vh;overflow:auto;background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,.25);z-index:99}
.fmode #vnBtn .vn-lab{display:none}

/* T30 — Russell document retrieval */
.ru-src{padding:6px 0;border-bottom:1px dashed var(--line);font-size:13px}
.ru-src a{font-weight:700}
.ru-src .muted{display:block;font-size:12px}

/* T31 — photo markup editor */
#muPanel{position:fixed;inset:0;background:rgba(10,16,26,.96);z-index:120;display:flex;flex-direction:column;padding:10px}
.mu-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:8px}
.mu-tool{background:#fff;border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:15px;cursor:pointer}
.mu-tool.sel{background:#e01818;color:#fff;border-color:#e01818}
#muPanel .link{color:#9fc3e8}
.mu-stage{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden}
#muCanvas{max-width:100%;max-height:100%;border-radius:8px;background:#000}
#muText{font-size:16px;padding:10px;border-radius:8px;border:none;margin-bottom:8px}

/* T34 — Russell autofill chip (long-hold to correct) */
.ai-chip{display:inline-flex;align-items:center;gap:4px;background:#eef4fb;border:1px solid var(--blue);color:var(--navy);border-radius:16px;padding:6px 12px;font-size:13px;font-weight:700;cursor:pointer;user-select:none;-webkit-user-select:none}

/* T34 — Sheet Lens */
#lensPanel{position:fixed;inset:0;background:rgba(10,16,26,.55);z-index:110;display:flex;justify-content:center}
.lens-sheet{background:#fff;width:100%;max-width:760px;overflow:auto;padding:16px}
.fmode .lens-sheet{max-width:none}
.lens-g{border:1px solid var(--line);border-radius:12px;padding:12px;margin-top:10px}
.lens-g.ok{border-left:4px solid var(--ok)}
.lens-g.warn{border-left:4px solid #c0392b;background:#fdf6f5}
.lens-row{padding:7px 0;border-bottom:1px dashed var(--line);font-size:15px}
.lens-row:last-child{border-bottom:none}
.lens-band{display:inline-block;font-size:11px;font-weight:800;border-radius:4px;padding:2px 7px;margin-left:6px}
.lens-band.ok{background:var(--ok);color:#fff}
.lens-band.warn{background:#c0392b;color:#fff}
.lens-shots{display:flex;gap:6px;flex-wrap:wrap}
.lens-shots img{width:86px;height:86px;object-fit:cover;border-radius:8px}

/* T34 §2B — sub suite cards + flag + huddle rail */
.ss-card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:14px;margin-top:10px}
.ss-row{padding:6px 0;border-bottom:1px dashed var(--line);font-size:15px}
.ss-row:last-child{border-bottom:none}
.vn-flag{margin-top:10px;border:1.5px solid var(--line);background:#fff;border-radius:22px;padding:10px 16px;font-size:15px;font-weight:700;cursor:pointer;color:var(--navy)}
.vn-flag.on{background:#c0392b;border-color:#c0392b;color:#fff}
.huddle-rail{background:#fff;border:1px solid var(--line);border-left:5px solid #c0392b;border-radius:12px;padding:12px 14px;margin:10px 14px}
.huddle-rail b{color:var(--navy)}
.hr-item{padding:8px 0;border-bottom:1px dashed var(--line);font-size:15px}
.hr-item:last-child{border-bottom:none}
.hr-meta{font-size:12px;color:var(--mut, #5b6b7c);margin-top:2px}

/* T35 — ASI supersede watermark */
.sheetwrap.dead img{filter:grayscale(.55) opacity(.75)}
.ss-water{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:9vw;font-weight:900;color:rgba(192,57,43,.42);transform:rotate(-24deg);pointer-events:none;letter-spacing:4px;text-align:center}
.ss-banner{background:#c0392b;color:#fff;font-weight:800;border-radius:10px;padding:10px 14px;margin:8px 0;font-size:15px}

/* v24.8 — field layout guards: nothing may widen the page past the phone */
html,body{overflow-x:hidden;max-width:100vw}
.vn-sheet,.lens-sheet,.ss-card,.panel{max-width:100vw;box-sizing:border-box}
#vnDiag{word-break:break-word;overflow-wrap:anywhere}
.fmode .intbar{display:none}  /* office plumbing, not field info (doctrine #11) */

/* ================= v26.6 PROFESSIONAL RESKIN =================
   Founder direction 2026-08-10: reads like construction office software,
   not a consumer app. Muted federal palette — navy leads, steel-blue is an
   accent only; status colors (green/amber/red) unchanged per doctrine #7.
   No emoji in the UI: icons are inline SVG or plain labels. */
:root{ --blue:#2E6DA6 }
button.btn, label.btn{background:var(--navy);color:#fff;border-radius:6px;font-weight:600;letter-spacing:.2px}
button.btn:hover, label.btn:hover{filter:brightness(1.18)}
button.btn.ghost, label.btn.ghost{background:#fff;color:var(--navy);border:1px solid #c7cfda}
button.link{color:#2E6DA6}
.badge{background:#2c3e5c;color:#dbe4f0;border-radius:4px}
.pill,.tag,.vtag{border-radius:4px}
.tabs button.active{border-bottom-color:var(--navy)}
.brand .tri{border-bottom-color:#4a7fb5}
.tagline span{color:#9fc3e8}
.week,.panel,.card,.bcard,.bweek,.field-card,.field-tile,.ss-card,.lens-g,#notifPanel,#notesPanel{border-radius:8px}
.propose-card{border:1px solid #c7cfda}
.bweek.dropover{outline:2px solid #2E6DA6}
#vnBtn{background:transparent;border:1px solid #3d5578;border-radius:6px;color:#dbe4f0;font-weight:600;gap:7px}
#fmBtn{font-size:11px;font-weight:700;letter-spacing:.8px;color:#9fb3cf;border:1px solid #3d5578;border-radius:6px;padding:5px 9px;opacity:1}
#fmBtn.on{color:#fff;border-color:#9fc3e8;filter:none}
#bellBtn{display:inline-flex;align-items:center}
.micb{border-radius:6px;display:inline-flex;align-items:center;justify-content:center;color:var(--navy)}
.micb.rec{color:#fff}
.vn-sheet{border-top:2px solid var(--navy);border-radius:10px 10px 0 0}
#vnText{border-radius:6px}
.vn-actions .btn{min-width:120px;text-align:center;justify-content:center}
.vn-dest.sel{background:var(--navy);border-color:var(--navy)}
.vn-flag{border-radius:8px}
.field-card-icon{display:none}
.ai-chip{border-radius:6px;background:#f2f6fb;border-color:#c7cfda;font-weight:600}
.mu-tool{border-radius:6px}
.qbtn{border-radius:5px}
.board-head{border-radius:8px}
/* v26.7 — no typewriter text anywhere: every input, textarea, select, and
   button inherits the app font (browsers default textareas to monospace). */
input,textarea,select,button{font-family:inherit}
#vnText{font-family:inherit}

/* ================= v26.8 BRAND: COBALT (founder decision 2026-08-10) =================
   Near-black masthead with a cobalt rule; cobalt primaries; status colors
   (green/amber/red) unchanged per doctrine #7. Supersedes the navy button block above
   (later rules win).

   TAGLINE — founder decision 2026-08-15: "The whole job. Up to spec." This comment used
   to read "Ground up. Spec up.", which nothing in the product ever used and which the
   founder's own lockup artwork contradicts. The line is now settled; it is the only one
   that appears anywhere. */
:root{ --brand:#003096; --brandlink:#2f62ff }
.topbar{background:#0a0d14;border-bottom:3px solid var(--brand)}
.brand{font-family:'Archivo','Arial Narrow',sans-serif;font-weight:800;letter-spacing:.5px}
.brand .tri{border-bottom-color:#2f62ff}
.tagline{color:#8b93a5}
.tagline span{color:#7d9bff}
h2{color:var(--brand)}
button.btn, label.btn{background:var(--brand)}
button.link{color:var(--brandlink)}
.tabs button.active{color:var(--brand);border-bottom-color:var(--brand)}
button.btn.ghost, label.btn.ghost{color:var(--brand);border-color:#b9c6e8;background:#fff}
.ai-chip{background:#eef2fc;border-color:#b9c6e8;color:var(--brand)}
.vn-dest.sel{background:var(--brand);border-color:var(--brand)}
#vnBtn{border-color:#2c3550}
#fmBtn{border-color:#2c3550;color:#8b93a5}
#fmBtn.on{color:#fff;border-color:#7d9bff}
.who select{border-color:#2c3550;background:#12151f}
.badge{background:#1a2440;color:#c9d6f5}
.week h3,.card b,.field-card-title,.field-tile-n{color:var(--navy)}
a{color:var(--brandlink)}
.pub{color:var(--brandlink)}

/* ================= v27.0 DESIGN CRAFT PASS (B9) =================
   Type hierarchy, vertical rhythm, focus states, table manners, tap targets.
   Polish only — no ids, no layout restructuring; status colors untouched. */
h2{font-size:22px;letter-spacing:-.01em;margin:0 0 6px}
.sub{line-height:1.55;color:#5a6675;font-size:13.5px;max-width:72ch}
.field label{font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#5a6675}
.panel{padding:18px;box-shadow:0 1px 3px rgba(21,38,60,.06)}
.grid{gap:12px}
.field input,.field select{height:38px;padding:8px 10px;font-size:14px}
.field textarea{padding:8px 10px;font-size:14px;min-height:76px}
input::placeholder,textarea::placeholder{color:#9aa4b1}
input:focus,select:focus,textarea:focus{outline:none;border-color:#2E6DA6;box-shadow:0 0 0 3px rgba(46,109,166,.14)}
table{box-shadow:0 1px 3px rgba(21,38,60,.06)}
th,td{padding:9px 10px}
tbody tr:hover{background:#f7f9fc}
.card,.bcard{box-shadow:0 1px 3px rgba(21,38,60,.08)}
.topbar{padding:12px 20px}
#buildTag{opacity:.3;font-size:8px}
.tabs button{padding:12px 13px;font-size:13.5px}
.fmode .btn,.fmode .qbtn{min-height:46px}
.fmode .field-card{min-height:46px}
.fmode .field-tile{padding:14px}
.toast{box-shadow:0 6px 24px rgba(10,16,26,.3)}
.empty{color:#5c6879;font-size:13.5px}   /* was #8a94a3 = 3.07:1 — the one sentence on an empty screen has to be readable */

/* ---- v27.2.2 — AUTH GATE: the sign-in screen shows only the sign-in screen ---- */
body.authgate .tabs, body.authgate .who, body.authgate #intbar { display: none !important; }

/* ---- v27.4 — PHONE TOPBAR: the identity block must fit the screen ----------
   Found by the rendered field walk: on a 412px phone .who measured 442px wide,
   pushing the signed-in name, role badge and Sign out ~186px off the right edge
   (unreachable). The bar now shrinks in priority order instead of overflowing. */
.topbar{ overflow:hidden }
.who{ min-width:0; flex-wrap:nowrap }
#whoName{ min-width:0; max-width:22vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
@media (max-width: 900px){
  .who{ gap:6px }
  .who label{ display:none }            /* "Signed in as" — the name says it */
  .who .vn-lab{ display:none }          /* mic icon carries Voice Log */
  #whoName{ max-width:26vw }
}
@media (max-width: 740px){
  .brandwrap .tagline{ display:none }   /* stops the wordmark wrapping to 2 lines */
  #roleBadge{ display:none }
  /* Collapsed to a dot: the DOT must carry the state colour. The pill's own
     background is near-black for every state, so a naked background renders all
     three identically — offline looked the same as synced on a phone. */
  #syncPill{ font-size:0; padding:0; width:12px; height:12px; border-radius:50%; display:inline-block }
  #syncPill.synced{ background:#57d38d }
  #syncPill.syncing{ background:#e0c04a }
  #syncPill.offline{ background:#ef8f8f }
  #whoName{ max-width:30vw }
}
@media (max-width: 430px){
  #whoName{ display:none }              /* Sign out beats the name for reachability */
}
/* Field mode: everything in the bar stays thumb-sized (doctrine 13) */
.fmode .who button, .fmode #signOut{ min-height:44px }

/* v27.4 — sign-in / create-account is the first screen a field user meets on a
   phone; its buttons must be thumb-sized too (walk found "Create account" at 31px). */
@media (max-width: 900px), (pointer: coarse){
  #view .panel .btn, #loginGo, #regGo, #tgGo{ min-height:44px; padding-top:11px; padding-bottom:11px }
  #view .field input, #view .field select{ min-height:44px }
}

/* ---- v27.5 DASHBOARD — the job at a glance --------------------------------
   Staff land here instead of on a working screen. Every number is a link. */
.dash-head h2{margin:0 0 2px;font-size:22px;letter-spacing:-.01em}
.dash-head p{margin:0 0 14px;font-size:13.5px}
.dash-needs{margin-bottom:14px}
.dneed{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:#fff;border:1px solid #e3e7ee;
  border-left:4px solid var(--brand,#003096);border-radius:6px;padding:11px 12px;margin-top:8px;cursor:pointer;
  font-family:inherit;font-size:14.5px;color:inherit;transition:border-color .12s,background .12s}
.dneed:hover{background:#f7f9fd;border-color:#b9c6e8}
.dneed.warn{border-left-color:#b7791f}
.dneed.bad{border-left-color:#c0392b}
.dn-n{font-weight:800;font-size:18px;min-width:26px;text-align:center}
.dn-dot{font-size:22px;line-height:1;color:#8a94a3}
.dn-l{flex:1}
.dn-go{font-size:12px;font-weight:600;color:var(--brandlink,#2f62ff);white-space:nowrap}
.dtiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}
@media(max-width:900px){.dtiles{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){.dtiles{grid-template-columns:1fr}}
.dtile{display:flex;flex-direction:column;align-items:flex-start;gap:2px;background:#fff;border:1px solid #e3e7ee;
  border-top:3px solid #cfd6e2;border-radius:8px;padding:14px;cursor:pointer;font-family:inherit;color:inherit;
  text-align:left;min-height:88px;transition:box-shadow .12s,border-color .12s}
.dtile:hover{box-shadow:0 4px 14px -8px rgba(10,16,26,.35);border-color:#b9c6e8}
.dtile.good{border-top-color:#1f9d55}
.dtile.warn{border-top-color:#b7791f}
.dtile.bad{border-top-color:#c0392b}
.dt-n{font-size:28px;font-weight:800;line-height:1;letter-spacing:-.02em}
.dt-l{font-size:14px;font-weight:600}
.dt-s{font-size:12px;color:#5a6675}
.dash-risk{border-left:4px solid #b7791f}
.dash-foot{font-size:12.5px;margin-top:6px}
.fmode .dneed,.fmode .dtile{min-height:52px}

/* v27.5 — install bar + a generic centred modal (the install "how" sheet) */
#installBar{position:fixed;left:0;right:0;bottom:0;z-index:240;background:#0a0d14;color:#fff;
  display:flex;align-items:center;gap:12px;justify-content:space-between;flex-wrap:wrap;
  padding:12px 16px;font-size:14px;box-shadow:0 -4px 18px rgba(10,16,26,.28)}
#installBar .ib-actions{display:flex;align-items:center;gap:10px}
#installBar .btn{min-height:44px}
#installBar .link{color:#9fb6ff}
.fmode #installBar{bottom:62px}          /* clear the field bottom nav */
.modal-wrap{position:fixed;inset:0;background:rgba(10,16,26,.55);z-index:260;display:flex;align-items:center;justify-content:center;padding:16px}
.modal-card{background:#fff;border-radius:10px;max-width:420px;width:100%;padding:20px}

/* v27.5 — the install offer is a real tap target on a phone, not a hairline link */
.fh-install{display:inline-block;margin-top:8px;min-height:44px;padding:12px 0;font-weight:600}
.fmode .fh-install{min-height:44px}

/* v27.5 — on a narrow screen the search box ran off the tab row; give it its
   own full-width line instead of hiding a feature people rely on. */
@media (max-width: 900px){
  #gWrap{margin-left:0 !important;display:block;width:100%;padding:6px 12px 8px;box-sizing:border-box}
  #gWrap #gSearch{width:100% !important;min-height:44px;box-sizing:border-box}
  #gWrap #gOut{left:12px;right:12px;min-width:0 !important}
}

/* v27.5 — a GC with more than one project gets a project <select> in the top
   bar. At phone width it pushed the mic, FIELD, bell and Sign out off-screen
   (caught by the narrow-desktop walk). Cap it and let it shrink. */
#projPicker{max-width:34vw;min-width:0}
@media (max-width: 740px){ #projPicker{max-width:28vw;font-size:12px;padding:6px} }
@media (max-width: 430px){ #projPicker{max-width:22vw} }

/* v27.5 — TAB STRIP ON NARROW SCREENS. .tabs was a single nowrap flex row and
   the body clips overflow-x, so on a phone in desktop mode every tab past
   "Constraint Log" was unreachable — most of the app, invisible. Tabs now wrap
   onto as many lines as they need, and the search takes its own full line.
   (In Field Mode the strip is hidden entirely, so this only affects the
   desktop-on-a-phone case the founder actually hit.) */
@media (max-width: 900px){
  .tabs{flex-wrap:wrap;padding:0 8px;row-gap:0}
  .tabs button{flex:0 0 auto;padding:11px 12px;font-size:13.5px;min-height:44px}
  .tabs #gWrap{flex:1 0 100%;order:99}
  .tabs #abBtn{order:98}
}

/* v27.5 — wide register/schedule tables on a phone: pan them instead of
   clipping them (the body hides overflow-x, so a clipped table is a lost one). */
/* v28.28 — narrowed to the tablet band. Below 740px a row becomes a card instead
   (see "ON A PHONE, A ROW IS A CARD" further down); panning a table sideways to
   read one row at a time is not something anybody does on a ladder. */
@media (min-width: 741px) and (max-width: 900px){
  #view table{display:block;width:100%;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}
  #view table thead, #view table tbody{display:table;width:100%;min-width:max-content}
  #view table tr{display:table-row}
  #view table th, #view table td{display:table-cell}
}

/* ---- v27.6 FIELD DASHBOARD — the answer, at arm's length -------------------
   Founder: "no field dashboard?" The phone home was eight doors and no status.
   Big numbers, thumb-sized, every one a link. */
.fstats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:10px 0 12px}
.fstat{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  background:#fff;border:1px solid #e3e7ee;border-top:3px solid #cfd6e2;border-radius:8px;
  padding:12px 6px;min-height:66px;cursor:pointer;font-family:inherit;color:inherit}
.fstat b{font-size:26px;font-weight:800;line-height:1;letter-spacing:-.02em}
.fstat span{font-size:11.5px;color:#5a6675;text-align:center;line-height:1.2}
.fstat.good{border-top-color:#1f9d55}
.fstat.warn{border-top-color:#b7791f}
.fstat.bad{border-top-color:#c0392b}
.fneeds{background:#fff;border:1px solid #e3e7ee;border-radius:10px;padding:12px;margin-bottom:12px}
.fn-h{font-size:11.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#5a6675;margin-bottom:8px}
.fneed{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:#f7f9fd;
  border:1px solid #e3e7ee;border-left:4px solid var(--brand,#003096);border-radius:8px;
  padding:12px;margin-top:8px;min-height:52px;cursor:pointer;font-family:inherit;color:inherit;font-size:15px}
.fneed:first-of-type{margin-top:0}
.fneed.warn{border-left-color:#b7791f}
.fneed.bad{border-left-color:#c0392b}
.fneed b{font-size:20px;font-weight:800;min-width:26px;text-align:center}
.fneed .fn-dot{color:#b7791f}
.fneeds.clear p{margin:0;color:#5a6675;font-size:14.5px}

/* ---- v27.7 RECEIVING GATE — photo it, Russell reads it, you sign it -------- */
.rg-start{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:14px}
.rg-start .btn{min-height:44px}
.rg-wrap{position:fixed;inset:0;background:rgba(10,16,26,.6);z-index:250;display:flex;align-items:flex-start;justify-content:center;overflow:auto;padding:16px}
.rg-card{background:#fff;border-radius:12px;width:100%;max-width:560px;padding:18px;margin:auto}
.rg-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.rg-head b{font-size:17px}
.rg-steps{list-style:none;margin:0;padding:0;counter-reset:s}
.rg-steps li{border-left:3px solid #e3e7ee;padding:0 0 18px 14px;margin-left:4px}
.rg-steps li.now{border-left-color:var(--brand,#003096)}
.rg-steps li.done{border-left-color:#1f9d55}
.rg-t{font-weight:700;font-size:15px;margin-bottom:8px}
.rg-steps .btn{min-height:46px}
.rg-hint{font-size:12.5px;color:#5a6675;margin:8px 0 0}
.rg-err{font-size:13.5px;color:#c0392b;margin:8px 0 0}
.rg-shots{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.rg-shot{position:relative;width:88px}
.rg-shot img{width:88px;height:88px;object-fit:cover;border-radius:8px;border:1px solid #cbd2dc;display:block}
.rg-x{position:absolute;top:-6px;right:-6px;background:#0a0d14;color:#fff;border:none;border-radius:12px;
  font-size:10px;padding:3px 7px;cursor:pointer;font-family:inherit}
.rg-verdict{font-family:'IBM Plex Mono',ui-monospace,monospace;font-weight:700;font-size:13px;letter-spacing:.06em;
  padding:12px 14px;border-radius:8px;text-align:center}
.rg-verdict.ok{background:#e7f6ee;color:#177245;border:1px solid #b6e3ca}
.rg-verdict.bad{background:#fdecea;color:#a5281b;border:1px solid #f0bdb6}
.rg-why{font-size:15.5px;line-height:1.5;margin:12px 0}
.rg-read{background:#f7f9fd;border:1px solid #e3e7ee;border-radius:8px;padding:12px;margin-bottom:14px}
.rg-kv{display:flex;justify-content:space-between;gap:12px;padding:5px 0;font-size:13.5px;border-bottom:1px solid #eef1f6}
.rg-kv:last-of-type{border-bottom:none}
.rg-kv span{color:#5a6675}
.rg-kv b{text-align:right}
.rg-src{font-size:12px;color:#5a6675;margin:8px 0 0;font-style:italic}
.rg-concerns{margin:8px 0 0;padding-left:18px;font-size:13px;color:#946000}
.rg-acts{display:flex;flex-direction:column;gap:8px}
.rg-acts .btn{min-height:48px}
.rg-acts .btn.danger{background:#c0392b}
.rg-acts .link{align-self:center;margin-top:4px;min-height:44px}
.rg-reason{background:#f7f9fd;border:1px solid #e3e7ee;border-radius:8px;padding:12px;margin-top:12px}
.rg-reason textarea{width:100%;min-height:74px;margin-top:6px}
@media (max-width:520px){ .rg-card{padding:14px;border-radius:10px} }
.rg-head .link{min-height:44px;padding:10px 4px}

/* ================= v28.0.1 — VOICE LOG, professional pass =================
   Founder read: "the dictation looked unprofessional." It was written like a
   consumer voice assistant — lowercase running commentary, a naked mic circle,
   and an arrow where a field label belongs. A superintendent hands this screen
   to a government inspector. One status chip that names the state, a labelled
   control row, sentence-case guidance. Status colours follow doctrine #7. */
.vn-head{align-items:center;gap:10px;padding-bottom:10px;border-bottom:1px solid var(--line);margin-bottom:10px}
.vn-head b{font-size:16px;letter-spacing:.01em}
.vn-head-actions{margin-left:auto;display:flex;gap:6px}
.vn-head-actions .vn-headbtn{min-width:0}

.vn-status{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:600;
  letter-spacing:.02em;padding:4px 11px 4px 9px;border-radius:20px;border:1px solid var(--line);
  background:#f5f7fa;color:#4a5568;white-space:nowrap}
.vn-status i{width:7px;height:7px;border-radius:50%;background:#98a4b3;flex:none}
.vn-status[data-tone="live"]{background:#fdeceb;border-color:#f3c4c0;color:#9c2f26}
.vn-status[data-tone="live"] i{background:#c0392b;animation:vnpulse 1.4s ease-in-out infinite}
.vn-status[data-tone="busy"]{background:#fff6e5;border-color:#f0d9a8;color:#7a5c00}
.vn-status[data-tone="busy"] i{background:#d9a441;animation:vnpulse 1.4s ease-in-out infinite}
.vn-status[data-tone="ok"]{background:#eaf5ec;border-color:#b9dcc0;color:#22683a}
.vn-status[data-tone="ok"] i{background:#2e7d32}
.vn-status[data-tone="warn"]{background:#fff6e5;border-color:#f0d9a8;color:#7a5c00}
.vn-status[data-tone="warn"] i{background:#d9a441}
.vn-status[data-tone="bad"]{background:#fdeceb;border-color:#f3c4c0;color:#9C0006}
.vn-status[data-tone="bad"] i{background:#c0392b}
@keyframes vnpulse{50%{opacity:.35}}

.vn-hint{font-size:13px;color:#5b6b7c;margin:0 0 8px}

.vn-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}
/* The mic is a labelled control, not a mystery circle. */
.vn-mic{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--line);background:#fff;
  color:var(--navy,#15263C);border-radius:6px;padding:0 16px;height:44px;font-size:14px;font-weight:600;
  font-family:inherit;cursor:pointer;letter-spacing:.01em}
.vn-mic:hover{border-color:#9fb0c4;background:#f8fafc}
.vn-mic.rec{background:#c0392b;border-color:#c0392b;color:#fff}
.vn-mic.rec svg{animation:vnpulse 1.4s ease-in-out infinite}
.vn-controls .btn{height:44px;min-width:0;padding:0 16px;font-size:14px}
.btn.vn-recording{background:#c0392b;border-color:#c0392b;color:#fff}

/* "Send to" is a field label, not an arrow. */
.vn-send{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px;
  padding-top:12px;border-top:1px solid var(--line)}
.vn-send-label{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#6b7a8c}
.vn-dests{display:flex;gap:8px;flex-wrap:wrap;flex:1}
.vn-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}

.vn-flag{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;
  margin-top:12px;font-size:14px;font-weight:600;padding:11px 16px}
.vn-flag span{font-size:12px;font-weight:600;opacity:.85}

/* Every routing target is a real tap target everywhere — these are pressed with a
   gloved thumb on a jobsite, and the walk flags anything under 40px. */
.vn-dests .btn{min-height:44px;display:inline-flex;align-items:center;justify-content:center}


/* Phone: the rows stack and every control goes full width. The audit found the
   send row running off a 412px screen and its buttons at 31px tall. */
@media (max-width: 560px){
  .vn-send{flex-direction:column;align-items:stretch;gap:8px}
  .vn-dests{flex-direction:column;width:100%}
  .vn-dests .btn{width:100%;min-width:0;min-height:46px}
  .vn-controls{flex-direction:column;align-items:stretch}
  .vn-mic,.vn-controls .btn,.vn-controls .vn-headbtn{width:100%;justify-content:center;height:46px}
  .vn-head{flex-wrap:wrap}
  .vn-head-actions{margin-left:auto}
  .vn-head-actions .vn-headbtn{padding:11px 14px;min-height:44px;font-size:14px}
  .vn-status{order:3;width:100%;justify-content:flex-start}
  .vn-flag{min-height:48px}
}

/* v28.0.1 — BUTTON HIERARCHY in the Voice Log. Every routing target was rendering
   as a filled primary, so four equal-weight cobalt blocks shouted at the user and
   nothing showed which one was selected. Options are outlined; the chosen one is
   filled. Declared last so it wins over the v26.8 cobalt block. */
/* `button.btn{background:var(--brand)}` (v26.8) is an element+class selector, so a
   bare `.vn-dest` loses to it. Matched specificity, declared later. */
button.btn.vn-dest{background:#fff;color:var(--navy,#15263C);border:1px solid var(--line);font-weight:600}
button.btn.vn-dest:hover{border-color:#9fb0c4;background:#f8fafc}
button.btn.vn-dest.sel{background:var(--brand,#003096);border-color:var(--brand,#003096);color:#fff}
/* Secondary controls get their own class rather than relying on `.ghost`, which
   is overridden inconsistently by the later brand rules. One class, one look. */
button.vn-headbtn{background:#fff;color:var(--navy,#15263C);border:1px solid var(--line);
  border-radius:6px;font-family:inherit;font-size:13px;font-weight:600;letter-spacing:.01em;
  padding:7px 14px;min-height:34px;cursor:pointer}
button.vn-headbtn:hover{border-color:#9fb0c4;background:#f8fafc}
button.vn-recbtn{height:40px;padding:0 16px;font-size:14px}
button.vn-recbtn.vn-recording{background:#c0392b;border-color:#c0392b;color:#fff}

/* v28.10 — multi-trade photo summaries. The ticked/unticked state and the confidence
   word both have to be visible at arm's length, outside, on a phone. */
.ai-multi{display:block;width:100%;border:1px solid var(--line,#cdd5dd);border-radius:8px;padding:10px;background:#fbfcfe;margin-top:4px}
.ai-multi-head{font-size:13px;margin-bottom:8px}
.ai-opt{display:flex;gap:9px;align-items:flex-start;padding:8px 9px;border:1px solid #dde3ea;border-radius:6px;background:#fff;margin-bottom:6px;cursor:pointer}
.ai-opt.on{border-color:var(--blue,#2E7DD1);background:#f3f8fd}
.ai-opt input{margin-top:3px;width:18px;height:18px;flex:0 0 auto}
.ai-opt-b{display:flex;flex-direction:column;gap:2px}
.ai-opt-t{font-size:13px;line-height:1.35}
.ai-multi-foot{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:2px}

/* v28.11 — the drawings against the specifications. Both sides of a finding have to be
   readable side by side; a conflict you can only see one half of is an assertion. */
.ps-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}
.ps-item{border:1px solid #dde3ea;border-left:4px solid var(--warn,#b7791f);border-radius:6px;padding:10px 12px;margin-top:10px;background:#fff}
.ps-item.ps-handled{opacity:.6;border-left-color:#cdd5dd}
.ps-title{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:14px;line-height:1.35}
.ps-side{margin-top:8px;font-size:13px}
.ps-lbl{display:block;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted,#5b6773)}
.ps-side q{display:block;margin-top:2px;padding:6px 10px;background:#f5f8fb;border-left:3px solid var(--blue,#2E7DD1);quotes:none}
.ps-side.ps-none i{display:block;margin-top:2px;padding:6px 10px;background:#fdf3e2;border-left:3px solid var(--warn,#b7791f);color:#7a5b12;font-style:normal}
.ps-q{margin-top:10px;font-size:13px;padding:8px 10px;background:#fbfcfe;border:1px dashed #cdd5dd;border-radius:4px}
.ps-acts{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}

/* v28.11 — field mode. `.fmode input{min-height:44px}` stretched the trade tickbox
   vertically only, leaving an 18×44 sliver: a distorted control on the one screen it is
   used on. The whole row is the tap target (that is what makes it usable with gloves on),
   so the box does not need to BE 44px — it needs to be square and readable at arm's
   length, and the row around it needs the thumb size. */
.fmode .ai-opt{padding:12px 11px;gap:12px}
.fmode .ai-opt input[type=checkbox]{width:26px;height:26px;min-width:26px;min-height:26px;margin-top:1px}
.fmode .ai-opt-t{font-size:15px;line-height:1.4}
.fmode .ai-multi-head{font-size:14px}
.fmode .ai-multi-foot .btn{min-height:44px}
/* and the plan/spec findings, for a phone that lands on Contract Docs */
.fmode .ps-side q,.fmode .ps-side.ps-none i{font-size:14px}
.fmode .ps-q{font-size:14px}
.fmode .ps-lbl{font-size:13px}

/* v28.11.2 — THE REAL MARK.
   The masthead carried a typeset stand-in — Oswald/Archivo "SPEC", a CSS-border
   triangle, "UP" — while buildspecup.com carried the actual artwork. They looked
   nothing alike, and the field masthead was the stand-in with the tagline hidden, so a
   phone showed no brand at all. The knockout is the founder's own file with the
   wordmark taken to white for the near-black bar; the blue is the locked #2f62ff. */
.brandmark{display:block;height:26px;width:auto;image-rendering:auto}
@media (max-width: 740px){ .brandmark{height:23px} }
@media (max-width: 430px){ .brandmark{height:21px} }
.fmode .brandmark{height:24px}

/* v28.12 — loop stage 04 on the sub's home. The whole row is the target; a package
   a subcontractor cannot find is a package that does not get sent. */
.owed-panel{border:1px solid var(--line,#cdd5dd);border-left:4px solid var(--warn,#b7791f);border-radius:8px;padding:10px;background:#fffdf7;margin:10px 0 14px}
.owed-head{display:flex;flex-direction:column;gap:2px;margin-bottom:8px;font-size:14px}
.owed-head .muted{font-size:12px}
.owed-row{display:flex;gap:10px;align-items:center;justify-content:space-between;width:100%;text-align:left;
  background:#fff;border:1px solid #dde3ea;border-radius:6px;padding:11px 12px;margin-bottom:6px;cursor:pointer;font:inherit;min-height:52px}
.owed-row:last-child{margin-bottom:0}
.owed-main{display:flex;flex-direction:column;gap:2px;min-width:0}
.owed-main b{font-size:14px}
.owed-main .muted{font-size:12px}
.fmode .owed-row{min-height:60px;padding:13px 12px}
.fmode .owed-main b{font-size:15px}
.fmode .owed-main .muted{font-size:13px}

/* ===========================================================================
   v28.13 — THE WALK BANNER.
   A walk that is running has to be visible while it is running, because everything
   the person captures is landing on it. It sits above the bottom nav in field mode
   and bottom-right at the desk, and it is never in the way of the five nav targets.
   =========================================================================== */
#walkBar{position:fixed;left:0;right:0;bottom:0;z-index:150;display:flex;align-items:center;gap:10px;
  background:#0a0d14;color:#fff;padding:10px 12px;font-size:13px;box-shadow:0 -2px 12px rgba(10,13,20,.28)}
.fmode #walkBar{bottom:calc(60px + env(safe-area-inset-bottom))}
/* The bar takes real estate at the bottom of the screen, so the content above it has
   to give that back — otherwise the last row of every list sits underneath it. */
body.walking main{padding-bottom:78px}
body.walking.fmode main{padding-bottom:150px}
#walkBar .wb-dot{width:9px;height:9px;border-radius:50%;background:#2f62ff;flex:0 0 auto;
  box-shadow:0 0 0 0 rgba(47,98,255,.7);animation:wbPulse 2s infinite}
@keyframes wbPulse{70%{box-shadow:0 0 0 8px rgba(47,98,255,0)}100%{box-shadow:0 0 0 0 rgba(47,98,255,0)}}
#walkBar .wb-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#walkBar .wb-n{color:#9fb2cc;flex:0 0 auto;white-space:nowrap}
#walkBar button{font:inherit;border-radius:6px;cursor:pointer;min-height:36px;padding:0 12px;flex:0 0 auto}
#walkBar .wb-go{background:#2f62ff;color:#fff;border:none;font-weight:700}
#walkBar .wb-x{background:none;color:#9fb2cc;border:1px solid #33405a}
.fmode #walkBar{padding:12px}
.fmode #walkBar button{min-height:44px;padding:0 14px}

/* v28.13–16 — form labels inside the new capture sheets. Same type scale as the rest
   of the app; 16px inputs so iOS does not zoom the page when they are focused. */
/* 13px is the field floor. A 12px label is unreadable at arm's length in sunlight,
   and these sheets are used standing up on a jobsite. */
.modal-card .fl{display:block;font-size:13px;color:#556;margin:10px 0 3px;letter-spacing:.2px}
.modal-card input[type=text],.modal-card input:not([type]),.modal-card input[type=number],
.modal-card input[type=date],.modal-card select,.modal-card textarea{
  width:100%;box-sizing:border-box;font-size:16px;padding:9px 10px;border:1px solid #cbd2dc;border-radius:6px;background:#fff}
/* NOT the checkbox. `min-height:44px` on a bare input stretches a tickbox vertically
   and leaves an 18×44 sliver — the exact distorted control the field walk caught on the
   trade picker in v28.11.1. A tickbox is square; the ROW around it is the tap target,
   because nobody aims at a tickbox with a glove on. */
.fmode .modal-card input:not([type=checkbox]),
.fmode .modal-card select,.fmode .modal-card textarea{min-height:44px}
.modal-card .fl:has(input[type=checkbox]){display:flex;align-items:center;gap:10px;
  min-height:44px;margin:12px 0 4px;cursor:pointer;font-size:14px;color:#233}
.modal-card .fl input[type=checkbox]{width:24px;height:24px;min-height:24px;flex:0 0 auto;margin:0}
.modal-card{max-width:460px}

/* v28.15 — MY SCOPE ON A PHONE.
   This is a subcontractor's primary screen and their primary device is a phone. A
   five-column table at 412px shows two columns and hides the three that answer the
   question. In field mode each row becomes a stacked card with its own labels, so
   nothing is behind a sideways scroll a person in a lift will never perform. */
@media (max-width: 560px){
  /* EVERY SELECTOR HERE IS PREFIXED WITH #view ON PURPOSE. The v27.5 pan-a-wide-table
     rule above is written `#view table td{display:table-cell}`, and an id beats a class
     — so the unprefixed version of this block lost silently and the table stayed a
     table. The rendered field walk is what found it; nothing in the API suite could.
     This block is the deliberate exception to that rule: a subcontractor's scope is not
     a wide report to pan across, it is the list they work from.
     `overflow:visible` because the generic `table{overflow:hidden}` clips a table turned
     into blocks. The label is an inline-block, not a flex item: a text node inside a
     flex row cannot shrink below its longest word, so "Welding procedure
     specifications" pushed the row wider than the phone all over again. */
  #view table.ms-tbl{display:block;width:auto;max-width:none;overflow:visible;border:none;background:none;border-radius:0}
  #view .ms-tbl thead{position:absolute;left:-9999px}
  #view .ms-tbl tbody,#view .ms-tbl tr,#view .ms-tbl td{display:block;width:auto;min-width:0}
  #view .ms-tbl tr{border:1px solid var(--line,#cdd5dd);border-radius:8px;padding:8px 10px;margin-bottom:8px;background:#fff}
  #view .ms-tbl td{border:none;padding:3px 0;font-size:14px;overflow-wrap:anywhere}
  /* padding-right, not just min-width: "TRANSMITTAL" is wider than the column, and
     without it the label runs straight into the value with no gap. */
  #view .ms-tbl td::before{content:attr(data-l);display:inline-block;min-width:86px;padding-right:10px;
    font-size:11px;letter-spacing:.4px;text-transform:uppercase;color:#667;vertical-align:baseline}
  #view .ms-tbl td:first-child{font-weight:700}
}

/* ===========================================================================
   v28.14 — THE FIRST RUN.
   The whole screen a brand-new account sees. It used to be the words "No project."
   on a blank page. Wide-ish measure, generous targets, and the same form controls as
   the capture sheets so a phone gets 44px and 16px inputs without a second ruleset.
   =========================================================================== */
.firstrun{max-width:620px;margin:0 auto;padding:6px 0 28px}
.firstrun h2{margin:0 0 4px}
.firstrun .panel{margin-bottom:14px}
.firstrun .fl{display:block;font-size:13px;color:#556;margin:10px 0 3px;letter-spacing:.2px}
.firstrun input{width:100%;box-sizing:border-box;font-size:16px;padding:10px;
  border:1px solid var(--line,#cbd2dc);border-radius:6px;background:#fff}
.firstrun .btn{min-height:44px;padding:0 18px;font-size:14px}
.fmode .firstrun input{min-height:44px}

/* ===========================================================================
   v28.15 — THE INVITE CONFIRMATION.
   The panel that appears the moment somebody is invited: the link, Copy, Share,
   Email it. Everything in it is a control a person USES — often on a phone, often
   one-handed, often standing next to the person they are inviting. Inherited
   .btn.small put them at 20-22px tall with a 12px link field. Same failure class
   as the v28.11.1 trade picker and the v28.13 tickbox sliver: a control that is
   technically present and physically unusable.
   =========================================================================== */
#pdInviteOut input{min-height:40px;font-size:13px;padding:0 10px;box-sizing:border-box;
  border:1px solid var(--line,#cbd2dc);border-radius:6px;background:#fff}
#pdInviteOut .btn,#pdInviteOut a.btn{min-height:40px;display:inline-flex;align-items:center;
  justify-content:center;padding:0 14px;font-size:13px}
/* "Email it" is an <a>, and every .btn rule in this sheet is scoped to button.btn — so
   it has always rendered as plain text beside a filled button doing the same job. Give
   it the secondary-button treatment so the two read as the pair they are. */
#pdInviteOut a.btn{background:#fff;color:var(--navy);border:1px solid #c7cfda;
  border-radius:6px;font-weight:600;text-decoration:none}

/* ===========================================================================
   v28.16 — DROP TARGETS.
   A submittal package arrives as a PDF in a folder. The panel says so, and lights
   up when a file is over it, because a drop target you cannot see is a drop target
   nobody uses. 44px of hint text, not a fashion statement.
   =========================================================================== */
#view .dropzone{position:relative;transition:background .12s,border-color .12s}
#view .dropzone.dz-over{background:#eef4fb;border-color:var(--blue,#003096);
  outline:2px dashed var(--blue,#003096);outline-offset:-4px}
#view .dropzone .dz-hint{font-size:12px;color:#667;margin:4px 0 0;letter-spacing:.2px}
#view .dropzone.dz-over .dz-hint{color:var(--navy,#003096);font-weight:700}

/* ===========================================================================
   v28.17 — THE NAV, GROUPED.
   Twenty flat tabs was an inventory, not navigation. Five groups; the buttons
   underneath are unchanged and still carry data-tab, so every deep link, role
   gate and walk works against the same DOM as before.
   =========================================================================== */
.tabs{position:relative}
.tgrp{position:relative;display:inline-block}
.tabs .tgh{font-weight:600}
.tgrp.has-active .tgh{color:var(--navy,#003096);box-shadow:inset 0 -3px 0 var(--navy,#003096)}
.tgrp .tgm{display:none;position:absolute;top:100%;left:0;z-index:260;min-width:230px;
  background:#fff;border:1px solid var(--line,#cbd2dc);border-radius:8px;padding:6px;
  box-shadow:0 10px 28px rgba(21,38,60,.18)}
.tgrp.open .tgm{display:block}
.tgrp .tgm button{display:block;width:100%;text-align:left;padding:10px 12px;min-height:40px;
  border:none;background:none;font-size:14px;border-radius:6px;cursor:pointer;box-shadow:none}
.tgrp .tgm button:hover{background:#eef4fb}
.tgrp .tgm button.active{background:#eef4fb;font-weight:700;color:var(--navy,#003096)}
.tabs button.tabhidden{display:none !important}
@media(max-width:740px){ .tgrp .tgm{position:fixed;left:8px;right:8px;min-width:0;top:auto} }

/* ===========================================================================
   v28.18.1 — NOTHING COVERS THE BOTTOM NAV.
   Founder report: "once you click the voice log you lose the nav bar at the
   bottom." #vnPanel is position:fixed inset:0 at z-index 100; #fnav sits at 55.
   So the sheet covered the nav completely — and in Field Mode .vn-sheet is
   flex:1 with no max-height, so it filled the screen right over it.

   The bottom nav is the one element in Field Mode that must always be there.
   It is how a person gets out of wherever they are, one thumb, without reading
   anything. A full-screen sheet that swallows it strands them — the only way
   back is a Close button they have to find. Walk step 09e already guards the
   nav from covering the WORK; this is the same rule in the other direction.

   Every full-screen overlay now stops above the nav instead of on top of it,
   so the nav stays visible AND tappable while a sheet is open.
   =========================================================================== */
:root{ --fnav-h:60px }
.fmode #vnPanel, .fmode #muPanel, .fmode #lensPanel, .fmode #rgPanel,
.fmode #apPanel, .fmode #arPanel, .fmode #ikPanel, .fmode #pdPanel,
.fmode #rdPanel, .fmode #abPanel, .fmode #bugPanel, .fmode #notesPanel,
.fmode #notifPanel, .fmode #tpPanel,
.fmode .modal-wrap, .fmode .rg-wrap{
  /* !important because several of these panels are built with an inline
     style.cssText of "position:fixed;inset:0", and inline wins over a plain rule.
     The walk caught exactly that: Voice Log obeyed the stylesheet and Ask Russell
     did not. A rule that only holds for the panels that happen to be written one
     way is not a rule. */
  bottom:calc(var(--fnav-h) + env(safe-area-inset-bottom)) !important;
}
/* Setup in the masthead when the tab strip is gone (Field Mode). Same door, same
   contents — it just has to live where there is a bar to live in. */
.who-setup{min-height:40px;padding:0 12px;font-size:13px}

/* v28.24 — three apps, one codebase. A tab that is not part of this app's job is
   hidden, not deleted: setTab, deep links and every walk still find it. */
.tabs button.appoff, .tgrp.appoff { display: none !important; }
#appLine { font-size: 12px; opacity: .75; }

/* ---- v28.25 · WHAT THE USABILITY WALK FOUND ON A 390px PHONE ---------------
   Every rule below was written against a specific measurement taken in a real
   browser at 390x844, not against a hunch. The walk that found them is
   tools/walk-usable.mjs and it runs with the others.                          */

/* 1. THE APP LINE I ADDED IN v28.24 BROKE THE MASTHEAD.
   Putting "QC Manager — the paper: the register…" inside .brandwrap widened the
   left block, and .who lost the fight for the remaining width: the bell ran 38px
   past the right edge and Sign out ran 74px past — clipped by .topbar{overflow:
   hidden}, so a control the person could see part of and tap none of. In the
   Delivery app the whole bar was squashed to 18px tall.
   On a phone the line is redundant anyway: they launched the app from its own
   icon, so they know which app it is. It earns its space on a tablet and up. */
@media (max-width: 900px){ #appLine{ display:none } }
#appLine{ max-width:42vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }

/* 2. THUMB-SIZED MEANS 44px, NOT 40.
   Apple and Google both publish 44. The bar was at 40 and the Delivery app was
   rendering some of those buttons at 18–32px once the bar was squeezed. These
   are the most-tapped controls in the product and they are tapped with gloves on. */
@media (max-width: 900px), (pointer: coarse){
  .who button, #signOut, #askR, #vnBtn, #fmBtn, #bellBtn{
    min-height:44px; min-width:44px; display:inline-flex; align-items:center; justify-content:center;
  }
}

/* 3. A WIDE TABLE MUST NOT DRAG THE PAGE SIDEWAYS.
   The punch table ran 183px past the right edge on a phone, which makes the
   whole page scroll horizontally — the clearest signal a web app can send that
   it was built on a desktop. The table still scrolls; the PAGE does not. */
@media (max-width: 740px){
  /* CORRECTED, same day, by tools/walk-roles.mjs. The first cut added
     `white-space:nowrap` and `width:max-content` so wide tables would scroll
     sideways in their own container. That broke a rule this product already had
     and already tested: on a subcontractor's phone every scope field has to be
     READABLE WITHOUT a sideways scroll, because nobody standing in a lift performs
     one. nowrap turned wrapping cells into clipped one-liners.
     So the cells wrap as they always did, and the scroller stays only as a safety
     net for a table that genuinely cannot fit — the page itself never scrolls. */
  /* v28.28 — CORRECTED AGAIN, and this time by looking at it.
     `table{display:block}` switches off table layout: the cells stop forming
     columns. On the punch list at 390px the header collapsed into one run of words
     and every row rendered its title and nothing else — status, assignee and the
     action were unaligned and unreadable, not merely off-screen. The screen whose
     job is to show what is wrong on the job showed a list of titles.
     The scroller belongs on a wrapper, never on the table. wrapTables() in app.js
     puts one around every table after each render. */
  html, body{ overflow-x:hidden }
}

/* 4. LABELS A 50-YEAR-OLD SUPERINTENDENT CAN READ IN THE SUN.
   Field form labels were 11px uppercase at 3.4:1. Uppercase at 11px is the
   hardest combination there is to read outdoors, and these sit above the inputs
   he is being asked to fill in. Bigger, and dark enough to pass AA. */
@media (max-width: 900px), (pointer: coarse){
  .field label{ font-size:13px; letter-spacing:.02em; color:#3d4757 }
  .pill, .status, .tag, .by, .card .code{ font-size:12px }
  th{ font-size:12px }
}

/* 5. CONTRAST ON THE THINGS THAT CARRY MEANING.
   --muted was #6b7688: 4.2:1 on white, under the 4.5:1 AA needs for body text.
   Every ".muted" caption, every empty-state line and every footer link in the
   product inherits it, so one value fixed the largest single group of findings.
   #5c6879 is 5.0:1 and still reads as secondary. */
:root{ --muted:#5c6879 }
.pill.uncommitted{ color:#8a5a08 }        /* was 3.3:1 on its own amber ground */
.pill.done{ color:#4b5565 }
.status{ color:#3d4757 }

/* 6. THE LEGAL LINKS.
   11px at 2.8:1 in the footer. They are the two links a person is entitled to
   read before they agree to anything. */
.legalrow, .legalrow a{ font-size:13px }
.legalrow a{ color:#41506b }
/* The footer links sit on their own row and are the only thing there — give them
   the height a thumb needs without pushing the row apart on a desktop. */
@media (max-width: 900px), (pointer: coarse){
  .legalrow a{ display:inline-block; padding:10px 6px; min-height:24px }
}

/* 7. THE SMALL PRINT THAT ISN'T SMALL PRINT.
   Status words on the field tiles ("ready to pull", "behind plan", "Needs you")
   and the labels beside controls were 11–11.5px. They are not decoration — they
   are what the tile MEANS, read at arm's length in daylight. */
@media (max-width: 900px), (pointer: coarse){
  .fstat span, .field-tile-l{ font-size:12.5px }
  .vn-send-label{ font-size:12.5px }
  #fmBtn{ font-size:12px }
}

/* 8. EVERYTHING YOU TAP IS 44px TALL — INCLUDING THE THINGS THAT LOOK LIKE TEXT.
   `button.link` renders as a text link with padding:0, so it measured 14px tall.
   "Switch to Field Mode" and "Add SPECUP to this device" are real actions on the
   home screen and were 14px of target. A punch row's Verify / Reopen / Mark ready
   buttons sat in 34px rows. A <select> came in at 19px. All of them are tapped
   with a gloved thumb, in the cold, one-handed. */
@media (max-width: 900px), (pointer: coarse){
  button.link{ min-height:44px; padding:6px 4px; display:inline-flex; align-items:center; font-size:13px }
  .pinrow{ padding:12px 10px }
  .pinrow button, .pinrow a, td button, td a, .ss-row button{
    min-height:44px; padding:8px 6px; display:inline-flex; align-items:center;
  }
  /* `input[type=text]` does NOT match `<input id="fhCap" placeholder="…">` — the
     default type is text but the ATTRIBUTE is absent, so the selector misses it.
     That is why the photo caption box was still measuring 21px after this rule went
     in. :not([type]) catches every bare input in the product. */
  select, textarea, input:not([type]),
  input[type=date], input[type=number], input[type=tel], input[type=text],
  input[type=password], input[type=email], input[type=search]{ min-height:44px }
  .micb{ width:44px; height:44px }
  #signOut{ min-height:44px }
  .legalrow a{ padding:14px 8px }
  /* number/step pickers on the daily — 40px squares become 44 */
  .numpick button, .npick button, .fstat{ min-height:44px }
}

/* 9. ONE NOTCH OF CONTRAST ON A LABEL THAT SAT AT 4.39:1.
   AA wants 4.5. Being a rounding error under the line is still under the line. */
.vn-send-label{ color:#5c6b7d }
.fn-h{ font-size:12.5px }

/* 10. THE LAST FEW PIXELS. The legal row was landing at 43px — a pixel under is
   still under, and these are the links somebody reads before they agree to
   anything. The day-picker squares on the daily log were 40. */
@media (max-width: 900px), (pointer: coarse){
  .legalrow a{ padding:15px 8px }
  .day, .npick button, .numpick button{ min-width:44px; min-height:44px }
}

/* 11. THE LAST TWO CAPTIONS. A byline and an "as of" timestamp at 11px. They are
   secondary, but they sit on the QC Manager app's home screen and secondary is not
   the same as unreadable. */
@media (max-width: 900px), (pointer: coarse){
  .dash-head .muted, .dash-sub, .asof, .byline{ font-size:12.5px }
  #view p.muted[style*="font-size:11px"], #view span.muted[style*="font-size:11px"]{ font-size:12.5px !important }
}

/* ---- v28.26 · THE PHOTO BUTTONS WERE NEVER BUTTONS -------------------------
   Founder, with a screenshot: "the photo options should be blue buttons."
   He was looking at "Take a photo" and "Use one from my phone" rendered as plain
   text floating on the background. Cause: a hidden <input type=file> is triggered
   by a <label for=…>, so both controls are <label class="btn"> — and every single
   .btn colour rule in this sheet was scoped to `button.btn`. A label matched none
   of them and inherited nothing. Someone had already noticed the elements existed
   (there was a `.fmode .pcap label.btn{font-size:18px}` rule sizing the text) but
   the background and colour never applied, so it has looked like this since the
   two-door photo capture went in.
   Fixed above by widening those selectors. The rules below make the pair read as
   the primary action pair they are: full-width and thumb-sized on a phone. */
.pcap label.btn{ line-height:1.2 }
@media (max-width: 900px), (pointer: coarse){
  .pcap{ gap:10px }
  .pcap label.btn{ flex:1 1 100%; min-height:48px; font-size:16px }
}
/* The trade picker, on the photo block and in the Voice Log sheet. */
select.whose{ flex:1 1 100%; min-width:0 }
.vn-whose{ display:flex; align-items:center; gap:10px; margin-top:10px; flex-wrap:wrap }
.vn-whose select.whose{ flex:1 1 auto }

/* ============================================================================
   v28.27 — ADMINISTRATION IN ORDER, MANY FILES AT ONCE, AND THE INVITE LIST.
   ============================================================================
   Founder, three things in one sitting:
     "project setup is different than project intake. this doesnt make sense.
      this should be at the administration level. there should be a create new
      project. that is then followed by the new project intake."
     "I need to be able to drop many files at once and see that they upload and
      also be able to delete them."
     "what is take off the shelf? this should be archive or delete."

   The numbered administration steps exist because two buttons side by side with
   no number on them are a menu, and he needed a sequence. The number is not
   decoration: it is the whole content of the fix. */
.adminstep{ display:flex; align-items:flex-start; gap:12px; width:100%; text-align:left;
  margin-bottom:8px; padding:12px 14px; line-height:1.35 }
.adminstep .stepno{ flex:0 0 24px; height:24px; border-radius:50%; display:flex; align-items:center;
  justify-content:center; font-size:13px; font-weight:700; background:rgba(255,255,255,.22) }
.btn.ghost .adminstep .stepno, .adminstep.ghost .stepno{ background:#e8edf6; color:var(--blue) }
.adminstep b{ display:block; font-size:14px }
.adminstep small{ display:block; font-weight:400; opacity:.85; font-size:12px; margin-top:2px }

/* One row per dropped file. It has to survive forty rows on a laptop and still be
   correctable on a phone, so the editable fields wrap and the filename sits
   underneath on its own line — the filename is evidence of what the guessed number
   came from, and hiding it would make a wrong guess impossible to check. */
.bulkrow{ display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:8px 0; border-bottom:1px solid #eef1f6 }
.bulkrow .pill{ flex:0 0 auto; min-width:74px; text-align:center }
.bulkrow .bulkfile{ flex:1 1 100%; font-size:11px; margin-left:2px }
.bulkrow input, .bulkrow select{ min-height:36px }
@media (max-width: 900px), (pointer: coarse){
  .bulkrow input, .bulkrow select{ min-height:44px; font-size:16px }
  .bulkrow input[data-bn]{ width:100px }
  .bulkrow input[data-bt]{ flex:1 1 100% }
}

/* v28.27 — THE DASHBOARD ROWS WERE NEVER THUMB-SIZED.
   Found by the usability walk the moment the new bulk drop put real rows on a QC
   manager's plate: `.plate-row` had 7px of padding and no minimum, so a row measured
   319x15 and the "open the file" link inside it 76x14 — both well under the 44px a
   gloved thumb needs, and both latent since v28.4 because the walk had never seen a
   populated plate. The comment above plateRow() says "the whole card is the target,
   which is what makes it usable with a glove on"; this is the CSS that makes that
   sentence true. */
.plate-row{ min-height:44px; display:flex; flex-direction:column; justify-content:center }
.plate-row a{ display:inline-block; padding:8px 2px; min-height:24px }
@media (max-width: 900px), (pointer: coarse){
  .plate-row{ padding:11px 0 !important; min-height:48px }
  .plate-row a{ min-height:44px; line-height:28px; font-size:13px }
  .plate-row .muted{ font-size:13px }
}

/* ============================================================================
   v28.28 — WHAT THE FULL EVALUATION FOUND.
   ============================================================================
   Founder: "you really need to do a full evaluation. I am finding simple things
   that should be commen sense for you as a SME yourself. easy, flow, appearance."

   He was right. The automated walks kept reporting CLEAN because they assert what
   somebody already thought to assert; they cannot see that a control is 28px off
   the edge of a phone or that a table has stopped being a table. Every rule below
   comes from a measurement taken on a rendered screen at 390x844. */

/* 1. A WIDE TABLE SCROLLS IN ITS OWN BOX, WITH ITS COLUMNS INTACT.
   The wrapper is the scroller. The table stays a table. */
.tscroll{ max-width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch; position:relative }
/* On a phone the table itself is the scroller (the v27.5 rule above restores
   display:table on its parts so the columns survive). The wrapper then must NOT
   also scroll, or a swipe fights two scrollers. */
@media (max-width: 900px){ .tscroll{ overflow-x:visible } }
.tscroll > table{ margin:0 }
/* A person who cannot see a scrollbar does not know to swipe, so a table that
   genuinely overflows says so — and stops saying it the moment they do. */
.tscroll-more::after{
  content:'swipe for status →'; position:sticky; right:0; bottom:0; float:right;
  display:block; margin:-26px 0 0; padding:3px 8px; border-radius:10px 0 0 0;
  background:linear-gradient(90deg,rgba(255,255,255,0),#eef2f8 40%);
  font-size:11.5px; color:#41506b; pointer-events:none; white-space:nowrap;
}
@media (min-width: 900px){ .tscroll-more::after{ display:none } }

/* 2. THE MASTHEAD STOPS TRYING TO FIT SIX CONTROLS ON A PHONE.
   Measured: Sign out ran x=374..418 on a 390px viewport and was clipped by
   .topbar{overflow:hidden} — a person on a phone could not sign out. Setup
   overlapped the wordmark. Six 44px targets plus gaps plus a wordmark do not fit
   390px and no amount of shaving labels changes that arithmetic. On a phone the bar
   keeps the two genuinely one-tap things and the rest moves into a sheet. */
#barMenuBtn{ display:none }
@media (max-width: 760px){
  #barMenuBtn{ display:inline-flex; align-items:center; justify-content:center;
    min-width:44px; min-height:44px; background:none; border:1px solid #2c3e5c;
    border-radius:10px; color:#fff; cursor:pointer }
  .who #askR, .who #vnBtn, .who .who-setup, .who #signOut,
  .who #userPicker, .who label, .who #whoName{ display:none !important }
  .who{ gap:8px }
}
#barMenu{ position:fixed; inset:0; background:rgba(10,16,26,.55); z-index:400;
  display:flex; align-items:flex-end; justify-content:center }
.bm-sheet{ background:#fff; width:100%; max-width:520px; border-radius:16px 16px 0 0;
  padding:8px 10px calc(10px + env(safe-area-inset-bottom)) }
.bm-who{ padding:12px 12px 10px; border-bottom:1px solid var(--line) }
.bm-who b{ display:block; font-size:16px }
.bm-who .muted{ font-size:13px }
.bm-row{ display:block; width:100%; text-align:left; background:none; border:none;
  border-bottom:1px solid #eef1f6; padding:15px 12px; font-size:16px; font-family:inherit;
  color:var(--navy); cursor:pointer; min-height:52px }
.bm-row:active{ background:#eef2f8 }
.bm-out{ color:#c0392b; font-weight:700 }
.bm-close{ border-bottom:none; color:var(--muted); text-align:center }

/* 3. THE OWNER CONSOLE.
   Founder: "also, how do I see and manage customers?" — a question the product had
   no answer to. It is a wide, dense, read-mostly screen, so it gets the width it
   needs on a desktop and turns into stacked scrolling tables on a phone. */
.ocbox{ background:#fff; border-radius:12px; width:100%; max-width:1080px;
  max-height:94vh; overflow:auto; padding:18px 20px }
.octiles{ display:grid; grid-template-columns:repeat(6,1fr); gap:10px; margin-bottom:6px }
.octile{ border:1px solid var(--line); border-radius:10px; padding:10px 12px; background:#f8fafd }
.octile b{ display:block; font-size:22px; line-height:1.1; color:var(--navy) }
.octile span{ font-size:12px; color:var(--muted) }
.ocsec{ font-size:14px; margin:18px 0 6px; color:var(--navy) }
.ocbox table{ width:100%; font-size:13px }
@media (max-width: 900px), (pointer: coarse){
  .ocbox{ padding:14px 12px; max-height:100vh; border-radius:0 }
  .octiles{ grid-template-columns:repeat(2,1fr) }
  .octile b{ font-size:20px }
}

/* 4. ON A PHONE, A ROW IS A CARD.
   Measured on the punch list at 390px: the table was 1420px wide in a 368px window.
   Even once it scrolled correctly, reading one deficiency meant swiping sideways
   past four columns, and doing that again for the next one. A table is a desktop
   element; below 740px each row restacks into a card with its column headings
   carried down as labels (set by wrapTables() in app.js, generically, for all
   eighteen tables in the product). */
@media (max-width: 740px){
  #view table, .ocbox table{ display:block; width:100%; border:0 }
  /* display:none, not the visually-hidden clip trick. Caught by walk-usable and
     walk-field: a clipped thead still reports its <tr>/<th> rectangles at full
     table width, so the overflow guard measured 218px of header running off the
     right edge on a screen where nothing was actually visible there. Nothing is
     lost by removing it — wrapTables() copies every column heading onto the cell
     it belongs to, so the label a screen reader announces travels WITH the value
     instead of sitting in a header row a card layout has no use for. */
  #view table thead, .ocbox table thead{ display:none }
  #view table tbody, .ocbox table tbody{ display:block }
  #view table tbody tr, .ocbox table tbody tr{
    display:block; border:1px solid var(--line); border-radius:10px;
    padding:10px 12px; margin:0 0 10px; background:#fff;
  }
  #view table tbody td, .ocbox table tbody td{
    display:flex; gap:10px; align-items:baseline; width:auto; border:0;
    padding:4px 0; font-size:14px; text-align:left;
    /* min-width:0 and overflow-wrap are what stop a long value — "Mark ready for
       review", a spec title, an email address — pushing the card past the screen.
       Caught by walk-desktop at 412px: a card layout that overflows is the same
       bug as the table it replaced, wearing a different shape. */
    min-width:0; overflow-wrap:anywhere;
  }
  /* Every DESCENDANT, not just the direct children. Caught by walk-desktop at 412px:
     the submittal scope <select> sits inside a wrapping <div>, so capping only the
     td's own children left the select at its intrinsic width — the longest option
     ("scope: UNVERIFIABLE") set it 102px past the screen. */
  #view table tbody td *, .ocbox table tbody td *{ min-width:0; max-width:100% }
  #view table tbody td select, #view table tbody td input{ width:100%; box-sizing:border-box }
  #view table tbody td::before, .ocbox table tbody td::before{
    content:attr(data-label); flex:0 0 38%; max-width:38%;
    font-size:11.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
    color:var(--muted);
  }
  /* The first cell is the thing itself — it gets to be a headline, not a field. */
  #view table tbody td:first-child, .ocbox table tbody td:first-child{
    display:block; font-size:16px; font-weight:700; padding:0 0 6px;
    border-bottom:1px solid #eef1f6; margin-bottom:6px;
  }
  #view table tbody td:first-child::before, .ocbox table tbody td:first-child::before{ display:none }
  /* An empty cell is noise on a card: a label with nothing after it. */
  #view table tbody td.td-empty, .ocbox table tbody td.td-empty{ display:none }
  /* The action column has no heading, so it becomes the card's footer row. */
  #view table tbody td.td-actions, .ocbox table tbody td.td-actions{
    display:flex; flex-wrap:wrap; gap:12px; padding-top:10px; margin-top:6px;
    border-top:1px solid #eef1f6; align-items:center;
  }
  #view table tbody td.td-actions::before{ display:none }
  #view table tbody td.td-actions .link, .ocbox table tbody td.td-actions .link{
    min-height:44px; display:inline-flex; align-items:center;
  }
  /* Nothing overflows any more, so the swipe hint must not appear. */
  .tscroll-more::after{ display:none }
  #view table tbody tr td:empty{ display:none }
}
.ochead{ display:flex; align-items:center; gap:10px; margin-bottom:2px }
.ochead b{ font-size:17px; color:var(--navy) }
.ochead .link{ margin-left:auto; font-size:14px }

/* ============================================================================
   v28.29 — MARKUPS: THE PAPER THAT WALKS THE JOB.
   ============================================================================
   Founder: "Some times team members will print plans or docs and walk the project and
   make notes on them. we need to be able to 'scan' them in and then convert the notes
   then log those notes and the document... we also need to make notes on the plans that
   are personal, internal or published."

   A PIN and a MARKUP sit on the same sheet and must never be mistaken for each other.
   A pin is a captured observation other people act on; a markup is a note, and most
   notes are not for other people yet. So a pin stays a filled dot and a markup is a
   numbered square — a glance tells you which is which, and the colour tells you who can
   see it. */
.mkdot{ position:absolute; transform:translate(-50%,-50%); min-width:22px; height:22px;
  border-radius:5px; color:#fff; font-size:12px; font-weight:700; line-height:22px;
  text-align:center; box-shadow:0 0 0 2px #fff, 0 1px 3px rgba(0,0,0,.4); cursor:pointer; padding:0 4px }
.mklevels{ display:flex; gap:6px 16px; flex-wrap:wrap; margin:6px 0 10px }
/* Three short labels that must read as one row of switches, not a paragraph that
   happens to have boxes in it. */
.mklevels .mklev{ white-space:nowrap }
.mklev{ display:flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; min-height:32px }
.mklev .sw{ width:12px; height:12px; border-radius:3px; display:inline-block }
.mkrow{ border-top:1px solid #eef1f6; padding:8px 0 }
.mkhead{ display:flex; gap:8px; align-items:flex-start; font-size:14px; line-height:1.35 }
.mknum{ flex:0 0 20px; height:20px; border-radius:4px; color:#fff; font-size:11px;
  font-weight:700; line-height:20px; text-align:center }
/* The handwriting travels with the words, permanently. If this image ever stops being
   rendered next to the transcription, the evidence for that note is gone and only a
   machine's opinion of it is left. */
.mkcrop{ display:block; max-width:100%; max-height:120px; margin:6px 0 4px;
  border:1px solid var(--line); border-radius:6px; background:#fff }

#mkScanPanel{ position:fixed; inset:0; background:rgba(10,16,26,.6); z-index:250;
  display:flex; align-items:flex-start; justify-content:center; padding:3vh 10px; overflow:auto }
.mkbox{ background:#fff; border-radius:12px; width:100%; max-width:760px; padding:18px 20px }
.mkwarp{ display:block; width:100%; border:1px solid var(--line); border-radius:8px; margin-bottom:12px }
.mkconf{ display:flex; flex-direction:column; gap:10px }
/* Crop on the left, what the machine thinks it says on the right, at the same size —
   so checking one against the other is a glance and not a task. */
.mkc{ display:flex; gap:12px; align-items:flex-start; border:1px solid var(--line);
  border-radius:10px; padding:10px; background:#fbfcfe }
.mkc > img{ flex:0 0 190px; max-width:190px; max-height:130px; object-fit:contain;
  background:#fff; border:1px solid var(--line); border-radius:6px }
.mkc-nocrop{ flex:0 0 190px; height:80px; display:flex; align-items:center; justify-content:center;
  color:var(--muted); font-size:12px; border:1px dashed var(--line); border-radius:6px }
.mkcb{ flex:1; min-width:0 }
.mkcb input{ width:100%; box-sizing:border-box; font-size:15px; min-height:40px }
.mkkeep{ display:flex; align-items:center; gap:8px; font-size:13px; margin-top:8px; min-height:32px }
.mkkeep input{ width:18px; height:18px }
@media (max-width: 740px){
  .mkc{ flex-direction:column }
  .mkc > img, .mkc-nocrop{ flex:1 1 auto; max-width:100%; width:100% }
  .mkbox{ padding:14px 12px }
  .mkcb input{ min-height:44px; font-size:16px }
  .mkkeep{ min-height:44px }
}
#mkNew{ position:fixed; inset:0; background:rgba(10,16,26,.55); z-index:255;
  display:flex; align-items:center; justify-content:center; padding:12px }
.mkflash{ background:#fff6d8; transition:background .4s }
#ocReset{ position:fixed; inset:0; background:rgba(10,16,26,.55); z-index:260;
  display:flex; align-items:center; justify-content:center; padding:12px }
