/* =========================================================================
   ApexityEdu — Components
   Chips, buttons, cards, lists, tables, forms, drawers, toasts, modals,
   calendars, widgets, etc. The whole kit.
   v2.23: Remove blue tap highlight on mobile — native-like touch
   ========================================================================= */

* {
  -webkit-tap-highlight-color: transparent;
}
a, button, input, select, textarea, [role="button"], .btn, .chip, .list-row, .tabs button {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* --- Buttons -------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 8px 14px;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
  user-select: none;
  white-space: nowrap;
}
.btn:hover { background: var(--paper-soft); border-color: var(--ink-mute); }
.btn:active { transform: translateY(0.5px); }
.btn:disabled { opacity: 0.5; }

/* Link-styled button (for forgot-password, back-to-login, etc.) */
.btn-link {
  background: none;
  border: 0;
  padding: 0;
  color: var(--indigo);
  cursor: pointer;
  font: inherit;
}
.btn-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.btn-link:focus-visible { outline: 2px solid var(--marigold); outline-offset: 2px; border-radius: var(--radius-sm); }

.btn-primary {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  border-color: var(--btn-primary-bg);
}
.btn-primary:hover { background: var(--btn-primary-hover); border-color: var(--btn-primary-hover); }

.btn-marigold {
  background: var(--btn-accent-bg);
  color: var(--btn-accent-fg);
  border-color: var(--accent-strong);
}
.btn-marigold:hover { background: var(--btn-accent-hover); color: var(--btn-accent-fg); }

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-soft);
}
.btn-ghost:hover { background: var(--paper-soft); color: var(--ink); }

/* v2.13 (FOOTER-BTN-BORDER): ghost buttons placed in an action FOOTER
   (drawer / modal / card) read as primary actions, not inline text links —
   e.g. the People drawer's "Print profile" / "Admit card" / "Essential
   certificates" buttons sat borderless next to bordered buttons ("Delete",
   "Edit"), which looked like a rendering bug. Give ghost buttons the same
   visible hairline as secondary buttons whenever they appear in a footer. */
.drawer-footer .btn-ghost,
.modal-footer .btn-ghost,
.card-footer .btn-ghost {
  border-color: var(--hairline-strong);
  color: var(--ink);
}
.drawer-footer .btn-ghost:hover,
.modal-footer .btn-ghost:hover,
.card-footer .btn-ghost:hover {
  background: var(--paper-soft);
  border-color: var(--ink-mute);
}

.btn-danger {
  background: var(--btn-danger-bg);
  color: var(--btn-danger-fg);
  border-color: var(--btn-danger-bg);
}
.btn-danger:hover { background: var(--btn-danger-hover); border-color: var(--btn-danger-hover); }

.btn-sm { padding: 5px 9px; font-size: var(--fs-xs); }
.btn-lg { padding: 11px 18px; font-size: var(--fs-md); }
.btn-block { display: flex; width: 100%; }

.btn .icon { width: 16px; height: 16px; flex-shrink: 0; }
.btn-sm .icon { width: 14px; height: 14px; }

/* Button loading spinner */
.btn.is-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 14px; height: 14px;
  border: 2px solid var(--ink-mute);
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.7s linear infinite;
  opacity: 0.7;
}
.btn-marigold.is-loading::after {
  border-color: var(--ink);
  border-top-color: transparent;
}
@keyframes btn-spin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

/* Icon-only button */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  position: relative;
}
.icon-btn:hover { background: var(--paper-soft); color: var(--ink); }
.icon-btn:focus-visible { outline: 2px solid var(--marigold); outline-offset: 2px; }
.icon-btn .icon { width: 18px; height: 18px; }
.icon-btn.has-badge::after {
  content: "";
  position: absolute;
  top: 5px; right: 5px;
  width: 8px; height: 8px;
  background: var(--vermillion);
  border: 2px solid var(--paper);
  border-radius: 50%;
}

/* --- Chips --------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.4;
  letter-spacing: 0;
  border-radius: var(--radius-pill);
  background: var(--paper-soft);
  color: var(--ink-soft);
  border: 1px solid transparent;
  white-space: nowrap;
}
.chip-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

.chip-present  { background: var(--forest-soft); color: var(--forest); }
.chip-late     { background: var(--marigold-soft); color: var(--marigold-deep); }
.chip-absent   { background: var(--vermillion-soft); color: var(--vermillion); }
.chip-leave    { background: var(--indigo-soft); color: var(--indigo); }
.chip-paid     { background: var(--forest-soft); color: var(--forest); }
.chip-due      { background: var(--vermillion-soft); color: var(--vermillion); }
.chip-draft    { background: var(--paper-deep); color: var(--ink-mute); }
.chip-published{ background: var(--indigo-soft); color: var(--indigo); }
.chip-issued   { background: var(--forest-soft); color: var(--forest); }
.chip-overdue  { background: var(--vermillion-soft); color: var(--vermillion); }
.chip-pending  { background: var(--marigold-soft); color: var(--marigold-deep); }
.chip-approved { background: var(--forest-soft); color: var(--forest); }
.chip-rejected { background: var(--vermillion-soft); color: var(--vermillion); }
.chip-info     { background: var(--indigo-soft); color: var(--indigo); }
.chip-neutral  { background: var(--paper-soft); color: var(--ink-soft); }
/* v2.24: palette-named chips. Tags pick one of these colours, and several
   screens (fees, reports) already asked for them by name — without these
   rules those chips fell back to the unstyled default. */
.chip-indigo     { background: var(--indigo-soft); color: var(--indigo); }
.chip-forest     { background: var(--forest-soft); color: var(--forest); }
.chip-marigold   { background: var(--marigold-soft); color: var(--marigold-deep); }
.chip-vermillion { background: var(--vermillion-soft); color: var(--vermillion); }

.chip-outline {
  background: transparent;
  border-color: var(--hairline-strong);
  color: var(--ink-soft);
}

/* Filter chips (toggleable) */
.chip-filter {
  cursor: pointer;
  background: var(--surface);
  border: 1px solid var(--hairline);
  color: var(--ink-soft);
  transition: all var(--dur-fast) var(--ease-out);
}
.chip-filter:hover { border-color: var(--ink-mute); color: var(--ink); }
.chip-filter.is-active {
  background: var(--ink-surface);
  border-color: var(--ink-surface);
  color: var(--on-ink-surface);
}

/* --- Cards & surfaces ---------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--hairline);
}
.card-header h3 {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
}
.card-header .actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.card-body { padding: var(--space-5); }
.card-body-tight { padding: var(--space-4); }
.card-footer {
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--hairline);
  background: var(--surface-tint);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* Stat card — big number + label */
.stat {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.stat .stat-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
}
.stat .stat-value {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.stat .stat-delta {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.stat .stat-delta.up { color: var(--forest); }
.stat .stat-delta.down { color: var(--vermillion); }

/* --- Lists --------------------------------------------------------- */
.list { list-style: none; margin: 0; padding: 0; }
.list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.list-row:hover { background: var(--surface-tint); }
.list-row:last-child { border-bottom: 0; }
.list-row.is-selected { background: var(--marigold-soft); }

.list-row .row-main { flex: 1; min-width: 0; }
.list-row .row-title {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-md);
  color: var(--ink);
  margin-bottom: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.list-row .row-sub {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
}
.list-row .row-meta {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  text-align: right;
  white-space: nowrap;
}

/* Selectable row (with checkbox) */
.list-row.selectable { cursor: default; }
.list-row.selectable:hover { background: var(--surface-tint); }
.list-row .row-check { flex-shrink: 0; }

/* Empty state */
.empty-state {
  padding: var(--space-12) var(--space-6);
  text-align: center;
  color: var(--ink-mute);
}
.empty-state .empty-icon {
  width: 48px; height: 48px;
  margin: 0 auto var(--space-4);
  color: var(--hairline-strong);
}
.empty-state h3 {
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--ink-soft);
  margin-bottom: var(--space-2);
}
.empty-state p { font-size: var(--fs-sm); margin-bottom: var(--space-4); }

/* Error state with retry */
.error-state {
  padding: var(--space-8) var(--space-6);
  text-align: center;
}
.error-state .err-icon { color: var(--vermillion); margin-bottom: var(--space-3); }
.error-state h3 { color: var(--ink); margin-bottom: var(--space-2); }
.error-state p { color: var(--ink-mute); margin-bottom: var(--space-4); font-size: var(--fs-sm); }

/* --- Tables -------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
table.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
table.tbl thead th {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
  text-align: left;
  padding: var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
}
table.tbl thead th.num { text-align: right; }
table.tbl tbody td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}
table.tbl tbody tr { cursor: pointer; transition: background var(--dur-fast) var(--ease-out); }
table.tbl tbody tr:hover { background: var(--surface-tint); }
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl td.num {
  font-family: var(--font-mono);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
table.tbl td.muted { color: var(--ink-mute); }

/* IMPL-MOBILE (v2.7 rebuild): raw `table.tbl` (i.e. NOT wrapped in a
   `.table-wrap`) must not push the page wider than the viewport now that
   the global `html,body { overflow-x: clip }` safety net has been
   removed (it clipped off-screen content, making it invisible — the
   user's primary complaint). Tables wrapped in `.table-wrap` already
   scroll internally via the `overflow-x: auto` rule above. For raw
   tables, we force header + cell text to wrap and break long tokens
   (student IDs, emails, UUIDs) so every column fits within the table's
   100% width. This trades a little visual density for guaranteed
   no-horizontal-scroll on phones — the explicit goal of the v2.7 mobile
   rebuild. The 600px breakpoint matches the existing small-phone rules
   below; tablets (600–880px) keep nowrap headers for readability. */
@media (max-width: 600px) {
  table.tbl { max-width: 100%; }
  table.tbl thead th,
  table.tbl tbody td {
    white-space: normal;       /* override thead th's default nowrap */
    word-break: break-word;    /* break long tokens at word boundaries */
    overflow-wrap: anywhere;   /* last-resort break for unbreakable strings */
    padding: var(--space-2);   /* tighten density to compensate for wrapping */
  }
  /* Numeric cells: keep right-aligned + tabular figures, but allow the
     value to wrap if it's unusually long (e.g. a 20-digit receipt no.). */
  table.tbl td.num,
  table.tbl thead th.num {
    white-space: normal;
    word-break: break-all;
  }

  /* v2.22 (TBL-VERTICAL-NAMES FIX): the rules above must NEVER apply to a
     table that sits inside a `.table-wrap`. With a 9–10 column roster
     (Attendance → Roster), `width:100%` + `overflow-wrap:anywhere` gave
     the Student column ~20px, so "John Doe" wrapped one LETTER per line:
     J / o / h / n … vertically. A wrapped table's whole purpose is to
     scroll horizontally inside its own container — so wrapped tables keep
     nowrap cells and size to their content (`width:max-content` with a
     `min-width` of the container). The page never scrolls sideways; only
     the table's own strip does. Dense rosters that want per-student cards
     on phones opt into `.tbl-cards` instead (see below). */
  .table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  .table-wrap table.tbl {
    width: max-content;
    min-width: 100%;
    max-width: none;
  }
  .table-wrap table.tbl thead th,
  .table-wrap table.tbl tbody td {
    white-space: nowrap;
    word-break: normal;
    overflow-wrap: normal;
    padding: var(--space-2);
  }
  .table-wrap table.tbl td.num,
  .table-wrap table.tbl thead th.num {
    white-space: nowrap;
    word-break: normal;
  }
  /* A person's name inside ANY raw (unwrapped) table still gets a floor
     width so multi-column raw tables can't crush it letter-by-letter;
     long tokens (IDs) keep breaking, names (spaces) wrap at word
     boundaries only. */
  table.tbl tbody td.cell-person { min-width: 9em; }
}

/* v2.5.0 (MOBILE-TABLE-CARDS): opt-in card-collapse for dense tables that
   stay cramped on phones even with wrapping (inventory, fees admin). Below
   720px a `table.tbl.tbl-cards` hides its header row and renders every body
   row as its own bordered mini-card; each cell becomes a label:value pair
   driven by the cell's `data-label` attribute (mirroring the column
   header). Action cells use `.td-actions` and flush their icon buttons to
   the right without a label. Above 720px the class is inert — the normal
   table layout applies. */
@media (max-width: 720px) {
  table.tbl.tbl-cards thead { display: none; }
  table.tbl.tbl-cards,
  table.tbl.tbl-cards tbody,
  table.tbl.tbl-cards tbody tr,
  table.tbl.tbl-cards tbody td { display: block; width: 100%; }
  table.tbl.tbl-cards tbody tr {
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background: var(--surface);
    padding: var(--space-2) var(--space-3);
    margin: var(--space-2) 0;
    cursor: default;
  }
  table.tbl.tbl-cards tbody tr:first-child { margin-top: var(--space-2); }
  table.tbl.tbl-cards tbody td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    border-bottom: 1px dashed var(--hairline);
    padding: var(--space-2) 0;
    text-align: right;
    white-space: normal;
    word-break: break-word;
    overflow-wrap: anywhere;
  }
  table.tbl.tbl-cards tbody td:last-child { border-bottom: 0; }
  table.tbl.tbl-cards tbody td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--ink-mute);
    text-align: left;
  }
  /* Cells without a data-label (or an empty one) render content-only. */
  table.tbl.tbl-cards tbody td:not([data-label])::before,
  table.tbl.tbl-cards tbody td[data-label=""]::before { content: none; }
  table.tbl.tbl-cards tbody td.num,
  table.tbl.tbl-cards tbody td .num { text-align: right; }
  table.tbl.tbl-cards tbody td.td-actions { justify-content: flex-end; }
  table.tbl.tbl-cards tbody td.td-actions::before { content: none; }
  table.tbl.tbl-cards tbody td.td-actions .row { justify-content: flex-end; }
}

/* --- Forms --------------------------------------------------------- */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.field:last-child { margin-bottom: 0; }
.field label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--ink-soft);
  letter-spacing: 0.01em;
}
.field .hint { font-size: var(--fs-xs); color: var(--ink-mute); }
.field .err-msg {
  font-size: var(--fs-xs);
  color: var(--vermillion);
  font-weight: var(--fw-medium);
  display: none;
}
.field.has-error input,
.field.has-error select,
.field.has-error textarea { border-color: var(--vermillion); background: var(--vermillion-soft); }
.field.has-error .err-msg { display: block; }

input, select, textarea {
  width: 100%;
  padding: 9px 11px;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size: var(--fs-sm);
  line-height: 1.4;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
input:hover, select:hover, textarea:hover { border-color: var(--ink-mute); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--marigold);
  box-shadow: var(--focus-ring);
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
input[type="checkbox"], input[type="radio"] {
  width: auto; padding: 0; margin: 0;
  accent-color: var(--marigold);
}
input[type="file"] { padding: 6px; cursor: pointer; }
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237C7A8E' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  padding-right: 32px;
}
textarea { resize: vertical; min-height: 80px; line-height: 1.5; }

.input-group {
  display: flex;
  align-items: stretch;
}
.input-group .input-prefix {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-3);
  background: var(--paper-soft);
  border: 1px solid var(--hairline-strong);
  border-right: 0;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
}
.input-group input { border-radius: 0 var(--radius-md) var(--radius-md) 0; }

/* Inline search input */
.search-input {
  position: relative;
}
.search-input input { padding-left: 34px; }
.search-input .search-icon {
  position: absolute;
  left: 10px; top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px;
  color: var(--ink-mute);
  pointer-events: none;
}

/* --- Pagination ---------------------------------------------------- */
.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--hairline);
  background: var(--surface-tint);
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}
.pagination .page-nums { display: flex; gap: 2px; }
.pagination .page-nums button {
  min-width: 28px; height: 28px;
  padding: 0 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  cursor: pointer;
}
.pagination .page-nums button:hover { background: var(--paper-soft); }
.pagination .page-nums button.is-active {
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  font-weight: var(--fw-semibold);
}
.pagination .page-nums button:disabled { opacity: 0.4; cursor: not-allowed; }

/* --- Drawer (right slide-over) ------------------------------------- */
.drawer-host {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  pointer-events: none;
}
.drawer-host.is-open { pointer-events: auto; }
.drawer-backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
.drawer-host.is-open .drawer-backdrop { opacity: 1; }
.drawer {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(var(--drawer-w), 100vw);
  background: var(--surface);
  box-shadow: var(--shadow-drawer);
  transform: translateX(100%);
  transition: transform var(--dur-slow) var(--ease-out);
  display: flex;
  flex-direction: column;
}
.drawer.wide { width: min(var(--drawer-w-wide), 100vw); }
.drawer-host.is-open .drawer { transform: translateX(0); }
.drawer-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5);
  border-bottom: 1px solid var(--hairline);
}
.drawer-header h2 {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: 1.2;
}
.drawer-header .drawer-sub {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  margin-top: 4px;
}
.drawer-body {
  flex: 1;
  overflow-y: auto;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: var(--space-5);
}
.drawer-footer {
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--hairline);
  background: var(--surface-tint);
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
  /* v2.12 (FOOTER-OVERFLOW): wrap the buttons instead of letting them
     overflow the drawer/modal they belong in (e.g. the People drawer with
     6+ action buttons spilled outside the menu). */
  flex-wrap: wrap;
  max-width: 100%;
  overflow: hidden;
}
.drawer-footer .btn,
.drawer-footer .icon-btn { flex-shrink: 0; }

/* --- Modal --------------------------------------------------------- */
.modal-host {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  pointer-events: none;
}
.modal-host.is-open { pointer-events: auto; }
.modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
.modal-host.is-open .modal-backdrop { opacity: 1; }
.modal {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 460px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  transform: translateY(8px);
  opacity: 0;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
.modal.wide { max-width: 640px; }
.modal-host.is-open .modal { transform: translateY(0); opacity: 1; }
.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--space-5);
  border-bottom: 1px solid var(--hairline);
}
.modal-header h2 {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: 1.2;
}
.modal-body { padding: var(--space-5); overflow-y: auto; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.modal-footer {
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--hairline);
  background: var(--surface-tint);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  /* v2.12 (FOOTER-OVERFLOW): wrap instead of overflowing the modal. */
  flex-wrap: wrap;
  max-width: 100%;
  overflow: hidden;
}

/* --- Toast --------------------------------------------------------- */
.toast-host {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  pointer-events: none;
  max-width: 380px;
}
.toast {
  pointer-events: auto;
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  transform: translateX(110%);
  transition: transform var(--dur-base) var(--ease-out);
}
.toast.is-shown { transform: translateX(0); }
.toast .toast-icon { flex-shrink: 0; width: 18px; height: 18px; margin-top: 1px; }
.toast .toast-title { font-weight: var(--fw-semibold); margin-bottom: 2px; }
.toast .toast-msg { font-size: var(--fs-xs); opacity: 0.85; }
.toast-success { background: var(--forest); }
.toast-error   { background: var(--vermillion); }
.toast-info    { background: var(--indigo); }
.toast-warning { background: var(--marigold-deep); }

/* --- Avatar -------------------------------------------------------- */
.avatar {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--indigo-soft);
  color: var(--indigo);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  flex-shrink: 0;
  overflow: hidden;
  user-select: none;
}
.avatar.lg { width: 56px; height: 56px; font-size: var(--fs-lg); }
.avatar.xl { width: 80px; height: 80px; font-size: var(--fs-2xl); }
.avatar.sm { width: 28px; height: 28px; font-size: var(--fs-xs); }
.avatar img { width: 100%; height: 100%; max-width: 100%; max-height: 100%; object-fit: cover; object-position: center; display: block; }

/* --- Tabs ---------------------------------------------------------- */
.tabs {
  display: flex;
  /* v2.31 (MOBILE-TAB-FLOW): the strip is a SCROLLER, never a page-widener.
     `min-width: 0` + `max-width: 100%` stop the sum of the (nowrap) tab labels
     from pushing the content column wider than the viewport on phones — which
     is what made People / SMS / Academics Setup / Templates / Routine feel like
     a desktop page you had to pan sideways. `flex-wrap: nowrap` keeps it one
     row (wrapping would break the active-tab underline), `overscroll-behavior`
     keeps a swipe from chaining into the document, and the scrollbar is hidden
     because the right-edge fade (see `.tabs-scroll`) is the affordance. */
  flex-wrap: nowrap;
  min-width: 0;
  max-width: 100%;
  gap: 0;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: var(--space-5);
  overflow-x: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  position: relative;
  z-index: 2;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  /* Tabs keep their natural width and scroll into view instead of squashing
     each other (or turning into identical ellipsised fragments). */
  flex: 0 0 auto;
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-mute);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  margin-bottom: -1px;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--ink); border-bottom-color: var(--marigold); font-weight: var(--fw-semibold); }
.tab .tab-count {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  background: var(--paper-soft);
  color: var(--ink-mute);
  border-radius: var(--radius-pill);
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
}
.tab.is-active .tab-count { background: var(--marigold-soft); color: var(--marigold-deep); }

/* --- Calendar (attendance month) ----------------------------------- */
.cal {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  font-size: var(--fs-xs);
}
.cal-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  margin-bottom: 4px;
}
.cal-weekdays div {
  text-align: center;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  color: var(--ink-mute);
  padding: 4px 0;
}
.cal-cell {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-family: var(--font-mono);
  color: var(--ink-soft);
  cursor: pointer;
  position: relative;
  transition: background var(--dur-fast) var(--ease-out);
  min-height: 32px;
}
.cal-cell:hover { background: var(--paper-soft); }
.cal-cell.is-empty { border-color: transparent; background: transparent; cursor: default; }
.cal-cell.is-today { box-shadow: inset 0 0 0 2px var(--marigold); }
.cal-cell.s-present  { background: var(--forest-soft); color: var(--forest); border-color: var(--forest-soft); }
.cal-cell.s-late     { background: var(--marigold-soft); color: var(--marigold-deep); border-color: var(--marigold-soft); }
.cal-cell.s-absent   { background: var(--vermillion-soft); color: var(--vermillion); border-color: var(--vermillion-soft); }
.cal-cell.s-leave    { background: var(--indigo-soft); color: var(--indigo); border-color: var(--indigo-soft); }
.cal-cell.s-holiday  { background: var(--paper-deep); color: var(--ink-mute); border-color: var(--paper-deep); }

/* --- Donut chart (CSS conic-gradient) ------------------------------ */
.donut {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  position: relative;
  flex-shrink: 0;
}
.donut::after {
  content: "";
  position: absolute;
  inset: 14px;
  background: var(--surface);
  border-radius: 50%;
}
.donut-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 1;
  text-align: center;
}
.donut-center .donut-num {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.donut-center .donut-label {
  font-size: var(--fs-2xs);
  color: var(--ink-mute);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  margin-top: 2px;
}

/* --- Bar chart (CSS) ----------------------------------------------- */
.bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 120px;
  padding: var(--space-2) 0;
}
.bars .bar-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  height: 100%;
  justify-content: flex-end;
}
.bars .bar {
  width: 60%;
  background: var(--marigold);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  min-height: 2px;
  transition: height var(--dur-slow) var(--ease-out);
}
.bars .bar-label {
  font-size: var(--fs-2xs);
  color: var(--ink-mute);
  font-family: var(--font-mono);
}

/* --- Skeleton rows ------------------------------------------------- */
.skel-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--hairline);
}
.skel-row .skel-avatar { width: 32px; height: 32px; border-radius: 50%; }
.skel-row .skel-lines { flex: 1; }

/* --- Utility classes ----------------------------------------------- */
.row { display: flex; align-items: center; gap: var(--space-2); }
.row-between { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.row-wrap { flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: var(--space-2); }
.gap-1 { gap: var(--space-1); } .gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); } .gap-4 { gap: var(--space-4); }
.gap-6 { gap: var(--space-6); }
.flex-1 { flex: 1; min-width: 0; }
.flex-shrink-0 { flex-shrink: 0; }
.ml-auto { margin-left: auto; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.text-mute { color: var(--ink-mute); }
.text-soft { color: var(--ink-soft); }
.text-vermillion { color: var(--vermillion); }
.text-forest { color: var(--forest); }
.text-marigold { color: var(--marigold-deep); }
.text-indigo { color: var(--indigo); }
.text-xs { font-size: var(--fs-xs); }
.text-sm { font-size: var(--fs-sm); }
.text-md { font-size: var(--fs-md); }
.text-lg { font-size: var(--fs-lg); }
.fw-medium { font-weight: var(--fw-medium); }
.fw-semibold { font-weight: var(--fw-semibold); }
.fw-bold { font-weight: var(--fw-bold); }
.uppercase { text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.mt-2 { margin-top: var(--space-2); } .mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); } .mt-6 { margin-top: var(--space-6); }
.mb-2 { margin-bottom: var(--space-2); } .mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); } .mb-6 { margin-bottom: var(--space-6); }
.hidden { display: none !important; }
.grid { display: grid; gap: var(--space-4); }
/* v2.31 (MOBILE-TAB-FLOW): `minmax(0, 1fr)` — a bare `1fr` track can never
   shrink below the min-content of its cell, so one long unbreakable value in a
   card/field (a mono ID, a template code, an SMS preview line) widened the
   whole screen on phones. Content-based floors stay with the content, not the
   track. */
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
/* Grid ITEMS carry `min-width: auto` as well, so a cell holding a nowrap
   child (a tab strip, a button row, a wide card header) could still push the
   box past the track. Zeroing it lets the track win; long text wraps instead
   (mono values already carry `overflow-wrap`). */
.grid > *, .grid-2 > *, .grid-3 > *, .grid-4 > *, .grid-auto > * { min-width: 0; }
@media (max-width: 720px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
  /* Drawer & modal footers wrap on mobile so buttons don't overflow */
  .drawer-footer, .modal-footer {
    flex-wrap: wrap;
    justify-content: stretch;
  }
  .drawer-footer .btn, .modal-footer .btn {
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Drawer & modal headers can wrap */
  .drawer-header, .modal-header {
    flex-wrap: wrap;
  }
  /* Drawer body padding tighter on mobile */
  .drawer-body, .modal-body {
    padding: var(--space-4);
  }
  /* Modal host: less padding so more screen real estate on mobile */
  .modal-host { padding: var(--space-2); }
  /* Page title actions wrap below title on narrow screens */
  .page-title { flex-wrap: wrap; }
  .page-title .actions-row { width: 100%; flex-wrap: wrap; }
  /* Tabs scroll horizontally instead of wrapping awkwardly */
  .tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .tabs .tab { flex-shrink: 0; }
  /* Sticky bars full-width */
  .sticky-bar { left: 0; right: 0; border-radius: 0; }
}

/* IMPL-MOBILE: 600px breakpoint — small tablets / large phones.
   - grid-3 and grid-4 drop to 1-col (redundant with the 720px rule above
     but explicit per the mobile audit; documents that these wide grids
     must never render multi-col below 600px even if the 720px rule is
     later loosened).
   - Tab labels are NEVER squeezed to fit any more: v2.31 deleted the
     `max-width: 32vw` + ellipsis rule, because a strip that scrolls
     (C.tabs renders `.tabs-scroll`) keeps the labels readable. */
@media (max-width: 600px) {
  .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
  /* v2.31 (MOBILE-TAB-FLOW): the old `max-width: 32vw` + ellipsis rule is GONE
     on purpose. Squeezing every tab into 32vw turned real labels ("Delivery
     logs", "Academic years") into identical "De…" fragments — the tabs looked
     broken even though the screen was fine. Since C.tabs() now always renders
     inside `.tabs-scroll`, the strip scrolls, snaps and reveals the active tab
     instead, so labels stay whole and readable. */
  .tabs { scroll-padding-inline: var(--space-3); margin-bottom: var(--space-4); }
  .tabs .tab { scroll-margin-inline: var(--space-3); }
  /* Horizontal utility rows reflow instead of pushing the page sideways. */
  .row { flex-wrap: wrap; }
  .row-between { row-gap: var(--space-2); }
}

/* --- Mobile responsive (880px breakpoint) -----------------------------
   Modals & drawers become near-full-screen on mobile to maximize the
   limited viewport. This block is placed AFTER the 720px block above so
   its rules cascade-override at <720px widths too. */
@media (max-width: 880px) {
  /* Modal: full-bleed on mobile */
  .modal-host {
    padding: 0;
    align-items: stretch;
    justify-content: stretch;
  }
  .modal {
    max-width: none;
    width: 100%;
    max-height: 100vh;
    height: 100vh;
    /* v2.31 (MOBILE-TAB-FLOW): a full-bleed modal sized with 100vh runs
       past the visible phone viewport, so its footer buttons (Save/Cancel)
       needed the page itself to be scrolled — dvh sizes it to what the user
       can actually see, and it also re-fits when the keyboard opens. */
    max-height: 100dvh;
    height: 100dvh;
    border-radius: 0;
    box-shadow: none;
  }
  .modal-body {
    flex: 1 1 auto;
    min-height: 0;
  }

  /* Drawer: full-bleed on mobile (covers .drawer and .drawer.wide) */
  .drawer, .drawer.wide {
    width: 100vw;
    max-width: 100vw;
  }

  /* Sticky bar / bulk bar: stretch full width on mobile */
  .bulk-bar {
    flex-wrap: wrap;
    border-radius: 0;
  }
  .bulk-bar .bb-actions { width: 100%; justify-content: stretch; }

  /* Toast: position above the bottom-nav on mobile so it stays visible */
  .toast-host {
    bottom: calc(72px + var(--space-3));
    right: var(--space-3);
    left: var(--space-3);
    max-width: none;
  }
}

/* IMPL-MOBILE: very-narrow phones (≤480px). Bulk action buttons become
   icon-only so 3+ actions fit on one row. The buttons are rendered by
   C.button() with icon + label-as-text-node; we hide the text node via
   font-size:0 (the SVG icons have explicit width/height attributes so
   they remain visible at their intrinsic size). The :has(svg) guard
   ensures we never hide the label of a text-only button. The .btn-label
   selector is included as documented intent (no-op today; if a future
   refactor wraps labels in .btn-label spans, this rule starts working). */
@media (max-width: 480px) {
  .bulk-bar .bb-actions .btn .btn-label { display: none; }
  .bulk-bar .bb-actions .btn:has(svg) { font-size: 0; padding: 8px 10px; }
  .bulk-bar .bb-actions .btn:has(svg) .icon { font-size: initial; }
}

/* --- Extreme mobile-friendliness ---------------------------------------
   Touch targets, iOS zoom prevention, wrapping, and bottom-nav clearance
   so the app is usable one-handed on a 360px phone. */
@media (max-width: 880px) {
  /* iOS Safari zooms any input < 16px on focus — keep fields readable. */
  input, select, textarea { font-size: 16px !important; }

  /* Comfortable tap targets */
  .btn { min-height: 44px; padding: 10px 14px; }
  .btn-sm { min-height: 36px; }
  .icon-btn { width: 44px; height: 44px; }
  .tab { min-height: 44px; padding: 12px 14px; }
  .list-row { padding: 12px 16px; }
  .chip-chip { min-height: 36px; padding: 8px 14px; }
  .bottom-nav .bn-item { min-height: 52px; }
  .am-pick, .am-undo, .qp-pb { min-width: 36px; min-height: 36px; }

  /* Kill the 300ms tap delay */
  .btn, .icon-btn, .tab, .list-row, .sidebar-item, .bn-item, a {
    touch-action: manipulation;
  }

  /* Page title + card headers stack so actions stay reachable */
  .page-title { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .page-title .actions-row,
  .card-header .actions { width: 100%; flex-wrap: wrap; }
  .card-header { flex-wrap: wrap; }
  .card-footer { flex-wrap: wrap; justify-content: stretch; }
  .card-footer .btn { flex: 1 1 auto; }

  /* List rows: meta wraps under the title instead of overflowing */
  .list-row { flex-wrap: wrap; }
  .list-row .row-meta { text-align: left; width: 100%; white-space: normal; }

  /* Pagination wraps and bigger hit targets */
  .pagination { flex-wrap: wrap; gap: var(--space-2); }
  .pagination .page-nums button { min-width: 36px; height: 36px; }

  /* More-sheet: 3 columns so labels stay readable */
  .more-sheet .ms-grid { grid-template-columns: repeat(3, 1fr); }

  /* Sticky pay/save bars sit above the bottom nav */
  .sticky-bar {
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
    flex-wrap: wrap;
  }
}

@media (max-width: 480px) {
  .more-sheet .ms-grid { grid-template-columns: repeat(3, 1fr); }
  .dash-cards { grid-template-columns: minmax(0, 1fr); }
  .am-grid { grid-template-columns: repeat(2, 1fr); }
  .school-grid { grid-template-columns: minmax(0, 1fr); }
  .feature-grid { grid-template-columns: minmax(0, 1fr); }
  .form-topbar { height: auto; min-height: var(--topbar-h); padding: 8px 12px; }
  .form-main { padding: var(--space-4) var(--space-3); }
  .identity-card .ic-meta { grid-template-columns: minmax(0, 1fr); }
}

/* Divider with label */
.divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0;
  color: var(--ink-mute);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}

/* Badge with number */
.badge-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--vermillion);
  color: var(--surface);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  border-radius: var(--radius-pill);
  line-height: 1;
}

/* Progress bar */
.progress {
  width: 100%;
  height: 4px;
  background: var(--paper-deep);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress .progress-bar {
  height: 100%;
  background: var(--marigold);
  border-radius: var(--radius-pill);
  transition: width var(--dur-slow) var(--ease-out);
}

/* File upload dropzone */
.dropzone {
  border: 1.5px dashed var(--hairline-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  text-align: center;
  background: var(--surface-tint);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.dropzone:hover, .dropzone.is-drag {
  border-color: var(--marigold);
  background: var(--marigold-soft);
}
.dropzone .dz-icon {
  width: 32px; height: 32px;
  margin: 0 auto var(--space-3);
  color: var(--ink-mute);
}

/* File preview chip */
.file-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 10px;
  background: var(--paper-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
}
.file-chip .file-icon { width: 14px; height: 14px; color: var(--ink-mute); }
.file-chip .file-name { font-weight: var(--fw-medium); }
.file-chip .file-remove { cursor: pointer; color: var(--ink-mute); }
.file-chip .file-remove:hover { color: var(--vermillion); }
.file-chip.is-pending { opacity: 0.6; border-color: var(--marigold); }
.file-chip.is-done { border-color: var(--forest); }
.file-chip.is-failed { border-color: var(--vermillion); }

/* File uploader */
.file-uploader { display: flex; flex-direction: column; gap: var(--space-2); }
.fu-drop {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1.5px dashed var(--hairline);
  border-radius: var(--radius-md);
  background: var(--paper-soft);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.fu-drop:hover { border-color: var(--marigold); background: var(--paper-deep); }
.fu-drop.is-over { border-color: var(--marigold); background: var(--marigold-soft); }
.fu-icon { color: var(--ink-mute); display: flex; align-items: center; }
.fu-title { font-weight: var(--fw-medium); }
.fu-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* KBD */
kbd {
  display: inline-block;
  padding: 1px 5px;
  background: var(--paper-soft);
  border: 1px solid var(--hairline-strong);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-soft);
  line-height: 1.4;
}

/* Stack of action buttons (right-aligned) */
.actions-row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.actions-row.right { justify-content: flex-end; }

/* Tag list */
.tag-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Notification panel item */
.notif-item {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.notif-item:hover { background: var(--surface-tint); }
.notif-item:last-child { border-bottom: 0; }
.notif-item.is-unread { background: var(--marigold-soft); }
.notif-item.is-unread:hover { background: var(--marigold-soft); }
.notif-item .notif-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--marigold);
  flex-shrink: 0;
  margin-top: 6px;
}
.notif-item .notif-title { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--ink); }
.notif-item .notif-body { font-size: var(--fs-xs); color: var(--ink-mute); margin-top: 2px; }
.notif-item .notif-time { font-size: var(--fs-2xs); color: var(--ink-mute); font-family: var(--font-mono); margin-top: 4px; }

/* Person name block — English primary, Bengali secondary */
.person-name {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.person-name .pn-en { font-weight: var(--fw-semibold); color: var(--ink); }
.person-name .pn-bn { font-family: var(--font-bengali); font-size: var(--fs-xs); color: var(--ink-mute); }

/* ID chip — monospace, framed */
.id-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  background: var(--paper-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-soft);
  font-weight: var(--fw-medium);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Sticky save bar */
.sticky-bar {
  position: sticky;
  bottom: 0;
  left: 0; right: 0;
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  padding: var(--space-3) var(--space-5);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  z-index: var(--z-sticky);
}
.sticky-bar .sticky-info { font-size: var(--fs-sm); }
.sticky-bar .sticky-info strong { font-family: var(--font-mono); }

/* Inline validation error message */
.field-error {
  font-size: var(--fs-xs);
  color: var(--vermillion);
  font-weight: var(--fw-medium);
}

/* Inline success message */
.field-success {
  font-size: var(--fs-xs);
  color: var(--forest);
  font-weight: var(--fw-medium);
}

/* Banner (error / maintenance) */
.banner {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.banner-error { background: var(--vermillion-soft); color: var(--vermillion); border: 1px solid var(--vermillion); }
.banner-warning { background: var(--marigold-soft); color: var(--marigold-deep); border: 1px solid var(--marigold); }
.banner-info { background: var(--indigo-soft); color: var(--indigo); border: 1px solid var(--indigo); }
.banner .banner-icon { flex-shrink: 0; width: 18px; height: 18px; margin-top: 1px; }
.banner .banner-content strong { display: block; margin-bottom: 2px; }

/* Code block */
pre, code {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  background: var(--paper-soft);
  border-radius: var(--radius-sm);
}
pre {
  padding: var(--space-3) var(--space-4);
  overflow-x: auto;
  border: 1px solid var(--hairline);
  line-height: 1.5;
}
code { padding: 1px 4px; }

/* Switch (toggle) */
.switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--hairline-strong);
  border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-out);
}
.switch .slider::before {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  left: 3px; bottom: 3px;
  background: var(--surface);
  border-radius: 50%;
  transition: transform var(--dur-fast) var(--ease-out);
}
.switch input:checked + .slider { background: var(--marigold); }
.switch input:checked + .slider::before { transform: translateX(16px); }
.switch input:focus-visible + .slider { box-shadow: var(--focus-ring); }

/* Segmented control */
.segmented {
  display: inline-flex;
  background: var(--paper-soft);
  border-radius: var(--radius-md);
  padding: 2px;
  gap: 2px;
}
.segmented button {
  padding: 5px 12px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--ink-soft);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.segmented button.is-active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.segmented button:hover:not(.is-active) { color: var(--ink); }

/* Bulk action bar (sticky bottom) */
.bulk-bar {
  position: sticky;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: var(--z-sticky, 300);
  margin-top: var(--space-3);
}
.bulk-bar .bb-count {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.bulk-bar .bb-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.bulk-bar .btn-ghost {
  background: transparent;
  color: var(--on-ink-surface);
  border-color: rgba(246, 241, 231, 0.45);
}
.bulk-bar .btn-ghost:hover {
  background: rgba(246, 241, 231, 0.12);
  color: var(--on-ink-surface);
}

/* IMPL-MOBILE: Admission form schema-field-row. Replaces the inline
   `display:grid; grid-template-columns:1fr 1fr 1fr auto` style that was
   hardcoded in admissions.js (the audit found it unusable on phones —
   4 fixed columns + gap + label/type/options/trash didn't fit on a 360px
   modal). Now a named class with responsive collapse:
   - >720px: 4 columns (label | type | options/file-type | required+trash)
   - ≤720px: 2 columns (options/file-type and required+trash wrap full-width)
   - ≤480px: 1 column (everything stacks) */
.schema-field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: var(--space-2);
  align-items: end;
}
@media (max-width: 720px) {
  .schema-field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .schema-field-row > :nth-child(3) { grid-column: 1 / -1; }  /* options field full-width */
  .schema-field-row > :last-child { grid-column: 1 / -1; }  /* trash button full-width */
}
@media (max-width: 480px) {
  .schema-field-row { grid-template-columns: minmax(0, 1fr); }
}

/* Chip group (multi-select) */
.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.chip-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 6px 12px;
  background: var(--paper-soft);
  color: var(--ink-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.chip-chip:hover {
  background: var(--paper-deep);
  color: var(--ink);
}
.chip-chip.is-on {
  background: var(--marigold-soft, rgba(232, 155, 44, 0.18));
  color: var(--marigold-deep);
  border-color: var(--marigold);
}
.chip-chip.is-on::before {
  content: "✓";
  font-weight: var(--fw-semibold);
  margin-right: 2px;
}

/* Checkbox row (in list) */
.cb-row {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  padding: 4px;
}
.cb-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--marigold);
  cursor: pointer;
}

/* Code / URL block (webhook docs) */
.code-block {
  background: var(--paper-deep);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.code-block .cb-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  background: var(--paper-soft);
  border-bottom: 1px solid var(--hairline);
}
.code-block .cb-label {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--fw-semibold);
}
.code-block .cb-pre {
  margin: 0;
  padding: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-all;
  overflow-x: auto;
}

/* Bootstrap loading skeleton (full-screen) */
.bootstrap-skeleton {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  gap: var(--space-3);
}
.bootstrap-skeleton .bs-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--paper-deep);
  border-top-color: var(--marigold);
  border-radius: 50%;
  animation: bs-spin 0.9s linear infinite;
}
.bootstrap-skeleton .bs-label {
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}
@keyframes bs-spin {
  to { transform: rotate(360deg); }
}

/* Webhook docs card */
.webhook-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}
.webhook-card h4 {
  margin: 0 0 var(--space-2) 0;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  color: var(--ink);
}
.webhook-card p {
  margin: 0 0 var(--space-3) 0;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* Tab helper text (filters per tab) */
.tab-helper {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  margin-bottom: var(--space-3);
}

/* Compact table density (toggled from Profile > Preferences) */
body.density-compact .tbl td,
body.density-compact .tbl th {
  padding-top: 6px;
  padding-bottom: 6px;
  font-size: 13px;
}
body.density-compact .list-row {
  padding-top: 8px;
  padding-bottom: 8px;
}
body.density-compact .card {
  padding: 12px;
}

/* =========================================================================
   v2.12.1 — mobile/desktop hardening (NAV-MOBILE-2)
   ========================================================================= */

/* Nothing may push the page wider than the viewport: wrap long unbroken
   strings inside list rows, callouts and toasts. */
.row-title, .row-sub, .callout, .toast-msg, .bb-count, .sticky-info {
  overflow-wrap: anywhere;
  min-width: 0;
}
.modal-header h2, .drawer-header h2 { overflow-wrap: anywhere; }

/* Quiz option labels wrap on narrow phones instead of clipping. */
.qq-option span, .quiz-q .qq-option span {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Sticky save/mark bars wrap instead of overflowing narrow screens. */
@media (max-width: 880px) {
  .sticky-bar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }
  .sticky-bar > .row,
  .sticky-bar > div { justify-content: flex-end; flex-wrap: wrap; }
  /* Drawer/modals: breathing room above the home indicator. */
  .drawer-footer { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-4)); }
  .modal-footer { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-4)); }
  /* Rows inside cards may contain many chips — let them wrap. */
  .row-between { flex-wrap: wrap; gap: var(--space-2); }
  .row-meta { white-space: normal; }
  /* Keep table cells from forcing huge widths: allow the table-wrap
     horizontal scroll to own overflow. */
  .tbl { min-width: 0; }
}

/* =========================================================================
   v2.22 — Templates tab mobile rebuild (TEMPLATES-MOBILE) + misc phone fixes
   ========================================================================= */

/* Scrollable tab strip with a visible affordance. `.tabs-scroll` wraps a
   .tabs bar that has more tabs than the viewport can show (Templates has 10).
   Horizontal scroll + scroll-snap + a right-edge fade hint that more tabs
   exist. Without this, the 600px `max-width: 32vw` truncation turned every
   long tab label into the same ellipsised fragment.
   v2.31 (MOBILE-TAB-FLOW): `C.tabs()` now ALWAYS returns its strip inside this
   wrapper, so every tabbed screen (People, SMS, Academics Setup, Exams Setup,
   Routine, Templates, fees / library / quizzes / …) gets the identical
   phone-friendly behaviour instead of only the screens that remembered to
   wrap it by hand. */
.tabs-scroll {
  position: relative;
  max-width: 100%;
  /* shrinkable in every layout context (flex/grid parents included) */
  min-width: 0;
  overflow: hidden;
}
.tabs-scroll::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 1px;
  width: 36px;
  background: linear-gradient(to right, transparent, var(--paper));
  pointer-events: none;
  opacity: 1;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.tabs-scroll.at-end::after { opacity: 0; }
.tabs-scroll .tabs {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.tabs-scroll .tabs::-webkit-scrollbar { display: none; }
.tabs-scroll .tabs .tab {
  flex-shrink: 0;
  scroll-snap-align: start;
  /* Only a guard against pathologically long labels — was 40vw, which started
     ellipsising ordinary labels once this wrapper became the default for every
     tabbed screen. 72vw keeps any realistic label whole; longer ones still get
     a fade-out rather than pushing the strip around. */
  max-width: min(72vw, 280px);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Template cards on phones: the header row (name+chips) and the button row
   stack cleanly instead of squeezing into two muddy columns. */
@media (max-width: 600px) {
  .dash-cards .card .actions-row { gap: var(--space-2); }
  .dash-cards .card .actions-row .btn { flex: 1 1 auto; }
  .dash-cards .card .row-between { flex-direction: column; align-items: flex-start; }
  /* The template HTML textarea must SOFT-WRAP — long template lines were
     widening the modal body and triggering janky horizontal scroll. */
  .modal textarea {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    word-break: break-word;
    font-size: 13px;
  }
  /* Long unbreakable tokens inside modal callouts ({{#students}} loops,
     code-ish hints) wrap instead of overflowing the modal. */
  .modal .callout { overflow-wrap: anywhere; }
  .modal .callout .mono { overflow-wrap: anywhere; }
}

/* Offline banner (v2.22 OFFLINE-MODE) — injected into the app shell by
   app.html's connectivity watcher. Sits under the topbar, above content. */
.offline-banner {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px var(--space-4);
  background: var(--vermillion);
  color: #fff;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-sm);
}
.offline-banner .ob-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: #fff;
  flex-shrink: 0;
  animation: ob-pulse 1.6s ease-in-out infinite;
}
@keyframes ob-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
.offline-banner .ob-body { min-width: 0; }
.offline-banner .ob-title { font-weight: var(--fw-semibold); }
.offline-banner .ob-sub {
  font-weight: var(--fw-regular);
  font-size: var(--fs-xs);
  opacity: 0.9;
  overflow-wrap: anywhere;
}
.offline-banner button {
  margin-left: auto;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--radius-sm);
  padding: 5px 10px;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.offline-banner button:hover { background: rgba(255, 255, 255, 0.28); }
