:root {
  --navy: #1F3A5F;
  --navy-dark: #142744;
  --gold: #C9A227;
  --gold-light: #F3E7B8;
  --bg: #F4F6F9;
  --card: #FFFFFF;
  --text: #1C2530;
  --muted: #6B7684;
  --border: #DDE3EA;
  --success: #1E8E5A;
  --danger: #C0392B;
  --radius: 10px;
  --shadow: 0 2px 10px rgba(20,39,68,0.08);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Segoe UI", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  padding-bottom: 60px;
}

header.app-header {
  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  color: #fff;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: var(--shadow);
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 50;
}

header.app-header .brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

header.app-header .brand .crest {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: #fff;
  object-fit: contain;
  padding: 2px;
}

header.app-header h1 {
  font-size: 16px;
  margin: 0;
  line-height: 1.2;
}

header.app-header p {
  margin: 0;
  font-size: 11px;
  color: var(--gold-light);
}

.status-pill {
  font-size: 11px;
  padding: 5px 12px;
  border-radius: 20px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
}

.status-pill.online { background: rgba(30,142,90,0.18); color: #7CF0B4; }
.status-pill.offline { background: rgba(192,57,43,0.2); color: #FF9C8E; }

.dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }

main {
  max-width: 720px;
  margin: 0 auto;
  padding: 18px 16px 40px;
}

.card {
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
  margin-bottom: 16px;
}

.card h2 {
  font-size: 15px;
  margin: 0 0 14px;
  color: var(--navy);
  border-bottom: 2px solid var(--gold-light);
  padding-bottom: 8px;
}

.field {
  margin-bottom: 14px;
}

.field label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 5px;
}

.field label .req { color: var(--danger); }

.field input,
.field select {
  width: 100%;
  padding: 10px 12px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  background: #fff;
  color: var(--text);
  font-family: inherit;
}

.field input:focus,
.field select:focus {
  outline: none;
  border-color: var(--navy);
}

.field input[readonly] {
  background: #F0F2F5;
  color: var(--muted);
}

.field.error input,
.field.error select {
  border-color: var(--danger);
}

.field .err-msg {
  font-size: 11px;
  color: var(--danger);
  margin-top: 4px;
  display: none;
}

.field.error .err-msg { display: block; }

.row2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.row3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}

@media (max-width: 480px) {
  .row2 { grid-template-columns: 1fr; }
  .row3 { grid-template-columns: 1fr; }
}

/* Photo capture */
.photo-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.photo-frame {
  width: 160px;
  height: 160px;
  border-radius: 12px;
  border: 2px dashed var(--border);
  background: #F7F9FB;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}

.photo-frame img, .photo-frame video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-frame .placeholder {
  color: var(--muted);
  font-size: 12px;
  text-align: center;
  padding: 10px;
}

.photo-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

button {
  cursor: pointer;
  font-family: inherit;
  border: none;
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  transition: transform 0.05s ease, opacity 0.15s ease;
}

button:active { transform: scale(0.97); }

.btn-primary { background: var(--navy); color: #fff; }
.btn-gold { background: var(--gold); color: var(--navy-dark); }
.btn-outline { background: #fff; color: var(--navy); border: 1.5px solid var(--navy); }
.btn-danger { background: #fff; color: var(--danger); border: 1.5px solid var(--danger); }
.btn-block { width: 100%; }
button:disabled { opacity: 0.5; cursor: not-allowed; }

.submit-bar {
  display: flex;
  gap: 10px;
  margin-top: 6px;
}

.toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--navy-dark);
  color: #fff;
  padding: 12px 20px;
  border-radius: 30px;
  font-size: 13px;
  box-shadow: var(--shadow);
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, bottom 0.25s ease;
}

.toast.show { opacity: 1; bottom: 30px; }
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }

.queue-banner {
  background: var(--gold-light);
  color: var(--navy-dark);
  font-size: 12px;
  padding: 8px 14px;
  border-radius: 8px;
  margin-bottom: 14px;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.queue-banner.show { display: flex; }

.helper {
  font-size: 11px;
  color: var(--muted);
  margin-top: -8px;
  margin-bottom: 14px;
}

/* Dashboard specifics */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(185px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.stat-card {
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px;
  text-align: center;
}

.stat-card .num {
  font-size: 24px;
  font-weight: 700;
  color: var(--navy);
  line-height: 1.2;
  /* Long money figures (e.g. LD 2,703,291.42) must never be clipped by
     the card edge: the cards are wide enough for them in the normal
     case, and if a figure is ever still too long it wraps onto a new
     line instead of being cut off. */
  overflow-wrap: anywhere;
  word-break: break-word;
}
.stat-card { min-width: 0; overflow: visible; }

/* A varied accent palette for stat cards, so a busy dashboard reads
   at a glance rather than every figure looking identically navy —
   applied via a modifier class on the card itself, overriding just
   the number's color; everything else about the card (background,
   shadow, label styling) stays exactly the same regardless of which
   accent is used. */
/* A varied accent palette for stat cards — coloring the card's own
   background, not just its number, per direct instruction (the
   text-only version color-coded the figures but the actual
   "placeholder" card underneath stayed the same plain white as
   every other card, which didn't read as "standing out" the way a
   colored card genuinely does). White text throughout for contrast
   against each solid, saturated background; the label uses a
   slightly translucent white rather than the page's usual muted
   gray, since that gray has too little contrast against any of
   these colors to stay readable. */
.stat-card.stat-teal { background: #0E7C86; }
.stat-card.stat-green { background: #1E8E5A; }
.stat-card.stat-gold { background: #B8860B; }
.stat-card.stat-purple { background: #6A4C93; }
.stat-card.stat-coral { background: #C0392B; }
.stat-card.stat-blue { background: #2563A8; }
.stat-card.stat-brown { background: #8B5E34; }
.stat-card.stat-magenta { background: #9B2F6B; }
.stat-card.stat-teal .num, .stat-card.stat-green .num, .stat-card.stat-gold .num,
.stat-card.stat-purple .num, .stat-card.stat-coral .num, .stat-card.stat-blue .num,
.stat-card.stat-brown .num, .stat-card.stat-magenta .num {
  color: #fff;
}
.stat-card.stat-teal .label, .stat-card.stat-green .label, .stat-card.stat-gold .label,
.stat-card.stat-purple .label, .stat-card.stat-coral .label, .stat-card.stat-blue .label,
.stat-card.stat-brown .label, .stat-card.stat-magenta .label {
  color: rgba(255, 255, 255, 0.85);
}

.stat-card .label {
  font-size: 11px;
  color: var(--muted);
  margin-top: 4px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* #districtTabs also carries class="tabs" (the same secondary
   district-filter row used on District & School Breakdown and
   Recent Registrations), which would otherwise inherit .tabs's own
   top: 74px and collide with the main navigation tabs sitting
   sticky at that exact same offset — this override gives it its
   own stacking position, directly below the main tabs instead of
   underneath them. */
/* Base/fallback offset only — the real value is computed and applied
   by updateStickyOffsets() in auth-guard.js at runtime, since the
   main nav bar's actual height varies (it now wraps to two lines on
   many screens with this many tabs) and a fixed guess drifts wrong
   the moment the tab count or viewport changes, exactly what caused
   this to visibly overlap real content instead of sitting cleanly
   above it. Declared fully self-sufficient (not just an override of
   .tabs) since this same id is also reused, per direct instruction
   from the school/district-scoped "Showing: X" label that replaces
   the tab bar entirely for those roles — that label never has the
   .tabs class, so it needs its own sticky/background/z-index here
   too, or it would scroll away while the table header below it
   stayed sticky, leaving a gap instead of a clean stack. */
#districtTabs {
  position: -webkit-sticky;
  position: sticky;
  top: 128px;
  z-index: 19;
  background: var(--bg);
}

/* Freezes each table's own column-header row — applied to the
   header cells themselves (not <thead>, which position: sticky
   doesn't reliably affect on its own across browsers). This is the
   definitive fix for the frozen-header bug that kept recurring with
   every previous attempt at computing a page-level pixel offset for
   it (stacking below the nav, the district tabs, and the card title
   all at once, needing exact JavaScript-measured values that kept
   landing wrong in ways impossible to verify without a live
   browser).

   A later attempt wrapped the table in its own bounded, internally-
   scrollable panel instead, with the header sticking to that panel's
   own top:0 — reasoned to be correct by construction. It genuinely
   wasn't: confirmed with a real, disposable Playwright-driven
   Chromium instance (not guessed) that scrolling the *whole page*
   (how anyone actually scrolls) moves that entire panel, header
   included, since the panel itself is not sticky to the page — only
   its own header is sticky *within* it. The header only stayed
   frozen while scrolling within the panel's own tiny internal
   scrollbar specifically, which is not how the feature was ever
   going to be used.

   Reverted to page-level stacking (below the nav, the district tabs,
   and the card title), but this time actually verified against real
   screenshots at multiple scroll depths, across a viewport resize
   that changes how many lines the nav wraps to, before treating it
   as done. `.table-scroll-panel` intentionally carries no overflow-x
   handling anymore, confirmed as the exact reason the page-level
   version of this fix was landing wrong in a much earlier attempt:
   per the CSS spec, giving a container any non-`visible` overflow on
   either axis forces the other axis to compute to `auto` as well —
   so `overflow-x: auto` alone was silently turning this container
   into its own scrolling context, which broke `position: sticky` for
   everything inside it relative to the page. A table too wide for a
   narrow screen now overflows the page itself horizontally instead
   of scrolling within a contained box — a real tradeoff, accepted
   deliberately in favor of the sticky header actually working,
   since these two behaviors cannot coexist on the same element per
   that same spec rule. Solid background required on the header,
   same reason as every other sticky element here: without it, rows
   scrolling underneath would show through it instead of being
   hidden by it. */
.table-scroll-panel {
}
.sticky-header-table thead th {
  position: -webkit-sticky;
  position: sticky;
  top: 182px;
  z-index: 5;
  background: var(--navy);
}

/* A third stacked sticky layer — the card's own title (e.g.
   "District & School Breakdown"), sitting between the district-tabs
   bar and the table's own column headers. Without this, the title
   scrolls away normally while the table header stays pinned at a
   fixed offset that assumed the title would still be there — which
   is exactly what made the header appear to float in the middle of
   the visible rows instead of sitting directly under the card's own
   title, the way it's supposed to. Solid background required, same
   reason as every other sticky element here. */
.sticky-card-title {
  position: -webkit-sticky;
  position: sticky;
  z-index: 17;
  background: var(--bg);
  margin: 0;
  padding: 20px 20px 12px;
}

table.report {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

table.report th, table.report td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
}

table.report th {
  background: var(--navy);
  color: #fff;
  font-size: 11px;
  text-transform: uppercase;
}

table.report tr:hover { background: #F7F9FB; }

.pill-tag {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  background: var(--gold-light);
  color: var(--navy-dark);
}

.tabs {
  max-width: 1000px;
  /* Found to be the real reason the centering/pinning below wasn't
     visibly doing anything: with no explicit width, a block box fills
     its CONTAINING block's width — and on a page where some other
     element (e.g. a wide table further down) forces <body> wider than
     the viewport, this bar stretched to match that full scrollable
     width too, not just the visible screen. Its buttons were then
     "centered" within that oversized, mostly off-screen box, and
     sticking the box's own left edge at left:0 didn't bring the
     (far-right-of-center) buttons back into view — 100vw pins this
     bar's actual size to the real viewport width regardless of how
     wide the rest of the page's content makes <body>, so centering and
     the sticky left below both work against the width the screen
     actually shows. */
  width: 100vw;
  margin: 14px auto 0;
  padding: 0 16px;
  box-sizing: border-box;
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
  flex-wrap: wrap;
  /* Centers each wrapped row of buttons instead of leaving it flush to
     the left (flex's default) — on a phone, where most rows only
     partially fill the width before wrapping, flex-start left them
     looking ragged/lopsided instead of balanced. */
  justify-content: center;
  /* Self-contained sizing/centering (max-width/margin/padding above),
     matching .county-nav's own approach exactly — this bar doesn't
     depend on being placed inside any particular wrapper to render in
     the right place. It originally relied on sitting inside
     enrollment-records.html's own <main style="max-width:1100px">
     for its horizontal position, which is exactly why the same tabs
     bar showed up flush against the left edge of the screen (and the
     whole row looked like it had shifted) on the three other pages,
     where it was placed directly in the page body rather than inside
     that specific wrapper.
     Same reasoning as .county-nav above — including the same fix:
     header.app-header is already sticky at top: 0 with a higher
     z-index, so sticking this bar at that same top: 0 would collide
     with it and end up visually covered by the header rather than
     sitting below it. Offset by the header's own fixed height so the
     two stack in their natural, original order instead.
     left: 0 (sticky on the horizontal axis too) keeps this bar pinned
     to the screen instead of scrolling away sideways on a page that's
     wider than the screen (e.g. breakdown.html's table) — without it,
     scrolling right to see the rest of a wide table also dragged this
     whole bar off to the left along with everything else, undoing the
     centering above. */
  position: -webkit-sticky;
  position: sticky;
  top: 74px;
  left: 0;
  z-index: 20;
  background: var(--bg);
  padding-top: 10px;
  padding-bottom: 10px;
}

.tabs button, .tabs a {
  background: #fff;
  color: var(--navy);
  border: 1.5px solid var(--border);
  padding: 8px 14px;
  text-decoration: none;
  display: inline-block;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  border-radius: 8px;
}

.tabs button.active, .tabs a.active {
  background: #fff;
  color: var(--navy);
  border-color: var(--navy);
}

/* A cyclical accent palette across every unselected tab — main nav,
   sub-tabs (Budget Management/Bank Reconciliation, Enrollment &
   Financial Summary/Requests & Updates), and the district-filter
   row alike — so a busy tab bar reads with the same "stand out"
   variety as the stat cards, rather than a uniform row of plain
   white buttons. The currently active tab always overrides back to
   white with navy text/border regardless of which color position it
   falls on, so it's still the one that visibly reads as "selected"
   against the now-colorful rest of the row — the opposite of the
   scheme's previous white-background/colored-active convention,
   per direct instruction. */
.tabs button:not(.active):nth-child(8n+1), .tabs a:not(.active):nth-child(8n+1),
.county-nav a:not(.active):nth-child(8n+1) { background: #0E7C86; color: #fff; border-color: #0E7C86; }
.tabs button:not(.active):nth-child(8n+2), .tabs a:not(.active):nth-child(8n+2),
.county-nav a:not(.active):nth-child(8n+2) { background: #1E8E5A; color: #fff; border-color: #1E8E5A; }
.tabs button:not(.active):nth-child(8n+3), .tabs a:not(.active):nth-child(8n+3),
.county-nav a:not(.active):nth-child(8n+3) { background: #B8860B; color: #fff; border-color: #B8860B; }
.tabs button:not(.active):nth-child(8n+4), .tabs a:not(.active):nth-child(8n+4),
.county-nav a:not(.active):nth-child(8n+4) { background: #6A4C93; color: #fff; border-color: #6A4C93; }
.tabs button:not(.active):nth-child(8n+5), .tabs a:not(.active):nth-child(8n+5),
.county-nav a:not(.active):nth-child(8n+5) { background: #C0392B; color: #fff; border-color: #C0392B; }
.tabs button:not(.active):nth-child(8n+6), .tabs a:not(.active):nth-child(8n+6),
.county-nav a:not(.active):nth-child(8n+6) { background: #2563A8; color: #fff; border-color: #2563A8; }
.tabs button:not(.active):nth-child(8n+7), .tabs a:not(.active):nth-child(8n+7),
.county-nav a:not(.active):nth-child(8n+7) { background: #8B5E34; color: #fff; border-color: #8B5E34; }
.tabs button:not(.active):nth-child(8n+8), .tabs a:not(.active):nth-child(8n+8),
.county-nav a:not(.active):nth-child(8n+8) { background: #9B2F6B; color: #fff; border-color: #9B2F6B; }

/* County-admin sub-navigation, shared across dashboard/breakdown/registrations/data-management/user-management */
.county-nav {
  max-width: 1000px;
  /* width:100vw + box-sizing:border-box — see the matching comment on
     .tabs above; same bug, same fix. */
  width: 100vw;
  margin: 14px auto 0;
  padding: 0 16px;
  box-sizing: border-box;
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  /* Centers each wrapped row instead of leaving it ragged/left-aligned —
     see the matching comment on .tabs above. */
  justify-content: center;
  /* Stays put while everything below it (the cards) scrolls past —
     rather than the nav itself scrolling away and needing to scroll
     back up to switch pages. header.app-header above is ALSO sticky
     (top: 0, z-index: 50) — sticking this at the same top: 0 would
     collide with it at the exact same position, and since the header
     carries the higher z-index, it would sit on top of and visually
     cover this bar rather than the two stacking in their natural
     order. Offset by the header's own fixed height (74px, confirmed
     the same across every page that uses it) so this sticks directly
     below the header instead — both pinned together, in their
     original relative positions, rather than overlapping. A solid
     background matching the page's own is required for a sticky
     element, otherwise content scrolling underneath would show
     through. left: 0 (sticky on the horizontal axis too) keeps this bar
     pinned to the screen instead of scrolling away sideways on a page
     wider than the screen — see the matching comment on .tabs above. */
  position: -webkit-sticky;
  position: sticky;
  top: 74px;
  left: 0;
  z-index: 20;
  background: var(--bg);
  padding-top: 10px;
  padding-bottom: 10px;
}

.county-nav a {
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--navy);
  background: #fff;
  border: 1.5px solid var(--border);
  padding: 8px 14px;
  border-radius: 8px;
}

.county-nav a.active {
  background: #fff;
  color: var(--navy);
  border-color: var(--navy);
}

.thumb {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--border);
}

footer.app-footer {
  text-align: center;
  font-size: 11px;
  color: var(--muted);
  padding: 20px;
}

footer.app-footer .credit {
  margin-top: 8px;
  font-size: 10.5px;
  color: var(--muted);
  opacity: 0.85;
  font-style: italic;
}

/* ---- Grades entry (grades.html) ---- */
.marks-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.marks-table th, .marks-table td { border: 1px solid var(--border); padding: 4px; text-align: center; }
.marks-table th { background: var(--navy); color: #fff; font-weight: 600; font-size: 11px; }
.marks-table td:first-child, .marks-table th:first-child { text-align: left; min-width: 150px; }
.marks-table input, .marks-table select { width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: 4px; padding: 4px; font-size: 12px; text-align: center; }
.subject-row { display: flex; gap: 6px; align-items: center; margin-bottom: 6px; }
.subject-row input { flex: 1; }
.subject-row button { padding: 4px 10px; font-size: 11px; }
.section-divider { border: none; border-top: 1px solid var(--border); margin: 22px 0 14px; }

/* ---- Daily Attendance Register fullscreen (personnel-welfare.html) ----
   Toggled on <body> by enterAttendanceFullscreen()/exitAttendanceFullscreen()
   in js/personnel-welfare.js. Hides this page's own header, the county-wide
   nav, and the Key Personnel/Daily Attendance sub-tabs, then expands
   #attendancePanel to fill the whole viewport (phone or desktop) so the
   embedded Check-In/Dashboard app actually gets real screen space instead of
   a cramped sliver — attendanceBackBtn (in the bar this reveals) is the only
   way out, back to this page's normal tabbed view. */
body.attendance-fullscreen > header.app-header,
body.attendance-fullscreen > #countyNav,
body.attendance-fullscreen > #pageTabs,
body.attendance-fullscreen #pwSubTabs,
body.attendance-fullscreen #keyPersonnelPanel,
body.attendance-fullscreen #attendanceHeading,
body.attendance-fullscreen #attendanceIntroText,
body.attendance-fullscreen #principalFooter {
  display: none !important;
}
/* The user-badge/Sign Out row sits directly in <body> with no id of its own
   (see personnel-welfare.html) — targeted by position instead. */
body.attendance-fullscreen > header.app-header + div {
  display: none !important;
}
body.attendance-fullscreen main {
  max-width: none;
  margin: 0;
  padding: 0;
}
body.attendance-fullscreen #attendancePanel {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: #fff;
  /* The iframe below does its own internal scrolling for its own content,
     but this panel ALSO scrolls (overflow-y, not hidden) as a safety net:
     resizeAttendanceIframe() in js/personnel-welfare.js computes the
     iframe's pixel height from measurements that can be off by a few
     pixels (sticky-bar reflow timing, border rounding, etc.) — when that
     happens the iframe's bottom edge lands a touch below the visible
     window, and an earlier version of this rule used overflow:hidden here,
     which clipped that sliver with no way to reach it (that's what cut off
     the page's footer/Admin Access link). Letting this panel scroll too
     means even an imperfect height calculation never permanently hides
     content — worst case, a couple of extra scroll pixels. */
  overflow-y: auto;
}
body.attendance-fullscreen #attendancePanel .card {
  margin: 0;
  padding: 0;
  border-radius: 0;
  border: none;
  min-height: 100vh;
  box-sizing: border-box;
  /* Deliberately plain block flow now — NOT flex, NOT grid. Both were tried
     (see git history/prior comments) and both left the iframe stuck short
     of the full remaining height: this app's rendering engine (the classic
     square-arrow scrollbar in screenshots points to an older/embedded
     Chromium webview, not a modern desktop Chrome) doesn't reliably hand a
     replaced element like <iframe> the space either layout mode computed
     for it. Given up on CSS doing this sizing at all — the iframe's actual
     pixel height is now set directly by resizeAttendanceIframe() in
     js/personnel-welfare.js (window.innerHeight minus the two bars'
     measured offsetHeight), which only depends on plain element geometry
     and works in literally any engine. */
}
body.attendance-fullscreen #attendanceFullscreenBar {
  background: #fff;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
body.attendance-fullscreen #attendanceSubTabs {
  padding: 0 14px;
}
/* !important throughout this rule: #attendanceIframe carries its own sizing
   as an inline style attribute (personnel-welfare.html), which otherwise
   beats any external stylesheet rule regardless of selector specificity.
   No height rule here on purpose — resizeAttendanceIframe() in
   js/personnel-welfare.js sets an explicit pixel height directly on the
   element, which (being a later, more specific inline style write) is what
   actually takes effect; a CSS height here would only fight it. min-height
   DOES need overriding, though: the element's original inline style (meant
   for the old cramped non-fullscreen embed) carries min-height:900px, which
   otherwise forces the iframe to be at least 900px tall no matter what
   height resizeAttendanceIframe() sets — on any screen shorter than that,
   the extra height hangs off the bottom of the viewport, and since the
   panel around it is overflow:hidden (see .card above), that overflow
   becomes permanently unreachable by scrolling, taking the page's footer
   (and the Admin Access link in it) down with it. */
body.attendance-fullscreen #attendanceIframe {
  display: block !important;
  width: 100% !important;
  min-height: 0 !important;
  border: none !important;
  border-radius: 0 !important;
}
.marks-table input.computed-mark { background: #F0F2F5; color: var(--muted); font-weight: 600; cursor: not-allowed; }

/* ---- Frozen (sticky-left) identifying column, on two specific tables ----
   table.report is shared across 11 pages, so these rules are scoped to a
   marker class added only on the one <table> each applies to — NOT a
   blanket nth-child rule that would wrongly freeze a column on every
   other page's unrelated table. table.report has no overflow-x of its
   own (see .table-scroll-panel comment above); the PAGE itself scrolls
   horizontally on a narrow screen, and position:sticky/left:0 pins
   against that the same way it would against a local scroll container —
   the columns before the frozen one scroll normally out of view, then
   the frozen one sticks to the screen's left edge from that point on. */
.freeze-school-col, .freeze-student-col {
  /* The real reason sticky-left silently did nothing: table.report uses
     border-collapse: collapse (set above), and position:sticky on a
     table cell is a well-known no-op in most browsers once a table's
     borders are collapsed — it only works reliably with "separate".
     border-spacing:0 keeps cells touching exactly as collapse did, and
     since every cell here only carries a border-bottom (no top/left/
     right border set anywhere), switching away from collapse doesn't
     create any visible doubled-border seam. */
  border-collapse: separate;
  border-spacing: 0;
}
/* The frozen column uses plain CSS sticky-left. On phones it sits inside
   .local-scroll-panel (below), a real overflow:auto box — the same
   bounded, internally-scrolling table box Personnel Welfare's staff
   listing uses — and sticky inside a genuine scroll container is the
   case every mobile browser (Android included) handles reliably. An
   earlier version pinned the column against whole-PAGE horizontal
   scroll instead, which is exactly what failed on Android, and was
   replaced by a JS workaround that this box makes unnecessary. */
.freeze-school-col td:nth-child(3),
.freeze-student-col td:nth-child(2) {
  position: -webkit-sticky;
  position: sticky;
  left: 0;
  z-index: 3;
  background: #fff;
  box-shadow: 2px 0 4px rgba(0, 0, 0, 0.08);
}
/* The header-row cell of that column (the top-left "corner") already
   gets position:sticky from .sticky-header-table thead th, so only left
   is added here — setting position again would override it. Higher
   z-index than data cells so it stays on top while scrolling both ways. */
.freeze-school-col thead th:nth-child(3),
.freeze-student-col thead th:nth-child(2) {
  left: 0;
  z-index: 6;
  background: var(--navy);
  box-shadow: 2px 0 4px rgba(0, 0, 0, 0.08);
}

/* A flagged duplicate row (registrations.js gives its <tr> a pink inline
   background) would otherwise show a plain white frozen Student cell,
   since the frozen cell needs its own solid background. */
.freeze-student-col tr[style*="FBEAEA"] td:nth-child(2) {
  background: #FBEAEA;
}

/* ---- Table in its own scrolling box, computer and phone alike ----
   The table scrolls inside a bordered box (both ways) while the page,
   the pinned tabs and the card title stay put — the same behavior as
   Personnel Welfare's staff listing. Header row sticks to the top of
   the BOX (top:0), overriding the page-level offset
   updateStickyOffsets() writes inline on each header cell. */
.local-scroll-panel {
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  max-height: 70vh;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.local-scroll-panel table.report thead th {
  top: 0 !important;
}

