@charset "UTF-8";
/* ============================================================================
   RCCG Trinity Sanctuary — attendance system design system
   ----------------------------------------------------------------------------
   One stylesheet, in this order:
     1  Design tokens (light) and the dark theme
     2  Reset, base elements, focus, scrollbars
     3  Typography utilities
     4  Layout primitives (app shell, grid)
     5  Navigation: sidebar, header, bottom nav
     6  Cards, KPIs, hero, section headers, empty states
     7  Buttons, tabs, toggles
     8  Forms, search
     9  Pills, chips, deltas, avatars
    10  Tables and list rows
    11  Data visualisation: meter, ring, sparkline, dots, heatmap, chart
    12  Overlays: submit bar, toast, drawer, modal
    13  Sign-in (split) layout and the error page
    14  Animation, reduced motion
    15  Responsive: >=900px sidebar, >=1180px roomy columns
   ----------------------------------------------------------------------------
   Rules of the road for anyone adding to this file:
   * No component carries a [data-theme="dark"] rule. Dark mode is tokens only.
   * Per-row dynamic values arrive as custom properties (--meter-pct,
     --avatar-size, --grid-min), never as a bare inline style declaration.
   * Colour is never the only signal: every tone is paired with a label,
     a glyph or a text alternative.
   ========================================================================= */

/* ==========================================================================
   1  TOKENS
   Values are the prototype's, verbatim, with six documented exceptions marked
   "a11y" — those failed WCAG AA and would have shipped illegible micro-labels
   and invisible field borders.
   ========================================================================== */
:root {
  color-scheme: light;

  /* paper / surface */
  --paper: #f7f6f3; --surface: #ffffff; --surface-2: #faf9f6; --surface-3: #fdfaf3;
  /* line / hair */
  --line: #e8e5df; --hair: #f0eee9; --hair-2: #f5f3ef;
  /* ink / muted — faint ramp darkened (a11y: 4.50/3.80/3.16 on white) */
  --ink: #111a2e; --mut: #5f6a7f; --faint: #636b7a; --faint-2: #676f7e; --faint-3: #6b7382;
  /* brand navy */
  --brand: #1d2f5c; --brand-hi: #16264c; --brand-ink: #1d2f5c; --brand-fg: #f4f1ea;
  --brand-soft: #eef1f7; --cream: #f2e4c8; --on-brand: #c3cddf; --on-brand-dim: #a9b7d4;
  --on-brand-ok: #a8e0c4; --on-brand-bad: #f0b9a8;
  /* accent gold — gold-text added (a11y: --gold on --gold-w was 2.80) */
  --gold: #b8873a; --gold-w: #f7efe1; --gold-b: #ecdcbd; --gold-d: #7d5c1f; --gold-text: #8a6420;
  /* accent green */
  --green: #2f7a5b; --green-w: #e7f2ec; --green-b: #cfe3d8; --green-hi: #276549;
  /* accent clay */
  --clay: #b0492f; --clay-w: #f9ece8; --clay-b: #f0d6cd; --clay-d: #7d2d18;
  /* track / dim / input / dash — --input darkened (a11y: 1.36 non-text) */
  --track: #f2f0eb; --dim: #eceae4; --dim-2: #e4e1da; --input: #908b82; --dash: #d8d5cd;
  --line-strong: #908b82;
  /* shadow / scrim / glass */
  --shadow: rgba(17, 26, 46, .28); --shadow-brand: rgba(29, 47, 92, .26);
  --scrim: rgba(17, 26, 46, .34); --scrim-2: rgba(17, 26, 46, .38);
  --glass: rgba(247, 246, 243, .92); --glass-2: rgba(255, 255, 255, .96);

  /* Hexes the prototype hid inside JS ternaries, promoted to tokens. */
  --row-present: #f6faf7;
  --plum-w: #f4eef4; --plum: #6b4f7a;
  --dot-mid: #9aa8c4; --dot-low: #c9d0de;
  --ramp-0: #f0eee9; --ramp-1: #d3d9e4; --ramp-2: #a3b0c9; --ramp-3: #6e82ab; --ramp-4: #3d5488; --ramp-5: #1d2f5c;
  --hist-0: #e0d3c4; --hist-1: #d8c0ae; --hist-2: #a3b0c9; --hist-3: #6e82ab; --hist-4: #1d2f5c;

  --logo: url(../img/logo-light.png);

  /* type */
  --font-display: 'Newsreader', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', serif;
  --font-ui: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  /* the z-index ladder, in one place so it cannot drift */
  --z-bar: 20; --z-header: 30; --z-bottomnav: 40; --z-drawer: 60; --z-modal: 70; --z-toast: 80;

  /* Motion. One decelerating curve for anything that enters the screen, so a
     drawer, a sheet and a toast all arrive with the same weight. */
  --ease-out-quint: cubic-bezier(.22, 1, .36, 1);
  --duration-fast: 150ms;
  --drawer-ms: 260ms;

  /* a focus ring that survives a navy background */
  --focus: #16264c; --focus-halo: var(--surface);
  --tap: 44px;
}

/* Dark by preference, unless the reader has explicitly chosen light. */
@media (prefers-color-scheme: dark) {
  body:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #0f1218; --surface: #171b23; --surface-2: #1e232d; --surface-3: #241f16;
    --line: #2c3341; --hair: #242a35; --hair-2: #1f242e;
    --ink: #e8ebf1; --mut: #a6afc0; --faint: #9199a9; --faint-2: #88919f; --faint-3: #818a99;
    --brand: #223a70; --brand-hi: #2c4a8e; --brand-ink: #93b2ec; --brand-fg: #eef1f7;
    --brand-soft: #1c2740; --cream: #f2e4c8; --on-brand: #c9d4e8; --on-brand-dim: #aab8d6;
    --on-brand-ok: #a8e0c4; --on-brand-bad: #f0b9a8;
    --gold: #d5a556; --gold-w: #332910; --gold-b: #4a3a16; --gold-d: #e8c98a; --gold-text: #e8c98a;
    --green: #56b78c; --green-w: #123026; --green-b: #1e4a38; --green-hi: #6ccb9f;
    --clay: #e08368; --clay-w: #3a1d15; --clay-b: #52281d; --clay-d: #f0a58c;
    --track: #262c37; --dim: #2a303b; --dim-2: #333a46; --input: #6b7488; --dash: #3a4250;
    --line-strong: #6b7488;
    --shadow: rgba(0, 0, 0, .55); --shadow-brand: rgba(0, 0, 0, .6);
    --scrim: rgba(5, 8, 14, .64); --scrim-2: rgba(5, 8, 14, .7);
    --glass: rgba(15, 18, 24, .9); --glass-2: rgba(23, 27, 35, .95);
    --row-present: #15241f;
    --plum-w: #2c2336; --plum: #c9a6df;
    --dot-mid: #4a70b4; --dot-low: #33405a;
    --ramp-0: #1b2333; --ramp-1: #24324d; --ramp-2: #35507f; --ramp-3: #4a70b4; --ramp-4: #7396dd; --ramp-5: #a9c4f2;
    --hist-0: #5a4a32; --hist-1: #7a6244; --hist-2: #35507f; --hist-3: #4a70b4; --hist-4: #93b2ec;
    --logo: url(../img/logo-dark.png);
    --focus: #93b2ec;
  }
}

/* The explicit choice always wins over the media query above. */
body[data-theme="dark"] {
  color-scheme: dark;
  --paper: #0f1218; --surface: #171b23; --surface-2: #1e232d; --surface-3: #241f16;
  --line: #2c3341; --hair: #242a35; --hair-2: #1f242e;
  --ink: #e8ebf1; --mut: #a6afc0; --faint: #9199a9; --faint-2: #88919f; --faint-3: #818a99;
  --brand: #223a70; --brand-hi: #2c4a8e; --brand-ink: #93b2ec; --brand-fg: #eef1f7;
  --brand-soft: #1c2740; --cream: #f2e4c8; --on-brand: #c9d4e8; --on-brand-dim: #aab8d6;
  --on-brand-ok: #a8e0c4; --on-brand-bad: #f0b9a8;
  --gold: #d5a556; --gold-w: #332910; --gold-b: #4a3a16; --gold-d: #e8c98a; --gold-text: #e8c98a;
  --green: #56b78c; --green-w: #123026; --green-b: #1e4a38; --green-hi: #6ccb9f;
  --clay: #e08368; --clay-w: #3a1d15; --clay-b: #52281d; --clay-d: #f0a58c;
  --track: #262c37; --dim: #2a303b; --dim-2: #333a46; --input: #6b7488; --dash: #3a4250;
  --line-strong: #6b7488;
  --shadow: rgba(0, 0, 0, .55); --shadow-brand: rgba(0, 0, 0, .6);
  --scrim: rgba(5, 8, 14, .64); --scrim-2: rgba(5, 8, 14, .7);
  --glass: rgba(15, 18, 24, .9); --glass-2: rgba(23, 27, 35, .95);
  --row-present: #15241f;
  --plum-w: #2c2336; --plum: #c9a6df;
  --dot-mid: #4a70b4; --dot-low: #33405a;
  --ramp-0: #1b2333; --ramp-1: #24324d; --ramp-2: #35507f; --ramp-3: #4a70b4; --ramp-4: #7396dd; --ramp-5: #a9c4f2;
  --hist-0: #5a4a32; --hist-1: #7a6244; --hist-2: #35507f; --hist-3: #4a70b4; --hist-4: #93b2ec;
  --logo: url(../img/logo-dark.png);
  --focus: #93b2ec;
}

/* Tone pairs. A component reads var(--tone) and never names a colour itself. */
.tone--brand { --tone: var(--brand-ink); --tone-w: var(--brand-soft); --tone-b: var(--line); --tone-text: var(--brand-ink); }
.tone--green { --tone: var(--green); --tone-w: var(--green-w); --tone-b: var(--green-b); --tone-text: var(--green); }
.tone--gold  { --tone: var(--gold);  --tone-w: var(--gold-w);  --tone-b: var(--gold-b);  --tone-text: var(--gold-text); }
.tone--clay  { --tone: var(--clay);  --tone-w: var(--clay-w);  --tone-b: var(--clay-b);  --tone-text: var(--clay); }
.tone--muted { --tone: var(--mut);   --tone-w: var(--hair);    --tone-b: var(--line);    --tone-text: var(--mut); }
.tone--ink   { --tone: var(--ink);   --tone-w: var(--hair);    --tone-b: var(--line);    --tone-text: var(--ink); }

/* ==========================================================================
   2  RESET AND BASE
   ========================================================================== */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  background: var(--paper); color: var(--ink);
  font-family: var(--font-ui); font-size: 14px; line-height: 1.5;
  transition: background-color .25s ease, color .25s ease;
  -webkit-font-smoothing: antialiased; text-wrap: pretty;
  overflow-x: hidden;
}
img, svg, video { max-width: 100%; }
img { height: auto; display: block; }
a { color: var(--brand-ink); text-decoration: none; }
a:hover { color: var(--gold-text); }
a:not([class]) { text-decoration: underline; text-underline-offset: 2px; }
button { font-family: inherit; cursor: pointer; border: 0; background: none; color: inherit; }
input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }

/* A <fieldset> is used here purely to group radios for a screen reader. The
   user-agent's border, padding and notched legend are decoration nobody asked
   for, and they drew a stray box around the register's status panel. */
fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
legend { padding: 0; }
h1, h2, h3, h4 { margin: 0; font-weight: 400; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
hr { border: 0; border-top: 1px solid var(--hair); margin: 0; }
[hidden] { display: none !important; }

/* A two-tone ring: the inner stroke reads on pale surfaces, the outer halo
   keeps it visible on the navy buttons, hero cards and the active nav pill. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-halo); border-radius: 4px;
}
.on-brand :focus-visible, .btn--primary:focus-visible, .btn--cream:focus-visible,
.navitem[aria-current="page"]:focus-visible {
  --focus: var(--cream); --focus-halo: var(--brand-hi);
}

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--dash); border-radius: 9px; }
::-webkit-scrollbar-track { background: transparent; }

::selection { background: var(--brand-soft); color: var(--brand-ink); }

/* assets/js/app.js puts a .js class on <html> once it has booted, so a control
   that only works with script can hide itself until then. */
/* Hidden only while JavaScript is absent. Written as a negation on purpose:
   the obvious `.js .requires-js { display: revert }` reverts to the USER-AGENT
   value, which threw away .btn's inline-flex and left every icon button
   rendering its icon above its label. Nothing to undo means nothing to get
   wrong. */
html:not(.js) .requires-js { display: none; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  background: var(--brand); color: #fff; font-weight: 700; font-size: 13px;
  padding: 11px 16px; border-radius: 0 0 11px 11px; transition: top .15s ease;
}
.skip-link:focus { top: 0; color: #fff; }

/* ==========================================================================
   3  TYPOGRAPHY UTILITIES
   .micro  — the uppercase mono micro-label (the most repeated element)
   .stat   — the Newsreader numeral
   .display— Newsreader headings
   ========================================================================== */
.micro {
  font-family: var(--font-mono); font-size: var(--micro-size, 9.5px);
  letter-spacing: var(--micro-track, .13em); text-transform: uppercase;
  color: var(--micro-colour, var(--faint)); line-height: 1.3;
}
.micro--tight { --micro-track: .1em; }
.micro--wide { --micro-track: .16em; }
.micro--sm { --micro-size: 9px; }
.micro--lg { --micro-size: 10px; }
.micro--gold { --micro-colour: var(--gold-text); }
.micro--on-brand { --micro-colour: var(--on-brand-dim); }
.micro--plain { text-transform: none; letter-spacing: 0; }

.stat {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--stat-size, 27px); letter-spacing: var(--stat-track, -1px);
  line-height: 1; color: var(--tone, var(--ink));
  font-variant-numeric: tabular-nums;
}
.display {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--display-size, 21px); letter-spacing: -.6px; line-height: 1.12;
  color: var(--ink);
}
.lede { font-size: 15px; line-height: 1.55; color: var(--mut); max-width: 42ch; }
.body-copy { font-size: 13px; line-height: 1.5; color: var(--mut); }
.row-name { font-size: 14px; font-weight: 600; letter-spacing: -.2px; color: var(--ink); }
.truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.numeric { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   4  LAYOUT PRIMITIVES
   ========================================================================== */
.app { min-height: 100vh; min-height: 100dvh; display: flex; background: var(--paper); }
.app__col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app__content {
  flex: 1; min-width: 0;
  padding: clamp(16px, 3vw, 26px) clamp(16px, 3vw, 26px)
           calc(112px + env(safe-area-inset-bottom, 0px));
}
.screen { display: flex; flex-direction: column; gap: var(--screen-gap, 14px); animation: fadeUp .3s ease both; }
.screen--roomy { --screen-gap: 18px; }
.screen--narrow { max-width: 920px; }

/* The one grid utility. --grid-min guards against phone overflow with
   min(100%, N) — a plain minmax(N, 1fr) overflows below N. */
.grid {
  display: grid; gap: var(--grid-gap, 14px);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 300px)), 1fr));
}
.grid--3up { grid-template-columns: repeat(3, 1fr); }
/* The widths the design actually uses, so a template never writes an inline
   grid. Pair with --gap-sm / --gap-md where the design tightens the gutters. */
.grid--110 { --grid-min: 110px; } .grid--130 { --grid-min: 130px; } .grid--150 { --grid-min: 150px; }
.grid--170 { --grid-min: 170px; } .grid--190 { --grid-min: 190px; } .grid--220 { --grid-min: 220px; }
.grid--260 { --grid-min: 260px; } .grid--290 { --grid-min: 290px; } .grid--320 { --grid-min: 320px; }
.grid--330 { --grid-min: 330px; } .grid--340 { --grid-min: 340px; }
.grid--gap-xs { --grid-gap: 8px; } .grid--gap-sm { --grid-gap: 10px; }
.grid--gap-md { --grid-gap: 12px; } .grid--gap-lg { --grid-gap: 13px; }
.span-all { grid-column: 1 / -1; }
.row { display: flex; align-items: center; gap: var(--row-gap, 12px); min-width: 0; }
.row--wrap { flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.row--baseline { align-items: baseline; }
.stack { display: flex; flex-direction: column; gap: var(--stack-gap, 6px); min-width: 0; }
.spacer { flex: 1; min-width: 0; }

/* A name squeezed between an avatar and two status pills becomes "Visual
   Che…" on a phone. Give it the whole first line and let the pills wrap
   underneath, where there is room to read them. */
@media (max-width: 539px) {
  /* Wherever it appears, not only in a .listrow: the identity column is the
     one that must stay readable, so on a phone it takes the whole first line
     and whatever shared the row with it wraps underneath.

     .listrow needs the wrap for that to mean anything — without it a 100%
     basis cannot break the line, so the items simply shrink and the name is
     cut to "Aaro…" instead. */
  .listrow { flex-wrap: wrap; }
  /* A floor, not a full line. At 100% the avatar that precedes the name was
     pushed onto a line of its own; a 60% minimum keeps the two together and
     still leaves too little room for a pill or a button, so those wrap. */
  .identity { flex: 1 1 auto; min-width: 60%; }
  .submitbar .identity ~ .btn { width: 100%; justify-content: center; }
}
/* ---------------------------------------------------------------------------
   Horizontal scroll containers.
   ---------------------------------------------------------------------------
   `contain: inline-size` is the part that matters, and it is not decoration.
   On a phone Chromium sizes the LAYOUT VIEWPORT to the widest intrinsic
   content on the page — and an `overflow-x: auto` ancestor does not stop that
   propagation. The 620px attendance heatmap therefore stretched the viewport
   itself to 633px: every fixed bar sized against it, so the bottom navigation
   laid its five items out across 633px and "More" sat off the screen.

   Containment stops the inner width escaping. The table still renders at its
   full width and still scrolls; the page around it no longer knows about it.
   --------------------------------------------------------------------------- */
.scroll-x, .table-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  contain: layout inline-size;
}

/* ==========================================================================
   5  NAVIGATION
   Both navs render on every page; CSS decides which one is visible, so a
   resize never needs a reload.
   ========================================================================== */
.sidebar { display: none; }
@media (min-width: 900px) {
  .sidebar {
    width: 250px; flex: 0 0 250px; background: var(--surface);
    border-right: 1px solid var(--line); display: flex; flex-direction: column;
    position: sticky; top: 0; height: 100vh; height: 100dvh;
  }
}
.sidebar__brand { padding: 20px 18px 16px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--hair); }
.brandmark { display: block; width: 38px; height: 38px; flex: 0 0 38px; background: var(--logo) center / contain no-repeat; }
.brandmark--sm { width: 34px; height: 34px; flex: 0 0 34px; }
.brandmark--lg { width: 52px; height: 52px; flex: 0 0 52px; }
.sidebar__name { font-size: 12.5px; font-weight: 700; letter-spacing: -.15px; }
.sidebar__nav { flex: 1; overflow-y: auto; padding: 14px 10px; display: flex; flex-direction: column; gap: 2px; }
.sidebar__foot { padding: 12px 10px; border-top: 1px solid var(--hair); }
.sidebar__user { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 11px; background: var(--surface-2); }

.navitem {
  display: flex; align-items: center; gap: 11px; padding: 10px 12px; border-radius: 10px;
  font-size: 13.5px; font-weight: 500; letter-spacing: -.15px; color: var(--mut);
  min-height: var(--tap); width: 100%; text-align: left; background: transparent;
}
.navitem:hover { background: var(--hair); color: var(--ink); }
.navitem[aria-current="page"] { background: var(--brand); color: #fff; font-weight: 700; }
.navitem[aria-current="page"]:hover { background: var(--brand-hi); color: #fff; }
.navitem__tick { width: 3px; height: 15px; flex: 0 0 3px; border-radius: 2px; background: transparent; }
.navitem[aria-current="page"] .navitem__tick { background: var(--cream); }
.navitem__icon { flex: 0 0 18px; width: 18px; height: 18px; }
.navitem__label { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navitem__badge {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; padding: 2px 6px;
  border-radius: 6px; background: var(--clay-w); color: var(--clay);
}
.navitem[aria-current="page"] .navitem__badge { background: rgba(255, 255, 255, .22); color: #fff; }

/* .bottomnav is defined once, in section 19. The earlier scrolling version
   was removed rather than overridden — two definitions is how overflow-x: auto
   survived a rewrite and widened the bar past the phone. */
/* .navitem--bottom is defined once, in section 19. The earlier version pinned
   each slot to a 74px minimum, which is what pushed the bar past a 375px
   phone once there were five of them. */
.navitem--bottom:hover { color: var(--brand-ink); }

.hdr {
  position: sticky; top: 0; z-index: var(--z-header);
  background: var(--glass); backdrop-filter: blur(10px); border-bottom: 1px solid var(--line);
}
/* .hdr__inner is defined once, in section 19; the earlier version wrapped. */
.hdr__crumb { --micro-track: .15em; }
.hdr__title {
  font-family: var(--font-display); font-weight: 400; font-size: clamp(21px, 3vw, 28px);
  letter-spacing: -.6px; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hdr__today { display: none; align-items: center; gap: 7px; padding: 8px 12px; border-radius: 10px; background: var(--surface); border: 1px solid var(--line); }
.dot { width: 6px; height: 6px; flex: 0 0 6px; border-radius: 50%; background: var(--tone, var(--green)); }
.dot--lg { width: 8px; height: 8px; flex: 0 0 8px; }

/* ==========================================================================
   6  CARDS, KPIs, HERO, SECTION HEADS, EMPTY STATES
   ========================================================================== */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 18px; min-width: 0; }
.card--flush { padding: 0; overflow: hidden; }
.card--tint { background: var(--surface-2); }
.card--outline-green { border-color: var(--green-b); }
.card--outline-gold { border-color: var(--gold-b); }
.card--outline-clay { border-color: var(--clay-b); }
.card--tone { border-color: var(--tone-b, var(--line)); }
.card--brand {
  background: var(--brand); color: var(--brand-fg); border: 0; border-radius: 18px;
  padding: clamp(20px, 3vw, 28px);
}
.card--brand a:not([class]), .on-brand a:not([class]) { color: var(--cream); }
.card__head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 15px; }
.card__headings { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.card__title { font-size: 14.5px; font-weight: 700; letter-spacing: -.2px; }
.card__sub { font-size: 12px; color: var(--faint); }
.card__actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.card__body { padding: 16px 18px; }
.card__foot { padding: 13px 18px; border-top: 1px solid var(--hair-2); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

.hero { display: flex; flex-wrap: wrap; gap: 22px; align-items: center; justify-content: space-between; }
.hero__value { font-family: var(--font-display); font-size: clamp(46px, 8vw, 72px); line-height: .9; letter-spacing: -3px; color: var(--cream); }
.hero__note { font-size: 13.5px; color: var(--on-brand); max-width: 44ch; line-height: 1.5; }
.hero__stats { display: flex; flex-wrap: wrap; column-gap: 30px; row-gap: 15px; }
.hero__stat { display: flex; flex-direction: column; gap: 6px; padding-left: 17px; border-left: 1px solid rgba(255, 255, 255, .2); min-width: 78px; }
.hero__stat .stat { --stat-size: 27px; --stat-track: -1.1px; color: var(--brand-fg); }

.kpi {
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  padding: 17px 18px; display: flex; flex-direction: column; gap: 9px; min-width: 0;
}
.kpi--md { border-radius: 14px; padding: 15px 16px; gap: 7px; }
.kpi--sm { border-radius: 14px; padding: 14px 15px; gap: 6px; }
.kpi--xs { border-radius: 13px; padding: 11px 15px; gap: 3px; }
.kpi__label { --micro-size: 9.5px; --micro-track: .13em; }
.kpi__row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.kpi__value { --stat-size: 32px; --stat-track: -1.2px; }
.kpi--md .kpi__value { --stat-size: 29px; --stat-track: -1.1px; }
.kpi--sm .kpi__value { --stat-size: 26px; }
.kpi--xs .kpi__value { --stat-size: 23px; --stat-track: -.9px; }
.kpi__note { font-size: 11.5px; color: var(--mut); line-height: 1.4; }

.statgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 110px), 1fr)); gap: 9px; }
.stattile { background: var(--surface-2); border: 1px solid var(--hair); border-radius: 12px; padding: 12px 13px; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.stattile__label { --micro-size: 9px; --micro-track: .12em; }
.stattile__value { font-size: 16px; font-weight: 700; letter-spacing: -.4px; color: var(--tone, var(--ink)); }
.stattile__note { font-size: 11px; color: var(--faint); }

.empty { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: var(--empty-pad, 44px 20px); }
.empty__badge {
  width: var(--empty-badge, 44px); height: var(--empty-badge, 44px); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--tone-w, var(--hair)); color: var(--tone, var(--faint)); flex: 0 0 auto;
}
.empty__badge svg { width: 20px; height: 20px; }
.empty__title { font-size: 14px; font-weight: 700; }
.empty__msg { font-size: 12.5px; color: var(--faint); max-width: 38ch; line-height: 1.5; }
.empty__action { margin-top: 6px; }

/* ==========================================================================
   7  BUTTONS, TABS, TOGGLES
   Heights are the prototype's. Anything the prototype drew below 44px keeps
   its visual box but gains a transparent 44px hit area (::after), because
   leaders mark registers standing up, one-handed.
   ========================================================================== */
.btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: var(--btn-h, 40px); padding: 0 var(--btn-pad, 15px); border-radius: var(--btn-r, 10px);
  font-size: var(--btn-size, 12.5px); font-weight: 700; letter-spacing: -.1px;
  white-space: nowrap; text-align: center;
  transition: background-color .14s ease, border-color .14s ease, color .14s ease;
}
.btn--h34 { --btn-h: 34px; --btn-pad: 12px; --btn-r: 9px; --btn-size: 11.5px; }
.btn--h38 { --btn-h: 38px; --btn-pad: 15px; --btn-r: 10px; --btn-size: 12px; }
.btn--h42 { --btn-h: 42px; --btn-pad: 17px; --btn-r: 11px; }
.btn--h46 { --btn-h: 46px; --btn-pad: 18px; --btn-r: 11px; --btn-size: 13.5px; }
.btn--h48 { --btn-h: 48px; --btn-pad: 22px; --btn-r: 12px; --btn-size: 14.5px; }
.btn--h52 { --btn-h: 52px; --btn-pad: 24px; --btn-r: 12px; --btn-size: 15px; }
.btn--block { width: 100%; }
.btn--grow { flex: 1; min-width: 88px; }
.btn svg { width: 16px; height: 16px; flex: 0 0 16px; }
.btn[aria-disabled="true"], .btn:disabled { opacity: .5; cursor: not-allowed; }
/* Buttons shorter than the 44px target keep a transparent hit area. */
.btn--h34::after, .btn--h38::after, .btn--icon-sm::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: var(--tap); transform: translateY(-50%);
}

.btn--primary { background: var(--brand); color: #fff; }
.btn--primary:hover { background: var(--brand-hi); color: #fff; }
.btn--cream { background: var(--cream); color: var(--brand-hi); }
.btn--cream:hover { background: var(--surface); color: var(--brand-hi); }
.btn--ghost { background: var(--surface); border: 1px solid var(--line-strong); color: var(--mut); }
.btn--ghost:hover { border-color: var(--brand-ink); color: var(--brand-ink); }
.btn--soft { background: var(--tone-w, var(--hair)); color: var(--tone-text, var(--mut)); }
.btn--soft:hover { background: var(--tone, var(--dim-2)); color: var(--surface); }
.btn--danger { background: var(--surface); border: 1px solid var(--clay-b); color: var(--clay); }
.btn--danger:hover { background: var(--clay-w); }
.btn--link { height: auto; padding: 0; background: none; font-size: 12.5px; font-weight: 700; color: var(--brand-ink); }
.btn--link:hover { color: var(--gold-text); text-decoration: underline; text-underline-offset: 3px; }

.btn--icon { width: var(--btn-h, 44px); height: var(--btn-h, 44px); padding: 0; flex: 0 0 auto; border-radius: var(--btn-r, 11px); }
.btn--icon-sm { --btn-h: 34px; --btn-r: 9px; }
.btn--icon svg { width: 17px; height: 17px; }

.tabs { display: flex; flex-wrap: wrap; gap: 7px; }
.tab {
  display: inline-flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px;
  padding: 9px 14px; min-height: var(--tab-h, 46px); border-radius: var(--tab-r, 11px);
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--mut);
  font-size: var(--tab-size, 13px); font-weight: 500; letter-spacing: -.15px;
  transition: background-color .14s ease, border-color .14s ease, color .14s ease;
}
.tab:hover { border-color: var(--brand-ink); color: var(--brand-ink); }
.tab[aria-pressed="true"], .tab[aria-current="true"], .tab[aria-selected="true"] {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 700;
}
.tab--md { --tab-h: 44px; --tab-r: 10px; --tab-size: 12.5px; }
.tab--sm { --tab-h: 44px; --tab-r: 9px; --tab-size: 11.5px; padding: 8px 12px; }
.tab__day { font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; opacity: .72; }

/* Square icon button. Both glyphs ship; CSS picks one, so switching the theme
   never changes the button's width and the header never reflows. */
.themebtn {
  width: var(--tap); height: var(--tap); padding: 0; border-radius: 10px;
  background: var(--surface); border: 1px solid var(--line-strong);
  color: var(--faint); display: inline-grid; place-items: center; flex: 0 0 auto;
}
.themebtn:hover { border-color: var(--brand-ink); color: var(--brand-ink); }
.themebtn svg { width: 18px; height: 18px; display: block; }
.themebtn__moon { display: none; }
body[data-theme="dark"] .themebtn__sun { display: none; }
body[data-theme="dark"] .themebtn__moon { display: block; }
@media (prefers-color-scheme: dark) {
  body:not([data-theme="light"]) .themebtn__sun { display: none; }
  body:not([data-theme="light"]) .themebtn__moon { display: block; }
}

/* The P / A register toggle. Real radios, visually a pair of pills, with the
   state carried by a glyph as well as the fill so colour is never alone. */
.mark { display: flex; gap: 5px; flex: 0 0 auto; border: 0; padding: 0; margin: 0; }
.mark__opt { position: relative; }
.mark__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.mark__pill, .mark__btn {
  display: flex; align-items: center; justify-content: center; gap: 3px;
  width: 46px; height: var(--tap); border-radius: 11px;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--faint);
  font-family: var(--font-mono); font-size: 14px; font-weight: 700;
  transition: background-color .14s ease, border-color .14s ease, color .14s ease;
}
.mark__input:checked + .mark__pill { color: #fff; }
.mark__opt--present .mark__input:checked + .mark__pill { background: var(--green); border-color: var(--green); }
.mark__opt--absent .mark__input:checked + .mark__pill { background: var(--clay); border-color: var(--clay); }
.mark__input:checked + .mark__pill::after { content: "✓"; font-size: 10px; }
.mark__opt--absent .mark__input:checked + .mark__pill::after { content: "✕"; }
.mark__input:focus-visible + .mark__pill { outline: 2px solid var(--focus); outline-offset: 2px; box-shadow: 0 0 0 4px var(--surface); }
.mark__input:disabled + .mark__pill { opacity: .45; }
/* Same pill, for a register built from <button aria-pressed> instead of
   radios. Radios are the better default — they submit without JavaScript —
   but both are supported so the register can be enhanced either way. */
.mark__btn[aria-pressed="true"] { color: #fff; }
.mark__btn[aria-pressed="true"]::after { content: "✓"; font-size: 10px; }
.mark__opt--present .mark__btn[aria-pressed="true"] { background: var(--green); border-color: var(--green); }
.mark__opt--absent .mark__btn[aria-pressed="true"] { background: var(--clay); border-color: var(--clay); }
.mark__opt--absent .mark__btn[aria-pressed="true"]::after { content: "✕"; }
.mark__btn[aria-disabled="true"] { opacity: .45; }

/* ==========================================================================
   8  FORMS AND SEARCH
   ========================================================================== */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field__label { --micro-size: 10px; --micro-track: .14em; --micro-colour: var(--mut); }
.field__hint { font-size: 11.5px; color: var(--faint); }
.field__error { font-size: 12px; font-weight: 600; color: var(--clay); }
.input, .select, .textarea {
  height: var(--field-h, 46px); padding: 0 13px; border: 1px solid var(--input);
  border-radius: 11px; background: var(--surface); font-size: 14px; width: 100%; min-width: 0;
}
.textarea { height: auto; min-height: 96px; padding: 12px 13px; line-height: 1.5; resize: vertical; }
.select { padding-right: 34px; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentcolor 50%), linear-gradient(135deg, currentcolor 50%, transparent 50%);
  background-position: calc(100% - 17px) calc(50% - 1px), calc(100% - 12px) calc(50% - 1px);
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
/* A file input had no styling of its own, so it inherited .input's fixed
   height and horizontal padding — and the browser's own "Choose File" button
   sits inside that box without being centred by either. The result was a
   control visibly off its own baseline. Styled here as what it actually is:
   a button and a filename sharing one field. */
.input[type="file"] {
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px 0 0; cursor: pointer; line-height: var(--field-h, 46px);
  /* A text field earns the full column; a file picker does not. Stretched to
     900px it reads as a banner rather than a control, and the filename it has
     to show is never that long. */
  max-width: 30rem;
}
.input[type="file"]::file-selector-button {
  height: calc(var(--field-h, 46px) - 2px); margin-right: 2px; padding: 0 15px;
  border: 0; border-right: 1px solid var(--input); border-radius: 10px 0 0 10px;
  background: var(--surface-2); color: var(--ink);
  font: inherit; font-weight: 600; cursor: pointer;
  transition: background-color var(--duration-fast) ease;
}
.input[type="file"]::file-selector-button:hover { background: var(--hair); }
.input[type="file"]:focus-within { border-color: var(--brand-ink); }

.input--lg, .select--lg { --field-h: 50px; font-size: 15px; padding-left: 15px; }
.input--sm, .select--sm { --field-h: 44px; font-size: 12.5px; font-weight: 600; padding-left: 11px; }
.input[aria-invalid="true"], .select[aria-invalid="true"] { border-color: var(--clay); }
.input::placeholder, .textarea::placeholder { color: var(--faint-3); }

.search {
  flex: 1; min-width: min(100%, 190px); display: flex; align-items: center; gap: 9px;
  height: var(--tap); padding: 0 12px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--line-strong);
}
.search:focus-within { border-color: var(--brand-ink); }
/* A search that sits above the control it narrows rather than beside a list. */
.search--sm { --tap: 40px; height: 40px; margin-bottom: 7px; }
.search--sm .search__input { font-size: 12.5px; }
.search__icon { color: var(--faint); flex: 0 0 16px; width: 16px; height: 16px; }
.search__input { flex: 1; min-width: 0; height: 100%; border: 0; background: transparent; font-size: 13.5px; }
.search__input:focus-visible { outline: 0; box-shadow: none; }
.search__count { --micro-size: 10.5px; --micro-track: 0; text-transform: none; }
.search--inline { border: 0; border-bottom: 1px solid var(--hair); border-radius: 0; background: var(--surface); padding: 11px 17px; height: auto; min-width: 0; }

.switch { display: inline-flex; align-items: center; gap: 10px; min-height: var(--tap); }
.switch__track { width: 42px; height: 24px; flex: 0 0 42px; border-radius: 12px; background: var(--dim-2); position: relative; transition: background-color .15s ease; }
.switch__track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: var(--surface); transition: transform .15s ease; }
.switch__input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__input:checked + .switch__track { background: var(--green); }
.switch__input:checked + .switch__track::after { transform: translateX(18px); }
.switch__input:focus-visible + .switch__track { outline: 2px solid var(--focus); outline-offset: 3px; }

.toolbar { padding: 13px 16px; border-bottom: 1px solid var(--hair); display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }

/* ==========================================================================
   9  PILLS, DELTAS, AVATARS
   ========================================================================== */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-mono); font-size: var(--pill-size, 9.5px);
  letter-spacing: var(--pill-track, .1em); text-transform: uppercase;
  padding: var(--pill-pad, 3px 7px); border-radius: var(--pill-r, 6px);
  background: var(--tone-w, var(--hair)); color: var(--tone-text, var(--mut));
  flex: 0 0 auto; white-space: nowrap; line-height: 1.4;
}
.pill--outline { background: transparent; border: 1px solid var(--tone-b, var(--line)); }
.pill--lg { --pill-size: 10.5px; --pill-pad: 5px 9px; --pill-r: 7px; }
.pill--plain { text-transform: none; letter-spacing: 0; }
.pill__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--tone); flex: 0 0 6px; }

.delta {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; padding: 3px 7px;
  border-radius: 7px; white-space: nowrap;
  background: var(--tone-w, var(--hair)); color: var(--tone-text, var(--mut));
}
.delta--on-brand { background: rgba(255, 255, 255, .14); color: var(--on-brand-ok); font-size: 12px; padding: 5px 10px; border-radius: 8px; }
.delta--on-brand.tone--clay { color: var(--on-brand-bad); }

.avatar {
  width: var(--avatar-size, 38px); height: var(--avatar-size, 38px); flex: 0 0 var(--avatar-size, 38px);
  border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: calc(var(--avatar-size, 38px) * .35); font-weight: 700; letter-spacing: -.3px;
  background: var(--brand-soft); color: var(--brand-ink); overflow: hidden;
}
.avatar[data-tone="0"] { background: var(--brand-soft); color: var(--brand-ink); }
.avatar[data-tone="1"] { background: var(--gold-w); color: var(--gold-text); }
.avatar[data-tone="2"] { background: var(--green-w); color: var(--green); }
.avatar[data-tone="3"] { background: var(--plum-w); color: var(--plum); }
.avatar[data-tone="4"] { background: var(--clay-w); color: var(--clay); }
.avatar--empty { background: var(--hair); color: var(--mut); font-weight: 400; }

/* ==========================================================================
   10  TABLES AND LIST ROWS
   The prototype drew tables out of flex rows. These are real <table>s so a
   screen reader announces the column a figure belongs to; the flex look is
   kept inside the cells.
   ========================================================================== */
.table { width: 100%; border-collapse: collapse; min-width: var(--table-min, 0); }
.table thead th {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .11em; text-transform: uppercase;
  color: var(--faint); font-weight: 400; text-align: left; padding: 9px 16px;
  background: var(--surface-2); border-bottom: 1px solid var(--hair); white-space: nowrap;
}
.table td { padding: 11px 16px; border-bottom: 1px solid var(--hair-2); vertical-align: middle; }
.table tbody tr { position: relative; background: var(--surface); }
.table tbody tr:hover { background: var(--surface-2); }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr[data-present="1"] { background: var(--row-present); }
.table tbody tr[data-severity] td:first-child { box-shadow: inset 3px 0 0 var(--tone, var(--gold)); }
.table .num, .table th.num { text-align: right; }
.table__person { display: flex; align-items: center; gap: 12px; min-width: 0; }
.table__meta { --micro-size: 10px; --micro-track: 0; text-transform: none; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.table__foot { padding: 14px 16px; border-top: 1px solid var(--hair); display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
/* One link per row, stretched over the whole row, so the row is one tab stop. */
.rowlink::after { content: ""; position: absolute; inset: 0; }
.rowlink:focus-visible { outline: 0; box-shadow: none; }
.table tbody tr:focus-within { background: var(--surface-2); outline: 2px solid var(--focus); outline-offset: -2px; }

.listrow {
  display: flex; align-items: center; gap: 11px; padding: 10px 11px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--hair); min-width: 0;
}
.listrow--tone { background: var(--tone-w); border-color: var(--tone-b); }
.listrow--plain { background: transparent; border: 0; border-bottom: 1px solid var(--hair-2); border-radius: 0; padding: 9px 0; }
.kvrow { display: flex; gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--hair-2); align-items: baseline; }
.kvrow:last-child { border-bottom: 0; }
.kvrow__key { width: 120px; flex: 0 0 120px; font-size: 12px; color: var(--faint); }
.kvrow__val { flex: 1; min-width: 0; font-size: 13px; font-weight: 500; line-height: 1.5; overflow-wrap: anywhere; }

/* ==========================================================================
   11  DATA VISUALISATION
   Every chart is server-rendered SVG: identical in print, no JS, no library.
   Each one carries a text alternative — a caption, a hidden table or an
   aria-label — because colour alone is never a reading of the data.
   ========================================================================== */
.meter { height: var(--meter-h, 7px); border-radius: 4px; background: var(--track); overflow: hidden; flex: 1; min-width: 40px; }
.meter__fill { height: 100%; border-radius: 4px; background: var(--tone, var(--brand-ink)); width: var(--meter-pct, 0%); display: block; }
.meter--sm { --meter-h: 6px; }
.meter--lg { --meter-h: 8px; }

.ring { flex: 0 0 auto; display: block; }
.ring__track { stroke: var(--hair); }
.ring__value { stroke: var(--tone, var(--brand-ink)); }
.ring__text { fill: var(--ink); font-family: var(--font-mono); font-size: 12px; font-weight: 600; }

.spark { display: flex; gap: 4px; align-items: flex-end; min-width: 0; }
.spark__bar { flex: 1 1 0; min-width: 4px; height: var(--spark-h, 22px); border-radius: 3px; background: var(--dim); display: block; }
.spark__bar[data-band="green"] { background: var(--green); }
.spark__bar[data-band="gold"] { background: var(--gold); opacity: .88; }
.spark__bar[data-band="clay"] { background: var(--clay); opacity: .8; }

.dots { display: inline-flex; gap: 3px; align-items: center; }
.dots__dot { width: var(--dot-w, 5px); height: var(--dot-h, 16px); border-radius: 2px; background: var(--dim); display: block; flex: 0 0 var(--dot-w, 5px); }
.dots__dot[data-band="on"] { background: var(--brand-ink); }
.dots__dot[data-band="mid"] { background: var(--dot-mid); }
.dots__dot[data-band="low"] { background: var(--dot-low); }

.squares { display: flex; gap: 4px; align-items: center; min-width: min-content; }
.squares__cell { width: 16px; height: 16px; border-radius: 4px; background: var(--dim); border: 1px solid var(--dim-2); display: block; flex: 0 0 16px; }
.squares__cell[data-on="1"] { background: var(--brand-ink); border-color: transparent; }

.heat { border-collapse: separate; border-spacing: 4px; min-width: 620px; }
.heat caption { caption-side: top; text-align: left; font-size: 12px; color: var(--faint); padding-bottom: 8px; }
.heat th { font-weight: 600; font-size: 11.5px; text-align: left; }
.heat thead th { font-family: var(--font-mono); font-size: 9px; font-weight: 400; color: var(--faint-2); text-align: center; letter-spacing: .04em; }
.heat thead th span { display: block; font-size: 9px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint-3); }
.heat tbody th { width: 122px; max-width: 122px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.heat thead td { background: transparent; }
.heat tbody td { height: 26px; min-width: 24px; border-radius: 4px; padding: 0; background: var(--ramp-0); }
.heat tbody td[data-shade="1"] { background: var(--ramp-1); }
.heat tbody td[data-shade="2"] { background: var(--ramp-2); }
.heat tbody td[data-shade="3"] { background: var(--ramp-3); }
.heat tbody td[data-shade="4"] { background: var(--ramp-4); }
.heat tbody td[data-shade="5"] { background: var(--ramp-5); }
.heat-legend { display: flex; gap: 9px; align-items: center; margin-top: 9px; }
.heat-legend__swatch { width: 22px; height: 11px; border-radius: 3px; display: block; }

.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart__grid { stroke: var(--hair); stroke-width: 1; }
.chart__area { fill: var(--brand-soft); }
.chart__line { fill: none; stroke: var(--brand-ink); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.chart__dot { fill: var(--surface); stroke: var(--brand-ink); stroke-width: 2.5; }
.chart__axis { display: flex; justify-content: space-between; margin-top: 9px; font-family: var(--font-mono); font-size: 9.5px; color: var(--faint); }

/* ==========================================================================
   12  OVERLAYS
   ========================================================================== */
.submitbar {
  position: sticky; bottom: calc(78px + env(safe-area-inset-bottom, 0px)); z-index: var(--z-bar);
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
  padding: 14px 17px; border-radius: 16px; background: var(--brand); color: var(--brand-fg);
  box-shadow: 0 14px 36px var(--shadow-brand);
}
.submitbar__title { font-size: 13.5px; font-weight: 700; letter-spacing: -.2px; }
.submitbar__note { --micro-size: 10.5px; --micro-track: 0; --micro-colour: var(--on-brand-dim); text-transform: none; }

.toasts {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(96px + env(safe-area-inset-bottom, 0px)); z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: min(92vw, 420px); pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: 10px; width: 100%; pointer-events: auto;
  /* Navy rather than the prototype's --ink: --ink inverts with the theme and
     would render a pale toast with pale text in dark mode. Navy and the
     on-brand accents are the one pair that reads in both. */
  padding: 12px 14px 12px 16px; border-radius: 12px; background: var(--brand-hi); color: var(--brand-fg);
  font-size: 13.5px; font-weight: 500; line-height: 1.45; box-shadow: 0 12px 32px var(--shadow);
  animation: popIn .2s ease both;
}
.toast__icon { flex: 0 0 18px; width: 18px; height: 18px; margin-top: 1px; color: var(--tone, var(--on-brand)); }
.toast__msg { flex: 1; min-width: 0; }
.toast__close { flex: 0 0 auto; width: 28px; height: 28px; border-radius: 8px; color: inherit; opacity: .7; display: flex; align-items: center; justify-content: center; font-size: 17px; line-height: 1; }
.toast__action { flex: 0 0 auto; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; color: inherit; }
.toast__close:hover { opacity: 1; background: rgba(255, 255, 255, .12); }
.toast--success { --tone: var(--on-brand-ok); }
.toast--error { --tone: var(--on-brand-bad); }
.toast--warning { --tone: var(--cream); }

/* Drawer and modal are native <dialog>: focus trap, Escape and focus return
   come free, which is most of what the accessibility audit asked for.
   assets/js/app.js falls back to hidden/aria-modal on iOS before 15.4, and
   treats a click that lands on the <dialog> itself as a backdrop click — so
   the dialog element is the full-screen scrim and .drawer / .modal are the
   boxes drawn inside it. */
dialog { border: 0; padding: 0; margin: 0; color: inherit; max-width: 100%; max-height: 100%; background: transparent; }
dialog:not([open]) { display: none; }
dialog::backdrop { background: var(--scrim-2); }
.dialog {
  position: fixed; inset: 0; width: 100%; height: 100%; max-width: 100%; max-height: 100%;
  display: flex; z-index: var(--z-modal); background: var(--scrim-2);
}
/* The navigation drawer comes in from the LEFT, under the menu button that
   opened it. A panel that arrives from the opposite side of the screen to its
   own control reads as a different, unrelated thing. Right-hand sheets earn
   their place for secondary context — a filter tray, a cart — not for the
   main navigation. */
.dialog--drawer { z-index: var(--z-drawer); justify-content: flex-start; background: transparent; }
.dialog--drawer::backdrop { background: var(--scrim); }
.dialog--modal { align-items: center; justify-content: center; padding: 18px; }
/* Nothing behind a dialog should scroll away under the reader's thumb. */
body[data-dialog-open] { overflow: hidden; }
.drawer {
  margin: 0 auto 0 0; width: min(380px, 88%); height: 100%; max-height: 100%;
  background: var(--surface); overflow-y: auto; display: flex; flex-direction: column;
  box-shadow: 0 0 40px var(--shadow);
}
.drawer__head {
  padding: 20px 22px 18px; border-bottom: 1px solid var(--hair); display: flex; gap: 14px;
  align-items: flex-start; position: sticky; top: 0; background: var(--surface); z-index: 2;
}
.drawer__body { padding: 18px 22px; display: flex; flex-direction: column; gap: 20px; }
.modal {
  margin: auto; width: min(520px, 100%); max-height: 88vh; overflow-y: auto;
  background: var(--surface); border-radius: 18px; box-shadow: 0 24px 60px var(--shadow);
  animation: popIn .22s ease both;
}
.modal__head { padding: 20px 22px 14px; border-bottom: 1px solid var(--hair); display: flex; align-items: flex-start; gap: 12px; }
.modal__title { font-family: var(--font-display); font-size: 22px; letter-spacing: -.6px; line-height: 1.15; }
.modal__sub { font-size: 12.5px; color: var(--faint); line-height: 1.45; }
.modal__body { padding: 18px 22px; display: flex; flex-direction: column; gap: 13px; }
.modal__foot { padding: 14px 22px 20px; display: flex; gap: 9px; justify-content: flex-end; border-top: 1px solid var(--hair); flex-wrap: wrap; }

/* ==========================================================================
   13  SIGN-IN LAYOUT AND ERROR PAGE
   ========================================================================== */
.auth { min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); background: var(--paper); }
.auth__form { display: flex; flex-direction: column; justify-content: center; padding: clamp(32px, 6vw, 56px) clamp(20px, 6vw, 80px); max-width: 640px; margin: 0 auto; width: 100%; }
.auth__brand { display: flex; align-items: center; gap: 13px; margin-bottom: clamp(28px, 6vw, 48px); }
.auth__eyebrow { --micro-size: 10px; --micro-track: .18em; --micro-colour: var(--gold-text); margin-bottom: 14px; }
.auth__title { font-family: var(--font-display); font-size: clamp(34px, 5vw, 46px); letter-spacing: -1px; line-height: 1.05; margin: 0 0 14px; }
.auth__title em { font-style: italic; color: var(--mut); }
.auth__lede { margin: 0 0 clamp(24px, 4vw, 36px); }
.auth__fields { display: flex; flex-direction: column; gap: 14px; max-width: 420px; }
.auth__foot { display: flex; flex-wrap: wrap; gap: 8px 16px; justify-content: space-between; font-size: 13px; color: var(--mut); }
.auth__hero {
  background: var(--brand); color: var(--brand-fg); display: flex; flex-direction: column;
  justify-content: space-between; padding: clamp(28px, 5vw, 56px); min-height: 320px;
  position: relative; overflow: hidden;
}
.auth__hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.auth__hero-veil { position: absolute; inset: 0; background: linear-gradient(175deg, rgba(29, 47, 92, .86) 0%, rgba(23, 38, 76, .93) 55%, rgba(17, 26, 46, .96) 100%); }
.auth__hero > * { position: relative; z-index: 1; }
.auth__hero-rate { font-family: var(--font-display); font-size: clamp(56px, 11vw, 96px); line-height: .92; letter-spacing: -3px; color: var(--cream); }
.auth__hero-note { font-size: 14px; color: var(--on-brand); max-width: 34ch; line-height: 1.5; margin-top: 10px; }

/* Mobile first: on a phone the navy panel is a screenful of scrolling between
   the leader and the thing they came to do, so it is not rendered at all below
   the two-column breakpoint. It returns when there is width to spare. */
@media (max-width: 767px) {
  .auth { grid-template-columns: 1fr; min-height: 100dvh; }
  .auth__hero { display: none; }
  .auth__form { justify-content: flex-start; padding-top: 28px; }
}

/* Both of these sit under a field and are easy to miss with a thumb. */
.auth__foot a, .btn--link { min-height: var(--tap); display: inline-flex; align-items: center; }
.auth__service { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, .12); }
.auth__error { padding: 11px 14px; border-radius: 10px; background: var(--clay-w); color: var(--clay); font-size: 13px; font-weight: 500; }

.errorpage { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: clamp(20px, 5vw, 48px); background: var(--paper); }
.errorpage__card { width: min(560px, 100%); background: var(--surface); border: 1px solid var(--line); border-radius: 18px; padding: clamp(24px, 5vw, 36px); display: flex; flex-direction: column; gap: 14px; }
.errorpage__status { font-family: var(--font-display); font-size: clamp(52px, 12vw, 76px); line-height: .9; letter-spacing: -3px; color: var(--brand-ink); }
.errorpage__trace { font-family: var(--font-mono); font-size: 11.5px; line-height: 1.6; color: var(--mut); background: var(--surface-2); border: 1px solid var(--hair); border-radius: 12px; padding: 14px; overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere; }

/* ==========================================================================
   14  ANIMATION
   ========================================================================== */
@keyframes fadeUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes popIn { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
/* ---------------------------------------------------------------------------
   Drawer motion, both ways.
   ---------------------------------------------------------------------------
   The old version animated with @keyframes on open and did nothing at all on
   close: the panel simply blinked out, which is most of why it felt abrupt.
   A transition plus `allow-discrete` lets the same rule run in reverse, so the
   drawer slides back out and the scrim fades with it. @starting-style gives
   the opening frame its from-state.

   Browsers without allow-discrete (older Safari, older Firefox) ignore all of
   it and get the instant open/close they had before — no worse than today.
   --------------------------------------------------------------------------- */
.dialog--drawer,
.dialog--drawer .drawer,
.dialog--drawer::backdrop {
  transition: opacity var(--drawer-ms) var(--ease-out-quint),
              transform var(--drawer-ms) var(--ease-out-quint),
              display var(--drawer-ms) allow-discrete,
              overlay var(--drawer-ms) allow-discrete;
}
.dialog--drawer .drawer { transform: translateX(0); }
.dialog--drawer::backdrop { opacity: 1; }

/* Closed, and the frame an opening drawer starts from. */
.dialog--drawer:not([open]) .drawer { transform: translateX(-100%); }
.dialog--drawer:not([open])::backdrop { opacity: 0; }

@starting-style {
  .dialog--drawer[open] .drawer { transform: translateX(-100%); }
  .dialog--drawer[open]::backdrop { opacity: 0; }
}

@keyframes slideIn { from { transform: translateX(-100%); } to { transform: none; } }
@keyframes shimmer { 0% { opacity: .5; } 50% { opacity: 1; } 100% { opacity: .5; } }
.skeleton { background: var(--hair); border-radius: 8px; animation: shimmer 1.2s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   15  RESPONSIVE
   Base is the phone: bottom nav, no sidebar, the narrow column set.
   ========================================================================== */
/* Same trap as .requires-js: reverting display discards the element's own
   rule, so the narrow case hides and the wide case simply does not. */
@media (max-width: 899.98px) { .only-wide { display: none !important; } }
.col-cell, .col-phone, .col-last { display: none; }

@media (min-width: 900px) {
  .app__content { padding-bottom: clamp(24px, 3vw, 36px); }
  .bottomnav { display: none; }
  .toasts { bottom: 90px; }
  .only-wide { display: revert !important; }
  .only-narrow { display: none !important; }
  .hdr__today { display: flex; }
  .submitbar { bottom: 14px; }
  .col-status { display: table-cell; }
}
@media (min-width: 1180px) {
  .col-cell, .col-phone, .col-last { display: table-cell; }
}
@media (max-width: 899.98px) {
  .col-status { display: none; }
  .card--brand { border-radius: 16px; }
  .kvrow__key { width: 96px; flex: 0 0 96px; }
}
/* Below this the page title and the header controls cannot share a line
   without the title being clipped, so the controls drop to their own row. */
@media (max-width: 520px) { .hdr__titles { flex: 1 0 100%; } }
@media (max-width: 420px) {
  .card { padding: 15px; border-radius: 14px; }
  .toolbar, .table thead th, .table td { padding-left: 13px; padding-right: 13px; }
  .drawer__head, .drawer__body, .modal__head, .modal__body, .modal__foot { padding-left: 16px; padding-right: 16px; }
}
@media print { .app__content { padding: 0; } }

/* ==========================================================================
   16  SMALL PIECES THE PARTIALS DEPEND ON
   ========================================================================== */
.icon { width: 18px; height: 18px; flex: 0 0 auto; display: block; }
.stack--tight { --stack-gap: 1px; }
.sidebar__user-name { font-size: 12.5px; font-weight: 600; }
.sidebar__user-role { font-size: 10.5px; color: var(--faint); }
.hdr__today-text { --micro-size: 10.5px; --micro-colour: var(--mut); }
.heat-legend__swatch[data-shade="0"] { background: var(--ramp-0); }
.heat-legend__swatch[data-shade="1"] { background: var(--ramp-1); }
.heat-legend__swatch[data-shade="2"] { background: var(--ramp-2); }
.heat-legend__swatch[data-shade="3"] { background: var(--ramp-3); }
.heat-legend__swatch[data-shade="4"] { background: var(--ramp-4); }
.heat-legend__swatch[data-shade="5"] { background: var(--ramp-5); }
.chart { height: var(--chart-h, 210px); }
.sidebar__user form { display: flex; }
.hdr__actions form { display: flex; }
.stack--wide { --stack-gap: 11px; }
.display--md { --display-size: 26px; }
.row--snug { --row-gap: 9px; }
/* Two actions that belong together share the row equally. Left to their
   natural widths they run past the card on a 320px screen. */
.row--split > .btn { flex: 1 1 0; min-width: 0; }
/* A grant reads "Church Administrator for the whole church" — a sentence, not
   a tag. Left nowrap it runs straight out of the card on a phone. */
.row--grants .pill { flex: 0 1 auto; white-space: normal; text-align: left; }

/* The buttons at the end of a list row, kept together.
   Left loose they wrap one at a time, so a row with two actions drops the
   second one onto a line of its own while the first stays put — which reads
   as a mistake rather than a layout. As a group they wrap as a unit and stay
   pushed to the right edge, wrapped or not. */
.rowactions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; margin-left: auto; }

/* The bottom of a form: the action, then the one line that qualifies it.
   Stacked on a phone so neither is squeezed; side by side from 540px, where
   there is room for both without the note breaking every three words. */
.formfoot { display: grid; gap: 10px; justify-items: start; }
.formfoot > .btn { width: 100%; justify-content: center; }
.formfoot > p { margin: 0; }

/* ---------------------------------------------------------------------------
   Setup tiles. Three admin screens that had no way in but a link inside a
   sentence, so nobody found them. A tile says what the screen is FOR, because
   "Church calendar" and "Import" do not explain themselves.
   --------------------------------------------------------------------------- */
.setupgrid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
}
.setuptile {
  display: grid; gap: 6px; align-content: start;
  padding: 15px 16px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); color: inherit; text-decoration: none;
  transition: border-color var(--duration-fast) ease,
              transform var(--duration-fast) ease,
              box-shadow var(--duration-fast) ease;
}
.setuptile:hover { border-color: var(--brand-ink); transform: translateY(-2px); box-shadow: 0 10px 24px var(--shadow); }
.setuptile:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.setuptile__icon { display: flex; color: var(--brand-ink); }
.setuptile__icon svg { width: 19px; height: 19px; }
.setuptile__name { font-size: 13.5px; font-weight: 700; letter-spacing: -.2px; }
.setuptile__what { font-size: 12.5px; line-height: 1.45; color: var(--faint); }

@media (prefers-reduced-motion: reduce) {
  .setuptile:hover { transform: none; }
}

@media (min-width: 540px) {
  .formfoot { grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 14px; }
  .formfoot > .btn { width: auto; }
}

/* ==========================================================================
   17  MEMBER-DRAWER ATTENDANCE HISTORY ROW
   The one component in partials/member-drawer.twig with no equivalent
   elsewhere: a service name, its 14-week square strip and the trailing
   percentage, on one line that scrolls sideways rather than wrapping.
   The drawer's squares are 15px against the 16px used on Today, which is what
   the design draws — the strip has a narrower column to live in.
   ========================================================================== */
.histrow { display: flex; align-items: center; gap: 12px; min-width: min-content; }
.histrow__label { width: 86px; flex: 0 0 86px; font-size: 11.5px; font-weight: 600; color: var(--ink); }
.histrow__pct { --micro-size: 10.5px; --micro-colour: var(--mut); margin-left: auto; }
.histrow .squares__cell { width: 15px; height: 15px; flex: 0 0 15px; }

/* ==========================================================================
   18  THE LEADERSHIP SCREENS
   Six components the library did not already carry, used by the family
   overview, cells, families, reports and analytics screens:

     .boardtile  the "who has reported this week?" tile a family head chases
                 a register from — a column, where .listrow is a row
     .rankrow    the ranked list line shared by the cell ranking, the service
                 comparison, the report breakdowns and leader discipline
     .cellcard / .famcard  the two card shapes: a stacked cell card, and a
                 family card split into band, body and foot
     .histo      the faithfulness histogram
     .insight    the written analytics card
     .scrolly    a list that scrolls inside its card rather than stretching it

   Everything else on those screens composes .card, .kpi, .listrow, .grid,
   .meter, .ring and .spark, which already existed.
   ========================================================================== */

/* A padded, ruled band at the top of a .card--flush, where .card__head would
   sit flush against the edge. */
.card__band { padding: 17px 18px 15px; border-bottom: 1px solid var(--hair-2); display: flex; gap: 13px; align-items: flex-start; }

.boardtile {
  background: var(--tone-w, var(--surface-3)); border: 1px solid var(--tone-b, var(--line));
  border-radius: 13px; padding: 13px 14px; display: flex; flex-direction: column; gap: 11px; min-width: 0;
}
.boardtile__name { font-size: 13px; font-weight: 700; letter-spacing: -.2px; }

.rankrow { display: flex; align-items: center; gap: 11px; min-width: 0; }
.rankrow--divided { padding: 9px 0; border-bottom: 1px solid var(--hair-2); }
.rankrow--divided:last-child { border-bottom: 0; }
.rankrow__pos { font-family: var(--font-mono); font-size: 10.5px; color: var(--faint-2); flex: 0 0 24px; }
.rankrow__label { font-size: 12.5px; font-weight: 600; flex: 1 1 120px; min-width: 0; }
.rankrow__pct {
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 600;
  color: var(--tone-text, var(--mut)); flex: 0 0 auto; min-width: 44px; text-align: right;
}

/* The league rank numeral. It takes its colour from a tone class in the
   markup rather than naming one here, so gold for first place and muted for
   the rest are decided where the row is written. */
.rank { --stat-size: 19px; --stat-track: -.4px; }
.table--league { --table-min: 860px; }
.hero__stat--care .stat { color: var(--on-brand-bad); }

.cellcard { display: flex; flex-direction: column; gap: 14px; }
.cellcard__name { font-size: 15px; font-weight: 700; letter-spacing: -.25px; }
.famcard { display: flex; flex-direction: column; }
.famcard .card__body { flex: 1; }
.famcard__rate { text-align: right; align-items: flex-end; flex: 0 0 auto; }
.famintro { max-width: 58ch; }

.histo { display: flex; align-items: stretch; gap: 9px; height: 168px; }
.histo__col { flex: 1 1 0; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.histo__track { flex: 1; display: flex; align-items: flex-end; }
.histo__bar {
  width: 100%; height: var(--histo-h, 0%); min-height: 3px;
  border-radius: 5px 5px 2px 2px; background: var(--histo-fill, var(--brand-ink)); display: block;
}
.histo__col[data-band="0"] { --histo-fill: var(--hist-0); }
.histo__col[data-band="1"] { --histo-fill: var(--hist-1); }
.histo__col[data-band="2"] { --histo-fill: var(--hist-2); }
.histo__col[data-band="3"] { --histo-fill: var(--hist-3); }
.histo__col[data-band="4"] { --histo-fill: var(--hist-4); }
.histo__cap { text-align: center; align-items: center; }

.insight { display: flex; flex-direction: column; gap: 9px; }
.insight__headline { font-family: var(--font-display); font-size: 19px; letter-spacing: -.4px; line-height: 1.25; }

.scrolly { max-height: var(--scrolly-h, 330px); overflow-y: auto; }

/* The plain form behind every pop-up, for a phone with scripting switched
   off. app.js marks the document .js once it has booted, and then the reader
   only ever meets the dialog. */
.js .no-js-form { display: none; }

/* ===========================================================================
   18  MOBILE FIRST CORRECTIONS
   ---------------------------------------------------------------------------
   Sections 1-17 were written desktop-down and reflowed. These rules put the
   phone first, because that is where a cell leader actually marks a register:
   standing up, one hand, in a building in Kaduna.
   =========================================================================== */

/* A card header is a title and a row of controls. Side by side they fight for
   a 375px line, and a tab strip collapses into a vertical stack. Below the
   two-column breakpoint the controls get their own full-width row. */
@media (max-width: 767px) {
  .card__head {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .card__actions {
    width: 100%;
    flex-wrap: nowrap;
  }

  /* Tabs scroll sideways rather than wrapping into a column. The row is the
     only thing on the page allowed to scroll horizontally. */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* No negative-margin bleed to the card edge: --card-pad is not always the
       card's real padding, and when it is larger the row grows wider than the
       page and everything scrolls sideways. */
    scroll-snap-type: x proximity;
  }

  .tabs::-webkit-scrollbar { display: none; }
  .tab { flex: 0 0 auto; scroll-snap-align: start; }

}

/* A seven-column table does not belong on a phone. Marked .table--stack, each
   row becomes a small record with its own labels, so nothing is truncated and
   nothing has to be scrolled sideways to be read. */
@media (max-width: 767px) {
  .table--stack { min-width: 0; }
  /* display:none, not the sr-only clip: a <thead> ignores overflow and
     position, so the clipped header row still laid out at its full width and
     dragged the whole page sideways. Each cell carries its own label through
     data-label, and the table keeps its sr-only <caption>. */
  .table--stack thead { display: none; }
  .table--stack tbody tr {
    display: block;
    padding: 12px 16px;
    border-bottom: 1px solid var(--hair-2);
  }
  .table--stack tbody td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 3px 0;
    border: 0;
    text-align: left;
  }
  .table--stack tbody td::before {
    content: attr(data-label);
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: .11em;
    text-transform: uppercase;
    color: var(--faint);
    flex: 0 0 auto;
  }
  /* Columns hidden on a narrow screen stay hidden: the blanket display rule
     above would otherwise bring Cell, Phone, Last seen and Status back as
     stacked lines, which is the duplication the lead cell already avoids. */
  .table--stack tbody td.col-cell,
  .table--stack tbody td.col-phone,
  .table--stack tbody td.col-last,
  .table--stack tbody td.col-status { display: none; }

  /* The lead cell carries the name and needs no label above it. */
  .table--stack tbody td[data-lead] { padding-bottom: 6px; }
  .table--stack tbody td[data-lead]::before { display: none; }
  .table--stack tbody td.num { justify-content: space-between; }
  .table--stack .meter { max-width: 140px; }

  /* Where only a name and one figure survive on a phone — the member
     directory — the figure sits beside the name rather than claiming a
     labelled line of its own. */
  .table--stack-inline tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 10px;
  }
  .table--stack-inline tbody td { padding: 0; }
  .table--stack-inline tbody td::before { display: none; }
  .table--stack-inline tbody td[data-lead] { padding-bottom: 0; }
}

/* The register is the one screen that must survive a 320px phone. */
@media (max-width: 400px) {
  .markrow { gap: 8px; padding-inline: 12px; }
  .markrow__dots { display: none; }
  .card__body, .card__head, .card__foot { --card-pad: 14px; }
}

/* The register on a phone. Previously the person cell took the width and the
   P / A control was pushed outside .table-wrap's scroll area — the one control
   the whole screen exists for, unreachable with a thumb.

   Two columns: the person takes what is left and truncates, the pills hold a
   fixed width on the right and never wrap. The extra-status panel lives inside
   the person cell and simply flows underneath. */
@media (max-width: 767px) {
  .table--register { --table-min: 0; }
  .table--register thead { display: none; }
  .table--register tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    /* Two rows: who they are beside how you mark them, and the slower panel
       across the full width underneath. Left inside the first cell the panel
       was squeezed into half the screen beside the P/A pills, with the radios
       breaking over two lines and the right-hand half empty. */
    grid-template-areas:
      "who  mark"
      "more more";
    align-items: center;
    column-gap: 10px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--hair-2);
  }
  .table--register tbody td { padding: 0; border: 0; min-width: 0; }

  /* The cell is only a table artefact here; its children are the real grid
     items. The table is already re-laid-out as a grid at this width — the
     head is hidden and the rows are grids — so no semantics are lost that
     were still being used. */
  .table--register tbody td:first-child { display: contents; }
  .table--register .table__person { grid-area: who; gap: 10px; min-width: 0; }
  .table--register tbody td.num { grid-area: mark; justify-self: end; }
  .table--register tbody tr > td:first-child > details { grid-area: more; margin-top: 4px; }

  /* A full-width bar rather than a link floating under the name. */
  .markrow__more {
    --btn-pad: 0 2px; --btn-h: 38px;
    width: 100%; justify-content: space-between; font-size: 12px;
    list-style: none; cursor: pointer;
  }
  .markrow__more::-webkit-details-marker { display: none; }
  .markrow__more::after {
    /* position: static on purpose. .btn--h38::after is an absolutely
       positioned transparent tap-area overlay, and without resetting it this
       chevron inherits that positioning and lands on top of the label. The
       summary is already 38px tall and full width, so the overlay is not
       needed here. */
    content: ""; position: static; width: 7px; height: 7px; margin-right: 4px;
    border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    inset: auto;
  }
  .table--register details[open] .markrow__more::after {
    transform: rotate(-135deg) translate(-3px, -3px);
  }

  /* Four statuses two to a row: the panel stays a thumb's height rather than
     pushing the next person off the screen. */
  .table--register details[open] > fieldset {
    padding: 10px 0 12px;
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px;
  }
  .table--register details[open] > fieldset > legend,
  .table--register details[open] > fieldset > .field { grid-column: 1 / -1; }
  .table--register details[open] > fieldset .listrow {
    padding: 9px 10px; min-height: var(--tap); gap: 8px; font-size: 12.5px;
  }
  .table--register details[open] > fieldset .listrow .row-name { font-size: 12.5px; line-height: 1.25; }
}

/* ===========================================================================
   19  APP SHELL — HEADER, BOTTOM BAR, DRAWER, ACCOUNT MENU
   =========================================================================== */

/* A dashboard header carries the way into the navigation, the mark, and the
   two controls that belong to the person. The page title is not one of them. */
.hdr__inner {
  display: flex; align-items: center; gap: 10px;
  padding: 10px clamp(14px, 3vw, 26px);
  /* One row, always. A wrapped header pushes the page down and reads as a
     mistake. */
  flex-wrap: nowrap;
}
.hdr__menu { flex: 0 0 auto; }
.hdr__brand { display: flex; align-items: center; gap: 10px; min-width: 0; color: inherit; margin-right: auto; min-height: var(--tap); }
.hdr__brand:hover { color: inherit; }
.hdr__brand-name { font-size: 13px; font-weight: 700; letter-spacing: -.15px; }
/* Below 420px the mark alone identifies the app; the full name is in the
   drawer, which is one tap away. */
@media (max-width: 480px) { .hdr__brand-name { display: none; } }
.hdr__actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* The sidebar already names the church and owns the navigation from 900px. */
@media (min-width: 900px) {
  .hdr__menu { display: none; }
  .hdr__brand { display: none; }
  .hdr__inner { justify-content: flex-end; }
}

/* The page's own heading, with the content it names. */
.pagehead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
}
.pagehead__titles { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pagehead__crumb { --micro-size: 9.5px; --micro-track: .15em; }
.pagehead__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(24px, 4vw, 30px); line-height: 1.1; letter-spacing: -.6px; margin: 0;
}
.pagehead__actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Five slots, equal width, no scrolling. Anything past the fourth route lives
   behind "More", which is the pattern every other app on the phone uses. */
.bottomnav {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: var(--z-bottomnav, 40);
  display: grid; grid-auto-flow: column;
  /* minmax(0, …): the default auto minimum is min-content, which lets a long
     label push the bar wider than the phone. */
  grid-auto-columns: minmax(0, 1fr);
  background: var(--glass-2); backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  padding: 6px 4px calc(8px + env(safe-area-inset-bottom, 0px));
}
@media (min-width: 900px) { .bottomnav { display: none; } }

.navitem--bottom {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 6px 2px; min-height: 52px; border-radius: 12px;
  color: var(--faint); background: none; border: 0; text-align: center; position: relative;
  /* Without this a long label sets the column's min-content width and the
     whole bar starts scrolling again. */
  min-width: 0; overflow: hidden;
}
.navitem--bottom .navitem__label {
  font-size: 10.5px; font-weight: 600; letter-spacing: -.1px;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.navitem--bottom[aria-current] { color: var(--brand-ink); background: var(--brand-soft); }
.navitem--bottom .navitem__badge { position: absolute; top: 2px; right: 50%; transform: translateX(22px); }

/* The drawer behind "More" and behind the header's menu button. */
.drawer--nav { width: min(320px, 88vw); display: flex; flex-direction: column; }
.drawer__nav { flex: 1; overflow-y: auto; padding: 12px 10px; display: flex; flex-direction: column; gap: 2px; }
.drawer__foot { padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--hair); display: flex; flex-direction: column; gap: 10px; }

/* Account menu. A <details>, so it opens with no JavaScript at all. */
.accountmenu { position: relative; }
.accountmenu__trigger {
  display: flex; align-items: center; gap: 4px; cursor: pointer; list-style: none;
  border-radius: 999px; padding: 2px; min-height: var(--tap);
}
.accountmenu__trigger::-webkit-details-marker { display: none; }
.accountmenu__trigger:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.accountmenu__chevron { color: var(--faint); display: flex; }
.accountmenu__chevron svg { width: 14px; height: 14px; }
.accountmenu[open] .accountmenu__chevron { transform: rotate(180deg); }
/* Hidden explicitly rather than left to the user-agent's <details> rule: in
   Chromium the closed panel still lays out at its 220px min-width and widens
   the header past the viewport. */
.accountmenu__panel { display: none; }
.accountmenu[open] .accountmenu__panel { display: flex; }
.accountmenu__panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: var(--z-menu, 50);
  min-width: 220px; padding: 6px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 18px 40px var(--shadow); flex-direction: column; gap: 2px;
}
.accountmenu__who { padding: 10px 12px 12px; border-bottom: 1px solid var(--hair); margin-bottom: 4px; display: flex; flex-direction: column; gap: 2px; }
.accountmenu__item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; min-height: var(--tap); border-radius: 10px;
  font-size: 13.5px; font-weight: 500; color: var(--ink); background: none; border: 0; text-align: left;
}
.accountmenu__item:hover { background: var(--surface-2); color: var(--ink); }
.accountmenu__item svg { width: 16px; height: 16px; flex: 0 0 16px; color: var(--faint); }
.accountmenu__item--danger { color: var(--clay); }
.accountmenu__item--danger svg { color: var(--clay); }

/* Show / hide password: an icon inside the field, not a line of text under it. */
.field--password {
  position: relative;
  /* Default matches .input--lg, the size used on every auth screen, so the eye
     is still placed correctly where :has() is unsupported. */
  --field-h: 50px;
}
.field--password .input { padding-right: 48px; }
/* Anchored to the input, not to the field: .field--password is a column that
   also holds the label, so height:100% would put the eye over the label. */
.pwtoggle {
  position: absolute; right: 6px; bottom: 0;
  height: var(--field-h, 50px); width: 40px;
  display: grid; place-items: center;
  background: none; border: 0; color: var(--faint); border-radius: 10px;
}
/* The custom property lives on the input, so mirror it onto the wrapper. */
.field--password:has(.input--lg) { --field-h: 50px; }
.field--password:has(.input--sm) { --field-h: 44px; }
.pwtoggle:hover { color: var(--brand-ink); }
.pwtoggle svg { width: 18px; height: 18px; }
.pwtoggle__off { display: none; }
.pwtoggle[aria-pressed="true"] .pwtoggle__on { display: none; }
.pwtoggle[aria-pressed="true"] .pwtoggle__off { display: block; }

/* ===========================================================================
   20  DENSITY ON A PHONE
   ---------------------------------------------------------------------------
   The stat grids and filter bars were sized for a desktop row. On a 375px
   screen each tile claimed a whole line, so a leader scrolled past four cards
   and four dropdowns before reaching a single name.
   =========================================================================== */
@media (max-width: 767px) {
  /* Small tiles pair up. minmax(0, 1fr) rather than the auto-fit minimum,
     which lands one pixel short of two columns at 375px and falls back to one. */
  .grid--110, .grid--130, .grid--150, .grid--170, .grid--190 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The search keeps its own row; the selects pair up beneath it. */
  .toolbar {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    padding: 12px 14px;
  }
  .toolbar .search { grid-column: 1 / -1; }
  .toolbar .select { width: 100%; min-width: 0; }
  .toolbar .btn { grid-column: 1 / -1; justify-content: center; }

  /* A KPI tile does not need desktop's air when there are two to a row. The
     note line is the first thing to go: the label and the figure carry the
     meaning, and the note costs a whole row of scrolling on every tile. */
  .kpi { --kpi-pad: 11px 12px; gap: 2px; }
  .kpi .stat { --stat-size: 23px; }
  .kpi__note { display: none; }
}

/* Only the very narrowest phones drop back to one tile. 375px — the commonest
   width there is — holds two comfortably. */
@media (max-width: 339px) {
  .grid--170, .grid--190 { grid-template-columns: minmax(0, 1fr); }
}

/* ===========================================================================
   21  CARD HEAD IN A FLUSH CARD, AND HEADER CONTROL SIZING
   =========================================================================== */

/* .card--flush zeroes the card's padding so a table or list can reach the
   edge. Its heading must not: without this the title and subtitle sit hard
   against the border. */
.card--flush > .card__head {
  padding: 16px 18px 0;
  margin-bottom: 12px;
}
.card--flush > .card__head + .table-wrap,
.card--flush > .card__head + .toolbar { border-top: 1px solid var(--hair); }

/* The header carries three controls and no content of its own, so they should
   read as quiet furniture rather than as the loudest thing on the screen.
   The boxes shrink; the tap area stays 44px through .btn's ::after. */
.hdr .themebtn,
.hdr .hdr__menu {
  width: 38px; height: 38px; min-height: 38px;
}
.hdr .themebtn svg,
.hdr .hdr__menu svg { width: 16px; height: 16px; }
.hdr .accountmenu__trigger { min-height: 38px; }
.hdr .accountmenu__chevron svg { width: 13px; height: 13px; }

/* Same for the drawer's close control. */
.drawer__head .btn--icon { width: 38px; height: 38px; min-height: 38px; }
.drawer__head .btn--icon svg { width: 16px; height: 16px; }

/* The header controls keep a full-size hit area even though the box is 38px. */
.hdr .themebtn::after,
.hdr .hdr__menu::after,
.drawer__head .btn--icon::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--tap); height: var(--tap); transform: translate(-50%, -50%);
}
.hdr .themebtn, .hdr .hdr__menu, .drawer__head .btn--icon { position: relative; }

/* ===========================================================================
   22  MEMBER PROFILE HEADER
   ---------------------------------------------------------------------------
   Avatar, identity and contact buttons sat on one flex row. On a phone the
   identity block (flex: 1, min-width: 0) gave up all its width to the buttons,
   so the name wrapped, the cell and family broke to one word a line, and the
   buttons ran over the text.
   =========================================================================== */
@media (max-width: 767px) {
  .profilehead {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 12px;
  }
  .profilehead__id { grid-column: 2; min-width: 0; }
  .profilehead__id .display { --display-size: 21px; }
  /* Contact gets its own full-width row, split evenly. */
  .profilehead__actions { grid-column: 1 / -1; display: flex; gap: 8px; }
  .profilehead__actions .btn { flex: 1 1 0; min-width: 0; }
}

/* ===========================================================================
   23  PERMISSION ROLE CARDS
   ---------------------------------------------------------------------------
   Replaces the capability matrix. A grid of four roles by twenty-one actions
   cannot be read on a phone; one role at a time can.
   =========================================================================== */
.rolecard { border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.rolecard + .rolecard { margin-top: 8px; }
.rolecard__head {
  display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none;
  padding: 12px 14px; min-height: var(--tap); border-radius: 12px;
}
.rolecard__head::-webkit-details-marker { display: none; }
.rolecard__head:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.rolecard__name { font-size: 13.5px; font-weight: 700; letter-spacing: -.2px; flex: 1; min-width: 0; }
.rolecard__count {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
  color: var(--faint); white-space: nowrap;
}
.rolecard__chevron { display: flex; color: var(--faint); }
.rolecard__chevron svg { width: 15px; height: 15px; }
.rolecard[open] .rolecard__chevron { transform: rotate(180deg); }
.rolecard[open] .rolecard__head { border-bottom: 1px solid var(--hair); border-radius: 12px 12px 0 0; }
.rolecard__list { padding: 10px 14px 14px; display: flex; flex-direction: column; gap: 7px; }
.rolecard__item {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 13px; line-height: 1.4; color: var(--ink);
}
.rolecard__item svg { width: 15px; height: 15px; flex: 0 0 15px; color: var(--green); margin-top: 2px; }
.rolecard__item--none { color: var(--faint); font-style: italic; }

@media (min-width: 1024px) {
  /* Two to a row: one word and a count do not need 1100px of card. Each card
     still grows on its own, so opening one does not stretch its neighbour. */
  .rolecard__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; align-items: start; }
  .rolecard__grid .rolecard + .rolecard { margin-top: 0; }
}

/* A service row opens into its own edit form. Without list-style: none the
   browser draws its default marker, which lands on a line of its own above
   the row because the summary's only child is a block — a stray triangle
   floating over the card. */
.svcrow { border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.svcrow + .svcrow { margin-top: 8px; }
.svcrow__head {
  display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none;
  padding: 10px 12px; min-height: var(--tap); border-radius: 12px;
}
.svcrow__head::-webkit-details-marker { display: none; }
.svcrow__head:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.svcrow__chevron { display: flex; color: var(--faint); flex: 0 0 auto; }
.svcrow__chevron svg { width: 15px; height: 15px; }
.svcrow[open] .svcrow__chevron { transform: rotate(180deg); }
.svcrow[open] .svcrow__head { border-bottom: 1px solid var(--hair); border-radius: 12px 12px 0 0; }
.svcrow > form { padding: 14px; }
/* The row inside the head is only there for its layout. Left with .listrow's
   own surface and border it draws a second box inside the first. */
.svcrow__head .listrow { background: none; border: 0; padding: 0; border-radius: 0; }

/* Add-a-service disclosure. Closed it is an ordinary button; open it is a form. */
.addservice { margin-top: 14px; }
.addservice__toggle { list-style: none; cursor: pointer; width: 100%; }
.addservice__toggle::-webkit-details-marker { display: none; }
.addservice[open] .addservice__toggle { margin-bottom: 12px; }
.addservice__form { padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); }

/* ===========================================================================
   24  SETTINGS SPACING
   =========================================================================== */

/* A block that follows a form inside the same card is a separate thought, not
   a continuation of it. Without the rule and the gap the summary tiles read as
   part of the form they sit under. */
.cardsplit {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--hair);
}

/* The per-family override is a second, quieter question. Pressed up against
   the row of choices above it, it read as one of them. */
.subrule { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hair); }
.subrule__toggle {
  display: flex; align-items: center; gap: 8px; cursor: pointer; list-style: none;
  min-height: var(--tap); font-size: 13.5px; font-weight: 600; color: var(--brand-ink);
}
.subrule__toggle::-webkit-details-marker { display: none; }
.subrule__toggle::after {
  content: ""; width: 7px; height: 7px; margin-left: 2px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
}
.subrule[open] .subrule__toggle::after { transform: rotate(-135deg) translate(-3px, -3px); }
.subrule__toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.subrule__body { margin-top: 12px; }
/* Inside a card that is already a list of things, the rule and the gap are
   noise — only the chevron behaviour is wanted. */
.subrule--bare { margin-top: 0; padding-top: 0; border-top: 0; }

/* The stat tiles want a little more room than 110px once they carry a note. */
.statgrid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 10px; }

/* ===========================================================================
   25  COLLAPSIBLE FILTER SET
   ---------------------------------------------------------------------------
   Four selects cost more vertical space on a phone than the list they filter,
   so below the two-column breakpoint they fold behind one button. From 768px
   there is room for them in a row and the disclosure disappears.
   =========================================================================== */
.filterset { grid-column: 1 / -1; min-width: 0; }
.filterset__toggle {
  list-style: none; cursor: pointer; width: 100%; justify-content: center;
}
.filterset__toggle::-webkit-details-marker { display: none; }
.filterset__count {
  display: inline-grid; place-items: center; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 9px; background: var(--brand); color: #fff;
  font-family: var(--font-mono); font-size: 10px;
}
.filterset[open] .filterset__toggle { margin-bottom: 8px; }
/* Hidden explicitly. Relying on the user-agent's <details> rule is not enough
   once the panel carries its own display — the same trap as the account menu. */
.filterset__body { display: none; }
.filterset[open] .filterset__body {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px;
}

@media (min-width: 768px) {
  /* No disclosure on a wide screen: the row has the room. `display: contents`
     looks tempting here but Chromium wraps a <details> panel in its own
     ::details-content box, so the chain breaks and the selects vanish. The
     filterset just becomes a flex row of its own instead. */
  /* The search is the control people reach for first, so it keeps more of
     the row than a select does. Without a basis of its own it shrinks to
     190px and clips its own placeholder. */
  .toolbar > .search { flex: 2 1 240px; }
  .filterset { flex: 3 1 auto; min-width: 0; }
  .filterset__toggle { display: none; }
  .filterset__body,
  .filterset[open] .filterset__body {
    display: flex; flex-wrap: wrap; gap: 9px; align-items: center;
  }
  .filterset .select { flex: 1 1 150px; min-width: 0; }
}

/* While a filter is in flight the list dims rather than jumping. */
[data-autofilter][aria-busy="true"] ~ [data-filter-results],
.card [data-autofilter][aria-busy="true"] + [data-filter-results] { opacity: .55; }
[data-filter-results] { transition: opacity .12s ease; }
