/* ============================================================
   OCBJJ — Mobile-first stylesheet (WCAG 2.1 AA target)

   Conventions:
   - Base layer is mobile (≤640px). Larger viewports layer on via
     min-width media queries (mobile-first).
   - Body font size is 16px to prevent iOS Safari zoom on focus
     and to meet WCAG minimums.
   - Interactive elements meet ≥44×44 CSS px (WCAG 2.5.5 / Apple HIG).
   - Color contrast tested ≥4.5:1 for body text, ≥3:1 for large.
   - Focus styles are always visible (never `outline:0` without
     a replacement) and respect prefers-reduced-motion.
   ============================================================ */

/* ---------- Design tokens ---------- */
:root {
  /* Palette */
  --c-bg:           #f4f6f8;
  --c-surface:      #ffffff;
  --c-surface-alt:  #f8fafc;
  --c-border:       #d6dbe2;        /* nudged darker for ≥3:1 against bg */
  --c-border-soft:  #e6e9ee;
  --c-text:         #1f2937;
  --c-text-muted:   #565e6b;        /* nudged darker → 7.1:1 on white */
  --c-link:         #0e4f8a;        /* 6.8:1 on white */
  --c-link-hover:   #0a3a66;
  --c-sidebar:      #1f2d3d;
  --c-sidebar-2:    #28394e;
  --c-sidebar-fg:   #e1e6ec;        /* lifted for 11:1 on sidebar */
  --c-sidebar-fg-2: #a3afc0;        /* 4.6:1 on sidebar */
  --c-primary:      #0e4f8a;        /* same as link — single accent */
  --c-primary-2:    #0a3a66;
  --c-focus:        #4a8fd6;
  --c-danger:       #a02020;        /* 6.5:1 */
  --c-danger-2:     #761818;
  --c-success-bg:   #e4f5dd;
  --c-success-bd:   #4a9e3a;
  --c-success-fg:   #1a4d11;
  --c-error-bg:     #fbe1e1;
  --c-error-bd:     #c95656;
  --c-error-fg:     #7a1818;
  --c-warning-bg:   #fff5cc;
  --c-warning-bd:   #b89c20;
  --c-warning-fg:   #5a4d10;
  --c-info-bg:      #e1eefb;
  --c-info-bd:      #6e9bc9;
  --c-info-fg:      #133355;
  --c-promo-bg:     #fff0d6;
  --c-promo-bd:     #b86200;
  --c-promo-fg:     #5a3000;

  /* Spacing scale */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;

  /* Radii / shadow */
  --rad-sm: 4px; --rad: 8px; --rad-lg: 12px;
  --sh-1: 0 1px 2px rgba(15,23,42,.06), 0 1px 1px rgba(15,23,42,.08);
  --sh-2: 0 2px 4px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.08);
  --sh-3: 0 8px 28px rgba(15,23,42,.20);

  /* Type */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Layout */
  --tap: 44px;                 /* minimum interactive target */
  --sidebar-w: 240px;
  --content-max: 1140px;
  --topbar-h: 60px;
}

/* ---------- Reset / Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; height: 100%; }
/* `min-height` (not `height: 100%`) on body — fixed body-height interacts
   badly with position:sticky on direct children in iOS Safari, making the
   sticky topbar scroll out of view. */
body { min-height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;                            /* avoids iOS zoom; meets WCAG */
  line-height: 1.5;
  color: var(--c-text);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { max-width: 100%; height: auto; }
a { color: var(--c-link); text-decoration: none; }
a:hover { color: var(--c-link-hover); text-decoration: underline; }
a:focus-visible, button:focus-visible, [tabindex]:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--c-focus);
  outline-offset: 2px;
  border-radius: var(--rad-sm);
}

h1, h2, h3 { color: #111827; letter-spacing: -0.01em; line-height: 1.25; }
h2 { font-size: 1.375rem; margin: 0 0 var(--sp-4); font-weight: 600; }
h3 { font-size: 1.0625rem; margin: 0 0 var(--sp-3); font-weight: 600; }
p  { margin: 0 0 var(--sp-3); }
hr { border: 0; border-top: 1px solid var(--c-border-soft); margin: var(--sp-5) 0; }
small { font-size: 0.8125rem; color: var(--c-text-muted); display: block; margin-top: var(--sp-2); line-height: 1.45; }

/* Reduced-motion users: cut all transitions/animations. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Skip link (a11y) ---------- */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--c-primary);
  color: #fff !important;
  padding: 10px 16px;
  border-radius: 0 0 var(--rad) 0;
  font-weight: 600;
  z-index: 200;
  transform: translateY(-110%);
  transition: transform .15s;
}
.skip-link:focus { transform: translateY(0); text-decoration: none; }

/* ---------- Visually hidden (a11y label text) ---------- */
.sr-only {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------- Layout (mobile-first) ---------- */
.wrap { display: flex; flex-direction: column; min-height: 100vh; }

.content {
  flex: 1;
  width: 100%;
  padding: var(--sp-4);
  /* Reserve room for a sticky bottom action bar (.action-bar) on mobile. */
  padding-bottom: calc(var(--sp-8) + env(safe-area-inset-bottom));
}

/* ---------- Top bar (mobile) ---------- */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--c-sidebar);
  color: #fff;
  padding: 0 var(--sp-3);
  padding-top: env(safe-area-inset-top);
  position: sticky;
  top: 0;
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  z-index: 60;
}
.topbar-brand {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #fff;
  flex: 0 0 auto;
}
.topbar-brand a { color: #fff; }
.topbar-brand a:hover { text-decoration: none; }
/* Below ~420px the brand competes with the search box for room — hide it; the
   drawer's OCBJJ heading still names the app. */
@media (max-width: 419px) {
  .topbar-brand { display: none; }
}

/* ---------- Topbar search (replaces the old sidebar-search) ----------
   Mobile (base): fills the remaining row after the hamburger + brand.
   Desktop: anchored next to the brand at a fixed, modest width so it doesn't
   dominate the bar (see the 1024px override). */
.topbar-search {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  max-width: 380px;
}
.topbar-search input {
  width: 100%;
  min-width: 0;
  min-height: 38px;            /* keeps topbar at 56px with vertical breathing room */
  padding: 7px 12px 7px 34px;  /* room for the inline search-icon glyph */
  font-size: 0.9375rem;
  border-radius: 999px;        /* pill shape reads as "search" at a glance */
  border: 1px solid var(--c-sidebar-2);
  background: #15202d url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a3afc0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><path d='m20 20-3.5-3.5'/></svg>") no-repeat 10px center;
  color: #fff;
}
.topbar-search input::placeholder { color: var(--c-sidebar-fg-2); }
.topbar-search input:focus {
  outline: 0;
  border-color: var(--c-focus);
  box-shadow: 0 0 0 3px rgba(74,143,214,0.30);
}
.topbar-search .search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 70;                /* above topbar (z=60), below modal */
  background: var(--c-surface);
  color: var(--c-text);
}

.hamburger {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: var(--tap);
  height: var(--tap);
  background: transparent;
  border: 0; padding: 0;
  cursor: pointer; box-shadow: none;
  color: #fff;
}
.hamburger:hover { background: rgba(255,255,255,0.08); }
.hamburger span {
  display: block;
  height: 2px;
  width: 22px;
  margin: 0 auto;
  background: #fff;
  border-radius: 2px;
}

/* ---------- Sidebar (drawer on mobile, sticky column on desktop) ---------- */
.sidebar {
  position: fixed;
  top: 0; left: 0;
  width: 280px; max-width: 88vw;
  /* 100vh excludes the iOS address bar from the calculation, so the
     bottom of the drawer (user info + actions) gets clipped. 100dvh
     follows the visible viewport; fall back to 100vh on older browsers. */
  height: 100vh;
  height: 100dvh;
  background: var(--c-sidebar);
  color: var(--c-sidebar-fg);
  display: flex;
  flex-direction: column;
  padding: calc(var(--sp-3) + env(safe-area-inset-top)) 0
           calc(var(--sp-4) + env(safe-area-inset-bottom));
  transform: translateX(-100%);
  transition: transform .18s ease-out;
  z-index: 100;
  box-shadow: 4px 0 16px rgba(0,0,0,0.25);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.sidebar.open { transform: translateX(0); }
.sidebar h1 {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #fff;
  margin: 0 var(--sp-5) var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--c-sidebar-2);
}
.sidebar nav { margin-bottom: var(--sp-3); }
.sidebar nav a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--c-sidebar-fg);
  padding: var(--sp-2) var(--sp-5);
  border-left: 3px solid transparent;
  font-size: 0.9375rem;
  transition: background .12s, color .12s, border-color .12s;
}
.sidebar nav a:hover {
  background: var(--c-sidebar-2);
  color: #fff;
  text-decoration: none;
}
.sidebar nav a[aria-current="page"],
.sidebar nav a.active {
  background: var(--c-sidebar-2);
  color: #fff;
  border-left-color: var(--c-focus);
  font-weight: 600;
}
.sidebar .user-info {
  margin-top: auto;
  padding: var(--sp-3) var(--sp-5);
  font-size: 0.8125rem;
  color: var(--c-sidebar-fg-2);
  border-top: 1px solid var(--c-sidebar-2);
}
.sidebar .user-info strong { color: var(--c-sidebar-fg); }
.sidebar-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-4);
}
.sidebar-actions form { margin: 0; }
.sidebar-btn,
.sidebar-actions form button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--tap);
  box-sizing: border-box;
  background: transparent;
  border: 1px solid var(--c-sidebar-2);
  color: var(--c-sidebar-fg) !important;
  padding: 8px 12px;
  font: inherit;
  font-size: 0.9375rem;
  text-align: center;
  text-decoration: none;
  border-radius: var(--rad-sm);
  cursor: pointer;
}
.sidebar-btn:hover,
.sidebar-actions form button:hover {
  background: var(--c-sidebar-2);
  color: #fff !important;
  text-decoration: none;
}

.scrim {
  display: none;
  position: fixed; inset: 0;
  background: rgba(15,23,42,0.55);
  z-index: 90;
}
body.menu-open .scrim { display: block; }
/* Lock body scroll while drawer is open. */
body.menu-open { overflow: hidden; }

/* ---------- Forms (mobile-first) ---------- */
label {
  display: block;
  font-weight: 600;
  font-size: 0.9375rem;
  color: #374151;
  margin: var(--sp-4) 0 var(--sp-2);
  line-height: 1.4;
}
label:first-child { margin-top: 0; }

input[type=text], input[type=password], input[type=date], input[type=month],
input[type=datetime-local], input[type=number], input[type=email], input[type=tel],
input[type=search], input[type=time], input[type=url],
select, textarea {
  font: inherit;
  font-size: 1rem;                /* 16px — prevents iOS zoom */
  padding: 12px 14px;
  width: 100%;
  max-width: 100%;
  min-height: var(--tap);
  border: 1px solid #b6bdc7;
  border-radius: var(--rad-sm);
  background: var(--c-surface);
  color: var(--c-text);
  transition: border-color .12s, box-shadow .12s;
  -webkit-appearance: none;
  appearance: none;
}
/* Native dropdown arrow on selects. */
select {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='%231f2937' d='M6 8 0 0h12z'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 36px;
}
input:focus, select:focus, textarea:focus {
  outline: 0;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(14,79,138,0.20);
}
input[disabled], select[disabled], textarea[disabled] {
  background: var(--c-surface-alt);
  color: var(--c-text-muted);
  cursor: not-allowed;
}
input[type=checkbox], input[type=radio] {
  vertical-align: middle;
  width: 20px; height: 20px;
  margin: 0 8px 0 0;
  accent-color: var(--c-primary);
  cursor: pointer;
}
/* Make label rows that wrap a checkbox themselves a tap target. */
label.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  font-weight: 500;
  margin: var(--sp-2) 0;
  cursor: pointer;
}
label.check-row small { margin-left: 28px; }

/* ---------- Buttons (mobile-first ≥44px) ---------- */
button, input[type=submit], .btn, a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  padding: 10px 18px;
  min-height: var(--tap);
  min-width: 44px;
  background: var(--c-primary);
  color: #fff !important;
  border: 1px solid var(--c-primary);
  border-radius: var(--rad-sm);
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  line-height: 1.2;
  transition: background .12s, transform .04s, box-shadow .12s;
  box-shadow: var(--sh-1);
  -webkit-tap-highlight-color: transparent;
}
button:hover, input[type=submit]:hover, .btn:hover, a.btn:hover {
  background: var(--c-primary-2);
  border-color: var(--c-primary-2);
  text-decoration: none;
}
button:active, .btn:active { transform: translateY(1px); }
button[disabled], .btn[disabled], button[aria-disabled="true"] {
  opacity: 0.55; cursor: not-allowed; transform: none;
}

button.secondary, .btn.secondary, a.btn.secondary {
  background: var(--c-surface);
  color: var(--c-text) !important;
  border: 1px solid #b6bdc7;
  box-shadow: var(--sh-1);
}
button.secondary:hover, .btn.secondary:hover, a.btn.secondary:hover {
  background: var(--c-surface-alt);
  border-color: #9aa3b0;
}

button.danger, .btn.danger, a.btn.danger {
  background: var(--c-danger);
  color: #fff !important;
  border-color: var(--c-danger);
}
button.danger:hover, .btn.danger:hover, a.btn.danger:hover {
  background: var(--c-danger-2);
  border-color: var(--c-danger-2);
}

/* Compact button — still meets tap height (uses padding-y reduction
   only when nested inside non-critical surfaces). */
.btn.small { padding: 6px 12px; font-size: 0.875rem; min-height: 36px; }

/* Full-width button (drops the inline default). */
.btn.block, button.block { width: 100%; }

/* Button row — wraps cleanly on mobile, gap-spaced. */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
  margin: 0 0 var(--sp-4);
}
.btn-row > .btn, .btn-row > button { flex: 0 1 auto; }

/* Sticky bottom action bar (use on long forms — save stays in reach
   of the thumb). The form gets bottom padding so content isn't covered. */
.action-bar {
  position: sticky;
  bottom: 0;
  left: 0; right: 0;
  display: flex;
  gap: var(--sp-3);
  background: var(--c-surface);
  border-top: 1px solid var(--c-border);
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom));
  margin: var(--sp-5) calc(-1 * var(--sp-4)) 0;   /* bleed to viewport edges */
  box-shadow: 0 -2px 8px rgba(15,23,42,0.06);
  z-index: 30;
}
.action-bar > .btn, .action-bar > button { flex: 1; }
.action-bar > .btn.secondary, .action-bar > button.secondary { flex: 0 1 auto; }

/* ---------- Tables (responsive: table on ≥640px, cards below) ---------- */
table {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  box-shadow: var(--sh-1);
}
th, td {
  padding: 12px var(--sp-4);
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--c-border-soft);
}
th {
  background: var(--c-surface-alt);
  color: var(--c-text-muted);
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
table thead tr:first-child th:first-child { border-top-left-radius:  var(--rad); }
table thead tr:first-child th:last-child  { border-top-right-radius: var(--rad); }
table tbody tr:last-child  td:first-child { border-bottom-left-radius:  var(--rad); }
table tbody tr:last-child  td:last-child  { border-bottom-right-radius: var(--rad); }
tbody tr:nth-child(even) { background: #fafbfc; }
tbody tr:hover           { background: #f0f6fd; }
tbody tr:last-child td   { border-bottom: 0; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }

th.sort-col { cursor: pointer; user-select: none; white-space: nowrap; }
th.sort-col:hover { color: var(--c-text); }
th.sort-col::after { content: "\2195"; opacity: 0.45; margin-left: 4px; font-size: 11px; }
th.sort-col[aria-sort="ascending"]::after  { content: "\2191"; opacity: 1; }
th.sort-col[aria-sort="descending"]::after { content: "\2193"; opacity: 1; }

/* MOBILE TABLE-AS-CARDS pattern.
   Tables tagged .data-table become a stack of cards below 640px.
   Each <td> must carry a data-label attribute used as the row label.
   Tables that *can't* be reflowed (heatmap, chart-table) opt out by
   not having the .data-table class; they fall back to horizontal scroll
   inside .table-wrap. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

@media (max-width: 639px) {
  table.data-table {
    border: 0;
    background: transparent;
    box-shadow: none;
    display: block;
  }
  table.data-table thead { display: none; }
  table.data-table tbody { display: block; }
  table.data-table tbody tr {
    display: block;
    position: relative;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--rad);
    box-shadow:
      0 1px 2px rgba(15, 23, 42, 0.04),
      0 2px 8px rgba(15, 23, 42, 0.05);
    margin-bottom: var(--sp-3);
    padding: var(--sp-2) 0 var(--sp-1);
    transition: box-shadow .12s ease, transform .12s ease;
  }
  table.data-table tbody tr:nth-child(even) { background: var(--c-surface); }
  table.data-table tbody tr:hover { background: var(--c-surface); }
  /* Tactile press feedback on tap. */
  table.data-table tbody tr:active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
  }
  table.data-table tbody tr td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--sp-3);
    padding: 7px var(--sp-4);
    border-bottom: 1px solid var(--c-border-soft);
    text-align: right;
    border-radius: 0;
  }
  table.data-table tbody tr td:last-child { border-bottom: 0; }
  table.data-table tbody tr td::before {
    content: attr(data-label);
    font-weight: 600;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-text-muted);
    text-align: left;
    flex: 0 0 auto;
    margin-right: var(--sp-3);
  }
  /* Cells with no data-label become a full-width row (e.g. action buttons). */
  table.data-table tbody tr td:not([data-label]) {
    justify-content: flex-end;
    text-align: right;
  }
  table.data-table tbody tr td:not([data-label])::before { content: ""; }
  /* Make the primary cell the obvious scan target — stronger type, no tint
     so any row-level color (severity left edge) can read through. */
  table.data-table tbody tr td:first-child {
    background: transparent;
    padding-top: 12px;
    padding-bottom: 10px;
    justify-content: flex-start;
    text-align: left;
  }
  table.data-table tbody tr td:first-child::before { display: none; }
  table.data-table tbody tr td:first-child a {
    display: block;
    padding: 4px 0;
    min-height: 32px;
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--c-text);
    letter-spacing: -0.005em;
  }
  table.data-table tbody tr td:first-child a:hover {
    color: var(--c-link);
    text-decoration: none;
  }
  /* Whole-card tap: the first-cell link's hit area stretches over the
     entire row via an absolutely-positioned ::after. Any other interactive
     element inside the row (button, secondary link) is bumped above this
     stretch so it stays independently tappable. */
  table.data-table tbody tr td:first-child a::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
  }
  table.data-table tbody tr td a:not(:first-of-type),
  table.data-table tbody tr td:not(:first-child) a,
  table.data-table tbody tr td button,
  table.data-table tbody tr td input,
  table.data-table tbody tr td select {
    position: relative;
    z-index: 2;
  }
  /* Visible "I'm tappable" affordance: arrow on the right of the primary
     cell, plus a subtle elevation on press. */
  table.data-table tbody tr td:first-child:has(a) {
    position: relative;                    /* anchor the chevron to this cell */
  }
  table.data-table tbody tr td:first-child:has(a)::after {
    content: "›";
    position: absolute;
    right: var(--sp-4);
    top: 50%;
    transform: translateY(-50%);
    font-size: 1.5rem;
    line-height: 1;
    color: var(--c-text-muted);
    font-weight: 400;
    pointer-events: none;
    z-index: 0;
  }
}

/* ---------- Cards / Fieldsets ---------- */
fieldset {
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  padding: var(--sp-4);
  margin: 0 0 var(--sp-4);
  border-radius: var(--rad);
  box-shadow: var(--sh-1);
}
legend {
  font-weight: 600;
  color: #111827;
  padding: 0 var(--sp-2);
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
fieldset table { border: 0; box-shadow: none; border-radius: 0; }
fieldset table tbody > tr > th { background: transparent; padding-left: 0; }
fieldset table thead th { background: var(--c-surface-alt); }
fieldset table td:first-child { padding-left: 0; }
fieldset > p:last-child { margin-bottom: 0; }

/* "Profile" key/value mini-tables — clean two-column rows on every viewport.
   Label on the left (muted small-caps), value on the right, divider between. */
fieldset table.kv { width: 100%; }
fieldset table.kv tbody tr > th,
fieldset table.kv tbody tr > td {
  padding: 10px 0;
  border-bottom: 1px solid var(--c-border-soft);
  vertical-align: top;
}
fieldset table.kv tbody tr:last-child > th,
fieldset table.kv tbody tr:last-child > td { border-bottom: 0; }
fieldset table.kv tbody tr > th {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--c-text-muted);
  padding-right: var(--sp-4);
  white-space: nowrap;
  width: 1%;                              /* shrink-to-fit label column */
}
fieldset table.kv tbody tr > td {
  font-weight: 500;
  color: var(--c-text);
  text-align: right;
}
@media (max-width: 480px) {
  /* On very narrow screens, stack: label above, value below — clean card row
     with comfortable spacing and a single divider between rows. The generic
     tbody:nth-child(even) zebra stripe is killed here; on a stacked label/value
     layout it reads as confusing highlight, not as a row marker. */
  fieldset table.kv tbody tr {
    display: block;
    padding: 10px 0 12px;
    border-bottom: 1px solid var(--c-border-soft);
    background: transparent !important;       /* kill the zebra stripe */
  }
  fieldset table.kv tbody tr:first-child { padding-top: 4px; }
  fieldset table.kv tbody tr:last-child { border-bottom: 0; padding-bottom: 4px; }
  fieldset table.kv tbody tr > th,
  fieldset table.kv tbody tr > td {
    display: block; width: auto;
    padding: 0; border: 0;
    text-align: left; white-space: normal;
  }
  /* Value: bigger, darker, semibold — easy to scan past the muted label. */
  fieldset table.kv tbody tr > td {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--c-text);
    margin-top: 3px;
  }
}

/* ---------- Flashes ---------- */
.flash {
  padding: 12px 16px;
  margin: 0 0 var(--sp-4);
  border-radius: var(--rad);
  border: 1px solid;
  font-size: 0.9375rem;
}
.flash.success { background: var(--c-success-bg); border-color: var(--c-success-bd); color: var(--c-success-fg); }
.flash.error   { background: var(--c-error-bg);   border-color: var(--c-error-bd);   color: var(--c-error-fg); }
.flash.warning { background: var(--c-warning-bg); border-color: var(--c-warning-bd); color: var(--c-warning-fg); }
.flash.info    { background: var(--c-info-bg);    border-color: var(--c-info-bd);    color: var(--c-info-fg); }
.flash.promo {
  background: var(--c-promo-bg);
  border: 2px solid var(--c-promo-bd);
  border-left-width: 8px;
  color: var(--c-promo-fg);
  font-size: 1rem;
  font-weight: 600;
  padding: 14px 18px;
}

/* ---------- Page subtitle (paragraph that hugs an h2) ---------- */
.subtitle {
  margin-top: calc(var(--sp-4) * -1 + var(--sp-1));  /* pull up beneath the h2 */
  margin-bottom: var(--sp-4);
  color: var(--c-text-muted);
}
.subtitle strong { color: var(--c-text); }

/* ---------- Form submit-button row ---------- */
/* Use on the wrapper around an inline form's submit button when it isn't
   wearing the sticky `.action-bar` treatment. Standardizes the gap between
   the last field and the button — replaces ad-hoc inline `margin-top: 12px`. */
.form-actions {
  margin-top: var(--sp-4);
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* ---------- Input with inline suffix (e.g. "days", "%", "px") ----------
   Use when a unit reads better next to the input than in the help text.
   Markup:
     <div class="input-suffix">
       <input type="number" …>
       <span>days</span>
     </div>
   The input sizes itself (`width: auto` + an explicit `size` attribute or a
   sensible default), so the unit stays glued to its right edge instead of
   wrapping below on narrow viewports. */
.input-suffix {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: nowrap;
  max-width: 100%;
}
.input-suffix > input {
  width: auto;
  min-width: 0;
  flex: 0 1 9ch;            /* room for ~4 digits + spinner; shrinks on narrow */
}
.input-suffix > span {
  color: var(--c-text-muted);
  font-size: 0.9375rem;
  white-space: nowrap;
}

/* ---------- Field error / muted ---------- */
.errors { color: var(--c-error-fg); margin: var(--sp-1) 0; }
.field-error {
  color: var(--c-error-fg);
  font-size: 0.8125rem;
  margin-top: var(--sp-2);
  font-weight: 500;
}
.field-error::before { content: "⚠ "; }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--c-error-bd);
  background: #fff8f8;
}
/* Date driven by a mapped class — read-only, visually distinct from an editable field. */
input.is-locked { background: var(--c-surface-alt); color: var(--c-text-muted); cursor: not-allowed; }
/* Inline guidance under a field — more noticeable than plain muted text, set off
   from the control above it. */
.field-note {
  margin: var(--sp-3) 0 var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--c-info-fg);
  background: var(--c-info-bg);
  border-left: 3px solid var(--c-info-bd);
  border-radius: var(--rad-sm);
}
.muted { color: var(--c-text-muted); }

/* ---------- Utility classes ----------
   These replace one-off inline styles scattered across templates so we have
   a single place to tune spacing/typography and so the CSP-restricted style
   attributes stay scarce. */

/* Trailing count next to a heading ("Students (12 active)"). */
.h-count {
  font-weight: 400;
  font-size: 0.9375rem;
  color: var(--c-text-muted);
  margin-left: 4px;
}
.h-count-block {
  display: block;
  font-weight: 400;
  font-size: 0.9375rem;
  color: var(--c-text-muted);
  margin-top: 2px;
}
.legend-danger { color: var(--c-error-fg); }

/* Empty-state cell that spans a full table row. */
.empty-row {
  color: var(--c-text-muted);
  text-align: center;
  padding: var(--sp-5);
}

/* Tag with a left margin — when a tag tails some text. */
.tag-spaced { margin-left: 8px; }

/* Stacked Edit/Delete buttons in a table action cell.
   On desktop the cell narrows; on mobile (data-table card mode) the cell
   becomes its own row, so the actions stretch comfortably. */
.row-actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
}
.row-actions form { margin: 0; }

/* Form card — replaces ad-hoc style="max-width: 460px;" / "420px". */
.form-card { max-width: 460px; }
.form-card-narrow { max-width: 420px; }
.form-card-wide { max-width: 640px; }

/* Spacing utilities — only the few we actually need. */
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mt-6 { margin-top: var(--sp-6); }

/* Multi-error list inside a .flash. Replaces inline `margin:0;padding-left:1.2em`. */
.flash-list { margin: 0; padding-left: 1.2em; }

/* Smaller-than-body muted help text under headings or fieldsets. */
.muted-help { font-size: 0.8125rem; }

/* Numbered setup steps (used by 2FA setup). */
.setup-steps { max-width: 560px; line-height: 1.6; }

/* QR code wrapper — caps the SVG width and gives breathing room. */
.qr-block { margin: var(--sp-4) 0; max-width: 220px; }

/* TOTP secret rendered as a code element — slightly larger + spaced for legibility. */
.secret-code {
  font-size: 1.0625em;
  letter-spacing: 1px;
  background: var(--c-surface-alt);
  padding: 4px 8px;
  border-radius: var(--rad-sm);
}

/* Inline submit form (e.g. Archive/Restore buttons in a table row). */
.inline-form { display: inline; }

/* Error pages (404/403/500/csrf) — wrap content in a friendly card so they
   feel like part of the app rather than a stock browser message. */
.error-page {
  max-width: 480px;
  margin: var(--sp-6) auto;
  padding: var(--sp-5) var(--sp-6);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad-lg);
  box-shadow: var(--sh-2);
  text-align: center;
}
.error-page h2 { margin-top: 0; }
.error-page p:last-child { margin-bottom: 0; }

/* ---------- Attendance: inline "Ready to promote" queue ---------- */
/* Sits above the attendance toolbar. Mobile-first: full-width stacked cards
   with chunky tap targets. The 2-stage inline confirm avoids modal hell on
   one-handed phones. Cards animate out as the AJAX promote completes. */
.promo-queue {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3);
  background: #fff8e6;
  border: 1px solid #e6c976;
  border-left: 4px solid #d4a72c;
  border-radius: var(--rad);
}
.promo-queue-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
.promo-queue-head h3 {
  margin: 0;
  flex: 1 1 auto;
  color: #5a4d10;
}
.promo-queue-count {
  display: inline-block;
  min-width: 1.5em;
  padding: 0 8px;
  margin-left: 6px;
  background: #d4a72c;
  color: #fff;
  border-radius: 999px;
  font-size: 0.8125rem;
  text-align: center;
  font-weight: 700;
  vertical-align: 1px;
}
.promo-queue-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.promo-queue-card {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  /* Smooth animate-out as the row is promoted. */
  transition: opacity 220ms ease, transform 220ms ease, max-height 220ms ease,
              margin 220ms ease, padding 220ms ease, border 220ms ease;
  max-height: 200px;
  overflow: hidden;
}
.promo-queue-card.is-leaving {
  opacity: 0;
  transform: translateX(8px);
  max-height: 0;
  margin: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;
}
.promo-queue-body { flex: 1 1 auto; min-width: 0; }
.promo-queue-name {
  font-weight: 600;
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.promo-queue-belts {
  font-size: 0.875rem;
  color: var(--c-text-muted);
  margin-top: 2px;
}
.promo-queue-belts strong { color: var(--c-text); }
.promo-queue-arrow { margin: 0 4px; color: #d4a72c; }
.promo-queue-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
}
.promo-go.is-confirming,
.promo-bulk.is-confirming {
  background: #d4a72c;
  border-color: #b88a16;
  color: #fff;
  animation: promo-pulse 1.2s ease-in-out infinite;
}
@keyframes promo-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(212, 167, 44, 0.5); }
  50%      { box-shadow: 0 0 0 6px rgba(212, 167, 44, 0); }
}
.promo-queue-empty {
  margin: 0;
  padding: var(--sp-2) 0 0;
  color: var(--c-text-muted);
  font-style: italic;
}
.promo-queue [disabled] { opacity: 0.55; cursor: not-allowed; }

/* On tight mobile widths the actions wrap below the name. */
@media (max-width: 420px) {
  .promo-queue-card { flex-wrap: wrap; }
  .promo-queue-actions { flex: 1 1 100%; justify-content: flex-end; }
  .promo-queue-actions .btn { min-width: 96px; }
}

/* ---------- Attendance: row-list with big tap targets ---------- */
.att-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad) var(--rad) 0 0;
  /* Solid bottom edge + a subtle drop-shadow so rows scrolling underneath
     are visually separated from the sticky toolbar — no "blends in" feel. */
  border-bottom: 1px solid var(--c-border);
  box-shadow: 0 4px 6px -2px rgba(15,23,42,0.08);
  /* Sticks below the mobile topbar; on desktop (no topbar) sticks at 0. */
  position: sticky;
  top: calc(var(--topbar-h) + env(safe-area-inset-top));
  z-index: 25;
}
.att-filter-wrap {
  position: relative;
  flex: 1 1 100%;
  min-width: 0;
}
.att-toolbar #att-filter {
  width: 100%;
  min-width: 0;
  font-size: 1rem;
  padding-right: 52px;             /* room for the clear button */
}
.att-filter-clear {
  position: absolute;
  top: 50%; right: 4px;
  transform: translateY(-50%);
  width: var(--tap); height: var(--tap);
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 0;
  background: var(--c-border-soft);
  color: var(--c-text) !important;
  border: 0;
  border-radius: 50%;
  font-size: 24px;
  font-weight: 400;
  line-height: 1;
  box-shadow: none;
}
.att-filter-clear:hover, .att-filter-clear:active {
  background: var(--c-border);
  color: var(--c-text) !important;
  border: 0;
}
.att-filter-clear:active {
  /* Re-assert the centering transform so the global button:active
     translateY(1px) doesn't knock the X out of the input. */
  transform: translateY(-50%);
}
.att-counter {
  font-size: 0.9375rem;
  color: var(--c-text-muted);
  white-space: nowrap;
  flex-basis: 100%;
}
.att-counter strong { color: var(--c-text); font-size: 1rem; }

.att-list {
  list-style: none;
  margin: 0 0 var(--sp-4);
  padding: 0;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-top: 0;                  /* toolbar's bottom border serves as the divider */
  border-radius: 0 0 var(--rad) var(--rad);
  box-shadow: var(--sh-1);
}
.att-row {
  border-bottom: 1px solid var(--c-border-soft);
  position: relative;
  overflow: hidden;
  /* Tell the browser we handle horizontal — it can still pan vertically.
     This is what stops the page-slides-over behaviour during swipe. */
  touch-action: pan-y;
}
.att-row:last-child { border-bottom: 0; }
.att-row.hidden { display: none; }

/* Reveal layers shown behind the label while swiping (iOS-style). */
.att-row::before, .att-row::after {
  position: absolute;
  top: 0; bottom: 0;
  width: 100%;
  display: flex;
  align-items: center;
  padding: 0 var(--sp-5);
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.02em;
  opacity: 0;
  pointer-events: none;
  z-index: 0;
}
.att-row::before {
  content: "✓  Present";
  left: 0;
  background: #2f8a1f;
  justify-content: flex-start;
}
.att-row::after {
  content: "Absent  ✕";
  right: 0;
  background: var(--c-danger);
  justify-content: flex-end;
}
.att-row.swiping-right::before { opacity: 1; }
.att-row.swiping-left::after   { opacity: 1; }

.att-row > label {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px var(--sp-4);
  margin: 0;
  font-weight: normal;
  cursor: pointer;
  user-select: none;
  min-height: 56px;
  background: var(--c-surface);
  position: relative;
  z-index: 1;
  transition: transform .18s cubic-bezier(.2,.8,.2,1), background-color .08s;
  will-change: transform;
}
.att-row.swiping > label { transition: none; }
.att-row > label:hover { background: #f0f6fd; }
.att-row.is-present > label { background: #e8f4e1; }
.att-row.is-present > label:hover { background: #ddeed3; }

/* Brief confirmation flash after a swipe commits. */
@keyframes att-flash-present { 0% { background: #b7e6a3; } 100% { background: #e8f4e1; } }
@keyframes att-flash-absent  { 0% { background: #f5c6cb; } 100% { background: var(--c-surface); } }
.att-row.flash-present > label { animation: att-flash-present .5s ease-out; }
.att-row.flash-absent  > label { animation: att-flash-absent  .5s ease-out; }
.att-row .att-check {
  margin: 0;
  width: 24px; height: 24px;
  flex-shrink: 0;
  cursor: pointer;
  accent-color: var(--c-primary);
}
.att-row.is-present .att-check { accent-color: var(--c-success-bd); }
.att-row .att-name {
  flex: 1;
  font-size: 1rem;
  color: var(--c-text);
  line-height: 1.3;
}
.att-row.is-present .att-name { font-weight: 600; }
.att-row .att-belt {
  font-size: 0.8125rem;
  color: var(--c-text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}
/* Pills sit on the same line as the name on desktop; on narrow mobile they
   drop to their own row beneath the name so a long name + long belt can't
   shove "★ promoted" or "billing" into a torn-up second visual line. */
.att-row > label .tag {
  flex-shrink: 0;
  white-space: nowrap;
}
@media (max-width: 520px) {
  .att-row > label {
    flex-wrap: wrap;
    column-gap: 12px;
    row-gap: 4px;
  }
  .att-row .att-name { flex: 1 1 calc(100% - 38px); }
  .att-row .att-belt { margin-left: 38px; }
  .att-row > label .tag { margin-left: 0; }
}
.att-row.is-inactive .att-name { color: var(--c-text-muted); }
.att-row.is-inactive .att-belt { color: var(--c-text-muted); opacity: 0.7; }

/* Unchecking a returning student → fade and collapse out of the list.
   Mirrors the add-confirmation animation in the sheet for symmetry. */
.att-row.returning-removing {
  pointer-events: none;
  overflow: hidden;
  animation: att-row-remove .35s ease-in 0s 1 both;
}
@keyframes att-row-remove {
  0%   { opacity: 1; max-height: 120px; }
  100% { opacity: 0; max-height: 0;     border-bottom-width: 0; }
}

/* "+ Add returning student" trigger lives in the attendance toolbar so it's
   reachable at the top of the page (mobile users shouldn't have to scroll
   through 100+ rows to reach it). Full-width on narrow viewports. */
.att-add-btn {
  flex: 1 1 100%;
  margin: 0;
}
@media (min-width: 720px) {
  .att-add-btn { flex: 0 0 auto; margin-left: auto; }
}

/* ---------- Returning-student sheet ----------
   Full-screen sheet on mobile, centered modal on desktop. We freeze the
   underlying page with position:fixed while the sheet is open so the iOS
   software keyboard can't shove the page around when the input focuses.
   The stored scroll offset is restored on close. */
body.att-sheet-open {
  position: fixed;
  width: 100%;
  overflow: hidden;
}

.att-sheet {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
}
.att-sheet[hidden] { display: none; }
.att-sheet-scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}
.att-sheet-panel {
  position: relative;
  background: var(--c-surface);
  display: flex;
  flex-direction: column;
  width: 100%;
  /* dvh tracks the *visible* viewport — when the iOS keyboard appears, the
     sheet shrinks to fit above it instead of being clipped or pushed. */
  height: 100dvh;
  max-height: 100dvh;
  overflow: hidden;
  /* iOS safe-areas — keep close button below the notch. */
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}
.att-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--c-border);
  flex-shrink: 0;
}
.att-sheet-head h3 {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 600;
}
.att-sheet-close {
  appearance: none;
  background: #eef0f2;
  border: 1px solid var(--c-border);
  font-size: 1.5rem;
  line-height: 1;
  width: 44px;
  height: 44px;
  padding: 0;
  cursor: pointer;
  color: var(--c-text);
  border-radius: 50%;
  min-height: 44px;
  flex-shrink: 0;
}
.att-sheet-close:hover { background: #dde0e3; }
.att-sheet-close:active { background: #cfd3d7; }

/* Running tally of additions in this sheet session — gives instructors a
   clear "yes, I added them" signal even after the row has faded out. */
.att-sheet-added {
  font-size: 0.875rem;
  color: var(--c-text-muted);
  margin-left: auto;
  margin-right: var(--sp-3);
  background: #e8f4e1;
  border: 1px solid #b7e6a3;
  color: #2f5e1c;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.att-sheet-added strong { color: #2f5e1c; font-weight: 700; }

/* Confirmation animation on a tapped result. Row turns green with a ✓,
   then collapses + fades out before being removed. */
.att-sheet-result.added {
  background: #d9efc8;
  color: #2f5e1c;
  pointer-events: none;
  animation: att-sheet-added-flash .5s ease-out 0s 1 both,
             att-sheet-added-collapse .35s ease-in .5s 1 both;
}
.att-sheet-result.added::before {
  content: "✓";
  color: #2f8a1f;
  font-weight: 800;
  font-size: 1.125rem;
  margin-right: 6px;
}
.att-sheet-result.added .att-sheet-name,
.att-sheet-result.added .att-sheet-meta { color: #2f5e1c; }
@keyframes att-sheet-added-flash {
  0%   { background: #b7e6a3; }
  100% { background: #d9efc8; }
}
@keyframes att-sheet-added-collapse {
  0%   { opacity: 1; max-height: 120px; padding-top: 14px; padding-bottom: 14px; border-bottom-width: 1px; }
  100% { opacity: 0; max-height: 0;     padding-top: 0;     padding-bottom: 0;     border-bottom-width: 0; }
}
.att-sheet-search {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--c-border-soft);
  flex-shrink: 0;
}
.att-sheet-search-wrap {
  position: relative;
}
.att-sheet-search input {
  width: 100%;
  /* 16px prevents iOS auto-zoom on focus. */
  font-size: 16px;
  padding: 12px 14px;
  padding-right: 44px;                /* room for the clear button */
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  min-height: 48px;
}
.att-sheet-search input:focus {
  outline: 2px solid var(--c-primary);
  outline-offset: -1px;
}
/* Clear-search X lives INSIDE the input. Visually different from the close-
   sheet X (header, larger, bordered) so they can't be confused:
   - Smaller (32px) and subtler (no border, lighter gray).
   - Hidden until there's text to clear.
   - Position pinned to the right edge of the input. */
.att-sheet-clear {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  background: var(--c-border-soft);
  color: var(--c-text-muted) !important;
  border: 0;
  border-radius: 50%;
  font-size: 20px;
  font-weight: 400;
  line-height: 1;
  box-shadow: none;
  cursor: pointer;
}
.att-sheet-clear:hover {
  background: var(--c-border);
  color: var(--c-text) !important;
}
.att-sheet-clear:active {
  background: var(--c-border);
  color: var(--c-text) !important;
  transform: translateY(-50%);        /* defeat global button:active translateY */
}
.att-sheet-results {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
}
.att-sheet-empty {
  padding: var(--sp-5) var(--sp-4);
  color: var(--c-text-muted);
  text-align: center;
  font-size: 0.9375rem;
}
.att-sheet-result {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 14px var(--sp-4);
  border-bottom: 1px solid var(--c-border-soft);
  min-height: 56px;
  cursor: pointer;
  transition: background-color .08s;
}
.att-sheet-result:hover,
.att-sheet-result:focus {
  background: #f0f6fd;
  outline: none;
}
.att-sheet-result:active { background: #e3eefb; }
.att-sheet-name {
  flex: 1 1 auto;
  font-weight: 600;
  font-size: 1rem;
  color: var(--c-text);
  min-width: 50%;
}
.att-sheet-meta {
  font-size: 0.8125rem;
  color: var(--c-text-muted);
  flex-basis: 100%;
}
.att-sheet-result.is-inactive .att-sheet-name { color: var(--c-text-muted); }
.att-sheet-foot {
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--c-border);
  flex-shrink: 0;
}

/* Desktop: centered modal instead of full-screen sheet. */
@media (min-width: 720px) {
  .att-sheet { align-items: center; justify-content: center; padding: var(--sp-4); }
  .att-sheet-panel {
    width: min(560px, 100%);
    height: min(640px, 90dvh);
    max-height: 90dvh;
    border-radius: var(--rad);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
  }
}

/* ---------- Search ---------- */
.search-results {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  max-height: 320px;
  overflow-y: auto;
  box-shadow: var(--sh-1);
}
.search-results a {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 10px var(--sp-3);
  border-bottom: 1px solid var(--c-border-soft);
  color: var(--c-text);
}
.search-results a > span:first-child { flex: 1 1 auto; }
.search-results a:last-child { border-bottom: 0; }
.search-results a:hover { background: #f0f6fd; text-decoration: none; }
.search-results .muted { padding: 10px var(--sp-3); }

/* ---------- Login ---------- */
.login-box {
  max-width: 420px;
  margin: var(--sp-6) auto;
  background: var(--c-surface);
  padding: var(--sp-5);
  border-radius: var(--rad-lg);
  box-shadow: var(--sh-2);
}
.login-box h2 { margin-top: 0; }
.login-box input { width: 100%; }
.login-box button { width: 100%; padding: 14px; margin-top: var(--sp-4); font-size: 1.0625rem; }

/* ---------- Promotion-status panel ---------- */
.promo-panel {
  border-radius: var(--rad);
  padding: var(--sp-4) var(--sp-5);
  margin: 0 0 var(--sp-4);
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  box-shadow: var(--sh-1);
}
.promo-panel .promo-title { font-size: 1.0625rem; font-weight: 600; margin-bottom: 6px; }
.promo-panel .promo-detail { font-size: 0.875rem; color: var(--c-text-muted); }
.promo-panel .promo-detail strong { color: var(--c-text); }
.promo-panel.promo-due {
  background: var(--c-promo-bg);
  border: 2px solid var(--c-promo-bd);
  border-left-width: 8px;
}
.promo-panel.promo-due .promo-title { color: var(--c-promo-fg); font-size: 1.125rem; }
.promo-panel.promo-due .promo-detail { color: var(--c-promo-fg); }
.promo-panel.promo-close {
  background: #fffbeb;
  border-left: 6px solid var(--c-warning-bd);
}
.promo-panel.promo-close .promo-title { color: #6a4d04; }

.progress {
  margin-top: 8px;
  width: 100%;
  height: 10px;
  background: var(--c-border-soft);
  border-radius: 999px;
  overflow: hidden;
}
.progress .bar { height: 100%; background: var(--c-primary); border-radius: 999px; }
.promo-panel.promo-due .progress .bar  { background: var(--c-promo-bd); }
.promo-panel.promo-close .progress .bar { background: var(--c-warning-bd); }

.promo-panel .promo-progress {
  margin-top: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.promo-row-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: 0.875rem;
  margin-bottom: 5px;
}
.promo-row-label { font-weight: 600; color: var(--c-text); }
.promo-row-stat { color: var(--c-text-muted); text-align: right; }
.promo-row-stat strong { color: var(--c-text); }
.promo-row-stat .met { color: var(--c-success-fg); font-weight: 600; }
.promo-panel .promo-row .progress { margin-top: 0; }
.promo-panel .progress .bar.bar-met { background: #16a34a; }
.promo-panel .promo-action { margin-top: var(--sp-4); margin-bottom: 0; }
.promo-panel .promo-action .btn { width: 100%; }

/* ---------- Danger zone ---------- */
fieldset.danger-zone {
  border: 1px solid #f5c6cb;
  background: #fff7f7;
  margin-top: var(--sp-6);
}
fieldset.danger-zone > legend { color: var(--c-danger); }
fieldset.danger-zone p:first-of-type { margin-top: 0; }

/* ---------- Toolbar / Pills ---------- */
.toolbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
}
.toolbar .spacer { flex: 1; }
.toolbar > .btn { width: 100%; }

.pill-group {
  display: inline-flex;
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  border-radius: 999px;
  padding: 4px;
  box-shadow: var(--sh-1);
  align-self: flex-start;
}
.pill-group a {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  min-height: 36px;
  border-radius: 999px;
  font-size: 0.875rem;
  color: var(--c-text-muted);
}
.pill-group a:hover { text-decoration: none; color: var(--c-text); }
.pill-group a[aria-current="page"],
.pill-group a.active {
  background: var(--c-primary); color: #fff; font-weight: 600;
}
.pill-group a[aria-current="page"]:hover,
.pill-group a.active:hover { color: #fff; background: var(--c-primary-2); }

.search-box { position: relative; width: 100%; }
.search-box input { width: 100%; min-width: 0; }
.search-box .search-results { margin-top: 6px; }

/* ---------- Breadcrumb ---------- */
.breadcrumb {
  font-size: 0.8125rem;
  color: var(--c-text-muted);
  margin: 0 0 var(--sp-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.breadcrumb a { color: var(--c-text-muted); padding: 2px 0; }
.breadcrumb a:hover { color: var(--c-link); text-decoration: underline; }
.breadcrumb > span:not(.sep) { color: var(--c-text); font-weight: 500; }
.breadcrumb .sep { color: var(--c-text-muted); opacity: 0.6; }

/* ---------- Year picker / small inline selects ---------- */
.year-picker {
  display: flex;                            /* block-level so vertical margins push */
  width: fit-content;                       /* but stay sized to content */
  max-width: 100%;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--c-text-muted);
  margin: var(--sp-3) 0 var(--sp-4);        /* breathing room above and below */
}
.year-picker label { margin: 0; font-weight: 500; font-size: 0.875rem; }
.year-picker select { width: auto; min-width: 140px; padding: 8px 36px 8px 12px; font-size: 0.9375rem; }

/* ---------- Analytics: tabs ---------- */
.analytics-tabs {
  display: flex;
  gap: 0;
  border-bottom: 2px solid var(--c-border);
  margin: var(--sp-3) calc(-1 * var(--sp-4)) var(--sp-4);
  padding: 0 var(--sp-4);
}
.analytics-tabs a {
  flex: 1 1 0;                        /* equal width — fills the row */
  padding: 10px 4px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8125rem;                /* tight enough to fit 4 tabs on 320px */
  font-weight: 500;
  color: var(--c-text-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  text-align: center;
  white-space: nowrap;
}
.analytics-tabs a:hover { color: var(--c-text); }
.analytics-tabs a[aria-current="page"],
.analytics-tabs a.active {
  color: var(--c-primary);
  border-bottom-color: var(--c-primary);
}

/* KPI deltas */
.kpi-up   { color: var(--c-success-fg); }
.kpi-down { color: var(--c-error-fg); }

/* Timeframe toolbar */
.timeframe-toolbar { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
.timeframe-toolbar .year-picker { width: 100%; }
.timeframe-toolbar .year-picker select { width: 100%; min-width: 0; }
.timeframe-or { color: var(--c-text-muted); font-size: 0.8125rem; text-align: center; }
.timeframe-custom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--c-text-muted);
}
.timeframe-custom label { margin: 0; font-weight: 500; font-size: 0.875rem; flex: 0 0 auto; }
.timeframe-custom input[type=date],
.timeframe-custom input[type=month] {
  flex: 1 1 130px; width: auto; min-width: 0;
}
.timeframe-custom button { flex: 1 1 100%; }

/* ---------- Tags ---------- */
.tag {
  display: inline-block;
  padding: 3px 9px;
  font-size: 0.6875rem;
  border-radius: 999px;
  background: #eef1f5; color: var(--c-text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
  font-weight: 600;
}
.tag.inactive   { background: #fde2e2; color: var(--c-error-fg); }
.tag.active     { background: #e2f5e6; color: var(--c-success-fg); }
.tag.role-admin { background: #e6e9fd; color: #3b3f8f; }
.tag.billing    { background: #fde2e2; color: var(--c-error-fg); border: 1px solid var(--c-error-bd); }
.tag.promo-due-tag   { background: var(--c-promo-bg); color: var(--c-promo-fg); }
.tag.promo-close-tag { background: #fffbeb; color: #6a4d04; }
.tag.promoted-here   { background: #fff3c4; color: #5a4d10; border: 1px solid #d4a72c; }

/* Billing-hold alert */
.billing-alert {
  padding: 12px 16px;
  margin: 0 0 var(--sp-4);
  border: 2px solid var(--c-error-bd);
  border-left-width: 8px;
  border-radius: var(--rad);
  background: var(--c-error-bg);
  color: var(--c-error-fg);
  font-weight: 600;
}
.att-row.has-billing .att-name { font-weight: 700; }

/* ---------- Tiles ---------- */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
}
.tile {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  padding: var(--sp-4);
  box-shadow: var(--sh-1);
}
.tile-num {
  font-size: 1.625rem;
  font-weight: 700;
  color: #111827;
  line-height: 1.1;
}
.tile-label {
  font-size: 0.75rem;
  color: var(--c-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: 6px;
}
.tile-label .muted { display: block; text-transform: none; letter-spacing: 0; margin-top: 2px; }

/* ---------- 2-up grid (stacks on mobile) ---------- */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-4);
  margin: 0 0 var(--sp-4);
}
.grid-2 > fieldset { margin-bottom: 0; }

/* ---------- Horizontal bar (chart-table) ---------- */
.chart-table { background: transparent; border: 0; box-shadow: none; }
.chart-table th {
  background: transparent;
  text-transform: none; letter-spacing: 0;
  font-size: 0.875rem; color: var(--c-text);
  font-weight: 500;
  width: 35%;
  padding-left: 0;
}
.chart-table td { padding-right: 0; }
.hbar-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.hbar {
  flex: 1;
  background: var(--c-border-soft);
  border-radius: var(--rad-sm);
  height: 18px;
  overflow: hidden;
  min-width: 0;
}
.hbar-fill {
  height: 100%;
  background: linear-gradient(90deg, #2a72bf, #0e4f8a);
  border-radius: var(--rad-sm);
}
.hbar-value {
  min-width: 36px;
  text-align: right;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--c-text);
}

/* ---------- Heatmap ---------- */
.heatmap-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 calc(-1 * var(--sp-2));
  padding: 0 var(--sp-2);
}
table.heatmap {
  border-collapse: separate;
  border-spacing: 2px;
  background: transparent;
  border: 0; box-shadow: none;
  width: auto;
  margin: 0 auto;
}
table.heatmap th {
  background: transparent;
  font-weight: 500;
  font-size: 0.6875rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--c-text-muted);
  padding: 4px 6px;
  border: 0;
  text-align: center;
}
table.heatmap thead th { background: var(--c-surface); }
table.heatmap td.hm-cell {
  width: 32px; height: 28px;
  border-radius: var(--rad-sm);
  border: 0;
  text-align: center;
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 0;
}

/* ---------- Details / collapsibles ---------- */
details {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--rad);
  margin: 0 0 var(--sp-3);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
details > summary {
  list-style: none;
  cursor: pointer;
  padding: 12px var(--sp-4);             /* matches legend + .sec-head */
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  background: var(--c-surface-alt);
  border-bottom: 1px solid transparent;
  border-radius: var(--rad) var(--rad) 0 0;
  user-select: none;
  /* Title typography matches legend / .sec-title — one project-wide pattern. */
  font-weight: 600;
  color: #111827;
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
details > summary::-webkit-details-marker { display: none; }
details > summary::before {
  content: '▶';
  font-size: 10px;
  color: var(--c-text-muted);
  transition: transform .15s;
  width: 12px;
  flex: 0 0 12px;
}
details[open] > summary::before { transform: rotate(90deg); }
details[open] > summary { border-bottom-color: var(--c-border); }
/* Inline metadata (counts, hints) inside the summary should NOT inherit the
   uppercase header treatment — they stay readable sentence-case. */
details > summary .muted,
details > summary .tag,
details > summary .h-count {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 0.8125rem;
}
details > summary .tag { font-weight: 600; }
.details-body { padding: var(--sp-3) var(--sp-4); }
.details-body table { border: 0; box-shadow: none; border-radius: 0; }
/* Same strip for tables inside section.sec — the section already provides
   the bordered card frame, so an inner table border reads as a "double box". */
section.sec table { border: 0; box-shadow: none; border-radius: 0; }

/* ---------- Dashboard / list sections (.sec) ----------
   Sections (section or details) tagged with a severity get a colored
   top stripe so the boundary between groups is obvious — especially
   on mobile where data-table rows reflow into a tall stack of cards.
   On mobile, section headers stick to the top of the viewport while
   their list scrolls, so the section identity stays visible mid-scroll. */

/* Severity tokens — referenced by both stripe and card left-edge below. */
.sec--danger        { --sec-color: var(--c-danger); }
.sec--promo         { --sec-color: var(--c-success-bd); }
.sec--warn          { --sec-color: var(--c-warning-bd); }
.sec--danger-muted  { --sec-color: var(--c-error-bd); }

/* Non-collapsible (section) variant: mirror the fieldset card look.
   Border sides set individually so .sec's border-top stripe wins. */
section.sec {
  background: var(--c-surface);
  border-right: 1px solid var(--c-border);
  border-bottom: 1px solid var(--c-border);
  border-left: 1px solid var(--c-border);
  border-radius: var(--rad);
  box-shadow: var(--sh-1);
  margin: 0 0 var(--sp-4);
}
section.sec > .sec-head {
  padding: 12px var(--sp-4);
  background: var(--c-surface-alt);
  border-bottom: 1px solid var(--c-border);
  border-radius: var(--rad) var(--rad) 0 0;
}
section.sec > .sec-head .sec-title {
  margin: 0;
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #111827;
}
section.sec > .sec-head .sec-title--danger { color: var(--c-error-fg); }
section.sec > .sec-body { padding: var(--sp-4); }
section.sec > .sec-body > p:last-child { margin-bottom: 0; }
section.sec > .sec-body > table { margin-top: var(--sp-3); }

/* Colored top stripe on every .sec — works for section, details, fieldset.
   Selector lists each tag explicitly so we win over each element's default
   border shorthand (border: 1px solid var(--c-border)). */
details.sec,
section.sec,
fieldset.sec { border-top: 4px solid var(--sec-color, var(--c-border)); }

/* Sticky header — all .sec containers, all viewports.
   Keeping the section identity visible while a long list scrolls is
   useful on desktop too. Sticky needs no overflow clip on the container. */
details.sec,
section.sec { overflow: visible; }

details.sec > summary,
section.sec > .sec-head {
  position: sticky;
  top: var(--topbar-h);
  z-index: 20;                           /* below topbar (60) + search (70), above content */
  background: var(--c-surface-alt);
  border-top-left-radius: var(--rad);
  border-top-right-radius: var(--rad);
  box-shadow: 0 1px 0 var(--c-border), 0 2px 6px rgba(15, 23, 42, 0.06);
  /* Re-apply severity stripe to the sticky bar itself, since the parent's
     border-top scrolls away with the section body. */
  border-top: 4px solid var(--sec-color, var(--c-border));
  margin-top: -4px;
}

@media (max-width: 639px) {
  /* Bigger gap between sections so the transition reads at a glance. */
  .sec { margin-bottom: var(--sp-6); }
}

/* ---------- Section + per-row severity (left edge) ----------
   Two parallel patterns:
   1. .sec table tr  — every card in a categorical section inherits the
      section's severity color via --sec-color (set on .sec--*).
   2. tr.row--*      — an individual row can declare its own severity
      regardless of section (e.g. a billing-hold student in a general
      list). Per-row wins over section because it's more specific.
   Desktop gets a subtler left border; mobile gets the full 3px stripe. */
.row--danger { --row-color: var(--c-danger); }
.row--warn   { --row-color: var(--c-warning-bd); }
.row--muted  { --row-color: var(--c-border); }

@media (max-width: 639px) {
  .sec table.data-table tbody tr {
    border-left: 3px solid var(--sec-color, var(--c-border));
  }
  table.data-table tbody tr.row--danger,
  table.data-table tbody tr.row--warn,
  table.data-table tbody tr.row--muted {
    border-left: 3px solid var(--row-color);
  }
  /* Muted rows (archived/inactive) fade slightly so they recede in a list. */
  table.data-table tbody tr.row--muted { opacity: 0.78; }
}
@media (min-width: 640px) {
  /* On desktop rows are table cells — only per-row severity makes sense
     (sections don't reflow into cards). Subtle left accent on the first cell. */
  table.data-table tbody tr.row--danger > td:first-child,
  table.data-table tbody tr.row--warn   > td:first-child,
  table.data-table tbody tr.row--muted  > td:first-child {
    box-shadow: inset 3px 0 0 var(--row-color);
    padding-left: calc(var(--sp-3) + 3px);
  }
  table.data-table tbody tr.row--muted { color: var(--c-text-muted); }
}

/* ---------- Modal ---------- */
dialog.modal {
  border: 0;
  border-radius: var(--rad-lg);
  padding: 0;
  max-width: 460px;
  width: calc(100% - var(--sp-4));
  box-shadow: var(--sh-3);
  font: inherit;
  color: var(--c-text);
  background: var(--c-surface);
}
dialog.modal::backdrop { background: rgba(15, 23, 42, 0.55); }
dialog.modal .modal-body { padding: var(--sp-5) var(--sp-5) var(--sp-3); }
dialog.modal h3 { margin: 0 0 var(--sp-3); font-size: 1.0625rem; }
dialog.modal p { margin: 0 0 var(--sp-4); line-height: 1.55; white-space: pre-line; }
dialog.modal .modal-actions {
  padding: var(--sp-3) var(--sp-5) calc(var(--sp-4) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column-reverse;
  gap: var(--sp-2);
  background: var(--c-surface-alt);
  border-top: 1px solid var(--c-border-soft);
}
dialog.modal .modal-actions button { width: 100%; }
dialog.modal.danger { border-top: 4px solid var(--c-danger); }
dialog.modal.danger h3 { color: var(--c-danger); }
dialog.modal.danger .modal-actions button:not(.secondary) { background: var(--c-danger); border-color: var(--c-danger); }
dialog.modal.danger .modal-actions button:not(.secondary):hover { background: var(--c-danger-2); border-color: var(--c-danger-2); }

/* ============================================================
   ≥ 640px (large phones, small tablets)
   ============================================================ */
@media (min-width: 640px) {
  .content { padding: var(--sp-6); }
  .analytics-tabs a { font-size: 0.9375rem; padding: 10px var(--sp-3); flex: 0 1 auto; }
  .analytics-tabs { gap: var(--sp-1); }
  .tile-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--sp-4); }
  .tile-num { font-size: 1.75rem; }
  h2 { font-size: 1.5rem; margin-bottom: var(--sp-5); }
  h3 { font-size: 1.125rem; }
  fieldset { padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-5); }
  .toolbar { flex-direction: row; align-items: center; flex-wrap: wrap; gap: var(--sp-4); }
  .toolbar > .btn { width: auto; }
  .pill-group { align-self: auto; }
  .search-box { max-width: 380px; }
  .att-filter-wrap { flex: 1 1 260px; }
  .att-counter { flex-basis: auto; }
  .timeframe-toolbar { flex-direction: row; align-items: center; flex-wrap: wrap; row-gap: var(--sp-3); }
  .timeframe-toolbar .year-picker { width: auto; }
  .timeframe-toolbar .year-picker select { width: auto; min-width: 180px; }
  .timeframe-custom button { flex: 0 1 auto; }
  .action-bar {
    margin: var(--sp-5) calc(-1 * var(--sp-6)) 0;
    padding: var(--sp-3) var(--sp-6);
  }
  /* On larger screens, fixed-width inputs are nicer than full-bleed. */
  input[type=text], input[type=password], input[type=date], input[type=month],
  input[type=datetime-local], input[type=number], input[type=email], input[type=tel],
  input[type=time], input[type=url],
  select, textarea {
    max-width: 420px;
  }
  .login-box input, .login-box button { max-width: none; }
  /* Forms inside .form-narrow get a comfortable max-width. */
  form.form-narrow { max-width: 460px; }
  form.form-narrow input, form.form-narrow select, form.form-narrow textarea { max-width: 100%; }
}

/* ============================================================
   ≥ 900px (small laptops, tablets landscape)
   ============================================================ */
@media (min-width: 900px) {
  .grid-2 { grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
}

/* ============================================================
   ≥ 1024px (sidebar nav, sticky thead)
   ============================================================ */
@media (min-width: 1024px) {
  /* Topbar stays visible at every breakpoint — that's where the search bar
     lives. The hamburger goes away because the sidebar is permanent here. */
  .hamburger { display: none; }
  /* Desktop bumps the topbar a little taller still — more breathing room for
     the search field on a wider canvas. Bumping --topbar-h propagates to
     every sticky offset (sidebar top, thead top, att-toolbar top). */
  :root { --topbar-h: 64px; }
  .topbar {
    padding: 0 var(--sp-6);
    gap: 0;
  }
  /* Brand fills the would-be sidebar column so the search that follows is
     naturally aligned with the start of the main content area below. */
  .topbar-brand {
    flex: 0 0 calc(var(--sidebar-w) - var(--sp-6));
    width: calc(var(--sidebar-w) - var(--sp-6));
  }
  /* Search is a modest, content-aligned tool — not a banner. */
  .topbar-search {
    flex: 0 0 300px;
    max-width: 300px;
  }
  .topbar-search input {
    /* On desktop the input should look like a polite control, not a chunky
       mobile target. Smaller height + smaller text leaves visible topbar
       above/below so the strip reads as chrome rather than a search bar. */
    min-height: 34px;
    font-size: 0.875rem;
    padding: 6px 12px 6px 32px;
    background-position: 9px center;
  }
  .wrap { flex-direction: row; }
  .content {
    flex: 1;
    padding: var(--sp-6) var(--sp-8);
    max-width: var(--content-max);
    padding-bottom: var(--sp-6);
  }
  .sidebar {
    position: sticky;
    transform: none;
    /* Sticks below the topbar (which itself stays sticky at the viewport top). */
    top: calc(var(--topbar-h) + env(safe-area-inset-top));
    left: auto;
    width: var(--sidebar-w);
    max-width: none;
    height: calc(100vh - var(--topbar-h) - env(safe-area-inset-top));
    box-shadow: none;
    border-right: 1px solid var(--c-border);
    align-self: flex-start;
    flex-shrink: 0;
    padding-top: var(--sp-5);
  }
  /* The drawer h1 is redundant with the topbar brand on desktop. */
  .sidebar h1 { display: none; }
  body.menu-open { overflow: auto; }
  body.menu-open .scrim { display: none; }
  thead th {
    /* Sticks below the topbar; an opaque background is mandatory or scrolled
       rows bleed through the text. */
    position: sticky;
    top: calc(var(--topbar-h) + env(safe-area-inset-top));
    z-index: 2;
    background: var(--c-surface-alt);
    box-shadow: inset 0 -1px 0 var(--c-border);
  }
  /* When the table is inside a sticky .sec header, the thead has to stack
     BELOW that header (which itself sits at top: var(--topbar-h)). Section
     headers are ~44px tall, so offset the thead by that much. */
  .sec thead th {
    top: calc(var(--topbar-h) + 44px + env(safe-area-inset-top));
  }
  /* Most forms don't need a sticky save bar on a wide screen — flatten
     back to a normal row. Take-attendance is the exception (long roster);
     it keeps the sticky bar via the #attendance-form override below. */
  .action-bar {
    position: static;
    margin: var(--sp-5) 0 0;
    padding: 0;
    background: transparent;
    border: 0;
    box-shadow: none;
  }
  .action-bar > .btn, .action-bar > button { flex: 0 1 auto; }

  /* Take-attendance: keep both the filter toolbar and Save bar in reach
     while scrolling a long roster on desktop too. */
  .att-toolbar { top: calc(var(--topbar-h) + env(safe-area-inset-top)); }
  #attendance-form .action-bar {
    position: sticky;
    bottom: 0;
    margin: var(--sp-4) 0 0;
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: 0 0 var(--rad) var(--rad);
    box-shadow: 0 -2px 8px rgba(15,23,42,0.06);
  }
  #attendance-form .action-bar > button[type="submit"] { flex: 1; }
}
