:root{
--bg:#F5F7F9;--surface:#FFFFFF;--surface-2:#FAFBFC;--surface-3:#F0F3F6;
--ink:#0D1826;--ink-2:#4A586B;--ink-3:#7B8899;--ink-4:#66727F;--ink-5:#A2ADBB;
--line:#E5E9EE;--line-2:#D3D9E1;
--navy:#0E2740;--navy-2:#0A1D30;--navy-line:rgba(255,255,255,.10);--navy-ink:#B8C6D6;--navy-ink-2:#7E92A8;
--brand:#2E6BA8;--brand-ink:#1C4E80;--brand-soft:#E7F0F8;
--slate:#64748B;--slate-soft:#EDF0F4;
--blue:#2E6BA8;--blue-soft:#E7F0F8;
--teal:#0E7C6B;--teal-soft:#E1F0ED;
--violet:#5F51BE;--violet-soft:#ECEAF9;
--amber:#A9711A;--amber-soft:#FAF0DC;
--green:#12795A;--green-soft:#E2F0EB;
--red:#BE4340;--red-soft:#FAEAE9;
--faint:#93A0AF;--faint-soft:#F0F2F5;
--r:10px;--r-sm:8px;
--sh:0 1px 2px rgba(13,24,38,.05);--sh-2:0 1px 3px rgba(13,24,38,.07),0 8px 24px rgba(13,24,38,.06);
--ring:0 0 0 3px rgba(46,107,168,.18);
/* Tells the browser which palette its OWN widgets should use — the native date
   and time pickers, form controls, scrollbars, autofill and the caret. It was
   never declared, so every one of them rendered light: the date picker opened
   as a white calendar in the middle of a dark app, because the browser had no
   way to know the app was dark. It has to be set in all THREE theme blocks,
   the same way the tokens are. */
color-scheme:light;
}
[data-theme="dark"]{
color-scheme:dark;
--bg:#0A1017;--surface:#111A24;--surface-2:#151F2A;--surface-3:#1A2632;
--ink:#E9EEF4;--ink-2:#9FADBE;--ink-3:#72808F;--ink-4:#8894A2;--ink-5:#5A6775;
--line:#1E2A37;--line-2:#2A3846;
--navy:#080E15;--navy-2:#05090E;--navy-line:rgba(255,255,255,.07);--navy-ink:#9DAEC1;--navy-ink-2:#67788B;
--brand:#579BDD;--brand-ink:#7DB4E8;--brand-soft:#132738;
--slate:#93A2B5;--slate-soft:#1B2531;
--blue:#579BDD;--blue-soft:#132738;
--teal:#3FBBA5;--teal-soft:#0D2A27;
--violet:#9A8DEC;--violet-soft:#1D1B33;
--amber:#D9A03F;--amber-soft:#2C2312;
--green:#3DB88E;--green-soft:#0C2721;
--red:#E4736F;--red-soft:#2C1615;
--faint:#75828F;--faint-soft:#182029;
--sh:0 1px 2px rgba(0,0,0,.4);--sh-2:0 2px 6px rgba(0,0,0,.45),0 12px 32px rgba(0,0,0,.4);
--ring:0 0 0 3px rgba(87,155,221,.22);
}
/* Honor the OS preference when the user hasn't made an explicit choice.
   An explicit [data-theme] (from the sidebar toggle, persisted) always wins. */
@media (prefers-color-scheme:dark){:root:not([data-theme]){
color-scheme:dark;
--bg:#0A1017;--surface:#111A24;--surface-2:#151F2A;--surface-3:#1A2632;
--ink:#E9EEF4;--ink-2:#9FADBE;--ink-3:#72808F;--ink-4:#8894A2;--ink-5:#5A6775;
--line:#1E2A37;--line-2:#2A3846;
--navy:#080E15;--navy-2:#05090E;--navy-line:rgba(255,255,255,.07);--navy-ink:#9DAEC1;--navy-ink-2:#67788B;
--brand:#579BDD;--brand-ink:#7DB4E8;--brand-soft:#132738;
--slate:#93A2B5;--slate-soft:#1B2531;
--blue:#579BDD;--blue-soft:#132738;
--teal:#3FBBA5;--teal-soft:#0D2A27;
--violet:#9A8DEC;--violet-soft:#1D1B33;
--amber:#D9A03F;--amber-soft:#2C2312;
--green:#3DB88E;--green-soft:#0C2721;
--red:#E4736F;--red-soft:#2C1615;
--faint:#75828F;--faint-soft:#182029;
--sh:0 1px 2px rgba(0,0,0,.4);--sh-2:0 2px 6px rgba(0,0,0,.45),0 12px 32px rgba(0,0,0,.4);
--ring:0 0 0 3px rgba(87,155,221,.22);
}}
*{box-sizing:border-box}
/* A <button> with no author `color` falls through to the UA default
   `buttontext`, which is BLACK — and black on this app's dark surfaces is
   unreadable. It bit four widgets at once (.rem in Coming up, .ag in Next 7
   days, .fr in the pipeline funnel, .hot in Hottest deals): in each, the child
   spans that DID set a colour looked fine while the ones that didn't rendered
   black, so it read as "some text is broken" rather than as one missing rule.
   Inheriting is the right default — every button that wants its own colour
   (.btn, .btn.primary, .nav button, .sidebtn, .iconbtn ...) sets one and still
   wins on specificity. Same failure the .iline/.costrow inputs had. */
button{color:inherit}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink);font:400 13.5px/1.5 "Instrument Sans",-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
h1,h2,h3{margin:0;font-weight:600;letter-spacing:-.014em}
a{color:var(--brand);text-decoration:none}
a:hover{color:var(--brand-ink);text-decoration:underline}
button{font:inherit}
[hidden]{display:none!important}
.tnum{font-variant-numeric:tabular-nums}
::selection{background:var(--brand-soft);color:var(--brand-ink)}
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:8px;border:3px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-track{background:transparent}

/* ---------- shell ---------- */
/* THE SHELL IS LOCKED. .app is a fixed-height box that never scrolls; the only
   scroller in the application is .scrollarea. 100dvh (not 100vh) so that
   iOS Safari's collapsing toolbars don't push the bottom of the app off-screen
   — the 100vh line before it is the fallback for browsers without dvh.
   grid-template-columns is transitioned because collapsing the nav animates it. */
.app{display:grid;grid-template-columns:236px minmax(0,1fr);height:100vh;height:100dvh;overflow:hidden;
  transition:grid-template-columns .18s ease,grid-template-rows .18s ease}
.side{background:var(--navy);color:#EDF2F7;display:flex;flex-direction:column;padding:18px 14px 14px;gap:20px;border-right:1px solid var(--navy-2)}
.brand{display:flex;align-items:center;gap:11px;padding:0 6px}
.mark{width:34px;height:34px;border-radius:9px;flex:none;object-fit:cover;box-shadow:inset 0 1px 0 rgba(255,255,255,.18),0 1px 3px rgba(0,0,0,.35)}
.bt{display:flex;flex-direction:column;line-height:1.2}
.bt b{font-weight:600;font-size:14px;color:#F4F8FC;letter-spacing:-.01em}
.bt em{font-style:normal;font-size:11px;color:var(--navy-ink-2);letter-spacing:.01em}
.navlab{display:block;font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--navy-ink-2);padding:0 8px;margin-bottom:7px}
.nav{display:flex;flex-direction:column;gap:2px}
.nav button{display:flex;align-items:center;gap:10px;width:100%;text-align:left;background:transparent;border:0;color:var(--navy-ink);padding:8px 9px;border-radius:8px;cursor:pointer;font-size:13.5px;font-weight:500;letter-spacing:-.005em}
.nav button svg{width:16px;height:16px;flex:none;opacity:.75}
.nav button:hover{background:rgba(255,255,255,.06);color:#F2F6FA}
.nav button[aria-current="true"]{background:rgba(255,255,255,.11);color:#fff;font-weight:600}
.nav button[aria-current="true"] svg{opacity:1;color:#7FB3E6}
.nb{margin-left:auto;font-size:11px;font-weight:600;color:var(--navy-ink-2);font-variant-numeric:tabular-nums}
.nb.warn{background:var(--red);color:#fff;border-radius:999px;min-width:18px;text-align:center;padding:0 5px;line-height:17px}
.sidefoot{margin-top:auto;display:flex;flex-direction:column;gap:2px}
.sidebtn{display:flex;align-items:center;gap:10px;width:100%;background:transparent;border:0;color:var(--navy-ink);padding:8px 9px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500}
.sidebtn svg{width:16px;height:16px;opacity:.7}
.sidebtn:hover{background:rgba(255,255,255,.06);color:#F2F6FA}
.userrow{display:flex;align-items:center;gap:9px;margin-top:10px;padding:10px 9px 2px;border-top:1px solid var(--navy-line)}
.av{width:28px;height:28px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:11px;font-weight:600;color:#DCE8F4;background:rgba(255,255,255,.10);border:1px solid var(--navy-line)}
.uinfo{min-width:0;display:flex;flex-direction:column;gap:2px}
.uinfo b{font-weight:500;font-size:12px;color:#E6EDF4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:118px}
.savechip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;color:var(--navy-ink-2);letter-spacing:.01em}
.savechip i{width:6px;height:6px;border-radius:50%;background:var(--navy-ink-2)}
.savechip[data-s="saved"] i{background:#3DBF8E;box-shadow:0 0 0 3px rgba(61,191,142,.16)}
.savechip[data-s="saving"] i{background:#D9A03F}
.savechip[data-s="error"] i{background:#E4736F}
.iconbtn{margin-left:auto;background:transparent;border:0;color:var(--navy-ink-2);cursor:pointer;padding:5px;border-radius:7px;display:grid;place-items:center}
.iconbtn svg{width:15px;height:15px}
.iconbtn:hover{background:rgba(255,255,255,.08);color:#fff}

.main{display:flex;flex-direction:column;min-width:0;overflow:hidden}
.top{display:flex;align-items:center;gap:14px;padding:0 24px;height:60px;flex:none;background:var(--surface);border-bottom:1px solid var(--line)}
.ttl{display:flex;align-items:baseline;gap:9px;min-width:0}
.ttl h1{font-size:17px;letter-spacing:-.02em}
.count{font-size:12.5px;color:var(--ink-3);white-space:nowrap;font-variant-numeric:tabular-nums;flex:0 1 auto}
.count b{color:var(--ink-2);font-weight:600}
.search{margin-left:14px;flex:1;max-width:340px;display:flex;align-items:center;gap:8px;background:var(--surface-3);border:1px solid transparent;border-radius:8px;padding:0 11px;height:34px}
.search:focus-within{background:var(--surface);border-color:var(--brand);box-shadow:var(--ring)}
.search svg{width:15px;height:15px;color:var(--ink-3);flex:none}
.search input{border:0;background:transparent;color:var(--ink);font:inherit;font-size:13px;width:100%;outline:none}
.search input::placeholder{color:var(--ink-3)}
.topacts{margin-left:auto;display:flex;align-items:center;gap:8px}
/* Lives in the top bar, not in the sidebar, because it has to stay reachable
   when the sidebar it controls is gone. */
.navtoggle{flex:none;width:32px;height:32px;display:grid;place-items:center;cursor:pointer;
  background:transparent;border:1px solid var(--line);border-radius:8px;color:var(--ink-3);padding:0}
.navtoggle:hover{background:var(--surface-3);color:var(--ink);border-color:var(--line-2)}
.navtoggle:focus-visible{outline:0;box-shadow:var(--ring)}
.navtoggle svg{width:16px;height:16px}
.navtoggle .pnl{transition:transform .18s ease;transform-origin:center}
body.navhidden .navtoggle .pnl{transform:scaleX(-1)}
.scrollarea{--pt:20px;flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:var(--pt) 24px 64px;display:flex;flex-direction:column;gap:16px}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:500;padding:0 13px;height:34px;border-radius:8px;border:1px solid var(--line-2);background:var(--surface);color:var(--ink);cursor:pointer;white-space:nowrap;transition:background .12s,border-color .12s}
.btn svg{width:15px;height:15px;opacity:.8}
.btn:hover{border-color:var(--ink-4);background:var(--surface-2)}
.btn:focus-visible{outline:none;box-shadow:var(--ring)}
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff}
.btn.primary:hover{background:var(--brand-ink);border-color:var(--brand-ink)}
.btn.ghost{background:transparent;border-color:transparent;color:var(--ink-2)}
.btn.ghost:hover{background:var(--surface-3);color:var(--ink)}
.btn.danger{color:var(--red);border-color:var(--line-2)}
.btn.danger:hover{border-color:var(--red);background:var(--red-soft)}
.btn.sm{height:29px;font-size:12.5px;padding:0 10px}
.btn .bts{display:none}

/* ---------- metrics ---------- */
.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
@media(max-width:1080px){.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}
.kpi{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:14px 16px 15px;box-shadow:var(--sh);display:flex;flex-direction:column;gap:3px;min-width:0}
.kpi .k{font-size:10.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3)}
.kpi .v{font-size:27px;font-weight:600;letter-spacing:-.03em;line-height:1.15;font-variant-numeric:tabular-nums}
.kpi .v small{font-size:15px;font-weight:500;color:var(--ink-3);letter-spacing:-.01em}
.kpi .s{font-size:11.5px;color:var(--ink-3)}
.kpi .track{height:4px;border-radius:3px;background:var(--surface-3);overflow:hidden;margin:8px 0 6px}
.kpi .track b{display:block;height:100%;border-radius:3px;background:var(--brand)}
.kpi.accent{background:linear-gradient(155deg,#153A5E,#0C2036);border-color:#0A1D30;color:#EDF3F9}
.kpi.accent .k{color:#8FA9C2}.kpi.accent .s{color:#93AABF}
.kpi.accent .v{color:#fff}.kpi.accent .v small{color:#8FA9C2}
.kpi.accent .track{background:rgba(255,255,255,.14)}
.kpi.accent .track b{background:#5FA5E4}

/* ---------- pipeline bar ---------- */
.pipe{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:14px 16px 13px;box-shadow:var(--sh)}
.pipe-h{display:flex;align-items:baseline;gap:10px;margin-bottom:11px}
.pipe-h h2{font-size:12.5px;font-weight:600;letter-spacing:-.005em}
.pipe-h span{font-size:11.5px;color:var(--ink-3)}
.pipe-h .clr{margin-left:auto;font-size:11.5px;color:var(--brand);background:none;border:0;cursor:pointer;padding:0}
.pipe-h .clr:hover{text-decoration:underline}
.stack{display:flex;gap:2px;height:9px;margin-bottom:12px}
.stack b{display:block;height:100%;border-radius:2px;cursor:pointer;transition:opacity .12s,transform .12s}
.stack b:first-child{border-radius:5px 2px 2px 5px}
.stack b:last-child{border-radius:2px 5px 5px 2px}
.stack b:hover{opacity:.8}
.stack.dim b:not(.on){opacity:.24}
.legend{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px 14px}
@media(max-width:1080px){.legend{grid-template-columns:repeat(2,minmax(0,1fr))}}
.lg{display:flex;align-items:center;gap:8px;background:none;border:0;padding:4px 6px;margin:0 -6px;border-radius:6px;cursor:pointer;text-align:left;color:var(--ink-2);font-size:12.5px}
.lg:hover{background:var(--surface-3)}
.lg[aria-pressed="true"]{background:var(--surface-3);color:var(--ink);font-weight:500}
.lg i{width:7px;height:7px;border-radius:2px;flex:none}
.lg .n{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--ink-3);font-weight:500}
.lg[aria-pressed="true"] .n{color:var(--ink)}

/* ---------- toolbar ---------- */
.toolbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.chip{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:500;height:30px;padding:0 11px;border-radius:999px;border:1px solid var(--line-2);background:var(--surface);color:var(--ink-2);cursor:pointer;white-space:nowrap}
.chip:hover{border-color:var(--ink-4);color:var(--ink)}
.chip[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--surface)}
.chip i{width:7px;height:7px;border-radius:2px;flex:none}
.chip .n{font-variant-numeric:tabular-nums;color:var(--ink-3);font-weight:500}
.chip[aria-pressed="true"] .n{color:var(--surface);opacity:.7}
.tsep{width:1px;height:20px;background:var(--line);margin:0 4px}
.tlab{font-size:10.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-4);margin-right:2px}

/* ---------- pills ---------- */
.pill{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:500;padding:2px 9px;border-radius:999px;white-space:nowrap;line-height:1.5;border:1px solid transparent}
.st-prospect{background:var(--slate-soft);color:var(--slate)}
.st-contacted{background:var(--blue-soft);color:var(--blue)}
.st-interested{background:var(--teal-soft);color:var(--teal)}
.st-demo{background:var(--violet-soft);color:var(--violet)}
.st-negotiation{background:var(--amber-soft);color:var(--amber)}
.st-won{background:var(--green-soft);color:var(--green)}
.st-lost{background:var(--red-soft);color:var(--red)}
.st-parked{background:var(--faint-soft);color:var(--faint)}
.pf-vsi{background:var(--navy);color:#EAF2FA}
.pf-fleetio{background:var(--red-soft);color:var(--red)}
.pf-paper{background:var(--slate-soft);color:var(--slate)}
.pf-other{background:var(--violet-soft);color:var(--violet)}
.pf-none{background:transparent;color:var(--ink-4);border-color:var(--line-2)}
.pf{background:var(--pcs,var(--slate-soft));color:var(--pc,var(--slate))}
[data-theme="dark"] .pf-vsi{background:#1C4067;color:#D2E5F7}
.unconf{opacity:.6}

/* ---------- settings: platforms ---------- */

/* Settings as one tile with a tab bar. Stacked cards put Google Calendar
   below a platform list that grows every time somebody adds a platform, and a
   setting's discoverability falls off with every row above it. */
.settile{padding:0;overflow:hidden}
.settabs{display:flex;gap:2px;padding:6px 6px 0;border-bottom:1px solid var(--line);
  overflow-x:auto;scrollbar-width:none}
.settabs::-webkit-scrollbar{display:none}
.settabs button{border:0;background:none;font:inherit;font-size:12.5px;font-weight:500;
  color:var(--ink-3);padding:9px 14px;cursor:pointer;white-space:nowrap;
  border-radius:var(--r-sm) var(--r-sm) 0 0;position:relative}
.settabs button:hover{color:var(--ink-2);background:var(--surface-2)}
/* The underline sits ON the container's border, so the active tab reads as
   joined to the pane rather than floating above it. */
.settabs button.on{color:var(--ink);font-weight:600}
.settabs button.on::after{content:"";position:absolute;left:10px;right:10px;bottom:-1px;
  height:2px;border-radius:2px 2px 0 0;background:var(--brand)}
.settabs button:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
/* No padding: every child here — .sethead, .platadd, .setnote, .gcalrow —
   already carries its own 20px gutter, which is what let these sections be
   full-bleed cards before. Adding pane padding would double every one. */
.setpane{padding:0}
/* The tab bar is the tile's top edge now, so the first section heading must
   not draw a second line right under it. */
.setpane>.sethead:first-child{border-top:0}
/* ...and the last note must not leave a rule hanging above the tile's edge. */
.setpane>*:last-child{border-bottom:0}
@media(max-width:640px){
  .settabs button{padding:9px 11px;font-size:12px}
}
.setcard{max-width:700px;padding:0}
.sethead{padding:18px 20px 15px;border-bottom:1px solid var(--line)}
.sethead h2{font-size:18px;font-weight:600}
.sethead p{margin:5px 0 0;color:var(--ink-3);font-size:13px;max-width:62ch}
.platlist{display:flex;flex-direction:column}
.platrow{display:flex;align-items:center;gap:13px;padding:11px 20px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.platrow.deleting{background:var(--red-soft)}
.pf-sw{width:13px;height:13px;border-radius:4px;flex:none;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}
/* The "core"/"locked" badge is a SIBLING of .pn-lock in the markup, not a child
   of it — so .pn-lock's inline-flex and gap:8px, written to sit a name beside
   its badge, never applied to the badge at all. Both were children of this
   column, and a flex item in a column stretches: the badge rendered as a
   full-width block on its own line, 453px wide on a desktop and 206px on a
   phone, for a word like "core".
   Row-wrap puts the name and its badge on one line the way the markup reads,
   and the district count keeps its own line by claiming a full basis. */
.platname{flex:1 1 150px;min-width:120px;display:flex;flex-wrap:wrap;align-items:center;gap:3px 8px}
.pn-lock{font-weight:600;font-size:14px}
.lk{font-size:9px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);background:var(--surface-3);border:1px solid var(--line);border-radius:4px;padding:1px 5px}
.platuse{flex:1 0 100%;font-size:11.5px;color:var(--ink-4)}
input.pn{font:inherit;font-size:14px;font-weight:500;color:var(--ink);background:var(--surface);border:1px solid transparent;border-radius:7px;padding:5px 9px;max-width:230px;width:100%}
input.pn:hover{border-color:var(--line-2)}
input.pn:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}
.platclrs{display:flex;gap:5px;flex-wrap:wrap;flex:none}
.platclrs.muted{font-size:11px;color:var(--ink-4);align-self:center}
.clr{width:19px;height:19px;border-radius:6px;border:2px solid transparent;cursor:pointer;padding:0;box-shadow:inset 0 0 0 1px rgba(0,0,0,.08)}
.clr:hover{transform:scale(1.12)}
.clr.on{border-color:var(--surface);box-shadow:0 0 0 2px var(--ink)}
.platord{display:flex;gap:3px;flex:none}
.ic{display:inline-flex;align-items:center;justify-content:center;width:29px;height:29px;border-radius:7px;border:1px solid var(--line);background:var(--surface);color:var(--ink-3);cursor:pointer;font-size:10px;line-height:1}
.ic:hover:not(:disabled):not(.dis){border-color:var(--ink-3);color:var(--ink)}
.ic:disabled,.ic.dis{opacity:.35;cursor:default}
.ic svg{width:15px;height:15px}
.platdel{flex:none}
.platdel .ic:hover{border-color:var(--red);color:var(--red)}
.del-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap;flex:1 1 auto;justify-content:flex-end}
.del-q{font-size:13px;color:var(--red);font-weight:500}
.del-sel{font:inherit;font-size:13px;padding:5px 8px;border-radius:7px;border:1px solid var(--line-2);background:var(--surface);color:var(--ink)}
.platadd{display:flex;align-items:center;gap:10px;padding:15px 20px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.platadd input.pn.add{max-width:210px;border-color:var(--line-2)}
.setnote{padding:13px 20px;margin:0;font-size:12px;color:var(--ink-4)}
/* Google Calendar mirror — status row in Settings */
.gcalrow{display:flex;align-items:center;gap:13px;padding:14px 20px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.gcaldot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--slate)}
.gcaldot.ok{background:var(--green)}
.gcaldot.bad{background:var(--red)}
.gcaldot.idle{background:var(--amber)}
.gcalstat{flex:1 1 220px;min-width:180px;display:flex;flex-direction:column;gap:2px}
.gcalstat b{font-size:14px;font-weight:600}
.gcalstat span{font-size:11.5px;color:var(--ink-4)}
.btn.sm{font-size:13px;padding:7px 13px;display:inline-flex;align-items:center;gap:6px}
.btn.sm svg{width:15px;height:15px}
.btn.danger{background:var(--red);border-color:var(--red);color:#fff}
.btn.danger:hover{filter:brightness(1.06)}

/* ---------- uploads: processing queue ---------- */
.up-card{max-width:940px;padding:0}
.up-top{display:flex;align-items:center;gap:12px;padding:11px 18px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.up-envs{display:flex;align-items:center;gap:7px}
.up-top [data-up-refresh],.up-top .chip.quiet{margin-left:auto;display:inline-flex;align-items:center;gap:6px}
.up-top [data-up-refresh] svg,.up-top .chip.quiet svg{width:14px;height:14px}
/* The refresh is a fallback, not the thing you came for — the list is live.
   It reads as available rather than offered. */
.chip.quiet{border-color:transparent;background:none;color:var(--ink-3)}
.chip.quiet:hover{background:var(--surface-2);color:var(--ink-2)}

/* Three states of ONE setting, so one control. Three pills of equal weight
   read as three separate switches. */
.envseg{display:inline-flex;gap:2px;padding:2px;background:var(--surface-2);
  border:1px solid var(--line);border-radius:9px}
.envseg button{border:0;background:none;font:inherit;font-size:12px;font-weight:600;
  color:var(--ink-3);padding:5px 13px;border-radius:7px;cursor:pointer;line-height:1.2}
.envseg button:hover{color:var(--ink-2)}
.envseg button.on{background:var(--surface);color:var(--ink);box-shadow:var(--sh)}
.envseg button:focus-visible{outline:2px solid var(--brand);outline-offset:-1px}

/* Groups, with air between them instead of hairline dividers. A row of chips
   split by 1px rules reads as a toolbar; this reads as three questions. */
.up-filters{display:flex;align-items:center;gap:8px 20px;padding:11px 18px;
  border-bottom:1px solid var(--line);flex-wrap:wrap}
.fgrp{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}

/* Sections, not filters. A tab bar rather than another row of chips, because
   "whose work is this" is a different question from "what kind is it", and
   putting them in one row of pills makes them look like the same question. */
.scopetabs{display:flex;gap:2px;padding:0 12px;border-bottom:1px solid var(--line);overflow-x:auto;
  scrollbar-width:none}
.scopetabs::-webkit-scrollbar{display:none}
.scopetabs button{border:0;background:none;font:inherit;font-size:12.5px;font-weight:500;
  color:var(--ink-3);padding:11px 12px 10px;cursor:pointer;position:relative;white-space:nowrap;
  display:inline-flex;align-items:center;gap:6px}
.scopetabs button:hover{color:var(--ink-2)}
.scopetabs button.on{color:var(--ink);font-weight:600}
.scopetabs button.on::after{content:"";position:absolute;left:10px;right:10px;bottom:-1px;
  height:2px;background:var(--brand);border-radius:2px}
.scopetabs button:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.scopetabs .n{font-size:10.5px;font-weight:700;color:var(--ink-3);background:var(--surface-2);
  border-radius:999px;padding:1px 6px;font-variant-numeric:tabular-nums}
.scopetabs button.on .n{background:var(--brand-soft);color:var(--brand-ink)}
.up-list{display:flex;flex-direction:column}
.up-group{border-bottom:1px solid var(--line)}
.up-ghead{display:flex;align-items:baseline;gap:10px;padding:12px 18px 9px;background:var(--surface-2)}
.up-ghead b{font-size:14px;font-weight:600}
.up-gsub{font-size:11.5px;color:var(--ink-4)}
.up-gpend{margin-left:auto;font-size:11px;font-weight:600;color:var(--amber);background:var(--amber-soft);border-radius:999px;padding:2px 9px;align-self:center}
.up-req{border-top:1px solid var(--line)}
.up-rhead{display:grid;grid-template-columns:62px 48px 1fr auto auto 18px;align-items:center;gap:12px;padding:11px 18px;cursor:pointer}
.up-rhead:hover{background:var(--surface-2)}
.up-type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;padding:2px 0;border-radius:5px;text-align:center}
.up-fleet{background:var(--blue-soft);color:var(--blue)}
.up-parts{background:var(--violet-soft);color:var(--violet)}
.up-files{font-size:12px;color:var(--ink-3);display:inline-flex;align-items:center;gap:4px;white-space:nowrap;font-variant-numeric:tabular-nums}
.up-files svg{width:14px;height:14px;color:var(--ink-4)}
.up-by{font-size:13px;color:var(--ink-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.up-date{font-size:12px;color:var(--ink-4);white-space:nowrap;font-variant-numeric:tabular-nums}
.upst{font-size:11px;font-weight:600;padding:2px 10px;border-radius:999px;white-space:nowrap}
.up-pending{background:var(--amber-soft);color:var(--amber)}
.up-processing{background:var(--blue-soft);color:var(--blue)}
.up-complete{background:var(--green-soft);color:var(--green)}
.up-rejected{background:var(--red-soft);color:var(--red)}
.up-caret{color:var(--ink-4);display:inline-flex}
.up-caret svg{width:16px;height:16px;transform:rotate(90deg);transition:transform .15s}
.up-caret.open svg{transform:rotate(-90deg)}
.up-body{padding:2px 18px 16px;background:var(--surface-2)}
.up-filelist{display:flex;flex-direction:column;gap:6px;margin:10px 0}
.up-file{display:flex;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:8px 12px}
.up-fname{font-size:13px;font-weight:500;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.up-fmeta{font-size:11.5px;color:var(--ink-4);white-space:nowrap}
.up-note{font-size:13px;color:var(--ink-2);background:var(--amber-soft);border:1px solid var(--amber);border-radius:8px;padding:9px 12px;margin:4px 0 10px}
.up-note b{color:var(--amber)}
.up-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.up-cmeta{font-size:11.5px;color:var(--ink-4)}
.up-reject{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.up-reject .nf{flex:1 1 240px;min-width:0}
.up-reject input.pn{max-width:none;border-color:var(--line-2)}
.btn.danger-o{background:transparent;border-color:var(--red);color:var(--red)}
.btn.danger-o:hover{background:var(--red-soft)}
@media(max-width:640px){
  .up-rhead{grid-template-columns:56px 1fr auto 16px;gap:9px}
  .up-files,.up-date{display:none}
}

/* ---------- issues: VSI ticket backlog ---------- */
/* The number leads the row. Fixed width and tabular figures so the column is
   a column — a district reads one of these out over the phone, and a ragged
   left edge is the thing that makes you scan instead of glance. */
.is-rhead{grid-template-columns:74px 58px 78px minmax(0,1fr) auto auto 22px auto 18px}
/* Who owns it, as a face rather than a name: the row already spends its width
   on the title, and a backlog is scanned for "which of these are mine". */
.tk-asg{display:grid;place-items:center}
.tk-asg.none{color:var(--ink-4);font-size:11px}
.tk-no{font-size:11.5px;font-weight:700;letter-spacing:.01em;color:var(--ink-2);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.tk-no.pending{color:var(--ink-4);font-weight:500}
.tk-meta b{font-variant-numeric:tabular-nums;color:var(--ink-2)}
.tk-cat,.tk-prio{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 0;border-radius:5px;text-align:center}
.tk-feature{background:var(--teal-soft);color:var(--teal)}
.tk-bug{background:var(--red-soft);color:var(--red)}
.tk-spike{background:var(--violet-soft);color:var(--violet)}
.tk-cat.tk-none,.tk-prio.tk-none{background:transparent;color:var(--ink-4)}
.tk-immediate{background:var(--red-soft);color:var(--red)}
.tk-high{background:var(--amber-soft);color:var(--amber)}
.tk-medium{background:var(--blue-soft);color:var(--blue)}
.tk-low{background:var(--slate-soft);color:var(--slate)}
.tk-casual{background:var(--faint-soft);color:var(--ink-4)}
.tk-title{font-size:13.5px;font-weight:600;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.upst.is-new{background:var(--slate-soft);color:var(--slate)}
.upst.is-open,.upst.is-progress{background:var(--blue-soft);color:var(--blue)}
.upst.is-waiting{background:var(--amber-soft);color:var(--amber)}
.upst.is-resolved{background:var(--green-soft);color:var(--green)}
.upst.is-closed{background:var(--surface-3);color:var(--ink-4)}
.tk-fld{margin:9px 0}
.tk-flabel{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-4);margin-bottom:3px}
.tk-fval{font-size:13.5px;color:var(--ink-2);line-height:1.5}
.tk-img{font-size:13px}
.tk-meta{font-size:11.5px;color:var(--ink-4);margin:8px 0 12px}
.tk-work{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-sm);padding:12px 14px;display:flex;flex-direction:column;gap:11px}
.tk-row{display:flex;align-items:center;gap:11px;flex-wrap:wrap}
.tk-row.col{flex-direction:column;align-items:stretch;gap:5px}
.tk-l{font-size:11.5px;font-weight:600;color:var(--ink-3);min-width:60px}
.tk-l em{font-weight:400;color:var(--ink-4);font-style:normal}
input.pn.tk-status{max-width:220px}
.tk-ta{width:100%;resize:vertical;font:inherit;font-size:13.5px;color:var(--ink);background:var(--surface);border:1px solid var(--line-2);border-radius:8px;padding:7px 9px}
.tk-ta:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}
.tk-quick{display:flex;gap:5px;flex-wrap:wrap}
.chip.sm2{font-size:11.5px;padding:3px 9px}
.tk-actions{display:flex;gap:9px;margin-top:2px}
@media(max-width:640px){
  /* The status column was `auto`, so "Waiting on submitter" took 130px of a
     375px row and left the TITLE 70px — 15 characters of a 46-character
     sentence, on the one line that says what the ticket actually is. An
     upload row survives that squeeze because its middle column is a person's
     name; an issue row does not. So the title gets the whole line here and
     the status drops underneath it, where it reads as belonging to the title
     rather than competing with it. */
  .is-rhead{grid-template-columns:56px minmax(0,1fr) 16px;
    grid-template-areas:"kind title caret" "no stat asg"; row-gap:5px}
  .is-rhead .tk-no{grid-area:no}
  .is-rhead .tk-asg{grid-area:asg;justify-self:end}
  .is-rhead .tk-cat{grid-area:kind}
  .is-rhead .tk-title{grid-area:title}
  .is-rhead .upst{grid-area:stat;justify-self:start}
  .is-rhead .up-caret{grid-area:caret}
  /* Two lines, then ellipsis: at 280px and 13.5px almost every title fits on
     one, the longest needs two, and nothing turns a row into a paragraph. */
  .is-rhead .tk-title{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;
    -webkit-box-orient:vertical;overflow:hidden}
  .is-rhead .tk-prio,.is-rhead .up-by,.is-rhead .up-date{display:none}
}
.estmark{color:var(--ink-4);font-size:10px;margin-left:4px;letter-spacing:.02em}

/* pill-shaped inline select */
.pillsel{position:relative;display:inline-flex;align-items:center;gap:4px;max-width:152px;padding:2px 7px 2px 10px;border-radius:999px;font-size:11.5px;font-weight:500;line-height:1.55;border:1px solid transparent}
.pillsel .pt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pillsel .cv{width:11px;height:11px;flex:none;opacity:.5}
.pillsel select{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;border:0;font:inherit}
.pillsel:hover{filter:brightness(.96)}
.pillsel:focus-within{box-shadow:var(--ring)}
.pillsel select option{background:var(--surface);color:var(--ink)}

/* ---------- cards (shared surface) ---------- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--sh);overflow:hidden}

/* ---------- leads table ----------
   ONE scroll only: the page (.scrollarea) scrolls. The table is NOT a scroll
   container on either axis — no nested scroll-in-scroll. A fluid fixed layout
   keeps every column inside the content width (no horizontal scroll), and the
   column header sticks to the page scroll so it stays legible down a long list. */
.tablecard{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--sh)}
.tablewrap{overflow:visible}
.leadtable{border-collapse:separate;border-spacing:0;width:100%;table-layout:fixed;font-size:13px}
/* column widths (fixed layout — sum to 100) */
.leadtable .c-status{width:11.5%}
.leadtable .c-platform{width:10%}
.leadtable .c-district{width:17%}
.leadtable .c-sup{width:15%}
.leadtable .c-phone{width:11%}
.leadtable .c-addr{width:18.5%}
.leadtable .c-fleet{width:8%}
.leadtable .c-drive{width:7%}
.leadtable .c-go{width:2%}
/* contract column set (fixed layout — sums to 100). A SECOND set of 8, not a
   9th column: the table can never grow past its container (.tablewrap is
   overflow:visible by design), so a new column can only take pixels from an
   existing one. Toggling the set costs zero pixels. */
.leadtable.cs-contract .c-cstat{width:12.5%}
.leadtable.cs-contract .c-vstat{width:11.5%}
.leadtable.cs-contract .c-district{width:20%}
.leadtable.cs-contract .c-cval{width:11%}
.leadtable.cs-contract .c-cend{width:11%}
.leadtable.cs-contract .c-cbus{width:7%}
.leadtable.cs-contract .c-sup{width:16%}
.leadtable.cs-contract .c-fleet{width:9%}
.leadtable thead th{position:sticky;top:calc(var(--pt,20px)*-1);z-index:3;background:var(--surface);text-align:left;font-weight:600;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);padding:0 14px;height:40px;border-bottom:1px solid var(--line);box-shadow:0 1px 0 var(--line),0 6px 10px -8px rgba(13,24,38,.14);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;user-select:none}
.leadtable thead th:first-child{border-top-left-radius:var(--r)}
.leadtable thead th.c-go{border-top-right-radius:var(--r)}
.leadtable thead th:hover{color:var(--ink)}
.leadtable thead th.no{cursor:default}
.leadtable thead th .ar{color:var(--brand);font-size:9px;margin-left:3px}
.leadtable tbody td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:middle}
.leadtable tbody tr:last-child td{border-bottom:0}
.leadtable tbody tr:last-child td:first-child{border-bottom-left-radius:var(--r)}
.leadtable tbody tr:last-child td:last-child{border-bottom-right-radius:var(--r)}
.leadtable tbody tr{cursor:pointer}
.leadtable tbody tr:hover td{background:var(--surface-2)}
.leadtable tbody tr.state td{background:var(--brand-soft)}
.leadtable tbody tr.state:hover td{background:var(--brand-soft);filter:brightness(.985)}
.nm{font-weight:600;letter-spacing:-.008em;display:flex;align-items:center;gap:7px;flex-wrap:wrap;row-gap:3px}
.tybadge{font-size:9.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-4);border:1px solid var(--line-2);border-radius:4px;padding:0 4px;line-height:15px;flex:none}
.sub{font-size:11.5px;color:var(--ink-3);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
td.addr .a{color:var(--ink-2);font-size:12.5px;word-break:break-word}
.mp{margin-left:5px;color:var(--ink-4);display:inline-flex;vertical-align:-2px}
.mp svg{width:13px;height:13px}
.mp:hover{color:var(--brand)}
.dash{color:var(--ink-4)}
.drive b{font-weight:600;font-variant-numeric:tabular-nums}
.drive small{display:block;color:var(--ink-4);font-size:10.5px;font-variant-numeric:tabular-nums}
.phone a{color:var(--ink-2);font-variant-numeric:tabular-nums;font-size:12.5px}
.rowgo{color:var(--ink-4);opacity:0;display:inline-flex;transition:opacity .12s}
.rowgo svg{width:15px;height:15px}
.leadtable tbody tr:hover .rowgo{opacity:1}
.empty{padding:56px 20px;text-align:center;color:var(--ink-3);font-size:13px}
/* An empty screen is a thing to design. The old one-liner in a 56px-padded box
   left a card that was mostly nothing and said almost nothing with it. */
.emptyx{display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:9px;padding:52px 24px 56px}
/* The border is doing the work in LIGHT mode, where --surface-2 sits a couple
   of percent off the card behind it and the badge all but disappears. */
.emptyx .exic{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;
  background:var(--surface-2);border:1px solid var(--line);color:var(--ink-4);margin-bottom:3px}
.emptyx .exic svg{width:19px;height:19px}
.emptyx h3{font-size:15px;font-weight:600;color:var(--ink);margin:0;letter-spacing:-.005em}
.emptyx p{margin:0;font-size:13px;line-height:1.6;color:var(--ink-3);max-width:46ch}
.emptyx p b{font-weight:600;color:var(--ink-2)}
.emptyx .btn{margin-top:5px}
.loading{padding:70px 20px;text-align:center;color:var(--ink-3)}

/* Tablet: drop the lowest-priority columns so the table keeps fitting the
   content width (still one page scroll, no horizontal scroll). Hidden fields
   remain one tap away in the row drawer. */
@media (max-width:1180px){ .leadtable .c-addr{display:none} .leadtable.cs-contract .c-sup{display:none} }
@media (max-width:1040px){ .leadtable .c-drive{display:none} .leadtable.cs-contract .c-fleet{display:none} }

/* Phone: the table restyles into stacked cards — a spreadsheet doesn't belong
   on a 390px screen. Same DOM, no thead, one card per lead. */
@media (max-width:640px){
  .tablecard{border:0;background:transparent;box-shadow:none}
  .leadtable{table-layout:auto}
  /* EVERY column class must be here. One left out keeps its percentage under
     table-layout:auto and distorts the whole card grid. */
  .leadtable .c-status,.leadtable .c-platform,.leadtable .c-district,.leadtable .c-sup,
  .leadtable .c-phone,.leadtable .c-addr,.leadtable .c-fleet,.leadtable .c-drive,
  .leadtable .c-cstat,.leadtable .c-vstat,.leadtable .c-cval,.leadtable .c-cend,
  .leadtable .c-cbus,
  /* The .cs-contract widths are (0,3,0); a (0,2,0) reset LOSES to them and the
     card grid collapses into percentage-width slivers. Match the specificity. */
  .leadtable.cs-contract .c-cstat,.leadtable.cs-contract .c-vstat,
  .leadtable.cs-contract .c-district,.leadtable.cs-contract .c-cval,
  .leadtable.cs-contract .c-cend,.leadtable.cs-contract .c-cbus,
  .leadtable.cs-contract .c-sup,.leadtable.cs-contract .c-fleet{width:auto}
  .leadtable thead{display:none}
  .leadtable tbody{display:flex;flex-direction:column;gap:10px}
  .leadtable tbody tr{display:grid;grid-template-columns:1fr 1fr;gap:11px 12px;align-items:start;
    background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--sh);padding:14px 15px}
  .leadtable tbody tr:hover td{background:transparent}
  .leadtable tbody tr.state{border-color:var(--brand);box-shadow:inset 0 0 0 1px var(--brand),var(--sh)}
  .leadtable tbody tr.state td{background:transparent}
  .leadtable tbody td{padding:0;border:0!important;border-radius:0}
  .leadtable td.c-district{grid-column:1/-1;order:1}
  .leadtable td.c-district .nm{font-size:16px}
  .leadtable td.c-status{order:2}
  .leadtable td.c-platform{order:3}
  .leadtable td.c-sup{order:4;min-width:0}
  .leadtable td.c-phone{order:5}
  .leadtable td.c-addr{grid-column:1/-1;order:6;display:block}
  .leadtable td.c-fleet{order:7;display:table-cell}
  .leadtable td.c-drive{order:8;display:table-cell}
  /* Every new class needs an explicit order — the default 0 would render the
     cell ABOVE the card title (c-district is order:1) — and anything a tablet
     tier hid must be explicitly restored: the phone card deliberately shows
     MORE than the tablet table. */
  .leadtable td.c-cstat{order:2}
  .leadtable td.c-vstat{order:3}
  .leadtable td.c-cval{order:4}
  .leadtable td.c-cend{order:5}
  .leadtable td.c-cbus{order:7;display:table-cell}
  .leadtable.cs-contract td.c-sup{order:8;display:table-cell;min-width:0}
  .leadtable.cs-contract td.c-fleet{order:9;display:table-cell}
  .leadtable td.c-go{display:none}
  .leadtable td.c-status .pillsel,.leadtable td.c-platform .pillsel{width:100%;max-width:none}
  .leadtable td.c-cstat .mpill,.leadtable td.c-vstat .mpill{width:100%;text-align:center}
  .leadtable td.c-sup .sub{max-width:none}
  .leadtable td[data-label]::before{content:attr(data-label);display:block;font-size:10px;font-weight:600;
    letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:3px}
}

/* ---------- kanban ---------- */
.kanban{display:flex;gap:14px;overflow-x:auto;align-items:flex-start;padding-bottom:10px}
.kcol{flex:0 0 262px;display:flex;flex-direction:column;gap:8px}
.khead{display:flex;align-items:center;gap:8px;padding:0 3px 8px;border-bottom:2px solid var(--line)}
.khead i{width:7px;height:7px;border-radius:2px}
.khead .kt{font-size:12px;font-weight:600;letter-spacing:.02em}
.khead .kc{margin-left:auto;font-size:11.5px;font-weight:600;color:var(--ink-3);font-variant-numeric:tabular-nums}
.kcol.dragover .khead{border-bottom-color:var(--brand)}
.kcol.dragover .kbody{background:var(--brand-soft);border-radius:var(--r-sm)}
.kbody{display:flex;flex-direction:column;gap:8px;min-height:60px;padding:2px}
.kcard{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;box-shadow:var(--sh);cursor:grab}
.kcard:hover{border-color:var(--line-2);box-shadow:var(--sh-2)}
.kcard.dragging{opacity:.35}
.kcard .kn{font-weight:600;font-size:13px;letter-spacing:-.008em;margin-bottom:7px}
.kmeta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:11.5px;color:var(--ink-3)}
.ksup{margin-top:7px;padding-top:7px;border-top:1px solid var(--line);font-size:11.5px;color:var(--ink-2)}
.kempty{font-size:11.5px;color:var(--ink-4);padding:12px 3px}
/* Desktop reads the whole sentence; a touch device gets only the half that is
   true for it, via the :after swap below. */
.kanhint{font-size:11.5px;color:var(--ink-4);margin:0 0 10px;padding:0 2px}
@media(hover:none),(pointer:coarse){
  .kanhint{font-size:0}
  .kanhint:after{content:"Tap a card to open it and change its stage";font-size:11.5px}
}

/* Pipeline on a phone. The board already scrolls horizontally — that is the
   right shape for a kanban and it stays — but a fixed 262px column on a 375px
   screen leaves a 40px sliver of the next stage and no sense of paging. The
   column is sized to the viewport and snapped, so one swipe is exactly one
   stage.

   Dragging is NOT available here and cannot be: the board moves cards with the
   HTML5 drag events (dragstart/dragover/drop in the #viewbody handlers), and
   those never fire from a touch. The card is still a tap target that opens the
   district drawer, where the Status select does the same job — so the hint
   under the header says that rather than leaving a dead gesture to discover.
   `cursor:grab` is removed for the same reason: it advertises an interaction
   this device does not have. */
@media(hover:none),(pointer:coarse){
  .kcard{cursor:pointer}
}
@media (max-width:640px){
  .kanban{gap:10px;scroll-snap-type:x mandatory;scroll-padding-left:2px;
          -webkit-overflow-scrolling:touch}
  .kcol{flex:0 0 min(280px,84vw);scroll-snap-align:start}
  /* The header is the only thing naming the stage once a column fills the
     screen, so it stays put while the cards scroll under it. */
  .khead{position:sticky;top:0;z-index:1;background:var(--bg);padding-top:2px}
  .kcard{padding:12px 13px}
  .kcard .kn{font-size:14px}
}

/* ---------- reminders ---------- */
.remind{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:13px 16px 14px;box-shadow:var(--sh)}
.remind-h{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.remind-h svg{width:15px;height:15px;color:var(--ink-3)}
.remind-h h2{font-size:12.5px;font-weight:600}
.remind-h span{font-size:11.5px;color:var(--ink-3)}
.remind-list{display:flex;gap:9px;overflow-x:auto;padding-bottom:2px}
.rem{flex:0 0 auto;display:flex;flex-direction:column;gap:2px;align-items:flex-start;text-align:left;cursor:pointer;border:1px solid var(--line);border-left:3px solid var(--blue);background:var(--surface-2);border-radius:var(--r-sm);padding:8px 13px;min-width:158px;max-width:250px}
.rem:hover{border-color:var(--line-2);border-left-color:var(--blue);background:var(--surface)}
.rem.overdue{border-left-color:var(--red)}.rem.today{border-left-color:var(--amber)}
.rem .w{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--blue)}
.rem .w svg{width:12px;height:12px;flex:none}
.rem.overdue .w{color:var(--red)}.rem.today .w{color:var(--amber)}
.rem .n{font-weight:600;font-size:13px;letter-spacing:-.008em}
.rem .r{font-size:11.5px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:222px}

/* ---------- calendar ---------- */
.cal{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--sh);padding:14px 16px 18px}
.cal-top{display:flex;align-items:center;gap:10px;margin-bottom:14px}
.cal-title{font-size:15px;font-weight:600;letter-spacing:-.015em;min-width:158px}
.cal-nav{display:inline-flex;border:1px solid var(--line-2);border-radius:8px;overflow:hidden}
.cal-nav button{width:30px;height:30px;display:grid;place-items:center;background:var(--surface);border:0;color:var(--ink-2);cursor:pointer}
.cal-nav button+button{border-left:1px solid var(--line-2)}
.cal-nav button:hover{background:var(--surface-3);color:var(--ink)}
.cal-nav svg{width:14px;height:14px}
.cal-hint{margin-left:auto;font-size:11.5px;color:var(--ink-4)}
.cal-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr))}
.cal-dow{font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-4);padding:0 0 8px 2px}
.cal-body{border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
.cal-cell{min-height:104px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:6px 7px;cursor:pointer;overflow:hidden;background:var(--surface)}
.cal-cell:nth-child(7n){border-right:0}
.cal-cell.out{background:var(--surface-2);cursor:default}
.cal-cell:hover:not(.out){background:var(--surface-2)}
.cal-num{font-size:11.5px;font-weight:500;color:var(--ink-3);font-variant-numeric:tabular-nums;margin-bottom:4px}
.cal-cell.today .cal-num{color:#fff;background:var(--brand);border-radius:50%;width:20px;height:20px;display:grid;place-items:center;font-weight:600;margin-bottom:2px}
.cal-trips{display:flex;flex-direction:column;gap:3px}
.cal-trip{display:flex;align-items:center;gap:4px;font-size:10.5px;line-height:1.35;padding:2px 6px;border-radius:4px;white-space:nowrap;overflow:hidden;background:var(--amber-soft);color:var(--amber)}
.cal-trip.done{background:var(--green-soft);color:var(--green)}
/* The chip keeps its status colour; the leading glyph is the only thing that
   says visit or call, so a filled month still reads at a glance. */
.cal-trip .ck{display:inline-flex;flex:none}
.cal-trip .ck svg{width:10px;height:10px}
/* The district name is the only thing here that may be cut. `>:last-child`
   used to carry the ellipsis, and the last ELEMENT child is the <b> — the
   district name is a bare text node — so in a narrow cell the chip clipped
   the CLOCK and left the name to run off the edge unmarked. "1:00 PM" came
   out as "1:00 …", which is worse than no time at all: it hides the half of
   the day it happens in. The time and the glyph are fixed; the name shrinks. */
.cal-trip .ck,.cal-trip b{flex:none}
.cal-trip .cn{min-width:0;overflow:hidden;text-overflow:ellipsis}
.cal-trip b{font-weight:600;font-variant-numeric:tabular-nums}

/* Dots and the day panel are phone-only; above the breakpoint the grid's own
   chips are readable and nothing here applies. */
.cal-dots,.cal-day{display:none}

/* ---------- calendar: the phone month ----------
   Measured at 375px the cell is 43px wide and the entry chip needs 88px, so it
   rendered ~2 characters. Text does not fit and cannot be made to, so the cell
   carries DOTS and the reading moves to .cal-day below the grid. Cell height
   drops with it: 104px existed to stack three or four chips, and a row of dots
   needs a fifth of that — which is what takes the month from 520px of mostly
   empty grid to something you can see under the header. */
@media (max-width:640px){
  .cal{padding:12px 12px 14px}
  .cal-top{flex-wrap:wrap;gap:8px;margin-bottom:10px}
  .cal-title{min-width:0;flex:1 1 auto;font-size:16px}
  /* 48px wide and 104px tall, wrapped into a column, this ONE span was what
     made the header taller than three rows of the month. The instruction it
     carries is also no longer true here — a phone taps a day to open it, not
     to schedule — and the panel below states its own affordances. */
  .cal-hint{display:none}
  .cal-nav button{width:38px;height:38px}
  .cal-dow{font-size:0;letter-spacing:0;text-align:center;padding:0 0 6px}
  .cal-dow:after{content:attr(data-d);font-size:9.5px;font-weight:600;letter-spacing:.06em}

  .cal-cell{min-height:52px;padding:5px 2px 4px;display:flex;flex-direction:column;align-items:center;gap:3px}
  .cal-num{font-size:12.5px;margin-bottom:0}
  .cal-cell.today .cal-num{width:22px;height:22px}
  /* The chips stay in the DOM — same markup at every width — and are simply
     not shown at this one. */
  .cal-trips{display:none}
  .cal-dots{display:flex;align-items:center;justify-content:center;gap:3px;min-height:7px;flex-wrap:wrap}
  .cal-dot{width:6px;height:6px;border-radius:50%;background:var(--amber)}
  .cal-dot.done{background:var(--green)}
  /* A call has no drive attached to it, so it reads as a ring rather than a
     filled dot — the same visit/call distinction the chip makes with its icon,
     which is the one thing a 6px dot still has room to say. */
  .cal-dot.k-call{background:transparent;box-shadow:inset 0 0 0 1.5px var(--amber)}
  .cal-dot.k-call.done{background:transparent;box-shadow:inset 0 0 0 1.5px var(--green)}
  .cal-more{font-size:8.5px;font-style:normal;color:var(--ink-4);line-height:1;font-variant-numeric:tabular-nums}
  .cal-cell.sel{background:var(--brand-soft);box-shadow:inset 0 0 0 1px var(--brand)}

  .cal-day{display:block;margin-top:12px}
  .cal-dhead{display:flex;align-items:center;gap:10px;margin-bottom:9px}
  .cal-dhead b{display:block;font-size:14px;font-weight:600;letter-spacing:-.01em}
  .cal-dhead span{display:block;font-size:11.5px;color:var(--ink-3);margin-top:1px}
  .cal-dhead .btn{margin-left:auto;flex:none}
  .cal-dempty{font-size:12.5px;color:var(--ink-4);text-align:center;padding:14px 8px 4px}
  .cal-drows{display:flex;flex-direction:column;gap:7px}
  /* The row is the [data-trip] the chip used to be: it is the only way to open
     an entry once the cell is a dot. */
  .cal-drow{display:flex;align-items:center;gap:10px;width:100%;text-align:left;cursor:pointer;
    background:var(--surface-2);border:1px solid var(--line);border-left:3px solid var(--amber);
    border-radius:var(--r-sm);padding:10px 12px;min-height:44px;font:inherit;color:var(--ink)}
  .cal-drow.done{border-left-color:var(--green)}
  .cal-dk{display:inline-flex;flex:none;color:var(--ink-4)}
  .cal-dk svg{width:14px;height:14px}
  .cal-dmain{min-width:0;flex:1 1 auto}
  .cal-dmain b{display:block;font-size:13.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cal-dmain em{display:block;font-style:normal;font-size:11.5px;color:var(--ink-3);margin-top:1px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cal-dtime{flex:none;text-align:right;font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums}
  .cal-dtime i{display:block;font-style:normal;font-size:9.5px;font-weight:600;letter-spacing:.06em;
    text-transform:uppercase;color:var(--ink-4);margin-top:1px}
  .cal-drow.done .cal-dtime i{color:var(--green)}
}

/* ---------- overlays ---------- */
.scrim{position:fixed;inset:0;background:rgba(10,20,32,.42);backdrop-filter:blur(1px);opacity:0;pointer-events:none;transition:opacity .16s;z-index:40}
#tmscrim,#mmscrim{z-index:42}
.scrim.open{opacity:1;pointer-events:auto}
.drawer{position:fixed;top:0;right:0;height:100%;width:min(500px,96vw);background:var(--surface);border-left:1px solid var(--line);box-shadow:-12px 0 40px rgba(10,20,32,.16);transform:translateX(100%);transition:transform .2s cubic-bezier(.32,.72,0,1);z-index:41;display:flex;flex-direction:column}
.drawer.open{transform:none}
.dhead{padding:18px 22px 15px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex:none}
.dhead h2{font-size:19px;letter-spacing:-.022em}
.dhead .dty{font-size:11.5px;color:var(--ink-3);margin-top:3px}
.dclose{background:transparent;border:0;color:var(--ink-3);cursor:pointer;padding:5px;border-radius:7px;display:grid;place-items:center}
.dclose svg{width:16px;height:16px}
.dclose:hover{background:var(--surface-3);color:var(--ink)}
/* Touch: 16px glyph + 5px padding is a 26px box, and the drawer header's
   align-stretch made it 22px WIDE. It is the control every editor session
   ends on. */
@media(hover:none),(pointer:coarse){
  /* flex:none matters more than the size here — .dhead is a flex row and the
     default flex-shrink:1 let a long record title squeeze the button down to
     22px wide however big it was told to be. */
  .dclose{flex:none;width:40px;height:40px;padding:0}
}
.dbody{padding:18px 22px 24px;overflow-y:auto;flex:1}
.dfoot{padding:13px 22px;border-top:1px solid var(--line);display:flex;gap:9px;align-items:center;flex:none;background:var(--surface-2)}
.seclab{font-size:10.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-4);margin:22px 0 11px;display:flex;align-items:center;gap:9px}
.seclab:first-child{margin-top:0}
.seclab::after{content:"";flex:1;height:1px;background:var(--line)}
.seclab.bare::after{display:none}
.seclab.bare{flex-wrap:wrap;gap:8px}
.seclab.bare .btn.sm{flex:none}
.seclab .rule{flex:1;height:1px;background:var(--line)}
.fld{margin-bottom:13px}
.fld label{display:block;font-size:11.5px;font-weight:500;color:var(--ink-2);margin-bottom:5px}
.fld input,.fld select,.fld textarea,
.iline input,.costrow input{width:100%;font:inherit;font-size:13px;background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-sm);padding:8px 10px;color:var(--ink);transition:border-color .12s,box-shadow .12s}
/* Every control on a row has to be the same height, and left alone they are
   not: a <select> computed 36px, a text input 37 and a date input 39, because
   each type sizes to its own content. Status sitting 3px shorter than the Date
   beside it is small and it is the kind of small that makes a form look
   unfinished. Fixed rather than min-height, or the date input keeps its 39. */
.fld input:not([type=checkbox]):not([type=radio]),.fld select{height:38px}
/* The little calendar/clock glyph is the only thing that opens the native
   picker, and at its default size it is a ~16px target with no hover state
   and no pointer cursor — it does not look like a control. Padding enlarges
   the hit box without moving the field, and the negative margin takes back
   the space so the glyph stays where it was. color-scheme (declared at the
   top of this file) is what makes it light on the dark theme; before that it
   was a dark glyph on a dark field. */
.fld input[type=date]::-webkit-calendar-picker-indicator,
.fld input[type=time]::-webkit-calendar-picker-indicator,
.fld input[type=month]::-webkit-calendar-picker-indicator{
  cursor:pointer;opacity:.5;padding:6px;margin:-6px -4px -6px 0;border-radius:6px}
.fld input[type=date]::-webkit-calendar-picker-indicator:hover,
.fld input[type=time]::-webkit-calendar-picker-indicator:hover,
.fld input[type=month]::-webkit-calendar-picker-indicator:hover{opacity:1;background:var(--surface-3)}
/* A disabled Until must not look pickable. */
.fld input:disabled{opacity:.5;cursor:not-allowed}
.fld input:disabled::-webkit-calendar-picker-indicator{cursor:not-allowed;opacity:.25}
.fld textarea{min-height:70px;resize:vertical;line-height:1.55}

/* ================= note fields ================= */
/* A free-text field that can be dictated into. The mic is absolutely
   positioned inside the box, so the field itself reserves a right gutter and
   nothing is ever typed underneath it. */
.nf{position:relative;display:block}
/* The gutter clears the mic. On a textarea it also clears the scrollbar the
   field grows into: the mic is pushed inboard of the 10px track so the two
   never sit on top of each other. */
.nf>input{padding-right:38px}
.nf>textarea{padding-right:44px}

/* SIZING. Every note field used to be one height — .fld textarea's flat 70px,
   about three lines — whether it held a street address or a full call history,
   and it neither grew with the text nor capped: dragging the grip could make it
   taller than the drawer that contains it. Now each field declares the range it
   is allowed to occupy, JS grows it to fit inside that range, and past the top
   it SCROLLS instead of pushing the Save button off the bottom of the panel.
   resize:vertical is kept — the grip still wins, and once dragged the automatic
   sizing gets out of the way for good (see nfGrow). */
.nf>textarea{display:block;min-height:0;line-height:1.6;overflow-y:auto;resize:vertical;
  transition:border-color .12s,box-shadow .12s}
.nf-sm>textarea{min-height:62px;max-height:190px}
.nf-md>textarea{min-height:96px;max-height:260px}
.nf-lg>textarea{min-height:132px;max-height:340px}
/* A one-line field has no resize grip and no scrollbar, so the mic sits
   centred and closer to the edge. */
.nf-one>textarea,.nf-one>input{resize:none}
.nf-one .micbtn{right:5px}

.micbtn{position:absolute;top:5px;right:9px;width:28px;height:28px;display:grid;place-items:center;
  padding:0;border:0;border-radius:8px;background:transparent;color:var(--ink-5);cursor:pointer;
  transition:background .12s,color .12s}
/* Top-right, deliberately clear of the bottom-right resize grip — the same
   placement vsi-web's TextAreaField settled on. On a single-line field there is
   no grip, so it centres instead. */
.nf-one .micbtn{top:50%;transform:translateY(-50%)}
.micbtn svg{width:15px;height:15px}
.micbtn:hover{background:var(--surface-3);color:var(--ink-2)}
.micbtn:focus-visible{outline:none;box-shadow:var(--ring)}
.micbtn.on{background:var(--red);color:#fff;animation:micpulse 1.4s ease-in-out infinite}
.micbtn.on:hover{background:var(--red);color:#fff}
.micbtn.bad{color:var(--red)}
@keyframes micpulse{0%,100%{opacity:1}50%{opacity:.55}}
@media(prefers-reduced-motion:reduce){.micbtn.on{animation:none}}
/* A live mic is worth showing on the field itself, not just the button — while
   dictating, the field is the thing being written to. */
.nf:has(.micbtn.on)>textarea,.nf:has(.micbtn.on)>input{border-color:var(--red);box-shadow:0 0 0 3px var(--red-soft)}
/* Touch: 28px is under thumb size, and these are used one-handed in a parking
   lot more than at a desk. */
@media(hover:none),(pointer:coarse){
  .micbtn{width:36px;height:36px}
  .micbtn svg{width:17px;height:17px}
  .nf>input{padding-right:46px}
  .nf>textarea{padding-right:52px}
}
.fld input:focus,.fld select:focus,.fld textarea:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}
.fld input::placeholder,.fld textarea::placeholder{color:var(--ink-3)}
/* Two-up field row — 30 of them across the editors, so this rule alone
   decides whether the back office is usable on a phone. A hard `1fr 1fr`
   made 147px columns in the 360px drawer, and a <select> cannot scroll its
   own label: "Shelby County (ALSHEL01)" needs 240px and rendered as "Shelby
   County…", i.e. you could not read which county the account was scoped to
   while editing exactly that. Email and Role were clipped the same way.
   auto-fit keys off the CONTAINER, not the viewport, so the 445px desktop
   drawer keeps its two columns (2x180+11 = 371) and the 305px phone one
   drops to a single column. 180px is the floor for a labelled field —
   anything under it clips a real value somewhere in these forms. */
.fld2{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:11px}
.chk{display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--ink-3);margin-top:6px;cursor:pointer}
/* Entry-type picker. Radios, not buttons: the checked state IS the value, so
   nothing has to be read back out of a class, and it is keyboard-navigable and
   labelled for free. */
.kindpick{display:grid;grid-template-columns:1fr 1fr;gap:8px}
/* `.kindpick .kopt`, not `.kopt`. This is a <label> inside a .fld, so the
   plain class (0,1,0) LOST to `.fld label` (0,1,1) — the picker was rendering
   display:block, which is why its icons sat on a text baseline four pixels
   above the words instead of centred beside them, why the labels came out at
   11.5px instead of 13.5px, and why the pair sat high in its own 40px box.
   Everything below was being declared and thrown away. */
.kindpick .kopt{position:relative;display:flex;align-items:center;justify-content:center;gap:7px;
  height:40px;margin:0;cursor:pointer;
  border:1px solid var(--line-2);border-radius:9px;background:var(--surface);
  font-size:13.5px;font-weight:500;color:var(--ink-2)}
.kindpick .kopt svg{flex:none}
.kopt svg{width:16px;height:16px;opacity:.7}
.kopt input{position:absolute;opacity:0;width:0;height:0}
.kopt:hover{border-color:var(--ink-4)}
.kopt:focus-within{box-shadow:var(--ring);border-color:var(--brand)}
.kopt.on{border-color:var(--brand);background:var(--brand-soft);color:var(--brand-ink);font-weight:600}
.kopt.on svg{opacity:1}
.chk input{width:auto;accent-color:var(--brand)}
.chk.gap{margin-bottom:15px}
.dmeta{font-size:11.5px;color:var(--ink-3);line-height:1.7;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;margin-top:4px}
.dmeta svg{width:13px;height:13px;vertical-align:-2px;margin-right:5px;color:var(--ink-4)}
/* Touch: the checkbox itself is 13x13, but it is wrapped in a <label>, so the
   real target is the whole 305px line — only its HEIGHT was short at 19px.
   "open map" had no such wrapper: 53x14, and it is the one control here
   someone actually uses standing next to the vehicle. Padding enlarges an
   inline element's hit box without moving it; the negative margin cancels the
   layout shift, and the neighbouring lines are plain text, so the few pixels
   of overlap cover nothing clickable. */
@media(hover:none),(pointer:coarse){
  .chk{min-height:26px;margin-top:8px}
  .chk input{width:16px;height:16px}
  .dmeta a{display:inline-block;padding:6px 4px;margin:-6px -4px}
}
.logrow{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;margin-bottom:8px;cursor:pointer}
.logrow:hover{border-color:var(--line-2);background:var(--surface)}
.logdate{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ink-3)}
.logdate b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
.logkind{display:inline-flex;align-items:center;gap:4px;font-size:9.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.06em;padding:1px 6px;border-radius:4px;background:var(--surface-3);color:var(--ink-3)}
.logkind svg{width:10px;height:10px}
.logrow.k-call .logkind{background:var(--violet-soft);color:var(--violet)}
.logst{font-size:9.5px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;padding:1px 6px;border-radius:4px}
.logst.planned{background:var(--amber-soft);color:var(--amber)}
.logst.done{background:var(--green-soft);color:var(--green)}
.logreason{font-size:13px;font-weight:500;margin-top:5px}
.logout{font-size:12.5px;color:var(--green);margin-top:2px}
.lognotes{font-size:12px;color:var(--ink-3);margin-top:3px;line-height:1.5}
.mini{font-size:12.5px;color:var(--ink-4);padding:2px}

.modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-48%) scale(.99);width:min(500px,94vw);max-height:88vh;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:var(--sh-2);z-index:43;opacity:0;pointer-events:none;transition:opacity .16s,transform .16s}
.modal.open{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
/* A modal over a locked body: same chaining, and a long member list is
   exactly where a thumb over-drags. */
.mmbody{padding:18px 22px;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain}
.mmlist{display:flex;flex-direction:column;gap:7px}
.mmrow{display:flex;align-items:center;gap:10px;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:9px 12px}
.mmrow .em{font-size:12.5px;font-weight:500;flex:1;word-break:break-all}
.mmrow .nm2{font-size:11px;color:var(--ink-3);font-weight:400}
.note{font-size:11.5px;color:var(--ink-2);line-height:1.6;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:11px 13px;margin-top:14px}

.toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(14px);background:var(--ink);color:var(--bg);padding:10px 16px;border-radius:9px;font-size:13px;font-weight:500;box-shadow:var(--sh-2);opacity:0;pointer-events:none;transition:.2s;z-index:60}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* ---------- gate ---------- */
.gate{position:fixed;inset:0;display:grid;place-items:center;background:var(--bg);z-index:100;padding:24px}
.gatecard{width:100%;max-width:396px;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:var(--sh-2);overflow:hidden}
.gatetop{background:linear-gradient(155deg,#173D63,#0A1D30);padding:26px 26px 24px;display:flex;align-items:center;gap:13px}
.gatetop .mark{width:40px;height:40px;border-radius:10px}
.gatetop h1{color:#fff;font-size:19px;letter-spacing:-.02em}
.gatetop p{color:#93AABF;font-size:12px;margin:3px 0 0}
.gatebody{padding:22px 26px 26px}
.gerr{background:var(--red-soft);color:var(--red);border:1px solid var(--red-soft);border-radius:var(--r-sm);padding:9px 12px;font-size:12.5px;margin-top:13px;font-weight:500}
.gmsg{font-size:12px;color:var(--ink-3);margin-top:15px;line-height:1.6}
.gdenied{color:var(--red);font-weight:600;font-size:14px}
.gate .btn{width:100%;justify-content:center;height:38px}

.railnav{display:none}
@media(max-width:900px){.railnav{display:grid;width:38px;height:38px;flex:none;border-radius:9px}}
/* Same reasoning as .railnav, one breakpoint tighter: below 700px the
   conversation list is a separate screen from the room, so the room header's
   bell is not reachable from it at all. Above that both panes are on screen
   together and a second bell is only clutter. */
.railbell{display:none}
@media(max-width:700px){
  .railbell{display:grid;width:38px;height:38px;flex:none;border-radius:9px;justify-self:end}
  .railbell.on{color:var(--brand);background:var(--brand-soft)}
}
.navtoggle .ntw,.navtoggle .ntm{place-items:center}
.navtoggle .ntw{display:grid}
.navtoggle .ntm{display:none}

@media(min-width:901px){
  body.navhidden .app{grid-template-columns:0 minmax(0,1fr)}
  body.navhidden .side{overflow:hidden;padding-left:0;padding-right:0;border-right-width:0}
}

/* ================= phone + tablet shell =================
   WAS a horizontal scroll strip in a first grid row. Measured on a 375px
   phone, `.sidefoot{margin-left:auto}` inside that `overflow-x:auto` row put
   Access, the theme toggle and SIGN OUT ~1530px past the right edge, behind a
   scroll gesture with no affordance pointing at it. Fourteen destinations do
   not fit on a phone row, and the ones that fell off were the account
   controls.

   The rail is now an off-canvas drawer: the same vertical markup as the
   desktop rail, positioned out of flow, brought in over a scrim. Nothing about
   the sidebar's own layout changes, which is why the nav, the badges and the
   footer all keep working untouched.

   height:100dvh (not 100vh) so iOS Safari's collapsing toolbars cannot push
   the bottom of the shell under the viewport. .scrollarea stays the only
   scroller at every width — that is what keeps the Leads table's sticky thead
   working, and it is why the drawer is `position:fixed` rather than a grid
   row that would reintroduce a document scroll. */
@media(max-width:900px){
  .app{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);height:100vh;height:100dvh;min-height:0}

  .side{
    position:fixed;top:0;bottom:0;left:0;z-index:46;
    width:min(284px,82vw);
    /* The rail is its own scroller here: 14 destinations plus the footer
       exceed a landscape phone's height. */
    overflow-y:auto;overscroll-behavior:contain;
    padding-top:calc(18px + env(safe-area-inset-top,0px));
    padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));
    border-right:1px solid var(--navy-2);
    transform:translateX(-100%);
    transition:transform .22s cubic-bezier(.4,0,.2,1);
    will-change:transform;
  }
  body.navopen .side{transform:none;box-shadow:0 0 40px rgba(0,0,0,.4)}
  /* Measured: 14 destinations at touch size + the footer came to 906px against
     an 812px phone, putting Sign out under the fold. The section label is the
     one thing here carrying no destination, and the rail is the only child
     group that needed 20px of separation on a desktop. Both go, which lands a
     modern phone (852px+) on a drawer that needs no scroll at all; a smaller
     one scrolls the last row into view rather than hiding it off-canvas. */
  .side{gap:12px}
  .navlab{display:none}
  /* Off-canvas means off the tab order too — otherwise Tab walks into a panel
     nobody can see. */
  body:not(.navopen) .side{visibility:hidden}
  body.navopen .side{visibility:visible}

  .navscrim{z-index:45}

  /* The panel glyph means "collapse this column", which is not what the
     control does here. */
  .navtoggle .ntw{display:none}
  .navtoggle .ntm{display:grid}
  .navtoggle{width:40px;height:40px;border-radius:10px}

  /* ONE row, not three. The old rule let .top wrap, and title + actions +
     a full-width search stacked into ~132px of a 812px phone before any
     content — 16% of the screen, permanently. Search gets its own compact
     row under the title; the actions stay on the title line. */
  .top{height:auto;min-height:56px;padding:10px 14px;gap:10px;flex-wrap:wrap;
       padding-top:calc(10px + env(safe-area-inset-top,0px))}
  .ttl{flex:1 1 0;min-width:0;overflow:hidden}
  .ttl h1{font-size:16px;flex:none;white-space:nowrap}
  .ttl .count{min-width:0;overflow:hidden;text-overflow:ellipsis}
  .topacts{margin-left:auto;flex:none;gap:6px}
  .search{margin-left:0;order:3;max-width:none;flex-basis:100%;height:38px}
  .top .btn{height:38px}
  .scrollarea{--pt:14px;padding:var(--pt) 14px calc(48px + env(safe-area-inset-bottom,0px))}
}

/* "Export" and "New entry" are desktop-sized words sitting next to each other
   on a 375px title row. The download icon carries Export on its own; the
   primary action keeps a word because a bare + is not a label. */
@media(max-width:560px){
  .top .btn.ghost .btl{display:none}
  .top .btn.ghost{padding:0 10px}
  .top .btn.primary .btl{display:none}
  .top .btn.primary .bts{display:inline}
}

@media(prefers-reduced-motion:reduce){*{transition:none!important}}

/* ================= chat ================= */
/* THE LAYOUT FIX. #viewbody has no rule of its own, so it is a content-sized
   flex item and the height chain from .scrollarea dies there — measured, the
   message log rendered 1955px tall with the composer 1300px below the fold.
   Both rules are scoped to the chat view (body.chatview, set in renderView):
   applying them globally would restyle the Leads table and its sticky thead. */
body.chatview .scrollarea{overflow:hidden;padding-bottom:var(--pt)}
body.chatview #viewbody{flex:1;min-height:0;display:flex;flex-direction:column}
/* Export dumps districts and "Schedule visit" opens a trip — neither does
   anything for a conversation — and the search box beside them filters the
   Leads table, not the rail. Chat brings its own search (.railsearch), so the
   header keeps only the title and the conversation count. */
body.chatview .topacts,body.chatview .search{display:none}

.chatroot{flex:1;min-height:0;display:grid;grid-template-columns:264px minmax(0,1fr);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--sh);overflow:hidden}

.chatrail{display:flex;flex-direction:column;min-height:0;border-right:1px solid var(--line);background:var(--surface-2)}
.railtop{display:flex;flex-direction:column;gap:6px;padding:12px;border-bottom:1px solid var(--line)}
.railtop .btn{justify-content:flex-start;width:100%}
/* Chat's own room filter. The global #q in .top cannot serve chat: .top is
   hidden below 900px, so room search was silently desktop-only, and its
   placeholder ("Search district or supervisor") described the Leads table. */
.railsearch{display:flex;align-items:center;gap:8px;height:32px;padding:0 10px;margin-top:2px;
  background:var(--surface-3);border:1px solid transparent;border-radius:8px}
.railsearch:focus-within{background:var(--surface);border-color:var(--brand);box-shadow:var(--ring)}
.railsearch svg{width:14px;height:14px;color:var(--ink-3);flex:none}
.railsearch input{flex:1;min-width:0;border:0;background:transparent;color:var(--ink);font:inherit;font-size:13px;outline:none;-webkit-appearance:none}
.railsearch input::-webkit-search-decoration,.railsearch input::-webkit-search-cancel-button{-webkit-appearance:none}
.railsearch input::placeholder{color:var(--ink-3)}
.raillist{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;
  overscroll-behavior:contain;padding:8px 8px 16px}
.raillab{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);padding:14px 8px 6px}
.railempty{margin:0;padding:2px 8px 6px;font-size:12.5px;color:var(--ink-3)}
/* How many are open to join, on the heading — the number is the reason to
   look at the section at all. */
.railn{margin-left:6px;font-size:10px;font-weight:700;letter-spacing:0;
  color:var(--ink-3);background:var(--surface-3);border-radius:999px;padding:1px 6px}
/* A channel you are not in reads one step back: the name is not bold, and the
   row carries the word that says what clicking it offers. */
.roomrow.open .rmid b{font-weight:500;color:var(--ink-2)}
.roomrow.open .ravatar{opacity:.72}
.rjoin{flex:none;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--brand-ink);background:var(--brand-soft);border-radius:999px;padding:2px 7px}
.roomrow.open:hover .rjoin{background:var(--brand);color:#fff}
/* The unjoined channel's log. Not an error and not an empty room — an
   invitation, with the count that says whether anyone is in there. */
.chatempty.joinpitch{display:flex;flex-direction:column;align-items:center;gap:9px;max-width:44ch;
  margin-left:auto;margin-right:auto;text-align:center}
.chatempty.joinpitch b{font-size:14.5px;font-weight:600;color:var(--ink)}
.chatempty.joinpitch span{font-size:13px;color:var(--ink-3);line-height:1.5}
.roomrow{display:flex;align-items:center;gap:9px;width:100%;padding:8px;border:0;border-radius:var(--r-sm);
  background:none;cursor:pointer;text-align:left;color:var(--ink);font:inherit}
.roomrow:hover{background:var(--surface-3)}
.roomrow.on{background:var(--brand-soft)}
.ravatar{flex:none;width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
  font-size:11.5px;font-weight:700;color:#fff;background:hsl(var(--h,210) 42% 42%)}
.rmid{min-width:0;flex:1;display:flex;flex-direction:column;gap:1px}
/* The DM picker. Deliberately the same shape as a .roomrow — picking someone
   to message and the conversation you end up in should not look like two
   different kinds of thing — but it is its own class: .roomrow carries rail
   state (.on, .un, the unread dot) that means nothing in a chooser. */
.picklist{display:flex;flex-direction:column;gap:4px;max-height:46vh;overflow-y:auto;margin:0 -6px;padding:0 6px}
.pickrow{display:flex;align-items:center;gap:10px;width:100%;padding:8px;border:0;border-radius:var(--r-sm);
  background:none;cursor:pointer;text-align:left;font:inherit;color:inherit}
.pickrow:hover{background:var(--surface-3)}
.pickrow:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.pickrow .rmid b{font-size:13px;font-weight:600}
.pickrow .rmid em{font-size:11.5px;color:var(--ink-3);font-style:normal;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Says "this conversation already exists" — clicking opens it rather than
   starting a second one, which the derived DM id makes impossible anyway. */
.pickon{flex:none;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-3);background:var(--surface-3);border-radius:5px;padding:2px 7px}

/* Channel settings. .seclab already divides the drawers; danger red-tints the
   last one so the two destructive rows are not the same weight as a rename. */
.seclab.danger{color:var(--red)}
.csn{margin-left:6px;font-size:11px;font-weight:600;color:var(--ink-3);
  background:var(--surface-3);border-radius:999px;padding:1px 7px}
.csrow{display:flex;align-items:center;gap:12px;padding:10px 0}
.csrow+.csrow{border-top:1px solid var(--line)}
.csrow>div{min-width:0;flex:1}
.csrow b{display:block;font-size:12.5px;font-weight:600;color:var(--ink)}
/* The consequence, not a restatement of the button. */
.csrow em{display:block;font-style:normal;font-size:11.5px;color:var(--ink-3);
  line-height:1.45;margin-top:2px}
.csrow .btn{flex:none}
/* The topic replaces the "open" chip, so it has to be able to run long
   without pushing the actions off the header. */
.chip.topic{color:var(--ink-2);background:var(--surface-3);
  max-width:min(52ch,46vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Sits above the guest toggles, so the absence of your own name reads as
   deliberate rather than as a bug. */
.nohost{margin:0 0 8px}
.rmid b{font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rmid em{font-style:normal;font-size:11.5px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.roomrow.un .rmid b{font-weight:700}
.rdot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--brand)}

.chatmain{display:flex;flex-direction:column;min-height:0;min-width:0;position:relative}
.chathead{display:grid;grid-template-columns:1fr auto;gap:4px 12px;align-items:center;
  padding:12px 16px;border-bottom:1px solid var(--line);background:var(--surface)}
.chattitle{display:flex;align-items:center;gap:10px;min-width:0;flex-wrap:wrap}
.chattitle h2{margin:0;font-size:16px;font-weight:700;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chattitle .hash{color:var(--ink-4);margin-right:1px}
.chathead .chip{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;
  padding:3px 8px;border-radius:999px;border:1px solid;white-space:nowrap}
.chathead .chip svg{width:13px;height:13px}
.chip.lock{color:var(--teal);background:var(--teal-soft);border-color:var(--teal)}
.chip.open{color:var(--ink-3);background:var(--surface-3);border-color:var(--line-2)}
.chatacts{display:flex;align-items:center;gap:6px;justify-self:end}
.chatacts .iconbtn.on{color:var(--brand);background:var(--brand-soft)}
/* Three states, because "never asked" and "asked and it failed" are not the
   same thing and used to paint identically. */
[data-chat-push].busy{opacity:.55}
[data-chat-push].bad{color:var(--red)}
.railbell.bad{color:var(--red)}
.pusherr{margin:0;padding:6px 16px 0;font-size:11.5px;color:var(--red);line-height:1.45}
.chatmeta{grid-column:1/-1;font-size:11.5px;color:var(--ink-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chatback{display:none}

/* A short conversation belongs at the BOTTOM of the log, next to the composer,
   not stranded at the top of 600px of white. The auto-margin spacer is used
   instead of justify-content:flex-end because flex-end makes overflowing
   content unreachable at the top in several engines; a first flex item with
   margin-top:auto collapses to nothing as soon as the log overflows. */
/* overscroll-behavior, and overflow-x SPELLED OUT. Every other view scrolls
   in .scrollarea, which contains its overscroll; chat turns .scrollarea off
   and scrolls here instead, and the guard did not come with it. On a phone
   body.chatview is position:fixed, so a drag that runs past the end of this
   log has nowhere to chain to and Safari rubber-bands the whole fixed shell —
   header, composer and all slide under the finger. `contain` keeps the drag
   in the log.
   overflow-x is the other half: declaring only overflow-y computes the other
   axis to `auto`, not `visible`, so the log was a horizontal scroller too and
   anything a pixel too wide let the screen be dragged sideways as well. */
.chatlog{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;
  overscroll-behavior:contain;padding:14px 16px 6px;
  display:flex;flex-direction:column;gap:2px;scroll-behavior:smooth}
.chatlog::before{content:"";margin-top:auto;flex:none}
/* Cap the measure. Unbounded, a message on a 2560px display runs past 200
   characters per line, which is roughly twice a readable line. */
.chatlog>.msg,.chatlog>.daydiv,.chatlog>.loadmore{width:100%;max-width:860px}
/* .mread is NOT in that list. It carries a 42px left margin to sit under the
   message body, and width:100% on top of that made every receipt row 42px
   wider than the log — enough to make the log a horizontal scroller, which
   `overflow-x:hidden` then turned into content clipped off the right edge
   with no way to reach it. The row is a few small items; it sizes itself. */
.mread{max-width:calc(100% - 42px)}
@media(prefers-reduced-motion:reduce){.chatlog{scroll-behavior:auto}}
.chatempty{margin:auto;padding:40px 20px;text-align:center;color:var(--ink-3);font-size:13px}
/* In the log, the auto-margin spacer above competes with .chatempty's own two
   auto margins and the free space splits three ways, parking the message
   two-thirds down. Dropping its top margin makes it one share each. */
.chatlog>.chatempty,.chatlog>.loading{margin:0 auto auto}
.loadmore{display:grid;place-items:center;padding:4px 0 12px}
.daydiv{display:flex;align-items:center;gap:10px;margin:14px 0 8px;color:var(--ink-3);font-size:11px;font-weight:600}
.daydiv::before,.daydiv::after{content:"";flex:1;height:1px;background:var(--line)}

.msg{display:grid;grid-template-columns:32px minmax(0,1fr) auto;gap:0 10px;padding:3px 0;border-radius:var(--r-sm)}
.msg:hover{background:var(--surface-2)}
.msg.grp{padding-top:0}
.mav{width:32px;height:32px;border-radius:10px;display:grid;place-items:center;
  font-size:11.5px;font-weight:700;color:#fff;background:hsl(var(--h,210) 42% 42%)}
.mav.sp{background:none}
.mbody{min-width:0}
.mhead{display:flex;align-items:baseline;gap:8px;margin-bottom:1px}
.mhead b{font-size:13px;font-weight:700}
.mhead time{font-size:11px;color:var(--ink-3)}
.mtext{margin:0;font-size:14px;line-height:1.5;color:var(--ink);overflow-wrap:anywhere;white-space:pre-wrap}
.mtext.del{color:var(--ink-3);font-style:italic}
/* Links in a message. underline-offset keeps the rule clear of the descenders
   in a URL, which are dense — g, p, y and the slashes all reach it. */
.mlink{color:var(--brand-ink);text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:color-mix(in srgb,var(--brand-ink) 45%,transparent);
  overflow-wrap:anywhere}
.mlink:hover{text-decoration-color:currentColor}

/* Read receipts. Aligned to the message column, not the avatar gutter, so the
   faces sit under the text they refer to. -2px pulls them up against that
   message rather than letting them float between two. */
/* Indented past the 32px avatar and its 10px gap so the faces line up with
   the message text. NOT right-aligned for your own messages — this log is
   left-aligned by sender name, Slack-style, not a two-sided bubble thread,
   so a right-aligned strip would be the only thing on that edge. */
.mread{display:flex;align-items:center;gap:3px;margin:-2px 0 4px 42px}
.rav{width:16px;height:16px;border-radius:50%;display:grid;place-items:center;
  font-size:8px;font-weight:700;letter-spacing:-.02em;
  color:#fff;background:hsl(var(--h,210) 42% 46%);
  box-shadow:0 0 0 1.5px var(--surface)}
.rav+.rav{margin-left:-5px}
.rav.more{background:var(--surface-3);color:var(--ink-3);font-size:8.5px}
/* Words, not just faces. On your own last message the row is always drawn, so
   that "nobody has read this" reads as a state instead of as a chat that does
   not do receipts at all. The hover title names both sides. */
.mread .rstate{font-size:10px;font-weight:600;letter-spacing:.02em;color:var(--ink-3);margin-left:4px}
.mread .rstate.none{color:var(--ink-4);font-weight:500;margin-left:0}
/* The faces are aria-hidden decoration; this is what a screen reader gets. */
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
.macts{opacity:0;display:flex;align-items:center;gap:8px;padding-right:4px}
.msg:hover .macts,.msg:focus-within .macts{opacity:1}
.lnk{display:inline-flex;align-items:center;gap:4px;border:0;background:none;padding:0;cursor:pointer;
  font:inherit;font-size:11.5px;color:var(--ink-3)}
.lnk svg{width:14px;height:14px}
.lnk:hover{color:var(--ink)}
.lnk:hover span{text-decoration:underline}
.lnk.danger{color:var(--red)}

/* The pill has to float just above the composer. It used to be absolutely
   positioned against .chatmain at a hard-coded bottom:96px, which was already
   1px INSIDE the composer on desktop (57px form + 42px footer) and buried
   under it on a phone, where the footer wraps to three lines. As a zero-height
   flex item between the log and the form it lands in the right place at every
   width with no magic number. */
.jump{position:relative;height:0;overflow:visible;display:grid;place-items:center;pointer-events:none;z-index:2}
.jump button{position:absolute;bottom:8px;pointer-events:auto;display:inline-flex;align-items:center;gap:6px;
  border:1px solid var(--line-2);background:var(--surface);color:var(--ink);
  border-radius:999px;padding:5px 12px;font:inherit;font-size:12px;font-weight:600;
  box-shadow:var(--sh-2);cursor:pointer}
.jump svg{width:14px;height:14px;transform:rotate(90deg)}

.chatform{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;gap:8px;align-items:end;
  padding:10px 16px 8px;border-top:1px solid var(--line);background:var(--surface)}
/* It scrolls the moment a message runs to a second line, and it is the one
   scroller a thumb rests on, so it chains like the log did. */
.chatform textarea{width:100%;resize:none;max-height:132px;min-height:38px;
  overscroll-behavior:contain;
  border:1px solid var(--line-2);border-radius:var(--r-sm);background:var(--surface);
  color:var(--ink);font:inherit;font-size:14px;line-height:1.45;padding:9px 11px}
.chatform textarea:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}
.chatform textarea:disabled{background:var(--surface-3);color:var(--ink-3)}
.chatform textarea::placeholder{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chatform .btn{height:38px;padding:0 14px}
.chatform .btn svg{width:16px;height:16px}
/* The composer's mic is a grid cell, not an overlay — it lines up with Send
   and with the bottom of a textarea that grows to 132px. */
.chatmic,.attbtn{position:static;width:38px;height:38px;border:1px solid var(--line-2);border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink-3)}
.chatmic svg,.attbtn svg{width:16px;height:16px}
.chatmic:hover,.attbtn:hover{background:var(--surface-2);border-color:var(--ink-4);color:var(--ink-2)}
.chatmic.on{border-color:var(--red)}
.chatmic:disabled,.attbtn:disabled{background:var(--surface-3);color:var(--ink-5);cursor:not-allowed}
.chatmic:disabled:hover,.attbtn:disabled:hover{background:var(--surface-3);color:var(--ink-5);border-color:var(--line-2)}
/* ---------- chat attachments ---------- */
/* Row 1 of the composer grid; collapses to nothing while [hidden]. */
.attrow{grid-column:1/-1;display:flex;flex-direction:column;gap:6px;min-width:0}
.atttile{display:flex;align-items:center;gap:10px;min-height:44px;min-width:0;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:6px 8px}
.attthumb{width:34px;height:34px;flex:none;border-radius:6px;object-fit:cover;background:var(--surface-3)}
.attdoc{display:grid;place-items:center;color:var(--ink-3)}
.attdoc svg{width:17px;height:17px}
.attmeta{min-width:0;flex:1 1 auto;display:flex;flex-direction:column}
.attmeta b{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attmeta em{font-style:normal;font-size:11px;color:var(--ink-4)}
/* Same idiom as .kpi .track — no new colour tokens, which would otherwise have
   to be added to all three theme blocks or be a light-only value on a dark path. */
.attbar{flex:none;width:64px;height:4px;border-radius:3px;background:var(--surface-3);overflow:hidden}
.attbar i{display:block;height:100%;width:0;border-radius:3px;background:var(--brand);transition:width .15s}
.attx{flex:none}
.atterr{margin:0;font-size:11.5px;color:var(--red);line-height:1.45}
.atterr .lnk2{margin-left:6px}

/* There is no `img` rule anywhere in this stylesheet, so an unsized <img> lays
   out at its intrinsic width. The box is reserved from the stored w/h via --ar
   BEFORE the bytes arrive: paintLog measures scrollHeight before it mutates and
   restores by delta, so an image that gained height after decode would shove
   the reader up the log or break the bottom pin.
   260px is the measured .mbody budget at 375px; min() covers 320px devices. */
.matt{margin:6px 0 0;max-width:min(260px,100%);border-radius:10px;overflow:hidden;
  background:var(--surface-3);aspect-ratio:var(--ar,4/3);cursor:pointer}
.matt img{display:block;width:100%;height:100%;object-fit:cover}
.matt.attgone{opacity:.4}
/* Reuses the Uploads view's file vocabulary rather than inventing a second one. */
.mfile{display:inline-flex;align-items:center;gap:9px;margin:6px 0 0;max-width:100%;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);
  padding:8px 11px;min-height:44px;cursor:pointer;font:inherit;color:var(--ink);text-align:left}
.mfile:hover{border-color:var(--line-2);background:var(--surface-3)}
.mfile svg{width:16px;height:16px;flex:none;color:var(--ink-4)}

/* Full-bleed, not the centred .modal transform, and a literal dark backdrop —
   var(--ink) inverts to near-white in dark mode. */
.modal.lightbox{position:fixed;inset:0;top:0;left:0;transform:none;width:100%;max-height:none;
  border:0;border-radius:0;background:rgba(8,12,18,.96);display:grid;place-items:center;
  padding:calc(16px + env(safe-area-inset-top,0px)) 16px calc(16px + env(safe-area-inset-bottom,0px))}
.modal.lightbox.open{transform:none}
.modal.lightbox .dclose{position:absolute;top:calc(10px + env(safe-area-inset-top,0px));right:12px;
  z-index:1;color:#E6EDF4;background:rgba(255,255,255,.10);width:40px;height:40px;border-radius:10px}
.lbbody{max-width:100%;max-height:100%;display:grid;place-items:center}
.lbbody img{max-width:100%;max-height:88vh;object-fit:contain;border-radius:8px}

.chatfoot{margin:0;padding:0 16px 10px;font-size:11px;line-height:1.45;color:var(--ink-3);background:var(--surface)}
.chatfoot.err{color:var(--red)}
/* Both wordings are always in the DOM and CSS picks one, so a rotation or a
   window drag re-picks without any JS having to re-run paintComposer. */
.chatfoot .fshort{display:none}

/* ---- narrow shell (≤900px): the sidebar is a horizontal strip and .app
   stops being height-bounded, so chat has to re-establish its own height here
   REGARDLESS of whether it is showing one pane or two. This is a DELIBERATE
   exception to the single-page-scroll rule the Leads work established (see
   .app above) — a composer pinned under a scrolling log is the whole
   interaction, and without it the log grows unbounded and pushes the composer
   thousands of px down the page. 100dvh (not 100vh) so iOS Safari's toolbars
   don't hide the composer; --vvh is set from visualViewport so the on-screen
   keyboard shrinks the pane instead of covering it. */
@media(max-width:900px){
  /* Lock the DOCUMENT, not just the pane. Setting the shell to the visual
     viewport's height fixed its SIZE but not its POSITION: iOS scrolls the
     layout viewport to reveal a focused input, and with a one-viewport-tall
     shell that dragged the whole thing upward — composer over the status bar,
     an empty void where the log had been, keyboard underneath. Taking the body
     out of flow removes the scroll range that motion needs.
     position:fixed rather than overflow:hidden because iOS honours the former
     and routinely ignores the latter for its own reveal-scroll. */
  html.chatview{overscroll-behavior:none}
  body.chatview{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;overflow:hidden;overscroll-behavior:none}
  body.chatview .app{height:var(--vvh,100dvh);min-height:0;overflow:hidden}
  body.chatview .scrollarea{padding:0}
  body.chatview .top{display:none}
  /* Chat is the one view where every vertical pixel is the product. The brand
     block wraps to three lines in the strip and costs ~30px of an 812px phone
     (21% of a 390px landscape phone went to chrome); the room name in the chat
     header already says where you are. */
  body.chatview .brand .bt{display:none}
  body.chatview .side{padding-top:calc(8px + env(safe-area-inset-top,0px));padding-bottom:8px}
  /* "Field log", "Uploads" and "Dark mode" each wrap to two lines in the
     strip, and one wrapped label sets the height of the whole row: 81px of an
     812px phone, 21% of a 390px landscape phone. */
  body.chatview .nav button,body.chatview .sidebtn{white-space:nowrap}
  .chatroot{grid-template-columns:216px minmax(0,1fr);border:0;border-radius:0}
}

/* ---- 700–900px is the TABLET band and it deliberately has no rules of its
   own: it keeps the two-pane desktop layout. A 4:3 tablet held upright is
   744–834px wide — room for a 216px rail and a 500px+ log — and the old
   900px cut-over handed it the phone layout, hiding the conversation list
   behind a back button for no reason.

   ---- phone (≤700px): one pane at a time. */
@media(max-width:700px){
  .chatroot{grid-template-columns:1fr}
  .chatroot[data-pane="room"] .chatrail{display:none}
  .chatroot[data-pane="rail"] .chatmain{display:none}
  .chatroot[data-pane="room"] .chatback{display:inline-grid}
  .chathead{grid-template-columns:auto minmax(0,1fr) auto;gap:0 8px;padding:8px 10px}
  /* min-width:0 is what keeps the room name alive: the h2 can shrink to zero
     and the nowrap chip cannot, so without a shrink limit on the chip a long
     DM name left the header showing ONLY "Private — you and <name>", clipped,
     on top of the notification bell. */
  .chattitle{grid-column:2;flex-wrap:nowrap;min-width:0;gap:7px}
  .chattitle h2{flex:1 1 auto;min-width:0}
  .chatmeta{display:none}
  /* The verbose "Open - anyone can join" chip does not fit next to the actions
     on a phone, and the same disclosure sits in the footer under the composer.
     The DM lock chip stays: that one is a privacy signal, not a nicety — but
     it drops to the bare word, because "you and <name>" only repeats the
     room title standing right next to it. */
  .chip.open{display:none}
  .chip.lock{flex:none;font-size:10.5px;padding:2px 7px}
  .chip.lock svg,.chip.lock .chiplong{display:none}
  /* 25px controls (5px padding + a 15px icon) are barely half of the 44px
     touch target every mobile HIG asks for, and the back chevron is the only
     way out of a room. */
  .chathead .iconbtn{width:40px;height:40px;padding:0;border-radius:10px}
  .chathead .btn.sm{height:36px}
  .chatlog{padding:10px 12px 4px}
  .chatform{padding:8px 12px 6px;gap:6px}
  .chatmic,.attbtn{width:40px;height:40px}
  .chatfoot{padding:0 12px calc(8px + env(safe-area-inset-bottom,0px))}
  .attrow{gap:4px}
  .atttile{min-height:36px;padding:4px 8px}
  .attthumb{width:28px;height:28px}
  /* The full disclosure wraps to three lines on a phone — ~60px of an 812px
     screen, permanently, for static boilerplate. An error still shows in
     full, because an error is the one thing here worth the space. */
  .chatfoot .ffull{display:none}
  .chatfoot .fshort{display:inline}
  /* The message actions sit in a third `auto` grid column, so a 55px "Remove"
     link narrowed the text of every message I sent. Icon-only reclaims ~30px
     of a 375px screen; the confirm step keeps its words. */
  .macts .lnk span{display:none}
  .macts .lnk.danger span,.macts .lnk[data-msg-del-cancel] span{display:inline}
  .railtop{display:grid;grid-template-columns:auto 1fr auto;gap:8px}
  .railtop .railsearch{grid-column:1/-1;height:36px;margin-top:0}
  .railtop [data-chat-new="dm"]{grid-column:1/-1;justify-content:center}
  .roomrow{padding:10px 8px}
}

/* ---- the status bar sits ON TOP of this view ----
   index.html ships `viewport-fit=cover` with
   `apple-mobile-web-app-status-bar-style: black-translucent`, and the manifest
   is `display:standalone` — so in a Home Screen web app the page extends UNDER
   the iOS status bar, ~47-59px of it on a notched iPhone.

   Every other view survives that because `.top` carries
   `padding-top: env(safe-area-inset-top)`. Chat is the one view that hides
   `.top` outright (above, to buy the log its vertical pixels), so NOTHING here
   supplied the inset: `.chathead` started at y=0 and the notifications bell sat
   at y=8-48 — entirely beneath the clock. You had to drag the page down to
   reach it.

   That only ever manifested in the Home Screen app, which is precisely what the
   push instructions tell everyone to install, so enabling notifications led
   straight into the one view where the toggle was hidden.

   Both panes need it: the rail is what you see first, the room header is where
   the bell lives. Padding rather than margin so each header's own background
   fills the status-bar strip instead of leaving a gap. Keyed on `body.chatview`
   (0,2,0) so it beats the plain `.chathead` padding shorthand above whatever
   the source order. */
@media(max-width:900px){
  body.chatview .chathead{padding-top:calc(8px + env(safe-area-inset-top,0px))}
  body.chatview .railtop{padding-top:calc(12px + env(safe-area-inset-top,0px))}
}

/* Five controls do not fit a 375px header: back, title, rename, Leave, bell.
   Measured, the title was squeezed to 144px while the actions took 155px.
   Renaming a channel is a rare admin action and an unpleasant one to do on a
   phone keyboard; the bell is the control people are actively hunting for. So
   rename goes at phone width — it is still there on tablet and desktop — and
   the bell gets room to be obvious. */
@media (max-width:700px){
  .chatacts [data-chat-rename]{display:none}
  .chatacts{gap:4px}
  /* The one control this view exists to surface right now. */
  .chatacts [data-chat-push]{width:40px;height:40px}
  .chatacts [data-chat-push].on{color:var(--brand);background:var(--brand-soft)}
}

/* Hover-reveal is fine with a mouse, but a touch device never hovers — without
   this the Remove control is unreachable on a phone. */
@media(hover:none),(pointer:coarse){
  .macts{opacity:1}
  /* The icon itself is 14px. Without a hit area around it, the one destructive
     control in chat is a 14x14 target. */
  .macts .lnk{min-width:32px;min-height:32px;justify-content:center}
}

/* A phone on its side is 844px WIDE and 390px tall, so every width-keyed rule
   above treats it as roomy while the log — the only part that matters — gets
   ~100px, two messages. Keyed on height, and placed last so it wins wherever
   the two overlap. */
@media(max-height:520px){
  .chatmeta{display:none}
  .chathead{padding:8px 12px}
  .chatlog{padding:8px 14px 4px}
  .chatform{padding:8px 14px 6px}
  .chatfoot{padding:0 14px calc(6px + env(safe-area-inset-bottom,0px))}
  .chatfoot .ffull{display:none}
  .chatfoot .fshort{display:inline}
  .railtop{padding:8px}
}

/* ---------- contracts, vendor registration, invoices ----------
   No new color token is introduced. Every state reuses an existing --x /
   --x-soft pair, which matters because the dark palette is declared VERBATIM
   in three separate blocks at the top of this file ([data-theme="dark"] and
   the prefers-color-scheme fallback); a new token added to only two of them
   leaves a light-only value on one of the dark paths.
   Every numeric cell gets tabular-nums, which is the house rule on every
   number in the app (.kpi .v, .drive b, .chip .n). Columns of dollars without
   it visibly jitter as the digits change. */
.mpill{display:inline-block;font-size:11px;font-weight:600;line-height:19px;padding:0 8px;border-radius:999px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.renew b{font-weight:600;font-variant-numeric:tabular-nums;font-size:12.5px}
.renew small{display:block;color:var(--ink-4);font-size:10.5px;font-variant-numeric:tabular-nums}
.renew.soon small{color:var(--amber)}
.renew.warn b,.renew.warn small{color:var(--amber)}
.renew.bad b,.renew.bad small{color:var(--red)}
.leadtable td.c-cval,.leadtable td.c-cbus,.leadtable td.m-val,.leadtable td.m-tcv{font-variant-numeric:tabular-nums}
.leadtable td.c-cval b,.leadtable td.m-val b{font-weight:600}
.cflag{font-size:10.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--amber);
  background:var(--amber-soft);border-radius:4px;padding:1px 5px;margin-left:4px}

/* The Contracts view's own table. A SECOND fixed-layout table with its own
   width block summing to 100 and its own tier ladder — that independence is
   the second reason this is a separate view rather than more columns on Leads.
   Its wrapper is deliberately NOT a scroll container either. */
.moneytable .m-dist{width:20%}
.moneytable .m-cstat{width:12%}
.moneytable .m-vstat{width:11.5%}
.moneytable .m-val{width:10%}
.moneytable .m-tcv{width:10%}
.moneytable .m-end{width:11%}
.moneytable .m-who{width:12%}
.moneytable .m-it{width:11.5%}
.moneytable.inv .m-dist{width:26%}
.moneytable.inv .m-cstat{width:14%}
.moneytable.inv .m-end{width:14%}
.moneytable.inv .m-val{width:16%}
@media (max-width:1180px){ .moneytable .m-it{display:none} }
@media (max-width:1040px){ .moneytable .m-tcv{display:none} }
@media (max-width:900px){ .moneytable .m-who{display:none} }
@media (max-width:640px){
  .moneytable .m-dist,.moneytable .m-cstat,.moneytable .m-vstat,.moneytable .m-val,
  .moneytable .m-tcv,.moneytable .m-end,.moneytable .m-who,.moneytable .m-it,
  /* .moneytable.inv is (0,3,0) and would otherwise beat the reset above. */
  .moneytable.inv .m-dist,.moneytable.inv .m-cstat,
  .moneytable.inv .m-end,.moneytable.inv .m-val{width:auto}
  .moneytable td.m-dist{grid-column:1/-1;order:1}
  .moneytable td.m-cstat{order:2}
  .moneytable td.m-vstat{order:3}
  .moneytable td.m-val{order:4}
  .moneytable td.m-tcv{order:5;display:table-cell}
  .moneytable td.m-end{order:6}
  .moneytable td.m-who{order:7;display:table-cell;grid-column:1/-1}
  .moneytable td.m-it{order:8;display:table-cell;grid-column:1/-1}
  .moneytable td.m-cstat .mpill,.moneytable td.m-vstat .mpill{width:100%;text-align:center}
  /* 25% of a contract card was labels over em dashes — IT contact alone was
     blank on 4 of 7 rows, Renews on 3, Annual and Term value on 2, and on the
     Invoices tab Paid is blank on every invoice that is merely sent. The
     status pill already says what a missing paid-date means. */
  .moneytable td.nodata{display:none}
}

/* invoice modal: line items */
.iline{display:grid;grid-template-columns:minmax(0,1fr) 76px 92px 92px 28px;gap:8px;align-items:center;margin-bottom:7px}
.iline input{width:100%;min-width:0}
.iline .il-qty,.iline .il-unit{text-align:right;font-variant-numeric:tabular-nums}
.il-amt{text-align:right;font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--ink-2)}
button.il-x{background:none;border:0;color:var(--ink-4);cursor:pointer;display:inline-flex;padding:3px;border-radius:5px}
button.il-x:hover{color:var(--red);background:var(--red-soft)}
button.il-x svg{width:14px;height:14px}
span.il-x{display:block}
.invtot{margin:12px 0 4px;padding-top:10px;border-top:1px solid var(--line)}
.invtot div{display:flex;justify-content:space-between;font-size:13px;padding:3px 0;color:var(--ink-2)}
.invtot b{font-variant-numeric:tabular-nums;font-weight:600;color:var(--ink)}
.invtot .tot{border-top:1px solid var(--line);margin-top:5px;padding-top:8px;font-size:15px}
.invtot .tot b{font-size:17px}
.ro{font-size:13px;color:var(--ink-2);padding:9px 0}
.invrow .logdate .mpill{margin-left:6px}

@media (max-width:560px){
  .iline{grid-template-columns:minmax(0,1fr) 72px 24px;grid-template-areas:"d d x" "q u a";gap:6px 8px;align-items:center}
  .iline .il-desc{grid-area:d}
  .il-amt{grid-area:a}
  button.il-x,span.il-x{grid-area:x;justify-self:end}
}

/* ---------- invoice print ----------
   The browser's own "Save as PDF" is the PDF generator. #invprint sits OUTSIDE
   .app precisely so this rule can hide the entire application shell and leave
   the document alone on the page. */
#invprint{display:none}
@media print{
  .app,.drawer,.modal,.scrim,.toast{display:none!important}
  html,body{background:#fff;height:auto;overflow:visible}
  #invprint{display:block;color:#000;font-size:12px}
  @page{margin:14mm}
  .inv-h{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;margin-bottom:22px}
  .inv-h h1{font-size:26px;letter-spacing:.04em;margin:0}
  .inv-no{font-size:13px;color:#444;margin-top:2px}
  .inv-from{text-align:right;font-size:11px;line-height:1.5}
  .inv-meta{display:flex;flex-wrap:wrap;gap:26px;border-top:1px solid #ccc;border-bottom:1px solid #ccc;padding:12px 0;margin-bottom:18px}
  .inv-meta span{display:block;font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:#666}
  .inv-meta b{font-size:12px;font-weight:600}
  .inv-t{width:100%;border-collapse:collapse;margin-bottom:16px}
  .inv-t th{text-align:left;font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:#666;
    border-bottom:1px solid #999;padding:0 0 6px}
  .inv-t td{padding:7px 0;border-bottom:1px solid #eee;vertical-align:top}
  .inv-t .n{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;padding-left:14px}
  .inv-sum{margin-left:auto;width:250px}
  .inv-sum div{display:flex;justify-content:space-between;padding:4px 0;font-size:12px}
  .inv-sum b{font-variant-numeric:tabular-nums}
  .inv-sum .tot{border-top:1px solid #999;margin-top:4px;padding-top:8px;font-size:15px;font-weight:700}
  .inv-notes{margin-top:26px;font-size:11px;color:#333;white-space:pre-wrap}
  .inv-foot{margin-top:16px;padding-top:10px;border-top:1px solid #ccc;font-size:10px;color:#555}
}

/* ---------- conferences ----------
   Its own fixed-layout table with its own width block summing to 100 and its
   own tier ladder, like .moneytable. Wrapper is NOT a scroll container. */
.conftable .k-name{width:24%}
.conftable .k-tier{width:10%}
.conftable .k-stat{width:11%}
.conftable .k-when{width:12%}
.conftable .k-where{width:17%}
.conftable .k-spend{width:11%}
.conftable .k-value{width:12%}
.conftable .k-go{width:3%}
.conftable td.k-value b.ok,.moneytable td b.ok{color:var(--green)}
/* The generic radius rule targets thead th.c-go; this table's last column is
   k-go, so without this the header's top-right corner stays square. */
.conftable thead th.k-go{border-top-right-radius:var(--r)}
.cfyr{font-weight:600;font-variant-numeric:tabular-nums;color:var(--ink-3);font-size:12px}
@media (max-width:1180px){ .conftable .k-where{display:none} }
@media (max-width:1040px){ .conftable .k-tier{display:none} }
@media (max-width:640px){
  .conftable .k-name,.conftable .k-tier,.conftable .k-stat,.conftable .k-when,
  .conftable .k-where,.conftable .k-spend,.conftable .k-value,.conftable .k-go{width:auto}
  .conftable td.k-name{grid-column:1/-1;order:1}
  .conftable td.k-tier{order:2;display:table-cell}
  .conftable td.k-stat{order:3}
  .conftable td.k-when{order:4}
  .conftable td.k-where{order:5;display:table-cell;grid-column:1/-1}
  .conftable td.k-spend{order:6}
  .conftable td.k-value{order:7}
  .conftable td.k-go{display:none}
  .conftable td.k-tier .mpill,.conftable td.k-stat .mpill{width:100%;text-align:center}
}

/* the itemized budget grid in the edition modal */
.costhead,.costrow{display:grid;grid-template-columns:minmax(0,1fr) 104px 104px;gap:8px;align-items:center}
.costhead{margin-bottom:5px}
.costhead span{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);text-align:right}
.costhead .cl{text-align:left}
.costrow{margin-bottom:6px}
.costrow .cl{font-size:12.5px;color:var(--ink-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.costrow input{width:100%;text-align:right;font-variant-numeric:tabular-nums}
.costrow .ca{background:var(--surface-2)}

/* "districts we met here" — a checkbox set, not a multi-select */
.metgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:6px;
  max-height:230px;overflow-y:auto;overscroll-behavior:contain;padding:2px;
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface-2)}
.metopt{display:flex;align-items:center;gap:7px;font-size:12.5px;padding:6px 8px;border-radius:6px;
  cursor:pointer;background:var(--surface);border:1px solid var(--line);min-width:0}
.metopt span,.metopt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.metopt input{flex:none;margin:0}
.metopt:hover{border-color:var(--line-2)}
.metopt.on{border-color:var(--green);background:var(--green-soft);color:var(--green);font-weight:600}
/* The trip modal puts these inside a .fld, where `.fld label` (0,1,1) beats
   `.metopt` (0,1,0) and would take away its display:flex and its font-size —
   the same collision that had the Type picker's icons sitting on a baseline.
   Everywhere else .metopt lives outside a .fld and needs none of this. */
.fld .metopt{display:flex;font-size:12.5px;margin:0}
.fld .metgrid{margin-bottom:8px}
/* `.fld input` sets width:100%, which is right for a text box and catastrophic
   for a checkbox: inside a .fld the box stretched to 198px, `.metopt input`
   pinned it there with flex:none, and the name beside it was squeezed to zero
   width — a row of green chips with no names on them. `.metopt input` only
   ever overrode flex and margin, so the width came straight through. */
.fld .metopt input[type=checkbox]{width:auto;height:auto;flex:none}
.fld .metopt span{flex:1 1 auto;min-width:0}

/* Inviting someone who is not on the access list. */
.attadd{display:flex;gap:8px;align-items:center}
.fld .attadd input{flex:1 1 auto;min-width:0}
.attadd .btn{flex:none}
.attchips{display:flex;flex-wrap:wrap;gap:6px}
.attchips:not(:empty){margin-top:8px}
.attchip{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;
  background:var(--surface-3);border:1px solid var(--line-2);border-radius:999px;
  padding:3px 4px 3px 10px;color:var(--ink-2);max-width:100%}
.attchip button{display:inline-flex;align-items:center;justify-content:center;
  width:18px;height:18px;padding:0;border:0;border-radius:50%;cursor:pointer;
  background:none;color:var(--ink-4)}
.attchip button:hover{background:var(--red-soft);color:var(--red)}
.attchip button svg{width:11px;height:11px}

/* contacts, repeated inside the conference drawer */
.ctrow{border:1px solid var(--line);border-radius:var(--r-sm);padding:11px 12px 8px;margin-bottom:9px;background:var(--surface-2)}
.ctdel{margin-top:2px}
.edrow .logdate .mpill{margin-left:6px}

@media (max-width:560px){
  .costhead,.costrow{grid-template-columns:minmax(0,1fr) 78px 78px;gap:6px}
  .metgrid{grid-template-columns:1fr}
}

/* ---------- contacts ----------
   Its own fixed-layout table, widths summing to 100, its own tier ladder.
   The 640px reset below matches these selectors' specificity exactly — a
   higher-specificity width rule beats the reset and collapses the phone cards,
   which is a bug this codebase has now shipped twice. */
.cttable .t-star{width:4%}
.cttable .t-name{width:22%}
.cttable .t-role{width:13%}
.cttable .t-org{width:20%}
.cttable .t-email{width:20%}
.cttable .t-phone{width:12%}
.cttable .t-state{width:6%}
.cttable .t-go{width:3%}
.cttable thead th.t-go{border-top-right-radius:var(--r)}
.cttable td.t-email a,.cttable td.t-phone a{color:var(--ink-2);font-size:12.5px}
.cttable td.t-email a{word-break:break-all}
.cttable td.t-phone{font-variant-numeric:tabular-nums}
.cttable td.t-state{font-weight:600;color:var(--ink-3);font-size:11.5px}
@media (max-width:1180px){ .cttable .t-org{display:none} }
@media (max-width:1040px){ .cttable .t-state{display:none} }
@media (max-width:640px){
  .cttable .t-star,.cttable .t-name,.cttable .t-role,.cttable .t-org,
  .cttable .t-email,.cttable .t-phone,.cttable .t-state,.cttable .t-go{width:auto}
  /* The star was `grid-column:1/-1` at order 0 — a full-width row holding one
     22px glyph, above the name it belongs to, on every card. Lifted out of the
     grid entirely and pinned to the card's top-right, which is both where a
     card's affordance is looked for and ~34px per card reclaimed. */
  .cttable tbody tr{position:relative;padding-right:46px}
  .cttable td.t-star{position:absolute;top:6px;right:6px;padding:0;width:auto}
  .cttable td.t-name{grid-column:1/-1;order:1}
  .cttable td.t-role{order:2}
  .cttable td.t-state{order:3;display:table-cell}
  .cttable td.t-org{order:4;grid-column:1/-1;display:table-cell}
  .cttable td.t-email{order:5;grid-column:1/-1}
  .cttable td.t-phone{order:6}
  .cttable td.t-go{display:none}
  .cttable td.t-role .mpill{width:100%;text-align:center}
  /* A contact with no org, email, phone or state rendered four labelled rows
     of "—": ~150px of a 375px screen spent saying nothing, and the conference
     and association records that carry only a name are exactly the ones where
     all four are blank. The desktop table keeps them — a column with a hole in
     it cannot be read — and the editor still shows every field.
     The marker is `nodata`, NOT `empty`: `.empty` is the global no-results
     panel (56px padding, centred, muted), and a bare `empty` on a <td> handed
     28 desktop cells centre-alignment and the wrong ink while every neighbour
     stayed left-aligned. Same trap for any table that copies this. */
  .cttable td.nodata{display:none}
}

/* the star: a one-click judgement, so it lives in the row */
.starbtn{background:none;border:0;padding:3px;cursor:pointer;color:var(--ink-5);
  display:grid;place-items:center;border-radius:6px;line-height:0}
.starbtn svg{width:16px;height:16px;fill:none}
.starbtn:hover{color:var(--amber);background:var(--amber-soft)}
.starbtn.on{color:var(--amber)}
.starbtn.on svg{fill:var(--amber)}
.ststar{display:inline-flex;color:var(--amber);margin-left:5px;vertical-align:-3px}
.ststar svg{width:13px;height:13px;fill:var(--amber)}
.segbar button .stx{width:13px;height:13px;fill:none;opacity:.8}
.segbar button[aria-pressed="true"] .stx{fill:var(--amber);color:var(--amber);opacity:1}
.ctrowline .logdate .mpill{margin-left:6px}
.ctwrap .logtop{margin-bottom:2px}

/* ---------- VSI users (cross-instance) ----------
   Own fixed-layout table, widths summing to 100, tier ladder, and a 640px
   reset at MATCHING specificity. */
.envrow{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.usertable .u-who{width:30%}
.usertable .u-auth{width:16%}
.usertable .u-role{width:14%}
.usertable .u-county{width:22%}
.usertable .u-state{width:10%}
.usertable .u-go{width:8%}
.usertable thead th.u-go{border-top-right-radius:var(--r)}
.usertable td.u-state{font-weight:600;color:var(--ink-3);font-size:11.5px}
/* An off-enum value is shown verbatim, and looks like what it is: real data
   the product tolerates, not a rendering failure. */
.offenum{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;
  color:var(--red);background:var(--red-soft);border-radius:4px;padding:1px 6px}
.warnsub{color:var(--amber)!important;font-weight:600}
@media (max-width:1180px){ .usertable .u-state{display:none} }
@media (max-width:1040px){ .usertable .u-role{display:none} }
@media (max-width:640px){
  .usertable .u-who,.usertable .u-auth,.usertable .u-role,
  .usertable .u-county,.usertable .u-state,.usertable .u-go{width:auto}
  .usertable td.u-who{grid-column:1/-1;order:1}
  .usertable td.u-auth{order:2}
  .usertable td.u-role{order:3;display:table-cell}
  .usertable td.u-county{order:4;grid-column:1/-1}
  .usertable td.u-state{order:5;display:table-cell}
  .usertable td.u-go{display:none}
  .usertable td.u-auth .mpill{width:100%;text-align:center}
  .usertable td.nodata{display:none}
}
/* A state profile's empty county is the CORRECT configuration. It must not
   read like the missing-value dash next to it. */
.statewide{font-size:11.5px;font-weight:600;color:var(--teal);background:var(--teal-soft);
  border-radius:4px;padding:1px 7px;display:inline-block}
/* VSI Queue — the merged Uploads + Issues surface. Reuses the whole .up-*
   layer (Issues already did), and adds only the per-district kind mix. */
.q-mix{font-size:11px;color:var(--ink-4);margin-left:2px}
@media(max-width:640px){ .q-mix{display:none} }

/* ===================== Meetings ======================================== */
/* The dashboard. Ordinary cards — this is a list of meetings, and it should
   look like the rest of the app. The ROOM is the only part that goes dark. */
.mtwrap{display:flex;flex-direction:column;gap:14px}
.mtactive{border-color:var(--line-2)}
.mtnone{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:6px 0 2px}
.mtnone b{font-size:13.5px}
.mtnone span{font-size:12.5px;color:var(--ink-3);margin-bottom:10px}

.mtlive{border:1px solid var(--brand);background:var(--brand-soft);border-radius:var(--r);padding:14px 16px}
.mtlive+.mtlive{margin-top:10px}
.mtlivetop{display:flex;align-items:center;gap:9px;margin-bottom:6px}
.mtlivetop b{font-size:14.5px;color:var(--ink)}
/* A live meeting is the one thing on this page that is happening RIGHT NOW,
   so it gets the only animated element in the app. Stilled for anyone who
   asked for less motion. */
.mtdot{width:9px;height:9px;border-radius:50%;background:var(--green);flex:none;
  box-shadow:0 0 0 0 color-mix(in srgb,var(--green) 70%,transparent);animation:mtpulse 2s infinite}
@keyframes mtpulse{70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
@media(prefers-reduced-motion:reduce){.mtdot{animation:none}}
.mtlivetag{font-size:10px;font-weight:700;letter-spacing:.08em;color:var(--green);
  background:var(--green-soft);border-radius:5px;padding:2px 7px}
.mtlivemeta{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:12px;color:var(--ink-2);margin-bottom:12px}
.mtliveacts{display:flex;gap:9px;align-items:center;flex-wrap:wrap}

.mtlist{display:flex;flex-direction:column}
.mtrow{display:grid;grid-template-columns:78px minmax(0,1fr) auto 16px;align-items:center;gap:12px;
  width:100%;padding:9px 8px;margin:0 -8px;border:0;border-radius:var(--r-sm);background:none;
  cursor:pointer;text-align:left;font:inherit;color:inherit}
.mtrow:hover{background:var(--surface-3)}
.mtrow+.mtrow{border-top:1px solid var(--line)}
.mtwhen{display:flex;flex-direction:column;gap:1px}
.mtwhen b{font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums}
.mtwhen em{font-style:normal;font-size:11px;color:var(--ink-3);font-variant-numeric:tabular-nums}
.mtmid{min-width:0;display:flex;flex-direction:column;gap:1px}
.mtmid b{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mtmid em{font-style:normal;font-size:11.5px;color:var(--ink-3)}
.mtdur{font-size:11.5px;color:var(--ink-3);font-variant-numeric:tabular-nums;white-space:nowrap}
.mtgo{color:var(--ink-4);display:inline-flex}
.mtgo svg{width:15px;height:15px}
.nb.live{background:var(--green);color:#fff}

.mtfacts{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:6px}
.mtfacts>div{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:9px 11px}
.mtfacts span{display:block;font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.mtfacts b{font-size:13.5px;font-weight:600}
.mtattlist{display:flex;flex-direction:column;gap:2px}
.mtattrow{display:flex;align-items:center;gap:10px;padding:6px 0}
.mtattrow+.mtattrow{border-top:1px solid var(--line)}
.mtattn{flex:1;min-width:0;display:flex;flex-direction:column}
.mtattn b{font-size:12.5px;font-weight:600}
.mtattn em{font-style:normal;font-size:10.5px;color:var(--ink-3)}
.mtattd{font-size:12px;color:var(--ink-2);font-variant-numeric:tabular-nums}

/* ---- the stage: lobby and room ---------------------------------------- */
/* Fixed, above everything, and its own dark ground regardless of theme — a
   light UI around video is glare, and every conferencing tool goes dark for
   the same reason. */
body.meeting{overflow:hidden}
.meetstage{position:fixed;inset:0;z-index:60;background:#0A0F16;color:#E9EEF4;
  display:flex;flex-direction:column;overscroll-behavior:none}

.mtlobby{margin:auto;width:min(560px,94vw);max-height:100%;overflow-y:auto;padding:24px 20px}
.mtkicker{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#7DB4E8}
.mtlobbyhead h1{font-size:22px;letter-spacing:-.02em;margin:4px 0 16px}
.mtpreviewwrap{position:relative;aspect-ratio:16/9;border-radius:12px;overflow:hidden;
  background:#111A24;border:1px solid #1E2A37}
.mtpreview{width:100%;height:100%;object-fit:cover;transform:scaleX(-1)}
.mtpreviewoff{position:absolute;inset:0;display:grid;place-items:center;gap:8px;align-content:center;
  background:#111A24;color:#72808F;font-size:12px}
.mtpreviewoff em{font-style:normal}
.mtav{width:56px;height:56px;border-radius:50%;display:grid;place-items:center;
  background:#1E2A37;color:#9FADBE;font-size:18px;font-weight:700}
.mtav.sm{width:26px;height:26px;font-size:10px}
/* Proof the microphone works, before six people are waiting to find out. */
.mtlevel{position:absolute;left:10px;right:10px;bottom:10px;height:4px;border-radius:2px;background:rgba(255,255,255,.14)}
.mtlevel i{display:block;height:100%;width:0;border-radius:2px;background:#3DB88E;transition:width .1s linear}
.mterr{margin-top:12px;padding:11px 13px;border-radius:10px;background:#2C1615;border:1px solid #E4736F}
.mterr b{display:block;font-size:12.5px;color:#E4736F}
.mterr span{display:block;font-size:12px;color:#9FADBE;margin-top:3px;line-height:1.5}
.mtdevices{display:grid;gap:10px;margin-top:16px}
.mtdevices .fld{margin:0}
.mtdevices span{display:block;font-size:11px;color:#9FADBE;margin-bottom:4px}
.mtdevices select{width:100%;height:38px;font:inherit;font-size:13px;background:#111A24;color:#E9EEF4;
  border:1px solid #2A3846;border-radius:8px;padding:0 10px}
.mtlobbyacts{display:flex;align-items:center;gap:9px;margin-top:18px;flex-wrap:wrap}
.mtspacer{flex:1}
.mtwho{font-size:11.5px;color:#72808F;margin:12px 0 0}

.mtbtn{display:inline-flex;align-items:center;gap:7px;height:38px;padding:0 13px;border-radius:9px;
  border:1px solid #2A3846;background:#151F2A;color:#E9EEF4;font:inherit;font-size:12.5px;cursor:pointer}
.mtbtn:hover{border-color:#3d4d5e}
.mtbtn svg{width:16px;height:16px}
.mtbtn.off{background:#2C1615;border-color:#E4736F;color:#E4736F}
.mtbtn.on{background:#132738;border-color:#579BDD;color:#7DB4E8}

/* ---- room -------------------------------------------------------------- */
.mtroom{display:flex;flex-direction:column;height:100%;min-height:0}
.mtroomhead{display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid #16202B;flex:none}
.mtroomtitle{display:flex;align-items:baseline;gap:9px;min-width:0}
.mtroomtitle b{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mtclock{font-size:11.5px;color:#72808F;font-variant-numeric:tabular-nums}
.mtlayoutpick{margin-left:auto;display:flex;gap:2px;background:#111A24;border:1px solid #1E2A37;border-radius:8px;padding:2px}
.mtlayoutpick button{border:0;background:none;color:#9FADBE;font:inherit;font-size:11.5px;
  padding:4px 10px;border-radius:6px;cursor:pointer}
.mtlayoutpick button.on{background:#1E2A37;color:#E9EEF4}
.mtbanner{padding:6px 16px;background:#2C2312;color:#D9A03F;font-size:12px;text-align:center;flex:none}
.mtbody{flex:1;min-height:0;display:flex}
.mtstagearea{flex:1;min-width:0;display:flex;flex-wrap:wrap;gap:8px;padding:12px;align-content:center;justify-content:center}
.mttile{position:relative;background:#111A24;border-radius:10px;overflow:hidden;
  aspect-ratio:16/9;flex:1 1 calc(50% - 8px);min-width:200px;max-width:100%}
.mttile video{width:100%;height:100%;object-fit:cover;display:block}
.mttile video.mirror{transform:scaleX(-1)}
/* A shared screen is the reason the meeting is happening. It gets the room,
   contain not cover so nothing is cropped off a spreadsheet, and the faces
   shrink to a strip. */
.mttile.big{flex:1 1 100%;aspect-ratio:auto;height:100%;max-height:100%}
.mttile.big video{object-fit:contain;background:#05090E}
.mtstagearea.sharing,.mtstagearea.speaker{align-content:stretch;flex-direction:column;flex-wrap:nowrap}
.mtstagearea.sharing .mttile:not(.big),.mtstagearea.speaker .mttile:not(.big){
  flex:0 0 108px;height:72px;aspect-ratio:auto;min-width:128px;max-width:160px}
.mtstagearea.sharing>.mtstrip,.mtstagearea.speaker>.mtstrip{flex:none}
/* The non-focused tiles wrap into a row under the big one. */
.mtstagearea.sharing,.mtstagearea.speaker{display:flex}
.mtstagearea.grid .mttile{flex:1 1 calc(50% - 8px)}
.mtstagearea.grid[style*="--n:1"] .mttile{flex:1 1 100%;max-width:min(100%,72vh*1.78)}
.mtoff{position:absolute;inset:0;display:grid;place-items:center;background:#111A24}
.mtname{position:absolute;left:8px;bottom:6px;font-size:11px;color:#E9EEF4;
  background:rgba(5,9,14,.62);border-radius:5px;padding:2px 7px;max-width:calc(100% - 40px);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mttile.speaking{box-shadow:inset 0 0 0 2px #3DB88E}
.mtq{position:absolute;right:8px;top:8px;width:7px;height:7px;border-radius:50%;background:transparent}
.mtq.poor{background:#D9A03F}
.mtq.lost{background:#E4736F}

.mtpeople{flex:none;width:274px;border-left:1px solid #16202B;overflow-y:auto;padding:12px}
.mtpeoplehead{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:#72808F;margin-bottom:8px}
.mtpeoplehead span{color:#E9EEF4}
.mtperson{display:flex;align-items:center;gap:9px;padding:6px 0}
.mtpn{flex:1;min-width:0;display:flex;flex-direction:column}
.mtpn b{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mtpn em{font-style:normal;font-size:10px;color:#7DB4E8}
.mtflag{font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:#3DB88E;background:#0C2721;border-radius:4px;padding:1px 5px}
.mticon{display:inline-flex;color:#9FADBE}
.mticon.off{color:#E4736F}
.mticon svg{width:14px;height:14px}
.mtpeoplenote{font-size:11px;color:#5A6775;line-height:1.5;margin:12px 0 0}

.mtbar{display:flex;align-items:center;gap:8px;padding:10px 16px;border-top:1px solid #16202B;
  flex:none;flex-wrap:wrap;padding-bottom:calc(10px + env(safe-area-inset-bottom,0px))}

/* ---- phones ------------------------------------------------------------ */
@media(max-width:760px){
  /* One or two tiles, big enough to read a face; the rest scroll. Controls
     lose their words and keep their icons. */
  .mtstagearea{padding:8px;gap:6px}
  .mtstagearea.grid .mttile{flex:1 1 100%;min-width:0}
  .mtbody{flex-direction:column}
  .mtpeople{width:auto;border-left:0;border-top:1px solid #16202B;max-height:44vh}
  .mtbtn span{display:none}
  .mtbtn{padding:0 11px}
  .mtroomhead{padding:8px 12px}
  .mtbar{padding:8px 12px;gap:6px;padding-bottom:calc(8px + env(safe-area-inset-bottom,0px))}
  .mtlobby{padding:16px 14px}
  .mtrow{grid-template-columns:64px minmax(0,1fr) auto}
  .mtrow .mtgo{display:none}
}
