/* app.css — PanelDB design system.
 *
 * Token palette from the design proposal: warm stone ground, OU crimson as
 * the single brand/interactive accent (EM also uses it as its panel hue),
 * slate blue for FM. Status colors (good/warn/bad) are semantic and
 * deliberately separate from the accent — panel hue answers "which panel?",
 * semantic color answers "what state?".
 *
 * Light + dark are both first-class: tokens are redefined per scheme, and
 * components only ever reference tokens. No frameworks, no build step.
 */

:root{
  --paper:#FAF8F5;
  --card:#FFFFFF;
  --ink:#2A2320;
  --muted:#6F655E;
  --line:#E6DFD7;
  --line-soft:#EFE9E2;
  --crimson:#8A1A1C;            /* BRAND accent only — links, buttons, focus,
                                   active nav. Not a panel identity. */
  --crimson-contrast:#FFFFFF;
  --fm:#2E6E8E;                 /* slate blue */
  /* Panel identity is its own pair of tokens, deliberately separate from the
     brand accent: crimson is "this is interactive", a panel hue is "this is
     which panel". Red reads as FIRE, so FM takes it (Matt, 2026-09-15 — the
     first pass had these the other way round). */
  --panel-em:var(--fm);         /* Emergency Managers — slate blue */
  --panel-fm:var(--crimson);    /* Fire Managers — red */
  --good:#336840;            /* darkened 2026-09-15: chip-good text was 4.38:1 on
                                 its own background */
  --warn:#8A5D09;            /* darkened 2026-09-15: the old #9C6A0B was 3.89:1 on
                                 line-soft and dropped below 4.5:1 on the panel tints */
  --bad:#B04130;             /* nudged 2026-09-15: was 4.48:1 on line-soft */
  --chip-alpha:12%;
  /* Panel tint: a wash of the panel hue behind its box on the contact page, so
     EM and FM are distinguishable at a glance. Kept deliberately weak — every
     ratio inside the box was measured against the resulting colour, and the
     tint is never the ONLY cue (stripe, panel name and chip all still say
     which panel it is). */
  --panel-tint:5%;
  --shadow:0 1px 2px rgba(42,35,32,.06),0 4px 14px rgba(42,35,32,.05);
  --topbar-h:53px;              /* measured height of .topbar — sticky offsets
                                   below it should reference this, not a guess */
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,Roboto,sans-serif;
  --mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
}
@media (prefers-color-scheme: dark){
  :root{
    --paper:#191512; --card:#221C18; --ink:#EAE3DC; --muted:#A69C93;
    --line:#39312A; --line-soft:#2E2721;
    /* dark crimson lightened 2026-09-15: chip-em text was 4.47:1 on its own
       background. Lightening also raises link and primary-button contrast. */
    --crimson:#E39A95; --crimson-contrast:#2A0F0F; --fm:#82B8D6;
    /* the aliases re-resolve per theme; no dark-mode overrides needed */
    --good:#8FC79C; --warn:#D9A946; --bad:#DE8B77; --chip-alpha:18%;
    --shadow:0 1px 2px rgba(0,0,0,.35),0 4px 14px rgba(0,0,0,.25);
    /* Dark needs more of the hue to read as a tint at all. */
    --panel-tint:12%;
  }
}

*{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--sans); font-size:14px; line-height:1.5;
}
a{color:var(--crimson); text-decoration:none;}
a:hover{text-decoration:underline;}
h2{font-size:20px; margin:0 0 4px; line-height:1.2;}
hr{border:none; border-top:1px solid var(--line-soft); margin:6px 0;}
code{font-family:var(--mono); font-size:.9em; background:var(--line-soft); padding:.1em .4em; border-radius:4px;}
.muted{color:var(--muted);}
.small{font-size:12px;}
.empty{color:var(--muted); padding:18px; text-align:center;}
/* Contacts search: non-active people the search also matched (2026-09-24).
   Muted label, full-contrast links — the names are what you act on. */
.elsewhere{margin:6px 0 16px; padding:0 4px; color:var(--muted);}
:focus-visible{outline:2px solid var(--crimson); outline-offset:2px; border-radius:2px;}
@media (prefers-reduced-motion: reduce){*{animation:none!important; transition:none!important;}}

/* ---------- top bar ---------- */
.topbar{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  padding:10px 18px; border-bottom:1px solid var(--line); background:var(--card);
  position:sticky; top:0; z-index:10;
}
.brand{font-weight:800; font-size:16px; letter-spacing:.02em; color:var(--ink);}
.brand em{font-style:normal; color:var(--crimson);}
.brand.big{font-size:28px;}
.navlinks{display:flex; gap:2px; flex-wrap:wrap;}
.navlinks a{
  color:var(--muted); font-size:13px; font-weight:500;
  padding:5px 10px; border-radius:7px;
}
.navlinks a:hover{color:var(--ink); background:var(--line-soft); text-decoration:none;}
.navlinks a.on{color:var(--ink); font-weight:650; box-shadow:inset 0 -2px 0 var(--crimson); border-radius:7px 7px 0 0;}
.topsearch{margin-left:auto;}
.topsearch input{min-width:170px;}
/* Now a link to /account (was a logout button). Flex-centred because an <a>
   doesn't centre its text the way a <button> did. */
.avatar{
  width:30px; height:30px; border-radius:50%; border:none; cursor:pointer; flex:none;
  display:inline-flex; align-items:center; justify-content:center; text-decoration:none;
  background:var(--crimson); color:var(--crimson-contrast);
  font-size:11px; font-weight:700; font-family:var(--sans);
}
.avatar:hover{text-decoration:none; filter:brightness(1.12);}
/* On the account page: a ring, so "you are here" is shown by shape too. */
.avatar.on{box-shadow:0 0 0 2px var(--card), 0 0 0 4px var(--crimson);}

/* ---------- account menu ---------- */
.acctmenu{position:relative;}
.acctmenu > summary{list-style:none;}
.acctmenu > summary::-webkit-details-marker{display:none;}
.acctmenu[open] > summary{box-shadow:0 0 0 2px var(--card), 0 0 0 4px var(--crimson);}
.acctpanel{
  position:absolute; right:0; top:calc(100% + 8px); z-index:20; width:230px;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  box-shadow:var(--shadow), 0 8px 24px rgba(42,35,32,.10); overflow:hidden;
}
.accthead{display:grid; padding:10px 14px; border-bottom:1px solid var(--line-soft);}
.accthead b{font-size:13px;}
.accthead span{font-size:11.5px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.acctitem{
  display:grid; width:100%; text-align:left; padding:9px 14px; gap:1px;
  font:inherit; font-size:13px; font-weight:600; color:var(--ink); text-decoration:none;
  background:none; border:none; cursor:pointer;
}
.acctitem span{font-size:11px; font-weight:500; color:var(--muted);}
.acctitem:hover, .acctitem:focus-visible{background:var(--line-soft); text-decoration:none;}
.acctlogout{margin:0; border-top:1px solid var(--line-soft);}
.acctlogout .acctitem{color:var(--bad);}
.accountcols{align-items:start;}
.fieldset h2.fs-title{margin:0;}

/* ---------- layout ---------- */
.content{max-width:1100px; margin:0 auto; padding:20px 18px 80px; display:grid; gap:18px; align-content:start;}
/* Grid items default to min-width:auto, i.e. they refuse to shrink below
   their widest content. One wide child (the import map table's 600px
   min-width, a long unbroken filename) then props the WHOLE page wider than
   a phone, instead of scrolling inside its own overflow wrapper. Letting
   every direct child shrink fixes that for all pages at once (WCAG 1.4.10
   reflow; found on the import mapping screen, 2026-09-28). */
.content > *{min-width:0;}
.content.narrow{max-width:420px; padding-top:9vh;}
.d-cols{display:grid; grid-template-columns:3fr 2fr; gap:16px; align-items:start;}
.d-cols.even{grid-template-columns:1fr 1fr;}
.sidecol{display:grid; gap:12px;}
/* align-items:start stops the shorter column stretching into dead space. */
.dashrow{display:grid; grid-template-columns:3fr 2fr; gap:16px; align-items:start;}

/* ---------- forms ---------- */
input,select,textarea{
  font-family:var(--sans); font-size:13.5px; color:var(--ink);
  background:var(--paper); border:1px solid var(--line); border-radius:7px;
  padding:7px 10px; width:100%;
}
textarea{resize:vertical;}
label{display:grid; gap:4px; font-size:11.5px; font-weight:600; color:var(--muted);}
.stack{display:grid; gap:12px;}
.frow{display:grid; grid-template-columns:1fr 1fr; gap:10px;}
.frow.three{grid-template-columns:90px 1fr 1fr;}
.fieldset{
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:16px; display:grid; gap:12px; box-shadow:var(--shadow); align-content:start;
}
.fieldset.horiz{grid-template-columns:2fr 1fr 1fr; align-items:end;}
.fs-title{font-size:11px; font-weight:650; text-transform:uppercase; letter-spacing:.09em; color:var(--muted);}
.inlineform{display:inline; margin:0;}

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  font-family:var(--sans); font-size:13px; font-weight:600; cursor:pointer;
  border-radius:8px; padding:7px 14px; border:1px solid var(--line);
  background:var(--card); color:var(--ink); text-decoration:none; width:fit-content;
}
.btn:hover{text-decoration:none; border-color:var(--muted);}
.btn.primary{background:var(--crimson); color:var(--crimson-contrast); border-color:transparent;}
.btn.primary:hover{filter:brightness(1.1);}
.btn.quiet{border-color:transparent; color:var(--muted); background:transparent;}
.btn.danger{color:var(--bad);}
/* Outlined: a button that must read as a button even without a fill — for
   consequential actions that used to be `.quiet` and looked like plain links
   (Archive / Deactivate on the contact page; Matt, 2026-09-16). The base .btn
   border is only 1.25:1 against the page, so the outline is strengthened to
   clear the 3:1 non-text contrast floor (measured: 3.46 light / 4.76 dark
   neutral, 3.73 / 4.88 danger). Text contrast 15.4 / 5.3+ and up. */
.btn.outlined{
  border-color:color-mix(in srgb, var(--muted) 75%, var(--line));
  background:var(--card); font-weight:650;
}
.btn.outlined:hover{border-color:var(--ink);}
.btn.outlined.danger{
  border-color:color-mix(in srgb, var(--bad) 75%, var(--line));
  background:color-mix(in srgb, var(--bad) 6%, var(--card));
}
.btn.outlined.danger:hover{
  border-color:var(--bad);
  background:color-mix(in srgb, var(--bad) 12%, var(--card));
}
/* The two header actions always stay SIDE BY SIDE (Matt, 2026-09-16). The
   Deactivate reason form used to expand inside this row, which widened its
   item and pushed Archive onto a second line at <=900px. It now opens as a
   dropdown panel taken out of flow, so opening it never reflows the buttons.
   Positioning: the containing block is the whole .d-head (full content width),
   so `right:0` + `width:min(360px,100%)` can never run off either edge — even on
   a phone, where this button row wraps under the name and sits at the LEFT (a
   panel anchored to the row itself ran 59px off-screen at 320px). `top:auto`
   keeps the panel's normal vertical position, i.e. directly under its button. */
.headeracts{display:flex; gap:8px; align-items:flex-start; margin-left:auto; flex-wrap:nowrap;}
.headeracts .removebox > .removeform{
  position:absolute; right:0; top:auto; z-index:6;
  width:min(360px, 100%); margin-top:6px;
  background:var(--card); box-shadow:var(--shadow);
}
/* When a Website link precedes the actions it already takes the auto margin;
   a second auto margin would split the free space and strand the link mid-header. */
.d-head > a + .headeracts{margin-left:0;}
.btn.small{font-size:12px; padding:4px 10px;}
.btn.wide{width:100%;}

/* ---------- chips ---------- */
.chip{
  display:inline-flex; align-items:center; gap:5px; white-space:nowrap;
  font-size:11px; font-weight:600; border-radius:999px; padding:2px 8px;
}
.chip::before{content:""; width:6px; height:6px; border-radius:50%; background:currentColor; flex:none;}
.chip.plain::before{display:none;}
/* A muted chip must stay readable: opacity on 11px text drops it below the
   4.5:1 floor. "hollow" keeps full-strength ink and signals the difference
   with an outline + transparent fill instead. */
.chip.hollow{background:transparent; box-shadow:inset 0 0 0 1px currentColor;}
.chip.hollow::before{opacity:.5;}

/* Visual hierarchy rule: COLOR MEANS LIVE, GREY MEANS SETTLED.
   Inactive and de-activated are correct, finished states — not signals — so
   they drop the panel hue entirely and render neutral, which stops them
   competing with active members and with anything flagged for attention.
   Implemented as a colour swap (muted = 4.7:1 or better on every surface here),
   never as opacity. `.chip.neutral` wins over .chip-em/.chip-fm by being later
   in the file with equal specificity — keep it below them. */
.chip.neutral{color:var(--muted); background:var(--line-soft); box-shadow:none;}
.chip.neutral::before{background:currentColor; opacity:.65;}
/* Chip backgrounds mix toward --card, NOT transparent. With `transparent` the
   ground showed through, so a chip's own background drifted toward its text
   hue on any tinted surface and the text-to-chip contrast fell — several
   combinations were already under 4.5:1 on plain cards before the panel tints
   existed. Anchoring to --card makes a chip look and measure identically
   wherever it sits. Verified: every chip clears 4.5:1 in both themes. */
.chip-em{color:var(--panel-em); background:color-mix(in srgb, var(--panel-em) var(--chip-alpha), var(--card));}
.chip-fm{color:var(--panel-fm); background:color-mix(in srgb, var(--panel-fm) var(--chip-alpha), var(--card));}
.chip-good{color:var(--good); background:color-mix(in srgb, var(--good) var(--chip-alpha), var(--card));}
.chip-warn{color:var(--warn); background:color-mix(in srgb, var(--warn) var(--chip-alpha), var(--card));}
.chip-bad{color:var(--bad); background:color-mix(in srgb, var(--bad) var(--chip-alpha), var(--card));}
.chip-off{color:var(--muted); background:var(--line-soft);}
.chiprow{display:flex; gap:6px; flex-wrap:wrap; margin-top:6px;}
.chiprow.specimen{margin:0;}

/* ---------- banners ---------- */
.banner{
  display:flex; gap:8px; align-items:center; flex-wrap:wrap;
  border:1px solid; border-radius:9px; padding:9px 14px; font-size:13px;
}
.banner.warn{border-color:color-mix(in srgb, var(--warn) 40%, transparent);
  background:color-mix(in srgb, var(--warn) 9%, transparent);}
.banner.warn b{color:var(--warn);}
.banner.bad{border-color:color-mix(in srgb, var(--bad) 40%, transparent);
  background:color-mix(in srgb, var(--bad) 9%, transparent); color:var(--bad);}
.banner.good{border-color:color-mix(in srgb, var(--good) 40%, transparent);
  background:color-mix(in srgb, var(--good) 9%, transparent);}
.banner.good b{color:var(--good);}
.banner.off{border-color:var(--line); background:var(--line-soft); color:var(--muted);}
.banner.off b{color:var(--ink);}
.banner .inlineform{margin-left:auto;}

/* ---------- dashboard ---------- */
.statgrid{display:grid; grid-template-columns:1fr 1fr; gap:14px;}
.statgrid:has(.adcard){grid-template-columns:repeat(3,1fr);}
.panelcard{background:var(--card); border:1px solid var(--line); border-radius:10px; overflow:hidden; box-shadow:var(--shadow);}
.pc-head{display:flex; align-items:center; gap:10px; padding:10px 15px; border-bottom:1px solid var(--line-soft);}
.pc-head b{font-size:14px;}
.pc-head .chip{margin-left:auto;}
.stripe{width:8px; height:8px; border-radius:2px; flex:none;}
.accent-em .stripe{background:var(--panel-em);}
.accent-fm .stripe{background:var(--panel-fm);}
/* 2 numbers since 2026-09-24: Active and Attention (Inactive moved to the
   Attention section). Rules target direct children so a cell may be an <a> or
   a plain <span>. */
.pc-nums{display:grid; grid-template-columns:repeat(2,1fr); padding:12px 15px 14px; gap:8px;}
.pc-nums > *{display:grid; color:var(--ink); text-decoration:none;}
.pc-nums a:hover b{color:var(--crimson);}
.pc-nums b{font-size:24px; font-weight:700; font-variant-numeric:tabular-nums; line-height:1.15;}
.pc-nums > * > span{font-size:10px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted);}
.pc-nums .warnnum b{color:var(--warn);}

/* ---------- action strip ---------- */
/* Only rendered for non-zero counts, so it is a to-do list rather than a
   scoreboard: an absent strip means nothing needs doing. */
.actionstrip{display:flex; gap:10px; flex-wrap:wrap;}
.act{
  display:flex; align-items:baseline; gap:8px; text-decoration:none;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:9px 15px; box-shadow:var(--shadow); color:var(--ink); flex:1 1 200px;
}
.act:hover{text-decoration:none; border-color:var(--muted);}
.act b{font-size:20px; font-weight:700; font-variant-numeric:tabular-nums; line-height:1;}
.act span{font-size:12px; color:var(--muted);}
.act-warn{border-left:3px solid var(--warn);} .act-warn b{color:var(--warn);}
.act-bad{border-left:3px solid var(--bad);}   .act-bad b{color:var(--bad);}

/* ---------- recency strip on the panel cards ---------- */
.recency{padding:0 15px 13px; display:grid; gap:6px;}
.rec-label{font-size:10px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted);}
.rec-bar{display:flex; height:9px; border-radius:5px; overflow:hidden; background:var(--line-soft);}
.rec-bar .seg{display:block; min-width:2px;}
.seg-fresh{background:var(--good);}
.seg-ok{background:color-mix(in srgb, var(--good) 55%, var(--warn));}
.seg-aging{background:var(--warn);}
.seg-stale{background:var(--bad);}
.seg-never{background:var(--muted); opacity:.45;}
.rec-key{list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:4px 12px; font-size:11px; color:var(--muted);}
.rec-key li{display:inline-flex; align-items:center; gap:5px;}
.rec-key i{width:8px; height:8px; border-radius:2px; flex:none;}
.rec-key b{color:var(--ink); font-variant-numeric:tabular-nums;}

/* ---------- per-survey status breakdown ---------- */
/* Every segment prints its own number — color is a second channel, never the
   only one. No completion percentage: see the dashboard route comment. */
.statuscounts{display:flex; gap:4px 10px; flex-wrap:wrap; justify-content:flex-end; font-size:11px; color:var(--muted);}
.sc{display:inline-flex; align-items:center; gap:4px; white-space:nowrap;}
.sc i{width:7px; height:7px; border-radius:2px; background:var(--muted); flex:none;}
.sc b{color:var(--ink); font-variant-numeric:tabular-nums; font-weight:650;}
.sc-finished i{background:var(--good);}
.sc-partial i{background:color-mix(in srgb, var(--good) 50%, var(--warn));}
.sc-opened i{background:var(--fm);}
.sc-sent i{background:var(--line);}
.sc-hard_bounce i,.sc-blocked i{background:var(--bad);}
.sc-soft_bounce i{background:var(--warn);}
.surveyrow .who b{font-size:13px;}

.widget{background:var(--card); border:1px solid var(--line); border-radius:10px; overflow:hidden; box-shadow:var(--shadow);}
.w-head{
  display:flex; justify-content:space-between; align-items:center; gap:10px;
  padding:10px 14px; border-bottom:1px solid var(--line-soft);
  font-size:11px; font-weight:650; text-transform:uppercase; letter-spacing:.09em; color:var(--muted);
}
.w-head a{font-size:12px; text-transform:none; letter-spacing:0; font-weight:600;}
/* The widget/panel headers are real <h2>s now (they were styled divs, which
   left the pages with no heading structure at all). Reset inherited sizing so
   the visual design is unchanged. */
.w-head h2{font:inherit; margin:0; color:inherit; letter-spacing:inherit;}
.pc-head h2{font-size:14px; margin:0; font-weight:700; line-height:1.2;}
.qlist{list-style:none; margin:0; padding:0;}
.qlist li{display:flex; align-items:center; gap:10px; padding:9px 14px; border-bottom:1px solid var(--line-soft); flex-wrap:wrap;}
/* Tight rows: one line, real columns, so a widget shows ~15 items in the
   space the old two-line rows gave 8. */
.qlist.tight li{
  display:grid; grid-template-columns:minmax(110px,1fr) minmax(0,1fr) 250px;
  align-items:center; gap:10px; padding:5px 14px; flex-wrap:nowrap;
}
/* The survey rows carry a wider breakdown than the other tight lists. */
.qlist.tight li.surveyrow{grid-template-columns:minmax(150px,1fr) minmax(0,260px) minmax(0,2fr);}

/* Narrow-container variant: inside the dashboard side column there is not
   enough width for three columns — a fixed metadata column crushed the middle
   one to a single letter. Stack instead: title on one line, metadata beneath. */
.qlist.stack li{
  display:grid; grid-template-columns:minmax(0,1fr) auto; gap:1px 10px;
  align-items:baseline; padding:6px 14px; flex-wrap:nowrap;
}
.qlist.stack .who{grid-column:1; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.qlist.stack .who b{font-size:12.5px;}
.qlist.stack .reason{grid-column:2; grid-row:1; font-size:11px; color:var(--muted); white-space:nowrap;}
.qlist.stack .rowrole{grid-column:1 / -1; grid-row:2; font-size:11px; color:var(--muted);}
.qlist.tight .who{display:block; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.qlist.tight .who b{font-size:12.5px;}
.qlist.tight .reason{font-size:11px; color:var(--warn); text-align:right; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.qlist.tight .reason.muted{color:var(--muted);}
.qlist.tight .rowrole{display:flex; align-items:center; gap:6px;}
.qlist li:last-child{border-bottom:none;}
.qlist .who{display:grid; color:var(--ink); text-decoration:none; min-width:0;}
.qlist .who b{font-size:13px; font-weight:650;}
.qlist .who:hover b{color:var(--crimson);}
.qlist .who span{font-size:11.5px; color:var(--muted);}
.qlist .chip{margin-left:auto;}

.bars{padding:10px 14px 14px; display:grid; gap:7px;}
.bar{display:grid; grid-template-columns:90px 1fr 40px; gap:8px; align-items:center; font-size:12px; color:var(--ink); text-decoration:none;}
.bar:hover i{color:var(--crimson);}
.bar i{font-style:normal; color:var(--muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.bar .track{height:8px; border-radius:4px; background:var(--line-soft); overflow:hidden; display:block;}
.bar .fill{height:100%; border-radius:4px; background:var(--crimson); opacity:.75; display:block;}
.bar .n{font-variant-numeric:tabular-nums; text-align:right; color:var(--muted);}

/* ---------- contact lists (responsive rows, not tables) ---------- */
.filterbar{
  display:flex; gap:8px; flex-wrap:wrap; align-items:center;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:10px 12px; box-shadow:var(--shadow);
  position:sticky; top:56px; z-index:5;
}
.filterbar input[type=search]{flex:2; min-width:160px; width:auto;}
.filterbar select{width:auto;}
.checkchip{
  display:inline-flex; flex-direction:row; align-items:center; gap:6px;
  font-size:12.5px; font-weight:600; color:var(--muted); cursor:pointer;
}
.checkchip input{width:auto;}

.rowlist{list-style:none; margin:0; padding:0; background:var(--card); border:1px solid var(--line); border-radius:10px; overflow:hidden; box-shadow:var(--shadow);}
.rowlist li{display:flex; align-items:center; gap:12px; padding:10px 14px; border-bottom:1px solid var(--line-soft); flex-wrap:wrap;}
.rowlist li:last-child{border-bottom:none;}
.rowmain{display:grid; min-width:0; flex:1; color:var(--ink); text-decoration:none;}
.rowmain b{font-size:13.5px; font-weight:650;}
.rowmain:hover b{color:var(--crimson);}
.rowmain .sub{font-size:11.5px; color:var(--muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.rowstate{font-size:12px; color:var(--muted); width:100px; flex:none;}
.rowchips{display:flex; gap:5px; flex-wrap:wrap; justify-content:flex-end;}

/* Dense variant: real columns. Flex let the variable-width chip cluster shove
   the state column to a different x on every row; a grid pins all four. One
   line per contact roughly doubles how many fit on screen. */
.rowlist.dense li{
  display:grid; grid-template-columns:minmax(150px,1.1fr) minmax(0,1.6fr) 92px 100px 100px 178px;
  align-items:center; gap:10px; padding:6px 14px; flex-wrap:nowrap;
}
.rowlist.dense .rowmain{display:block; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.rowlist.dense .rowmain b{font-size:13px;}
.rowrole{
  font-size:11.5px; color:var(--muted);
  overflow:hidden; white-space:nowrap; text-overflow:ellipsis; min-width:0;
}
.noname{font-style:italic; color:var(--muted); font-weight:600;}
/* Whole-row recession for settled contacts. Deliberately does NOT touch the
   name — you still have to be able to scan for a person by eye. */
/* Colour swap only — NO opacity. Measured: muted at 85% over the light card
   is 4.08:1, under the 4.5:1 small-text floor; at full strength it is 5.68:1.
   This is the same trap that `.chip.dim` fell into. */
.rowlist.dense li.settled .rowrole,
.rowlist.dense li.settled .rowstate{color:var(--muted);}
.rowlist.dense li.settled .rowmain b{font-weight:600;}

/* ---------- duplicate comparison ---------- */
/* A wide side-by-side table. Per CLAUDE.md a table may scroll horizontally
   inside its own container — the page body still must not. */
.comparewrap{overflow-x:auto;}
.comparetable{border-collapse:collapse; width:100%; font-size:12.5px; min-width:560px;}
.comparetable caption{text-align:left;}
.comparetable th, .comparetable td{
  padding:6px 12px; border-bottom:1px solid var(--line-soft); vertical-align:top; text-align:left;
}
.comparetable thead th{
  font-size:11px; border-bottom:1px solid var(--line); color:var(--ink);
  position:sticky; top:0; background:var(--card);
}
.comparetable thead th span{display:block; font-weight:500;}
.comparetable tbody th{
  font-size:10.5px; text-transform:uppercase; letter-spacing:.07em;
  color:var(--muted); font-weight:650; white-space:nowrap; width:130px;
}
/* A differing row is marked by a glyph AND weight, not by colour alone. */
.comparetable tr.differs{background:color-mix(in srgb, var(--warn) 7%, transparent);}
.comparetable tr.differs td{font-weight:650;}
.diffmark{color:var(--warn); font-size:8px; vertical-align:middle; margin-left:5px;}
.comparetable tr:last-child th, .comparetable tr:last-child td{border-bottom:none;}
.mergetable .resultval{font-weight:700; background:color-mix(in srgb, var(--good) 8%, transparent);}
.pickval{display:flex; gap:6px; align-items:flex-start; font-size:12.5px; font-weight:500;
  color:var(--ink); cursor:pointer; text-transform:none; letter-spacing:0;}
.pickval input{width:auto; margin-top:2px; flex:none;}
.pickhint{padding:8px 14px 12px; margin:0;}
.mergefoot{padding:0 0 4px;}
.movelist li{grid-template-columns:minmax(140px,1fr) minmax(0,2fr) auto;}

.verdicts{display:grid; gap:10px;}
.verdicts h2{font-size:15px; margin:0;}
.verdictgrid{display:grid; grid-template-columns:repeat(3,1fr); gap:12px; align-items:start;}
.vcard{
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:14px 15px; box-shadow:var(--shadow); display:grid; gap:8px; align-content:start;
}
.vcard h3{font-size:13.5px; margin:0;}
.vcard p{margin:0;}
.vcard form{display:grid; gap:8px;}
.vnote{font-size:11px;}
/* The dupe queue's own columns: the match-reason chips need real width or
   they stack and double every row's height. */
.rowlist.dense.dupelist li,
.rowhead.dupehead{grid-template-columns:minmax(135px,0.95fr) minmax(0,0.75fr) minmax(300px,1.5fr) 104px;}
/* The basis line: a short chip plus the caveat that matters for it. */
.matchwhy{display:flex; gap:7px; align-items:baseline; flex-wrap:nowrap; min-width:0;}
.whynote{font-size:11px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.rowhead.dupehead span{font-size:10.5px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); font-weight:650;}
/* Name | role | reason | one fixed slot per panel | clear.
   The panel slots are fixed-width so a pill sits at the same x on every row
   whether or not its neighbour belongs to the other panel. Widened to fit the
   longest label ("EM · Not enrolled"). */
.rowlist.dense.attnlist li,
.rowhead.attnhead{grid-template-columns:minmax(140px,1fr) minmax(0,1.1fr) minmax(0,1.3fr) 52px 52px;}
/* Sticky column header: the queue is 56 rows, so the labels have to survive
   scrolling or the EM/FM ticks become unreadable half way down. Needs an
   opaque ground (rows scroll under it) and to clear the sticky topbar. */
.rowhead.attnhead{
  position:sticky; top:var(--topbar-h); z-index:4;
  background:var(--paper); margin-bottom:0;
  padding-top:10px; padding-bottom:7px;
  border-bottom:1px solid var(--line);
}
.tickcol{text-align:center;}
.tick{
  color:var(--good); font-size:14px; font-weight:700; line-height:1;
  display:inline-block;
}
.pslot{display:flex; align-items:center; min-width:0;}
/* Panel code inside a chip/tick: redundant on desktop (the column header names
   the panel) so hidden there; shown on phones, where the header row is hidden
   and a bare "Active" or ✓ would not say active in WHAT. */
.pcode{display:none;}
.tickcode{font-size:10.5px; font-weight:700; color:var(--muted); margin-left:4px; letter-spacing:.04em;}
/* In the dense lists the flags cell sits under a left-aligned "Flags" header,
   so it starts at the left edge rather than the older right-packed chip cluster. */
.rowlist.dense .rowchips{justify-content:flex-start;}
.colhead{font-size:10.5px; font-weight:650; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); padding:2px 4px;}

/* ---------- orphans ---------- */
.rowlist.dense.orphanlist li,
.rowhead.orphanhead{grid-template-columns:minmax(130px,0.9fr) minmax(0,0.85fr) minmax(0,2fr) 140px 76px;}
/* Anchored rows must clear the sticky topbar + sticky column header, or the
   row you land on after archiving sits hidden underneath them. */
.rowlist.dense.orphanlist li{scroll-margin-top:calc(var(--topbar-h) + 44px);}
/* Archived list: same look as Inactive, four columns (no flags, no action —
   unarchiving happens on the contact page). */
.rowlist.dense.archivedlist li,
.rowhead.orphanhead.archivedhead{grid-template-columns:minmax(130px,0.9fr) minmax(0,0.85fr) minmax(0,2fr) 110px;}
.rowaction{justify-self:end;}
/* Sticky like its sibling queue: 219 rows is long enough to lose the labels. */
.rowhead.orphanhead{
  position:sticky; top:var(--topbar-h); z-index:4;
  background:var(--paper); padding-top:10px; padding-bottom:7px;
  border-bottom:1px solid var(--line);
}
.rowhead.orphanhead span{font-size:10.5px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); font-weight:650;}
.orphanwhy{font-size:11.5px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;}
.pslot .chip{max-width:100%;}
.attnreason{font-size:11.5px; color:var(--warn); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.attnhead span{font-size:10.5px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); font-weight:650;}
.visually-hidden{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap;}

/* ---------- admin section left-hand nav ---------- */
/* A vertical rail rather than a horizontal strip: Admin is a small, settled
   set of destinations, and the rail leaves full width to the tables and forms
   in the section. The active item is marked by an inset bar on the LEFT edge
   (the top nav uses a bottom edge) plus weight — shape, not colour alone. */
.adminlayout{
  display:grid; grid-template-columns:186px minmax(0,1fr);
  max-width:1100px; margin:0 auto; align-items:start;
}
.sidenav{
  display:flex; flex-direction:column; gap:1px;
  padding:20px 10px 20px 18px; position:sticky; top:71px;
}
.sidenav-title{
  font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.1em;
  color:var(--muted); padding:0 10px 7px;
}
.sidenav a{
  font-size:13px; font-weight:600; color:var(--muted);
  padding:7px 11px; border-radius:7px; text-decoration:none;
  border-left:2px solid transparent;
}
.sidenav a:hover{color:var(--ink); background:var(--line-soft); text-decoration:none;}
.sidenav a.on{
  color:var(--ink); font-weight:700; background:var(--line-soft);
  border-left-color:var(--crimson); border-radius:0 7px 7px 0;
}
/* The rail already supplies the left gutter. */
.content.adminmain{max-width:none; margin:0; padding-left:14px;}

/* Admin landing cards */
.adcard{
  display:grid; gap:6px; align-content:start; text-decoration:none; color:var(--ink);
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:15px 16px; box-shadow:var(--shadow);
}
.adcard:hover{border-color:var(--muted); text-decoration:none;}
.adcard h2{font-size:15px; margin:0;}
.adcard p{margin:0;}
.adnum{font-size:12px; color:var(--muted); margin-top:2px;}
.adnum b{color:var(--ink); font-variant-numeric:tabular-nums; font-size:14px;}
.sectionh1{font-size:19px; margin:0 0 2px;}

/* Disclosure for disabled accounts — closed by default. */
.disabledbox{border-top:1px solid var(--line-soft);}
.disabledbox summary{
  cursor:pointer; list-style:none; padding:9px 14px;
  font-size:12px; font-weight:600; color:var(--muted);
}
.disabledbox summary::-webkit-details-marker{display:none;}
.disabledbox summary:hover{color:var(--ink); background:var(--line-soft);}
.disabledbox .dsum::before{content:"▸ "; font-size:10px;}
.disabledbox[open] .dsum::before{content:"▾ ";}
.disabledbox[open] summary{border-bottom:1px solid var(--line-soft);}

/* ---------- search row + panel flag ---------- */
/* Search is its own control, carrying nothing but the query and a panel flag.
   The browse controls (status, state) live below it and compose with it. */
.searchrow{
  display:flex; gap:8px; flex-wrap:wrap; align-items:center;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:10px 12px; box-shadow:var(--shadow);
}
.searchrow input[type=search]{flex:1; min-width:200px; width:auto;}
.panelflag{display:inline-flex; border:1px solid var(--line); border-radius:8px; overflow:hidden;}
.panelflag .pf{
  display:inline-flex; align-items:center; gap:6px; padding:6px 12px;
  font-size:12.5px; font-weight:600; color:var(--muted); text-decoration:none;
  border-right:1px solid var(--line); background:var(--card);
}
.panelflag .pf:last-child{border-right:none;}
.panelflag .pf:hover{color:var(--ink); background:var(--line-soft); text-decoration:none;}
.panelflag .pf b{font-size:11px; font-variant-numeric:tabular-nums; font-weight:700;}
/* Selected flag: filled, plus weight — shape and fill, not hue alone. */
.panelflag .pf.on{background:var(--ink); color:var(--paper); font-weight:700;}
.panelflag .pf.on b{color:var(--paper);}
.panelflag .pf-em.on{background:var(--panel-em); color:var(--paper);}
.panelflag .pf-em.on b{color:var(--paper);}
.panelflag .pf-fm.on{background:var(--panel-fm); color:var(--crimson-contrast);}
.panelflag .pf-fm.on b{color:var(--crimson-contrast);}
.resetbtn{margin-left:auto;}

/* ---------- state browse ---------- */
/* The browse axis. Counts re-scope to whatever is selected above, so the set
   shrinks as you narrow — picking FM lists only the states FM reaches. */
.statebrowse{
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  box-shadow:var(--shadow); overflow:hidden;
}
.sb-head{
  display:flex; justify-content:space-between; align-items:baseline; gap:10px;
  padding:9px 14px; border-bottom:1px solid var(--line-soft); flex-wrap:wrap;
}
.sb-head h2{font-size:11px; font-weight:650; text-transform:uppercase; letter-spacing:.09em; color:var(--muted); margin:0;}
/* The state buttons scroll once there are many states. Their breathing room is
   a MARGIN, not padding: padding inside a scroll container scrolls away with the
   content, so at rest the last visible row butted against the card's bottom
   border (Matt, 2026-09-16). The margin keeps the clip edge inset from the
   border at all times. A 3px inner padding keeps focus rings from being
   clipped. max-height is snapped to whole rows (buttons 26px, gap 5px): 6 rows =
   3 + 6*26 + 5*5 + 3 = 187px, so no half-cut row shows at rest. */
.statebtns{display:flex; flex-wrap:wrap; gap:5px; margin:7px 11px 11px; padding:3px; max-height:187px; overflow-y:auto;}
.sb{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-size:12px; font-weight:600; color:var(--ink); text-decoration:none;
  background:var(--paper); border:1px solid var(--line); border-radius:7px;
  padding:3px 5px 3px 9px;
}
.sb:hover{border-color:var(--muted); text-decoration:none;}
.sb b{font-size:10.5px; font-weight:700; font-variant-numeric:tabular-nums;
  color:var(--muted); background:var(--line-soft); border-radius:5px; padding:1px 5px;}
.sb.on{background:var(--crimson); color:var(--crimson-contrast); border-color:transparent; font-weight:700;}
.sb.on b{background:rgba(255,255,255,.22); color:var(--crimson-contrast);}

.exportrow{display:flex; gap:8px; flex-wrap:wrap; align-items:center;}

/* ---------- sortable column header ---------- */
/* Mirrors .rowlist.dense's grid exactly so the header sits over its columns.
   Direction is shown by an arrow glyph AND the active column's weight, so it
   does not depend on colour. */
.rowhead{
  display:grid; grid-template-columns:minmax(150px,1.1fr) minmax(0,1.6fr) 92px 100px 100px 178px;
  align-items:center; gap:10px; padding:4px 14px; margin-bottom:-4px;
}
.rowhead .sortcol{
  display:inline-flex; align-items:center; gap:4px; width:fit-content;
  font-size:10.5px; font-weight:650; text-transform:uppercase; letter-spacing:.08em;
  color:var(--muted); text-decoration:none; padding:2px 4px; border-radius:5px;
}
.rowhead .sortcol:hover{color:var(--ink); background:var(--line-soft); text-decoration:none;}
.rowhead .sortcol.on{color:var(--ink); font-weight:750;}
.rowhead .arr{font-size:8px; line-height:1;}
.sorthint{
  font-size:10.5px; text-transform:uppercase; letter-spacing:.08em;
  text-align:right; font-weight:650;
}

/* ---------- quick filters ---------- */
.quickbar{display:flex; gap:6px; flex-wrap:wrap; align-items:center;}
.quickbar .qf{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-size:12.5px; font-weight:600; color:var(--muted); text-decoration:none;
  background:var(--card); border:1px solid var(--line); border-radius:999px;
  padding:4px 6px 4px 11px;
}
.quickbar .qf:hover{color:var(--ink); border-color:var(--muted); text-decoration:none;}
.quickbar .qf b{
  font-size:11px; font-weight:700; font-variant-numeric:tabular-nums;
  background:var(--line-soft); color:var(--ink); border-radius:999px; padding:1px 7px;
}
/* Current filter is marked by weight + a filled pill + an inset underline —
   never by color alone (the nav uses the same shape cue). */
.quickbar .qf.on{
  color:var(--ink); font-weight:700; border-color:var(--muted);
  box-shadow:inset 0 -2px 0 var(--crimson);
}
.quickbar .qf.on b{background:var(--crimson); color:var(--crimson-contrast);}
.quickbar .qf-em b{color:var(--panel-em);}
.quickbar .qf-fm b{color:var(--panel-fm);}
.quickbar .qf-warn b{color:var(--warn);}
/* Settled facets: neutral count pill, so the coloured ones are the live ones. */
.quickbar .qf-quiet{color:var(--muted);}
.quickbar .qf-quiet b{color:var(--muted); background:transparent; box-shadow:inset 0 0 0 1px var(--line);}
.quickbar .qf.on b{color:var(--crimson-contrast);}

.pagehead{display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;}
.pagehead h1{font-size:19px; margin:0; line-height:1.2;}
.pagehead p{margin:0;}

/* ---------- contact detail ---------- */
.d-head{display:flex; align-items:flex-start; gap:14px; flex-wrap:wrap; position:relative;} /* containing block for the Deactivate dropdown */
.d-head>a{margin-left:auto;}
.pid{font-family:var(--mono); font-size:11px; color:var(--muted);}
.memb{background:var(--card); border:1px solid var(--line); border-radius:10px; overflow:hidden; box-shadow:var(--shadow);}
/* Mixed against --card (not transparent) so the result is an opaque colour the
   contrast of the text on it can actually be reasoned about. */
.memb.accent-em{
  background:color-mix(in srgb, var(--panel-em) var(--panel-tint), var(--card));
  border-color:color-mix(in srgb, var(--panel-em) 22%, var(--line));
}
.memb.accent-fm{
  background:color-mix(in srgb, var(--panel-fm) var(--panel-tint), var(--card));
  border-color:color-mix(in srgb, var(--panel-fm) 22%, var(--line));
}
.m-head{display:flex; align-items:center; gap:8px; padding:10px 13px; border-bottom:1px solid var(--line-soft);}
/* Header takes a touch more hue so the box reads as having a head. */
.memb.accent-em .m-head{
  background:color-mix(in srgb, var(--panel-em) 4%, transparent);
  border-bottom-color:color-mix(in srgb, var(--panel-em) 18%, var(--line-soft));
}
.memb.accent-fm .m-head{
  background:color-mix(in srgb, var(--panel-fm) 4%, transparent);
  border-bottom-color:color-mix(in srgb, var(--panel-fm) 18%, var(--line-soft));
}
/* Inputs inside a tinted box need to stay distinguishable from their ground. */
.memb.accent-em .removeform, .memb.accent-fm .removeform{background:var(--card);}
.m-head b{font-size:13px;}
.m-head .chip{margin-left:auto;}
.m-body{padding:11px 13px; display:grid; gap:9px; font-size:12.5px;}
.kv{display:flex; justify-content:space-between; gap:10px;}
.kv span{color:var(--muted);}
.m-actions{display:flex; gap:8px;}
.timeline{list-style:none; margin:2px 0; padding:0; border-left:2px solid var(--line);}
.timeline li{margin-left:12px; padding:2px 0 7px; position:relative; font-size:12px;}
.timeline li::before{
  content:""; position:absolute; left:-19px; top:7px; width:8px; height:8px;
  border-radius:50%; background:var(--line); border:2px solid var(--card);
}
.timeline li.done::before{background:var(--good);}
.timeline li.bounce::before{background:var(--bad);}
.timeline b{font-weight:600;}
.timeline span{color:var(--muted); display:block; font-size:10.5px;}
.note{border-bottom:1px dashed var(--line-soft); padding-bottom:8px;}
.note p{margin:4px 0 0; font-size:12.5px;}
.notehead{display:flex; align-items:center; gap:8px;}
.notehead .inlineform{margin-left:auto;}
.changehist summary{cursor:pointer; list-style:none;}
.changehist summary::-webkit-details-marker{display:none;}
.dupegroup{border-left:3px solid var(--warn);}

/* ---------- imports ---------- */
.filebar{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  padding:10px 14px; font-size:13px; box-shadow:var(--shadow);
}
.filebar .fname{font-family:var(--mono); font-size:12px; word-break:break-all;}
.maptable-wrap{background:var(--card); border:1px solid var(--line); border-radius:10px; overflow-x:auto; box-shadow:var(--shadow);}
.maptable{border-collapse:collapse; width:100%; font-size:12.5px; min-width:600px;}
.maptable th{
  text-align:left; font-size:10.5px; text-transform:uppercase; letter-spacing:.09em;
  color:var(--muted); padding:9px 13px; border-bottom:1px solid var(--line); white-space:nowrap;
}
.maptable td{padding:7px 13px; border-bottom:1px solid var(--line-soft); vertical-align:middle;}
.maptable tr:last-child td{border-bottom:none;}
.maptable .csvh{font-family:var(--mono); font-size:11.5px; white-space:nowrap;}
.maptable .sample{color:var(--muted); font-size:11.5px; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.maptable select{width:auto; min-width:160px;}
.maptable select.matched{border-color:color-mix(in srgb, var(--good) 45%, transparent);}
/* Column guide on the mapping screen. Existing tokens only (no new colours,
   so the contrast audit still holds). Long headers/examples/URLs wrap
   anywhere so the list can never prop the page wider than a phone. */
.colguide{background:var(--card); border:1px solid var(--line); border-radius:10px; box-shadow:var(--shadow); min-width:0; margin-bottom:14px;}
.colguide summary{cursor:pointer; list-style:none; padding:10px 14px;}
.colguide summary::-webkit-details-marker{display:none;}
.colguide summary:hover{background:var(--line-soft);}
.colguide .cg-title{display:inline; font-size:13px; margin:0;}
.colguide .cg-title::before{content:"▸ "; font-size:10px;}
.colguide[open] .cg-title::before{content:"▾ ";}
.colguide[open] summary{border-bottom:1px solid var(--line-soft);}
.cg-body{padding:4px 14px 12px;}
.cg-list{display:grid; grid-template-columns:repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap:10px 22px; margin:8px 0 0;}
.cg-item{min-width:0;}
.cg-item dt{font-weight:600; font-size:13px;}
.cg-item dt code{font-family:var(--mono); font-size:11.5px; font-weight:400; color:var(--muted); margin-left:4px;}
.cg-item dd{margin:2px 0 0; font-size:12.5px; overflow-wrap:anywhere;}
.cg-meta{display:block; color:var(--muted); font-size:11.5px; margin-top:2px;}
.cg-ex{font-family:var(--mono);}
.importfoot{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
.importfoot .spacer{flex:1;}

/* ---------- login ---------- */
body.bare{display:block;}
/* ---------- contacts: paging toolbar, pager, floating A–Z bar ---------- */
.listtools{display:flex; align-items:center; justify-content:space-between; gap:8px 14px; flex-wrap:wrap; margin-bottom:-8px;}
.listtools p{margin:0;}
.perpage{display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap;}
.perpage .muted{margin-right:2px;}
.pp, .pg{
  display:inline-flex; align-items:center; justify-content:center; min-width:28px; min-height:28px;
  font-size:12px; font-weight:600; color:var(--ink); text-decoration:none;
  border:1px solid var(--line); border-radius:7px; padding:2px 8px; background:var(--card);
}
.pp:hover, .pg:hover{border-color:var(--muted); text-decoration:none;}
/* Current page / size: filled, heavier weight — not colour alone. */
.pp.on, .pg.on{background:var(--crimson); color:var(--crimson-contrast); border-color:transparent; font-weight:750;}
.pager{display:flex; justify-content:center; align-items:center; gap:5px; flex-wrap:wrap;}
.pg-gap{color:var(--muted); padding:0 2px;}

/* The bar floats over the list (sticky to the viewport bottom) and settles at
   the end of the page. Scroll padding keeps keyboard focus, letter anchors and
   the post-"Load more" focus from ending up hidden under it or the top bar
   (WCAG 2.4.11), and only applies on pages that have the bar. */
:root:has(.bottombar){
  scroll-padding-top:calc(var(--topbar-h) + 8px);
  scroll-padding-bottom:64px;
}
.bottombar{
  position:sticky; bottom:0; z-index:6;
  min-width:0;   /* it is a grid item of .content: without this the letter strip's
                    full width props the page open and it scrolls sideways at 320px */
  display:flex; align-items:center; gap:10px;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 -2px 14px rgba(42,35,32,.10), var(--shadow);
  padding:6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
}
.azbar{display:flex; gap:2px; flex:1; min-width:0; overflow-x:auto; -webkit-overflow-scrolling:touch;}
.az{
  flex:none; display:inline-flex; align-items:center; justify-content:center;
  min-width:26px; height:30px; border-radius:6px;
  font-size:13px; font-weight:600; color:var(--muted); text-decoration:none;
}
.az:hover{background:var(--line-soft); color:var(--ink); text-decoration:none;}
/* Letters with rows currently on screen: ink + bold + underline. */
.az.here{color:var(--ink); font-weight:800; text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:2px;}
.azhint{flex:1; margin:0 6px; color:var(--muted);}
.loadmore{flex:none; margin-left:auto; white-space:nowrap;}
/* Busy state: the "Loading…" label is the cue. No opacity — see the .settled contrast trap. */
.loadmore[aria-disabled="true"]{pointer-events:none;}

.logincard{
  background:var(--card); border:1px solid var(--line); border-radius:12px;
  padding:28px; display:grid; gap:14px; box-shadow:var(--shadow);
}
.logincard .muted{margin:-6px 0 4px;}

/* ---------- mobile ---------- */
@media (max-width:760px){
  .content{padding:14px 12px 70px;}
  .statgrid,.statgrid:has(.adcard),.dashrow,.d-cols,.d-cols.even,.fieldset.horiz{grid-template-columns:1fr;}
  /* Rail becomes a horizontal scroll strip; it would otherwise eat the width. */
  .adminlayout{grid-template-columns:1fr;}
  .sidenav{
    flex-direction:row; position:static; gap:2px; padding:8px 12px 0;
    overflow-x:auto; flex-wrap:nowrap; -webkit-overflow-scrolling:touch;
  }
  .sidenav-title{display:none;}
  .sidenav a{border-left:none; border-bottom:2px solid transparent; white-space:nowrap;}
  .sidenav a.on{border-left:none; border-bottom-color:var(--crimson); border-radius:7px 7px 0 0;}
  .content.adminmain{padding-left:12px;}
  .frow,.frow.three{grid-template-columns:1fr;}
  .topbar{gap:8px; padding:8px 12px;}
  .topsearch{order:3; width:100%; margin-left:0;}
  .topsearch input{width:100%;}
  .navlinks{order:4; width:100%; overflow-x:auto; flex-wrap:nowrap; -webkit-overflow-scrolling:touch;}
  .acctmenu{margin-left:auto;}
  .filterbar{position:static;}
  .rowstate{display:none;}
  .rowchips{width:100%; justify-content:flex-start;}
  /* Dense rows return to stacked flow on phones — four columns cannot fit. */
  .rowlist.dense li{grid-template-columns:1fr; gap:2px; padding:9px 14px;}
  /* No column grid on phones, so the header would label nothing. */
  .rowhead{display:none;}
  .pcode{display:inline;}
  .pslot-empty{display:none;}
  .rowlist.dense.orphanlist li,
  .rowlist.dense.archivedlist li{grid-template-columns:1fr;}
  .rowaction{justify-self:start; margin-top:4px;}
  .orphanwhy{white-space:normal;}
  .rowhead.dupehead{display:none;}
  .rowlist.dense.dupelist li,
  .rowlist.dense.attnlist li{grid-template-columns:1fr;}
  .verdictgrid{grid-template-columns:1fr;}
  .comparetable{min-width:480px;}
  /* .surveyrow needs its own override — it is more specific than the generic
     tight-row rule, so without this the 3-column grid survived onto phones and
     the status breakdown overflowed its cell. */
  .qlist.tight li,
  .qlist.tight li.surveyrow{grid-template-columns:1fr; gap:2px; padding:8px 14px;}
  .qlist.stack li{grid-template-columns:minmax(0,1fr) auto;}
  .qlist.tight .reason{text-align:left;}
  .statuscounts{justify-content:flex-start;}
  .actionstrip .act{flex:1 1 100%;}
  .rowlist.dense .rowchips{margin-top:3px;}
  .quickbar{overflow-x:auto; flex-wrap:nowrap; -webkit-overflow-scrolling:touch; padding-bottom:2px;}
  .searchrow{padding:9px 10px;}
  .searchrow input[type=search]{flex:1 1 100%;}
  .panelflag{flex:1 1 auto;}
  .panelflag .pf{flex:1; justify-content:center;}
  .statebtns{max-height:125px;}   /* 4 whole rows: 3 + 4*26 + 3*5 + 3 */
  .resetbtn{margin-left:0;}
  .exportrow .btn{flex:1 1 100%;}
  .bottombar{border-radius:10px 10px 0 0; margin:0 -12px -1px; border-bottom:none;}
  .listtools{margin-bottom:0;}
  .btn{padding:9px 16px;}   /* larger touch targets */
  .m-actions .btn{width:100%;}
}

/* ---------- remove / deactivate disclosures ---------- */
/* Both destructive-ish actions open a short form rather than firing on click:
   a removal wants a reason, and an opt-out is a consent record. */
.removebox summary{list-style:none; display:inline-flex; cursor:pointer;}
.removebox summary::-webkit-details-marker{display:none;}
.removeform{
  margin-top:8px; padding:10px; border:1px solid var(--line);
  border-radius:9px; background:var(--paper);
}
.removeform p{margin:0;}
.unknownreason{color:var(--warn);}
