/* ============================================================
   GODDY TABI — DASHBOARD CSS
   All role dashboards and common dashboard components
   ============================================================ */

/* ── Dashboard page ── */
.dashboard-page {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* ── Greeting row ── */
.dash-greeting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s5);
}
.dash-greeting__left {}
.dash-greeting__hi {
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-weight: var(--medium);
}
.dash-greeting__name {
  font-size: var(--text-2xl);
  font-weight: var(--black);
  color: var(--navy);
  line-height: 1.1;
}

/* ── Overview card (navy gradient) ── */
.overview-card {
  background: linear-gradient(135deg, var(--navy-deep) 0%, var(--navy-mid) 100%);
  border-radius: var(--r-2xl);
  padding: var(--s5);
  margin-bottom: var(--s4);
  position: relative;
  overflow: hidden;
}
.overview-card::before {
  content: '';
  position: absolute;
  top: -50px; right: -50px;
  width: 180px; height: 180px;
  border-radius: var(--r-full);
  background: rgba(212,160,23,0.08);
  pointer-events: none;
}
.overview-card::after {
  content: '';
  position: absolute;
  bottom: -30px; left: 20px;
  width: 100px; height: 100px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,0.03);
  pointer-events: none;
}
.overview-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s4);
}
.overview-card__label {
  font-size: var(--text-xs);
  font-weight: var(--semibold);
  color: rgba(255,255,255,0.55);
  letter-spacing: 0.5px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.overview-card__label svg {
  width: 14px; height: 14px;
  stroke: rgba(255,255,255,0.55);
}
.overview-card__period {
  font-size: var(--text-xs);
  font-weight: var(--semibold);
  color: rgba(255,255,255,0.65);
  background: rgba(255,255,255,0.08);
  border-radius: var(--r-full);
  padding: 4px 10px;
  cursor: pointer;
}
.overview-stats {
  display: grid;
  /* stat count varies by role now (2 for teacher/parent, up to 5 for
     admin) — --stat-cols is set inline per render instead of a fixed 4 */
  grid-template-columns: repeat(var(--stat-cols, 4), 1fr);
  gap: var(--s2);
  position: relative;
  z-index: 1;
}
.overview-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  position: relative;
  /* Grid items default to min-width:auto, which blocks them from
     shrinking below their content's intrinsic size (the fixed-size
     icon circle + non-wrapping number). On narrow/cheap Android
     screens that intrinsic minimum for 4 columns can exceed the
     available card width, so instead of shrinking to fit, the grid
     overflowed and one tile wrapped onto its own row — "4 stats"
     rendering as "3 stats (+1 orphaned)". min-width:0 lets the track
     actually shrink; the fluid sizes below make sure it stays legible
     once it does. */
  min-width: 0;
}
.overview-stat--action { cursor: pointer; }
.overview-stat--action:active { opacity: 0.7; }
/* Small gold dot under the label — the only hint (besides tap feedback)
   that a tile leads somewhere, since :hover/cursor mean nothing on the
   touch devices these tiles are actually used on. */
.overview-stat--action .overview-stat__label::after {
  content: '';
  display: inline-block;
  width: 4px; height: 4px;
  margin-left: 4px;
  border-radius: 50%;
  background: var(--gold);
  vertical-align: middle;
}
.overview-stat + .overview-stat::before {
  content: '';
  position: absolute;
  left: 0; top: 15%; height: 70%;
  width: 1px;
  background: rgba(255,255,255,0.1);
}
.overview-stat__icon-wrap {
  /* Fluid instead of fixed-34px — that fixed size was the main driver
     of the mobile overflow (see .overview-stat's min-width:0 note). */
  width: clamp(24px, 8vw, 34px); height: clamp(24px, 8vw, 34px);
  border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.overview-stat__icon-wrap svg {
  width: 60%; height: 60%;
}
.overview-stat__value {
  font-size: clamp(14px, 4.6vw, var(--text-xl));
  font-weight: var(--black);
  color: white;
  line-height: 1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* min-width: 0 is critical for text-overflow to work in a flex
     column: without it, the flex item defaults to min-width:auto and
     won't shrink below its content (e.g., "Pending" overflows). */
  min-width: 0;
}
.overview-stat__label {
  font-size: clamp(8.5px, 2.6vw, 10px);
  color: rgba(255,255,255,0.45);
  font-weight: var(--medium);
  text-align: center;
  max-width: 100%;
  overflow-wrap: break-word;
}
.overview-stat__sub {
  font-size: clamp(7.5px, 2.3vw, 9px);
  color: rgba(255,255,255,0.38);
  font-weight: var(--medium);
  text-align: center;
  line-height: 1.3;
  max-width: 100%;
  overflow-wrap: break-word;
}

/* ── Home nudge — small encouraging one-liner under the Overview card
   (e.g. "no reports sent yet" / "all reports sent"). Only appears at
   the two extremes so it stays a genuine nudge, not routine noise. ── */
.dash-nudge {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-lg);
  margin-bottom: var(--s4);
  font-size: var(--text-xs);
  line-height: 1.4;
}
.dash-nudge--action {
  background: rgba(217,119,6,0.09);
  border: 1px solid rgba(217,119,6,0.22);
  color: #92400E;
  cursor: pointer;
}
.dash-nudge--action:active { opacity: 0.75; }
.dash-nudge--good {
  background: rgba(5,150,105,0.09);
  border: 1px solid rgba(5,150,105,0.22);
  color: #065F46;
}
.dash-nudge__icon { font-size: 18px; flex-shrink: 0; }
.dash-nudge__text { font-weight: var(--medium); }
.dash-nudge__dismiss {
  flex-shrink: 0;
  width: 22px; height: 22px;
  border: none; background: transparent;
  color: inherit; opacity: 0.6;
  font-size: 15px; line-height: 1;
  cursor: pointer;
  border-radius: 50%;
}
.dash-nudge__dismiss:hover { opacity: 1; background: rgba(0,0,0,0.06); }

/* ── First-run guidance card — replaces the whole home body for a
   brand-new teacher/parent with nothing linked yet, so a screen full of
   unexplained zeros never reads as "broken". ── */
.dash-empty-card {
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: var(--r-2xl);
  padding: 36px 24px;
  text-align: center;
}
.dash-empty-card__icon { font-size: 40px; margin-bottom: 12px; }
.dash-empty-card__title {
  font-size: var(--text-base);
  font-weight: var(--bold);
  color: var(--navy);
  margin-bottom: 8px;
}
.dash-empty-card__sub {
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.5;
  max-width: 320px;
  margin: 0 auto;
}

/* ── Week progress ── */
.week-progress-card {
  background: var(--surface);
  border-radius: var(--r-xl);
  border: 1px solid var(--border-light);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s4);
}
.week-progress-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s3);
}
.week-progress-card__title {
  font-size: var(--text-sm);
  font-weight: var(--bold);
  color: var(--text-primary);
}
.week-progress-card__count {
  font-size: var(--text-sm);
  font-weight: var(--bold);
  color: var(--navy);
}
.week-progress-card__sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: var(--s2);
}

/* ── Quick access list ── */
.qa-card {
  background: var(--surface);
  border-radius: var(--r-xl);
  border: 1px solid var(--border-light);
  overflow: hidden;
  margin-bottom: var(--s4);
}
.qa-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 14px var(--s4);
  border-bottom: 1px solid var(--border-light);
  cursor: pointer;
  transition: background var(--fast);
}
.qa-item:last-child { border-bottom: none; }
.qa-item:hover { background: var(--bg); }
.qa-item:active { background: var(--border-light); transform: scale(0.99); }
.qa-item__icon {
  width: 42px; height: 42px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.qa-item__icon svg { width: 20px; height: 20px; }
.qa-item__text { flex: 1; min-width: 0; }
.qa-item__title {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  color: var(--text-primary);
}
.qa-item__sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: 1px;
}
.qa-item__right {
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.qa-item__arrow {
  width: 16px; height: 16px;
  stroke: var(--text-muted);
  stroke-width: 2;
}

/* ── Stat rows — teacher/parent "this week" status card (learners
   total, report/attendance/punctuality status, due countdown).
   Same shell as .qa-item but never clickable, with a right-aligned
   value/badge instead of a chevron. ── */
.stat-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 13px var(--s4);
  border-bottom: 1px solid var(--border-light);
}
.stat-row:last-child { border-bottom: none; }
.stat-row__icon {
  width: 40px; height: 40px;
  border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.stat-row__icon svg { width: 19px; height: 19px; }
.stat-row__body { flex: 1; min-width: 0; }
.stat-row__label {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  color: var(--text-primary);
}
.stat-row__sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: 1px;
}
.stat-row__value {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  text-align: right;
  white-space: nowrap;
  flex-shrink: 0;
}
.stat-row__badge {
  display: inline-block;
  font-size: 11px;
  font-weight: var(--semibold);
  padding: 3px 9px;
  border-radius: 100px;
  white-space: nowrap;
}

/* .dash-banner / .dash-all-good rules removed with the components
   that used them — see the note in dashboard.js where alertRow /
   allGoodRow / bannerCard used to be defined. */

/* ── Children row ── */
.children-row {
  display: flex;
  gap: var(--s2);
  overflow-x: auto;
  padding-bottom: var(--s2);
  margin-bottom: var(--s4);
  scrollbar-width: none;
}
.children-row::-webkit-scrollbar { display: none; }

.child-pill {
  display: flex;
  align-items: center;
  gap: var(--s2);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--r-full);
  padding: var(--s1) var(--s3) var(--s1) 5px;
  cursor: pointer;
  flex-shrink: 0;
  transition: all var(--fast);
}
.child-pill.active { border-color: var(--navy); }
.child-pill:active { transform: scale(0.96); }
.child-pill__photo {
  width: 30px; height: 30px;
  border-radius: var(--r-full);
  background: var(--navy);
  display: flex; align-items: center; justify-content: center;
  color: var(--gold);
  font-size: 10px;
  font-weight: var(--bold);
  overflow: hidden;
  flex-shrink: 0;
}
.child-pill__photo img {
  width: 100%; height: 100%;
  object-fit: cover; border-radius: var(--r-full);
}
/* "All Children" rows in the Reports/Forum child-picker sheets reuse
   this same avatar-circle slot with an icon instead of a photo. */
.child-pill__photo svg { width: 15px; height: 15px; }
.child-pill__name {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  color: var(--navy);
  white-space: nowrap;
}
.child-pill__class {
  font-size: 10px;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ── Child switcher — compact avatar + "Switch Child" button in the
   top-right of a parent's greeting row. Replaces the old inline
   children-row of pills (kept above for its .child-pill__photo styling,
   reused inside the switcher's own picker sheet below).
   Deliberately NOT a navy-circle-with-gold-ring — that's the main
   topbar profile avatar's look, and using the same shape/colors here
   read as "a second profile button" rather than its own control. This
   one is smaller, a rounded square, color-reversed (gold fill / navy
   ring), and carries its own small "swap" badge + labeled pill so it's
   unmistakably a distinct, secondary action. ── */
.child-switcher {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}
.child-switcher:disabled { cursor: default; }
.child-switcher:active:not(:disabled) { transform: scale(0.96); }
.child-switcher__avatar-wrap {
  position: relative;
}
.child-switcher__avatar {
  width: 32px; height: 32px;
  border-radius: var(--r-md);
  background: linear-gradient(155deg, var(--gold-light) 0%, var(--gold) 100%);
  border: 2px solid var(--navy);
  display: flex; align-items: center; justify-content: center;
  color: var(--navy);
  font-size: 12px;
  font-weight: var(--bold);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.child-switcher__avatar img {
  width: 100%; height: 100%;
  object-fit: cover; border-radius: 8px;
}
.child-switcher__swap-badge {
  position: absolute;
  bottom: -3px; right: -5px;
  width: 16px; height: 16px;
  border-radius: var(--r-full);
  background: var(--navy);
  border: 1.5px solid var(--surface);
  display: flex; align-items: center; justify-content: center;
  color: var(--gold-light);
  box-shadow: var(--shadow-xs);
}
.child-switcher__swap-badge svg { width: 9px; height: 9px; }
.child-switcher__label {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 9.5px;
  font-weight: var(--bold);
  color: var(--navy);
  white-space: nowrap;
  background: var(--gold-pale);
  padding: 2px 7px;
  border-radius: var(--r-full);
  letter-spacing: 0.2px;
}
.child-switcher__label svg { width: 9px; height: 9px; flex-shrink: 0; color: var(--gold); }
.child-switcher:disabled .child-switcher__label { visibility: hidden; }

/* Guidance banner at the top of the switcher sheet, telling the parent
   what to do with the list below it — shown only when there's actually
   more than one child to switch between. */
.child-switch-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 var(--s5) var(--s3);
  padding: 9px 12px;
  border-radius: var(--r-md);
  background: var(--gold-pale);
  color: var(--navy);
  font-size: 12.5px;
  font-weight: var(--semibold);
}
.child-switch-hint svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--gold); }

.child-switch-row__check {
  width: 18px; height: 18px;
  color: var(--navy);
  flex-shrink: 0;
}
.child-switch-row__check svg { width: 100%; height: 100%; }

/* ── Currently-active child gets its own visibly separate section —
   a tinted, gold-bordered card with an explicit "Selected" pill, rather
   than blending into the same plain list as every other linked child
   with only a small checkmark to tell them apart. ── */
.child-switch-active-label,
.child-switch-others-label {
  font-size: 11px;
  font-weight: var(--bold);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-muted);
  margin: 0 0 6px;
}
.child-switch-active-label { margin-top: 2px; }
.child-switch-others-label { margin-top: var(--s2); }
.child-switch-row--active {
  background: var(--gold-pale);
  border: 1.5px solid var(--gold);
  border-radius: var(--r-md);
}
.child-switch-row__selected {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  background: var(--navy);
  color: var(--gold-light);
  font-size: 10.5px;
  font-weight: var(--bold);
  padding: 4px 9px 4px 7px;
  border-radius: var(--r-full);
}
.child-switch-row__selected svg { width: 12px; height: 12px; }

/* ── Report feed item ── */
.report-feed-item {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border-light);
  cursor: pointer;
  transition: background var(--fast);
}
.report-feed-item:last-child { border-bottom: none; }
.report-feed-item:hover { background: var(--bg); }
.report-feed-item.unread {
  border-left: 3px solid var(--red);
  padding-left: calc(var(--s4) - 3px);
}
.report-feed-item__icon {
  width: 38px; height: 38px;
  border-radius: 10px;
  background: var(--navy);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
}
.report-feed-item__icon img {
  width: 100%; height: 100%; object-fit: cover;
}
.report-feed-item__icon svg {
  width: 18px; height: 18px;
  stroke: white; stroke-width: 1.8;
}
.report-feed-item__content { flex: 1; min-width: 0; }
.report-feed-item__title {
  font-size: var(--text-sm);
  font-weight: var(--semibold);
  color: var(--text-primary);
}
.report-feed-item__preview {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.5;
}
.report-feed-item__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 5px;
}
.report-feed-item__time {
  font-size: 10px;
  color: var(--text-muted);
}
/* Whose report/post this is, shown inline next to the title — only
   used when more than one child is linked and/or the "All Children"
   view is active (see reports.js's reportFeedRowHTML), since without
   the old per-child tabs that context would otherwise be invisible
   until the parent actually opens the item. */
.report-feed-item__tag {
  font-weight: var(--medium);
  color: var(--navy);
  font-size: 10.5px;
  background: var(--gold-pale);
  padding: 1.5px 8px;
  border-radius: var(--r-full);
  margin-left: 5px;
  white-space: nowrap;
}

/* ── Child select — a centered, obviously-tappable dropdown control
   (photo + first name + class + chevron) used by Reports and Forum in
   place of a horizontal per-child tab row, which got unwieldy with
   3-4 kids. Deliberately styled like a real select control (pill
   shape, clean surface background, subtle border+shadow) rather than
   a segmented tab bar, so it reads as "tap to change" rather than
   "pick one of these fixed views". ── */
.child-select {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 300px;
  margin: 0 auto;
  padding: 9px 14px;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--r-full);
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: background var(--fast), border-color var(--fast);
}
.child-select:active { background: var(--bg); border-color: var(--gold); }
.child-select__photo {
  width: 32px; height: 32px;
  flex-shrink: 0;
}
.child-select__photo svg { width: 15px; height: 15px; stroke: var(--gold); }
.child-select__text {
  flex: 1;
  min-width: 0;
  text-align: left;
}
.child-select__name {
  font-size: 13.5px;
  font-weight: var(--bold);
  color: var(--navy);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.child-select__class {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.child-select__chev {
  flex-shrink: 0;
  color: var(--navy);
  opacity: 0.55;
}

/* ============================================================
   GODDY TABI — ADMIN PANEL STYLES
   APPEND THIS ENTIRE BLOCK to the bottom of css/dashboard.css
   DO NOT create a new CSS file — architecture rules.
   ============================================================ */

/* ── Page header ────────────────────────────────────────────── */
.admin-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  gap: 12px;
}
.admin-page-title {
  font-size: 20px;
  font-weight: 900;
  color: var(--navy-deep);
  margin: 0;
}
.admin-page-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* ── Filter bar ─────────────────────────────────────────────── */
.admin-filter-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  align-items: center;
}
.admin-search-wrap {
  flex: 1;
  position: relative;
  display: flex;
  align-items: center;
}
.admin-search-icon {
  position: absolute;
  left: 12px;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
  pointer-events: none;
  display: flex;
  align-items: center;
}
.admin-search-icon svg {
  width: 16px;
  height: 16px;
  stroke: var(--text-muted);
}
.admin-search-input {
  width: 100%;
  padding: 10px 12px 10px 36px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  font-size: 14px;
  color: var(--text-primary);
  background: var(--surface);
  font-family: var(--font);
  outline: none;
  transition: border-color 0.15s;
}
.admin-search-input:focus { border-color: var(--navy); }
.admin-search-input::placeholder { color: var(--text-muted); }
.admin-select {
  padding: 10px 12px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  font-size: 13px;
  color: var(--text-primary);
  background: var(--surface);
  font-family: var(--font);
  outline: none;
  cursor: pointer;
  transition: border-color 0.15s;
  flex-shrink: 0;
}
.admin-select:focus { border-color: var(--navy); }

/* ── Cards ──────────────────────────────────────────────────── */
.admin-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: var(--surface);
  border-radius: 14px;
  border: 1px solid var(--border-light);
  margin-bottom: 8px;
  transition: box-shadow 0.15s, background 0.15s;
}
.admin-card--interactive {
  cursor: pointer;
  user-select: none;
}
.admin-card--interactive:hover {
  background: var(--border-light);
  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.admin-card--interactive:active { background: var(--border); }

.admin-card--skeleton {
  pointer-events: none;
}
.admin-card--highlight {
  border-color: var(--gold);
  background: var(--gold-pale);
}
.admin-card__body { flex: 1; min-width: 0; }
.admin-card__name {
  font-size: 15px;
  font-weight: 700;
  color: var(--navy-deep);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.admin-card__meta {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 2px;
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.admin-card__sep { opacity: 0.4; }
.admin-card__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.admin-card__icon {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border-radius: 10px;
  flex-shrink: 0;
}
.admin-card__icon svg {
  width: 18px;
  height: 18px;
  stroke: var(--navy);
}
.chevron-icon {
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  color: var(--text-muted);
  flex-shrink: 0;
}
.chevron-icon svg {
  width: 16px;
  height: 16px;
  stroke: var(--text-muted);
  stroke-width: 2;
}

/* ── Skeleton animations ─────────────────────────────────────── */
.skel {
  background: linear-gradient(90deg, var(--border) 25%, var(--border-light) 50%, var(--border) 75%);
  background-size: 200% 100%;
  animation: skel-shimmer 1.4s infinite;
  border-radius: 6px;
}
@keyframes skel-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skel--avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  flex-shrink: 0;
}
.skel--line {
  height: 14px;
  width: 100%;
}

/* ── Avatar extras ──────────────────────────────────────────── */
.avatar--sm {
  width: 32px;
  height: 32px;
  font-size: 11px;
}
.avatar--xl {
  width: 72px;
  height: 72px;
  font-size: 24px;
}
.avatar--navy {
  background: var(--navy);
  color: white;
}

/* ── Profile hero ───────────────────────────────────────────── */
.admin-profile-hero {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px 0 8px;
  margin-bottom: 8px;
}
.admin-profile-name {
  font-size: 20px;
  font-weight: 900;
  color: var(--navy-deep);
  margin: 0 0 4px;
}
.admin-profile-meta {
  font-size: 13px;
  color: var(--text-muted);
}

/* ── Sections ───────────────────────────────────────────────── */
.admin-section {
  margin-bottom: 20px;
}
.admin-section-label,
.section-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 10px;
}

/* ── Info rows ──────────────────────────────────────────────── */
.admin-info-rows {
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: 14px;
  overflow: hidden;
}
.admin-info-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 16px;
  border-bottom: 1px solid var(--border-light);
  gap: 12px;
}
.admin-info-row:last-child { border-bottom: none; }
.admin-info-row__label {
  font-size: 13px;
  color: var(--text-muted);
  flex-shrink: 0;
}
.admin-info-row__val {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  text-align: right;
}

/* ── Info boxes ─────────────────────────────────────────────── */
.admin-info-box {
  padding: 12px 14px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.5;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 8px;
}
.admin-info-box svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 2px; }
.admin-info-box--blue  { background: var(--blue-bg);  color: var(--blue);  }
.admin-info-box--amber { background: var(--amber-bg); color: var(--amber); }
.admin-info-box--green { background: var(--green-bg); color: var(--green); }
.admin-info-box--red   { background: var(--red-bg);   color: var(--red);   }

/* ── Stats grid ─────────────────────────────────────────────── */
.admin-stats-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 20px;
}
@media (min-width: 768px) {
  .admin-stats-grid { grid-template-columns: repeat(4, 1fr); }
}
.admin-stat-card {
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: 16px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: box-shadow 0.15s;
}
.admin-stat-card__icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.admin-stat-card__icon svg {
  width: 18px;
  height: 18px;
  stroke-width: 1.8;
}
.admin-stat-card--blue   .admin-stat-card__icon { background: var(--blue-bg);  }
.admin-stat-card--blue   .admin-stat-card__icon svg { stroke: var(--blue);  }
.admin-stat-card--green  .admin-stat-card__icon { background: var(--green-bg); }
.admin-stat-card--green  .admin-stat-card__icon svg { stroke: var(--green); }
.admin-stat-card--amber  .admin-stat-card__icon { background: var(--amber-bg); }
.admin-stat-card--amber  .admin-stat-card__icon svg { stroke: var(--amber); }
.admin-stat-card--navy   .admin-stat-card__icon { background: rgba(27,45,94,0.1); }
.admin-stat-card--navy   .admin-stat-card__icon svg { stroke: var(--navy); }
.admin-stat-card__val {
  font-size: 28px;
  font-weight: 900;
  color: var(--navy-deep);
  line-height: 1;
}
.admin-stat-card__label {
  font-size: 12px;
  color: var(--text-muted);
  font-weight: 500;
}

/* ── Photo upload widget ─────────────────────────────────────── */
.admin-photo-upload {
  width: 88px;
  height: 88px;
  border-radius: 50%;
  border: 2px dashed var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
  margin: 0 auto;
  transition: border-color 0.15s;
  position: relative;
}
.admin-photo-upload:hover { border-color: var(--navy); }
.admin-photo-upload--wide {
  width: auto;
  height: auto;
  border-radius: 16px;
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 16px;
}
.admin-photo-preview-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}
.admin-photo-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: var(--text-muted);
  font-size: 11px;
}
.admin-photo-placeholder svg {
  width: 20px;
  height: 20px;
  stroke: var(--text-muted);
}

/* ── Empty states ───────────────────────────────────────────── */
.admin-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  text-align: center;
  gap: 8px;
}
.admin-empty__icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 4px;
}
.admin-empty__icon svg {
  width: 24px;
  height: 24px;
  stroke: var(--text-muted);
  opacity: 0.7;
}
.admin-empty__title {
  font-size: 16px;
  font-weight: 700;
  color: var(--navy-deep);
}
.admin-empty__sub {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 12px;
}
.admin-empty-small {
  font-size: 13px;
  color: var(--text-muted);
  padding: 16px;
  text-align: center;
  background: var(--bg);
  border-radius: 12px;
}

/* ── Required field marker ──────────────────────────────────── */
.req {
  color: var(--red);
  margin-left: 2px;
}

/* ── Topbar back button ─────────────────────────────────────── */
.topbar__back-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--navy);
  font-size: 15px;
  font-weight: 700;
  padding: 0;
  font-family: var(--font);
}
.topbar__back-btn svg {
  width: 18px;
  height: 18px;
  stroke: var(--navy);
  stroke-width: 2.2;
}

/* ── Detail view (push-on-top) ──────────────────────────────── */
.admin-detail-view {
  background: var(--bg);
  animation: slide-in-right 0.22s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-in-right {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

/* ── Tablet / desktop adjustments ──────────────────────────── */
@media (min-width: 768px) {
  .admin-card { padding: 16px 20px; }
  .admin-page-title { font-size: 22px; }

  /* Cards become table-like rows on desktop */
  .admin-filter-bar { margin-bottom: 16px; }
}

/* ── Claim link / invite modal ───────────────────────────── */
.claim-modal__who {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  background: var(--bg);
  border-radius: 14px;
}

.claim-modal__link-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.claim-modal__link-input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  font-size: 11px;
  font-family: monospace;
  color: var(--text-primary);
  background: var(--surface);
  outline: none;
  cursor: text;
}
.claim-modal__link-input:focus { border-color: var(--navy); }

.claim-modal__divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-muted);
  font-size: 12px;
}
.claim-modal__divider::before,
.claim-modal__divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ══════════════════════════════════════════════════════════════
   ROLL CALL (ATTENDANCE + PUNCTUALITY) — PHASE 4
   Scoped to #screen-roster via .roster-* classes.
   Redesign goals: roll call list is the hero of the screen; chrome
   above it (class/date/type controls) stays compact; every status
   icon is legible without relying on any other stylesheet.
   ══════════════════════════════════════════════════════════════ */

.roster-page {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 10px;
  padding-bottom: 4px;
}

/* ── Compact top zone ─────────────────────────────────────── */
.roster-topzone {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.roster-title {
  font-size: 19px;
  font-weight: 800;
  color: var(--navy-deep);
  margin: 0;
  letter-spacing: -0.2px;
}

.roster-empty-icon {
  width: 48px;
  height: 48px;
  color: var(--navy);
  opacity: 0.3;
}
.roster-empty-icon svg { width: 100%; height: 100%; }

/* Class pills */
.roster-classbar {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
}
.roster-classbar::-webkit-scrollbar { display: none; }
.roster-class-pill {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px 8px;
  border-radius: 99px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.15s;
}
.roster-class-pill__icon,
.roster-class-pill svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}
.roster-class-pill.active {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}
/* Subject suffix on a class pill (e.g. "Class 3 French") — a subject
   teacher's own class pill needs to read as distinct from a homeroom
   teacher's plain class name; see reports.js's class-pill bar. */
.roster-class-pill__tag {
  opacity: 0.7;
  font-weight: 600;
}
.roster-single-class {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-deep);
}
.roster-single-class svg { width: 16px; height: 16px; color: var(--gold); }

/* Combined Roll Call — shown instead of the class-pill switcher when a
   teacher is linked to more than one class (see js/modules/attendance.js
   COMBINED-CLASS BLOCK). Wraps gracefully since joined class names can
   run long ("Pre-Nursery + Nursery 1 + Nursery 2"). */
.roster-single-class--combined { flex-wrap: wrap; row-gap: 4px; }
.roster-combined-tag {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--navy);
  background: var(--gold-light, #FEF3C7);
  border-radius: 99px;
  padding: 2px 8px;
  margin-left: 2px;
}
.roster-row__meta--class {
  font-size: 11.5px;
  color: var(--text-muted);
  font-weight: 600;
}

/* Type segment + date bar share a row on wider screens */
.roster-controlrow {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.roster-segbar {
  display: flex;
  background: var(--bg);
  border-radius: 11px;
  padding: 3px;
  gap: 2px;
  flex: 1 1 180px;
  min-width: 0;
}
.roster-seg {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 7px 8px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
  cursor: pointer;
  border: none;
  background: transparent;
  transition: all 0.15s;
  white-space: nowrap;
}
.roster-seg svg { width: 14px; height: 14px; flex-shrink: 0; }
.roster-seg.active {
  background: var(--surface);
  color: var(--navy);
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.roster-datebar {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: 11px;
  padding: 3px;
  flex: 1 1 150px;
}
.roster-datebar__label {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy-deep);
  cursor: pointer;
  padding: 5px 4px;
}
.roster-datebar__label svg { width: 14px; height: 14px; color: var(--text-muted); flex-shrink: 0; }
.roster-datebar input[type="date"] {
  border: none;
  background: transparent;
  width: 1px;
  height: 1px;
  opacity: 0;
  position: absolute;
  pointer-events: none;
}
.roster-datebar__btn {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: none;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-secondary);
  flex-shrink: 0;
}
.roster-datebar__btn:hover { background: var(--bg); }
.roster-datebar__btn:disabled { opacity: 0.3; cursor: default; }
.roster-datebar__btn svg { width: 15px; height: 15px; }

/* Inline notes — small, not big alarming boxes */
.roster-note {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 9px;
  background: var(--amber-bg);
  color: var(--amber);
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.3;
}
.roster-note svg { width: 14px; height: 14px; flex-shrink: 0; }
.roster-note--info {
  background: var(--blue-bg);
  color: var(--blue);
  margin-top: 6px;
}

/* ── Roll-call list ────────────────────────────────────────── */
.roster-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}
.roster-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-bottom: 4px;
}

.roster-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  min-height: 60px;
}
.roster-row--tappable {
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
  -webkit-tap-highlight-color: transparent;
}
.roster-row--tappable:active { background: var(--bg); }
.roster-row--disabled {
  opacity: 0.55;
}
.roster-row__body { flex: 1; min-width: 0; }
.roster-row__name {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--navy-deep);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.roster-row__meta {
  font-size: 11.5px;
  color: var(--text-muted);
  margin-top: 1px;
}

/* Status pill — the single tap target that cycles a student's status */
.roster-status-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 8px 10px;
  border-radius: 11px;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  background: var(--bg);
  color: var(--text-muted);
  border: 1.5px solid transparent;
}
.roster-status-pill svg { width: 16px; height: 16px; flex-shrink: 0; }
.roster-status-pill--lg {
  min-width: 118px;
  justify-content: center;
  padding: 10px 12px;
}
.roster-status-pill--unmarked {
  background: var(--bg);
  color: var(--text-muted);
  border-color: var(--border);
}
.roster-status-pill--muted {
  background: var(--bg);
  color: var(--text-muted);
}
.roster-status-pill--green { background: var(--green-bg); color: var(--green); }
.roster-status-pill--red   { background: var(--red-bg);   color: var(--red); }
.roster-status-pill--blue  { background: var(--blue-bg);  color: var(--blue); }
.roster-status-pill--amber { background: var(--amber-bg); color: var(--amber); }

/* ── Mark-all CTA — the primary, most-used action ─────────── */
.roster-markall-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 14px 16px;
  border-radius: 14px;
  border: none;
  font-size: 14.5px;
  font-weight: 800;
  color: #fff;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0,0,0,0.12);
  transition: transform 0.1s, box-shadow 0.1s;
}
.roster-markall-btn:active { transform: scale(0.98); }
.roster-markall-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
/* Was green/blue by type — didn't read as an intentional brand
   choice. One fixed navy-to-gold treatment for both, since this is
   an action button, not a status indicator (those still use the
   green/blue POS_COLOR mapping elsewhere, e.g. the "done" stats). */
.roster-markall-btn { background: linear-gradient(135deg, var(--navy), var(--navy-deep)); }

.roster-quickbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 2px;
}
.roster-progress {
  font-size: 12px;
  color: var(--text-muted);
  font-weight: 700;
}
.roster-hint {
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
}

/* Floating — NOT stuck to the literal page end, sits above the
   mobile bottom nav with breathing room on all sides.
   This padding must clear THREE stacked things above the true page
   bottom: the mobile bottom nav (--bottom-nav-h), the gap above it
   the submit bar floats at (14px), and the submit bar's own height
   (~74px: 12px+12px padding plus a ~46-50px button/text row). The
   old fixed 84px only cleared part of that stack, so the last
   student row in a long class list rendered underneath the
   floating submit bar and was unreachable by scrolling. Padding
   now mirrors the bar's own `bottom` offset (below) plus its
   height and a safety margin, so it can never fall short again
   even if --bottom-nav-h or the bar's content height changes. */
.roster-page {
  padding-bottom: calc(var(--bottom-nav-h, 62px) + 14px + 74px + 24px + env(safe-area-inset-bottom));
}
.roster-submitbar {
  position: fixed;
  left: 14px;
  right: 14px;
  bottom: calc(var(--bottom-nav-h, 62px) + 14px + env(safe-area-inset-bottom));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  box-shadow: 0 10px 30px rgba(15, 30, 66, 0.16);
  z-index: 20;
}
@media (min-width: 768px) {
  .roster-submitbar { left: auto; right: 24px; bottom: 24px; width: 360px; }
}

/* ── Done state — full hero card, register hidden ─────────── */
.roster-done-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 28px 20px 22px;
  border-radius: 20px;
  background: var(--surface);
  border: 1.5px solid var(--border-light);
}
.roster-done-hero__icon {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 2px;
  animation: roster-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.roster-done-hero__icon svg { width: 40px; height: 40px; }
.roster-done-hero__icon--green { background: var(--green-bg); color: var(--green); }
.roster-done-hero__icon--blue  { background: var(--blue-bg);  color: var(--blue); }
@keyframes roster-pop {
  from { transform: scale(0.6); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}
.roster-done-hero__title {
  font-size: 19px;
  font-weight: 800;
  color: var(--navy-deep);
}
.roster-done-hero__sub {
  font-size: 13px;
  color: var(--text-muted);
  max-width: 260px;
  line-height: 1.4;
}

/* Only shown when a class has more than one teacher and someone OTHER
   than the person currently looking marked today's roster — tells a
   second teacher whose work this is instead of an unexplained "done". */
.roster-done-hero__marked-by {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--surface-alt, #F3F5FA);
  color: var(--text-secondary, #5A6485);
  font-size: 12px;
}
.roster-done-hero__marked-by svg { width: 14px; height: 14px; flex-shrink: 0; }

/* Clickable recap stats — tap to filter the (normally hidden) list */
.roster-done-stats {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 4px;
}
.roster-done-stat {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 13px;
  border-radius: 11px;
  font-size: 12.5px;
  font-weight: 700;
  border: 1.5px solid transparent;
  cursor: pointer;
  background: var(--bg);
  color: var(--text-secondary);
  transition: all 0.12s;
}
.roster-done-stat:disabled { cursor: default; }
.roster-done-stat svg { width: 14px; height: 14px; }
.roster-done-stat--green  { background: var(--green-bg); color: var(--green); }
.roster-done-stat--red    { background: var(--red-bg);   color: var(--red); }
.roster-done-stat--blue   { background: var(--blue-bg);  color: var(--blue); }
.roster-done-stat--amber  { background: var(--amber-bg); color: var(--amber); }
.roster-done-stat--muted  { background: var(--bg);       color: var(--text-muted); }
.roster-done-stat.active  { border-color: currentColor; box-shadow: 0 0 0 3px rgba(0,0,0,0.05); }

/* Weekly progress — Tue–Fri school-day tracker for this teacher */
.roster-week-progress {
  width: 100%;
  max-width: 300px;
  margin-top: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--border-light);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.roster-week-progress__label {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
}
.roster-week-progress__dots {
  display: flex;
  gap: 10px;
}
.roster-week-dot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.roster-week-dot__circle {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--border);
  color: var(--text-muted);
}
.roster-week-dot__circle svg { width: 13px; height: 13px; }
.roster-week-dot__label {
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
}
.roster-week-dot--done .roster-week-dot__circle {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}
.roster-week-dot--missed .roster-week-dot__circle {
  background: var(--red-bg);
  border-color: var(--red);
  color: var(--red);
}
.roster-week-dot--pending .roster-week-dot__circle {
  border-color: var(--navy);
  border-style: dashed;
  color: var(--navy);
}
.roster-week-dot--upcoming .roster-week-dot__circle {
  opacity: 0.4;
}

.roster-review-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 6px;
  padding: 10px 18px;
  border-radius: 12px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  color: var(--navy);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.roster-review-btn svg { width: 15px; height: 15px; }
.roster-review-btn:hover { background: var(--bg); }
/* The class-report nudge shown right after a teacher finishes every
   personal report — used to be visually identical to the plain "View
   Reports" button next to it (same size/weight), easy to miss as just
   another secondary option. This is the obvious next action, so it
   gets full-width, filled, and its own line above the rest. */
.roster-review-btn--primary {
  width: 100%;
  justify-content: center;
  background: var(--navy);
  border-color: var(--navy);
  color: var(--gold-light);
  font-size: 14px;
  padding: 13px 18px;
  box-shadow: var(--shadow-sm);
}
.roster-review-btn--primary:hover { background: var(--navy-deep); }
.roster-review-btn--primary svg { width: 17px; height: 17px; }

/* ── Self-guided flow: "today's tasks" checklist + next-step nudge ───
   Ties Attendance → Punctuality → (Fridays only) Reports into one
   guided routine instead of three screens a teacher has to remember
   to visit on their own. Only shown on the "all done for today" hero. */
.roster-checklist {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 2px;
}
.roster-checklist__step {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  background: var(--bg);
  color: var(--text-muted);
  white-space: nowrap;
}
.roster-checklist__dot {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid currentColor;
  flex-shrink: 0;
}
.roster-checklist__dot svg { width: 9px; height: 9px; }
.roster-checklist__step--done { background: var(--green-bg); color: var(--green); }
.roster-checklist__step--done .roster-checklist__dot { background: var(--green); border-color: var(--green); color: #fff; }
.roster-checklist__step--upcoming { opacity: 0.55; }
.roster-checklist__arrow { display: flex; color: var(--text-muted); opacity: 0.45; flex-shrink: 0; }
.roster-checklist__arrow svg { width: 12px; height: 12px; }

.roster-nextstep {
  width: 100%;
  margin-top: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--border-light);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.roster-nextstep__text {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.4;
  max-width: 280px;
}
.roster-nextstep__text strong { color: var(--navy-deep); }
.roster-nextstep__done {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--green);
}
.roster-nextstep__done svg { width: 15px; height: 15px; }

/* ── Tablet / desktop ──────────────────────────────────────── */
@media (min-width: 768px) {
  .roster-controlrow { flex-wrap: nowrap; }
  .roster-segbar { flex: 0 0 240px; }
  .roster-datebar { flex: 0 0 220px; }
  .roster-markall-btn { max-width: 420px; }
  /* Desktop/tablet: bar becomes a 360px floating card pinned bottom-right
     (24px inset) instead of a full-width bar above the bottom nav — no
     --bottom-nav-h to clear here, but it still needs its own height plus
     the same 24px inset cleared beneath the list, or the last row(s) can
     land underneath it exactly like the mobile bug above. */
  .roster-page { padding-bottom: calc(74px + 24px + 24px); }
}
/* ═══════════════════════════════════════════════════════════
   MESSAGING — PHASE 6
   Conversation list reuses .roster-row/.roster-list (attendance);
   thread view is bespoke (bubbles + composer).

   The composer is position:fixed, anchored directly to the
   viewport — the SAME technique .bottom-nav already uses in this
   app (layout.css). An earlier version tried a "sticky inside the
   scrollable body" approach, which depends on that body actually
   being the element that scrolls; because the app shell's ancestor
   chain uses min-height rather than a hard height cap, the whole
   page ends up scrolling instead in practice, and sticky degrades
   to normal in-flow positioning (which is why it drifted). Fixed
   positioning has no such dependency — it's anchored to the
   viewport unconditionally.
   ═══════════════════════════════════════════════════════════ */

.msg-listbar {
  padding: 4px 2px 14px;
}
.msg-listbar__title {
  font-size: 20px;
  font-weight: 800;
  color: var(--navy-deep);
}

/* Conversation row — extends .roster-row */
.msg-row__subtitle {
  font-weight: 600;
  color: var(--text-muted);
  font-size: 0.92em;
}
.msg-row__preview {
  max-width: 220px;
  display: flex;
  align-items: center;
  gap: 4px;
}
.msg-row__preview svg { width: 13px; height: 13px; flex-shrink: 0; opacity: 0.7; }
.msg-row__start { color: var(--text-muted); font-style: italic; }
.msg-row__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  flex-shrink: 0;
}
.msg-row__time {
  font-size: 11px;
  color: var(--text-muted);
}
.msg-row__badge {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--navy);
  color: white;
  font-size: 11px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Thread view ──────────────────────────────────────────── */
.msg-thread {
  display: flex;
  flex-direction: column;
  flex: 1;
  background: var(--bg);
}
.msg-thread__header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
  /* sticky (not just relative) so the "who you're chatting with" bar
     stays pinned as the thread scrolls. This whole layout never actually
     gives .msg-thread__body a bounded height (.app-shell/.main-wrap/
     #main-content/.screen all use min-height, never height, all the way
     up to <body>), so .msg-thread__body's own overflow-y:auto never
     engages — the real scrolling box is the document/viewport itself,
     the exact same scroll context .topbar (also position:sticky;top:0)
     already relies on. That's the bug this top offset fixes: with
     top:0, this header raced .topbar for the same y=0 slot in that
     shared scroll context and lost — .topbar sits at a higher z-index
     (400 vs this element's 2) and is always present (it's part of the
     persistent app shell, not this screen), so once scrolled, this
     header was landing exactly underneath it, fully hidden rather than
     "not sticking". Offsetting by the topbar's own fixed height
     (--header-h, 60px, constant across every breakpoint — only the
     topbar's *inner content* toggles by width, never the bar itself)
     makes it stick directly below the topbar instead of behind it. */
  position: sticky;
  top: var(--header-h);
  z-index: 2;
}
.msg-thread__who { min-width: 0; flex: 1; }
.msg-thread__name {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--navy-deep);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.msg-thread__role {
  font-size: 11.5px;
  color: var(--text-muted);
}

/* .msg-thread__body doubles up as .page-content for its scroll
   behaviour, with extra bottom padding on top of the standard 80px
   bottom-nav clearance — enough room for the fixed composer (up to
   ~150px expanded with an attachment chip showing) to sit on top
   without ever covering the last message. A subtle, low-contrast
   dot grid + faint diagonal gradient gives it a bit of texture
   without reading as a social app — professional first, a little
   warmth on top. */
.msg-thread__body.page-content {
  padding-bottom: calc(80px + 160px + env(safe-area-inset-bottom));
  background-color: var(--bg);
  background-image:
    radial-gradient(rgba(27,45,94,0.05) 1px, transparent 1px),
    linear-gradient(160deg, rgba(212,160,23,0.035) 0%, transparent 40%);
  background-size: 18px 18px, 100% 100%;
  background-position: 0 0, 0 0;
}
.msg-messages {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
  z-index: 1;
}
.msg-thread__loading { padding-top: 4px; }
.msg-thread__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
}

.msg-day-divider {
  display: flex;
  justify-content: center;
  margin: 14px 0 10px;
}
.msg-day-divider span {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  background: var(--surface);
  padding: 4px 12px;
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(15,30,66,0.06);
}

.msg-bubble-row {
  display: flex;
  justify-content: flex-start;
  margin-bottom: 6px;
}
.msg-bubble-row--mine { justify-content: flex-end; }
.msg-bubble {
  max-width: 78%;
  padding: 9px 12px 7px;
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(15,30,66,0.06);
}
.msg-bubble--theirs {
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-bottom-left-radius: 4px;
}
.msg-bubble--mine {
  background: var(--navy);
  border-bottom-right-radius: 4px;
}
.msg-bubble__text {
  font-size: 14px;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--navy-deep);
}
.msg-bubble--mine .msg-bubble__text { color: white; }
/* .msg-bubble__text used to hold plain escaped text, so setting color:
   white on it (above) was enough on its own. It now renders through
   GT.utils.formatRichMessage(), whose .rich-msg__* elements each set
   their OWN text color directly (tuned for the light chat/report
   background) — a color set directly on an element always wins over an
   inherited one, no matter the parent's specificity, so every message
   YOU sent was rendering dark, barely-readable text on the dark navy
   "mine" bubble. These re-assert white/light text specifically inside
   a "mine" bubble, same idea as the report-view override above. */
.msg-bubble--mine .rich-msg__heading,
.msg-bubble--mine .rich-msg__p,
.msg-bubble--mine .rich-msg__list > li,
.msg-bubble--mine .rich-msg__sublist li { color: white; }
.msg-bubble--mine .rich-msg__list strong { color: white; }
.msg-bubble--mine .rich-msg__list--pin > li { background: rgba(255,255,255,0.14); }
.msg-bubble__meta {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 3px;
}
.msg-bubble__time {
  font-size: 10px;
  color: var(--text-muted);
}
.msg-bubble--mine .msg-bubble__time { color: rgba(255,255,255,0.65); }
.msg-bubble__tick { display: flex; color: rgba(255,255,255,0.65); }
.msg-bubble__tick svg { width: 15px; height: 11px; }
.msg-bubble__tick--read { color: #6FD3FF; } /* WhatsApp-style blue read tick */

/* ── Attachments in bubbles ───────────────────────────────── */
.msg-attach-img {
  width: 180px;
  max-width: 100%;
  aspect-ratio: 4/3;
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
  margin-bottom: 4px;
  background: var(--bg);
}
.msg-attach-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

.msg-attach-doc {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border-radius: 12px;
  background: rgba(255,255,255,0.12);
  margin-bottom: 4px;
  min-width: 200px;
  cursor: pointer;
}
.msg-bubble--theirs .msg-attach-doc { background: var(--bg); }
.msg-attach-doc__icon {
  position: relative;
  width: 36px; height: 36px;
  border-radius: 8px;
  background: var(--gold);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.msg-attach-doc__icon svg { width: 18px; height: 18px; stroke: white; }
.msg-attach-doc__ext {
  position: absolute; bottom: -4px; left: 50%; transform: translateX(-50%);
  font-size: 8px; font-weight: 800; color: var(--navy-deep);
  background: white; border-radius: 4px; padding: 1px 3px;
  line-height: 1.2;
}
.msg-attach-doc__info { flex: 1; min-width: 0; }
.msg-attach-doc__name {
  display: block;
  font-size: 12.5px; font-weight: 700;
  color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msg-attach-doc__size {
  display: block;
  font-size: 10.5px;
  opacity: 0.7;
}
.msg-attach-doc__dl { flex-shrink: 0; opacity: 0.7; }
.msg-attach-doc__dl svg { width: 15px; height: 15px; }

/* ── Composer — fixed to the viewport (see note at top of block) ── */
.msg-composer-wrap {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(80px + env(safe-area-inset-bottom));
  z-index: 550; /* above .bottom-nav's 500 */
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 10px;
}
@media (min-width: 768px) {
  .msg-composer-wrap { left: calc(var(--sidebar-w-sm) + var(--s4) * 2); bottom: 14px; }
}
@media (min-width: 1100px) {
  .msg-composer-wrap { left: calc(var(--sidebar-w) + var(--s4) * 2); }
}

.msg-attach-preview {
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  box-shadow: 0 4px 16px rgba(15,30,66,0.08);
}
.msg-attach-preview__thumb {
  width: 34px; height: 34px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
}
.msg-attach-preview__doc {
  width: 34px; height: 34px;
  border-radius: 8px;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: var(--navy);
}
.msg-attach-preview__doc svg { width: 17px; height: 17px; }
.msg-attach-preview__name {
  flex: 1; min-width: 0;
  font-size: 12.5px;
  color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msg-attach-preview__remove {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: var(--text-muted);
}
.msg-attach-preview__remove svg { width: 13px; height: 13px; }

.msg-composer {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 8px;
  border-radius: 24px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  box-shadow: 0 4px 16px rgba(15,30,66,0.1);
}
.msg-composer__attach {
  width: 36px; height: 36px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: var(--text-muted);
  transition: background 0.15s, color 0.15s;
}
.msg-composer__attach:hover { background: var(--bg); color: var(--navy); }
.msg-composer__attach svg { width: 19px; height: 19px; }
.msg-composer__input {
  flex: 1;
  resize: none;
  max-height: 120px;
  padding: 8px 4px;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text-primary);
}
.msg-composer__input:focus { outline: none; }
.msg-composer__send {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--navy);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: opacity 0.15s, transform 0.1s;
}
.msg-composer__send svg { width: 18px; height: 18px; }
.msg-composer__send:disabled { opacity: 0.4; cursor: default; }
.msg-composer__send:not(:disabled):active { transform: scale(0.9); }

/* ── Lightbox — minimal full-screen image preview ────────────── */
.msg-lightbox {
  position: fixed;
  inset: 0;
  z-index: 6000;
  background: rgba(15,30,66,0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.msg-lightbox.open { opacity: 1; }
.msg-lightbox img {
  max-width: 92vw;
  max-height: 80vh;
  border-radius: 8px;
  object-fit: contain;
}
.msg-lightbox__close {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top));
  right: 16px;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(255,255,255,0.15);
  color: white;
  display: flex; align-items: center; justify-content: center;
}
.msg-lightbox__close svg { width: 18px; height: 18px; }
.msg-lightbox__download {
  position: absolute;
  bottom: calc(24px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  padding: 10px 18px;
  border-radius: 999px;
  background: white;
  color: var(--navy-deep);
  font-size: 13px;
  font-weight: 700;
}
.msg-lightbox__download svg { width: 16px; height: 16px; }

@media (min-width: 640px) {
  .msg-bubble { max-width: 60%; }
}

/* ═══════════════════════════════════════════════════════════
   FORUM
   Class-scoped feed: Weekly Updates (from class_reports, created
   in the Reports tab) + Announcements (quick tap-based posts,
   created right here). No photos/reactions — Spotlight already
   owns that; this is informational, closer to a notice board.
   ═══════════════════════════════════════════════════════════ */

/* Faint decorative background — a few soft, low-opacity dot clusters
   and a subtle diagonal texture behind the feed, purely so the Forum
   page reads a little less like a bare white list. Kept deliberately
   subtle: fixed attachment so it doesn't scroll/repeat awkwardly with
   the content, and never so strong it competes with card text. */
.forum-page-bg {
  background-color: var(--bg-page, #F7F8FB);
  background-image:
    radial-gradient(circle at 8% 6%, rgba(201,151,31,0.07) 0, transparent 40%),
    radial-gradient(circle at 94% 14%, rgba(27,45,94,0.05) 0, transparent 36%),
    radial-gradient(circle at 12% 92%, rgba(27,45,94,0.05) 0, transparent 34%),
    radial-gradient(circle at 96% 88%, rgba(201,151,31,0.06) 0, transparent 32%);
  background-repeat: no-repeat;
  background-attachment: local;
}

.forum-header {
  display: flex;
  align-items: center;
  padding: 4px 2px 14px;
}
.forum-header__title {
  display: flex; align-items: center; gap: 8px;
  font-size: 20px;
  font-weight: 800;
  color: var(--navy-deep);
}
.forum-header__title svg { width: 20px; height: 20px; stroke: var(--gold); }

/* ── Class/child switcher — reuses the same pill visual language
   as .roster-class-pill (attendance/reports) for consistency ── */
.forum-switcher {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 14px;
  scrollbar-width: none;
}
.forum-switcher::-webkit-scrollbar { display: none; }
.forum-pill {
  flex-shrink: 0;
  padding: 7px 14px;
  border-radius: var(--r-full);
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-muted);
}
.forum-pill.active {
  background: var(--navy);
  border-color: var(--navy);
  color: white;
}
.forum-class-select-wrap { flex-shrink: 0; }
.forum-class-select {
  padding: 7px 14px;
  border-radius: var(--r-full);
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy-deep);
}

/* ── Feed cards ───────────────────────────────────────────── */
.forum-card {
  display: flex;
  gap: 10px;
  padding: 14px;
  margin-bottom: 10px;
  border-radius: 16px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  box-shadow: 0 1px 3px rgba(15,30,66,0.05);
}
/* "New" — posted since this device last opened this class's forum.
   Reads as a clear, full-width row treatment (bold left border + tinted
   background the whole card, same red/dot vocabulary as the bell badge
   and .report-feed-item's unread state elsewhere in the app) rather than
   a subtle, easy-to-miss color cue — gold alone doesn't work here since
   this app already uses gold decoratively everywhere, so a gold-tinted
   card didn't read as "unread" at a glance. Read cards stay the plain,
   calm default .forum-card look by comparison. Clears itself
   automatically next time the feed loads (see forum.js: markSeenNow). */
.forum-card--new {
  border: 1.5px solid var(--red-bg);
  border-left: 4px solid var(--red);
  background: linear-gradient(to right, var(--red-bg), var(--surface) 60%);
  box-shadow: 0 1px 4px rgba(220,38,38,0.08);
}
.forum-card__new-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 9.5px;
  font-weight: 800;
  color: var(--red);
  background: var(--surface);
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  box-shadow: 0 0 0 1px rgba(220,38,38,0.25) inset;
}
.forum-card__new-badge::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--red);
  flex-shrink: 0;
}
.forum-card--skeleton { align-items: center; }
.forum-card__body { flex: 1; min-width: 0; }
.forum-card__top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.forum-card__name { font-size: 13.5px; font-weight: 700; color: var(--navy-deep); }
.forum-card__kind {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 999px;
}
.forum-card__kind--update { background: rgba(27,45,94,0.09); color: var(--navy); }
.forum-card__kind--announce { background: rgba(212,160,23,0.15); color: #96780E; }
/* Which of the parent's children's classes a post is for — shown once
   more than one child (so possibly more than one class) is linked; see
   forum.js's postCardHTML `classLabel` param. */
.forum-card__class-tag {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--navy);
  background: rgba(27,45,94,0.07);
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.forum-card__heading {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-deep);
  margin-bottom: 6px;
}
.forum-card__heading strong { color: inherit; }
.forum-card__prose {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-primary);
}
.forum-card__prose strong { color: var(--navy-deep); }
.forum-card__prose--muted {
  color: var(--text-muted);
  margin-top: 8px;
}
.forum-card__pinlist {
  list-style: none;
  margin: 8px 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.forum-card__pinlist li {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-primary);
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(212,160,23,0.06);
}
.forum-card__pinlist li strong { color: var(--navy-deep); }

/* ── Rich message formatting — shared by report readbacks and forum
   cards (see GT.utils.formatRichMessage in js/app.js). Headings, real
   numbered lists with nested sub-topics, and a pinned bullet list, so a
   structured message (subjects covered, an announcement) scans in
   seconds instead of reading like one flat paragraph. ─────────────── */
.rich-msg__heading {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-deep);
  margin: 14px 0 6px;
  letter-spacing: 0.2px;
}
.rich-msg__heading:first-child { margin-top: 0; }
.rich-msg__p {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-primary);
  margin: 0 0 8px;
}
.rich-msg__p:last-child { margin-bottom: 0; }
.rich-msg__list {
  margin: 4px 0 10px;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rich-msg__list > li {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-primary);
}
.rich-msg__list strong { color: var(--navy-deep); }
.rich-msg__sublist {
  list-style: disc;
  margin: 4px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rich-msg__sublist li { font-size: 13px; color: var(--text-muted); }
.rich-msg__list--pin {
  list-style: none;
  padding-left: 0;
}
.rich-msg__list--pin > li {
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(212,160,23,0.08);
  position: relative;
  padding-left: 26px;
}
.rich-msg__list--pin > li::before {
  content: '📌';
  position: absolute;
  left: 8px;
  top: 7px;
  font-size: 11px;
}
.forum-card__text {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-primary);
  white-space: pre-wrap;
}
.forum-card__time {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 6px;
}

/* ── FAB — same fixed-and-clears-the-nav convention as Spotlight's ── */
.forum-fab {
  position: fixed;
  right: 18px;
  bottom: calc(90px + env(safe-area-inset-bottom));
  width: 56px; height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--navy);
  color: white;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-md), 0 0 0 1px rgba(255,255,255,0.08);
  z-index: 12;
}
.forum-fab svg { width: 24px; height: 24px; }
@media (min-width: 768px) {
  .forum-fab { bottom: 24px; left: calc(var(--sidebar-w-sm) + var(--s4) * 2 + 18px); right: auto; }
}
@media (min-width: 1100px) {
  .forum-fab { left: calc(var(--sidebar-w) + var(--s4) * 2 + 18px); }
}

/* ── Announcement composer (inside the shared .sheet component) ──
   Two steps: a template grid, then a single editable message field.
   No chips/preview anymore — picking a template IS the preview,
   since it drops straight into the editable field. ─────────────── */
.forum-composer__scope {
  font-size: 12px;
  font-weight: 700;
  color: var(--navy);
  background: rgba(27,45,94,0.06);
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  margin-bottom: 14px;
}
.forum-composer__label {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-muted);
  margin-bottom: 10px;
}
.forum-composer__label svg { width: 14px; height: 14px; }

/* Step 1 — template grid */
.forum-template-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 14px;
}
.forum-template-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 8px;
  border-radius: 14px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy-deep);
  text-align: center;
  line-height: 1.3;
}
.forum-template-btn:active { background: var(--bg); transform: scale(0.98); }
.forum-template-btn__emoji { font-size: 26px; }
.forum-write-own-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px;
  border-radius: 14px;
  border: 1.5px dashed var(--border);
  background: transparent;
  font-size: 13px;
  font-weight: 700;
  color: var(--navy);
}
.forum-write-own-btn svg { width: 16px; height: 16px; }

/* Step 2 — editor */
.forum-back-link {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy);
  background: none;
  border: none;
  padding: 0;
  margin-bottom: 14px;
}
.forum-back-link svg { width: 15px; height: 15px; }
.forum-editor {
  width: 100%;
  padding: 14px;
  border-radius: 14px;
  border: 1.5px solid var(--border);
  background: var(--bg);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-primary);
  resize: vertical;
  min-height: 180px;
}
.forum-editor:focus { border-color: var(--navy); background: white; outline: none; }

.forum-composer__actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
  padding-bottom: 8px;
}
.forum-send-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 12px;
  border-radius: 12px;
  border: none;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}
.forum-send-btn svg { width: 17px; height: 17px; }
.forum-send-btn img { width: 18px; height: 18px; border-radius: 5px; }
.forum-send-btn--app { background: var(--navy); color: white; }
.forum-send-btn--wa { background: #25D366; color: white; }
.forum-send-btn:disabled { opacity: 0.45; cursor: default; }
/* Not native :disabled — stays tappable so a tap can explain why
   (parent hasn't claimed/installed the app yet). See pr-send-app. */
.forum-send-btn--claim-locked { opacity: 0.45; }

/* ═══════════════════════════════════════════════════════════
   SOCIAL — thin Forum/Inbox wrapper for admin & teacher
   (parents keep a plain standalone Forum tab, no wrapper needed)
   ═══════════════════════════════════════════════════════════ */
.social-shell {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.social-tabbar {
  display: flex;
  gap: 6px;
  padding: 10px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
  /* Sticky by default, plus a hide/show transform layered on top
     during scroll — same mechanism as Spotlight's own header
     (spotlight.css: .spotlight-navwrap), reused here for consistency. */
  position: sticky;
  top: 0;
  z-index: 8;
  transform: translateY(0);
  transition: transform 0.25s ease;
}
.social-tabbar--hidden { transform: translateY(-100%); }
.social-tab {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px;
  border-radius: 10px;
  border: none;
  background: transparent;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-muted);
}
.social-tab svg { width: 16px; height: 16px; }
.social-tab.active {
  background: var(--bg);
  color: var(--navy);
}
.social-tab__badge {
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--red);
  color: white;
  font-size: 10.5px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}
.social-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ═══════════════════════════════════════════════════════════
   PERSONAL REPORT — TAP COMPOSER
   Two steps inside the existing .sheet component: tap selections,
   then one editable message (reuses .forum-editor/.forum-send-btn/
   .forum-composer__actions from the Forum block — same visual
   language, one less thing to maintain twice).
   ═══════════════════════════════════════════════════════════ */
.pr-step {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-bottom: 16px;
}
.pr-blocked {
  text-align: center;
  padding: 40px 20px;
}
.pr-unclaimed-tag {
  font-size: 11px;
  color: #96780E;
  font-weight: 600;
}
.pr-unclaimed-notice {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(212,160,23,0.1);
  border: 1px solid rgba(212,160,23,0.3);
  font-size: 12.5px;
  color: #6B5610;
  margin-bottom: 14px;
  line-height: 1.4;
}

.pr-auto-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 16px;
}
.pr-auto-card {
  padding: 12px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border-light);
}
.pr-auto-card__label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.pr-auto-card__val {
  font-size: 15px;
  font-weight: 800;
  color: var(--navy-deep);
}

.pr-field-label {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-muted);
  margin: 14px 0 8px;
}
.pr-quant-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.pr-quant-btn {
  flex: 1;
  min-width: 70px;
  padding: 10px 6px;
  border-radius: 10px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-primary);
  text-align: center;
}
.pr-quant-btn.active {
  background: var(--navy);
  border-color: var(--navy);
  color: white;
}

.pr-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.pr-chip {
  padding: 7px 13px;
  border-radius: var(--r-full);
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-primary);
}
.pr-chip.active {
  background: var(--gold);
  border-color: var(--gold);
  color: white;
  font-weight: 700;
}
.pr-empty-note { font-size: 12.5px; color: var(--text-muted); }

.pr-draft-link {
  display: block;
  margin: 12px auto 0;
  background: none;
  border: none;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: underline;
}

/* ═══════════════════════════════════════════════════════════
   ROLL CALL NAV REDESIGN
   Icon-only type tabs (reuses .social-tab/.social-tabbar directly —
   "feel like Social") + a compact day-badge replacing the old
   prev/next date bar. The popover it opens reuses the exact same
   .roster-week-progress/.roster-week-dot widget already built for
   the "all done" hero card, just made tappable and shown compact.
   ═══════════════════════════════════════════════════════════ */
.roster-controlrow--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}

.roster-icon-tabbar {
  position: static; /* .social-tabbar defaults to sticky — not wanted nested in this row */
  width: 100%;
}
/* Labeled variant — text + icon, spaced out properly rather than
   cramped icon-only pills. */
.roster-icon-tabbar--labeled .social-tab {
  flex: 1;
  gap: 7px;
  padding: 10px 12px;
  font-size: 13px;
  font-weight: 700;
}
.roster-icon-tabbar--labeled .social-tab svg { width: 17px; height: 17px; flex-shrink: 0; }

/* Day-badge sits on its own row, pulled to the far end — deliberately
   NOT visually attached to the type tabs above it, so the two don't
   read as one merged control. */
.roster-daybadge-wrap { position: relative; align-self: flex-end; }
.roster-daybadge {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 40px;
  padding: 0 12px;
  border-radius: 12px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 14px;
  font-weight: 800;
  color: var(--navy-deep);
}
.roster-daybadge svg { width: 15px; height: 15px; stroke: var(--gold); }

.roster-day-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 20;
  background: var(--surface);
  border-radius: 16px;
  box-shadow: var(--shadow-md), 0 0 0 1px rgba(15,30,66,0.06);
  padding: 14px 16px;
  min-width: 220px;
}
.roster-day-popover__error {
  font-size: 12.5px;
  color: var(--text-muted);
  padding: 8px;
}
.roster-week-progress--popover {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}
.roster-week-dot--tappable {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.roster-week-dot--tappable[disabled] { cursor: default; }
.roster-week-dot--tappable:not([disabled]):active .roster-week-dot__circle { transform: scale(0.9); }

/* ═══════════════════════════════════════════════════════════
   PERSONAL REPORT — additions for the descriptive-word rework,
   progress row, completion screen, and reading views.
   ═══════════════════════════════════════════════════════════ */
.pr-word-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.pr-word-btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-primary);
}
.pr-word-btn.active {
  background: var(--navy);
  border-color: var(--navy);
  color: white;
  font-weight: 700;
}
.pr-optional-tag {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  background: var(--bg);
  padding: 2px 7px;
  border-radius: 999px;
  margin-left: 4px;
}
.pr-hint-note {
  font-size: 11.5px;
  color: var(--text-muted);
  margin: 4px 0 8px;
  line-height: 1.4;
}
.pr-checkbox-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  margin-top: 14px;
  border-radius: 12px;
  background: var(--bg);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
}
.pr-checkbox-row input { width: 18px; height: 18px; accent-color: var(--navy); }

.pr-progress-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 4px 14px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
}
.pr-progress-row__link {
  background: none;
  border: none;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy);
  text-decoration: underline;
}

.pr-done-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 280px;
  margin-top: 8px;
}

/* Redesigned to read as a single message "card" — WhatsApp-style bubble
   with a tail, sitting inside .msg-thread__body's dot-grid backdrop
   (see that rule above) — rather than a bare block of running text.
   That "notepad" look was the #1 complaint about the report views,
   especially on wide desktop screens where an unstyled paragraph just
   stretched edge-to-edge with nothing to anchor it. Used both by the
   full-page parent report view and by the teacher's smaller "view
   sent" sheets, so the fix applies everywhere a report message is
   displayed back. */
.pr-report-readback {
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--text-primary);
  white-space: pre-wrap;
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: var(--r-lg);
  border-top-left-radius: 4px;
  padding: 16px 16px 14px;
  box-shadow: var(--shadow-sm);
  max-width: 640px;
  margin: 4px 2px 16px;
}
.pr-report-readback__meta {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--border-light);
  font-size: 10.5px;
  font-weight: var(--medium);
  color: var(--text-muted);
  text-align: right;
}
/* The report body's actual text is written with GT.utils.formatRichMessage(),
   whose .rich-msg__* classes (above) were sized for the CHAT thread and
   Forum cards — compact contexts where several messages/posts share the
   screen. A report is a single, longer message someone is meant to sit
   and read, so it needs to win that sizing back here rather than
   inheriting chat's smaller 13.5px/13px text. */
.pr-report-readback .rich-msg__p,
.pr-report-readback .rich-msg__list > li { font-size: 14.5px; line-height: 1.7; }
.pr-report-readback .rich-msg__heading { font-size: 15px; }
.pr-readback-body { padding-top: 18px; }
/* .msg-thread__body.page-content (shared with the Inbox chat thread)
   reserves ~240px of bottom padding to clear the chat composer bar — the
   report reading view has no composer, so that padding just left a short
   report's text sitting in a small strip at the top of a mostly empty
   screen, reading as "tiny text pushed down/hidden at the bottom." This
   3-class selector (reports.js always applies all three classes together)
   out-specifies the shared rule above without needing !important. */
.msg-thread__body.page-content.pr-readback-body {
  padding-bottom: calc(96px + env(safe-area-inset-bottom));
}

.pr-autosave-note {
  text-align: center;
  font-size: 11.5px;
  color: var(--text-muted);
  min-height: 16px;
}

/* ═══════════════════════════════════════════════════════════
   REPORTS NAV — week popover stepper, edit-button affordance
   ═══════════════════════════════════════════════════════════ */
.pr-week-stepper {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}
.pr-week-stepper button {
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  color: var(--navy);
}
.pr-week-stepper span {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-deep);
  min-width: 80px;
  text-align: center;
}

/* Makes clear the message below is editable, not just decorative —
   this was the thing that got missed entirely before. */
.pr-editor-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.pr-edit-btn {
  width: 32px; height: 32px;
  border-radius: 50%;
  border: none;
  background: rgba(212,160,23,0.15);
  color: #96780E;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.pr-edit-btn:active { transform: scale(0.9); }
.forum-editor--flash {
  animation: pr-editor-flash 0.7s ease;
}
@keyframes pr-editor-flash {
  0%, 100% { border-color: var(--border); }
  30% { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(212,160,23,0.2); }
}

/* ── ROLL CALL — "No School Today" (Sat/Sun/Mon) ─────────────
   Friendlier full-screen state than emptyBlock(): this is an expected,
   normal daily state (not a misconfiguration), so it gets its own warm
   "day off" visual, a glance at the surrounding Tue–Fri teaching week,
   and a quiet escape hatch for the rare teacher who genuinely has
   something to record today. See js/modules/attendance.js noSchoolBlock(). */
.roster-noschool {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 72vh;
  gap: 22px;
  text-align: center;
}
.roster-noschool__card {
  width: 100%;
  max-width: 320px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: 0 10px 30px rgba(15, 30, 66, 0.08);
  padding: 32px 24px 26px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.roster-noschool__icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--gold-pale);
  color: var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 13px;
  margin-bottom: 4px;
}
.roster-noschool__icon svg { width: 100%; height: 100%; }
.roster-noschool__title {
  font-size: 19px;
  font-weight: 800;
  color: var(--navy-deep);
}
.roster-noschool__sub {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 250px;
}
.roster-noschool__week {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}
.roster-noschool__daychip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--bg);
  min-width: 42px;
}
.roster-noschool__daychip-dow {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.3px;
}
.roster-noschool__daychip-num {
  font-size: 14px;
  font-weight: 700;
  color: var(--navy);
}
.roster-noschool__next {
  font-size: 12px;
  font-weight: 600;
  color: var(--gold);
  margin-top: 6px;
}
.roster-noschool__override {
  border: none;
  background: none;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 8px;
}
.roster-noschool__override:active { color: var(--navy); }

/* ── Small role tag — used on search results that mix parent and
   teacher accounts together (student form's "Link to an existing
   parent or teacher account" picker) so it's clear at a glance which
   is which. ── */
.admin-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: var(--r-full, 9999px);
  margin-left: 6px;
  vertical-align: middle;
}
.admin-tag--teacher {
  background: rgba(27,45,94,0.08);
  color: var(--navy, #1B2D5E);
}

/* ── Recovered from unlinked dashboard-admin-additions.css ──────────
   These were the only rules in that file NOT already duplicated here.
   .avatar--green/--amber: Roll Call Status done/pending avatar tint.
   .ds-cal-*: Daily Stats history calendar (was rendering unstyled). */
.avatar--green {
  background: rgba(5, 150, 105, 0.14);
  color: #059669;
}
.avatar--amber {
  background: rgba(217, 119, 6, 0.14);
  color: #D97706;
}

.ds-cal-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-muted);
  margin-top: 4px;
}
.ds-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.ds-cal-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--navy-deep);
  cursor: pointer;
  position: relative;
}
.ds-cal-day:disabled {
  color: var(--border);
  cursor: default;
}
.ds-cal-day--saved {
  background: rgba(5, 150, 105, 0.10);
  font-weight: 700;
  color: #059669;
}
.ds-cal-day--saved::after {
  content: '';
  position: absolute;
  bottom: 3px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #059669;
}
.ds-cal-day--today {
  outline: 1.5px solid var(--navy);
  outline-offset: -1.5px;
}
.ds-cal-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(5, 150, 105, 0.10);
  position: relative;
}
.ds-cal-dot::after {
  content: '';
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  background: #059669;
}

/* ============================================================
   DAILY STATS — RESTRUCTURED LAYOUT
   Expected is a termly baseline, Actual is the daily job. The
   layout below reflects that: Expected collapses to a read-only
   roll-up in the left column (which doubles as the enrollment
   monitor), the live WhatsApp preview sits beside it on the
   right, and the Actual entry fields get the full width
   underneath. History moved into a sheet behind the date chip
   instead of being permanently mounted at the bottom — that
   block plus the Recent list was most of the old scrolling.
   ============================================================ */

/* ── Top bar: date chip + config gear ───────────────────────── */
.ds-topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
}

.ds-datechip {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--surface, #fff);
  border: 1.5px solid var(--border);
  border-radius: 12px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--navy-deep);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.ds-datechip:active { background: var(--surface-alt, #F5F6FA); }
.ds-datechip svg { width: 17px; height: 17px; stroke: currentColor; flex: none; }
.ds-datechip__text { font-size: 14px; font-weight: 700; }
.ds-datechip__hint {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.ds-configbtn {
  flex: none;
  width: 42px; height: 42px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  color: var(--text-secondary);
}
.ds-configbtn svg { width: 19px; height: 19px; }

/* ── Expected | Preview split ────────────────────────────────
   Two columns on anything with room, stacked on a narrow phone.
   Expected first in source order so it stays on top when
   stacked — it's the context you read before the message. */
.ds-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin-top: 16px;
}
@media (min-width: 560px) {
  .ds-split { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 16px; }
}
.ds-split__col { min-width: 0; }

/* ── Expected roll-up (read-only) ────────────────────────────── */
.ds-expected-card {
  background: var(--surface, #fff);
  border: 1.5px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ds-enrol-total {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.ds-enrol-total__num {
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  color: var(--navy-deep);
}
.ds-enrol-total__lbl {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}
.ds-enrol-total__split {
  flex-basis: 100%;
  font-size: 11.5px;
  color: var(--text-secondary);
  margin-top: 2px;
}

.ds-expected-sec + .ds-expected-sec { margin-top: 4px; }
.ds-expected-sec__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--navy-deep);
}
.ds-expected-sec__total { font-size: 14px; font-weight: 800; }
.ds-expected-sec__sub {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 1px;
  margin-bottom: 5px;
}

.ds-class-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 3px 0;
  font-size: 11.5px;
  color: var(--text-secondary);
}
.ds-class-line__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds-class-line__num {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--navy-deep);
}

.ds-expected-edit {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 9px;
  margin-top: 2px;
  background: var(--surface-alt, #F5F6FA);
  border: none;
  border-radius: 9px;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: var(--navy-deep);
  cursor: pointer;
}
.ds-expected-edit svg { width: 14px; height: 14px; stroke: currentColor; }
.ds-expected-edit:active { background: var(--border); }

/* ── Live message preview ────────────────────────────────────
   Scrolls inside itself rather than growing the page — the whole
   point of this layout is that nothing below it moves. */
.ds-preview-pre {
  white-space: pre-wrap;
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.55;
  background: var(--surface-alt, #F5F6FA);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin: 0;
  max-height: 340px;
  overflow-y: auto;
  overflow-x: auto;
}

/* ── Actual entry ────────────────────────────────────────────── */
.ds-actual-classhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--navy-deep);
}
.ds-actual-classhead__exp {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ── Inline notes (read-only banner, prefill hint) ───────────── */
/* .ds-actual-note is the container renderDailyStats() injects the
   per-day hint into (either "already saved" or "pre-filled from Roll
   Call"). It has no chrome of its own — the .ds-readonly-note inside
   carries all the styling — it only needs to collapse to nothing while
   empty, which it is on first paint before loadForDate() resolves.
   Without :empty it would leave a stray gap above the Actual cards. */
.ds-actual-note:empty { display: none; }

.ds-readonly-note {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 9px 11px;
  background: var(--surface-alt, #F5F6FA);
  border-radius: 9px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-secondary);
}
.ds-readonly-note svg {
  width: 14px; height: 14px;
  stroke: currentColor;
  flex: none;
  margin-top: 1px;
}

/* ── Read-only day modal ─────────────────────────────────────── */
.ds-rollup-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.ds-rollup-grid > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ds-rollup-k {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
}
.ds-rollup-v {
  font-size: 17px;
  font-weight: 800;
  color: var(--navy-deep);
  font-variant-numeric: tabular-nums;
}
.ds-rollup-v small {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}
.ds-attend-pct {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* ══════════════════════════════════════════════════════════════
   SYNC REGISTER
   What would change if this app followed the Bursar's register,
   class by class. Every row is ticked by default and every tick
   can be cleared: the head teacher is agreeing to each change,
   not watching one happen.

   The left border carries the kind of change, so a column of
   them reads at a glance without anyone parsing the words.
   ══════════════════════════════════════════════════════════════ */
.sync-class {
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  overflow: hidden;
  background: var(--card, #fff);
}
.sync-class__head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 12px 14px;
  background: var(--surface-2, #f9fafb);
  border: none; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
}
.sync-class__head strong { min-width: 0; overflow-wrap: anywhere; }
.sync-class__body { padding: 8px; display: flex; flex-direction: column; gap: 8px; }

.sync-pill {
  background: #fef3c7; color: #92400e;
  font-size: 11px; font-weight: 700;
  padding: 3px 8px; border-radius: 999px; white-space: nowrap;
}

.sync-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  background: var(--surface-2, #f9fafb);
  border-radius: 8px;
}
.sync-row__tick { flex: none; padding-top: 2px; }
.sync-row__tick input { width: 18px; height: 18px; }
.sync-row__name { font-weight: 700; font-size: 15px; overflow-wrap: anywhere; }
.sync-row__tag {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  margin-top: 1px;
}
.sync-row__diff { font-size: 13px; margin-top: 4px; overflow-wrap: anywhere; }
.sync-row__old  { color: var(--text-dim, #6b7280); text-decoration: line-through; }
/* An admission number split across two lines reads as two numbers. */
.sync-row__adm {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--text-dim, #6b7280);
  margin-top: 3px; white-space: nowrap;
}
.sync-row__note {
  font-size: 12px; line-height: 1.5;
  color: var(--text-dim, #6b7280); margin-top: 4px;
}

/* ══════════════════════════════════════════════════════════════
   TAPPABLE CARD REGION
   A card that both navigates somewhere and carries a destructive
   button. The whole card cannot be the link, or somebody heading
   to a parent's page unlinks them instead — so only this region
   is tappable, and Unlink stays outside it.
   ══════════════════════════════════════════════════════════════ */
.admin-card__link {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
  cursor: pointer;
  border-radius: 10px;
  margin: -4px -6px;
  padding: 4px 6px;
}
.admin-card__link:hover { background: var(--surface-2, #f9fafb); }
.admin-card__link:focus-visible {
  outline: 2px solid var(--color-primary, #1B5480);
  outline-offset: 2px;
}
.admin-card__link .chevron-icon { margin-left: auto; flex: none; }

.admin-count {
  font-weight: 600;
  color: var(--text-muted, #6b7280);
  text-transform: none;
  letter-spacing: 0;
}

/* ══════════════════════════════════════════════════════════════
   DAY PICKER — which weekdays the school runs.
   Seven targets on one row has to stay tappable at 320px, so the
   chips shrink rather than wrap into a ragged second line.
   ══════════════════════════════════════════════════════════════ */
.daypicker { display: flex; gap: 6px; }
.daypicker__day {
  flex: 1 1 0;
  min-width: 0;
  padding: 11px 2px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--surface, #fff);
  color: var(--text-muted, #6b7280);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.daypicker__day.is-on {
  background: var(--color-primary, #1B2D5E);
  border-color: var(--color-primary, #1B2D5E);
  color: #fff;
}
.daypicker__day:focus-visible {
  outline: 2px solid var(--color-primary, #1B2D5E);
  outline-offset: 2px;
}

/* ══════════════════════════════════════════════════════════════
   DUPLICATE PAIRS
   Two records that may be one child. Both sides are shown at
   equal weight: the app does not know which is the real one, and
   laying one out as the obvious answer would push somebody into
   a merge they have not actually checked.
   ══════════════════════════════════════════════════════════════ */
.dup-pair {
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 12px;
  padding: 14px;
  background: var(--surface, #fff);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px;
}
.dup-pair__side { flex: 1 1 200px; min-width: 0; }
.dup-pair__vs   { flex: 0 0 auto; color: var(--text-muted, #6b7280); font-size: 13px; padding-top: 2px; }
.dup-pair__name { font-weight: 700; overflow-wrap: anywhere; }
.dup-pair__meta { font-size: 12px; color: var(--text-muted, #6b7280); margin-top: 2px; }
.dup-pair__stats{ font-size: 12px; margin-top: 4px; }
.dup-pair__none { color: var(--text-muted, #6b7280); font-style: italic; }
.dup-pair__why  {
  flex: 1 1 100%;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-muted, #6b7280);
  border-top: 1px solid var(--border-light, #f3f4f6);
  padding-top: 10px;
}
.dup-pair__acts {
  flex: 1 1 100%;
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
}
@media (max-width: 480px) {
  .dup-pair__vs { flex: 1 1 100%; }
  .dup-pair__acts .btn { flex: 1 1 auto; }
}

.mp-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-muted, #6b7280); margin-bottom: 2px;
}
.mp-keep, .mp-drop { padding: 12px; border-radius: 10px; }
.mp-keep { background: #ecfdf5; border: 1px solid #16a34a; }
.mp-drop { background: var(--surface-2, #f9fafb); border: 1px dashed var(--border, #e5e7eb); }
.mp-drop .dup-pair__name { text-decoration: line-through; color: var(--text-muted, #6b7280); }

/* A sync row that may be a child already on the register. Amber,
   not red: nothing is wrong yet, and nothing has been written —
   there is a judgement waiting that only a person can make. */
.sync-row__warn {
  font-size: 12.5px;
  line-height: 1.5;
  color: #b7791f;
  background: #fffaf0;
  border-radius: 6px;
  padding: 6px 8px;
  margin-top: 5px;
}

/* ══════════════════════════════════════════════════════════════
   MONTH CALENDAR
   A month of one class: what was recorded, and what was missed.

   Colour carries the meaning and a glyph repeats it, because a
   register read at arm's length in daylight is not the place to
   rely on a green square and a red square being told apart.
   ══════════════════════════════════════════════════════════════ */
.mcal__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 12px;
}
.mcal__month { font-size: 17px; font-weight: 800; }
.mcal__nav {
  width: 38px; height: 38px; flex: none;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--surface, #fff);
  font-size: 20px; line-height: 1; cursor: pointer;
  color: inherit;
}
.mcal__nav:active { transform: scale(.95); }

.mcal__summary { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.mcal__stat {
  font-size: 12px; font-weight: 700;
  padding: 5px 10px; border-radius: 999px;
  background: var(--surface-2, #f3f4f6); color: var(--text-muted, #6b7280);
}
.mcal__stat--done   { background: #ecfdf5; color: #15803d; }
.mcal__stat--missed { background: #fef2f2; color: #b91c1c; }
.mcal__stat--muted  { background: transparent; }

.mcal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}
.mcal__dow {
  text-align: center; font-size: 10.5px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-muted, #6b7280); padding-bottom: 4px;
}
.mcal__cell {
  position: relative;
  aspect-ratio: 1 / 1;
  min-height: 40px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: var(--surface-2, #f9fafb);
  font: inherit; color: inherit; cursor: default;
  padding: 0;
}
button.mcal__cell { cursor: pointer; }
button.mcal__cell:active { transform: scale(.94); }
.mcal__num  { font-size: 12.5px; font-weight: 700; line-height: 1; }
.mcal__mark { font-size: 12px; line-height: 1; }

.mcal__cell--pad    { background: transparent; }
.mcal__cell--off    { background: transparent; color: var(--text-muted, #9ca3af); opacity: .55; }
.mcal__cell--future { background: transparent; color: var(--text-muted, #9ca3af); border: 1px dashed var(--border, #e5e7eb); }
.mcal__cell--done   { background: #ecfdf5; color: #15803d; border-color: #bbf7d0; }
.mcal__cell--part   { background: #fffbeb; color: #b45309; border-color: #fde68a; }
.mcal__cell--missed { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }

/* The weekly report, on the week's last teaching day. */
.mcal__dot {
  position: absolute; bottom: 4px; right: 4px;
  width: 6px; height: 6px; border-radius: 50%;
}
.mcal__dot--ok     { background: #15803d; }
.mcal__dot--report { background: #b91c1c; }

.mcal__key {
  display: flex; flex-wrap: wrap; gap: 10px 14px;
  margin-top: 14px; font-size: 11.5px; color: var(--text-muted, #6b7280);
}
.mcal__key span { display: flex; align-items: center; gap: 5px; }
.mcal__swatch {
  width: 12px; height: 12px; border-radius: 4px; display: inline-block;
  border: 1px solid transparent;
}
.mcal__swatch--done   { background: #ecfdf5; border-color: #bbf7d0; }
.mcal__swatch--part   { background: #fffbeb; border-color: #fde68a; }
.mcal__swatch--missed { background: #fef2f2; border-color: #fecaca; }
.mcal__swatch--off    { background: var(--surface-2, #f3f4f6); }

.mcal__empty { padding: 28px 12px; text-align: center; color: var(--text-muted, #6b7280); }

.mcal__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 11px 0;
  border-bottom: 1px solid var(--border-light, #f3f4f6);
}
.mcal__row:last-child { border-bottom: none; }
.mcal__row-label { font-weight: 600; }
.mcal__row-value { font-size: 13.5px; font-weight: 700; white-space: nowrap; }
.mcal__row-value.is-ok     { color: #15803d; }
.mcal__row-value.is-part   { color: #b45309; }
.mcal__row-value.is-missed { color: #b91c1c; }
.mcal__note {
  margin-top: 12px; font-size: 13px; line-height: 1.55;
  color: var(--text-muted, #6b7280);
  background: var(--surface-2, #f9fafb);
  border-radius: 10px; padding: 11px 12px;
}
/* A day the school does not run, carrying records anyway. */
.mcal__cell--offdata {
  background: var(--surface-2, #f3f4f6);
  color: var(--text-muted, #6b7280);
  border-color: var(--border, #e5e7eb);
  border-style: dashed;
}

/* Work saved on the device but not yet at the school. "All done" is
   true of the teacher either way; it is not true of the server, and
   the difference is the whole reason this label exists. */
.roster-done-hero__waiting {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 10px; padding: 6px 12px;
  border-radius: 999px;
  background: #fffaf0; color: #b7791f;
  font-size: 12.5px; font-weight: 700;
}
.roster-done-hero__waiting svg { width: 14px; height: 14px; }
