/* ==========================================================================
   GoSchool — Finance Web Application Stylesheet
   Modern, glassmorphic UI design system with dynamic CSS variables
   ========================================================================== */

:root {
  /* Default Branding Colors (Overridden dynamically by applyTheme) */
  --color-primary: #1e40af;
  --color-secondary: #3b82f6;
  --color-primary-rgb: 30, 64, 175;
  --color-secondary-rgb: 59, 130, 246;

  /* UI Color Tokens */
  --bg-dark: #0f172a;
  --bg-surface: #1e293b;
  --card-bg: rgba(30, 41, 59, 0.7);
  --card-border: rgba(255, 255, 255, 0.1);
  /* ── The text ladder ──────────────────────────────────────────────────────
     Three steps, and every one of them now passes WCAG AA for body text
     (4.5:1) against the card, the page and the inside of an input. The
     bottom step used to be #64748b at 3.27:1, which is below the line for
     anything that has to be read — and it was the colour of every hint,
     placeholder and empty-state sentence in the app.

     The ladder moved up a step rather than being flattened: muted is what
     secondary used to be, and secondary is lighter again, so the hierarchy
     a reader relies on survives (1.73:1 between the two, which the eye
     separates comfortably without either one being faint). */
  --text-primary: #f8fafc;    /* 14.9:1 on a card */
  --text-secondary: #cbd5e1;  /* 10.5:1 — labels, which is where the report started */
  --text-muted: #94a3b8;      /*  6.1:1 — hints, placeholders, empty states */

  /* ── Short names the inline styles in app.js use ──────────────────────────
     Used 56 times between them here and defined nowhere, and an undefined
     custom property makes the whole declaration invalid — so the browser
     dropped every one. Panels rendered with no background and bordered boxes
     with no border. Aliases rather than copies, so a school's own primary
     colour set by applyTheme() still reaches everything that names it. */
  --bg-surface-subtle: rgba(255, 255, 255, 0.05);
  --border-color: var(--card-border);

  /* ── The edge of something you have to find ───────────────────────────────
     A text field was drawn with --card-border, 10% white, which measures
     1.36:1 against the card behind it. WCAG wants 3:1 for the boundary of a
     control, and the gap between those two numbers is the whole of the
     report from the school: the fields were not hard to read, they were hard
     to *locate*. 34% white measures 3.04:1.

     Kept separate from --card-border on purpose. A card's edge is decoration
     and has no threshold to meet; drawing every panel this strongly would
     turn the page into a grid of boxes. */
  --input-border: rgba(255, 255, 255, 0.34);
  --input-border-hover: rgba(255, 255, 255, 0.52);
  --input-bg: rgba(15, 23, 42, 0.6);

  /* ── A colour for text that links somewhere ───────────────────────────────
     --color-primary is whatever a school picked for its own branding, so
     nothing legible can depend on it; the default alone measures 1.78:1 as
     text on a dark card. This one is fixed and measures 6.1:1. */
  --link-color: #60a5fa;
  --danger: var(--color-danger);
  --primary: var(--color-primary);

  /* Semantic Colors */
  --color-success: #10b981;
  --color-success-bg: rgba(16, 185, 129, 0.15);
  --color-warning: #f59e0b;
  --color-warning-bg: rgba(245, 158, 11, 0.15);
  /* Lightened from #ef4444, which measured 4.14:1 as text — just under the
     line, on the one thing in the app that must always be read. The fill for
     a destructive *button* goes the other way, darker, because what needs
     contrast there is the white sitting on top of it. */
  --color-danger: #f87171;        /* 5.6:1 as text on a card */
  --color-danger-strong: #dc2626; /* as a fill: white on it is 4.8:1 */
  --color-danger-bg: rgba(248, 113, 113, 0.15);
  --color-info: #06b6d4;
  --color-info-bg: rgba(6, 182, 212, 0.15);

  /* Typography */
  --font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  
  /* Layout Geometry */
  --sidebar-width: 260px;
  --header-height: 70px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 20px;

  /* Elevation & Shadows */
  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.4);
  --glass-glow: 0 0 25px rgba(var(--color-secondary-rgb), 0.15);

  /* Animations */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reset & Base Setup */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  background-color: var(--bg-dark);
  color: var(--text-primary);
  min-height: 100vh;
  overflow-x: hidden;
  line-height: 1.5;
  background-image: 
    radial-gradient(circle at 10% 20%, rgba(var(--color-primary-rgb), 0.18) 0%, transparent 40%),
    radial-gradient(circle at 90% 80%, rgba(var(--color-secondary-rgb), 0.15) 0%, transparent 40%);
  background-attachment: fixed;
}

/* Typography Utilities */
h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.02em;
}

/* Pre-login Auth Screen */
.auth-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 20px;
}

.auth-card {
  width: 100%;
  max-width: 440px;
  background: var(--card-bg);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  padding: 40px;
  box-shadow: var(--shadow-lg), var(--glass-glow);
  animation: fadeIn 0.4s ease-out;
}

.auth-header {
  text-align: center;
  margin-bottom: 32px;
}

.auth-logo {
  width: 56px;
  height: 56px;
  border-radius: 14px;
  background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 28px;
  margin-bottom: 16px;
  box-shadow: 0 8px 16px rgba(var(--color-primary-rgb), 0.4);
}

.auth-title {
  font-size: 24px;
  margin-bottom: 6px;
}

.auth-subtitle {
  color: var(--text-secondary);
  font-size: 14px;
}

/* Forms */
.form-group {
  margin-bottom: 20px;
}

.form-label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.form-input, .form-select, .form-textarea {
  width: 100%;
  padding: 12px 16px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 15px;
  transition: var(--transition-fast);
}

.form-input:focus, .form-select:focus, .form-textarea:focus {
  outline: none;
  border-color: var(--color-secondary);
  box-shadow: 0 0 0 3px rgba(var(--color-secondary-rgb), 0.25);
  background: rgba(15, 23, 42, 0.8);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  border: none;
  transition: var(--transition-fast);
  text-decoration: none;
}
/* A button that cannot be pressed has to look it (owner, 2026-10-28): with no
   rule here, a disabled button looked exactly like a live one, so a sign-in in
   progress read as a box nobody had touched. */
.btn:disabled,
.btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
  filter: saturate(0.6);
}
.btn:disabled:hover,
.btn[disabled]:hover { transform: none; }

.btn-primary {
  background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
  color: white;
  box-shadow: 0 4px 12px rgba(var(--color-primary-rgb), 0.3);
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(var(--color-primary-rgb), 0.4);
  filter: brightness(1.08);
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-primary);
  border: 1px solid var(--card-border);
}

.btn-secondary:hover {
  background: rgba(255, 255, 255, 0.14);
}

.btn-danger {
  background: var(--color-danger-strong);
  color: white;
}

.btn-danger:hover {
  filter: brightness(1.1);
}

.btn-sm {
  padding: 6px 12px;
  font-size: 13px;
  border-radius: 6px;
}

.btn-block {
  width: 100%;
}

/* Layout Shell */
.app-layout {
  display: flex;
  min-height: 100vh;
}

/* Sidebar */
.sidebar {
  width: var(--sidebar-width);
  background: rgba(15, 23, 42, 0.85);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-right: 1px solid var(--card-border);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 100;
  transition: var(--transition-normal);
}

.sidebar-brand {
  height: var(--header-height);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 24px;
  border-bottom: 1px solid var(--card-border);
}

.brand-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 20px;
  font-weight: bold;
}

.brand-title {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.2;
}

.brand-subtitle {
  font-size: 11px;
  color: var(--color-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.nav-menu {
  list-style: none;
  padding: 20px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  /* The sidebar is fixed to the window height and cannot scroll itself, so
     when the menu and the account controls together are taller than the
     window, something has to give. It must not be the footer: Sign out and
     Change password live there, and at 520px they sat below the edge with no
     way to reach them. The menu scrolls instead. overflow-y also zeroes this
     flex item's automatic minimum height, which is what lets it shrink at
     all. Checked by tools/check-modal-scroll.php. */
  overflow-y: auto;
  scrollbar-width: thin;
}

.nav-item a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  color: var(--text-secondary);
  text-decoration: none;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
  transition: var(--transition-fast);
}

.nav-item a:hover {
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.05);
}

.nav-item.active a {
  color: white;
  background: linear-gradient(90deg, rgba(var(--color-primary-rgb), 0.5), rgba(var(--color-secondary-rgb), 0.2));
  border-left: 3px solid var(--color-secondary);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.nav-icon {
  font-size: 18px;
  width: 22px;
  text-align: center;
}

.sidebar-footer {
  padding: 16px;
  border-top: 1px solid var(--card-border);
}

.user-badge {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: var(--radius-sm);
}

.user-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: white;
}

.user-info {
  flex: 1;
  overflow: hidden;
}

.user-name {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.user-role {
  font-size: 11px;
  color: var(--text-muted);
}

/* Account actions under the user badge. Stacked full-width so the labels
   fit the 260px sidebar without wrapping mid-word. */
.sidebar-account-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
}

.sidebar-account-actions .btn {
  width: 100%;
  justify-content: center;
}

/* Main Content Area */
.main-wrapper {
  margin-left: var(--sidebar-width);
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.header {
  height: var(--header-height);
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--card-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 32px;
  position: sticky;
  top: 0;
  z-index: 90;
}

.page-title {
  font-size: 20px;
  font-weight: 700;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.badge-success { background: var(--color-success-bg); color: var(--color-success); border: 1px solid rgba(16, 185, 129, 0.3); }
.badge-warning { background: var(--color-warning-bg); color: var(--color-warning); border: 1px solid rgba(245, 158, 11, 0.3); }
.badge-danger  { background: var(--color-danger-bg); color: var(--color-danger); border: 1px solid rgba(239, 68, 68, 0.3); }
.badge-info    { background: var(--color-info-bg); color: var(--color-info); border: 1px solid rgba(6, 182, 212, 0.3); }

.content-area {
  padding: 32px;
  flex: 1;
}

/* Dashboard Cards Grid */
/* Seven tiles now, not five, and they were each big enough for a headline.
   Tightened so the whole row is one glance rather than a scroll: a summary you
   have to scroll past is one people stop reading. Narrower columns also mean
   more fit per row before wrapping, which is what was pushing the recent
   activity table below the fold. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}

.stat-card {
  background: var(--card-bg);
  backdrop-filter: blur(12px);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  box-shadow: var(--shadow-sm);
  transition: var(--transition-fast);
  position: relative;
  overflow: hidden;
}

.stat-card:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 255, 255, 0.2);
  box-shadow: var(--shadow-md);
}

.stat-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 4px;
  height: 100%;
  background: var(--color-secondary);
}

.stat-card.primary::before { background: var(--color-primary); }
.stat-card.success::before { background: var(--color-success); }
.stat-card.warning::before { background: var(--color-warning); }
.stat-card.danger::before  { background: var(--color-danger); }

.stat-title {
  font-size: 11.5px;
  color: var(--text-secondary);
  font-weight: 500;
  margin-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.stat-value {
  font-size: 19px;
  font-weight: 700;
  color: var(--text-primary);
  /* A long naira figure has to shrink rather than spill out of a narrower tile. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Section Cards & Tables */
.card {
  background: var(--card-bg);
  backdrop-filter: blur(12px);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  margin-bottom: 24px;
  overflow: hidden;
}

.card-header {
  padding: 20px 24px;
  border-bottom: 1px solid var(--card-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.card-title {
  font-size: 16px;
  font-weight: 600;
}

.card-body {
  padding: 24px;
}

/* Data Table */
.table-responsive {
  overflow-x: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
  font-size: 14px;
}

.table th {
  padding: 14px 20px;
  background: rgba(15, 23, 42, 0.4);
  color: var(--text-secondary);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--card-border);
}

/* ── A column you can sort by ───────────────────────────────────────
   The control sits in the header of the column it sorts. Anywhere else — a
   row of buttons above the table, a dropdown of column names — and the reader
   has to work out which control goes with which column, which is work the
   header does for free by being in the right place.

   Search already answered "is this one here". These answer "who owes the
   most", "who joined last", "who has not paid since July" — questions about a
   group, which no amount of searching will answer. */
.table th.sortable {
  cursor: pointer;
  user-select: none;
  position: relative;
  padding-right: 32px;   /* the arrow's room, reserved, so the label never
                            shifts sideways when a column becomes sorted */
  white-space: nowrap;
}

.table th.sortable:hover {
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.07);
}

/* Keyboard focus needs its own mark. A hover colour is not a focus ring, and
   somebody tabbing through the headers has no pointer to follow. */
.table th.sortable:focus-visible {
  outline: none;
  color: var(--text-primary);
  box-shadow: inset 0 0 0 2px var(--link-color);
}

/* Unsorted: a dim pair of arrows, which is the only thing telling a reader
   that the header is a control and not just a label. */
.table th.sortable::after {
  content: '⇅';
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 12px;
  font-weight: 400;
  /* 3.34:1 against the composited header background, measured in a browser.
     Not decoration: this glyph is the only thing telling a reader the header
     is a control at all, so it has to meet the 3:1 this file already holds
     every control's boundary to — the school's report was that things were
     hard to *locate*, not hard to read. 0.4 measured 2.92:1. */
  opacity: 0.45;
  transition: opacity 120ms ease;
}

.table th.sortable:hover::after,
.table th.sortable:focus-visible::after { opacity: 0.85; }

/* Sorted: one arrow, pointing the way the column now reads.
   --link-color rather than --color-primary: the primary is whatever colour a
   school picked for its branding and measures as little as 1.78:1 here, so
   nothing a reader has to see can be drawn in it. This one is fixed at
   6.1:1 — the same reasoning as every other legible accent in this file. */
.table th.sortable.is-sorted {
  color: var(--text-primary);
  background: rgba(96, 165, 250, 0.12);
}

.table th.sortable.is-sorted::after {
  content: '↑';
  opacity: 1;
  font-size: 14px;
  font-weight: 700;
  color: var(--link-color);
}

.table th.sortable.is-sorted.is-desc::after { content: '↓'; }

.table td {
  padding: 16px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  color: var(--text-primary);
}

.table tr:hover td {
  background: rgba(255, 255, 255, 0.02);
}

/* Tabs & Filters */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.filter-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.tabs {
  display: flex;
  gap: 4px;
  background: rgba(15, 23, 42, 0.6);
  padding: 4px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--card-border);
}

.tab {
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: var(--transition-fast);
}

.tab.active {
  color: white;
  background: var(--color-primary);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}

/* Modals */
.modal-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(15, 23, 42, 0.8);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  padding: 24px;
  overflow-y: auto;
}

.modal-backdrop.show {
  opacity: 1;
  pointer-events: auto;
}

.modal {
  width: 100%;
  max-width: 580px;
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  transform: translateY(20px);
  transition: transform 0.2s ease;
  margin: auto;
}

.modal-backdrop.show .modal {
  transform: translateY(0);
}

/* For a modal that is a table of fields rather than a form: filling in twenty
   pupils' debt inside 580px means typing into a horizontal scrollbar. Bounded
   by the viewport too, so a narrow laptop still sees the whole width. */
.modal.modal-wide {
  max-width: min(1040px, 96vw);
}

/* The entry grid. Cells are deliberately tight — a row has to read as a row,
   and the inputs carry their own borders inside the table already. */
.grid-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.grid-table th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-weight: 600;
  padding: 6px;
  white-space: nowrap;
  border-bottom: 1px solid var(--card-border);
  position: sticky;
  top: 0;
  background: var(--bg-surface);
  z-index: 1;
}

.grid-table td {
  padding: 3px 4px;
  vertical-align: middle;
}

.grid-table .form-input,
.grid-table .form-select {
  padding: 6px 8px;
  font-size: 13px;
  min-width: 0;
  width: 100%;
}

/* A cell the pre-check flagged. Not colour alone — the reason is printed above
   the grid too, because a red outline says something is wrong and never what. */
.grid-table .cell-flagged {
  border-color: var(--danger, #c0392b);
  box-shadow: 0 0 0 1px var(--danger, #c0392b) inset;
}

/* Half the subtle surface: a stripe that separates rows without turning every
   other one into a panel. */
.grid-table tbody tr:nth-child(even) {
  background: rgba(255, 255, 255, 0.025);
}

.modal-header {
  padding: 18px 24px;
  border-bottom: 1px solid var(--card-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}

.modal-close {
  background: transparent;
  border: none;
  color: var(--text-secondary);
  font-size: 24px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.modal-body {
  padding: 24px;
  overflow-y: auto;
  flex: 1 1 auto;
  -webkit-overflow-scrolling: touch;
}

.modal-footer {
  padding: 16px 24px;
  background: rgba(15, 23, 42, 0.4);
  border-top: 1px solid var(--card-border);
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  flex-shrink: 0;
}

/* Toast Notifications */
.toast-container {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.toast {
  padding: 14px 20px;
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: white;
  border: 1px solid var(--card-border);
  box-shadow: var(--shadow-md);
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  animation: slideInRight 0.3s ease;
}

.toast-success { border-left: 4px solid var(--color-success); }
.toast-error   { border-left: 4px solid var(--color-danger); }
.toast-warning { border-left: 4px solid var(--color-warning); }

/* ── Something went wrong: top and centre, for five seconds ──────────────────
 * Same as Admin's, and for the same reason: an error in the corner beside
 * "Saved", gone in four seconds, is an error nobody sees. See Admin's
 * styles.css for the account of how that was found.
 */
.toast-alerts {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1450; /* above dialogs (1000) and questions (1400) */
  width: min(640px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: 10px;
  pointer-events: none;
}

.toast-alert {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border-radius: var(--radius-md, 12px);
  background: #7f1d1d;
  border: 2px solid #fca5a5;
  color: #fff;
  font-size: 15.5px;
  line-height: 1.5;
  box-shadow: 0 18px 48px rgba(2, 6, 23, 0.6);
  animation: alertDrop 0.25s ease;
}

.toast-alert-icon { font-size: 20px; line-height: 1.2; }
.toast-alert-text { flex: 1; }

.toast-alert-close {
  flex-shrink: 0;
  border: 1px solid rgba(255, 255, 255, 0.55);
  background: transparent;
  color: #fff;
  border-radius: var(--radius-sm, 8px);
  padding: 4px 12px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
}

.toast-alert-close:hover { background: rgba(255, 255, 255, 0.12); }

@keyframes alertDrop {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.signin-again-backdrop { z-index: 1500; }
/* While it is open the red notices say only what it already says, and on a
   narrow screen they cover its heading. They come back if it is dismissed. */
body:has(.signin-again-backdrop) .toast-alerts { display: none; }
.signin-again-who {
  margin: 0 0 4px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary, #f8fafc);
}

/* Keyframe Animations */
@keyframes fadeIn {
  from { opacity: 0; transform: scale(0.98); }
  to { opacity: 1; transform: scale(1); }
}

@keyframes slideInRight {
  from { opacity: 0; transform: translateX(30px); }
  to { opacity: 1; transform: translateX(0); }
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .sidebar {
    transform: translateX(-100%);
  }
  .sidebar.open {
    transform: translateX(0);
  }
  .main-wrapper {
    margin-left: 0;
  }
  .header {
    padding: 0 16px;
  }
  .content-area {
    padding: 16px;
  }
}

/* ==========================================================================
   Searchable Student Combobox
   ========================================================================== */
.student-combobox-wrapper {
  position: relative;
  width: 100%;
}

.student-combobox-input-wrap {
  display: flex;
  align-items: center;
  position: relative;
}

.student-combobox-input {
  width: 100%;
  padding: 12px 36px 12px 14px;
  background: var(--bg-dark);
  /* A field edge, not a panel edge: --card-border measured 1.55:1 here (audit 2026-10-25). */
  border: 1px solid var(--input-border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.student-combobox-input::placeholder { color: var(--text-muted); opacity: 1; }

.student-combobox-input:focus {
  border-color: var(--color-secondary);
  box-shadow: 0 0 0 3px rgba(var(--color-secondary-rgb), 0.2);
}

.student-combobox-clear-btn {
  position: absolute;
  right: 10px;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 14px;
  padding: 4px;
  border-radius: 4px;
  display: none;
}

.student-combobox-clear-btn:hover {
  color: var(--text-primary);
}

.student-selected-preview {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: rgba(var(--color-primary-rgb), 0.15);
  border: 1px solid rgba(var(--color-primary-rgb), 0.4);
  border-radius: var(--radius-sm);
  margin-top: 8px;
}

.student-combobox-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  background: #1e293b;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  z-index: 1200;
  display: none;
  list-style: none;
  padding: 4px 0;
}

.student-combobox-dropdown.open {
  display: block;
}

.student-combobox-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  transition: background var(--transition-fast);
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}

.student-combobox-item:last-child {
  border-bottom: none;
}

.student-combobox-item:hover,
.student-combobox-item.active {
  background: rgba(var(--color-secondary-rgb), 0.2);
}

.student-avatar-slot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
  color: #fff;
  font-weight: 600;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: var(--shadow-sm);
}

.student-item-details {
  flex: 1;
  min-width: 0;
}

.student-item-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.student-item-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
}

/* The children a family search result names. The child the search words
   matched is bold and listed first, so the right family is recognisable
   without opening it. */
.student-item-children {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 2px;
  white-space: normal;
  line-height: 1.4;
}

.student-item-children strong {
  color: var(--text-primary);
}

.student-item-class-badge {
  background: rgba(255, 255, 255, 0.1);
  padding: 2px 6px;
  border-radius: 4px;
  font-weight: 500;
  color: #93c5fd;
}

.student-item-admission {
  color: var(--text-muted);
  font-size: 11px;
}

.student-combobox-empty {
  padding: 16px;
  text-align: center;
  color: var(--text-muted);
  font-size: 13px;
}


/* Receipt printing deliberately has NO styles here.
   The receipt is printed from an isolated iframe (printReceiptDocument() in
   app.js), which carries its own self-contained stylesheet.

   The previous approach — hiding the app with `body * { visibility: hidden }`
   and promoting the slip with `position: absolute` — printed only the
   receipt's header and nothing else. Three separate things in the modal CSS
   above conspire to clip it: `.modal` sets `overflow: hidden` with a
   `max-height`, `.modal-body` is its own scroll container, and both `.modal`
   (transform) and `.modal-backdrop` (backdrop-filter) establish containing
   blocks — so the promoted element was positioned inside a box one viewport
   tall and cropped to it. `position: fixed` would have been trapped by those
   containing blocks too, and `visibility` never escapes ancestor clipping.
   Printing a copy in a document with no such ancestors avoids all of it, and
   paginates properly across pages. */

/* ── Waiting ─────────────────────────────────────────────────────────────────
   The same spinner Admin uses, mirrored here rather than shared: each app
   carries its own stylesheet, and a 20-line primitive is cheaper duplicated
   than it is to introduce a shared CSS dependency between two no-build apps.

   The API runs on a free tier that cold-starts, so a few seconds of waiting is
   the normal case. A control that gives no sign of having registered a click
   reads as broken — a user pressed "New Student" in Admin repeatedly for
   exactly that reason, which is the correct response to a dead button. */
.inline-spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: inline-spin 0.7s linear infinite;
  vertical-align: -2px;
  flex-shrink: 0;
}

@keyframes inline-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .inline-spinner { animation-duration: 2s; }
}

.saving-note {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
  font-size: 11px;
  color: var(--text-muted);
  text-transform: none;
}

/* Author display rules beat the hidden attribute, so restate it — otherwise
   the note would show permanently. */
.saving-note[hidden] {
  display: none;
}

/* ── An error somebody has to act on ─────────────────────────────────────────
 *
 * The old inline error was a subtle surface with a 3px bar: a school
 * administrator and the school's owner stood in front of the screen together
 * and neither of them saw it. On a dark theme `--bg-surface-subtle` against
 * the card behind it is almost no contrast at all, and 13px grey text below a
 * tall grid reads as a footnote.
 *
 * So an error now looks like one — a filled danger panel, a solid bar, a
 * marker, and text at body size rather than caption size. `showInlineError()`
 * also scrolls it into view, because contrast cannot help with something that
 * is off screen.
 *
 * Deliberately not a toast: these list several things to fix and stay on
 * screen while they are being fixed. Toasts are for what just happened.
 */
.form-error-box {
  display: none;
  margin: 12px 0;
  padding: 12px 14px 12px 42px;
  position: relative;
  border-radius: var(--radius-sm);
  background: var(--color-danger-bg, rgba(239, 68, 68, 0.15));
  border: 1px solid var(--color-danger, #ef4444);
  border-left-width: 5px;
  color: var(--text-primary, inherit);
  font-size: 14px;
  line-height: 1.45;
}

/* The marker is content, not decoration — it survives a copy-paste of the
   message into an email asking for help, and it reads to a screen reader as
   part of the alert. */
.form-error-box::before {
  content: "!";
  position: absolute;
  left: 13px;
  top: 12px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-danger, #ef4444);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

.form-error-box strong { color: inherit; }

/* Shown by script, so the class alone must not reveal it. */
.form-error-box.is-shown { display: block; }

/* ── A panel held still while something happens to it ────────────────────────
 *
 * See beginBlockingWait() in @school-suite/core. Reported from a sign-in on a
 * poor connection: the button said "Signing in…" and nothing else changed, so
 * the form was still typeable, Enter still resubmitted it, and a motionless
 * label was indistinguishable from a frozen page. The user pressed it again.
 *
 * The cover is the part that does the work. Disabling the controls underneath
 * matters for a keyboard and a screen reader; for everybody else, not being
 * able to reach the form is the whole message.
 */
.blocking-wait {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  border-radius: inherit;
  /* Translucent rather than opaque: the form stays visible behind it, so it
     reads as "this is busy" rather than "this has been replaced". */
  background: rgba(10, 14, 20, 0.72);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: blocking-wait-in 0.18s ease;
  cursor: progress;
}

/* No fade for anybody who has asked for less motion — it still appears, it
   just does not animate in. */
@media (prefers-reduced-motion: reduce) {
  .blocking-wait { animation: none; }
}

@keyframes blocking-wait-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.blocking-wait-inner {
  text-align: center;
  max-width: 300px;
}

.blocking-wait-spinner {
  width: 30px;
  height: 30px;
  margin: 0 auto 14px;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--color-primary, #3b82f6);
  border-radius: 50%;
  animation: blocking-wait-spin 0.8s linear infinite;
}

/* Slowed rather than stopped: a spinner that does not move is the thing this
   replaced, so it keeps turning even here — just gently. */
@media (prefers-reduced-motion: reduce) {
  .blocking-wait-spinner { animation-duration: 2.4s; }
}

@keyframes blocking-wait-spin {
  to { transform: rotate(360deg); }
}

.blocking-wait-message {
  margin: 0;
  color: #fff;
  font-size: 15px;
  font-weight: 600;
}

.blocking-wait-slow {
  margin: 8px 0 0;
  color: rgba(255, 255, 255, 0.75);
  font-size: 13px;
  line-height: 1.45;
}

.blocking-wait-cancel {
  margin-top: 16px;
  padding: 7px 16px;
  border-radius: var(--radius-sm, 6px);
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: transparent;
  color: #fff;
  font-size: 13px;
  cursor: pointer;
}

.blocking-wait-cancel:hover {
  background: rgba(255, 255, 255, 0.12);
}

/* ── Things waiting to be dealt with ─────────────────────────────────────────
 *
 * The same arrangement as Admin's Overview, drawn by the same code in core
 * (createAttentionBoard). One strip per thing waiting, each with its own
 * action and its own ✕.
 *
 * The ✕ hides the strip and nothing else: the dot on the sidebar stays until
 * the matter is actually dealt with, so putting aside is "not now" rather
 * than "done".
 *
 * The rules are duplicated from the Admin app rather than shared, as every
 * other component here is — the two apps have separate stylesheets and only
 * their JavaScript comes from one place. Kept byte-identical so a change to
 * one is a visible diff against the other.
 */
.attention-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 14px;
  margin-bottom: 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-surface-subtle);
  border-left: 3px solid var(--color-warning, #f59e0b);
  font-size: 15px;
  line-height: 1.45;
}

.attention-strip:last-child { margin-bottom: 0; }

/* Nothing waiting, or everything put aside. No bar, no urgency — it is a
   statement rather than a request. */
.attention-strip-quiet {
  border-left-color: var(--border-color, rgba(255, 255, 255, 0.12));
  color: var(--text-muted);
  font-size: 14px;
}

.attention-strip-text { flex: 1 1 320px; min-width: 0; }

.attention-strip-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* Deliberately quiet next to the action beside it. Putting something aside
   should never be the easier of the two things to do. */
.attention-dismiss {
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.attention-dismiss:hover {
  background: var(--bg-surface-subtle);
  color: var(--text-primary, #fff);
}

/* The "Show again" in the everything-put-aside line. Underlined rather than
   coloured alone: the text around it is already muted, and a difference of
   shade is exactly the contrast problem reported from the school. */
.cell-link {
  display: inline-block;
  margin-top: 3px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--link-color, #60a5fa);
  font-size: 11.5px;
  text-decoration: underline;
  cursor: pointer;
  white-space: nowrap;
}

.cell-link:hover { color: var(--text-primary, #fff); }

/* ── The dot on a sidebar section ────────────────────────────────────────────
 *
 * Set from everything waiting, including what has been put aside on the
 * dashboard — a dot that disappeared with its strip would make "not now"
 * indistinguishable from "done", which is the one thing this must not do.
 */
.nav-attention {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-left: 7px;
  border-radius: 50%;
  background: var(--color-warning, #f59e0b);
  /* A ring, so it reads as a marker rather than as part of the label on a
     busy background. */
  box-shadow: 0 0 0 2px var(--bg-sidebar, rgba(0, 0, 0, 0.3));
  vertical-align: middle;
  flex-shrink: 0;
}

.nav-attention[hidden] { display: none; }

/* ── Asking the user something ───────────────────────────────────────────────
 *
 * Replaces the browser's own confirm/prompt/alert (createDialogs() in
 * @school-suite/core). A native dialog titles itself with the *site* — a
 * question about rejecting four pupils arrived as "goschool.techeous.com
 * says", which reads like a warning from somewhere else — cannot show which
 * button is the dangerous one, and freezes the tab including whatever
 * progress the user was watching.
 *
 * Above .modal-backdrop (z-index 1000) on purpose: most of these are asked
 * from inside an open dialog.
 */
.ask-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(2, 6, 23, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.ask {
  width: 100%;
  max-width: 440px;
  padding: 24px;
  border-radius: var(--radius-md, 12px);
  background: var(--bg-surface, #1e293b);
  border: 1px solid var(--card-border, rgba(255, 255, 255, 0.1));
  /* Lifted well clear of whatever it was opened over, so it reads as the
     thing being answered rather than as part of the page behind it. */
  box-shadow: 0 24px 64px rgba(2, 6, 23, 0.65);
}

/* A destructive question is marked before it is read, not after. */
.ask-danger { border-top: 3px solid var(--color-danger, #ef4444); }

.ask-title {
  margin: 0 0 10px;
  font-size: 17px;
  font-weight: 650;
  line-height: 1.35;
  color: var(--text-primary, #f8fafc);
}

.ask-message {
  margin: 0 0 10px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-secondary, #94a3b8);
}

/* The consequence — what somebody needs *after* deciding they want the thing.
   Set apart rather than run on, and never the first thing read. */
.ask-detail {
  margin: 0 0 4px;
  padding: 10px 12px;
  border-radius: var(--radius-sm, 8px);
  background: var(--bg-surface-subtle, rgba(255, 255, 255, 0.05));
  border-left: 3px solid var(--color-warning, #f59e0b);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-secondary, #94a3b8);
  /* A detail may be a list — every bill a payment is being recorded against,
     one per line. `pre-line` honours those breaks while still collapsing
     ordinary runs of spaces, so a one-line detail is unaffected. */
  white-space: pre-line;
}

.ask-field-label {
  display: block;
  margin: 14px 0 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary, #f8fafc);
}

.ask-field {
  width: 100%;
  padding: 11px 13px;
  border-radius: var(--radius-sm, 8px);
  border: 1px solid var(--input-border, rgba(255, 255, 255, 0.18));
  background: var(--input-bg, rgba(15, 23, 42, 0.6));
  color: var(--text-primary, #f8fafc);
  font-family: inherit;
  font-size: 14.5px;
}

.ask-field:focus {
  outline: none;
  border-color: var(--color-primary, #3b82f6);
  box-shadow: 0 0 0 3px rgba(var(--color-secondary-rgb, 59, 130, 246), 0.28);
}

/* Said on the field. A dialog that answers a dialog is where people start
   clicking blindly. */
.ask-error {
  margin: 8px 0 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-danger, #ef4444);
}

.ask-error[hidden] { display: none; }

.ask-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 20px;
  flex-wrap: wrap;
}

.ask-actions .btn { min-width: 104px; justify-content: center; }

/* ── Pointing at a field ─────────────────────────────────────────────────────
 * Part of the same report: a box you cannot see is one you find by waving the
 * pointer around. The border lifts to 52% white under the pointer, which is
 * well clear of the 3:1 the resting state already meets.
 */
.form-input:hover, .form-select:hover, .form-textarea:hover,
.ask-field:hover {
  border-color: var(--input-border-hover);
}

/* A placeholder is a hint, not content — the muted step, which now passes
 * 4.5:1 rather than the 3.3:1 it did when it was #64748b. Spelled out because
 * browsers do not inherit a colour into it. */
.form-input::placeholder,
.form-textarea::placeholder,
.ask-field::placeholder {
  color: var(--text-muted);
  opacity: 1;   /* Firefox dims placeholders by default, on top of the colour */
}

/* ── When a list will not load ───────────────────────────────────────────────
 *
 * Every view could already say a load had failed. None offered to try again,
 * so the only way out of a dropped request was reloading the whole page —
 * which on a school's connection means refetching everything and losing
 * whatever was half typed into a form on the way.
 *
 * Centred in the space the rows would have filled, so it reads as "the list
 * is not here" rather than as one odd row among real ones.
 */
.load-failure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 26px 16px;
  text-align: center;
}

.load-failure-text {
  margin: 0;
  max-width: 48ch;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-primary);
}

/* Why trying again is not on offer. Quieter than the sentence above it,
   because it is the explanation rather than the news. */
.load-failure-why {
  margin: 0;
  max-width: 48ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-muted);
}

.load-failure-action { min-width: 120px; justify-content: center; }

/* ── Large type for weighty decisions (owner, 2026-10-28) ───────────────────
   Confirming or turning away pupils, and similar owner decisions, are made by
   somebody who may be reading with glasses. `ask-large` (createDialogs'
   `large`) makes the question bigger and its words full-contrast; `page-wait`
   (beginPageWait) covers the whole page in large type while it is carried out. */
.ask.ask-large { max-width: 560px; padding: 30px; }
.ask-large .ask-title { font-size: 23px; }
.ask-large .ask-message { font-size: 18px; line-height: 1.55; color: var(--text-primary, #f8fafc); }
.ask-large .ask-detail { font-size: 17px; color: var(--text-primary, #f8fafc); white-space: pre-line; }
.ask-large .ask-actions .btn { font-size: 17px; padding: 13px 22px; }

.page-wait {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(2, 6, 23, 0.82);
  backdrop-filter: blur(3px);
}
.page-wait-box {
  max-width: 520px;
  width: 100%;
  padding: 34px 30px;
  border-radius: var(--radius-md, 12px);
  background: var(--bg-surface, #1e293b);
  border: 2px solid var(--color-primary, #3b82f6);
  text-align: center;
  box-shadow: 0 24px 64px rgba(2, 6, 23, 0.65);
}
.page-wait-spinner {
  width: 48px;
  height: 48px;
  margin: 0 auto 18px;
  border: 5px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--color-primary, #3b82f6);
  border-radius: 50%;
  animation: page-wait-spin 0.9s linear infinite;
}
.page-wait-message { margin: 0 0 8px; font-size: 24px; font-weight: 650; line-height: 1.35; color: var(--text-primary, #f8fafc); }
.page-wait-note { margin: 0; font-size: 17px; color: var(--text-primary, #f8fafc); opacity: 0.85; }
@keyframes page-wait-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .page-wait-spinner { animation-duration: 2.4s; } }

/* ── Help: the button, and the guides it opens (owner, 2026-10-28) ──────────
   createHelp() in core. Large, plain and high-contrast on purpose: these are
   read by newcomers, some of them reading with glasses. */
.help-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 16px 7px 8px;
  border-radius: 999px;
  border: 2px solid var(--color-primary, #3b82f6);
  background: rgba(59, 130, 246, 0.16);
  color: var(--text-primary, #f8fafc);
  font-family: inherit;
  font-size: 16px;
  font-weight: 650;
  cursor: pointer;
}
.help-button:hover { background: rgba(59, 130, 246, 0.3); }
.help-button:focus-visible { outline: 3px solid var(--color-primary, #3b82f6); outline-offset: 2px; }
.help-button[hidden] { display: none; }
.help-button-icon {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--color-primary, #3b82f6);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 800;
}

.help-layer { position: fixed; inset: 0; z-index: 2500; }
.help-layer-dim { background: rgba(2, 6, 23, 0.74); }
.help-spotlight {
  position: fixed;
  border-radius: 12px;
  box-shadow: 0 0 0 4px var(--color-primary, #3b82f6), 0 0 0 9999px rgba(2, 6, 23, 0.74);
  pointer-events: none;
  transition: top 0.2s, left 0.2s, width 0.2s, height 0.2s;
}
.help-card {
  position: fixed;
  width: min(460px, calc(100vw - 24px));
  padding: 22px 24px;
  border-radius: 14px;
  background: var(--bg-surface, #1e293b);
  border: 2px solid var(--color-primary, #3b82f6);
  box-shadow: 0 24px 64px rgba(2, 6, 23, 0.7);
  color: var(--text-primary, #f8fafc);
}
.help-card-centred { top: 50% !important; left: 50% !important; transform: translate(-50%, -50%); }
.help-card-count { margin: 0 0 6px; font-size: 15px; color: var(--text-primary, #f8fafc); opacity: 0.85; }
.help-card-title { margin: 0 0 10px; font-size: 22px; line-height: 1.3; color: var(--text-primary, #f8fafc); }
.help-card-text { margin: 0 0 18px; font-size: 18px; line-height: 1.55; color: var(--text-primary, #f8fafc); }
.help-card-actions { display: flex; justify-content: space-between; gap: 12px; }
.help-card-actions .btn { font-size: 17px; padding: 12px 22px; }
.help-card-foot { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.help-card-link {
  padding: 4px 0;
  border: none;
  background: none;
  color: var(--text-primary, #f8fafc);
  font-family: inherit;
  font-size: 15px;
  text-decoration: underline;
  cursor: pointer;
}
@media (prefers-reduced-motion: reduce) { .help-spotlight { transition: none; } }
