:root{
  --bg:#0f172a; --panel:#ffffff; --muted:#64748b; --line:#e2e8f0; --ink:#1e293b;
  --green:#16a34a; --green-bg:#dcfce7; --yellow:#b45309; --yellow-bg:#fef3c7;
  --red:#dc2626; --red-bg:#fee2e2; --blue:#2563eb; --blue-bg:#dbeafe;
  --grey:#475569; --grey-bg:#f1f5f9;
}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  color:var(--ink);background:#f8fafc;font-size:14px;line-height:1.5}
a{color:var(--blue);text-decoration:none}
a:hover{text-decoration:underline}
.layout{display:flex;min-height:100vh}
.sidebar{width:230px;background:var(--bg);color:#cbd5e1;padding:18px 0;flex-shrink:0;
  position:sticky;top:0;height:100vh;overflow:auto}
.sidebar h1{color:#fff;font-size:16px;margin:0 18px 4px}
.sidebar .tag{color:#64748b;font-size:11px;margin:0 18px 18px;display:block}
.sidebar a{display:block;color:#cbd5e1;padding:8px 18px 8px 26px;font-size:13px}
.sidebar a:hover{background:#1e293b;text-decoration:none}
.sidebar a.active{background:#1e293b;color:#fff;border-left:3px solid var(--blue);padding-left:23px}

/* Collapsible nav groups (native <details>/<summary> — keyboard accessible, works without JS) */
.navgroup{border-top:1px solid rgba(148,163,184,.14)}
.navgroup>summary{list-style:none;cursor:pointer;color:#94a3b8;font-size:10.5px;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;padding:11px 18px 7px;display:flex;align-items:center;gap:6px;
  user-select:none}
.navgroup>summary::-webkit-details-marker{display:none}
.navgroup>summary::before{content:"▸";font-size:9px;color:#64748b;transition:transform .12s ease}
.navgroup[open]>summary::before{transform:rotate(90deg)}
.navgroup>summary:hover{color:#cbd5e1}
.navgroup>summary:focus-visible{outline:2px solid var(--blue);outline-offset:-2px}
.navgroup-body{padding-bottom:4px}

/* Mobile nav toggle (hidden on desktop) */
.navtoggle{display:none;position:fixed;top:8px;left:8px;z-index:40;background:var(--bg);color:#fff;
  border:1px solid #334155;border-radius:7px;padding:7px 11px;font-size:13px;cursor:pointer}
.navuser{margin:14px 18px 0;padding-top:12px;border-top:1px solid rgba(148,163,184,.25);font-size:12px;color:#94a3b8}
.navuser-name{color:#cbd5e1}
.navuser-role{text-transform:uppercase;letter-spacing:.05em;font-size:10px}
.navuser-signout{color:#94a3b8;display:inline-block;margin-top:6px}
.navlogout{margin-top:18px;color:#94a3b8}
@media(max-width:820px){
  .navtoggle{display:block}
  .sidebar{position:fixed;left:0;top:0;z-index:35;transform:translateX(-100%);transition:transform .18s ease;
    box-shadow:2px 0 18px rgba(0,0,0,.35)}
  .sidebar.open{transform:translateX(0)}
  .content{width:100%}
  .topbar{padding-left:64px}
  .main{padding:16px 14px}
  /* Stack multi-column layouts on narrow screens (override inline grid-template-columns). */
  .form-grid{grid-template-columns:1fr!important}
  .cards{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))!important}
  table{display:block;overflow-x:auto;white-space:nowrap}
  body{overflow-x:hidden}
}
.content{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar{position:sticky;top:0;z-index:20;background:var(--panel);border-bottom:1px solid var(--line);
  display:flex;gap:12px;align-items:center;padding:10px 22px}
.searchform{flex:1;margin:0}
.searchform input{width:100%;border-radius:20px;background:#f1f5f9}
.topbar-actions{display:flex;gap:6px;align-items:center}
.main{padding:22px 26px;max-width:1320px}
h2{margin:0 0 16px;font-size:22px}
h3{font-size:15px;margin:22px 0 10px;color:var(--grey)}
.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:18px;margin-bottom:18px}
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:14px}
.stat{background:#fff;border:1px solid var(--line);border-radius:10px;padding:16px}
.stat .n{font-size:28px;font-weight:700}
.stat .l{color:var(--muted);font-size:12px;margin-top:4px}
table{width:100%;border-collapse:collapse;background:#fff}
th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);background:#f8fafc}
tr:hover td{background:#fafcff}
.badge{display:inline-block;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap}
.badge-green{color:var(--green);background:var(--green-bg)}
.badge-yellow{color:var(--yellow);background:var(--yellow-bg)}
.badge-red{color:var(--red);background:var(--red-bg)}
.badge-blue{color:var(--blue);background:var(--blue-bg)}
.badge-grey{color:var(--grey);background:var(--grey-bg)}
.btn{display:inline-block;padding:7px 14px;border-radius:7px;border:1px solid var(--line);
  background:#fff;color:var(--ink);cursor:pointer;font-size:13px}
.btn:hover{background:#f1f5f9;text-decoration:none}
.btn-primary{background:var(--blue);color:#fff;border-color:var(--blue)}
.btn-primary:hover{background:#1d4ed8}
.btn-danger{color:var(--red);border-color:#fecaca}
.btn-sm{padding:4px 9px;font-size:12px}
.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.toolbar .spacer{flex:1}
input,select,textarea{font:inherit;padding:7px 9px;border:1px solid var(--line);border-radius:7px;width:100%;background:#fff}
textarea{min-height:64px;resize:vertical}
.form-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px}
.field label{display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:600}
.field.full{grid-column:1/-1}
.flash{padding:11px 14px;border-radius:8px;margin-bottom:16px;background:var(--blue-bg);color:#1e40af;border:1px solid transparent}
/* A notice about a mutation that did NOT happen must not look like one about a mutation that did. */
.flash-ok{background:var(--green-bg);color:#166534;border-color:#bbf7d0}
.flash-warn{background:var(--yellow-bg);color:#92400e;border-color:#fde68a}
.flash-error{background:var(--red-bg);color:#991b1b;border-color:#fecaca;font-weight:600}
.kanban{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.kcol{background:#f1f5f9;border-radius:10px;padding:12px}
.kcol h4{margin:0 0 10px;font-size:13px}
.kcard{background:#fff;border:1px solid var(--line);border-radius:8px;padding:10px;margin-bottom:8px;font-size:13px}
.muted{color:var(--muted)}
.next-box{background:var(--blue-bg);border:1px solid #bfdbfe;border-radius:10px;padding:14px 16px}
.next-box b{color:#1e40af}
.src-card{border:1px solid var(--line);border-radius:8px;padding:11px 13px;margin-bottom:8px;display:flex;justify-content:space-between;gap:10px}
.src-card .t{font-weight:600}
.warn{background:var(--red-bg);border:1px solid #fecaca;color:#991b1b;padding:11px 14px;border-radius:8px;margin-bottom:14px}

/* ── Research Transcript: recommendation rendered as an analyst report (status separated from narrative) ──
   Only the status badge and the review-triggers callout carry warning colour; the AI narrative is body text.
   All colours use CSS custom properties so the section adapts to light/dark automatically. */
.reco-report{padding:0;overflow:hidden}
.reco-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px 12px;padding:14px 18px;border-bottom:1px solid var(--line)}
.reco-badge{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;letter-spacing:.02em;padding:5px 12px;border-radius:999px;white-space:nowrap}
.reco-badge-warn{background:var(--red-bg);color:var(--red);border:1px solid var(--red)}
.reco-badge-ok{background:var(--green-bg);color:var(--green);border:1px solid var(--green)}
.reco-head-note{margin-left:auto}
.reco-meta{display:flex;flex-wrap:wrap;gap:12px;padding:16px 18px 2px}
.reco-tile{flex:1 1 200px;min-width:0;border:1px solid var(--line);border-radius:9px;padding:11px 13px}
.reco-tile-l{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:4px}
.reco-tile-v{font-size:16px;font-weight:600;color:var(--ink);line-height:1.35;word-break:break-word}
.reco-section{padding:16px 18px 0}
.reco-h{font-size:12px;font-weight:700;color:var(--muted);margin:0 0 7px;text-transform:uppercase;letter-spacing:.04em}
.reco-prose{color:var(--ink);line-height:1.65;font-size:14px;max-width:74ch}
.reco-prose p{margin:0 0 11px}
.reco-prose p:last-child{margin-bottom:0}
.reco-list{margin:0;padding-left:22px;max-width:74ch}
.reco-list li{margin-bottom:7px;color:var(--ink);line-height:1.55}
.reco-list li:last-child{margin-bottom:0}
.reco-triggers{margin:16px 18px 0;border:1px solid var(--red);border-left-width:4px;background:var(--red-bg);border-radius:9px;padding:12px 15px}
.reco-triggers-h{margin:0 0 8px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--red)}
.reco-contact{border:1px solid var(--line);border-left:4px solid var(--blue);border-radius:9px;padding:12px 15px;line-height:1.55;color:var(--ink);max-width:74ch;word-break:break-word}
.reco-more{margin-top:8px}
.reco-more>summary{cursor:pointer;color:var(--blue);font-weight:600;font-size:13px;list-style:none;display:inline-flex;align-items:center;gap:5px;width:fit-content}
.reco-more>summary::-webkit-details-marker{display:none}
.reco-more>summary::before{content:"▸";font-size:9px}
.reco-more[open]>summary::before{content:"▾"}
.reco-more[open]>summary{margin-bottom:9px}
.reco-foot{padding:14px 18px;margin:14px 0 0;border-top:1px solid var(--line)}
.reco-error{margin:16px 18px 0}
@media(max-width:820px){
  .reco-head-note{margin-left:0;flex-basis:100%}
  .reco-tile{flex-basis:100%}
}

/* ── M4E submission workspace ── */
.stage-badge{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;letter-spacing:.02em;padding:5px 12px;border-radius:999px;white-space:nowrap;border:1px solid var(--line)}
.stage-badge.stage-muted{background:transparent;color:var(--muted)}
.stage-badge.stage-blue{background:var(--blue-bg);color:var(--blue);border-color:var(--blue)}
.stage-badge.stage-green{background:var(--green-bg);color:var(--green);border-color:var(--green)}
.stage-badge.stage-yellow{background:var(--yellow-bg);color:var(--yellow);border-color:var(--yellow)}
.stage-badge.stage-red{background:var(--red-bg);color:var(--red);border-color:var(--red)}
.ws-grid{display:grid;grid-template-columns:2fr 1fr;gap:16px;align-items:start}
/* Destination facts. THREE THINGS WERE WRONG HERE and MA|MARLBOROUGH showed all of them at once.
   1. Grid items default to min-width:auto, so a cell holding an unbreakable token — a portal URL, or
      an inbound identity like r-6kym5gwqr2s3@datarequests.archmetrics.com — could not shrink to its
      track and overflowed into its neighbour.
   2. `.ws-facts .v` (0,2,0) outranks the `.wb-break-all` utility (0,1,0), so the class added to those
      very cells to break long addresses never applied. word-break:break-word is also a legacy alias
      that does not break a long unbroken run; overflow-wrap:anywhere does.
   3. `.warn` is a BLOCK callout (padding 11px 14px, margin-bottom 14px). It was being used as an
      inline span inside a 13px fact cell — "unknown — verify" on the portal account row, which is
      exactly the row Marlborough renders — so a padded box sat on top of the rows around it. Inline
      warnings get an inline style; the block callout stays block. */
.ws-facts{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px;font-size:13px}
.ws-facts>div{display:flex;flex-direction:column;gap:2px;min-width:0;padding:6px 0;border-bottom:1px solid var(--line)}
.ws-facts .k{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.ws-facts .v{color:var(--ink);min-width:0;overflow-wrap:anywhere;word-break:normal;line-height:1.5}
.ws-facts .v .muted.small{display:block;margin-top:2px}
.warn-inline{color:#991b1b;background:var(--red-bg);border:1px solid #fecaca;border-radius:5px;
  padding:0 6px;font-size:12px;font-weight:600;display:inline-block;line-height:1.6}
/* A block callout that follows the facts grid must clear it rather than butt against the last row. */
.ws-facts + .warn, .ws-facts + p, .ws-facts + .ws-unresolved{margin-top:12px}
.ws-unresolved{margin-top:10px}
.ws-pb{margin-top:10px;border-top:1px dashed var(--line);padding-top:8px;line-height:1.5}
.ws-tokhist{margin-top:8px}
.cp-row{border:1px solid var(--line);border-radius:8px;padding:8px 10px;margin-bottom:8px}
.cp-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:5px}
.cp-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700}
.cp-val{color:var(--ink);font-size:13px}
.cp-inline{word-break:break-word;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
.cp-pre{white-space:pre-wrap;word-break:break-word;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;background:transparent}
.cp-btn{white-space:nowrap;flex:0 0 auto}
.cp-btn.copied{background:var(--green-bg);color:var(--green);border-color:var(--green)}
.cp-note{margin-top:4px}
@media(max-width:820px){ .ws-grid{grid-template-columns:1fr} .ws-facts{grid-template-columns:1fr} }
/* M4G — direct-email review & send */
.ws-cansend{background:var(--green-bg);border:1px solid var(--green);color:var(--ink);border-radius:9px;padding:10px 13px;font-size:13px}
.ws-manual{background:var(--yellow-bg);border:1px solid var(--yellow);color:var(--ink);border-radius:9px;padding:10px 13px;font-size:13px;margin-bottom:10px}
.ws-emailbody{max-height:200px;overflow:auto;border:1px solid var(--line);border-radius:8px;padding:12px 14px}
.ws-emailedit textarea{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}

/* ── Municipality Research roster ── */
.roster-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}
.roster-chip{display:inline-flex;align-items:center;gap:5px;padding:5px 11px;border:1px solid var(--line);border-radius:999px;font-size:12px;color:var(--ink);background:var(--panel)}
.roster-chip:hover{border-color:var(--blue);text-decoration:none}
.roster-chip.active{background:var(--blue);color:#fff;border-color:var(--blue)}
.roster-chip.active b{color:#fff}
.roster-chip b{color:var(--blue)}
.roster-batch{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 14px}
.roster-tbl th{position:sticky;top:0;z-index:2}
.roster-tbl td{vertical-align:middle}
.roster-tbl tbody tr:hover td{background:var(--blue-bg)}
@media(max-width:820px){ .roster-chip{font-size:11px;padding:4px 9px} }

.row-actions{white-space:nowrap}
.small{font-size:12px}

/* Inline quick-edit controls */
.qf{display:inline;margin:0}
select.q{width:auto;min-width:90px;padding:3px 6px;font-size:12px;border-radius:14px;font-weight:600;cursor:pointer}
select.q.badge-green{color:var(--green);background:var(--green-bg);border-color:#bbf7d0}
select.q.badge-yellow{color:var(--yellow);background:var(--yellow-bg);border-color:#fde68a}
select.q.badge-red{color:var(--red);background:var(--red-bg);border-color:#fecaca}
select.q.badge-blue{color:var(--blue);background:var(--blue-bg);border-color:#bfdbfe}
select.q.badge-grey{color:var(--grey);background:var(--grey-bg)}
input.q-score{width:70px;padding:4px 7px;font-size:13px;text-align:center;font-weight:700}
.qgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}
.qgrid label{display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:600}
.preset-row{display:flex;flex-wrap:wrap;gap:8px}
a.stat{display:block;color:inherit}
a.stat:hover{text-decoration:none;border-color:var(--blue);box-shadow:0 1px 4px rgba(37,99,235,.12)}

/* Tabs */
.tabnav{display:flex;gap:4px;border-bottom:2px solid var(--line);margin-bottom:16px;flex-wrap:wrap}
.tabnav button{background:none;border:none;border-bottom:2px solid transparent;margin-bottom:-2px;
  padding:9px 14px;font-size:13px;font-weight:600;color:var(--muted);cursor:pointer}
.tabnav button:hover{color:var(--ink)}
.tabnav button.active{color:var(--blue);border-bottom-color:var(--blue)}

/* Progress bars */
.pbar{background:var(--grey-bg);border-radius:8px;height:9px;overflow:hidden}
.pbar-fill{height:100%;border-radius:8px}
.pb-green{background:var(--green)} .pb-blue{background:var(--blue)}
.pb-yellow{background:#eab308} .pb-red{background:var(--red)}
.score-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px}
.score-row .lab{font-size:12px;display:flex;justify-content:space-between;margin-bottom:3px}
.bigbadge{font-size:13px;padding:4px 12px}

/* Evidence + timeline */
.evcard{border:1px solid var(--line);border-left:4px solid var(--blue);border-radius:8px;padding:11px 13px;margin-bottom:10px}
.evcard .hd{display:flex;justify-content:space-between;align-items:center;gap:8px}
.evcard .ic{font-size:18px;margin-right:6px}
.timeline{list-style:none;margin:0;padding:0}
.timeline li{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--line)}
.timeline .ic{font-size:15px;width:22px;text-align:center}
.timeline .tx{flex:1}
.timeline .dt{color:var(--muted);font-size:11px;white-space:nowrap}
.warnlist{margin:0;padding-left:18px}
.warnlist li{margin:3px 0}
.section-nav{position:sticky;top:54px;z-index:10}

/* Dark mode */
[data-theme="dark"]{
  --panel:#1e293b;--ink:#e2e8f0;--muted:#94a3b8;--line:#334155;--grey-bg:#334155;--grey:#cbd5e1;
  --green-bg:#14352a;--yellow-bg:#3a2e10;--red-bg:#3a1d1d;--blue-bg:#13294b;
}
[data-theme="dark"] body{background:#0f172a;color:var(--ink)}
[data-theme="dark"] body,[data-theme="dark"] .main{background:#0f172a}
[data-theme="dark"] th{background:#172033}
[data-theme="dark"] tr:hover td{background:#172033}
[data-theme="dark"] input,[data-theme="dark"] select,[data-theme="dark"] textarea{background:#0f172a;color:var(--ink);border-color:var(--line)}
[data-theme="dark"] .searchform input{background:#0f172a}
[data-theme="dark"] .btn{background:#1e293b;color:var(--ink);border-color:var(--line)}
[data-theme="dark"] .stat,[data-theme="dark"] .topbar{background:#1e293b}

/* ═══ CSP-SAFE PRESENTATION LAYER ═══════════════════════════════════════════════════════════════
   Production serves `default-src 'self'` with no style-src and no 'unsafe-inline', which refuses a
   style= attribute exactly as it refuses a <style> block — verified in WebKit, not assumed from the
   spec. Every inline declaration in this application was therefore INERT: the source described a
   layout the browser never applied, and the operator has been looking at the fallback all along.

   These rules restore that intent from external CSS. Names are generated from the VALUES actually
   in use rather than invented, which keeps every migration value-preserving and assertable by
   computed style, and bounds the scale by real usage instead of by a framework's opinion. This is a
   deliberate, closed utility layer — it is not an invitation to add .mt-37 later.

   Genuinely repeated multi-property patterns follow as named components. Single-use bespoke layouts
   were deliberately NOT machine-named; see the stage report for that remaining inventory. */

/* spacing — margin */
.m-0{margin:0}
.m-0-0-10{margin:0 0 10px}
.m-0-0-8{margin:0 0 8px}
.m-10-0{margin:10px 0}
.m-10-0-0{margin:10px 0 0}
.m-10-0-4{margin:10px 0 4px}
.m-10-0-6{margin:10px 0 6px}
.m-12-0{margin:12px 0}
.m-12-0-6{margin:12px 0 6px}
.m-14-0-6{margin:14px 0 6px}
.m-16-0-6{margin:16px 0 6px}
.m-2{margin:2px}
.m-2-0{margin:2px 0}
.m-4-0{margin:4px 0}
.m-4-0-0{margin:4px 0 0}
.m-4-0-0-18{margin:4px 0 0 18px}
.m-4-0-10{margin:4px 0 10px}
.m-6-0{margin:6px 0}
.m-6-0-0{margin:6px 0 0}
.m-6-0-0-16{margin:6px 0 0 16px}
.m-6-0-0-18{margin:6px 0 0 18px}
.m-6-0-10{margin:6px 0 10px}
.m-6-0-14{margin:6px 0 14px}
.m-6-0-8{margin:6px 0 8px}
.m-8-0{margin:8px 0}
.m-8-0-4{margin:8px 0 4px}
.m-8-0-6{margin:8px 0 6px}
.mb-0{margin-bottom:0}
.mb-10{margin-bottom:10px}
.mb-12{margin-bottom:12px}
.mb-14{margin-bottom:14px}
.mb-2{margin-bottom:2px}
.mb-6{margin-bottom:6px}
.mb-8{margin-bottom:8px}
.ml-22{margin-left:22px}
.ml-6{margin-left:6px}
.ml-8{margin-left:8px}
.ml-auto{margin-left:auto}
.mt-0{margin-top:0}
.mt-10{margin-top:10px}
.mt-12{margin-top:12px}
.mt-14{margin-top:14px}
.mt-16{margin-top:16px}
.mt-18{margin-top:18px}
.mt-2{margin-top:2px}
.mt-3{margin-top:3px}
.mt-4{margin-top:4px}
.mt-5{margin-top:5px}
.mt-6{margin-top:6px}
.mt-8{margin-top:8px}

/* spacing — padding */
.p-0{padding:0}
.p-0-18-16{padding:0 18px 16px}
.p-11{padding:11px}
.p-2-0{padding:2px 0}
.p-4-0{padding:4px 0}
.p-8-10{padding:8px 10px}
.p-8-12{padding:8px 12px}
.p-8-14{padding:8px 14px}
.pt-0{padding-top:0}

/* display & flow */
.d-inline{display:inline}
.d-none{display:none}
.ovx-auto{overflow-x:auto}
/* Communication history cards. The rail colour never applied before: the element carried TWO class
   attributes (`class="panel" class="muni-msg rail-blue"`) and a browser keeps only the first, so
   inbound and outbound were visually identical on a request with several attempts. */
.muni-msg{margin-bottom:12px}
.muni-msg-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.muni-msg-addr{display:grid;grid-template-columns:1fr 1fr;gap:2px 18px;margin:6px 0}
.muni-msg-addr>div{display:flex;gap:8px;min-width:0;align-items:baseline}
.muni-msg-addr .k{flex:0 0 auto;min-width:34px;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.muni-msg-addr .v{min-width:0;overflow-wrap:anywhere;color:var(--ink);font-weight:600}
.muni-msg-prov{margin:2px 0 6px;overflow-wrap:anywhere}
@media(max-width:820px){ .muni-msg-addr{grid-template-columns:1fr} }
.m-10-0-8{margin:10px 0 8px}
.m-0-0-12{margin:0 0 12px}
.m-8-0-0{margin:8px 0 0}
.wb-break-all{overflow-wrap:anywhere;word-break:break-all}
.ws-facts .v.m-10-0-8{margin:10px 0 8px}
.m-0-0-12{margin:0 0 12px}
.m-8-0-0{margin:8px 0 0}
.wb-break-all{overflow-wrap:anywhere;word-break:break-all}   /* beat `.ws-facts .v` (0,2,0) */
.ws-nowrap{white-space:nowrap}
/* pre-wrap honours newlines AND refuses to break a long token, so one URL, provider id or payload
   hash in a timeline event pushed the whole page wider than the viewport (measured: 490px of document
   in a 390px window, the event text the widest node on the page). overflow-wrap lets those break while
   the line structure the rule exists for is untouched. */
.ws-pre-wrap{white-space:pre-wrap;overflow-wrap:anywhere;min-width:0}

/* sizing */
.minh-28{min-height:28px}
.minw-120{min-width:120px}
.minw-130{min-width:130px}
.minw-150{min-width:150px}
.minw-200{min-width:200px}
.minw-220{min-width:220px}
.minw-24{min-width:24px}
.mw-110{max-width:110px}
.mw-140{max-width:140px}
.mw-150{max-width:150px}
.mw-220{max-width:220px}
.mw-230{max-width:230px}
.mw-240{max-width:240px}
.mw-420{max-width:420px}
.mw-480{max-width:480px}
.w-100pc{width:100%}
.w-160{width:160px}
.w-180{width:180px}
.w-200{width:200px}
.w-220{width:220px}
.w-26{width:26px}
.w-260{width:260px}
.w-50pc{width:50%}
.w-60pc{width:60%}
.w-70pc{width:70%}
.w-90{width:90px}
.w-p-pctp-pc{width:'.$pct.'%}

/* typography */
.fs-10{font-size:10px}
.fs-11{font-size:11px}
.fs-14{font-size:14px}
.fs-16{font-size:16px}
.fs-22{font-size:22px}
.fs-9{font-size:9px}
.fw-400{font-weight:400}
.fw-bold{font-weight:bold}

/* colour & emphasis */
.bg-fff5f5{background:#fff5f5}
.bg-fffbeb{background:#fffbeb}
.bg-panel2-f6f7f9{background:var(--panel2,#f6f7f9)}
.c-20654a{color:#20654A}
.c-4ade80{color:#4ade80}
.c-8e3520{color:#8E3520}
.c-900{color:#900}
.c-blue{color:var(--blue)}
.c-e2e8f0{color:#e2e8f0}
.c-fbbf24{color:#fbbf24}
.c-fff{color:#fff}
.c-green{color:var(--green)}
.c-green-16a34a{color:var(--green,#16a34a)}
.c-p-colp{color:'.$col.'}
.c-red{color:var(--red)}
.c-yellow{color:var(--yellow)}
.op-p65{opacity:.65}
.op-p7{opacity:.7}

/* layout */
.bl-3-solid-blue{border-left:3px solid var(--blue)}
.bl-3-solid-green{border-left:3px solid var(--green)}
.bl-4-solid-blue{border-left:4px solid var(--blue)}
.bl-4-solid-f59e0b{border-left:4px solid #f59e0b}
.bl-4-solid-green{border-left:4px solid var(--green)}
.bl-4-solid-grey{border-left:4px solid var(--grey)}
.bl-4-solid-red{border-left:4px solid var(--red)}
.cur-pointer{cursor:pointer}
.gc-1-3{grid-column:1/3}
.gtc-1fr-1fr{grid-template-columns:1fr 1fr}
.gtc-1p35fr-1fr{grid-template-columns:1.35fr 1fr}
.gtc-repeat-auto-fill-minmax-130-1fr{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}
.gtc-repeat-auto-fill-minmax-140-1fr{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}
.gtc-repeat-auto-fill-minmax-150-1fr{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.jc-space-between{justify-content:space-between}

/* repeated multi-property patterns, named for what they are */
.grid-2{grid-template-columns:1fr 1fr}
.grid-2-gap14-start{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;margin-top:12px}
.grid-2-start{grid-template-columns:1fr 1fr;align-items:start}
.grid-fill-130{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}
.grid-fill-150{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.mt-10-mb-4{margin:10px 0 4px}
.mt-10-mb-6{margin:10px 0 6px}
.mt-16-mb-6{margin:16px 0 6px}
.mt-8-mb-4{margin:8px 0 4px}
.my-10{margin:10px 0}
.my-10-0{margin:10px 0 0}
.my-12{margin:12px 0}
.my-2{margin:2px 0}
.my-4{margin:4px 0}
.my-6-0{margin:6px 0 0}
.rail-blue{border-left:3px solid var(--blue)}
.rail-blue-4{border-left:4px solid var(--blue)}
.rail-green-4{border-left:4px solid var(--green)}
.rail-grey-4{border-left:4px solid var(--grey)}
.rail-red-4{border-left:4px solid var(--red)}
.row-10{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.row-12-end{display:flex;gap:12px;align-items:end;flex-wrap:wrap}
.row-4{display:flex;gap:4px}
.row-4-wrap-my10{display:flex;gap:4px;flex-wrap:wrap;margin:10px 0}
.row-divided{padding:4px 0;border-bottom:1px solid rgba(148,163,184,.15)}
.tone-bad-soft{background:#fee;color:#900}

/* ── conditional tone rails & state, chosen SERVER-SIDE as a class ─────────────────────────
   The PHP was already branching on tone; it now picks a class instead of interpolating a colour
   token into a style attribute the browser refuses. Same branch, same values, CSP-valid. */
.rail-red{border-left:3px solid var(--red)}
.rail4-red{border-left:4px solid var(--red)}
.rail6-red{border-left:6px solid var(--red)}
.rail-yellow{border-left:3px solid var(--yellow)}
.rail4-yellow{border-left:4px solid var(--yellow)}
.rail6-yellow{border-left:6px solid var(--yellow)}
.rail-green{border-left:3px solid var(--green)}
.rail4-green{border-left:4px solid var(--green)}
.rail6-green{border-left:6px solid var(--green)}
.rail-blue{border-left:3px solid var(--blue)}
.rail4-blue{border-left:4px solid var(--blue)}
.rail6-blue{border-left:6px solid var(--blue)}
.rail-grey{border-left:3px solid var(--grey)}
.rail4-grey{border-left:4px solid var(--grey)}
.rail6-grey{border-left:6px solid var(--grey)}
.rail-muted{border-left:3px solid var(--muted)}
.rail4-muted{border-left:4px solid var(--muted)}
.rail6-muted{border-left:6px solid var(--muted)}
.stat-22{font-size:22px}
.p-12-16{padding:12px 16px}
.tone-confirm-soft{background:rgba(255,200,0,.07)}
.barw-0{width:0%}
.barw-5{width:5%}
.barw-10{width:10%}
.barw-15{width:15%}
.barw-20{width:20%}
.barw-25{width:25%}
.barw-30{width:30%}
.barw-35{width:35%}
.barw-40{width:40%}
.barw-45{width:45%}
.barw-50{width:50%}
.barw-55{width:55%}
.barw-60{width:60%}
.barw-65{width:65%}
.barw-70{width:70%}
.barw-75{width:75%}
.barw-80{width:80%}
.barw-85{width:85%}
.barw-90{width:90%}
.barw-95{width:95%}
.barw-100{width:100%}
.bar-track{height:100%}

/* ── dynamic geometry, as bounded bands (§13) ───────────────────────────────────────────────────
   width:<?=$pct?>% cannot be a fixed class, and style="--w:…" would still be an inline attribute and
   still refused. Five-point bands are CSP-valid and land within 2.5% of the exact value on bars a
   few pixels tall — a difference no operator can see, on rules that currently do not render at all. */

/* ── page-scoped components, migrated from single-use inline layouts ──────────────────── */
/* hub */
.hub-s1{display:block;padding:4px 0;border-bottom:1px solid var(--line,#eee)}
.hub-s2{grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:8px}
/* acq-intel */
.acq-intel-s1{margin:2px}
.acq-intel-s2{padding:6px 0;border-bottom:1px solid rgba(148,163,184,.15)}
.acq-intel-s3{background:#fef9c3;color:#854d0e}
/* acq-municipality */
.acq-municipality-s1{background:#fee;color:#900;vertical-align:middle}
.acq-municipality-s2{background:#fff5f5;color:#900}
.acq-municipality-s3{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:10px 14px}
.acq-municipality-s4{font-size:14px}
.acq-municipality-s5{margin-top:8px;display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.acq-municipality-s6{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
/* The municipal acquisition page's two-column rows never collapsed, so at phone width their cards
   were laid out side by side in 390px and the page scrolled sideways. It was already true before the
   Submission channel card moved into the top row; promoting that card — whose kv table is the widest
   content on the page — widened the overflow from 498px to 619px, which is what made it worth fixing
   now. Same 820px breakpoint and same one-column collapse the other grids on this page already use. */
@media(max-width:820px){ .acq-municipality-s6, .grid-2-gap14-start{grid-template-columns:1fr} }
/* The proposed-path ruling: one decision, at the top, with its caveat visible but not burying it. */
.cp-rule{margin-bottom:14px}
.cp-rule-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.cp-rule-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}
.acq-municipality-s7{margin-top:12px;border-top:1px solid rgba(148,163,184,.2);padding-top:10px}
.acq-municipality-s8{max-height:360px;overflow:auto}
/* acq-research */
.acq-research-s1{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
.acq-research-s2{font-size:1.05em;padding:6px 12px}
.acq-research-s3{grid-template-columns:repeat(auto-fill,minmax(170px,1fr));margin:8px 0}
.acq-research-s4{border-left:4px solid var(--green);background:rgba(0,160,80,.05)}
.acq-research-s5{margin-top:8px;display:flex;gap:8px;flex-wrap:wrap}
.acq-research-s6{margin:8px 0;gap:8px;flex-wrap:wrap}
.acq-research-s7{margin:6px 0;gap:8px;flex-wrap:wrap}
.acq-research-s8{margin-top:8px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.acq-research-s9{grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:10px}
/* The research queue's two count populations, side by side and separately labelled: the worker's
   backlog on the left, the operator's review load on the right. They were one undifferentiated strip,
   which is how "Nothing is queued." came to sit above dozens of rows demanding a ruling. */
.rob-countbar{display:grid;grid-template-columns:minmax(220px,1fr) minmax(340px,2fr);gap:16px;align-items:start}
.rob-countgroup-h{text-transform:uppercase;letter-spacing:.06em;font-size:11px;margin:0 0 4px}
.rob-countgroup .acq-research-s9{grid-template-columns:repeat(3,1fr)}
@media (max-width:820px){.rob-countbar{grid-template-columns:1fr}}
/* acq-submission */
.acq-submission-s1{word-break:break-all;font-family:ui-monospace,monospace}
.acq-submission-s2{margin-top:6px;padding-left:10px;border-left:2px solid #C5CAC8}
.acq-submission-s3{max-height:160px;overflow:auto}
.acq-submission-s4{display:grid;grid-template-columns:1fr 1fr;gap:12px}
/* acquisition */
.acquisition-s1{margin-bottom:14px;border-left:4px solid var(--red)}
.acquisition-s2{background:#fee;color:#900;margin:2px}
/* admin */
.admin-s1{max-width:150px}
/* comm-match — evidence beside the decision, and the decision is not a sidebar.
   `1.1fr 1fr` never applied: a grid item's default min-width is auto, so the evidence panel's
   min-content floor — a 70-char RFC Message-ID in the kv table, 60-char attachment filenames plus
   their "archived · sha …" status — pinned the left track at 657px whatever the fraction said, and
   the operator form got only the leftovers (487px at a 1440 viewport, 348px at 1280, and less again
   once the sidebar is out). minmax(0,…) removes the floor; the table rules below are what stop the
   content from pushing straight back through it. */
.comm-match-s1{grid-template-columns:minmax(0,1fr);align-items:start}
.comm-match-s1>*{min-width:0}
/* Two columns only where both can be comfortable: the decision panel keeps a hard 26rem floor so it
   can never degrade into a strip again, and below this width the workspace simply sits underneath
   the evidence at full width. */
@media(min-width:1180px){
  .comm-match-s1{grid-template-columns:minmax(0,1.5fr) minmax(26rem,1fr)}
}

/* EVIDENCE TABLES: fixed layout, declared proportions. Auto layout sizes columns from content, which
   is what let one 60-character filename decide the whole page — and simply letting long tokens break
   made it worse, because a column whose min-content is one character gets starved to nothing and
   "References found" came back as "Referenc es found". Naming the proportions settles both. */
.comm-match-s1 table{width:100%;table-layout:fixed}
/* break-word, not anywhere: fixed layout already guarantees the table fits, so a token is broken
   only when it genuinely cannot — "application/zip" and "18,442,133 B" stay whole, the Message-ID
   and the filename wrap. */
.comm-match-s1 th,.comm-match-s1 td{white-space:normal;overflow-wrap:break-word}
.comm-match-s1 .kv td:first-child{width:30%}
/* the attachments table: #, filename, type, size, archive status */
.comm-match-s1 table:not(.kv) th:nth-child(1){width:5%}
.comm-match-s1 table:not(.kv) th:nth-child(2){width:36%}
.comm-match-s1 table:not(.kv) th:nth-child(3){width:18%}
.comm-match-s1 table:not(.kv) th:nth-child(4){width:15%}

/* DECISION PANEL: give the controls room to breathe. Label, control and explanatory sentence ran
   together with no vertical rhythm at all, so classification, attachment disposition, reference and
   rationale read as one paragraph of instructions rather than four things to do. */
.comm-match-s1 .field{margin:14px 0 0}
.comm-match-s1 .field>.muted{margin-top:5px;line-height:1.5}
.comm-match-s1 select,.comm-match-s1 input[type=text],.comm-match-s1 input:not([type]),
.comm-match-s1 textarea{width:100%}
.comm-match-s1 textarea{min-height:78px}
.comm-match-s1 .match-option{display:block;margin-bottom:8px;line-height:1.45}
.comm-match-s1 .match-option .muted{margin-top:4px}
.comm-match-s2{white-space:pre-wrap;max-height:340px;overflow:auto;background:var(--panel2,#f6f7f9);padding:8px;border-radius:6px}
/* communications */
.communications-s1{color:var(--yellow)}
.communications-s2{margin-top:14px;border-left:3px solid var(--yellow)}
.communications-s3{margin-top:14px;border-left:3px solid var(--red)}
/* contact-queue */
.contact-queue-s1{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap}
.contact-queue-s2{margin-top:8px}
.contact-queue-s3{align-self:end}
.contact-queue-s4{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
/* coverage */
.coverage-s1{min-width:130px}
/* dashboard */
.dashboard-s1{margin-top:2px;opacity:.75}
.dashboard-s2{vertical-align:middle;margin-left:8px}
.dashboard-s3{background:linear-gradient(135deg,#0f172a,#1e293b);color:#e2e8f0;border:none}
.dashboard-s4{display:flex;justify-content:space-between;flex-wrap:wrap;gap:14px;align-items:flex-start}
.dashboard-s5{flex:1;min-width:260px}
.dashboard-s6{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#94a3b8}
.dashboard-s7{font-size:19px;font-weight:700;margin:3px 0 6px}
.dashboard-s8{color:#cbd5e1;font-size:13px}
.dashboard-s9{color:#94a3b8;font-size:12px;margin-top:8px}
.dashboard-s10{color:#94a3b8;font-size:12px;margin-top:3px}
.dashboard-s11{font-size:11px;color:#cbd5e1;display:flex;justify-content:space-between;margin:0 0 3px}
.dashboard-s12{background:#334155;border-radius:6px;height:7px;overflow:hidden;margin-bottom:9px}
.dashboard-s13{display:flex;gap:11px;align-items:flex-start;padding:10px 0;border-bottom:1px solid var(--line)}
.dashboard-s14{font-size:18px;line-height:1.4}
.dashboard-s15{flex:1;min-width:0}
.dashboard-s16{margin-left:6px}
.dashboard-s17{grid-template-columns:repeat(3,1fr);gap:8px}
.dashboard-s18{font-size:22px;color:var(--green)}
.dashboard-s19{font-size:22px;color:var(--yellow)}
.dashboard-s20{display:flex;gap:0;flex-wrap:wrap;align-items:stretch}
.dashboard-s21{font-size:24px;font-weight:700}
.dashboard-s22{cursor:pointer;padding:14px 18px;font-weight:600;color:var(--grey)}
/* data-request */
.data-request-s1{display:flex;flex-wrap:wrap;align-items:center;gap:10px;justify-content:space-between}
.data-request-s2{margin-top:6px;flex-wrap:wrap;gap:4px}
.data-request-s3{color:var(--red);font-weight:600}
.data-request-s4{margin-top:8px;padding-top:8px;border-top:1px solid var(--border,#e5e7eb)}
.data-request-s5{margin-top:6px;gap:4px;flex-wrap:wrap}
/* THE REQUEST PAGE'S MAIN/ASIDE SPLIT.
   `1.3fr 1fr` is really `minmax(auto,1.3fr) minmax(auto,1fr)`, and a grid track's `auto` minimum is its
   MIN-CONTENT. The main column holds forms whose <input>/<textarea> carry a ~20-character intrinsic
   size, giving it a 534px floor it will not go below — so as the viewport narrows the main track keeps
   534px and the aside takes whatever is left: measured on the live Hudson request at 1440/1024/900,
   the aside went 497px (44%) → 192px (26%) → 145px (21%), and below ~950px the page overflowed
   horizontally (951px of document in a 900px viewport) because the tracks together no longer fit.
   .
   minmax(0,…) releases that floor so the main column may shrink; minmax(320px,…) gives the aside a
   real minimum so Tracking / Add note / Log a reply stay usable. And the grid gets the stacking rule
   it never had — .ws-grid has had one since it was written, this one was simply missing. */
.data-request-s6{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(320px,1fr);gap:16px;margin-top:14px}
.data-request-s6>*{min-width:0}
.data-request-s6 input,.data-request-s6 select,.data-request-s6 textarea{min-width:0}
@media(max-width:1000px){ .data-request-s6{grid-template-columns:1fr} }
.data-request-s7{font-family:monospace;font-size:12px}
.data-request-s8{margin-top:8px;list-style:none;padding:0}
/* A timeline row carries provider ids, URLs and raw message text — tokens with no break opportunity.
   Without this the row sets the page's width instead of the page setting the row's. */
.data-request-s9{padding:6px 0;border-bottom:1px solid var(--border,#e5e7eb);min-width:0;overflow-wrap:anywhere;word-break:break-word}
.data-request-s9>*{min-width:0;max-width:100%}
/* data-requests */
.data-requests-s1{margin:2px}
/* deadline */
.deadline-s1{font-weight:bold}
/* import-workspace */
.import-workspace-s1{grid-template-columns:repeat(auto-fill,minmax(130px,1fr));margin-top:8px}
/* imports */
.imports-s1{display:flex;gap:8px;align-items:end;flex-wrap:wrap}
.imports-s2{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
.imports-s3{display:flex;gap:6px;align-items:center;margin:4px 0 10px}
.imports-s4{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}
.imports-s5{max-width:180px;overflow:hidden;text-overflow:ellipsis}
/* municipalities */
.municipalities-s1{flex-wrap:wrap;gap:6px;margin:8px 0}
.municipalities-s2{display:inline-flex;gap:4px}
.municipalities-s3{display:flex;gap:5px;flex-wrap:wrap;margin:6px 0}
.municipalities-s4{display:flex;gap:5px;flex-wrap:wrap;margin:0 0 8px}
.municipalities-s5{max-height:160px;overflow:auto;margin:6px 0}
.municipalities-s6{display:inline-block;width:220px}

/* ── THE PIPELINE TABLE STAYS USABLE AS THE WINDOW NARROWS ─────────────────────────────────────
   `table{width:100%}` cannot shrink a table below its MIN-CONTENT width, and `.panel` contained no
   overflow — so measured against production data the table held a 921px floor while the panel fell
   to 742px at a 1024 viewport and 618px at 900. The surplus left the page: document scrollWidth
   1188 inside a 1024 window, with all 60 "Next action" buttons past the right edge (right=1178).
   Most of that floor is the Flags column, which would not go under 243px because `.badge` is
   nowrap and rows carry several. Below 820 a different mechanism hid the same defect: the mobile
   `table{display:block;overflow-x:auto;white-space:nowrap}` made the table its own scroller and
   `body{overflow-x:hidden}` swallowed the page overflow — the CTA then sat at right=1487 in a 768
   window, reachable only by scrolling sideways to look for it.

   The action column is the reason the operator opened this page, so it is pinned and floored; the
   middle columns drop out in priority order, lowest value first (Impact → Vendor/path → Last
   movement → Owner), each of them metadata the town's own page still carries. Municipality, Stage
   and the CTA are never dropped. */
.mun-tablewrap{overflow-x:auto}
.mun-table{width:100%;display:table;white-space:normal}   /* outrank the ≤820 `table{display:block…}` */
.mun-table .mun-c-name{min-width:140px}
.mun-table .mun-c-act{min-width:150px}     /* a CTA squeezed to one word per line is not legible */
.mun-table .mun-c-act .btn{white-space:normal;text-align:left;max-width:210px}
/* Pinned CTA. Opaque and stacked above the cells that scroll under it, painted to match the row it
   belongs to — base, hover and dark — so nothing can show through. Explicit column classes, not
   :last-child, because the empty-state row's colspan cell is also a last child and must not pin. */
.mun-table th.mun-c-act,.mun-table td.mun-c-act{position:sticky;right:0;border-left:1px solid var(--line)}
.mun-table td.mun-c-act{background:#fff;z-index:2}
.mun-table th.mun-c-act{background:#f8fafc;z-index:3}
.mun-table tr:hover td.mun-c-act{background:#fafcff}
[data-theme="dark"] .mun-table th.mun-c-act{background:#172033}
[data-theme="dark"] .mun-table tr:hover td.mun-c-act{background:#172033}
@media(max-width:1339px){
  .mun-table .mun-c-impact{display:none}
  .mun-table .mun-c-flags .badge{white-space:normal}   /* flags compress before anything is dropped */
}
@media(max-width:1199px){ .mun-table .mun-c-path{display:none} }
@media(max-width:1079px){ .mun-table .mun-c-move{display:none} }
@media(max-width:959px){ .mun-table .mun-c-owner{display:none} }
/* municipality */
.municipality-s1{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px}
.municipality-s2{font-size:14px}
.municipality-s3{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:8px}
.municipality-s4{grid-template-columns:1.2fr 1fr;align-items:start}
.municipality-s5{white-space:pre-wrap;max-height:260px;overflow:auto}
.municipality-s6{display:flex;gap:14px;flex-wrap:wrap;align-items:baseline}
.municipality-s7{white-space:pre-wrap;max-height:280px;overflow:auto}
.municipality-s8{margin-top:6px;padding:6px 8px;background:var(--panel2,rgba(0,0,0,.03));border-radius:4px}
.municipality-s9{margin-top:8px;padding:8px 10px;border-left:3px solid var(--yellow)}
.municipality-s10{padding:5px 0;border-bottom:1px solid var(--line)}
.municipality-s11{border-left:4px solid var(--green);padding:8px 12px}
.municipality-s12{padding:6px 0;border-bottom:1px solid var(--line)}
/* parcel-detail */
.parcel-detail-s1{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.parcel-detail-s2{white-space:pre-wrap;overflow:auto;max-height:200px}
/* The unit list spans the full grid: one Haverhill parcel carries 439 assessment units, so it is
   the widest thing on the page and must scroll inside itself rather than widening the document. */
.parcel-detail-units{grid-column:1 / -1}
.parcel-detail-s3{overflow-x:auto;max-height:520px;overflow-y:auto}
.parcel-detail-s3 table{width:100%}
.parcel-detail-s3 thead th{position:sticky;top:0;background:var(--panel,#fff);z-index:1}
/* playbooks */
.playbooks-s1{min-width:120px}
.playbooks-s2{margin-top:10px;max-width:320px}
/* providers */
.providers-s1{max-width:240px}
.providers-s2{margin:0;gap:6px}
/* request-ops */
.request-ops-s1{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0}
.request-ops-s2{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 10px}
/* search */
.search-s1{max-width:420px}
/* sources */
.sources-s1{max-width:240px}
/* today */
.today-s1{margin:8px 0;flex-wrap:wrap;gap:6px}
.today-s2{grid-template-columns:repeat(auto-fill,minmax(128px,1fr));margin:8px 0}
.today-s3{border-left:4px solid var(--red);padding:8px 14px}

/* ── final tail: conditional states and the last bespoke components ───────────────────────────── */
.tone-review-soft{background:#fffbeb}
.tone-conflict-soft{background:#fff5f5}
.row-current{outline:2px solid var(--blue)}
.fw-bold{font-weight:bold}
.c-overdue{color:#dc2626}
.c-muted{color:var(--muted,#999)}
.badge-stage-lg{font-size:1.05em;padding:6px 12px}
.match-option{display:block;padding:8px 10px;margin-bottom:6px}
.imports-band{padding:8px 14px;margin-bottom:6px}
.muni-msg{padding:8px 12px;margin-bottom:6px}
.muni-outcome{padding:10px 12px;margin-bottom:10px}
.geo-stage{flex:1;min-width:96px;text-align:center;padding:4px 6px}
.geo-stage-div{border-right:1px solid var(--line)}
/* The readiness bar paints a share of the tile; the band class supplies the stop position. */
/* Readiness tile fill. A gradient stop cannot come from a width, and a CSS variable cannot be
   set without an inline style attribute — which is the very thing being removed. So the band
   class carries the stop directly. */
.ready-fill.barw-0{background:linear-gradient(90deg,rgba(34,197,94,.15) 0%,transparent 0%)}
.ready-fill.barw-5{background:linear-gradient(90deg,rgba(34,197,94,.15) 5%,transparent 5%)}
.ready-fill.barw-10{background:linear-gradient(90deg,rgba(34,197,94,.15) 10%,transparent 10%)}
.ready-fill.barw-15{background:linear-gradient(90deg,rgba(34,197,94,.15) 15%,transparent 15%)}
.ready-fill.barw-20{background:linear-gradient(90deg,rgba(34,197,94,.15) 20%,transparent 20%)}
.ready-fill.barw-25{background:linear-gradient(90deg,rgba(34,197,94,.15) 25%,transparent 25%)}
.ready-fill.barw-30{background:linear-gradient(90deg,rgba(34,197,94,.15) 30%,transparent 30%)}
.ready-fill.barw-35{background:linear-gradient(90deg,rgba(34,197,94,.15) 35%,transparent 35%)}
.ready-fill.barw-40{background:linear-gradient(90deg,rgba(34,197,94,.15) 40%,transparent 40%)}
.ready-fill.barw-45{background:linear-gradient(90deg,rgba(34,197,94,.15) 45%,transparent 45%)}
.ready-fill.barw-50{background:linear-gradient(90deg,rgba(34,197,94,.15) 50%,transparent 50%)}
.ready-fill.barw-55{background:linear-gradient(90deg,rgba(34,197,94,.15) 55%,transparent 55%)}
.ready-fill.barw-60{background:linear-gradient(90deg,rgba(34,197,94,.15) 60%,transparent 60%)}
.ready-fill.barw-65{background:linear-gradient(90deg,rgba(34,197,94,.15) 65%,transparent 65%)}
.ready-fill.barw-70{background:linear-gradient(90deg,rgba(34,197,94,.15) 70%,transparent 70%)}
.ready-fill.barw-75{background:linear-gradient(90deg,rgba(34,197,94,.15) 75%,transparent 75%)}
.ready-fill.barw-80{background:linear-gradient(90deg,rgba(34,197,94,.15) 80%,transparent 80%)}
.ready-fill.barw-85{background:linear-gradient(90deg,rgba(34,197,94,.15) 85%,transparent 85%)}
.ready-fill.barw-90{background:linear-gradient(90deg,rgba(34,197,94,.15) 90%,transparent 90%)}
.ready-fill.barw-95{background:linear-gradient(90deg,rgba(34,197,94,.15) 95%,transparent 95%)}
.ready-fill.barw-100{background:linear-gradient(90deg,rgba(34,197,94,.15) 100%,transparent 100%)}
.today-card{padding:10px 14px;margin-bottom:8px}
/* Weight/opacity utilities that only ever appeared inside multi-property or conditional
   declarations, so the value-derived generator never minted them on their own. */
.fw-600{font-weight:600}
.op-p45{opacity:.45}

/* State and affordances that used to be written by JS as inline styles. CSP permits CSSOM
   assignment, so these were never violations — but presentation belongs here. */
.is-hidden{display:none}
table.sortable th{cursor:pointer}
.collapse-toggle{cursor:pointer}
.cp-offscreen{position:fixed;left:-9999px}
.pbar{width:400px}

/* ── Submission Workspace: decision-first layout ──────────────────────────────────────────────
   The page answers "what do I decide right now?" before it shows any artifact. These carry the
   hierarchy that used to be implied by section order alone. */
.ws-decision{padding:0;overflow:hidden}
.ws-decision-head{border-bottom:1px solid var(--line)}
.ws-nextline{margin:0;padding:11px 18px;font-size:15px;line-height:1.55;color:var(--ink);max-width:78ch}
.ws-backlink{margin:0 0 8px}

/* The blocked mode: one prerequisite, stated plainly, with the control that clears it. */
.ws-blocker{border-left:4px solid var(--red)}
.ws-blocker h3{color:var(--red)}
.ws-blocker-cta{display:flex;flex-wrap:wrap;align-items:center;gap:10px 12px;margin:14px 0 0}
.ws-blocker-pkg{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}

/* The actionable mode: numbered steps so the primary action is unmistakable. */
.ws-act{border-left:4px solid var(--blue)}
.ws-act-step{display:flex;gap:14px;align-items:flex-start;padding:12px 0;border-top:1px solid var(--line)}
.ws-act-step:first-of-type{border-top:0}
.ws-act-n{flex:0 0 auto;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
          color:var(--muted);background:var(--grey-bg);border-radius:999px;padding:5px 11px;white-space:nowrap}
.ws-act-b{flex:1 1 auto;min-width:0}
.m-0-0-16{margin:0 0 16px}
@media(max-width:820px){ .ws-act-step{flex-direction:column;gap:8px} }

/* ── Path review: the acquisition decision, rendered above the municipal dashboard ─────────────
   The operator arrives here with one job. It used to sit below a full master record and an
   evidence ledger dozens of rows long, expressed as a single "Verify" button that could only say
   yes. These carry the decision hierarchy: what we think, what you must determine, what you found. */
.pr-box{border-left:4px solid var(--yellow)}
.pr-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px 12px;margin-bottom:12px}
.pr-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}
.pr-col{min-width:0}
.pr-out{border:1px solid var(--line);border-radius:9px;padding:10px 13px;margin-top:9px}
.pr-out>summary{cursor:pointer;font-size:14px;color:var(--ink);list-style:none;display:flex;align-items:center;gap:7px}
.pr-out>summary::-webkit-details-marker{display:none}
.pr-out>summary::before{content:"▸";font-size:10px;color:var(--muted)}
.pr-out[open]>summary::before{content:"▾"}
.pr-out[open]>summary{margin-bottom:6px;padding-bottom:7px;border-bottom:1px solid var(--line)}
.pr-out .field{margin-bottom:8px}
.pr-fill{margin:2px 4px 2px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.m-10-0{margin:10px 0}
.mt-14{margin-top:14px}
@media(max-width:820px){ .pr-grid{grid-template-columns:1fr} }
.pr-fallback{border-top:1px dashed var(--line);padding-top:8px;line-height:1.55}
.pr-fb{display:inline-block;border:1px solid var(--line);border-radius:6px;padding:1px 7px;margin:2px 4px 2px 0}

/* ── Research conclusion (Review AI Findings, Level 1) ─────────────────────────────────────────
   The decision-first half of pages/acq_research.php. Everything below it in the page is evidence. */
.rc-panel{padding:0;overflow:hidden;border-left:4px solid var(--blue)}
.rc-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:14px 18px;border-bottom:1px solid var(--line)}
.rc-h3{margin:0;font-size:16px}
.rc-links{display:flex;flex-wrap:wrap;gap:12px;padding:16px 18px 2px}
.rc-link{flex:1 1 280px;min-width:0;border:1px solid var(--line);border-radius:9px;padding:11px 13px}
.rc-link-l{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:4px}
.rc-link-v{font-size:15px;font-weight:600;line-height:1.35;word-break:break-all}
.rc-link-n{margin-top:4px}
.rc-section{padding:16px 18px 0}
.rc-headline{margin:0 0 8px;font-size:15px;font-weight:600;color:var(--ink);line-height:1.45;max-width:74ch}
.rc-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:16px 18px 4px}
.rc-cta{font-size:15px;padding:9px 18px}
.rc-foot{padding:12px 18px 16px;margin:10px 0 0;max-width:88ch}
.ml-6{margin-left:6px}
@media (max-width:700px){.rc-link{flex-basis:100%}.rc-cta{width:100%;text-align:center}}
.rc-shortlist{margin-top:4px}
.rc-shortlist .rc-top td{background:var(--blue-bg)}
/* Sibling mailboxes of the same office: subordinate to their row, still separately committable. */
.rc-shortlist .rc-siblings td{border-top:0;padding-top:0;padding-left:22px}
.rc-sibling{display:inline-flex;align-items:center;gap:6px;margin-right:14px}
.rc-consequence{margin:8px 18px 0;padding:9px 12px;border:1px solid var(--line);border-left:3px solid var(--blue);border-radius:7px;max-width:88ch}

/* ── Outreach Queue (pages/outreach.php) ─────────────────────────────────────────────────────── */
.oq-board{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;font-size:13px}
.oq-card{padding:12px 14px}
.oq-card.oq-focus{border-color:var(--blue);box-shadow:0 0 0 2px var(--blue-bg)}
.oq-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px;flex-wrap:wrap}
.oq-name{text-decoration:none}
.oq-why{margin-top:3px}
.oq-send{margin-top:8px}
.oq-cand{display:flex;gap:9px;align-items:flex-start;padding:7px 10px;border:1px solid var(--line);
         border-radius:8px;margin-top:6px;cursor:pointer}
.oq-cand:hover{background:var(--panel-alt,#f8fafc)}
/* THE RADIO IS A CONTROL, NOT A FIELD. The global `input,select,textarea{…width:100%}` above is written
   for the full-width text inputs of an edit form; applied to a radio inside a flex row it becomes a
   flex item ~550-740px wide that shoves the recipient text into whatever is left. Measured on the live
   queue: Watertown's five radios were 742/620/630/630/630px against a 1128px row, so the description
   collapsed into a ~360-478px column and each choice grew to 91-109px tall. Nothing was wrong with the
   markup; one inherited declaration was sizing the wrong kind of input. */
.oq-cand input[type="radio"]{flex:0 0 auto;width:auto;min-width:0;padding:0;margin:2px 0 0;
                             border:0;border-radius:0;background:none}
/* …and the description takes the row that frees up. flex:1 with min-width:0 so a long address or URL
   wraps inside the column instead of setting a floor under its width. */
.oq-cand-body{display:flex;flex-direction:column;gap:1px;flex:1 1 auto;min-width:0;
              overflow-wrap:anywhere;line-height:1.35}
.oq-cand-body>b{font-size:13px}
/* Which option is selected has to be legible at a glance in a queue meant for speed. */
.oq-cand:has(input[type="radio"]:checked){border-color:var(--blue);background:var(--blue-bg)}
.oq-hidden{display:none}
.oq-rejected{margin-top:5px;padding-left:8px;border-left:2px solid var(--line)}
.oq-kv{display:flex;gap:12px;padding:4px 0;border-bottom:1px solid var(--line);font-size:13px}
.oq-kv:last-child{border-bottom:none}
.oq-kv .k{flex:0 0 118px;color:var(--muted)}
.oq-kv .v{flex:1 1 auto;min-width:0;word-break:break-word}
.oq-body{white-space:pre-wrap;word-break:break-word;font-size:12.5px;line-height:1.5;
         background:var(--panel-alt,#f8fafc);border:1px solid var(--line);border-radius:8px;
         padding:10px 12px;max-height:420px;overflow:auto;margin:6px 0 0}
.oq-bad{color:var(--red)}
.oq-stop{color:var(--red);font-size:13px}
.oq-warn{color:var(--yellow);margin-top:4px}

/* ── Disposition console (partials/disposition_console.php) ──────────────────────────────────── */
.dz{padding:14px 16px}
.dz-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.dz-site{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;padding:9px 11px;margin-bottom:12px;
         border:1px solid var(--blue);background:var(--blue-bg);border-radius:8px}
.dz-site-l{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.dz-site-a{font-size:15px;font-weight:700;text-decoration:none}
.dz-site-a:hover{text-decoration:underline}
.dz-sent{font-size:13px;color:var(--yellow);border-left:3px solid var(--yellow);padding-left:10px;margin:0 0 12px}
.dz-opts{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:8px}
.dz-opt{display:flex;gap:9px;align-items:flex-start;padding:9px 11px;border:1px solid var(--line);
        border-radius:8px;cursor:pointer}
.dz-opt:hover{background:var(--panel-alt,#f8fafc)}
.dz-opt input[type="radio"]{flex:0 0 auto;width:auto;min-width:0;padding:0;margin:2px 0 0;border:0;
                            border-radius:0;background:none}
.dz-opt-b{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere;line-height:1.35}
.dz-opts:has(#dz-email:checked) .dz-opt[for="dz-email"],
.dz-opts:has(#dz-portal:checked) .dz-opt[for="dz-portal"],
.dz-opts:has(#dz-bulk:checked) .dz-opt[for="dz-bulk"],
.dz-opts:has(#dz-lookup:checked) .dz-opt[for="dz-lookup"],
.dz-opts:has(#dz-none:checked) .dz-opt[for="dz-none"]{border-color:var(--blue);background:var(--blue-bg)}
/* Only the chosen outcome's controls. Without :has() every panel shows — disclosure, not a guard. */
.dz:has(input[name="dz_outcome"]:checked) .dz-panel{display:none}
.dz:has(#dz-email:checked)  .dz-p-email,
.dz:has(#dz-portal:checked) .dz-p-portal,
.dz:has(#dz-bulk:checked)   .dz-p-bulk,
.dz:has(#dz-lookup:checked) .dz-p-lookup,
.dz:has(#dz-none:checked)   .dz-p-none{display:block}
.dz-panel{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.dz-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-bottom:10px}
.dz-fields .dz-wide{grid-column:1/-1}
.dz-warn{color:var(--yellow);border-left:3px solid var(--yellow);padding-left:10px;margin:0 0 10px}
.dz-pkg{margin-top:14px;padding:11px 12px;border:1px solid var(--line);border-radius:8px;
        background:var(--panel-alt,#f8fafc)}
.dz-pkg-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.dz-next{margin-top:14px;padding-top:11px;border-top:1px solid var(--line);display:flex;gap:10px;
         align-items:center;flex-wrap:wrap}

/* ── Municipalities: why the table is empty ─────────────────────────────────────────────────── */
.mun-explain{padding:10px 12px;border-left:3px solid var(--blue);background:var(--blue-bg);
             border-radius:0 8px 8px 0;font-size:13px}
.mun-explain + .mun-explain{margin-top:10px}

/* ── Owner review: batch selection ──────────────────────────────────────────────────────────────
   The batch shipped with the browser's default checkbox, which at this table's font size renders as
   a ~13px dot. An operator scanning eight rows to decide which ones to EXCLUDE could not see what
   was selected, which is most of the point of reviewing a batch rather than clicking eight cards.

   Everything here is presentation. The checkbox is a real <input type="checkbox"> — enlarged, not
   replaced — so the native control, its keyboard behaviour and its accessible name are untouched,
   and the form posts the same exact ids whether or not this stylesheet or app.js ever loads. */
.ob-check{width:20px;height:20px;cursor:pointer;accent-color:var(--blue);vertical-align:middle}
/* the whole cell is the target, so the click does not have to land on 20 square pixels */
.ob-check-cell{padding:6px 10px;cursor:pointer}
.ob-row{cursor:pointer}
.ob-row:hover{background:var(--grey-bg)}
/* Selected state. Deliberately quiet, and deliberately NOT carried by colour alone: the checkbox
   stays the primary indicator, the tint and the left rule are the scannable secondary. */
.ob-row.is-selected{background:var(--blue-bg)}
.ob-row.is-selected > td:first-child{box-shadow:inset 3px 0 0 0 var(--blue)}
.ob-row td{padding-top:7px;padding-bottom:7px}
/* the card link must stay a link, not a bigger click target for the row toggle */
.ob-row a{position:relative;z-index:1}
.ob-tools{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0}
.ob-count{font-variant-numeric:tabular-nums}
/* a name for screen readers where the visible column header is deliberately blank */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
         clip:rect(0 0 0 0);white-space:nowrap;border:0}
