/* Shared auth/profile header */
.auth-header {
  position: relative;
  z-index: 30;
  background: rgba(247, 249, 250, 0.9);
  backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid var(--border);
}
.auth-header .container {
  height: 76px;
  display: flex;
  align-items: center;
}
.profile-header-inner {
  justify-content: space-between;
  gap: 20px;
  /* MATCHES .profile-inner, and must keep matching it (2026-08-24).
     The header inherited .container's 1140px while the page below it widens
     to 1560px, so on a large screen the logo sat ~210px inboard of every card
     it was meant to sit above - a gap that reads as the logo being misplaced
     rather than as two different grids. Both values are duplicated here
     deliberately, including the breakpoint below: they are one measurement
     expressed twice, so changing one without the other reopens this. */
  max-width: min(92vw, 1240px);
}
.profile-header-inner .brand-left {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}
/* The logo and the child-selector chip used to float as two separate,
   loosely-related elements with a wide dead-space gap between them —
   this border reads as a divider connecting "brand" to "who you're
   viewing" into one identity cluster. Lives on .child-selector itself
   (not a separate element) so it naturally disappears whenever the
   selector is hidden (no children yet) instead of needing its own
   show/hide wiring. */
.profile-header-inner .child-selector {
  padding-left: 14px;
  border-left: 1px solid var(--border);
}
/* The logo read too small against the rest of this header — bumped up
   here specifically (not the shared .brand used by the marketing nav on
   index.html, which is sized for a different, more content-dense header). */
.auth-header .brand-icon { width: 46px; height: 46px; }
.auth-header .brand-word { font-size: 23px; }
.auth-header .brand-tagline { font-size: 11.5px; }
/* The profile chip and its dropdown MOVED TO style.css (2026-08-25).
   index.html grew the same header when it became session-aware, and that
   page loads style.css only - so the markup rendered as raw text: an email,
   four link labels running together, and a bare Sign out button.

   Moved rather than copied. Every page that has this chip - profile,
   dashboard, index - loads style.css, so one copy reaches all three and
   there is nothing to keep in sync. Fourth time this week that markup has
   met a stylesheet its page does not load. */

.student-id-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-family: 'Consolas', monospace;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--teal);
  background: rgba(15, 123, 138, 0.1);
  border: 1px solid rgba(15, 123, 138, 0.25);
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
}
.student-id-badge:hover { background: rgba(15, 123, 138, 0.18); }
.student-id-badge:active { transform: scale(0.98); }

.student-id-empty-hint {
  display: flex;
  width: 100%;
  align-items: center;
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--text-muted);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}
.student-id-create-link { color: var(--teal); font-weight: 600; text-decoration: underline; margin-left: 4px; }
.student-id-empty-hint:hover .student-id-create-link { color: var(--navy); }
.student-id-copy-icon { flex-shrink: 0; opacity: 0.75; }

/* Manage student logins modal */
.login-manage-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 0;
  border-bottom: 1px solid var(--border);
}
.login-manage-row:last-child { border-bottom: none; }
.login-manage-row .who { flex: 1; min-width: 0; }
.login-manage-row .who .name { font-weight: 700; font-size: 14.5px; color: var(--navy-dark); }
.login-manage-row .who .meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.login-manage-id {
  font-family: 'Consolas', monospace;
  font-size: 12.5px;
  color: var(--teal);
  white-space: nowrap;
}
.login-manage-none { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }
.login-manage-create {
  margin-top: 14px;
  padding: 16px 18px;
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.login-manage-id-readout {
  font-family: 'Consolas', monospace;
  font-size: 15px;
  font-weight: 700;
  color: var(--teal);
  background: #fff;
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  display: inline-block;
}
.link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--teal);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.link-btn:hover { color: var(--amber-dark); }

.link-btn-muted {
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link-btn-muted:hover { color: var(--text-dark); }

@media (max-width: 560px) {
  .auth-header .container { height: auto; padding-top: 14px; padding-bottom: 14px; }
  .profile-header-inner { flex-direction: column; align-items: flex-start; gap: 12px; }
  .brand-left { flex-wrap: wrap; gap: 10px; width: 100%; }
  .profile-account { width: 100%; justify-content: space-between; gap: 10px; }
  .profile-account-email { display: none; }
}

/* Sign-in page */
.auth-main {
  position: relative;
  overflow: hidden;
  min-height: calc(100vh - 76px);
  background: var(--bg-light);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 64px 24px;
}

.auth-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(560px 420px at 85% 12%, rgba(15, 123, 138, 0.10), transparent 60%),
    radial-gradient(480px 380px at 10% 90%, rgba(243, 113, 48, 0.10), transparent 60%),
    var(--bg-light);
}
.auth-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(40, 41, 114, 0.09) 1.4px, transparent 1.4px);
  background-size: 26px 26px;
  mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, black 40%, transparent 85%);
}

.auth-blob {
  position: absolute;
  border-radius: 50%;
  opacity: 0.55;
  animation: float 7s ease-in-out infinite;
  z-index: 0;
}
.auth-blob-1 {
  width: 240px; height: 240px;
  background: radial-gradient(circle at 30% 30%, #DDF2F5, #C7E9EE);
  top: 8%; right: 8%;
}
.auth-blob-2 {
  width: 200px; height: 200px;
  background: radial-gradient(circle at 30% 30%, #FDEBE0, #FBD9C6);
  bottom: 6%; left: 6%;
  animation-delay: -3s;
}

.auth-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 440px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 20px;
  box-shadow: var(--shadow-lg);
  padding: 40px 36px;
  text-align: center;
}
.auth-card-logo {
  width: 64px;
  height: 64px;
  margin: 0 auto 20px;
  display: block;
}
.auth-card h1 {
  font-size: 26px;
  color: var(--navy-dark);
  margin-bottom: 14px;
}
.auth-sub {
  color: var(--text-muted);
  font-size: 15px;
  margin-bottom: 28px;
}

.google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  background: #fff;
  border: 1px solid #dadce0;
  border-radius: 4px;
  font-family: 'Roboto', Arial, sans-serif;
  font-size: 14px;
  font-weight: 500;
  color: #3c4043;
  cursor: pointer;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.google-btn:hover { box-shadow: 0 1px 3px rgba(60, 64, 67, 0.3); border-color: #d2d5d9; }
.google-btn:active { background: #f8f9fa; }

.auth-demo-note {
  margin-top: 20px;
  padding: 10px 14px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  font-size: 12.5px;
  font-style: italic;
  color: var(--text-muted);
}

.auth-footnote {
  margin-top: 20px;
  font-size: 12px;
  color: var(--text-muted);
}

.admin-toggle-link {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: 20px;
  font-size: 13px;
}

.auth-back {
  position: relative;
  z-index: 1;
  display: block;
  font-size: 14px;
  color: var(--text-muted);
  text-align: center;
}
.auth-back:hover { color: var(--navy); }

/* Profile / dashboard page */
.profile-main {
  min-height: calc(100vh - 76px);
  background: var(--bg-light);
  padding: 56px 0 96px;
}
.profile-inner {
  max-width: min(92vw, 1240px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.profile-notice {
  padding: 16px 20px;
  border-radius: var(--radius);
  background: rgba(15, 123, 138, 0.08);
  border: 1px solid rgba(15, 123, 138, 0.25);
  color: var(--text-dark);
  font-size: 14px;
}
.profile-notice strong { color: var(--teal); }

.profile-section {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
}
.profile-section .section-title {
  text-align: left;
  margin: 0 0 6px;
  font-size: 22px;
  max-width: none;
}
.profile-section .section-sub {
  text-align: left;
  margin: 0 0 24px;
  max-width: none;
}

/* Subscriptions sidebar (2026-08-07, replacing the old Subscriptions tab) —
   a persistent rail next to the main content instead of a destination you
   have to click into, filling what used to be empty margin on wide
   screens. Sticky so it stays visible while the (often taller) main column
   scrolls; the "Buy more" catalog moved into a modal, since it's an
   occasional action, not something worth permanent screen space. */
/* ONE COLUMN (2026-08-25). Subscriptions was the last rail, and it is a tab
   now: a short list of plans and a buy action is something a parent visits
   deliberately a few times a year, not something worth 300px beside every
   report they read. The column is the page. */
.profile-columns {
  display: block;
}
/* The "full-page reading mode" this used to describe is gone: its rule was
   removed at some point and updateReportExpandedLayout() now only ever
   REMOVES the class, never adds it. The column is full width by default
   now, which is what that mode was for. */
.profile-main-col { min-width: 0; }

.subscriptions-sidebar-title {
  font-family: 'Poppins', sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0 0 14px;
}
/* The list and its rows are unchanged - they read well at any width. Only
   the two rules that existed to squeeze them into 300px are gone: the
   sticky rail, and a Buy button stretched to the full rail width. */
.subscriptions-buy-btn { margin-top: 14px; }
.subscriptions-sidebar-title { display: none; }
#tab-subscriptions .sidebar-sub-row { max-width: 620px; }

.sidebar-subs-empty { font-size: 13px; color: var(--text-muted); margin: 0 0 4px; }
.sidebar-sub-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-left: 3px solid var(--navy);
  background: var(--bg-light);
  border-radius: 0 8px 8px 0;
  padding: 9px 10px;
  margin-bottom: 8px;
}
.sidebar-sub-row-main { min-width: 0; }
.sidebar-sub-name { font-size: 12.5px; font-weight: 600; color: var(--navy-dark); margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-sub-meta { font-size: 11.5px; color: var(--text-muted); margin: 2px 0 0; }
.sidebar-sub-more { font-size: 11.5px; color: var(--text-muted); margin: 0 0 4px; }


/* Achievements moved into this sidebar (2026-08-11, direct request) — the
   section markup/render fn (js/badges.js) is unchanged and shared with
   student-dashboard.html's in-panel placement, so everything here just
   compacts it to the 300px rail: single-column badge tiles, a smaller
   title, and a divider separating it from the Subscriptions block above. */

.subscriptions-sidebar .achievements-header {
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}
.subscriptions-sidebar .achievements-header .section-title {
  font-size: 16px;
}
.subscriptions-sidebar .achievements-header .link-btn { font-size: 12.5px; }
/* The rail-compacting rules above are dead: #overview-achievements moved
   into the main column on 2026-08-25 because a badge tile could not do its
   job in 234px - icon, title, a sentence and a Share button, with the
   sentence breaking over three lines. It gets the full column now and the
   tiles sit four across. */
#overview-achievements .badge-row {
  display: grid;
  /* auto-FILL, not auto-fit. auto-fit collapses the empty tracks and lets
     the survivors share the row, so a child with two badges got two 675px
     cards - a tile built for an icon, a title and a sentence, stretched to
     twice the width of the four-badge case. auto-fill keeps the tracks, so
     a badge is the same size whether you have two or ten. */
  /* 300, not 250: with auto-fill the minimum IS the card size, and 250 gave
     263px tracks - three-line descriptions all over again, which is the
     thing this move was for. At 300 a 1428px column makes four tracks of
     about 350. */
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 12px;
}
#overview-achievements .achievements-section {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* Module info rail (2026-08-07) — one unified card filling the leftover
   left margin on genuinely wide monitors, reusing the landing page's own
   module names/icons/one-line descriptions rather than writing new copy.
   Redesigned same day from 3 separately-spaced tiles into rows inside a
   single card (matching the Subscriptions sidebar's own card treatment)
   after the tile-spacing looked arbitrary once stretched to full height.
   Hidden below 1500px on purpose: below that width there isn't enough
   spare room left for it once the main column + Subscriptions sidebar
   already have comfortable space — it only earns its keep when there's
   real unused margin to fill, not by squeezing the real content. */
/* No longer a rail - see .module-info-fold. The class is kept because the
   rows inside it still use module-info-row/name/desc. */
.module-info-rail { display: none; }

/* The science block, folded into the foot of the main column. */
.module-info-fold {
  margin-top: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  overflow: hidden;
}
.module-info-fold > summary {
  cursor: pointer;
  padding: 14px 18px;
  font-family: 'Poppins', sans-serif;
  font-size: 14.5px;
  font-weight: 700;
  color: var(--navy-dark);
  list-style: none;
}
.module-info-fold > summary::-webkit-details-marker { display: none; }
.module-info-fold > summary::before { content: "+ "; color: var(--teal); font-weight: 700; }
.module-info-fold[open] > summary::before { content: "− "; }
.module-info-fold[open] > summary { border-bottom: 1px solid var(--border); }
.module-info-fold .module-info-head { padding: 16px 18px 0; }
/* WIDER SHELL, TWO COLUMNS (2026-08-25). The third column was 240px of
   every wide screen holding reference a parent reads once, and it cost the
   report the room its tables and tile rows actually need: the report was
   976px at a 1900px window while 340px of page sat empty either side.
   Every grid that came out 2 + 1 today was an inner card at 433-691px,
   nested inside that column.

   The science rail is a collapsed block at the foot of the column now, and
   the report gets 1478px. Subscriptions keeps its rail: a short status
   list is what a rail is good at, and it is the one thing worth seeing
   without scrolling. */
@media (min-width: 1500px) {
  .profile-inner,
  .profile-header-inner { max-width: min(96vw, 1800px); }
}
.module-info-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
}
.module-info-row:last-child { border-bottom: none; }
.module-info-icon { display: inline-flex; width: 20px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.module-info-icon svg { width: 100%; height: 100%; }
.module-info-name { font-size: 13px; font-weight: 700; color: var(--navy-dark); margin: 0 0 3px; }
.module-info-desc { font-size: 11.5px; color: var(--text-muted); margin: 0; line-height: 1.45; }
/* Each rail row now opens the matching section of science.html — the
   one-liner stays the whole visible summary, the depth lives there. */
.module-info-head { padding: 14px 18px 12px; border-bottom: 1px solid var(--border); background: var(--bg-light); }
.module-info-head-title { font-family: 'Poppins', sans-serif; font-size: 12.5px; font-weight: 700; color: var(--navy-dark); margin: 0 0 3px; }
.module-info-head-sub { font-size: 11px; color: var(--text-muted); margin: 0; line-height: 1.45; }
.module-info-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 7px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--teal);
  text-decoration: none;
}
.module-info-link:hover { text-decoration: underline; }
.module-info-link span { transition: transform 0.15s ease; }
.module-info-link:hover span { transform: translateX(2px); }
.module-info-all {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 13px 18px;
  border-top: 1px solid var(--border);
  background: var(--bg-light);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--navy-dark);
  text-decoration: none;
}
.module-info-all:hover { color: var(--teal); }

@media (max-width: 900px) {
  .profile-columns { grid-template-columns: 1fr; }
  .subscriptions-sidebar { position: static; }
}

.profile-step-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.profile-step-head-main {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  flex: 1 1 380px;
  min-width: 0;
}
.profile-step-head .step-number {
  margin-bottom: 0;
  flex-shrink: 0;
}
.profile-step-head .section-title { margin-bottom: 4px; }
.profile-step-head .section-sub { margin-bottom: 0; }
.profile-step-head .section-sub strong { color: var(--amber-dark); }
.profile-step-action { flex-shrink: 0; }

.children-empty {
  padding: 20px;
  border: 1px dashed var(--border);
  border-radius: 12px;
  background: var(--bg-light);
  color: var(--text-muted);
  font-size: 14px;
  text-align: center;
}

/* The "no children yet" welcome state on profile.html — deliberately
   more prominent than the generic .children-empty pattern used for
   plain filtered-list empty states elsewhere. */
.first-run-state {
  padding: 64px 32px;
  text-align: center;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.first-run-icon {
  width: 64px;
  height: 64px;
  margin: 0 auto 20px;
  display: block;
}

.children-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.child-card {
  padding: 14px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-light);
}
.child-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.child-card-name {
  font-weight: 600;
  color: var(--navy-dark);
  font-size: 15px;
}
.child-card-meta {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 4px;
}
.child-standard-badge {
  display: inline-flex;
  background: var(--navy);
  color: #fff;
  font-size: 11.5px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  margin-left: 8px;
}
.child-remove-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  padding: 6px 10px;
}
.child-remove-btn:hover { color: #b3261e; }

.child-slot-section {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.child-slot-booked {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--teal);
  margin: 0 0 10px;
}
.child-dashboard-link {
  display: inline-block;
  font-size: 13px;
  margin-top: 10px;
}

/* Per-child dashboard (child-dashboard.html) */
.child-dashboard-hero .eyebrow { margin-bottom: 8px; }
/* A very faint brand-mark watermark (2026-08-07) — the hero read as bare white
   space once the tagline and logo were the only things breaking it up;
   this gives the card some depth without competing with the real
   content. Purely decorative, so it's a background image, not a
   duplicate <img>, and stays out of the accessibility tree entirely. */
.child-dashboard-hero {
  position: relative;
  overflow: hidden;
  margin-bottom: 20px;
}
.child-dashboard-hero::before {
  content: '';
  position: absolute;
  top: -60px;
  right: -40px;
  width: 220px;
  height: 220px;
  background-image: url('../assets/logo/topperlens-junior-icon.png?v=20260821b');
  background-size: contain;
  opacity: 0.045;
  pointer-events: none;
}
/* A thin brand-gradient stripe along the top edge (2026-08-09) — the
   card read as flat/plain with only the barely-visible watermark above
   to break up the white. .child-dashboard-hero's own overflow:hidden
   clips this to the card's rounded corners, so no radius needed here. */
.child-dashboard-hero::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--navy) 0%, var(--teal) 55%, var(--amber) 100%);
}
.child-dashboard-hero-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  position: relative;
}
.hero-id-block {
  display: flex;
  align-items: center;
  gap: 18px;
  min-width: 0;
}
/* Groups the Student ID badge and the login-creation hint into one
   bordered panel (2026-08-07) instead of two loose lines of text — gives
   them a shared edge to align to, per direct feedback that they read as
   floating/unaligned on their own. */
.hero-id-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
  /* Shrink-to-fit rather than stretching to the hero-id-block column's
     full width (which used to be driven wide by a long unwrapped meta
     line) — otherwise centering its own content just centers it inside
     an oversized box that still looks off. Auto side-margins center the
     shrink-to-fit box itself in the (normal block-flow) parent — the
     panel's parent isn't a flex container, so align-self has no effect. */
  width: fit-content;
  max-width: 100%;
  margin: 10px auto 0;
}
/* .student-id-badge/.student-id-empty-hint carry their own margin-top for
   the contexts where they stand alone (dashboard.html's inline row) — the
   panel's own gap already spaces them here, so that would double up. */
.hero-id-panel .student-id-badge,
.hero-id-panel .student-id-empty-hint {
  margin-top: 0;
}
/* Fills the space between "who you're viewing" and the brand mark — a
   short, memorable line naming the 3 real modules (2026-08-07), picked
   over generic phrasing since it can't apply to any other product. The
   dividers and amber dots give it a defined frame instead of floating in
   bare space. Hidden well before the brand mark (950px vs 700px) since it
   needs room on BOTH sides to read as centered, not just its own. */
.hero-tagline-mid {
  /* BASIS ZERO, NOT AUTO. With basis auto this column claims its content
     width - the tagline is a long line of 19px Poppins - and in a wrapping
     flex row wrapping is decided BEFORE shrinking. So between about 950px
     and 1500px the three zones did not fit at their content widths, this
     one wrapped to a second line, and it took the brand mark with it: the
     hero came apart into an id block on one row and a tagline plus logo
     below. Narrower than 950 it is hidden, wider than 1500 everything fit,
     which is why it only showed up on a bigger screen.

     At basis 0 it claims nothing, never forces a wrap, and grows into
     whatever is left after the id block and the brand mark. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 28px;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  align-self: stretch;
}
.hero-tagline-mid-dots { font-size: 13px; font-weight: 700; letter-spacing: 0.2em; color: var(--amber); margin: 0 0 6px; }
.hero-tagline-mid-main {
  font-family: 'Poppins', sans-serif;
  font-size: 19px;
  font-weight: 700;
  color: var(--navy);
  margin: 0;
  text-wrap: balance;
}
.hero-tagline-mid-sub { font-size: 12px; color: var(--text-muted); margin: 6px 0 0; }
/* One small dot per module in its own colour (2026-08-09, fourth added
   2026-08-21) — a quiet echo of the real modules so the tagline column
   ties back to the actual product instead of reading as generic
   marketing copy sitting in isolation. THE COUNT IS THE POINT: General
   Knowledge shipped 2026-08-20 and both this row and the line above it
   still described three, so the echo was quietly wrong. If a fifth
   module ever lands, this row and hero-tagline-mid-main move together. */
.hero-tagline-mid-chips { display: flex; justify-content: center; gap: 8px; margin: 10px 0 0; }
.hero-tagline-mid-chips span { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.hero-tagline-mid-chips .chip-navy { background: var(--navy); }
.hero-tagline-mid-chips .chip-teal { background: var(--teal); }
.hero-tagline-mid-chips .chip-amber { background: var(--amber); }
.hero-tagline-mid-chips .chip-violet { background: var(--violet); }
@media (max-width: 950px) {
  .hero-tagline-mid { display: none; }
}

.hero-brand-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  width: 150px;
  text-align: center;
}
.hero-brand-icon { width: 64px; height: 64px; flex-shrink: 0; }
.hero-brand-tagline { font-size: 12px; color: var(--text-muted); line-height: 1.45; margin: 0; }
@media (max-width: 700px) {
  .hero-brand-mark { display: none; }
}
.child-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--navy), var(--teal));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 22px;
  flex-shrink: 0;
  overflow: hidden;
}
.child-avatar svg { width: 62%; height: 62%; }
.child-avatar img { width: 100%; height: 100%; object-fit: cover; }
#child-view-avatar { cursor: pointer; }
#child-view-avatar:hover { box-shadow: 0 0 0 3px rgba(15, 123, 138, 0.25); }

/* Avatar picker (2026-08-07) — shared by the "Add a child" form and the
   hero's own "change avatar" modal, so both stay visually identical.
   60 real illustrated avatars per gender is too many for one flat grid,
   so a boy/girl toggle switches which set is shown; "use initial letter"
   sits above the toggle since it applies regardless of which set is open. */
.avatar-picker-initial-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.avatar-picker-initial-label { font-size: 13px; color: var(--text-muted); }
.avatar-gender-toggle { display: flex; gap: 8px; margin: 12px 0 8px; }
.avatar-gender-btn {
  flex: 1;
  padding: 7px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: #fff;
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
}
.avatar-gender-btn.active { background: var(--teal); border-color: var(--teal); color: #fff; }
.avatar-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 10px;
  max-height: 300px;
  overflow-y: auto;
  padding: 2px 4px 2px 0;
}
.avatar-option-btn {
  background: none;
  border: 2px solid transparent;
  border-radius: 50%;
  padding: 3px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.avatar-option-btn.selected { border-color: var(--amber); }
.avatar-option-preview { width: 44px; height: 44px; font-size: 17px; }
@media (max-width: 480px) {
  .avatar-picker-grid { grid-template-columns: repeat(3, 1fr); }
}
.profile-subsection-title { font-size: 20px; margin-bottom: 6px; }

/* In-page tabs (Overview / Modules & purchases / Progress) */
.portal-tabbar {
  display: flex;
  gap: 4px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}
.portal-tab {
  background: none;
  border: none;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  color: var(--text-muted);
  cursor: pointer;
  position: relative;
  white-space: nowrap;
  flex-shrink: 0;
}
@media (max-width: 480px) {
  .portal-tab { padding: 10px 12px; font-size: 13px; }
}
.portal-tab.active { color: var(--navy); }
.portal-tab.active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--amber);
  border-radius: 2px;
}

/* Overview tab */
/* auto-fit, NOT repeat(4, 1fr) (2026-08-25). Fixed at four columns, a row of
   three tiles filled three of them and left a quarter of the row empty - so
   the group hugged the left and looked misaligned rather than centred. This
   fills the row whatever the count, which is what "centre the tiles" actually
   needed. */
.stat-row {
  display: grid;
  /* --tiles is the actual tile count, written on the element by
     stampStatRows() in js/auth.js. auto-fit is the fallback for the
     instant before that runs, and for anything the observer misses -
     it is the old behaviour, so nothing is worse than it was. */
  grid-template-columns: repeat(var(--tiles, auto-fit), minmax(150px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
/* Teal, because these are personal BESTS - achievements. Amber is this
   palette's "needs attention" colour and would have said the opposite thing
   about a child's best result. */
.stat-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 92px;
  background: linear-gradient(160deg, rgba(15, 123, 138, 0.09), rgba(15, 123, 138, 0.03));
  border: 1px solid rgba(15, 123, 138, 0.18);
  border-radius: 14px;
  padding: 16px 14px;
  text-align: center;
}
.stat-tile .num {
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 23px;
  line-height: 1.1;
  color: var(--teal);
}
.stat-tile .label {
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text-muted);
  margin-top: 0;
}
.reco-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: #fff;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.reco-card h4 { font-size: 15px; color: var(--navy-dark); margin: 0 0 4px; }
.reco-sub { font-size: 13px; color: var(--text-muted); margin: 0; }

/* Progress tab (2026-08-03) — real trend charts across a child's own
   attempts. Teal = strength/improving, amber = growth focus, navy = neutral
   label text, same semantic convention already used in css/report.css's
   score-bar-strength/-typical/-growth. */
.progress-ipsative-note {
  font-size: 12.5px;
  color: var(--text-muted);
  text-align: center;
  margin: 0 0 20px;
}
.progress-trend-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px 26px;
  margin-bottom: 16px;
}
.progress-trend-head { margin-bottom: 6px; }
.progress-trend-head h3 { font-size: 16.5px; color: var(--navy-dark); margin: 0 0 4px; }

/* Module tab redesign (2026-08-11): credits strip + labeled reports.
   One shared visual system: mock = navy accent, practice = teal accent,
   carried consistently from the credit cards down through every report
   card so a parent can scan by color alone. */
.module-hero {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px 18px 14px;
  margin-bottom: 16px;
}
.module-hero-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.module-hero-ring {
  flex-shrink: 0;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.module-hero-ring-empty { background: var(--border); }
.module-hero-ring-inner {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.module-hero-ring-num {
  font-family: 'Poppins', sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.module-hero-ring-of { font-size: 9px; font-weight: 600; color: var(--text-muted); margin-top: 1px; }
.module-hero-lead { flex: 1; min-width: 0; }
.module-hero-eyebrow {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--teal);
  margin: 0 0 3px;
}
.module-hero-level {
  font-family: 'Poppins', sans-serif;
  font-size: 19px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0 0 5px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.module-hero-desc { font-size: 12.5px; line-height: 1.5; color: var(--text-muted); margin: 0; max-width: 82ch; }
.module-hero-only { color: var(--navy-dark); font-weight: 600; }

/* ---- THE SUBJECT BANNER IS DARK; THIS TEXT WAS NOT (2026-08-27) ----------
   Four pieces of copy on the navy hero were still carrying the colours they
   had when this panel had a light background. Two of them were set to
   --navy-dark, which IS the darker gradient stop - #1B1C50 text on #1B1C50,
   a contrast ratio of 1:1. Not "hard to read": the same colour.

   Measured against BOTH stops of the gradient (#1B1C50 and #282972), because
   a colour that only passes on the darker half fails wherever the panel
   lightens:

     white @78%   10.03:1 / 8.34:1
     #FFB98A       9.45:1 / 7.61:1
     white        15.81:1 / 12.72:1

   Brand --amber (#F37130) is NOT used here: it measures 4.37:1 on the
   lighter stop and fails. #FFB98A is the same hue carried up to where it
   works on a dark ground - the eyebrow keeps its amber identity without
   keeping the problem. */
.module-hero .module-hero-only { color: rgba(255, 255, 255, 0.78); font-weight: 600; }
.module-hero .mhfocus-note { color: rgba(255, 255, 255, 0.78); }
.module-hero .mhfocus-eyebrow { color: #FFB98A; }
.module-hero .mhfocus-area { color: #fff; }


.module-hero-desc strong { color: var(--navy-dark); font-weight: 600; }

/* Five numbers on one thin line — this replaced two large credit cards
   plus a separate three-tile stat row (2026-08-13, user: too much space
   for what these say). */
.module-hero-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 7px 18px;
  margin: 12px 0 0;
  padding-top: 11px;
  border-top: 1px solid var(--border);
}
.module-hero-fact { display: flex; align-items: baseline; gap: 6px; }
/* Splits lifetime effort from what the plan still holds, so six figures
   read as two groups rather than one undifferentiated run. */
.module-hero-fact.mhf-divide {
  border-left: 1px solid var(--border);
  padding-left: 22px;
  margin-left: -4px;
}
@media (max-width: 620px) {
  .module-hero-fact.mhf-divide { border-left: none; padding-left: 0; margin-left: 0; }
}
.mhf-value {
  font-family: 'Poppins', sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.mhf-label { font-size: 12px; color: var(--text-muted); }
/* This week's focus — one row, not the report's old full-width block
   with its own heading, paragraph and oversized button. */
.module-hero-focus {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding: 10px 13px;
  border-radius: 10px;
  background: rgba(243, 113, 48, 0.09);
  border: 1px solid rgba(243, 113, 48, 0.28);
}
.mhfocus-eyebrow {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--amber-dark);
  white-space: nowrap;
}
.mhfocus-area { font-size: 14px; font-weight: 700; color: var(--navy-dark); }
.mhfocus-note { font-size: 12px; color: var(--text-muted); flex: 1; min-width: 140px; }
.mhfocus-cta { flex-shrink: 0; }
@media (max-width: 560px) { .mhfocus-cta { width: 100%; text-align: center; } }

.module-hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-left: auto; }
.module-hero-actions .module-action-muted { align-self: center; }

@media (max-width: 560px) {
  .module-hero-top { align-items: flex-start; }
  .module-hero-strip { gap: 8px 18px; }
  .module-hero-actions .btn { flex: 1; text-align: center; }
}

.module-reports-title {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: 'Poppins', sans-serif;
  font-size: 17px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 26px 0 14px;
}
.module-reports-title::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}
.module-reports-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 18px;
}
.module-report-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px 22px 20px;
  border-left-width: 4px;
}
.module-report-card.mrc-mock { border-left-color: var(--navy); }
.module-report-card.mrc-practice { border-left-color: var(--teal); }
.module-report-eyebrow {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin: 0 0 10px;
}
.module-report-big {
  font-family: 'Poppins', sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: var(--navy-dark);
  line-height: 1.1;
  margin: 0 0 6px;
  font-variant-numeric: tabular-nums;
}
.module-report-big span {
  font-family: 'Inter', sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  margin-left: 4px;
}
.module-report-meta {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 14px;
}
.module-report-empty { font-size: 13.5px; color: var(--text-muted); margin: 4px 0 0; }
.module-report-section { margin-bottom: 18px; }
.module-report-section .module-report-eyebrow { margin-bottom: 8px; }

/* Collapsible report sections (2026-08-11) — native <details>, teaser
   visible while closed so the headline number needs no click. */
/* Five independent report sections (2026-08-13). All start collapsed,
   so each closed row has to earn its place: a coloured icon tile, the
   title, a one-line teaser, and the headline number on the right. The
   accent runs through the left border and the icon tile together, so the
   two mock sections and the two practice sections read as pairs without
   being nested inside one another again. */
.module-collapse {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 14px;
  margin-bottom: 10px;
  border-left-width: 4px;
  border-left-color: var(--accent, var(--border));
  overflow: hidden;
  transition: box-shadow 0.16s ease, transform 0.16s ease;
}
.module-collapse.mcol-mock-latest { --accent: var(--navy); --accent-soft: rgba(40, 41, 114, 0.10); }
.module-collapse.mcol-mock-all { --accent: var(--navy); --accent-soft: rgba(40, 41, 114, 0.10); }
.module-collapse.mcol-practice-latest { --accent: var(--teal); --accent-soft: rgba(15, 123, 138, 0.12); }
.module-collapse.mcol-practice-all { --accent: var(--teal); --accent-soft: rgba(15, 123, 138, 0.12); }
.module-collapse.mcol-downloads { --accent: var(--amber-dark); --accent-soft: rgba(243, 113, 48, 0.16); }
.module-collapse:hover { box-shadow: 0 6px 18px rgba(40, 41, 114, 0.07); }
.module-collapse[open] { box-shadow: 0 8px 24px rgba(40, 41, 114, 0.09); }

.module-collapse summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 18px;
  cursor: pointer;
  list-style: none;
}
.module-collapse summary::-webkit-details-marker { display: none; }
.module-collapse summary:hover { background: var(--bg-light); }
.module-collapse summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.module-collapse-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--accent-soft, var(--bg-light));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
}
.module-collapse-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.module-collapse-title {
  font-family: 'Poppins', sans-serif;
  font-size: 15px;
  font-weight: 700;
  color: var(--navy-dark);
  line-height: 1.25;
}
.module-collapse-teaser {
  font-size: 12.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.module-collapse-stat {
  flex-shrink: 0;
  font-family: 'Poppins', sans-serif;
  font-size: 19px;
  font-weight: 700;
  color: var(--accent, var(--navy-dark));
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.module-collapse-chevron {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: 14px;
  transition: transform 0.15s ease;
}
.module-collapse[open] > summary .module-collapse-chevron { transform: rotate(180deg); }
.module-collapse[open] > summary { border-bottom: 1px solid var(--border); background: var(--bg-light); }
.module-collapse-body { padding: 16px 18px 18px; }
.module-collapse-body .progress-trend-card { border: none; padding: 0 0 8px; margin-bottom: 8px; }
@media (prefers-reduced-motion: reduce) {
  .module-collapse, .module-collapse-chevron { transition: none; }
}
.module-collapse-body .progress-trend-card { border: none; padding: 0 0 8px; margin-bottom: 8px; }
.module-collapse-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }

.mini-bar-heading {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin: 16px 0 8px;
}
.mini-bar-list { display: flex; flex-direction: column; gap: 7px; }
.mini-bar-row { display: flex; align-items: center; gap: 10px; }
.mini-bar-label {
  flex: 0 0 150px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--navy-dark);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mini-bar-track {
  flex: 1;
  height: 8px;
  background: var(--bg-light);
  border-radius: 999px;
  overflow: hidden;
}
.mini-bar-fill { height: 100%; background: var(--teal); border-radius: 999px; }
.mini-bar-fill.mb-growth { background: var(--amber-dark); }
.mini-bar-pct { flex: 0 0 40px; text-align: right; font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--navy-dark); }
@media (max-width: 560px) {
  .mini-bar-label { flex-basis: 110px; }
}

@media (max-width: 860px) {
  .module-reports-grid { grid-template-columns: 1fr; }
  .module-credit-note { min-height: 0; }
}
.mock-attempt-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.mock-attempt-row:first-of-type { border-top: none; padding-top: 4px; }
.mock-attempt-date { font-size: 14px; font-weight: 600; color: var(--navy-dark); margin: 0; }
.mock-attempt-latest {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--teal);
  background: rgba(15, 123, 138, 0.1);
  border-radius: 999px;
  padding: 2px 8px;
  margin-left: 6px;
  vertical-align: 1px;
}
.mock-attempt-meta { font-size: 13px; color: var(--text-muted); margin: 2px 0 0; }
.mock-attempt-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Overview v2 (2026-08-12): narrative, action queue, goal ring, usage */
/* Metric "i" buttons + shared tooltip (2026-08-13) */
.metric-info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(15, 123, 138, 0.45);
  background: rgba(15, 123, 138, 0.1);
  color: var(--teal);
  font-family: 'Poppins', sans-serif;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  vertical-align: 1px;
  flex-shrink: 0;
}
.metric-info-btn:hover { background: rgba(15, 123, 138, 0.22); }
.metric-tooltip {
  /* FIXED, not absolute (2026-08-31). It is positioned from a viewport
     rect, and it now also has to work inside a modal <dialog> whose body
     is its own scroll container - an absolutely-positioned tip offset by
     window.scrollY lands nowhere near its button in there. z-index is for
     the ordinary in-page case; against a modal dialog nothing wins on
     z-index, so showMetricTooltip() re-parents into the dialog instead. */
  position: fixed;
  z-index: 10000;
  background: var(--navy-dark);
  color: #fff;
  border-radius: 10px;
  padding: 12px 14px;
  box-shadow: 0 10px 30px rgba(27, 28, 80, 0.25);
}
.metric-tooltip-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.6);
  margin: 0 0 4px;
}
.metric-tooltip-text { font-size: 13px; line-height: 1.55; margin: 0; color: #fff; }
@media print { .metric-info-btn, .metric-tooltip { display: none !important; } }

/* "The short version" summary — given a real heading so it reads as the
   page's opening statement rather than a stray note. */
.overview-narrative-card {
  background: #fff;
  border: 1px solid var(--border);
  border-left: 4px solid var(--teal);
  border-radius: 14px;
  padding: 18px 22px 20px;
}
.overview-narrative-eyebrow {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--teal);
  margin: 0 0 4px;
}
.overview-narrative-heading {
  font-family: 'Poppins', sans-serif;
  font-size: 20px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0 0 12px;
}
/* "Our observations" (2026-08-13) — three evidence-backed columns:
   strengths, weaknesses, and the working patterns behind them. Each
   column is tinted by its own semantic colour so a parent can tell the
   three apart before reading a word. */
.obs-note {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted-text, #63637A);
}
/* Subject tabs (2026-08-13) reuse .rec-tab from Our Recommendations so
   the two sections stay visually identical; only the count chip is new. */
.obs-tab-count {
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--navy-dark);
  background: var(--bg-light);
  border-radius: 999px;
  padding: 1px 7px;
}
.rec-tab.active .obs-tab-count { background: rgba(255, 255, 255, 0.22); color: #fff; }
.obs-panel-empty {
  background: var(--bg-light);
  border: 1px dashed var(--border);
  border-radius: 12px;
  padding: 20px 22px;
}
.obs-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
.obs-col {
  border: 1px solid var(--border);
  border-top: 3px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.obs-col-title {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-dark);
}
.obs-strength { background: rgba(15, 123, 138, 0.05); border-top-color: var(--teal); }
.obs-weakness { background: rgba(243, 113, 48, 0.07); border-top-color: var(--amber); }
.obs-pattern { background: rgba(40, 41, 114, 0.05); border-top-color: var(--navy); }
.obs-item { display: flex; flex-direction: column; gap: 3px; }
.obs-headline {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--navy-dark);
}
.obs-evidence {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
.obs-strength .obs-evidence { color: var(--teal); }
.obs-weakness .obs-evidence { color: var(--amber-dark); }
.obs-pattern .obs-evidence { color: var(--navy); }
.obs-meaning {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--muted-text, #63637A);
}
.obs-empty {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted-text, #63637A);
}
@media (max-width: 860px) {
  .obs-grid { grid-template-columns: 1fr; }
}
/* Side-by-side module cards (2026-08-12) — one card per subject, in a
   responsive grid, each carrying its own actions and an honest
   score-impact footer computed from the child's own latest attempt. */
/* One hero action + compact secondary list (2026-08-13) — three equal
   tiles gave the eye nowhere to land, so the single highest-priority
   task is promoted and everything else compressed to one line each. */
.overview-hero {
  background: linear-gradient(135deg, #1B1C50 0%, #23245F 100%);
  color: #fff;
  border-radius: 16px;
  padding: 22px 26px 20px;
  border-left: 5px solid var(--amber);
}
.overview-hero.oh-cognitive { border-left-color: #6FC0CE; }
.overview-hero.oh-spelling { border-left-color: var(--teal); }
.overview-hero.oh-maths { border-left-color: var(--amber); }
.overview-hero-eyebrow {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--amber);
  margin: 0 0 6px;
}
.overview-hero-title {
  font-family: 'Poppins', sans-serif;
  font-size: 23px;
  font-weight: 700;
  color: #fff;
  margin: 0 0 6px;
}
.overview-hero-detail {
  font-size: 14.5px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.82);
  margin: 0 0 16px;
  max-width: 62ch;
}
/* Per-subject recommendation tabs (2026-08-13) — each subject owns a
   tab so a parent can look one up directly; the recommended starting
   subject is flagged rather than being the only one shown. */
/* ONE ROW WHEN THERE IS ROOM FOR ONE (2026-08-25).

   As a wrapping flex row the pills were sized by their own text, so four
   modules came out 3 + 1 with "General Knowledge" - the longest label -
   stranded underneath and a gap beside it.

   A VIEWPORT BREAKPOINT CANNOT DECIDE THIS. The card holding these tabs
   is 691px wide at a 1280px viewport and 433px at a 1000px one - the
   profile layout changes shape in between, so card width does not track
   viewport width at all. A media query tuned on one of those widths
   overflowed at the other, pills clipped rather than wrapped, which is
   worse than the problem it replaced.

   The container query asks the only question that matters: is this card
   wide enough for four pills side by side. Two columns is the default, so
   a browser without container queries gets the safe layout rather than a
   broken one. */
/* ---- "there is more behind these" (2026-08-26) -----------------------
   Four subject pills with one filled in look like a label, not a control, and
   a parent who never pressed one saw a quarter of what was written for them.
   The report's stepper had the same problem and answers it with a line of
   plain instruction above the numbers; this is that same answer, in the same
   words and the same type. */
.rec-tabs-hint {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-muted);
}
.rec-tabs-hint strong { color: var(--navy); font-weight: 600; }

.rec-tabs-wrap { container-type: inline-size; }

/* KEPT UP FOR AS LONG AS THE PAGE IS OPEN, on request - it stopped after
   three passes before and was being missed.
   
   A BURST AND A REST, not a metronome. The visible part is the same two
   pulses over 1.6s that this had originally; what follows is three seconds
   of nothing before it comes round again. Constant motion beside text a
   parent is trying to read stops being a hint within about a minute, and the
   rest is what makes it bearable to leave running.
   
   It stays inside the flash threshold (WCAG 2.3.1) at well under 3Hz, and it
   has the stop WCAG 2.2.2 asks for: pressing any tab ends it for the rest of
   the session, which is the one action it is asking for anyway. Nothing runs
   at all under prefers-reduced-motion. */
/* Strengthened 2026-08-27: at 4px and 18% alpha this was there in the DOM and
   invisible on screen, which is the same as not being there. 7px and 34%, and
   the rest cut from 3.4s to 1.9s so the eye is far more likely to be pointing
   at it during a pulse. */
@keyframes nudgeRing {
  0% { box-shadow: 0 0 0 0 rgba(15, 123, 138, 0); }
  11% { box-shadow: 0 0 0 7px rgba(15, 123, 138, 0.34); }
  22% { box-shadow: 0 0 0 0 rgba(15, 123, 138, 0); }
  33% { box-shadow: 0 0 0 7px rgba(15, 123, 138, 0.34); }
  44%, 100% { box-shadow: 0 0 0 0 rgba(15, 123, 138, 0); }
}
.rec-tabs-wrap:not(.tabs-touched) .rec-tab:not(.active) {
  animation: nudgeRing 3.6s ease-in-out infinite;
}
/* The pills are staggered so the row reads left to right rather than
   pulsing as one block, which looks like a glitch. */
.rec-tabs-wrap:not(.tabs-touched) .rec-tab:not(.active):nth-of-type(3) { animation-delay: 0.12s; }
.rec-tabs-wrap:not(.tabs-touched) .rec-tab:not(.active):nth-of-type(4) { animation-delay: 0.24s; }

@media (prefers-reduced-motion: reduce) {
  .rec-tabs-wrap .rec-tab { animation: none !important; }
}
.rec-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}
/* 556px is the four measured at their natural widths plus the gaps; the
   rest is margin for a longer label or a wider font. */
@container (min-width: 600px) {
  .rec-tabs {
    grid-template-columns: repeat(var(--tabs, 4), auto);
    justify-content: start;
  }
  /* Only here. In the two-column fallback the cells can be 99px on a
     phone, and a label told never to wrap simply overflows its pill. */
  .rec-tabs .rec-tab { white-space: nowrap; }
}
.rec-tab {
  display: inline-flex;
  align-items: center;
  /* Centred, because the pills are now sized by the grid rather than by
     their text - left-aligned labels in equal boxes look like a ragged
     column with a border round each one. */
  justify-content: center;
  gap: 7px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-muted);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 12px;
  cursor: pointer;
  min-width: 0;
}
.rec-tab:hover { border-color: var(--navy); color: var(--navy-dark); }
.rec-tab.active { background: var(--navy-dark); border-color: var(--navy-dark); color: #fff; }
.rec-tab-flag {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #282972;
  background: var(--amber);
  border-radius: 999px;
  padding: 2px 8px;
}
.rec-empty {
  background: #fff;
  border: 1px dashed var(--border);
  border-radius: 16px;
  padding: 24px;
  text-align: center;
}
.rec-empty-text { font-size: 14px; color: var(--text-muted); margin: 0 0 14px; max-width: 52ch; margin-left: auto; margin-right: auto; }

/* Sequenced recommendation stepper inside the hero (2026-08-13) — a
   connector line runs behind the numbered dots so the steps read as one
   path rather than two unrelated tasks. */
.growth-steps { margin-top: 4px; }
.growth-step {
  position: relative;
  display: flex;
  gap: 14px;
  padding-bottom: 18px;
}
.growth-step:last-child { padding-bottom: 0; }
.growth-step::before {
  content: '';
  position: absolute;
  left: 13px;
  top: 30px;
  bottom: 2px;
  width: 2px;
  background: rgba(255, 255, 255, 0.18);
}
.growth-step:last-child::before { display: none; }
.growth-step-dot {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--amber);
  color: #282972;
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.growth-step:not(:first-child) .growth-step-dot {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}
.growth-step-body { flex: 1; min-width: 0; }
.growth-step-kind {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--amber);
  margin: 4px 0 3px;
}
.growth-step-kind span { color: rgba(255, 255, 255, 0.55); font-weight: 600; letter-spacing: 0.02em; text-transform: none; }
.growth-step:not(:first-child) .growth-step-kind { color: rgba(255, 255, 255, 0.75); }
.growth-step-title {
  font-family: 'Poppins', sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  margin: 0 0 4px;
  line-height: 1.3;
}
.growth-step-detail {
  font-size: 13.5px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.78);
  margin: 0 0 10px;
  max-width: 62ch;
}
.growth-step-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.growth-step-actions .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.45);
  background: transparent;
}
.growth-step-actions .btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }

.overview-hero-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.overview-hero-actions .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.5);
  background: transparent;
}
.overview-hero-actions .btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }
.overview-share-fallback {
  flex: 1 1 100%;
  margin-top: 8px;
  font-size: 12.5px;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}
.overview-hero-momentum {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
  margin: 14px 0 0;
}
.overview-hero-impact {
  font-size: 13px;
  line-height: 1.55;
  color: #fff;
  background: rgba(243, 113, 48, 0.16);
  border: 1px solid rgba(243, 113, 48, 0.4);
  border-radius: 10px;
  padding: 10px 13px;
  margin: 12px 0 0;
}
.overview-hero-impact strong { color: var(--amber); }
.overview-alsonext-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin: 20px 0 8px;
}
.overview-alsonext {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.overview-alsonext-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 18px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.overview-alsonext-row:first-child { border-top: none; }
.overview-alsonext-icon { flex-shrink: 0; font-size: 15px; }
.overview-alsonext-text { flex: 1; min-width: 200px; font-size: 13.5px; color: var(--navy-dark); }
.overview-alsonext-text strong { font-weight: 700; }
.overview-alsonext-sub { display: block; font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
.overview-alsonext-plan { background: var(--bg-light); }
.overview-badge-line {
  font-size: 13px;
  color: var(--text-muted);
  margin: 12px 0 0;
  text-align: center;
}
.overview-badge-line strong { color: var(--amber-dark); }
.overview-queue-group {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
  border-top-width: 4px;
  padding: 14px 16px 16px;
}
.overview-queue-group.oqg-cognitive { border-top-color: var(--navy); }
.overview-queue-group.oqg-spelling { border-top-color: var(--teal); }
.overview-queue-group.oqg-maths { border-top-color: var(--amber-dark); }
.overview-queue-group.oqg-general { border-top-color: var(--text-muted); }
.overview-queue-module {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0 0 4px;
}
.overview-queue-module.oqm-cognitive { color: var(--navy); }
.overview-queue-module.oqm-spelling { color: var(--teal); }
.overview-queue-module.oqm-maths { color: var(--amber-dark); }
.overview-queue-module.oqm-general { color: var(--text-muted); }
.overview-queue-num.oqn-cognitive { background: var(--navy); }
.overview-queue-num.oqn-spelling { background: var(--teal); }
.overview-queue-num.oqn-maths { background: var(--amber-dark); }
.overview-queue-num.oqn-general { background: var(--text-muted); }
.overview-queue { background: transparent; }
/* Narrow tiles: number + text on one line, CTA on its own line beneath
   (left-aligned under the text) rather than a wrapped flex row that
   left ragged gaps. */
.overview-queue-row {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 6px 12px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
  align-items: start;
}
.overview-queue-row:first-child { border-top: none; padding-top: 8px; }
.overview-queue-text { grid-column: 2; }
.overview-queue-cta { grid-column: 2; }
.overview-queue-cta:empty { display: none; }
.overview-queue-impact {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--navy-dark);
  background: rgba(243, 113, 48, 0.1);
  border: 1px solid rgba(243, 113, 48, 0.28);
  border-radius: 10px;
  padding: 9px 12px;
}
.overview-queue-num {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--navy);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}
.overview-queue-text { flex: 1; min-width: 200px; font-size: 14px; color: var(--navy-dark); margin: 0; }
.overview-queue-cta { flex-shrink: 0; }


/* Nested per-attempt collapsibles + the dedicated downloads section */
.mock-attempt-collapse { border-top: 1px solid var(--border); }
.mock-attempt-collapse:first-of-type { border-top: none; }
.mock-attempt-collapse summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 4px;
  cursor: pointer;
  list-style: none;
}
.mock-attempt-collapse summary::-webkit-details-marker { display: none; }
.mock-attempt-collapse summary:hover { background: var(--bg-light); }
.mock-attempt-collapse .mock-attempt-info { display: block; min-width: 0; }
.mock-attempt-collapse .mock-attempt-date { display: block; }
.mock-attempt-collapse .mock-attempt-meta { display: block; }
.mock-attempt-collapse[open] > summary .module-collapse-chevron { transform: rotate(180deg); }
.mock-attempt-body { padding: 4px 4px 14px; }
.mock-attempt-openlink { margin: 12px 0 0; font-size: 13.5px; }
.download-list { display: flex; flex-direction: column; }
.download-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.download-row:first-of-type { border-top: none; padding-top: 4px; }
.download-row .mock-attempt-date { display: block; }
.download-row .mock-attempt-meta { display: block; }

/* Inline report embed. In "Latest mock test" the report is the section
   body itself; in the aggregated attempt list it stays behind this
   compact toggle, since there the row's own value is the area-by-area
   comparison. Restyled 2026-08-13 — it used to borrow the sub-collapse
   classes, which were deleted when the report sections were flattened. */
.report-embed-collapse {
  margin-top: 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.report-embed-collapse > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
  background: var(--bg-light);
}
.report-embed-collapse > summary::-webkit-details-marker { display: none; }
.report-embed-collapse > summary:hover { background: rgba(40, 41, 114, 0.05); }
.report-embed-collapse[open] > summary .module-collapse-chevron { transform: rotate(180deg); }
.report-embed-title { font-size: 13px; font-weight: 700; color: var(--navy-dark); }
.report-embed-body { padding: 0; }
.report-embed-frame {
  display: block;
  width: 100%;
  border: 0;
  min-height: 480px;
  background: #fff;
}
.progress-trend-svg { width: 100%; height: 190px; display: block; }

.progress-domain-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 16px;
}
.progress-domain-card {
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
}
.progress-domain-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.progress-domain-name { font-size: 13px; font-weight: 600; color: var(--navy-dark); }
.progress-sparkline { width: 100%; height: 40px; display: block; }
.progress-domain-note { font-size: 12px; color: var(--text-muted); margin: 6px 0 0; }

.progress-delta-badge {
  font-size: 11.5px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.progress-badge-up { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.progress-badge-focus { background: rgba(243, 113, 48, 0.14); color: var(--amber-dark); }
.progress-badge-steady { background: var(--border); color: var(--text-muted); }

.progress-empty-module {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 28px 24px;
  text-align: center;
  margin-bottom: 16px;
}
.progress-empty-module .section-sub { margin-bottom: 14px; }

/* One compact action line per module tab (2026-08-07, replacing the old
   Overview tile grid — one card per unused credit plus another for
   practice/results). Sits above the stats/Mock/Practice content already
   in progressModulePanelHtml. */
.module-action-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 16px;
  margin-bottom: 20px;
}
.module-action-text { font-size: 13px; color: var(--text-muted); margin: 0; }
.module-action-btns { display: flex; gap: 8px; flex-shrink: 0; }
/* Practice-quota exhausted/not-included state (2026-08-09) — sits where
   the "Practice now" link/button would otherwise be, so the layout
   doesn't shift between the two states. */
.module-action-muted { font-size: 12.5px; color: var(--text-muted); font-style: italic; }

/* Cognitive/Spelling/Maths are their own top-level tabs (2026-08-07,
   replacing the standalone Progress tab) — each just Mock/Practice, so a
   parent can answer "practice or the real test?" without every module's
   data mixed into one flat trend list. The weekly time-per-module summary
   moved to Overview, since it spans all 3 modules at once. */
/* FOUR, matching MODULES. Written as repeat(3, 1fr) when there were three
   subjects, so General Knowledge dropped onto a row of its own - the same
   defect the Levels strip had. Explicit counts, not auto-fit: auto-fit lands
   on 3 columns at some widths and recreates the 3 + 1 orphan. */
.progress-week-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
  margin-bottom: 24px;
}
@media (max-width: 1040px) { .progress-week-row { grid-template-columns: repeat(2, 1fr); } }
.progress-week-card {
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
}
.progress-week-label { font-size: 12.5px; color: var(--text-muted); margin: 0 0 4px; }
.progress-week-value { font-size: 22px; font-weight: 700; color: var(--navy-dark); margin: 0 0 10px; font-variant-numeric: tabular-nums; }
.progress-week-split {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 8px;
}
.progress-week-split-fill { height: 100%; background: var(--teal); }
.progress-week-note { font-size: 11.5px; color: var(--text-muted); margin: 0; }

.progress-subtabs { display: flex; gap: 8px; margin-bottom: 16px; }
.progress-subtab {
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  padding: 6px 16px;
  cursor: pointer;
}
.progress-subtab.active { background: var(--navy-dark); color: #fff; border-color: var(--navy-dark); }

.progress-metric-value { font-size: 22px; font-weight: 700; color: var(--navy-dark); margin: 0; font-variant-numeric: tabular-nums; }
.progress-metric-sub { font-size: 12.5px; font-weight: 400; color: var(--text-muted); }

@media (max-width: 720px) {
  .progress-domain-grid { grid-template-columns: repeat(2, 1fr); }
  .progress-week-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .progress-domain-grid { grid-template-columns: 1fr; }
  .progress-trend-card { padding: 20px 18px; }
  .progress-week-row { grid-template-columns: 1fr; }
  .progress-subtab { font-size: 12.5px; padding: 6px 12px; }
}

@media (max-width: 720px) {
  /* At most three here, from the same stamp. A hard 2 was what left the
     third tile of Personal bests and Session snapshot stranded. */
  .stat-row { grid-template-columns: repeat(var(--tiles-mid, 2), minmax(0, 1fr)); }
  .module-digest-row { grid-template-columns: 1fr; }
}
/* QUANTITY QUERIES. The rule above is right for four tiles and wrong for
   three: two columns leave the third alone on a second row, under a row
   stretched to fill - which is what "Personal bests" and "Session
   snapshot" were doing at every width under 720, and the report is often
   read at exactly that width inside the profile page.

   :has() lets the column count follow the tile count. Five goes to three
   rather than five, because five across a 578px card is 110px a tile and
   3 + 2 reads as deliberate where 2 + 2 + 1 does not. Below 540 there is
   not room for three and they stack as before. */
/* The same thing in pure CSS, for the moment before the stamp lands and
   for anything that renders with JS disabled. Guarded so it never
   contradicts a stamped row. */
@media (min-width: 540px) {
  .stat-row:not([style*="--tiles"]):has(> :last-child:nth-child(3)),
  .stat-row:not([style*="--tiles"]):has(> :last-child:nth-child(5)) {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (max-width: 560px) {
  /* Even counts pair up, odd ones stack - see the note in stampStatRows.
     Anything unstamped keeps the single column it had here before. */
  .stat-row { grid-template-columns: repeat(var(--tiles-narrow, 1), minmax(0, 1fr)); }
}
@media (max-width: 420px) {
  .stat-row { grid-template-columns: 1fr; }
}

/* Overview tab's per-module digest cards (2026-08-09) — one headline
   metric + one strength/growth line per module, distinct from that
   module's own full tab/report one click away. Reuses the score-band
   semantic colors already established in css/report.css (teal=strength,
   amber=growth) rather than inventing a new convention. */
.module-digest-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 24px;
}
.module-digest-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  /* A grid item's default min-width is auto (its content's min-content),
     and a percentage-width SVG child (the sparkline below) can't resolve
     that percentage before the track size is known — so the browser
     falls back to the SVG's own viewBox width (200) as this card's
     min-content, forcing the whole 1fr column wider than the viewport at
     narrow widths. min-width: 0 opts this card out of that fallback. */
  min-width: 0;
}
.module-digest-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); margin: 0 0 6px; }
.module-digest-headline { font-family: 'Poppins', sans-serif; font-size: 22px; font-weight: 700; color: var(--navy-dark); margin: 0; }
.module-digest-headline span { font-size: 13px; font-weight: 600; color: var(--text-muted); margin-left: 2px; }
.module-digest-sub { font-size: 12px; color: var(--text-muted); margin: 3px 0 10px; }
.module-digest-line { font-size: 12.5px; margin: 0 0 4px; padding-left: 14px; position: relative; }
.module-digest-line::before { content: ''; position: absolute; left: 0; top: 6px; width: 7px; height: 7px; border-radius: 50%; }
.module-digest-strength { color: var(--teal); }
.module-digest-strength::before { background: var(--teal); }
.module-digest-growth { color: var(--amber-dark); }
.module-digest-growth::before { background: var(--amber-dark); }
.module-digest-card .link-btn { margin-top: 6px; font-size: 12.5px; }
.module-digest-card-empty { display: flex; flex-direction: column; align-items: flex-start; justify-content: center; }
.module-digest-empty { font-size: 13px; color: var(--text-muted); margin: 2px 0 10px; }

/* Overview tab v2 (2026-08-10) — cross-module Levels strip, a "this
   week's focus" nudge, a consistency calendar, and personal bests, plus
   the digest card's new delta chip/sparkline/practice line. Reuses the
   same card shell (#fff, var(--border), var(--radius)) as every other
   Overview tile so these read as one coherent tab, not a bolted-on
   widget. `.overview-block` just gives each new section the same
   bottom margin the existing sections already use. */
/* Assign practice modal (2026-08-13) — parent picks a subject, then the
   areas within it; each area becomes its own to-do item for the child. */
.assign-modal-card { max-width: 620px; text-align: left; }
.assign-modal-card .section-title, .assign-modal-card .section-sub { text-align: left; margin-left: 0; margin-right: 0; }
.assign-empty { font-size: 14px; line-height: 1.6; color: var(--text-muted); margin: 0; }
.assign-modules { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 20px; }
.assign-module-pill {
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--navy-dark);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 16px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.assign-module-pill:hover { border-color: var(--teal); }
.assign-module-pill.active { background: var(--navy); border-color: var(--navy); color: #fff; }

.assign-areas-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.assign-areas-title { font-size: 13px; font-weight: 700; color: var(--navy-dark); margin: 0; }
.assign-areas {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-bottom: 20px;
}
.assign-area {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  color: var(--navy-dark);
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 9px 12px;
  cursor: pointer;
}
.assign-area:hover { border-color: var(--teal); }
.assign-area.checked { background: rgba(15, 123, 138, 0.08); border-color: var(--teal); font-weight: 600; }
.assign-area input { accent-color: var(--teal); width: 15px; height: 15px; flex-shrink: 0; cursor: pointer; }

.assign-count-row { margin-bottom: 18px; }
.assign-counts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.assign-count-pill {
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--navy-dark);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 15px;
  cursor: pointer;
}
.assign-count-pill:hover { border-color: var(--teal); }
.assign-count-pill.active { background: var(--teal); border-color: var(--teal); color: #fff; }

.assign-summary { font-size: 13px; line-height: 1.55; color: var(--navy-dark); margin: 0 0 6px; }
.assign-summary-warn { color: var(--amber-dark); font-weight: 600; }
.assign-queued { font-size: 12.5px; color: var(--text-muted); margin: 0 0 6px; }
.assign-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
.assign-actions .btn[disabled] { opacity: 0.5; pointer-events: none; }

.assign-toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translate(-50%, 16px);
  z-index: 1200;
  background: var(--navy-dark);
  color: #fff;
  font-size: 13.5px;
  font-weight: 600;
  padding: 12px 20px;
  border-radius: 999px;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.assign-toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .assign-toast { transition: none; } }
@media (max-width: 560px) { .assign-areas { grid-template-columns: 1fr; } }

/* To-do list (2026-08-13) — the child's side of Assign practice. Leads the
   student dashboard; on the parent's Overview it sits under the
   recommendations as a read-out of what is still outstanding. */
.todo-card {
  background: #fff;
  border: 1px solid var(--border);
  border-left: 4px solid var(--amber);
  border-radius: var(--radius);
  padding: 18px 20px 16px;
}
.todo-card-empty { border-left-color: var(--border); }
.todo-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.todo-eyebrow {
  font-family: 'Poppins', sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0;
}
.todo-count {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--amber-dark);
  background: rgba(243, 113, 48, 0.14);
  border-radius: 999px;
  padding: 3px 10px;
  white-space: nowrap;
}
.todo-sub { font-size: 12.5px; color: var(--text-muted); margin: 4px 0 14px; }
.todo-empty { font-size: 13px; line-height: 1.6; color: var(--text-muted); margin: 0; }
.todo-items { display: flex; flex-direction: column; gap: 8px; }
.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 10px;
  padding: 11px 14px;
}
.todo-cognitive { border-left-color: var(--navy); }
.todo-spelling { border-left-color: var(--teal); }
.todo-maths { border-left-color: var(--amber-dark); }
.todo-gk { border-left-color: var(--violet); }
.todo-check {
  flex-shrink: 0;
  width: 17px;
  height: 17px;
  border: 2px solid var(--border);
  border-radius: 5px;
  background: #fff;
}
.todo-body { flex: 1; min-width: 0; }
.todo-title { font-size: 14px; font-weight: 700; color: var(--navy-dark); margin: 0 0 2px; }
.todo-meta { font-size: 11.5px; color: var(--text-muted); margin: 0; }
.todo-cta { flex-shrink: 0; }
.todo-remove { flex-shrink: 0; font-size: 12px; }

@media (max-width: 520px) {
  .todo-item { flex-wrap: wrap; }
  .todo-cta { width: 100%; text-align: center; }
}

.overview-block { margin-bottom: 24px; }

/* Standing caveat under Observations and Recommendations (2026-08-13) —
   dashed rather than solid so it reads as a footnote to the section it
   closes, not as another finding competing for attention. */
/* NO BREAKPOINT, AND DELIBERATELY NOT ONE. This box renders both inside the
   tile panel - up to 1000px wide, whatever the window is doing - and inside a
   page column that may be half that on the same screen. A viewport query
   cannot tell those apart and would have laid one of them out for the wrong
   width, the same trap the recommendation tabs fell into. Flex basis decides
   it from the space actually available: the lead sits beside the explanation
   while both fit, and the explanation drops to its own full-width line when
   they do not. */
.overview-disclaimer {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: 16px;
  row-gap: 6px;
  margin: 16px 0 0;
  padding: 13px 16px;
  background: var(--bg-light);
  border: 1px dashed var(--border);
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-muted);
}
.overview-disclaimer strong { color: var(--navy-dark); font-weight: 600; }
.overview-disclaimer-icon { flex: 0 0 auto; color: var(--teal); font-size: 14px; line-height: 1.5; }
.overview-disclaimer-lead { flex: 0 1 auto; min-width: 150px; max-width: 230px; font-weight: 600; color: var(--navy-dark); }
/* Columns rather than one long line. The box now spans the panel, and a
   measure that suits a 620px column becomes a 120-character line at 1000px -
   so the width is spent on a second column instead of on longer lines. The
   count is left to column-width: two where there is room, one where there is
   not, without a breakpoint to keep in step with the panel. */
/* Columns rather than one long line: the box spans the panel now, and a
   measure that suits a 600px column becomes a 120-character line at 1000px -
   so the extra width buys a second column instead of longer lines. The count
   is left to column-width, which gives two where there is room and one where
   there is not, with nothing to keep in step. */
.overview-disclaimer-body { flex: 1 1 340px; column-width: 34ch; column-gap: 26px; }

/* Activity (2026-08-13) — one section holding time-on-task, plan/streak
   stats and the next-badge nudge, which used to be three loose blocks.
   Time this week leads because it's the thing that changes daily; the
   week tiles are deliberately shrunk here so the section reads as one
   compact summary rather than three competing rows. */
.overview-activity {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px 22px 22px;
}
.overview-activity .profile-subsection-title { margin: 0 0 4px; }
.overview-activity-sub { font-size: 13px; line-height: 1.5; color: var(--text-muted); margin: 0 0 16px; }
/* Weekly usage hero (2026-08-13) — the headline of the Activity section:
   a progress ring, where the week stands against the 3-hour target, and
   the next step already worked out. Tone drives the accent so the status
   reads before any of the words do. */
.usage-hero {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  border: 1px solid var(--border);
  border-left: 4px solid var(--usage-accent, var(--teal));
  border-radius: 14px;
  padding: 16px 20px;
  margin-bottom: 10px;
  background: var(--usage-bg, var(--bg-light));
}
.uh-cold { --usage-accent: var(--amber-dark); --usage-bg: rgba(243, 113, 48, 0.10); }
.uh-low { --usage-accent: var(--amber-dark); --usage-bg: rgba(243, 113, 48, 0.08); }
.uh-mid { --usage-accent: var(--amber); --usage-bg: rgba(243, 113, 48, 0.06); }
.uh-close { --usage-accent: var(--teal); --usage-bg: rgba(15, 123, 138, 0.07); }
.uh-done { --usage-accent: var(--teal); --usage-bg: rgba(15, 123, 138, 0.10); }

.usage-hero-ring {
  flex-shrink: 0;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: conic-gradient(var(--usage-accent) 0% var(--usage-pct, 0%), rgba(40, 41, 114, 0.10) var(--usage-pct, 0%) 100%);
}
.usage-hero-ring-inner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #fff;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 1px;
  font-family: 'Poppins', sans-serif;
  font-size: 17px;
  font-weight: 700;
  color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
  line-height: 48px;
}
.usage-hero-ring-inner span { font-size: 10px; font-weight: 600; color: var(--text-muted); }

.usage-hero-body { flex: 1; min-width: 190px; }
.usage-hero-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 0 0 4px;
}
.usage-hero-chip {
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  background: var(--usage-accent);
  border-radius: 999px;
  padding: 2px 9px;
}
.usage-hero-value {
  font-family: 'Poppins', sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0 0 4px;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.usage-hero-value span { font-size: 14px; font-weight: 600; color: var(--text-muted); }
.usage-hero-msg { font-size: 13.5px; line-height: 1.55; color: var(--navy-dark); margin: 0; max-width: 54ch; }
.usage-hero-cta { flex-shrink: 0; font-size: 13.5px; padding: 9px 16px; }
.usage-hero-note { font-size: 11.5px; line-height: 1.5; color: var(--text-muted); margin: 0 0 14px; }

@media (max-width: 620px) {
  .usage-hero { gap: 14px; padding: 14px 16px; }
  .usage-hero-cta { width: 100%; text-align: center; }
}

.overview-activity .progress-week-row { gap: 10px; margin-bottom: 12px; }
/* Label and value share a row so each tile is three lines, not four —
   the whole point of folding these into one section was to stop them
   dominating the tab. */
.overview-activity .progress-week-card {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: 8px;
  padding: 10px 13px 11px;
  border-radius: 10px;
}
.overview-activity .progress-week-label { grid-column: 1; font-size: 11px; margin: 0; }
.overview-activity .progress-week-value { grid-column: 2; grid-row: 1; font-size: 17px; margin: 0; }
.overview-activity .progress-week-split { grid-column: 1 / -1; height: 4px; margin: 6px 0 5px; }
.overview-activity .progress-week-note { grid-column: 1 / -1; font-size: 10.5px; }
.overview-activity .stat-row { gap: 10px; margin-bottom: 0; }
.overview-activity .stat-tile { padding: 11px 12px; }
.overview-activity .stat-tile .num { font-size: 17px; }
.overview-activity .stat-tile .label { font-size: 11px; }
.overview-activity .overview-badge-line { margin: 14px 0 0; }

.module-digest-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.module-digest-top > div { min-width: 0; }
.module-digest-spark { margin: 8px 0 6px; }
.module-digest-spark .progress-sparkline { width: 100%; height: 28px; display: block; }
.module-digest-practice { font-size: 11.5px; color: var(--text-muted); margin: 10px 0 0; padding-top: 10px; border-top: 1px dashed var(--border); }

.overview-delta-chip { flex-shrink: 0; font-size: 11.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.overview-delta-chip.up { color: var(--teal); background: rgba(15, 123, 138, 0.1); }
.overview-delta-chip.down { color: var(--amber-dark); background: rgba(184, 76, 24, 0.12); }
.overview-delta-chip.steady { color: var(--text-muted); background: var(--bg-light); }

.overview-nudge-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: #FFF4EC;
  border: 1px solid #F0CBAC;
  border-radius: var(--radius);
  padding: 16px 20px;
}
.overview-nudge-icon {
  font-size: 22px;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #FBDFC9;
  display: flex;
  align-items: center;
  justify-content: center;
}
.overview-nudge-body { flex: 1; min-width: 0; }
.overview-nudge-title { font-weight: 700; font-size: 14.5px; color: var(--navy-dark); margin: 0 0 2px; }
.overview-nudge-sub { font-size: 13px; color: var(--text-muted); margin: 0; }
.overview-nudge-cta {
  flex-shrink: 0;
  background: var(--navy-dark);
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  display: inline-block;
}
.overview-nudge-cta:hover { background: var(--navy); }
.overview-all-good-card {
  background: #EFF8FA;
  border: 1px solid #BFE3DC;
  border-radius: var(--radius);
  padding: 14px 20px;
  font-size: 13.5px;
  color: var(--navy-dark);
}

/* FOUR columns, because there are four modules. This said repeat(3, 1fr) -
   written when there were three - so General Knowledge wrapped onto a second
   row on its own and the strip read as 3 subjects plus an afterthought.
   Explicit counts rather than auto-fit: auto-fit picks 3 columns at some
   widths and puts us straight back to a 3 + 1 orphan. */
.overview-levels-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
@media (max-width: 1040px) { .overview-levels-row { grid-template-columns: repeat(2, 1fr); } }
.overview-level-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 16px;
  text-align: left;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* Every card is the same four parts in the same order, so an untested
   subject lines up with a tested one instead of leaving a hole. */
.ov-lvl-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 20px; }
.overview-level-module { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin: 0; }

/* --- the five-step ladder (2026-08-26, replaces the ring) --------------
   Five boxes, numbered: the step you are on is filled, the ones behind it
   are dimmed, the ones ahead are empty and visibly still there. */
.overview-ladder { display: flex; gap: 4px; margin: 12px 0 10px; }
.ov-step {
  flex: 1 1 0;
  min-width: 0;
  height: 30px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  background: var(--bg-light);
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.ov-step.is-done { background: rgba(15, 123, 138, 0.16); border-color: rgba(15, 123, 138, 0.3); color: var(--teal); }
.ov-step.is-here { background: var(--teal); border-color: var(--teal); color: #fff; }
.overview-ladder.is-blank .ov-step { color: #C9C9D6; }

/* The level name and the number share a line - the number used to sit on a
   line of its own under it, which is a whole row of card height spent on
   two words and four characters that belong together. */
.ov-lvl-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.overview-level-name { font-size: 13px; font-weight: 400; color: var(--text-dark); margin: 0; }
.overview-level-headline {
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 16px;
  color: var(--navy-dark);
  margin: 0;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.overview-level-empty { font-size: 13px; color: var(--text-muted); margin: 0; }
/* Holds the number's place on a card that has no number, so the four cards
   line up line for line whether or not a test has been sat. */
.overview-level-headline-blank { color: var(--text-muted); font-weight: 600; }

.ov-lvl-bar { height: 6px; border-radius: 3px; background: var(--bg-light); margin-top: 10px; overflow: hidden; }
.ov-lvl-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--teal), var(--amber)); }

/* Compact GitHub-style heatmap (2026-08-12) — fixed 16px day cells with
   the summary/legend beside the grid, not under it. The old repeat(7, 1fr)
   + aspect-ratio version stretched each day to a seventh of the column
   width, which ballooned into a screen-filling wall of squares. */
.overview-calendar-card {
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 20px;
}
.overview-calendar-grid { display: grid; grid-template-columns: repeat(7, 16px); gap: 4px; }
.overview-cal-cell { width: 16px; height: 16px; border-radius: 4px; background: var(--bg-light); }
.overview-calendar-side { flex: 1; min-width: 180px; }
.overview-calendar-count { font-size: 14px; color: var(--navy-dark); margin: 0 0 2px; }
.overview-calendar-count strong { font-family: 'Poppins', sans-serif; font-size: 18px; }
.overview-calendar-hint { font-size: 12.5px; color: var(--text-muted); margin: 0 0 8px; }
.overview-cal-cell.l1 { background: #C9E9E4; }
.overview-cal-cell.l2 { background: #6FC0CE; }
.overview-cal-cell.l3 { background: var(--teal); }
.overview-calendar-legend { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--text-muted); }
.overview-calendar-legend-swatches { display: flex; gap: 3px; align-items: center; }
.overview-calendar-legend-swatches .overview-cal-cell { width: 11px; height: 11px; }

.overview-bests-row { display: flex; gap: 12px; flex-wrap: wrap; }
.overview-best-chip { display: flex; align-items: center; gap: 10px; background: #fff; border: 1px solid var(--border); border-radius: 999px; padding: 8px 16px 8px 10px; }
.overview-best-icon { font-size: 16px; }
.overview-best-text { font-size: 12.5px; color: var(--text-muted); }
.overview-best-text strong { color: var(--navy-dark); }

@media (max-width: 720px) {
  .overview-levels-row { grid-template-columns: 1fr; }
  .overview-nudge-card { flex-direction: column; align-items: flex-start; }
  .overview-nudge-cta { width: 100%; text-align: center; }
}

/* Child selector (2026-08-07) — a dropdown in the header, next to the
   logo, replacing the old below-header tab row. Scales to any number of
   children without the header ever getting wider (the closed button is
   always just the current child), and folds "+ Register a child" in as
   the menu's last row instead of a separate always-visible header button. */
.child-selector { position: relative; }
.child-selector-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 6px;
  border-radius: 999px;
  border: 1px solid rgba(15, 123, 138, 0.3);
  background: rgba(15, 123, 138, 0.045);
  cursor: pointer;
  font-family: 'Poppins', sans-serif;
}
.child-selector-toggle:hover { border-color: var(--teal); }
.child-selector-avatar { width: 32px; height: 32px; font-size: 13px; }
.child-selector-name { font-size: 14px; font-weight: 700; color: var(--navy); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.child-selector-caret { font-size: 10px; color: var(--text-muted); }
.child-selector-menu {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 220px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 8px;
  display: none;
  z-index: 20;
}
.child-selector.open .child-selector-menu { display: block; }
/* THE MENU HAS TO CLEAR THE SWITCH BANNER (2026-08-27).
   The menu was painting UNDERNEATH .child-switch-banner, so in a child's
   view the top half of the dropdown - the child you are already viewing -
   was hidden behind a teal bar.
   Raising .child-selector-menu cannot fix it. .auth-header is position:
   relative with z-index: 30, which makes it a STACKING CONTEXT: every
   z-index inside is ranked against its siblings within the header, and the
   header as a whole is then ranked at 30 against the banner's 80. The menu
   could be z-index: 9999 and still lose.
   So the header is what lifts, and only while the menu is open - the rest
   of the time its stacking is left exactly as it was. 90 clears the banner
   (80) and the impersonation banner (50) while staying under .modal-overlay
   (100), so an open dialog is still never painted over by the header. */
.auth-header:has(.child-selector.open) { z-index: 90; }
.child-selector-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 8px 10px;
  border-radius: 9px;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
}
.child-selector-item:hover { background: var(--bg-light); }
.child-selector-item.active { background: var(--bg-light); }
.child-selector-item-avatar { width: 30px; height: 30px; font-size: 12px; flex-shrink: 0; }
/* Shrink-to-fit, not flex:1 — a short name (e.g. "Nihar") used to get
   stretched apart from its checkmark by a wide empty gap, most visible
   on the highlighted/active row where the background made the gap
   obvious. A capped max-width still ellipsizes a genuinely long name. */
.child-selector-item-text { flex: 0 1 auto; min-width: 0; max-width: 150px; }
.child-selector-item-name { margin: 0; font-size: 13px; font-weight: 600; color: var(--navy); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.child-selector-item-age { margin: 0; font-size: 11px; color: var(--text-muted); }
.child-selector-item-check { color: var(--teal); font-size: 14px; flex-shrink: 0; margin-left: 2px; }
.child-selector-divider { height: 1px; background: var(--border); margin: 6px 4px; }
/* --- Shared table date ranges (2026-08-23) ---------------------------- */
.admin-daterange { display: inline-flex; align-items: center; gap: 6px; }
.admin-date-input {
  font-family: inherit;
  font-size: 13.5px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: #fff;
  color: var(--text-dark);
  min-width: 138px;
}
.admin-date-input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.admin-daterange-sep { font-size: 12.5px; color: var(--text-muted); }
.admin-daterange-clear {
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 19px;
  line-height: 1;
  padding: 2px 6px;
  cursor: pointer;
  border-radius: 6px;
}
.admin-daterange-clear:hover { color: var(--amber-dark); background: rgba(184, 76, 24, 0.08); }
@media (max-width: 720px) {
  .admin-daterange { flex-wrap: wrap; }
  .admin-date-input { min-width: 0; flex: 1; }
}

/* --- Admin management + capability visibility (2026-08-23) ------------ */
/* Whole surfaces an admin without the capability never sees. The API strips
   the data as well - admin-dashboard.js drops revenue, admin-audit-list.js
   drops payment rows - so this is tidiness, not the control. Anyone can open
   a network tab; a permission enforced only in CSS is a suggestion. */
body.cannot-payments [data-tab="money"],
body.cannot-payments #stat-total-revenue,
body.cannot-payments #chart-revenue,
body.cannot-audit [data-tab="audit"] { display: none !important; }

.admin-inline-form { margin: 18px 0 8px; padding: 18px 20px; background: var(--bg-light); border-radius: 12px; }
.admin-field { display: flex; flex-direction: column; gap: 5px; max-width: 380px; margin-bottom: 14px; }
.admin-field span { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.admin-field input { font-family: inherit; font-size: 14.5px; padding: 9px 11px; border: 1px solid var(--border); border-radius: 9px; }
.admin-caps { border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; background: #fff; }
.admin-caps legend { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--navy); padding: 0 6px; }
.admin-caps-note { margin: 0 0 10px; font-size: 13px; color: var(--text-muted); }
.admin-caps-grid { display: flex; flex-wrap: wrap; gap: 10px 20px; }
.admin-cap { display: flex; align-items: center; gap: 7px; font-size: 14px; cursor: pointer; }
.admin-cap-super { margin: 12px 0; padding-top: 12px; border-top: 1px dashed var(--border); }
.admin-form-actions { display: flex; gap: 10px; }
.admin-new-result { margin: 14px 0; padding: 16px 18px; border: 2px solid var(--teal); border-radius: 12px; background: rgba(15, 123, 138, 0.06); }
.admin-temp-password { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 17px; font-weight: 700; color: var(--navy); background: #fff; padding: 10px 14px; border-radius: 8px; display: inline-block; }
.admin-muted { color: var(--text-muted); }
.admin-row-disabled { opacity: 0.55; }
.admin-row-actions { white-space: nowrap; }
.admin-edit-row > td { background: var(--bg-light); }
.admin-cap-cell { font-size: 13.5px; }

/* --- Report stepper (2026-08-24) ---------------------------------------
   LIVES HERE, NOT IN report.css. The report is EMBEDDED in profile.html,
   which loads style.css and auth.css but not report.css - styling it there
   reached the standalone page only, and parents saw unstyled controls in the
   one place they actually read the report. */

.report-stepper { margin: 0 0 24px; }

/* The rails are drawn on the ROW, not per-dot, so they cannot end up half a
   pixel out between circles at odd zoom levels. */
.report-stepper-dots {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 4px;
  margin: 0 0 16px;
  padding-top: 2px;
}
.report-stepper-dots::before {
  content: "";
  position: absolute;
  left: 16px;
  right: 16px;
  top: 17px;
  height: 3px;
  border-radius: 2px;
  background: var(--border);
  z-index: 0;
}

/* The travelled part of the rail. Shows how much is BEHIND you, which is the
   question someone has when deciding whether to keep reading - a row of
   circles alone only answers where you are. */
.report-stepper-fill {
  position: absolute;
  left: 16px;
  top: 17px;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--teal), var(--navy));
  z-index: 0;
  max-width: calc(100% - 32px);
  transition: width 0.32s cubic-bezier(.4, 0, .2, 1);
}

.report-stepper-dot {
  position: relative;
  z-index: 1;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  min-width: 0;
}
.report-stepper-dot:focus-visible { outline: none; }

/* The circle itself is the ::before so the button can also carry a caption
   underneath without the two fighting over the same box. */
.rsd-num, .rsd-tick {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: #fff;
  color: var(--text-muted);
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.rsd-tick { display: none; font-size: 15px; }

.report-stepper-dot:hover .rsd-num,
.report-stepper-dot:hover .rsd-tick { border-color: var(--navy); color: var(--navy); }
.report-stepper-dot:focus-visible .rsd-num,
.report-stepper-dot:focus-visible .rsd-tick { outline: 2px solid var(--teal); outline-offset: 3px; }

/* DONE SHOWS A TICK, not the number. A number says "you are here-ish"; a tick
   says "you have read this", which is the difference between a position
   indicator and a progress one. */
.report-stepper-dot.done .rsd-num { display: none; }
.report-stepper-dot.done .rsd-tick {
  display: flex;
  background: var(--teal);
  border-color: var(--teal);
  color: #fff;
}

.report-stepper-dot.active .rsd-num {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
  transform: scale(1.16);
  /* The soft ring is what makes the current step read as "you are here"
     rather than merely as another filled circle. */
  box-shadow: 0 0 0 5px rgba(40, 41, 114, 0.12), 0 4px 12px rgba(40, 41, 114, 0.32);
}

.rsd-label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text-muted);
  text-align: center;
  max-width: 92px;
  /* Hidden below 720px - six captions on a phone become six columns of
     single letters, which is worse than no captions at all. */
  display: none;
}
.report-stepper-dot.active .rsd-label { color: var(--navy); }
.report-stepper-dot.done .rsd-label { color: var(--teal); }

/* COUNT ON ITS OWN LINE, title beneath it. Side by side on one baseline,
   "STEP 5 OF 7" and "Compared to last time" competed for the same glance and
   the long titles pushed the pair onto two ragged lines anyway. Stacked, it
   is the ordinary eyebrow-over-headline shape and the title starts at the
   same x on every step. */
.report-stepper-label { margin: 0; display: block; }
.rsl-count {
  display: block;
  margin-bottom: 3px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.rsl-title {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--navy);
}

/* Sits between the numbers it describes and the step title, with enough air
   under it that the two small grey lines - this and the STEP N OF M eyebrow -
   do not read as one stacked pair. */
.report-stepper-hint {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.5;
}
.report-stepper-hint strong { color: var(--navy); }

.report-tabpanel > *:first-child { margin-top: 0; }

.report-stepnav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 30px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
.report-stepnav-next:disabled {
  background: var(--bg-light);
  color: var(--text-muted);
  box-shadow: none;
  cursor: default;
}

/* EVERY PANEL PRINTS, whatever is on screen - see the beforeprint handler in
   js/report-tabs.js. A PDF of one step is not the report a parent asked for.
   The handler opens every fold for the print as well; these rules take the
   summary rows and the Open-all button back out, since a printed page has no
   controls to press. */
@media print {
  .report-stepper, .report-stepnav { display: none !important; }
  .report-tabpanel { display: block !important; }
}

@media (min-width: 720px) {
  .rsd-label { display: block; }
}

@media (max-width: 560px) {
  .rsl-title { font-size: 17px; }
  .rsd-num, .rsd-tick { width: 28px; height: 28px; font-size: 12px; }
  .report-stepper-dots::before, .report-stepper-fill { top: 15px; }
  .report-stepper-hint { font-size: 12.5px; }
  /* Stacked and full width, so neither button is a thumb-sized target
     squeezed against the screen edge. */
  .report-stepnav { flex-direction: column-reverse; align-items: stretch; }
  .report-stepnav .btn { width: 100%; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .rsd-num, .rsd-tick, .report-stepper-fill { transition: none; }
  .rsd-num { animation: none !important; }
  .report-stepper-dot.active .rsd-num { transform: none; }
}

/* --- Impersonation replica (2026-08-24) ------------------------------- */
/* Clears the fixed banner so the replica does not start underneath it. */
body.impersonating { padding-top: 56px; }
/* Parent-only chrome that would be a lie inside a read-only replica: signing
   out of an account you are not signed in to, and a menu whose every action
   is discarded. The portal renders them because it does not know it is being
   impersonated - that ignorance is what keeps the replica faithful, so the
   few things that cannot apply are removed here instead. */
body.impersonating .profile-menu,
body.impersonating #admin-sign-out-btn { display: none !important; }

/* --- Roles (2026-08-23) ---------------------------------------------- */
/* Super-Admin-only controls, hidden from a plain Admin. A GUARD RAIL, NOT THE
   LOCK: requireSuperAdmin() in the API is what actually refuses these, and it
   refuses them whatever this stylesheet says. Hiding the button only spares
   someone a click that was always going to fail. */
body.is-plain-admin .super-only { display: none !important; }
.admin-role-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
body.is-super-admin .admin-role-badge { background: rgba(40, 41, 114, 0.10); color: var(--navy); }
body.is-plain-admin .admin-role-badge { background: rgba(15, 123, 138, 0.10); color: var(--teal); }

/* --- Audit date + time filtering (2026-08-23) ------------------------- */
.audit-presets { display: flex; gap: 6px; }
.audit-preset {
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 14px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.audit-preset:hover { color: var(--navy); border-color: var(--navy); }
.audit-preset.active { background: var(--navy); border-color: var(--navy); color: #fff; }

.audit-range {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin: 16px 0 10px;
  padding: 14px 16px;
  background: var(--bg-light);
  border-radius: 12px;
}
.audit-range-field { display: flex; flex-direction: column; gap: 5px; }
.audit-range-field span {
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.audit-range-field input {
  font-family: inherit;
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: #fff;
  color: var(--text-dark);
}
.audit-range-field input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.audit-search-field { flex: 1 1 220px; min-width: 200px; }
.audit-search-field input {
  font-family: inherit;
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: #fff;
  color: var(--text-dark);
  width: 100%;
}
.audit-search-field input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.audit-range-note { margin: 0 0 14px; font-size: 13.5px; color: var(--text-muted); min-height: 18px; }

@media (max-width: 620px) {
  .audit-range { gap: 10px; }
  .audit-range-field { flex: 1 1 100%; }
  .audit-range .btn { flex: 1; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) { .audit-preset { transition: none; } }

/* The audit sentence is the column that matters, so it gets the room. */
.audit-summary { line-height: 1.55; }
.audit-ip { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }

/* The tabbar badge is a bare number, so it needs to read as a count rather
   than as part of the tab label. */
#admin-tabbar .admin-count-badge {
  margin-left: 6px;
  background: var(--amber-dark);
  color: #fff;
  border-radius: 999px;
  padding: 1px 7px;
  font-size: 11px;
  font-weight: 700;
}
.admin-export-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }

/* --- Switch to a child's view (2026-08-23) ---------------------------- */
.child-view-switch { margin-right: 12px; }
@media (max-width: 560px) {
  .child-view-switch { margin-right: 0; margin-bottom: 10px; width: 100%; justify-content: center; }
}


/* DIRECTLY UNDER THE HEADER (2026-08-27). This was fixed to the foot of the
   window, on the reasoning that every page carrying it had a sticky header at
   top: 0 and a second sticky bar would either overlap it or need that height
   hard-coded. All thirteen of these pages use .auth-header, which is
   position: relative - so there was nothing to collide with, and the warning
   was sitting at the far end of the page from the thing it describes.

   Sticky rather than in the flow: it pins to the top once the header scrolls
   past, so "everything here is recorded as theirs" is never scrolled away. It
   outranks the header, which passes underneath it. */
/* THE SAME RIBBON, IN TWO STATES (2026-08-27).
   Teal means the child is at home and the parent can take the device back.
   Orange means they are inside a test or a practice session, where the way
   out is their own home page - "Back to parent view" is not offered there,
   because pressing it mid-question abandons a part-answered sitting.

   #B84C18 is --amber-dark, already in the palette. Brand --amber (#F37130)
   measures 2.91:1 behind white text and fails; this measures 5.14:1. It
   darkens downward to #8F380D (7.67:1) exactly as the teal one does, and for
   the same reason: the top stop has to pass on its own rather than the
   gradient passing on average. */
.child-switch-banner.is-activity {
  background: linear-gradient(180deg, #B84C18 0%, #8F380D 100%);
  box-shadow: 0 6px 18px rgba(184, 76, 24, 0.28);
}
.ribbon-home-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 15px 7px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #fff; font-family: 'Poppins', sans-serif; font-size: 13.5px;
  font-weight: 600; text-decoration: none; cursor: pointer; white-space: nowrap;
}
.ribbon-home-btn svg { width: 17px; height: 17px; flex: 0 0 17px; }
.ribbon-home-btn:hover,
.ribbon-home-btn:focus-visible { background: rgba(255, 255, 255, 0.28); border-color: #fff; color: #fff; }

.child-switch-banner {
  position: sticky;
  top: 0;
  z-index: 80;
  /* Darkens downward, never upward: the lighter teal I first put at the top
     measured 4.19:1 against white, under the 4.5 this text needs. */
  background: linear-gradient(180deg, var(--teal) 0%, #0C6673 100%);
  color: #fff;
  box-shadow: 0 6px 18px rgba(15, 123, 138, 0.28);
}
.child-switch-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 20px;
  flex-wrap: wrap;
}
.child-switch-text {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14.5px;
  line-height: 1.4;
}
/* Says "being looked at" without spending a word on it. */
.child-switch-eye { width: 17px; height: 17px; flex: none; opacity: 0.9; }
.child-switch-text strong { font-weight: 700; }
.child-switch-banner .btn-ghost {
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #fff;
  flex-shrink: 0;
}
.child-switch-banner .btn-ghost:hover { background: rgba(255, 255, 255, 0.28); }
/* Nothing to reserve any more: the bar is in the flow at the top rather than
   floating over the foot of the page. */

/* Parent-only controls, hidden while a child is using the device. A GUARD
   RAIL, NOT A LOCK - see getChildSwitch() in js/auth.js. It stops a child
   wandering into billing by accident, which matters here because a plan
   enters the child's queue the moment Buy is clicked, and it stops them
   signing the parent out. It stops nobody who types a URL, and it is not
   meant to. */
body.child-switch-active .profile-menu,
body.child-switch-active #sidebar-buy-more-btn,
body.child-switch-active [data-action="see-plans"],
body.child-switch-active [data-archive-child],
body.child-switch-active #archive-section { display: none !important; }

@media (max-width: 560px) {
  .child-switch-inner { padding: 10px 14px; gap: 10px; }
  .child-switch-text { font-size: 13px; }
  /* On a phone the sentence and the button stack, so the bar is two rows -
     let it scroll away rather than pin a third of the screen open. */
  .child-switch-banner { position: static; }
}

.child-selector-add {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 10px 10px;
  border-radius: 9px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--teal);
  cursor: pointer;
}
.child-selector-add:hover { background: var(--bg-light); }
@media (max-width: 480px) {
  .child-selector-name { max-width: 100px; }
}

/* "Add a child" modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(27, 28, 80, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.modal-card {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  overflow-y: auto;
  text-align: left;
}
.purchase-confirm-card { max-width: 380px; text-align: center; }
.modal-card-wide { max-width: 1000px; }
.modal-close-btn {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--bg-light);
  color: var(--text-muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.modal-close-btn:hover { background: var(--border); color: var(--navy-dark); }

/* Archive confirm block — used inside the Archive/Unarchive modal. */
.hide-child-confirm {
  max-width: 480px;
  margin: 0 auto;
  padding: 18px 20px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: left;
}
.hide-child-confirm .section-sub { margin-bottom: 14px; }

.hidden-child-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.hidden-child-row:last-child { border-bottom: none; }
.hidden-child-row .name { font-weight: 600; color: var(--navy-dark); font-size: 14px; }
.child-slot-empty {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
}
.slot-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.slot-picker select {
  flex: 1 1 180px;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13.5px;
  font-family: inherit;
  color: var(--text-dark);
  background: #fff;
}
.slot-picker .btn { flex: 0 0 auto; padding: 9px 18px; font-size: 13.5px; }

.child-form .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 18px;
}

/* minmax(0, 1fr), NOT 1fr (2026-08-28). A bare 1fr keeps an implicit
   min-width:auto, so a column is never allowed to be narrower than its
   widest child - and a <select> is as wide as its longest option. "Dadra &
   Nagar Haveli and Daman & Diu" in the State list is 36 characters, which
   pushed its column past half the 560px card and gave the modal a second,
   horizontal scrollbar with the right-hand fields cut off. */
.child-form-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 4px 20px;
  align-items: start;
}
.child-form-grid .field { margin-bottom: 14px; min-width: 0; }
.child-form-grid .field-span-2 { grid-column: 1 / -1; }
.child-form-actions {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}
@media (max-width: 560px) {
  .child-form-grid { grid-template-columns: 1fr; }
}
.child-form label {
  font-size: 13px;
  font-weight: 600;
  color: var(--navy-dark);
}
.child-form input,
.child-form select,
.child-form textarea {
  /* The field fills its column instead of the column being sized by the
     field. Without this a long <option> sets the width of everything. */
  width: 100%;
  max-width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text-dark);
  background: #fff;
  resize: vertical;
}
.child-form input:focus,
.child-form select:focus,
.child-form textarea:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(15, 123, 138, 0.15);
}
.child-form .btn { margin-top: 6px; }

.field-optional {
  font-weight: 500;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
}

.child-form-divider {
  margin: 6px 0 2px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  font-weight: 700;
  color: var(--navy-dark);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.child-form-divider span {
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-muted);
}

.consent-block {
  margin-top: 4px;
  padding: 16px 18px;
  background: rgba(243, 113, 48, 0.08);
  border: 1px solid rgba(243, 113, 48, 0.3);
  border-radius: 12px;
}
.consent-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
  font-size: 13.5px;
  color: var(--text-dark);
  text-align: left;
}
.consent-checkbox-row input[type="checkbox"] {
  margin-top: 2px;
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  accent-color: var(--amber-dark);
}

.field-row {
  display: flex;
  gap: 10px;
}
.field-row > select,
.field-row > .field { flex: 1; }
.field-row > .field { margin-bottom: 0; }

.schedule-children {
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.schedule-child-row {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  background: var(--bg-light);
}
.schedule-child-info { margin-bottom: 4px; }

.payment-panel {
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #fff;
}
.payment-fee {
  font-size: 15px;
  color: var(--navy-dark);
  margin: 0 0 12px;
}
.payment-fee strong { color: var(--amber-dark); font-size: 18px; }
.payment-panel .field { margin-bottom: 0; }
.payment-panel .auth-demo-note { margin-top: 12px; margin-bottom: 0; text-align: left; }
.payment-actions {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}

/* Purchase catalog (per child, inside "Buy modules") */
.catalog-existing-list {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13.5px;
  color: var(--text-dark);
}

.status-pill {
  font-size: 11px;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.status-pill.active { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.status-pill.expired { background: rgba(179, 38, 30, 0.1); color: #B3261E; }
/* "Buy a plan" — every module x tier combination as its own tile, grouped
   in a column per module so they're still scannable, plus the All-3-
   Modules bundle as its own column. Replaces the old dropdown-based
   3-tier catalog per explicit instruction that every offering should be
   a separate, individually visible tile. */
.offer-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin: 20px 0 8px;
  align-items: stretch;
}
/* The catalogue splits into two groups (2026-08-21): Single Test is the only
   per-subject offer, everything else covers all four and is bought once.
   Without a label the second row reads as four more subject tiles. */
.offer-group-label {
  font-family: 'IBM Plex Mono', 'Poppins', sans-serif;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  margin: 22px 0 0;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.offer-group-label:first-of-type { margin-top: 8px; }
@media (max-width: 900px) {
  .offer-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .offer-grid { grid-template-columns: 1fr; }
}
@media (max-width: 900px) { .offer-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .offer-grid { grid-template-columns: 1fr; } }

.offer-column { display: flex; flex-direction: column; gap: 12px; }

.offer-tile {
  background: #fff;
  border: 1px solid var(--border);
  border-top: 3px solid var(--navy);
  border-radius: 12px;
  padding: 14px 14px 12px;
  text-align: center;
}
.offer-column.accent-spelling .offer-tile { border-top-color: var(--teal); }
.offer-column.accent-maths .offer-tile { border-top-color: var(--amber-dark); }
.offer-mod {
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--navy);
  margin-bottom: 6px;
}
.offer-column.accent-spelling .offer-mod { color: var(--teal); }
.offer-column.accent-maths .offer-mod { color: var(--amber-dark); }
.offer-desc { font-size: 13px; font-weight: 600; color: var(--text-dark); line-height: 1.35; margin-bottom: 10px; min-height: 34px; }
.offer-price { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 16px; color: var(--navy-dark); margin-bottom: 10px; }
.offer-price span { font-size: 11px; font-weight: 500; color: var(--text-muted); }
.offer-tile .btn { width: 100%; }

.offer-column-bundle .offer-tile-bundle { flex: 1; }
.offer-column-bundle .offer-mod { color: var(--amber-dark); }
.offer-tile-bundle {
  position: relative;
  border: 2px solid var(--amber);
  border-top: 2px solid var(--amber);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 22px 14px;
}
.offer-badge {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--gradient-amber);
  color: var(--navy-dark);
  font-size: 10.5px;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}

/* "Create a login for your child" mini-panel */
.student-login-panel {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.student-login-open-btn { font-size: 13.5px; }

.profile-next {
  background: linear-gradient(120deg, rgba(15, 123, 138, 0.07), rgba(243, 113, 48, 0.08));
  border: 1px solid var(--border);
}

/* Admin page */
.admin-badge {
  background: var(--navy);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
}
.admin-demo-note {
  margin: 0 0 8px;
}
.admin-form { margin-top: 0; }

.field-error {
  display: none;
  font-size: 12.5px;
  color: #b3261e;
  margin: 4px 0 0;
}

.exam-dates-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.exam-date-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  background: var(--bg-light);
}
.exam-date-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.exam-date-header h3 {
  font-size: 16px;
  color: var(--navy-dark);
  margin: 0;
}
.slot-badge-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.slot-badge {
  font-size: 12px;
  font-weight: 600;
  color: var(--navy);
  background: rgba(40, 41, 114, 0.08);
  padding: 5px 11px;
  border-radius: 999px;
}

/* Dashboard */
.exam-status-list {
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.exam-child-group .exam-status-top {
  margin-bottom: 12px;
}
.exam-child-modules {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
@media (max-width: 720px) {
  .exam-child-modules { grid-template-columns: 1fr; }
}
.exam-status-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  background: var(--bg-light);
}
.exam-add-child-card {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  border: 1.5px dashed var(--border);
  border-radius: 12px;
  color: var(--teal);
  font-weight: 600;
  font-size: 14.5px;
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s;
}
.exam-add-child-card:hover { border-color: var(--teal); background: rgba(15, 123, 138, 0.05); }
.exam-add-child-plus { font-size: 18px; line-height: 1; }
.exam-status-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
/* .child-standard-badge/.student-id-badge normally carry their own
   margin-left/margin-top for contexts with no flex `gap` (e.g. inline
   inside a name's text flow) — this row already spaces its children via
   `gap` above, so their own margins would just double up and, for
   .student-id-badge specifically, push it visibly out of vertical center
   with its siblings (margin lives inside the centered flex item's own box). */
.exam-status-top .child-standard-badge { margin-left: 0; }
.exam-status-top .student-id-badge { margin-top: 0; }
.dashboard-add-child-btn { flex-shrink: 0; white-space: nowrap; }
.exam-status-meta {
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0 0 12px;
}
.exam-status-module {
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 13.5px;
  color: var(--teal);
  margin: 0 0 6px;
}
.countdown-block {
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.countdown-value {
  font-family: 'Poppins', sans-serif;
  font-size: 24px;
  font-weight: 600;
  color: var(--navy-dark);
  margin: 0 0 6px;
}
.countdown-note {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
}
.exam-join-link { display: none; }
.countdown-closed {
  display: none;
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0;
}

/* Admin: login, stats, tables, finance */
.admin-login-main {
  min-height: 60vh;
  padding: 48px 24px;
}

.admin-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.admin-stat {
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-light);
  text-align: center;
}
.admin-stat-value {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: 26px;
  font-weight: 600;
  color: var(--navy-dark);
}
.admin-stat-label {
  display: block;
  font-size: 12.5px;
  color: var(--text-muted);
  margin-top: 4px;
}

.exam-date-capacity {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 12px;
}
.slot-badge-full {
  background: rgba(179, 38, 30, 0.1);
  color: #b3261e;
}

.slot-full-error {
  font-size: 12.5px;
  color: #b3261e;
  margin: 0 0 8px;
}
.slot-full-error:empty { display: none; }

.admin-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 20px;
}
.admin-filters select,
.admin-filters input {
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13.5px;
  font-family: inherit;
  color: var(--text-dark);
  background: #fff;
}
.admin-filters input { flex: 1; min-width: 200px; }

.admin-table-wrap { overflow-x: auto; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}
.admin-table th {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 2px solid var(--border);
  color: var(--navy-dark);
  font-weight: 600;
  white-space: nowrap;
}
.admin-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text-dark);
  white-space: nowrap;
}

.pay-status {
  font-size: 12px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
}
.pay-status-paid { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.pay-status-unpaid { background: rgba(179, 38, 30, 0.1); color: #b3261e; }

.admin-export-row {
  display: flex;
  gap: 12px;
  margin-top: 20px;
  flex-wrap: wrap;
}

/* Admin dashboard charts (2026-08-11) — reuses buildTrendChartSvg's own
   styling (progress-trend-svg), just laid out side by side. */
.admin-charts-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-top: 20px;
}
.admin-chart-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  background: var(--bg-light);
}
.admin-chart-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--navy-dark);
  margin: 0 0 10px;
}
.admin-chart-caption {
  font-size: 12px;
  color: var(--text-muted);
  margin: 8px 0 0;
}
@media (max-width: 720px) {
  .admin-charts-grid { grid-template-columns: 1fr; }
}

/* Admin CRUD (2026-08-11) — section header with an action button, and the
   small inline add-student/add-parent forms it reveals. */
.admin-section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}
.admin-inline-form {
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  margin: 16px 0;
  max-width: 420px;
}
.admin-form-actions {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}
.admin-row-actions {
  display: flex;
  gap: 8px;
  flex-wrap: nowrap;
}
.admin-row-actions .link-btn {
  font-size: 12.5px;
  white-space: nowrap;
}
.admin-row-actions .link-btn.danger { color: #b3261e; }
.admin-status-pill {
  font-size: 11.5px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
}
.admin-status-pill.active { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.admin-status-pill.archived { background: rgba(91, 107, 118, 0.12); color: var(--text-muted); }
.admin-card-actions {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.impersonate-banner {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--amber);
  color: var(--navy-dark);
}
.impersonate-banner-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 24px;
  flex-wrap: wrap;
}
.impersonate-banner-text { font-size: 13.5px; font-weight: 600; }
.impersonate-banner-text strong { color: var(--navy-dark); }
.impersonate-banner .btn-ghost {
  background: rgba(255, 255, 255, 0.55);
  border-color: var(--navy-dark);
}

.finance-total {
  font-size: 16px;
  color: var(--navy-dark);
  margin: 0 0 20px;
}
.finance-total strong { color: var(--amber-dark); font-size: 20px; }

@media (max-width: 720px) {
  .admin-stats-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  .auth-card { padding: 32px 24px; }
  .profile-section { padding: 24px 20px; }
  .admin-stats-grid { grid-template-columns: 1fr; }
}

/* Achievements/badges (2026-08-10) — Overview tab section + full gallery
   modal. Locked tiles reuse the amber "growth focus" semantic already
   established by .module-digest-growth; earned tiles get a solid teal
   accent instead of the neutral card border, so an earned badge visibly
   "pops" against a locked one without inventing a new color. */
.achievements-section { margin-bottom: 24px; }
.achievements-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.achievements-count {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.achievements-empty-note { text-align: left; margin: 0 0 14px; }

.badge-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 14px;
}
.badge-row-wrap { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-bottom: 0; }

.badge-tile {
  position: relative;
  display: flex;
  gap: 12px;
  align-items: flex-start;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
}
.badge-tile-earned { border-color: var(--teal); box-shadow: 0 0 0 1px rgba(15, 123, 138, 0.14) inset; }
.badge-tile-locked { background: var(--bg-light); }
.badge-tile-locked .badge-icon { opacity: 0.45; filter: grayscale(0.4); }
.badge-icon { font-size: 30px; line-height: 1; flex-shrink: 0; }
.badge-tile-body { min-width: 0; flex: 1; }
.badge-name { font-size: 13.5px; font-weight: 700; color: var(--navy-dark); margin: 0 0 2px; }
.badge-desc { font-size: 12px; color: var(--text-muted); margin: 0 0 8px; }
.badge-progress-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 6px;
}
.badge-progress-fill { height: 100%; background: var(--amber); border-radius: 999px; }
.badge-progress-label { font-size: 11.5px; color: var(--text-muted); margin: 0; }
.badge-progress-label-earned { color: var(--teal); font-weight: 600; }
.badge-share-btn {
  flex-shrink: 0;
  align-self: flex-start;
  background: var(--navy);
  color: #fff;
  border: none;
  border-radius: 999px;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  padding: 5px 12px;
  cursor: pointer;
}
.badge-share-btn:hover { background: var(--navy-dark); }

.badge-next-up {
  background: var(--bg-light);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 14px 18px;
}
.badge-next-up-label { font-size: 13px; color: var(--navy-dark); margin: 0 0 8px; }
.badge-next-up-label strong { color: var(--amber-dark); }
.badge-next-up .badge-progress-bar { margin-bottom: 6px; }
.badge-next-up .badge-progress-label { font-size: 12.5px; }

.badge-gallery-card { max-width: 900px; }
.badge-gallery-grid { margin-top: 16px; display: flex; flex-direction: column; gap: 20px; }
.badge-gallery-category-title {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  margin: 0 0 10px;
}

@media (max-width: 720px) {
  .badge-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .badge-row { grid-template-columns: 1fr; }
  .badge-tile { flex-wrap: wrap; }
  .badge-share-btn { margin-left: auto; }
}

/* Practice pages (2026-08-10) — plan/quota line + collapsible tips,
   shown on every picker screen across all 3 modules. */
.practice-plan-quota {
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg-light);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 14px;
  margin: 10px 0 18px;
  text-align: center;
}
.practice-plan-quota strong { color: var(--navy-dark); }
.practice-tips {
  margin: 0 0 18px;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
}
.practice-tips summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 13.5px;
  color: var(--teal);
}
.practice-tips ul { margin: 10px 0 2px; padding-left: 20px; }
.practice-tips li { font-size: 13px; color: var(--text-muted); margin-bottom: 6px; }

/* "Leaving the parent portal" modal (2026-08-10) — same .modal-overlay/
   .modal-card shell as every other modal in this app. */
.leaving-portal-actions { display: flex; gap: 10px; margin-top: 20px; }
.leaving-portal-actions .btn { flex: 1; text-align: center; }

/* ---- Assignments tab (2026-08-14) --------------------------------------
   Parent: "Assignments". Student: "My tasks". Same panel, readOnly split. */
.asg-block { margin-bottom: 30px; }
.asg-block-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.asg-block-title {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 16px; font-weight: 700; color: var(--navy-dark); margin: 0 0 4px;
}
.asg-block-sub { font-size: 13px; line-height: 1.55; color: var(--text-muted); margin: 0 0 14px; max-width: 62ch; }
.asg-empty { font-size: 13.5px; color: var(--text-muted); margin: 0; }

/* Only the parent ever sees this. */
.asg-credits { background: var(--bg-soft, #f7f9fa); border: 1px solid var(--border); border-radius: 14px; padding: 18px 20px; margin-bottom: 26px; }
.asg-credits-title { font-family: 'Poppins', Arial, Helvetica, sans-serif; font-size: 14px; font-weight: 700; color: var(--navy-dark); margin: 0 0 4px; }
.asg-credits-sub { font-size: 12.5px; line-height: 1.55; color: var(--text-muted); margin: 0 0 14px; max-width: 60ch; }
.asg-credit-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.asg-credit { background: #fff; border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.asg-credit-mod { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 0 0 6px; }
.asg-credit-val { font-family: 'Poppins', Arial, Helvetica, sans-serif; font-size: 18px; font-weight: 700; color: var(--navy-dark); margin: 0; font-variant-numeric: tabular-nums; }
.asg-credit-val span { font-size: 11.5px; font-weight: 500; color: var(--text-muted); margin-left: 4px; }
.asg-credit-mock { font-size: 14px; margin-top: 2px; }

.asg-suggestions, .asg-rows { display: flex; flex-direction: column; gap: 10px; }
.asg-suggestion, .asg-row {
  display: flex; align-items: center; gap: 14px;
  background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 13px 16px;
}
.asg-suggestion { border-left: 3px solid var(--navy); }
.asg-suggestion.asg-spelling { border-left-color: var(--teal); }
.asg-suggestion.asg-maths { border-left-color: var(--amber); }
.asg-row-body { flex: 1; min-width: 0; }
.asg-row-title { font-size: 14px; font-weight: 600; color: var(--navy-dark); margin: 0 0 2px; }
.asg-row-meta { font-size: 12.5px; color: var(--text-muted); margin: 0; }
.asg-row-note { font-size: 12px; color: var(--text-muted); }
.asg-tick {
  flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%;
  background: rgba(15, 123, 138, 0.14); color: var(--teal);
  display: inline-flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700;
}

/* Due chips. todo-due-over is parent-only; todo-due-soft is the child's
   "Still to do" — deliberately neutral, never a failure state. */
.todo-due {
  display: inline-block; margin-left: 8px; font-size: 11px; font-weight: 700;
  padding: 2px 9px; border-radius: 999px; vertical-align: middle;
  background: rgba(40, 41, 114, 0.08); color: var(--navy-dark);
}
.todo-due-over { background: rgba(243, 113, 48, 0.18); color: var(--amber-dark); }
.todo-due-soft { background: rgba(40, 41, 114, 0.07); color: var(--text-muted); }

.assign-due-input {
  margin-top: 10px; width: 100%; padding: 9px 12px;
  border: 1px solid var(--border); border-radius: 9px; font-size: 13.5px; font-family: inherit;
}

@media (max-width: 720px) {
  .asg-credit-row { grid-template-columns: 1fr; }
  .asg-suggestion, .asg-row { flex-direction: column; align-items: flex-start; }
}

/* A module that can't currently be assigned is shown greyed with the
   reason, never omitted — see assignBlockedReason(). */
.assign-module-pill.assign-module-blocked {
  opacity: 0.42;
  cursor: not-allowed;
  border-style: dashed;
}
.assign-blocked-note {
  font-size: 12px;
  color: var(--text-muted);
  margin: 8px 0 0;
  line-height: 1.5;
}

/* Practice vs mock chooser at the top of the assign modal (2026-08-14). */
.assign-types { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.assign-type-pill {
  display: flex; flex-direction: column; gap: 3px; text-align: left;
  padding: 12px 14px; border-radius: 11px;
  border: 1.5px solid var(--border); background: #fff; cursor: pointer;
  transition: border-color 150ms ease, background 150ms ease;
}
.assign-type-pill:hover { border-color: var(--teal); }
.assign-type-pill.active { border-color: var(--navy-dark); background: rgba(40, 41, 114, 0.05); }
.assign-type-name { font-size: 14px; font-weight: 700; color: var(--navy-dark); }
.assign-type-sub { font-size: 11.5px; line-height: 1.45; color: var(--text-muted); }

.assign-mock-note {
  background: var(--bg-soft, #f7f9fa); border: 1px solid var(--border);
  border-radius: 11px; padding: 14px 16px; margin-bottom: 4px;
}
.assign-mock-title { font-size: 14px; font-weight: 700; color: var(--navy-dark); margin: 0 0 4px; }
.assign-mock-sub { font-size: 12.5px; line-height: 1.55; color: var(--text-muted); margin: 0; }

@media (max-width: 560px) { .assign-types { grid-template-columns: 1fr; } }

/* All-tasks ledger. A table on purpose — this is the one place in the
   product meant for scanning, not reading. */
.asg-table-wrap { overflow-x: auto; }
.asg-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 640px; }
.asg-table th {
  text-align: left; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted);
  padding: 0 12px 8px 0; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.asg-table td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--border); color: var(--navy-dark); vertical-align: middle; }
.asg-table tr:last-child td { border-bottom: none; }
.asg-table a { color: var(--teal); font-weight: 600; text-decoration: none; }
.asg-table a:hover { text-decoration: underline; }

.asg-chip { display: inline-block; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }
.asg-chip-done { background: rgba(15, 123, 138, 0.14); color: var(--teal); }
.asg-chip-todo { background: rgba(40, 41, 114, 0.08); color: var(--navy-dark); }
.asg-chip-over { background: rgba(243, 113, 48, 0.18); color: var(--amber-dark); }

/* Who set the work. Teal for the child's own initiative rather than amber:
   practice nobody had to ask for is the best thing on this table, and amber
   is this palette's "needs attention" colour. */
.asg-chip-set { background: rgba(40, 41, 114, 0.09); color: var(--navy); }
.asg-chip-own { background: rgba(15, 123, 138, 0.12); color: #0A5C68; }
.asg-score {
  flex-shrink: 0; font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 15px; font-weight: 700; color: var(--navy-dark); font-variant-numeric: tabular-nums;
}
.asg-score span { font-size: 11px; font-weight: 500; color: var(--text-muted); margin-left: 3px; }

/* Per-session practice report, opened in place from the Completed table.
   Practice has no report page of its own and inventing a fourth one would
   repeat the duplication this week was spent undoing. */
.asg-score-cell { font-weight: 700; font-variant-numeric: tabular-nums; }
.asg-detail-toggle {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--teal); font-weight: 600; font-size: 13px; font-family: inherit;
}
.asg-detail-toggle:hover { text-decoration: underline; }
.asg-detail-row td { background: var(--bg-soft, #f7f9fa); padding: 0 !important; }
.asg-detail { padding: 16px 18px; }
.asg-detail-empty { padding: 14px 18px; font-size: 12.5px; color: var(--text-muted); margin: 0; }
.asg-detail-stats { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 14px; font-size: 12.5px; color: var(--text-muted); }
.asg-detail-stats strong {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 16px; color: var(--navy-dark); margin-right: 4px;
}
.asg-detail-areas { display: flex; flex-direction: column; gap: 7px; }
.asg-detail-area { display: flex; align-items: center; gap: 12px; }
.asg-detail-area-name { flex: 0 0 150px; font-size: 12.5px; color: var(--navy-dark); }
.asg-detail-area-bar { flex: 1; height: 7px; border-radius: 999px; background: rgba(40, 41, 114, 0.09); overflow: hidden; }
.asg-detail-area-bar span { display: block; height: 100%; background: var(--teal); border-radius: 999px; }
.asg-detail-area-pct { flex: 0 0 46px; text-align: right; font-size: 12px; font-weight: 700; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.asg-detail-missed { margin-top: 14px; }
.asg-detail-missed-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 0 0 7px; }
.asg-miss {
  display: inline-block; margin: 0 6px 6px 0; padding: 3px 10px;
  background: rgba(243, 113, 48, 0.16); color: var(--amber-dark);
  border-radius: 999px; font-size: 12px; font-weight: 600;
}
@media (max-width: 720px) { .asg-detail-area-name { flex-basis: 100px; } }


/* ---- Assignments tab, visual pass (2026-08-14) ------------------------ */

/* State first: three numbers read before any list. */
.asg-summary { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 24px; }
.asg-sum-tile {
  flex: 1 1 130px; display: flex; flex-direction: column; gap: 2px;
  padding: 16px 18px; border-radius: 14px;
  border: 1px solid var(--border); background: #fff;
  border-left-width: 4px;
}
.asg-sum-num {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 28px; font-weight: 700; line-height: 1; color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.asg-sum-label { font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.asg-sum-todo { border-left-color: var(--navy); }
.asg-sum-over { border-left-color: var(--amber); background: rgba(243, 113, 48, 0.06); }
.asg-sum-over .asg-sum-num { color: var(--amber-dark); }
.asg-sum-done { border-left-color: var(--teal); }
.asg-sum-done .asg-sum-num { color: var(--teal); }

/* Credits as a meter — a bare "970" says nothing without its total. */
.asg-credits { background: none; border: none; padding: 0; margin-bottom: 28px; }
.asg-credit-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.asg-credit { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
.asg-credit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
.asg-credit-mod { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.asg-credit-mocks {
  font-size: 10.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px;
  background: rgba(15, 123, 138, 0.13); color: var(--teal); white-space: nowrap;
}
.asg-credit-mocks.is-empty { background: rgba(40, 41, 114, 0.07); color: var(--text-muted); }
.asg-credit-val {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 19px; font-weight: 700; color: var(--navy-dark); margin: 0 0 9px;
  font-variant-numeric: tabular-nums; line-height: 1;
}
.asg-credit-val span { font-size: 11px; font-weight: 500; color: var(--text-muted); margin-left: 4px; }
.asg-credit-bar { display: block; height: 6px; border-radius: 999px; background: rgba(40, 41, 114, 0.08); overflow: hidden; }
.asg-credit-bar span { display: block; height: 100%; border-radius: 999px; background: var(--navy); }
.asg-credit-spelling .asg-credit-bar span { background: var(--teal); }
.asg-credit-maths .asg-credit-bar span { background: var(--amber); }
.asg-credit-gk .asg-credit-bar span { background: var(--violet); }

/* Suggestions read as offers, not list rows. */
.asg-suggestion { border-left-width: 4px; padding: 15px 18px; transition: box-shadow 150ms ease; }
.asg-suggestion:hover { box-shadow: 0 4px 14px rgba(11, 30, 48, 0.07); }

/* Tables: breathing room and a hover target, still scannable. */
.asg-table { min-width: 560px; }
.asg-table th { padding-bottom: 10px; }
.asg-table td { padding: 13px 14px 13px 0; }
.asg-table tbody tr:not(.asg-detail-row):hover { background: rgba(40, 41, 114, 0.022); }
.asg-table td:first-child { font-weight: 600; }

.asg-block-title { font-size: 17px; }

/* Module hero with its action buttons removed (2026-08-14). Assigning and
   starting now live only on the Assignments tab, so the hero's top row is
   a single column — without this it kept the two-column split and left a
   large empty right half where the buttons used to sit. */
.module-hero-top-solo { display: block; }
.module-hero-top-solo .module-hero-level { max-width: none; }
.module-hero-pointer {
  font-size: 12.5px;
  color: var(--text-muted);
  margin: 14px 0 0;
  padding-top: 13px;
  border-top: 1px solid var(--border);
}
.module-hero-pointer strong { color: var(--navy-dark); font-weight: 600; }
/* The focus line lost its button, so it no longer needs to reserve space
   for one — let the note run the full width instead of stopping short. */
.module-hero-focus .mhfocus-note { flex: 1; }

/* ---- Parental consent notice (DPDP Act 2023) — 2026-08-14 ------------- */
.consent-lead { font-size: 13px; color: var(--text-muted); margin: 0 0 10px; line-height: 1.55; }
.consent-open-btn {
  background: none; border: none; padding: 0; margin: 0 0 14px;
  font-family: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--teal); cursor: pointer; text-decoration: underline;
}
.consent-open-btn:hover { color: var(--navy-dark); }

.consent-modal-card { max-width: 720px; }
/* The notice is long by necessity — informed consent needs the detail. It
   scrolls inside the card so the accept button stays reachable rather than
   stranded past a wall of text. */
.consent-body {
  max-height: 52vh;
  overflow-y: auto;
  padding: 4px 18px 4px 0;
  margin: 4px 0 20px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.consent-body h3 {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 14px; font-weight: 700; color: var(--navy-dark);
  margin: 20px 0 6px;
}
.consent-body h3:first-child { margin-top: 14px; }
.consent-body p { font-size: 13.5px; line-height: 1.65; color: var(--text-muted); margin: 0 0 8px; }
.consent-body ul { margin: 0 0 10px; padding-left: 20px; }
.consent-body li { font-size: 13.5px; line-height: 1.6; color: var(--text-muted); margin-bottom: 6px; }
.consent-body strong { color: var(--navy-dark); font-weight: 600; }
.consent-body a { color: var(--teal); font-weight: 600; }

.consent-modal-actions { display: flex; gap: 12px; flex-wrap: wrap; }

@media (max-width: 1000px) {
  .parent-glance .pg-rows {
    grid-template-columns: repeat(var(--pg-cols-mid, 3), minmax(0, 1fr));
  }
}
@media (max-width: 640px) {
  .consent-body { max-height: 46vh; }
  .consent-modal-actions .btn { width: 100%; }
}

/* Dashboard is the roster now, not a second module surface (2026-08-14). */
.exam-child-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.btn-confirm-danger {
  border-color: var(--amber) !important;
  color: var(--amber-dark) !important;
  background: rgba(243, 113, 48, 0.10) !important;
}

/* ---- Child roster cards (2026-08-14) ---------------------------------
   This page stopped being a module surface, so it stopped needing the
   module-status layout. A grid of person-cards: who they are first, then
   what you can do. */
.exam-status-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
  align-items: stretch;
}
.roster-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 20px;
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.roster-card:hover { box-shadow: 0 8px 24px rgba(11, 30, 48, 0.08); transform: translateY(-2px); }
.roster-card-head { display: flex; align-items: center; gap: 14px; }
.roster-avatar { width: 52px; height: 52px; flex-shrink: 0; }
.roster-id { min-width: 0; }
.roster-name {
  display: block;
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 17px; font-weight: 700; color: var(--navy-dark);
  text-decoration: none; line-height: 1.25;
}
.roster-name:hover { color: var(--teal); }
.roster-meta { font-size: 12.5px; color: var(--text-muted); margin: 3px 0 0; }
.roster-student-id { align-self: flex-start; }
.roster-no-login { font-size: 12px; color: var(--text-muted); font-style: italic; margin: 0; }

/* Actions pinned to the bottom so cards of differing height still line up. */
.roster-actions {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border);
}
.roster-open { flex: 1; text-align: center; }
/* Archive is deliberately quiet — reversible, rarely wanted, and it should
   never compete with opening the child. */
.roster-archive {
  background: none; border: none; padding: 6px 4px;
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--text-muted); cursor: pointer; white-space: nowrap;
}
.roster-archive:hover { color: var(--amber-dark); }
.roster-archive.btn-confirm-danger {
  color: var(--amber-dark) !important;
  background: rgba(243, 113, 48, 0.12) !important;
  border-radius: 8px; padding: 6px 10px;
}

/* The add tile is a peer of the child cards, not a banner under them. */
.exam-add-child-card {
  display: flex; align-items: center; justify-content: center;
  min-height: 150px; border: 2px dashed var(--border); border-radius: 16px;
  color: var(--teal); font-weight: 600; font-size: 14px;
  text-decoration: none; transition: border-color 160ms ease, background 160ms ease;
}
.exam-add-child-card:hover { border-color: var(--teal); background: rgba(15, 123, 138, 0.05); }

@media (max-width: 560px) { .exam-status-list { grid-template-columns: 1fr; } }

/* Roster footer now carries three weights: Manage (primary), Results
   (secondary link), Archive (quiet). */
.roster-actions { flex-wrap: wrap; }
.roster-open { flex: 1 1 auto; text-align: center; }
.roster-secondary {
  font-size: 12.5px; font-weight: 600; color: var(--teal);
  text-decoration: none; white-space: nowrap; padding: 6px 4px;
}
.roster-secondary:hover { text-decoration: underline; }

/* Archive warning + archived roster (2026-08-14) */
.archive-warning-card { max-width: 470px; }
.archive-warning-body { margin: 6px 0 20px; }
.archive-warning-body p { font-size: 13.5px; line-height: 1.6; color: var(--text-muted); margin: 0 0 10px; }
.archive-warning-body strong { color: var(--navy-dark); font-weight: 600; }
.archive-warning-reassure {
  background: rgba(15, 123, 138, 0.08);
  border-left: 3px solid var(--teal);
  border-radius: 8px; padding: 10px 13px; margin-top: 14px !important;
}
.archived-head { margin-top: 34px; }
/* Muted so the archived grid never competes with the active roster. */
.roster-card-archived { background: var(--bg-soft, #f7f9fa); }
.roster-card-archived .roster-avatar { filter: grayscale(0.75); opacity: 0.75; }
.roster-card-archived .roster-name { color: var(--text-muted); }

/* Start action in the child's to-do table. */
.asg-start { white-space: nowrap; }
.asg-table td:last-child { text-align: right; }

/* What's left to assign, inside the assign modal (2026-08-15). */
.assign-avail {
  background: var(--bg-soft, #f7f9fa);
  border: 1px solid var(--border);
  border-radius: 11px;
  padding: 13px 15px;
  margin: 14px 0 4px;
}
.assign-avail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.assign-avail-title {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted); margin: 0;
}
.assign-avail-buy { font-size: 12.5px; font-weight: 600; color: var(--teal); text-decoration: none; white-space: nowrap; }
.assign-avail-buy:hover { text-decoration: underline; }
.assign-avail-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.assign-avail-cell { background: #fff; border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; }
/* The module being assigned is highlighted, so the number that matters
   right now is not just one of three. */
.assign-avail-cell.is-current { border-color: var(--navy); box-shadow: inset 0 0 0 1px var(--navy); }
.assign-avail-mod { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin: 0 0 3px; }
.assign-avail-nums { font-size: 12px; color: var(--text-muted); margin: 0; }
.assign-avail-nums strong { color: var(--navy-dark); font-variant-numeric: tabular-nums; }
.assign-avail-none { font-size: 12.5px; color: var(--text-muted); margin: 0; }
@media (max-width: 560px) { .assign-avail-row { grid-template-columns: 1fr; } }

/* Equal-weight credit pair (2026-08-15). Overrides the earlier rules that
   gave practice a large number and tests a small chip. */
.asg-credit-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 8px 0 10px; }
.asg-credit-stat { min-width: 0; }
.asg-credit-val {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 22px; font-weight: 700; line-height: 1;
  color: var(--navy-dark); margin: 0; font-variant-numeric: tabular-nums;
}
.asg-credit-val.is-zero { color: var(--text-muted); }
.asg-credit-unit { font-size: 11px; font-weight: 600; color: var(--navy-dark); margin: 5px 0 0; line-height: 1.3; }
.asg-credit-of { font-size: 10.5px; color: var(--text-muted); margin: 1px 0 0; font-variant-numeric: tabular-nums; }

/* Task table alignment (2026-08-15). The action column was right-aligned by
   an earlier blanket rule while its header stayed left, so the button sat
   under nothing. Header and cell now share an alignment, and the column is
   sized to the button so it does not drift as task names change length. */
.asg-table th:last-child,
.asg-table td:last-child { text-align: left; width: 1%; white-space: nowrap; }
.asg-table th { vertical-align: bottom; }
.asg-table td { vertical-align: middle; }
/* Status chips share a column with varying content — pin the width so the
   chips form a straight edge rather than tracking the text beside them. */
.asg-table th:nth-child(5),
.asg-table td:nth-child(5) { width: 1%; white-space: nowrap; }
.asg-start { display: inline-flex; align-items: center; justify-content: center; min-width: 104px; }
/* Resume reads as continuing, not restarting. */
.asg-resume { background: var(--gradient-brand, var(--navy)); color: #fff; }

/* Institution application (2026-08-15) */
.auth-card-wide { max-width: 720px; }
.child-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.child-form-grid .field-span-2 { grid-column: 1 / -1; }
@media (max-width: 640px) { .child-form-grid { grid-template-columns: 1fr; } }

/* Institution review queue (2026-08-18). Extends the two admin status
   pills to the four states an application can be in. Amber = waiting on
   us, matching the amber-means-attention language used in the reports. */
.admin-status-pill.pending { background: rgba(214, 158, 46, 0.16); color: var(--amber-dark); }
.admin-status-pill.rejected { background: rgba(179, 38, 30, 0.10); color: #b3261e; }

.admin-count-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 10px;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(214, 158, 46, 0.16);
  color: var(--amber-dark);
  font-family: "Inter", sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  vertical-align: middle;
}

/* Second line inside a table cell: the email under the institution name,
   the role under the contact. Keeps the table to eight columns. */
.admin-cell-sub {
  display: block;
  margin-top: 2px;
  color: var(--text-muted);
  font-size: 0.78rem;
}

.admin-storage-note {
  margin-top: 12px;
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.5;
}



/* The one destructive confirm button on the admin side. Solid rather
   than ghost, because in the review modal it is the primary action of
   that mode -- but red, so it never reads as the safe default. */
.btn-danger {
  background: #b3261e;
  color: #fff;
  border: 1px solid #b3261e;
}
.btn-danger:hover { background: #99201a; border-color: #99201a; }

@media (max-width: 560px) {
}

/* Institution sign-in + the one-time password reveal (2026-08-18). */
.issued-password {
  margin: 14px 0;
  padding: 14px 16px;
  border-radius: 10px;
  background: var(--bg, #fff);
  border: 1px dashed var(--teal);
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-align: center;
  word-break: break-all;
  user-select: all;
}

.field-hint-block {
  margin: 6px 0 0;
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.5;
}


/* Institution roster + enrolment (2026-08-18). */
.roster-count {
  margin: 16px 0 10px;
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* Sample reports index. */
.sample-note {
  margin: 16px 0 0; padding: 12px 14px; border-radius: 11px;
  background: var(--bg-light); border: 1px dashed rgba(91, 107, 118, 0.28);
  font-size: 12.5px; color: var(--text-muted); line-height: 1.6; max-width: 74ch;
}
.sample-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 16px; margin-top: 22px;
}
.sample-card {
  display: flex; flex-direction: column; gap: 9px;
  padding: 22px; background: #fff;
  border: 1px solid var(--border); border-radius: 16px;
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.sample-card:hover { box-shadow: 0 8px 24px rgba(11, 30, 48, 0.08); transform: translateY(-2px); }
.sample-icon { display: inline-flex; width: 30px; height: 30px; }
.sample-icon svg { width: 100%; height: 100%; }
.sample-title {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 17px; font-weight: 700; color: var(--navy-dark); margin: 2px 0 0;
}
.sample-desc { font-size: 13px; color: var(--text-muted); line-height: 1.6; margin: 0; }
/* Actions pinned to the bottom so cards of differing text length line up. */
.sample-actions {
  display: flex; align-items: center; gap: 14px;
  margin-top: auto; padding-top: 15px; border-top: 1px solid var(--border);
}
.sample-print-tip {
  margin: 22px 0 0; padding: 13px 15px; border-radius: 11px;
  background: rgba(243, 113, 48, 0.10); border-left: 3px solid var(--amber);
  font-size: 12.5px; color: var(--navy-dark); line-height: 1.65; max-width: 74ch;
}

/* The school's colour drives the primary action on a branded page.
   LONGHANDS, not the `background` shorthand. .btn-primary paints itself
   with `background: var(--gradient-amber)`, and overriding a shorthand
   that holds a var() with another shorthand that holds a var() left the
   button with no background at all — the gradient cleared, the colour
   never applied. Setting background-image to none and the colour
   separately is unambiguous about which layer is being replaced.

   The white text is not optional: cobrandReadableAccent darkens the
   school's colour until WHITE clears 4.5:1, so the button MUST carry
   white text or the contrast the gate guarantees is not the contrast on
   screen. Left alone it inherited navy. */
.brand-grid { display: flex; flex-direction: column; gap: 18px; margin-top: 4px; }
.brand-grid .field { margin: 0; }
.brand-color-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.brand-color-row input[type="color"] {
  width: 52px; height: 38px; padding: 2px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 9px; background: #fff;
}
.brand-color-row input[type="url"] { flex: 1 1 260px; min-width: 0; }
#brand-color-hex {
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; color: var(--text-muted);
}
.brand-preview-title { margin: 22px 0 8px; font-size: 13px; font-weight: 700; color: var(--navy-dark); }
/* A real preview, not a swatch: a school should see the darkened colour on
   an actual button before their families do. */
.brand-preview {
  border: 1px solid var(--border); border-radius: 14px;
  padding: 22px; text-align: center; background: var(--bg-light);
}
.brand-preview-lockup {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 14px; margin-bottom: 12px;
  border: 1px solid var(--border); border-radius: 12px; background: #fff;
}
.brand-preview-logo { max-height: 28px; max-width: 96px; object-fit: contain; }
.brand-preview-name { font-size: 13px; font-weight: 700; color: var(--navy-dark); }
.brand-preview-h {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 17px; font-weight: 700; color: var(--navy-dark); margin: 0 0 14px;
}
.brand-preview-btn {
  display: inline-block; padding: 9px 26px; border-radius: 999px;
  font-size: 13.5px; font-weight: 650; color: #fff; background: var(--teal);
}

/* Set work + Plans. The student picker is shared by both — a school
   choosing who gets homework and who gets a plan is picking from the same
   list, so it renders through one function and one set of rules. */
.asg-form { display: flex; flex-wrap: wrap; gap: 16px; margin: 4px 0 6px; }
.asg-form .field { flex: 1 1 170px; margin: 0; }
.asg-pick-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 22px 0 10px; flex-wrap: wrap;
}
.asg-pick-title { margin: 0; font-size: 13px; font-weight: 700; color: var(--navy-dark); }
.asg-pick-actions { display: flex; gap: 14px; }

.pick-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 8px; max-height: 320px; overflow-y: auto; padding: 2px;
}
.pick-item {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 10px;
  align-items: center; padding: 10px 12px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px; background: #fff;
}
.pick-item:hover { border-color: rgba(15, 123, 138, 0.4); }
.pick-item input { grid-row: span 2; width: 16px; height: 16px; accent-color: var(--teal); }
.pick-name { font-size: 13.5px; font-weight: 650; color: var(--navy-dark); overflow-wrap: anywhere; }
.pick-meta { font-size: 11.5px; color: var(--text-muted); }
.pick-item:has(input:checked) { border-color: var(--teal); background: rgba(15, 123, 138, 0.05); }

.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin-top: 4px; }
.plan-card {
  display: flex; flex-direction: column; gap: 5px; text-align: left;
  padding: 16px 17px; cursor: pointer; font: inherit;
  border: 1px solid var(--border); border-radius: 14px; background: #fff;
}
.plan-card:hover { border-color: rgba(15, 123, 138, 0.4); }
.plan-card.selected { border-color: var(--teal); box-shadow: 0 0 0 1px var(--teal) inset; }
.plan-name { font-size: 13px; font-weight: 700; color: var(--navy-dark); }
.plan-price {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 21px; font-weight: 700; color: var(--navy-dark);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.plan-per { font-size: 11.5px; font-weight: 500; color: var(--text-muted); letter-spacing: 0; }
.plan-blurb { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
/* The school's share sits on the card. It is the honest reason a school is
   on this page, and burying it would not make it less true. */
.plan-share {
  margin-top: 4px; align-self: flex-start;
  font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 5px;
  background: rgba(15, 123, 138, 0.10); color: var(--teal);
}

/* Institution Overview. Semantic severity (attention / urgent) is kept
   separate from the teal accent, so "needs chasing" never reads as brand
   colour. Figures are tabular so columns of numbers line up. */
/* Built from the parent portal's own vocabulary — the conic ring from
   .module-hero, the tile from .stat-tile — so a school and a parent are
   looking at one product rather than two. */
.ov-hero {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px 20px 16px;
}
.ov-hero-top { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.ov-ring {
  flex-shrink: 0; width: 74px; height: 74px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.ov-ring-inner {
  width: 57px; height: 57px; border-radius: 50%; background: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1;
}
.ov-ring-num {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 19px; font-weight: 700; color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.ov-ring-of { font-size: 8.5px; font-weight: 600; color: var(--text-muted); margin-top: 2px; letter-spacing: 0.04em; }
.ov-hero-lead { flex: 1; min-width: 220px; }
.ov-hero-eyebrow {
  font-size: 10px; font-weight: 700; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--teal); margin: 0 0 4px;
}
.ov-hero-line {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 19px; font-weight: 700; letter-spacing: -0.01em;
  color: var(--navy-dark); margin: 0; line-height: 1.3;
}
.ov-hero-sub { font-size: 12.5px; color: var(--text-muted); margin: 5px 0 0; }

.ov-funnel {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: 18px;
}
/* Tiles rather than a divided strip: each step is a thing you can act on,
   and a shared border made them read as one undifferentiated block. */
.ov-step {
  padding: 13px 14px 14px;
  border-radius: 12px;
  background: var(--bg-light);
  border: 1px solid transparent;
  transition: border-color 150ms ease, transform 150ms ease;
}
.ov-step:hover { border-color: rgba(15, 123, 138, 0.3); transform: translateY(-1px); }
/* The step carrying the biggest loss is the one worth looking at, so it is
   tinted rather than left to be found by reading all four. */
.ov-step.is-leak { background: rgba(179, 38, 30, 0.055); border-color: rgba(179, 38, 30, 0.18); }
.ov-step-n {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 30px; font-weight: 700; letter-spacing: -0.03em;
  color: var(--navy-dark); line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.ov-step-l { font-size: 12.5px; color: var(--text-muted); margin-top: 3px; }
/* The drop between two steps is the actual signal, so it is drawn rather
   than left for the reader to subtract. */
.ov-drop {
  display: inline-flex; align-items: center; margin-top: 9px;
  font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 5px;
}
.ov-drop.ok { background: rgba(15, 123, 138, 0.10); color: var(--teal); }
.ov-drop.warn { background: rgba(243, 113, 48, 0.17); color: #93400F; }
.ov-drop.bad { background: rgba(179, 38, 30, 0.09); color: #B3261E; }

/* Each row is its own tile with a tinted count disc, the same device the
   module collapse rows use — a bare number in a column read as a table,
   not as four things to do. */
.ov-row {
  display: grid; grid-template-columns: 46px minmax(0, 1fr) auto;
  gap: 15px; align-items: center;
  padding: 14px 15px; margin-top: 10px;
  border-radius: 13px; border: 1px solid var(--border); background: #fff;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}
.ov-row:first-child { margin-top: 4px; }
.ov-row:hover { border-color: rgba(15, 123, 138, 0.32); box-shadow: 0 6px 18px rgba(11, 30, 48, 0.06); }
.ov-row-n {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700;
  color: var(--teal); background: rgba(15, 123, 138, 0.10);
  font-variant-numeric: tabular-nums;
}
.ov-row-n.warn { color: #93400F; background: rgba(243, 113, 48, 0.18); }
.ov-row-n.bad { color: #B3261E; background: rgba(179, 38, 30, 0.10); }
.ov-row-t { font-size: 14px; font-weight: 650; color: var(--navy-dark); }
.ov-row-d { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }

/* One tab per subject. The product is three co-equal modules, so the
   cohort panel has to show three — reporting only Cognitive described a
   different product from the one a school bought. */
.ov-modtabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 2px 0 16px; }
.ov-modtab {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 650;
  color: var(--text-muted); background: var(--bg-light); border: 1px solid transparent;
}
.ov-modtab:hover { color: var(--navy-dark); }
.ov-modtab.active { background: rgba(15, 123, 138, 0.10); border-color: rgba(15, 123, 138, 0.28); color: var(--navy-dark); }
/* The count is on the tab so a school can see where the data actually is
   before clicking through three empty panels. */
.ov-modtab-n {
  font-size: 11px; font-weight: 700; padding: 1px 6px; border-radius: 999px;
  background: rgba(91, 107, 118, 0.14); color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ov-modtab.active .ov-modtab-n { background: rgba(15, 123, 138, 0.18); color: var(--teal); }
.ov-panel-count { font-size: 12.5px; color: var(--text-muted); margin: 0 0 12px; }

.ov-dist { display: flex; flex-direction: column; gap: 9px; margin-top: 4px; }
.ov-dist-row {
  display: grid; grid-template-columns: 74px minmax(0, 1fr) 32px;
  gap: 10px; align-items: center; font-size: 12.5px;
}
.ov-dist-lab { color: var(--text-muted); }
.ov-bar { height: 9px; border-radius: 999px; background: var(--bg-light, #F1F1F6); overflow: hidden; }
.ov-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--teal); opacity: 0.85; }
.ov-dist-v { text-align: right; font-weight: 650; color: var(--navy-dark); font-variant-numeric: tabular-nums; }

.ov-domain-title { font-size: 12.5px; color: var(--text-muted); margin: 20px 0 8px; }
.ov-domain { display: flex; flex-direction: column; gap: 8px; }
.ov-domain-row { display: grid; grid-template-columns: minmax(0, 1fr) 34px; gap: 10px; align-items: center; }
.ov-domain-name { display: block; font-size: 12.5px; color: var(--text-muted); margin-bottom: 3px; }
.ov-domain-bar { display: block; height: 7px; border-radius: 999px; background: var(--bg-light, #F1F1F6); overflow: hidden; }
.ov-domain-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--text-muted); opacity: 0.5; }
.ov-domain-row.is-low .ov-domain-bar > i { background: var(--amber-dark); opacity: 0.9; }
.ov-domain-v { text-align: right; font-weight: 650; font-size: 12.5px; color: var(--navy-dark); font-variant-numeric: tabular-nums; }

.ov-privacy {
  margin: 18px 0 0; padding: 11px 13px; border-radius: 10px;
  background: var(--bg-light, #f7f9fa); border: 1px dashed rgba(91, 107, 118, 0.28);
  font-size: 12.5px; color: var(--text-muted); line-height: 1.6;
}

@media (max-width: 720px) {
  .ov-funnel { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ov-step:nth-child(3) { border-left: none; padding-left: 0; }
  .ov-step { border-top: 1px solid var(--border); padding-top: 14px; }
  .ov-step:nth-child(-n+2) { border-top: none; padding-top: 0; }
  .ov-row { grid-template-columns: 40px minmax(0, 1fr); }
  .ov-row .btn { grid-column: 2; justify-self: start; }
}

/* Institution portal: navigation down the left rather than across the top.
   Only this portal uses it — see the markup comment for why a school moving
   between separate admin areas differs from a parent switching views of one
   child. */
.portal-shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
.portal-shell-main { min-width: 0; display: flex; flex-direction: column; gap: 32px; }
.portal-sidenav {
  position: sticky;
  top: 24px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 16px;
}
.portal-navitem {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 11px 14px;
  border: none;
  border-radius: 10px;
  background: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  text-align: left;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}
.portal-navitem:hover { background: var(--bg-light); color: var(--navy); }
.portal-navitem.active { background: rgba(15, 123, 138, 0.09); color: var(--navy-dark); }
/* The active marker is a left bar rather than the amber underline the top
   tab bars use — an underline reads as "tab" and would fight the vertical
   axis this rail sets up. */
.portal-navitem.active::before {
  content: "";
  position: absolute;
  left: 0; top: 9px; bottom: 9px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--teal);
}
.portal-navicon { display: inline-flex; width: 18px; height: 18px; flex-shrink: 0; }
.portal-navicon svg { width: 100%; height: 100%; }

/* Below the breakpoint the rail becomes a horizontal strip above the
   content — a 232px column would leave the panels unusably narrow, and a
   school on a tablet is a real case. Sticky is dropped with it: a bar
   pinned to the top of a short viewport eats the content it navigates. */
@media (max-width: 900px) {
  .portal-shell { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .portal-sidenav {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding: 8px;
  }
  .portal-navitem { width: auto; flex-shrink: 0; }
  .portal-navitem.active::before { top: auto; left: 10px; right: 10px; bottom: 0; width: auto; height: 3px; border-radius: 3px 3px 0 0; }
}

/* The institution roster deliberately has NO card CSS of its own
   (2026-08-19). It previously carried a second, denser set of rules under
   the SAME class names the parent roster uses -- .roster-card,
   .roster-meta and .roster-id were all redefined here, and being later in
   the file they silently won on dashboard.html too. A .roster-open added
   during the portal restructure was the worst of it: padding/border/
   background reset to none, which stripped the parent's "Manage" button
   back to bare text.

   Both rosters now share one definition, up at .roster-card. If the
   institution card ever needs to differ, scope it under a wrapper class
   rather than redeclaring a shared name. */
.student-view-back { margin-bottom: 16px; }
/* The school's initial where a child's avatar sits on the other heroes. */
.inst-avatar {
  display: grid; place-items: center;
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 26px; font-weight: 700; color: #fff;
  background: linear-gradient(150deg, var(--navy) 0%, var(--teal) 100%);
}
/* The pupil's name opens their view, so it is a <button> here where the
   parent uses an <a>. Only the button chrome needs resetting -- the type
   comes from the shared .roster-name. */
button.roster-name {
  padding: 0;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
}

.enrol-result {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: 14px;
  border-left: 3px solid var(--teal);
  background: rgba(15, 123, 138, 0.07);
}
.enrol-result-title { margin: 0 0 6px; font-size: 1rem; }
.enrol-result-note {
  margin: 0 0 14px;
  color: var(--text-muted);
  font-size: 0.84rem;
  line-height: 1.6;
}
.enrol-cred-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 14px;
}
.enrol-cred-row > div {
  flex: 1 1 180px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--bg, #fff);
  border: 1px dashed var(--teal);
}
.enrol-cred-label {
  display: block;
  margin-bottom: 4px;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.enrol-cred-value {
  display: block;
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  overflow-wrap: anywhere;
  user-select: all;
}

.divider-note {
  font-weight: 400;
  color: var(--text-muted);
  font-size: 0.78rem;
}

.submitted-link-row {
  margin: 18px 0 0;
  padding: 14px 16px;
  border-radius: 12px;
  background: rgba(15, 123, 138, 0.07);
  border-left: 3px solid var(--teal);
}
.submitted-link-label {
  display: block;
  margin-bottom: 6px;
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.submitted-link-row code {
  font-size: 0.86rem;
  overflow-wrap: anywhere;
  user-select: all;
}

.review-logo {
  max-height: 60px;
  max-width: 160px;
  object-fit: contain;
  margin-bottom: 12px;
}

.share-link-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 14px 16px;
  border-radius: 12px;
  background: rgba(15, 123, 138, 0.07);
  border-left: 3px solid var(--teal);
}
.share-link-row code {
  flex: 1 1 260px;
  min-width: 0;
  font-size: 0.86rem;
  overflow-wrap: anywhere;
  user-select: all;
}

.field-ok {
  margin: 8px 0 0;
  color: var(--teal);
  font-size: 0.84rem;
  font-weight: 600;
}



/* Setting a student password from the roster (2026-08-19). */
.roster-reset {
  margin-top: 10px;
  font-size: 0.8rem;
}
.roster-pw-form {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid rgba(91, 107, 118, 0.18);
}
.roster-pw-label {
  display: block;
  margin-bottom: 6px;
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 600;
}
.roster-pw-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid rgba(91, 107, 118, 0.3);
  font-family: "Roboto Mono", ui-monospace, monospace;
  font-size: 0.86rem;
}
.roster-pw-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}
.roster-pw-form .field-error,
.roster-pw-form .field-ok {
  font-size: 0.76rem;
  line-height: 1.5;
}

/* Institution revenue share (2026-08-19). */
.credit-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin: 18px 0 22px;
}
.credit-stat {
  padding: 16px 18px;
  border-radius: 13px;
  background: var(--bg-light);
  border: 1px solid transparent;
  transition: border-color 150ms ease;
}
.credit-stat:hover { border-color: rgba(15, 123, 138, 0.24); }
/* The figure that decides what can be requested, so it is the one that
   reads first — a filled tile rather than an accent stripe, which is how
   the parent portal marks its own lead figure. */
.credit-stat.is-primary {
  background: linear-gradient(160deg, rgba(15, 123, 138, 0.13) 0%, rgba(15, 123, 138, 0.06) 100%);
  border-color: rgba(15, 123, 138, 0.24);
}
.credit-stat-value {
  display: block;
  font-family: "Poppins", sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.credit-stat.is-primary .credit-stat-value { color: var(--teal); }
.credit-stat-label {
  display: block;
  margin-top: 4px;
  color: var(--text-muted);
  font-size: 0.78rem;
}
.redeem-row {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}
.redeem-row .field { flex: 0 1 220px; margin: 0; }
.credit-history-title {
  margin: 26px 0 0;
  font-size: 0.95rem;
}

/* ===================================================================== */
/* Parent-first report layer (2026-08-20)                                */
/*                                                                       */
/* Shared by the Cognitive, Spelling and Maths reports. The markup that  */
/* uses these classes lives in js/parent-report.js — one implementation  */
/* for all three, so the three cannot drift apart.                       */
/* ===================================================================== */

/* ---- "Your child in 60 seconds" as a HERO (2026-08-25) ----------------
   It says "if you read nothing else, read this" and then looked like every
   other section on the page - a white card with a faint tint. A tinted
   border is not emphasis when the six blocks under it are also cards.

   Compound selector on purpose: report.html loads report.css AFTER
   auth.css, and .report-section there sets a white background at the same
   specificity, so a bare .parent-glance loses. This wins on specificity
   rather than on load order, which is the part nobody remembers to check.

   Same navy-and-teal ground as the portal's practice hero, so "this is the
   one that matters" looks the same in both places. */
.report-section.parent-glance {
  --pg-ring-track: rgba(255, 255, 255, 0.2);
  position: relative;
  border: 0;
  border-radius: 20px;
  padding: 30px 32px 32px;
  color: #fff;
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(15, 123, 138, 0.45), transparent 60%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  box-shadow: 0 18px 40px rgba(27, 28, 80, 0.24);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.report-section.parent-glance h2 { color: #fff; }
.report-section.parent-glance > .auth-sub { color: rgba(255, 255, 255, 0.72); }
/* The section "i" is a dark glyph on a dark ground otherwise. */
.parent-glance .metric-info-btn {
  color: rgba(255, 255, 255, 0.8);
  border-color: rgba(255, 255, 255, 0.4);
}

/* A white medallion, larger than it was. The score is the single number a
   parent repeats, and it was competing with the heading beside it. */
.parent-glance .pg-ring { width: 122px; height: 122px; }
.parent-glance .pg-ring-in { width: 92px; height: 92px; }
.parent-glance .pg-lead .eyebrow { color: #6FDCE8; }
.parent-glance .pg-profile { color: #fff; font-size: 24px; }
.parent-glance .pg-note { color: rgba(255, 255, 255, 0.78); }

/* THE THREE FACTS BECOME TILES, not table rows. Read as a table, the
   strength and the priority carried the same weight as their labels and
   the eye had to travel the full width to pair each one up. Side by side
   they are three things, countable at a glance - which is the entire job
   of a section called "in 60 seconds". Markup is unchanged. */
/* ONE ROW, however many tiles there are. auto-fit sized the columns to the
   available width and stranded whatever did not divide into it: three
   tiles came out 2 + 1, five came out 4 + 1, and the lone tile on the
   second row took a different height from the four above it.

   --pg-cols is the actual tile count, set on the element by the builder.
   Between 640 and 1000 it falls back to at most three, so five tiles
   become 3 + 2 rather than five 120px slivers - two on the last row reads
   as deliberate in a way that one never does. */
.parent-glance .pg-rows {
  display: grid;
  grid-template-columns: repeat(var(--pg-cols, 3), minmax(0, 1fr));
  gap: 10px;
  margin-top: 20px;
  border: 0;
  background: none;
  overflow: visible;
}
.parent-glance .pg-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  /* Tighter than the 15/16 it was: these are five short facts, not five
     paragraphs, and the padding was doing more work than the content. */
  padding: 11px 13px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.09);
  /* Equal height across the row, so the score rows below can line up. */
  height: 100%;
}
/* PINNED TO THE BOTTOM. This is what makes a row of tiles read as a row:
   the names above are 1, 2 or 3 lines depending on the skill, and without
   this the scores sat wherever their own name happened to end. */
.parent-glance .pg-meta {
  margin-top: auto;
  padding-top: 8px;
  display: flex;
  /* THE PILL ALWAYS TAKES ITS OWN LINE. Beside the score it fitted for
     'strongest' and '+17' and not for 'worth strengthening', so one tile
     in three came out 27px taller inside and broke the row it was meant
     to sit in. A column does the same job for every label length, and
     the next one nobody has written yet. */
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
}
.parent-glance .pg-score {
  font-family: 'Poppins', sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.parent-glance .pg-k { flex: none; font-size: 10px; letter-spacing: 0.06em; line-height: 1.35; color: rgba(255, 255, 255, 0.62); }
.parent-glance .pg-v { flex: none; font-size: 14px; line-height: 1.35; }
.parent-glance .pg-v { flex: none; color: #fff; font-size: 15px; line-height: 1.35; }
/* The light-ground pills are near-invisible tints; on navy they need to be
   the brighter half of the same hues. */
/* Never broken across two lines. "worth strengthening" is the longest of
   them and was wrapping inside its own pill, which turns a label into a
   two-line block and drags that tile out of line with the rest. */
.parent-glance .pg-pill {
  margin-left: 0;
  margin-top: 0;
  display: inline-block;
  white-space: nowrap;
  font-size: 10px;
  padding: 2px 8px;
}
.parent-glance .pg-pill.up { background: rgba(111, 220, 232, 0.2); color: #8FE7F0; }
.parent-glance .pg-pill.watch { background: rgba(243, 113, 48, 0.26); color: #FFC0A0; }

/* Light panel on the dark ground. This is the longest prose in the hero and
   the one paragraph a parent actually reads word by word - it earns a
   normal reading surface rather than white-on-navy. */
.parent-glance .pg-takeaway {
  margin-top: 20px;
  padding: 17px 20px;
  border-left: 4px solid var(--teal);
  border-radius: 0 12px 12px 0;
  background: rgba(255, 255, 255, 0.96);
  color: var(--text-dark);
}
.parent-glance .pg-takeaway p { color: var(--text-dark); }
.parent-glance .pg-takeaway strong { color: var(--navy-dark); }

@media (max-width: 640px) {
  .report-section.parent-glance { padding: 22px 18px 24px; border-radius: 16px; }
  .parent-glance .pg-profile { font-size: 20px; }
  .parent-glance .pg-rows { grid-template-columns: 1fr; }
}

/* PRINT: back to a light card. A full-bleed navy block costs a parent most
   of an ink cartridge for the first page of a report they were told to
   keep, and the shadow prints as a grey smear. Everything the dark ground
   forced to white is put back. */
@media print {
  .report-section.parent-glance {
    --pg-ring-track: rgba(91, 107, 118, 0.15);
    color: var(--text-dark);
    background: #fff;
    border: 1px solid rgba(15, 123, 138, 0.28);
    box-shadow: none;
  }
  .report-section.parent-glance h2,
  .parent-glance .pg-profile,
  .parent-glance .pg-v { color: var(--navy-dark); }
  .report-section.parent-glance > .auth-sub,
  .parent-glance .pg-note,
  .parent-glance .pg-k { color: var(--text-muted); }
  .parent-glance .pg-lead .eyebrow { color: var(--teal); }
  .parent-glance .pg-row { background: #fff; border-color: var(--border); }
  .parent-glance .pg-pill.up { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
  .parent-glance .pg-pill.watch { background: rgba(243, 113, 48, 0.16); color: var(--amber-dark); }
  .parent-glance .pg-takeaway { background: var(--bg-light); }
}

.pg-top {
  display: flex;
  align-items: center;
  gap: 22px;
  margin: 18px 0 4px;
  flex-wrap: wrap;
}

/* The ring is a conic-gradient donut: cheap, printable, and it needs no
   SVG or canvas to stay crisp when a parent prints the report. */
.pg-ring {
  width: 108px;
  height: 108px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.pg-ring-in {
  width: 82px;
  height: 82px;
  border-radius: 50%;
  background: var(--bg-card);
  display: grid;
  place-items: center;
  line-height: 1;
  text-align: center;
}
.pg-ring-n { font-family: 'Poppins', sans-serif; font-size: 30px; font-weight: 700; color: var(--navy-dark); display: block; }
.pg-ring-l {
  font-size: 9px; font-weight: 700; letter-spacing: 0.07em;
  color: var(--text-muted); display: block; margin-top: 4px;
  white-space: nowrap; max-width: 74px; overflow: hidden; text-overflow: ellipsis;
}

.pg-lead { flex: 1 1 260px; min-width: 240px; }
.pg-lead .eyebrow {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--teal); margin: 0 0 5px;
}
.pg-profile { font-family: 'Poppins', sans-serif; font-size: 21px; font-weight: 700; color: var(--navy-dark); margin: 0 0 6px; line-height: 1.25; }
.pg-note { font-size: 13px; color: var(--text-muted); margin: 0; line-height: 1.55; }

.pg-rows {
  margin: 20px 0 0;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg-card);
}
.pg-row {
  display: flex;
  gap: 14px;
  align-items: baseline;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px;
  flex-wrap: wrap;
}
.pg-row:last-child { border-bottom: 0; }
.pg-k { flex: 0 0 190px; font-size: 11.5px; font-weight: 700; letter-spacing: 0.045em; text-transform: uppercase; color: var(--text-muted); }
.pg-v { flex: 1 1 200px; font-weight: 600; color: var(--navy-dark); }

.pg-pill {
  display: inline-block;
  margin-left: 9px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  vertical-align: middle;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.pg-pill.up { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.pg-pill.watch { background: rgba(243, 113, 48, 0.16); color: var(--amber-dark); }

.pg-takeaway {
  margin-top: 18px;
  padding: 15px 18px;
  border-left: 3px solid var(--teal);
  background: var(--bg-light);
  border-radius: 0 10px 10px 0;
}
.pg-takeaway p { margin: 0 0 9px; font-size: 13.5px; line-height: 1.65; }
.pg-takeaway p:last-child { margin-bottom: 0; }

/* --- a single skill, explained ------------------------------------- */
/* A LEFT EDGE IN THE ROLE'S COLOUR (2026-08-24). Two near-identical white
   cards is what made this read as plain - nothing distinguished the strength
   from the area needing work until you read the number. The stripe, the badge
   and the meter all say it before a word is read, and they say it three
   different ways so none of them is load-bearing on its own. */
.pg-skill {
  position: relative;
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: 14px;
  padding: 18px 20px;
  margin: 16px 0 0;
  background: var(--bg-card);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
  break-inside: avoid;
  page-break-inside: avoid;
}
.pg-skill:hover { box-shadow: 0 10px 26px rgba(40, 41, 114, 0.09); }
.pg-skill-strong { border-left-color: var(--teal); background: linear-gradient(180deg, rgba(15, 123, 138, 0.045), transparent 92px), var(--bg-card); }
.pg-skill-focus { border-left-color: var(--amber-dark); background: linear-gradient(180deg, rgba(184, 76, 24, 0.05), transparent 92px), var(--bg-card); }

.pg-skill-role {
  display: inline-block;
  margin: 0 0 5px;
  padding: 3px 10px;
  border-radius: 999px;
  font-family: 'Poppins', sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.pg-skill-strong .pg-skill-role { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.pg-skill-focus .pg-skill-role { background: rgba(184, 76, 24, 0.12); color: var(--amber-dark); }

/* A RING, drawn with a conic-gradient - no SVG, no library, and it scales
   with the text. It sits where the flat coloured number used to: same corner,
   same glance, but the score now has a SHAPE. 97 and 7 are a world apart and
   a dial shows that before the digits are read, where two numerals in the
   same size and weight do not. */
.pg-skill-ring {
  position: relative;
  flex: none;
  width: 78px;
  height: 78px;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.pg-skill-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(var(--ring-colour) calc(var(--pct) * 1%), var(--bg-light) 0);
}
.pg-skill-ring::after {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: 50%;
  background: var(--bg-card);
}
.pg-skill-ring-num {
  position: relative;
  z-index: 1;
  font-family: 'Poppins', sans-serif;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--ring-colour);
}
.pg-skill-ring-of {
  position: relative;
  z-index: 1;
  margin-top: 2px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
}
.pg-skill-strong { --ring-colour: var(--teal); }
.pg-skill-focus { --ring-colour: var(--amber-dark); }
/* Also keyed off the ring's own class, so the colour survives if a skill
   block is ever rendered outside a .pg-skill-strong/-focus wrapper. */
.pg-skill-ring.is-strong { --ring-colour: var(--teal); }
.pg-skill-ring.is-focus { --ring-colour: var(--amber-dark); }

/* THE CARD SETS ITS OWN COLOUR. It is a white card, but it sits inside
   .report-areas-hero, which sets color:#fff on everything below it - so
   these two lines were inheriting white onto white and measuring 1.0:1 and
   1.1:1 against their backgrounds. The text was there and could not be
   seen. Declared here rather than as a hero override, so the card is
   correct on whatever ground it is dropped onto next. */
.pg-skill { color: var(--text-dark); }
.pg-skill-lines {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 8px 14px;
  align-items: start;
  margin: 12px 0 0;
}
.pg-skill-lines dt {
  font-family: 'Poppins', sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-top: 3px;
}
.pg-skill-lines dd {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-dark);
}
/* The row to act on keeps an accent, without the box and heading it used to
   carry. */
.pg-skill-lines dt.is-do { color: var(--teal); }
.pg-skill-lines dd.is-do {
  padding-left: 11px;
  border-left: 2px solid rgba(15, 123, 138, 0.35);
}
.pg-skill-focus .pg-skill-lines dt.is-do { color: var(--amber-dark); }
.pg-skill-focus .pg-skill-lines dd.is-do { border-left-color: rgba(184, 76, 24, 0.4); }
@media (max-width: 560px) {
  /* No room for a label column; the label sits above its line. */
  .pg-skill-lines { grid-template-columns: 1fr; gap: 3px; }
  .pg-skill-lines dt { padding-top: 9px; }
  .pg-skill-lines dt:first-child { padding-top: 0; }
}

/* --- Skill block disclosure (2026-08-24) ------------------------------ */
/* Five definition rows per skill, twice over, made the section a wall
   before a parent reached anything actionable. Two rows stay open; the
   background sits behind this. Styled as a quiet link rather than a button
   so it reads as "there is more if you want it", not as an action. */
.pg-skill-more { margin-top: 12px; border-top: 1px dashed var(--border); padding-top: 10px; }
.pg-skill-more > summary {
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--teal);
  list-style: none;
}
.pg-skill-more > summary::-webkit-details-marker { display: none; }
.pg-skill-more > summary::before { content: "+ "; font-weight: 700; }
.pg-skill-more[open] > summary::before { content: "\2013 "; }
.pg-skill-more > summary:hover { text-decoration: underline; }
.pg-skill-more[open] > summary { margin-bottom: 10px; }
/* Open for print: a PDF cannot be clicked, and the detail should be in the
   copy a parent keeps or shows a teacher. */
@media print { .pg-skill-more { display: block; } .pg-skill-more > summary { display: none; } .pg-skill-more > * { display: block !important; } }

/* PRINT. The action box is gone (its accent is a border now, which prints
   without needing colour-adjust), so this is left holding the tiles. */
@media print {
  .stat-tile {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    break-inside: avoid;
    page-break-inside: avoid;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pg-skill, .pg-skill-meter-fill { transition: none; }
}
.pg-skill-head {
  display: flex;
  /* centre, not flex-start - the ring is 78px tall against two short lines
     of text, and top-aligning left it hanging below them. */
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 12px;
}
.pg-skill-headings { min-width: 0; }
.pg-skill-name { font-family: 'Poppins', sans-serif; font-size: 17px; font-weight: 700; color: var(--navy-dark); margin: 0; }
.pg-skill-term { font-size: 11.5px; color: var(--text-muted); margin: 3px 0 0; }
.pg-skill-score {
  font-family: 'Poppins', sans-serif;
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  flex: 0 0 auto;
}
.pg-skill-score.is-strong { color: var(--teal); }
.pg-skill-score.is-focus { color: var(--amber-dark); }
.pg-skill-of { font-size: 11px; font-weight: 600; color: var(--text-muted); margin-left: 2px; }

.pg-qa { margin: 0; }
.pg-qa dt {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
  margin: 12px 0 4px;
}
.pg-qa dt:first-child { margin-top: 0; }
.pg-qa dd { margin: 0; font-size: 13.5px; line-height: 1.62; color: var(--text-dark); }
.pg-hedge { color: var(--text-muted); font-style: italic; }

.pg-evidence {
  margin-top: 15px;
  padding: 13px 16px;
  border-radius: 10px;
  background: var(--bg-light);
  border: 1px dashed var(--border);
}
.pg-evidence > strong { display: block; font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 7px; }
.pg-evidence ul { margin: 0; padding-left: 18px; }
.pg-evidence li { font-size: 12.5px; line-height: 1.6; color: var(--text-dark); margin-bottom: 3px; }
.pg-evidence li:last-child { margin-bottom: 0; }

/* --- what it does not mean ------------------------------------------ */
.pg-h3 {
  font-family: 'Poppins', sans-serif;
  font-size: 14.5px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 20px 0 8px;
}
.pg-notmean {
  border-left: 3px solid var(--amber);
  background: rgba(243, 113, 48, 0.06);
  border-radius: 0 10px 10px 0;
  padding: 13px 18px 13px 20px;
}
.pg-notmean ul { margin: 0; padding-left: 18px; }
.pg-notmean li { font-size: 13.5px; line-height: 1.62; margin-bottom: 7px; }
.pg-notmean li:last-child { margin-bottom: 0; }

.pg-concern {
  border: 1px solid rgba(15, 123, 138, 0.25);
  background: rgba(15, 123, 138, 0.045);
  border-radius: 12px;
  padding: 16px 20px;
  margin-top: 14px;
}
.pg-concern p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.65; }
.pg-concern p:first-child { font-weight: 700; color: var(--navy-dark); }
.pg-concern .pg-last { margin-bottom: 0; }

/* --- action plan ----------------------------------------------------- */
.pg-plan {
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-top: 10px;
  overflow: hidden;
  background: var(--bg-card);
  break-inside: avoid;
  page-break-inside: avoid;
}
.pg-plan-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 16px;
  background: var(--bg-light);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.pg-plan-t { font-family: 'Poppins', sans-serif; font-size: 15px; font-weight: 700; color: var(--navy-dark); }
.pg-dose {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--teal);
  background: rgba(15, 123, 138, 0.1);
  border-radius: 999px;
  padding: 3px 11px;
  white-space: nowrap;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* Labels in their own column. As bold run-ins they sat inside the first
   line of each paragraph, pushing the sentence down a line and starting
   the three of them at three different left edges. */
.pg-plan-b {
  display: grid;
  grid-template-columns: 66px 1fr;
  gap: 7px 14px;
  padding: 13px 16px;
  margin: 0;
}
.pg-plan-b dt {
  font-family: 'Poppins', sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  /* Sits on the first line of its text rather than above it. */
  padding-top: 3px;
}
.pg-plan-b dd {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dark);
}
/* A colour down the edge says which kind of step this is before the words
   do - teal for keep-doing-this, amber for work-on-this. */
.pg-plan-strong { border-left: 3px solid var(--teal); }
.pg-plan-focus { border-left: 3px solid var(--amber-dark); }
.pg-plan-focus .pg-dose {
  color: var(--amber-dark);
  background: rgba(243, 113, 48, 0.12);
}
@media (max-width: 560px) {
  /* No room for two columns; the label goes above its text. */
  .pg-plan-b { grid-template-columns: 1fr; gap: 2px; }
  .pg-plan-b dt { padding-top: 8px; }
  .pg-plan-b dt:first-child { padding-top: 0; }
}

/* --- teacher questions + home checklist ------------------------------ */
.pg-check-list { margin-top: 12px; }
.pg-check {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-bottom: 8px;
  font-size: 13.5px;
  line-height: 1.55;
  background: var(--bg-card);
}
.pg-check:last-child { margin-bottom: 0; }
.pg-box {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  margin-top: 3px;
  border: 1.5px solid var(--teal);
  border-radius: 4px;
}

.pg-caveat {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-muted);
  border-radius: var(--radius);
  padding: 18px 24px;
  margin: 0 0 24px;
  font-size: 12.5px;
  line-height: 1.62;
  color: var(--text-dark);
}
.pg-caveat strong { color: var(--navy-dark); }

@media (max-width: 640px) {
  .pg-k { flex: 1 1 100%; }
  .pg-ring { width: 92px; height: 92px; }
  .pg-ring-in { width: 70px; height: 70px; }
  .pg-ring-n { font-size: 25px; }
}

/* ===================================================================== */
/* Practice a student can start themselves (2026-08-20)                  */
/* Assignments tab / "My tasks". Colours follow the module convention    */
/* already set by .todo-* and .asg-credit-*: cognitive navy, spelling    */
/* teal, maths amber. Getting this wrong would have made the same three  */
/* subjects a different colour on two panels of the same page.           */
/* ===================================================================== */

/* One per row rather than an auto-fit grid: with exactly three modules,
   a two-column grid always leaves the third stranded on its own row, and
   the area chips need the full width to sit on one or two lines. */
.sp-grid { display: grid; gap: 10px; margin-top: 14px; }

.sp-card {
  border: 1px solid var(--border);
  border-left: 3px solid var(--navy);
  border-radius: 12px;
  padding: 14px 18px;
  background: var(--bg-card);
}
.sp-card.sp-spelling { border-left-color: var(--teal); }
.sp-card.sp-maths { border-left-color: var(--amber-dark); }
.sp-card.sp-gk { border-left-color: var(--violet); }
.sp-card.is-spent { border-left-color: var(--border); }

.sp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.sp-mod {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 15px; font-weight: 700; color: var(--navy-dark); margin: 0;
}
.sp-left { font-size: 12px; color: var(--text-muted); margin: 0; font-variant-numeric: tabular-nums; }
.sp-left strong { font-size: 17px; color: var(--navy-dark); font-weight: 700; margin-right: 2px; }

.sp-bar {
  display: block; height: 6px; border-radius: 999px;
  background: rgba(40, 41, 114, 0.08); overflow: hidden; margin: 9px 0 0;
}
.sp-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--navy); }
.sp-spelling .sp-bar > span { background: var(--teal); }
.sp-maths .sp-bar > span { background: var(--amber); }
.sp-gk .sp-bar > span { background: var(--violet); }

.sp-note { font-size: 11.5px; color: var(--text-muted); margin: 8px 0 0; line-height: 1.5; }

.sp-actions { display: flex; align-items: center; gap: 16px; margin-top: 12px; flex-wrap: wrap; }

.sp-areas {
  display: flex; flex-wrap: wrap; gap: 7px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.sp-areas[hidden] { display: none; }

.sp-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px;
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 12.5px; font-weight: 600;
  color: var(--navy-dark); text-decoration: none; background: var(--bg-card);
}
.sp-chip:hover { border-color: var(--teal); color: var(--teal); }
/* The weakest area from the last test leads the row and says why. */
.sp-chip.is-weakest { border-color: var(--amber); background: rgba(243, 113, 48, 0.08); }
.sp-chip.is-weakest:hover { border-color: var(--amber-dark); color: var(--amber-dark); }
.sp-chip-tag {
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--amber-dark);
}

@media (max-width: 520px) {
  .sp-actions { gap: 10px; }
  .sp-actions .btn { width: 100%; justify-content: center; }
}

.asg-credits-self {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.55;
  margin: 12px 0 0;
  padding-top: 11px;
  border-top: 1px dashed var(--border);
}

/* ===================================================================== */
/* General Knowledge (2026-08-20)                                        */
/* The test/practice runner (.gk-*) and the report's own blocks (.gkr-*). */
/* Module colour is teal-on-navy like Cognitive; the strand accents come  */
/* from the shared palette so nothing new enters the brand here.         */
/* ===================================================================== */

.gk-container { max-width: 760px; padding-top: 28px; padding-bottom: 60px; }

.gk-panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px 32px 24px;
  box-shadow: var(--shadow);
}
.gk-panel-msg { text-align: center; padding: 48px 32px; }
.gk-panel-msg h1 { font-size: 22px; color: var(--navy-dark); margin: 0 0 8px; }

.gk-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.gk-progress { flex: 1 1 240px; min-width: 200px; }
.gk-progress-text {
  display: block; font-size: 11.5px; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 7px;
}
.gk-progress-bar {
  display: block; height: 6px; border-radius: 999px;
  background: rgba(40, 41, 114, 0.08); overflow: hidden;
}
.gk-progress-bar > span { display: block; height: 100%; background: var(--teal); border-radius: 999px; transition: width 0.25s ease; }

.gk-timer {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 20px; font-weight: 700; color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
  background: var(--bg-light); border-radius: 999px; padding: 5px 16px;
}
.gk-timer.is-low { color: #fff; background: var(--amber-dark); }
.gk-untimed { font-size: 12px; color: var(--text-muted); }

.gk-strand {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--teal); margin: 0 0 8px;
}
.gk-question {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 22px; font-weight: 600; line-height: 1.4;
  color: var(--navy-dark); margin: 0 0 22px;
}

.gk-options { display: grid; gap: 10px; }
.gk-option {
  display: flex; align-items: center; gap: 14px;
  width: 100%; text-align: left;
  padding: 14px 18px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  background: var(--bg-card);
  font-family: inherit; font-size: 15px; color: var(--text-dark);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.gk-option:hover:not(:disabled) { border-color: var(--teal); background: rgba(15, 123, 138, 0.04); }
.gk-option:disabled { cursor: default; }
.gk-option-key {
  flex: 0 0 auto;
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--bg-light); color: var(--text-muted);
  font-size: 12.5px; font-weight: 700;
}
.gk-option.is-chosen { border-color: var(--teal); background: rgba(15, 123, 138, 0.07); }
.gk-option.is-chosen .gk-option-key { background: var(--teal); color: #fff; }
/* Practice only — a test never reveals these mid-paper. */
.gk-option.is-correct { border-color: var(--teal); background: rgba(15, 123, 138, 0.12); }
.gk-option.is-correct .gk-option-key { background: var(--teal); color: #fff; }
.gk-option.is-wrong { border-color: var(--amber-dark); background: rgba(243, 113, 48, 0.1); }
.gk-option.is-wrong .gk-option-key { background: var(--amber-dark); color: #fff; }

.gk-feedback {
  margin-top: 16px; padding: 12px 16px; border-radius: 10px;
  font-size: 13.5px; line-height: 1.55;
}
.gk-feedback.is-right { background: rgba(15, 123, 138, 0.1); color: var(--navy-dark); }
.gk-feedback.is-wrong { background: rgba(243, 113, 48, 0.12); color: var(--navy-dark); }
.gk-feedback.is-checking, .gk-feedback.is-quiet { background: var(--bg-light); color: var(--text-muted); }

.gk-actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; margin-top: 24px; flex-wrap: wrap;
}
.gk-footnote { font-size: 11.5px; color: var(--text-muted); margin: 18px 0 0; line-height: 1.55; }

.gk-spinner {
  width: 26px; height: 26px; margin: 0 auto 14px;
  border: 3px solid var(--border); border-top-color: var(--teal);
  border-radius: 50%; animation: gk-spin 0.8s linear infinite;
}
@keyframes gk-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .gk-spinner { animation-duration: 3s; } }

.gk-msg-actions { display: flex; gap: 12px; justify-content: center; margin-top: 20px; flex-wrap: wrap; }

.gk-summary-score {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 38px; font-weight: 700; color: var(--teal); margin: 6px 0 6px;
}
.gk-summary-score span { font-size: 17px; font-weight: 600; color: var(--text-muted); }
.gk-summary-h2 { font-size: 16px; color: var(--navy-dark); margin: 26px 0 12px; }
.gk-allright { margin-top: 20px; font-size: 14px; color: var(--teal); font-weight: 600; }

.gk-review { display: grid; gap: 10px; }
.gk-review-row { border: 1px solid var(--border); border-left: 3px solid var(--amber); border-radius: 10px; padding: 12px 16px; }
.gk-review-q { font-size: 14px; font-weight: 600; color: var(--navy-dark); margin: 0 0 6px; line-height: 1.45; }
.gk-review-a { font-size: 12.5px; margin: 0; display: flex; gap: 14px; flex-wrap: wrap; }
.gk-review-yours { color: var(--amber-dark); }
.gk-review-right { color: var(--teal); font-weight: 600; }
.gk-review-why { font-size: 12px; color: var(--text-muted); margin: 6px 0 0; line-height: 1.5; }

/* --- Report ---------------------------------------------------------- */
.gkr-strand {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 15px 18px; margin-top: 12px; background: var(--bg-card);
  break-inside: avoid; page-break-inside: avoid;
}
.gkr-strand-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.gkr-strand-name { font-family: 'Poppins', Arial, Helvetica, sans-serif; font-size: 15px; font-weight: 700; color: var(--navy-dark); margin: 0; }
.gkr-strand-cov { font-size: 12px; color: var(--text-muted); margin: 4px 0 0; line-height: 1.5; max-width: 62ch; }
.gkr-strand-score {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 22px; font-weight: 700; color: var(--teal); flex: 0 0 auto; line-height: 1;
}
.gkr-bar { display: block; height: 6px; border-radius: 999px; background: rgba(40, 41, 114, 0.08); overflow: hidden; margin: 12px 0 8px; }
.gkr-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--teal); }
.gkr-strand-meta { font-size: 11.5px; color: var(--text-muted); margin: 0; line-height: 1.55; }
.gkr-strand-src { font-style: italic; }

.gkr-review { display: grid; gap: 10px; margin-top: 14px; }
.gkr-review-row {
  border: 1px solid var(--border); border-left: 3px solid var(--amber);
  border-radius: 10px; padding: 13px 16px; background: var(--bg-card);
  break-inside: avoid; page-break-inside: avoid;
}
.gkr-review-q { font-size: 14px; font-weight: 600; color: var(--navy-dark); margin: 0 0 7px; line-height: 1.45; }
.gkr-review-tag {
  display: inline-block; font-size: 9.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--teal); background: rgba(15, 123, 138, 0.1);
  border-radius: 999px; padding: 2px 9px; margin-right: 9px; vertical-align: middle;
}
.gkr-review-a { font-size: 12.5px; margin: 0; display: flex; gap: 16px; flex-wrap: wrap; }
.gkr-said { color: var(--amber-dark); }
.gkr-right { color: var(--teal); font-weight: 600; }
.gkr-why { font-size: 12px; color: var(--text-muted); margin: 7px 0 0; line-height: 1.5; }
.gkr-more, .gkr-unreached { font-size: 12.5px; color: var(--text-muted); margin: 12px 0 0; }

@media (max-width: 560px) {
  .gk-panel { padding: 22px 18px 20px; }
  .gk-question { font-size: 19px; }
  .gk-actions { flex-direction: column-reverse; align-items: stretch; }
  .gk-actions .btn { width: 100%; justify-content: center; }
}

/* The formal test inside a self-serve card. Quieter than the practice
   action on purpose: practice is repeatable, a test spends a credit. */
.sp-test {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.sp-test-copy { min-width: 0; }
.sp-test-title { font-size: 13px; font-weight: 700; color: var(--navy-dark); margin: 0; }
.sp-test-note { font-size: 11.5px; color: var(--text-muted); margin: 3px 0 0; line-height: 1.5; }
@media (max-width: 520px) {
  .sp-test { align-items: stretch; }
  .sp-test .btn { width: 100%; justify-content: center; }
}

/* --- Free trial tile (2026-08-22) ------------------------------------
   Sits above the paid grid. .offer-grid is a 4-column layout, so the single
   trial tile would otherwise render a quarter-width and read as one option
   among four rather than the thing to do first. */
.offer-grid-trial { grid-template-columns: 1fr; margin-bottom: 4px; }
.offer-tile-trial {
  border: 2px solid var(--teal);
  background: linear-gradient(160deg, rgba(15, 123, 138, 0.09), rgba(15, 123, 138, 0.02) 70%), var(--bg-card);
  position: relative;
}
.offer-tile-trial .offer-mod { color: var(--teal); }
.offer-tile-trial .offer-desc { max-width: 52ch; }
.offer-badge-trial { background: var(--teal); }
.offer-tile-trial .btn-primary {
  background: var(--teal);
  box-shadow: 0 6px 16px rgba(15, 123, 138, 0.35);
}

/* --- Admin truncation warning (2026-08-22) ---------------------------
   The console loads a bounded number of pages and then renders. It used to
   do that silently, so every total on the dashboard could be computed from
   a fraction of the data on a page that looked completely healthy. This is
   deliberately loud and deliberately not dismissible - a wrong number that
   looks right is worse than an obviously missing one. */
.admin-truncation {
  background: rgba(179, 38, 30, 0.07);
  border: 1px solid #B3261E;
  border-left: 4px solid #B3261E;
  border-radius: 8px;
  padding: 14px 18px;
  margin: 0 0 22px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-dark);
}
.admin-truncation strong { color: #B3261E; }

/* --- Admin audit trail (2026-08-22) ----------------------------------- */
.admin-inline-select {
  padding: 7px 11px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-card); color: var(--text-dark); font-size: 14px;
}
.audit-when, .audit-target {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 12.5px; color: var(--text-muted); white-space: nowrap;
}
/* A deletion should not look like an archive at a glance. */
.audit-destructive td:first-child { border-left: 3px solid #B3261E; }
.audit-destructive td:nth-child(3) { color: #B3261E; font-weight: 600; }

/* --- Purchase support actions (2026-08-22) ---------------------------- */
.admin-row-actions { white-space: nowrap; }
.admin-row-actions .link-btn { margin-right: 10px; font-size: 13px; }
.admin-row-actions .link-btn:last-child { margin-right: 0; }
.link-btn-danger { color: #B3261E; }
/* Revoked is not the same as expired: one lapsed on its own, the other was
   taken away by a person. They should not look alike in a finance table. */
.pay-status-revoked {
  background: rgba(179, 38, 30, 0.10);
  color: #B3261E;
  border: 1px solid rgba(179, 38, 30, 0.35);
}

/* --- Support lookup (2026-08-22) -------------------------------------- */
.support-result { margin-top: 14px; }
.support-msg { color: var(--text-muted); font-size: 14.5px; margin: 0; }
.support-card {
  border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; margin-bottom: 10px; background: var(--bg-card);
}
.support-card-title { font-family: 'Poppins', sans-serif; font-weight: 600; color: var(--navy); margin: 0 0 4px; }
.support-meta { font-size: 13.5px; color: var(--text-muted); margin: 0 0 3px; }
.support-meta code { font-size: 12.5px; }
.support-warn { color: #B3261E; }
.support-table { margin: 10px 0; font-size: 13.5px; }
.support-problems { margin: 8px 0 0; padding-left: 18px; }
/* The plain-language answer, which is the actual point of the screen - an
   admin should not have to look up what FORCE_CHANGE_PASSWORD means. */
.support-problems li { font-size: 14px; margin-bottom: 5px; line-height: 1.5; }

/* ---- Overview practice hero (2026-08-25) ------------------------------
   Top of the Overview tab. Two columns: the named plan and its action on
   the left, what the practice engine actually does on the right. The right
   rail is what makes it a promoter rather than another status block - but
   it is kept as a quiet aside, not a second headline, because the thing a
   parent came for is still the plan on the left.

   Navy ground on purpose. Every other block on this tab is a white card;
   one dark block is what makes a hero read as a hero, and it does that
   without a single extra pixel of height. */
.practice-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: 28px;
  align-items: start;
  padding: 26px 28px;
  border-radius: 18px;
  color: #fff;
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(15, 123, 138, 0.42), transparent 62%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  box-shadow: 0 14px 34px rgba(27, 28, 80, 0.22);
}
.practice-hero-main { min-width: 0; }
.practice-hero-eyebrow {
  margin: 0 0 6px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}
.practice-hero-title {
  margin: 0 0 8px;
  font-family: 'Poppins', sans-serif;
  font-size: 25px;
  font-weight: 700;
  line-height: 1.22;
  color: #fff;
}
.practice-hero-lead {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.85);
}

/* The three numbers a parent weighs before starting: how many sittings,
   how long each, and how much of it they have already paid for. */
.practice-hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}
.practice-hero-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 96px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.16);
}
.phs-num {
  font-family: 'Poppins', sans-serif;
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
  color: #fff;
}
.phs-label { font-size: 11px; line-height: 1.3; color: rgba(255, 255, 255, 0.68); }

.practice-hero-actions { display: flex; flex-wrap: wrap; gap: 10px; }
/* The ghost button's default border is the light-theme one and vanishes on
   navy, so it gets its own treatment rather than inheriting an invisible
   outline. */
.practice-hero-actions .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.38);
  background: transparent;
}
.practice-hero-actions .btn-ghost:hover {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.1);
}

.practice-hero-engine {
  padding: 16px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.practice-hero-engine-title {
  margin: 0 0 10px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}
.practice-hero-engine-list { margin: 0; padding: 0; list-style: none; }
.practice-hero-engine-list li {
  position: relative;
  padding-left: 20px;
  margin-bottom: 9px;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.86);
}
.practice-hero-engine-list li:last-child { margin-bottom: 0; }
.practice-hero-engine-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--amber);
}

/* No plan to name yet. Same shape, no numbers strip - an empty stats row
   would be three blanks where the specifics are meant to be. */
.practice-hero-empty .practice-hero-title { font-size: 22px; }

@media (max-width: 860px) {
  .practice-hero { grid-template-columns: 1fr; gap: 16px; padding: 20px 18px; }
  .practice-hero-title { font-size: 21px; }
  /* Stacked, the hero came to 763px on a 375-wide phone - the whole screen,
     with the child's levels pushed below the fold. The specifics all stay;
     the padding around them does not. */
  .practice-hero-lead { margin-bottom: 12px; font-size: 13.5px; }
  .practice-hero-stats { gap: 8px; margin-bottom: 14px; }
  .practice-hero-stat { min-width: 0; flex: 1 1 30%; padding: 8px 10px; }
  .phs-num { font-size: 17px; }
  .phs-label { font-size: 10.5px; }
  .practice-hero-engine { padding: 13px 15px; }
  .practice-hero-engine-list li { margin-bottom: 7px; font-size: 12px; }
  .practice-hero-actions .btn { flex: 1 1 auto; text-align: center; }
}
@media print { .practice-hero { display: none !important; } }

/* ---- Every tab opens on a hero (2026-08-25) ---------------------------
   Overview got one and the other five still opened on a plain white card,
   so the portal looked like one designed tab and five spreadsheets. These
   are the existing first blocks - .module-hero on the four subject tabs and
   the suggestions block on Assignments - given the same ground as the
   practice hero rather than new markup nobody asked for.

   ONE ground, shared: the navy-and-teal gradient is now what "this is the
   top of the page and the thing that matters" means across the whole
   product - portal heroes, the report's 60-seconds block, and this. */
.module-hero,
.asg-summary,
.overview-levels-row {
  color: #fff;
  border: 0 !important;
  border-radius: 18px;
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(15, 123, 138, 0.42), transparent 62%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%) !important;
  box-shadow: 0 14px 34px rgba(27, 28, 80, 0.22);
}

/* --- the four subject tabs ------------------------------------------- */
.module-hero .module-hero-eyebrow { color: rgba(255, 255, 255, 0.72); }
.module-hero .module-hero-level { color: #fff; }
.module-hero .module-hero-desc { color: rgba(255, 255, 255, 0.82); }
/* The number the sentence is about. .module-hero-desc was lifted for the dark
   banner above; the <strong> inside it kept --navy-dark and so rendered at
   1.00:1 - the same colour as the panel. It is emphasis, so it goes brighter
   than the surrounding copy rather than merely legible. */
.module-hero .module-hero-desc strong { color: #fff; }
.module-hero .module-hero-pointer { color: rgba(255, 255, 255, 0.68); }
.module-hero .module-hero-pointer strong { color: #fff; }

/* ---- THE EMPTY RIGHT-HAND SIDE OF THE SUBJECT BANNER (2026-08-28) --------
   .module-hero-lead is flex:1 so it owns the full width, but the copy inside
   it is short - which left a large blank area on the right of all four
   subject banners. This puts the TopperLens lens mark there.

   THE SPACE IS RESERVED, WHICH IS WHAT LETS THE MARK BE BRIGHT. The first
   version of this was painted dark, because a pale mark ghosted over the
   navy could not be made legal: this banner is already close to the floor -
   .mhf-label and .module-hero-ring-of are white at 66%, and where the
   panel's teal radial lifts the top-right corner they measure 4.89:1
   against a 4.5 requirement. Lightening the ground under them broke it at
   any opacity worth having (teal at 6% -> 4.44:1, and 6% is already
   invisible).

   But that whole calculation only applies where text SITS ON the mark, and
   at these widths none of it does - the copy stops hundreds of pixels short.
   So rather than dimming the mark to survive an overlap that does not
   happen, the padding-right below makes the overlap impossible, and the
   mark is then free to be brand teal at an opacity you can actually see.
   Reserved width is 232px: the 200px box, its 16px offset, and a 16px
   gutter. Text wraps before it; the focus panel and the strip's rule still
   run the full width, because only their padding changes, not their box.

   The icon supplies its SHAPE only, via mask-image. Its artwork is drawn
   for light backgrounds (mean luminance 35/255) so painting it directly
   would have shown almost nothing; the mask throws the colour away and this
   rule fills the silhouette. The lens cutout in its own alpha lets the
   panel show through the middle, which is what makes it read as the mark
   rather than a disc.

   No ?v= on the URL, deliberately. The favicon carries a stamp that moves
   every release; auth.css:671 pinned that stamp once and has been stale
   since, quietly costing a second download of the same file on every bump.
   A bare URL caches once and stays cached. */
.module-hero { position: relative; isolation: isolate; }

/* Without mask support this would paint as a plain teal rectangle, so the
   whole thing is behind a support query rather than degrading into a smear. */
@supports ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .module-hero::after {
    content: '';
    position: absolute;
    top: 10px;
    bottom: 10px;
    right: 16px;
    width: 200px;
    /* mask-size:contain fits the SHORTER side, so the mark can never grow
       past the banner however tall the banner happens to be - the focus row
       is conditional and three of the four heroes are shorter than the
       fourth. No overflow:hidden needed, and nothing inside gets clipped. */
    background: linear-gradient(to bottom left, rgba(126, 224, 226, 0.30), rgba(126, 224, 226, 0.07));
    -webkit-mask-image: url('../assets/logo/topperlens-junior-icon.png');
            mask-image: url('../assets/logo/topperlens-junior-icon.png');
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: right center;
            mask-position: right center;
    -webkit-mask-size: contain;
            mask-size: contain;
    pointer-events: none;
    /* isolation:isolate above makes .module-hero a stacking context, so -1
       lands between the panel's own gradient and its content: behind every
       word, in front of the background. */
    z-index: -1;
  }

  /* THE GUARANTEE THE BRIGHTNESS RESTS ON. Every text-bearing row in the
     banner stops short of the mark's column, so no glyph is ever composited
     over a lightened ground. Paired with the rule below: the reservation
     exists exactly while the mark does, and both stop at the same width. */
  @media (min-width: 901px) {
    .module-hero .module-hero-lead,
    .module-hero .module-hero-focus,
    .module-hero .module-hero-pointer,
    .module-hero .module-hero-strip { padding-right: 232px; }
  }
}

/* Narrow banners cannot spare 232px for decoration, and the facts strip
   reflows rightward across the mark. Both go together. */
@media (max-width: 900px) {
  .module-hero::after { content: none; }
}


/* The ring's track was var(--border) hardcoded INSIDE the inline
   conic-gradient - a pale grey that reads as filled on navy, so level 2 of
   5 would have drawn as a full ring. An inline style cannot be overridden
   from a stylesheet, so the track is a custom property now, the same fix
   the report's score ring needed. */
.module-hero { --mh-ring-track: rgba(255, 255, 255, 0.18); }
.module-hero .module-hero-ring-empty { background: rgba(255, 255, 255, 0.18); }
.module-hero .module-hero-ring-inner { background: var(--navy-dark); }
.module-hero .module-hero-ring-num { color: #fff; }
.module-hero .module-hero-ring-of { color: rgba(255, 255, 255, 0.66); }

.module-hero .module-hero-strip { border-top-color: rgba(255, 255, 255, 0.16); }
.module-hero .module-hero-fact.mhf-divide { border-left-color: rgba(255, 255, 255, 0.16); }
.module-hero .mhf-value { color: #fff; }
.module-hero .mhf-label { color: rgba(255, 255, 255, 0.66); }

.module-hero .module-hero-focus {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.88);
}
.module-hero .module-hero-focus strong { color: #fff; }

/* Ghost buttons carry a light-theme border that disappears on navy. */
.module-hero .btn-ghost,
.asg-summary .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.38);
  background: transparent;
}
.module-hero .btn-ghost:hover,
.asg-summary .btn-ghost:hover {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.1);
}
.module-hero .module-action-muted { color: rgba(255, 255, 255, 0.7); }

/* --- Overview -----------------------------------------------------------
   With the practice hero moved into the report, the levels strip is what
   opens this tab, so it takes the hero ground. It is the right block for it
   anyway: where the child stands, subject by subject, is what a parent came
   to the front page for. */
.overview-levels-row { padding: 22px 24px; }
.overview-levels-row .overview-level-card {
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 14px;
}
.overview-levels-row .overview-level-module { color: rgba(255, 255, 255, 0.78); }
.overview-levels-row .overview-level-name { color: rgba(255, 255, 255, 0.85); }
.overview-levels-row .overview-level-headline { color: #fff; }
.overview-levels-row .overview-level-headline-blank { color: rgba(255, 255, 255, 0.55); }
.overview-levels-row .overview-level-empty { color: rgba(255, 255, 255, 0.78); }

/* The ladder on navy. --teal is a mid tone: it carries white text well on a
   white ground, but on this gradient #fff on #0F7B8A is only 4.3:1 and the
   filled step half disappears into the background. And a done step cannot be
   a translucent tint here - 16% of a teal over the gradient's teal corner
   leaves its numeral at 3.8:1, and lightening it only works by going to
   near-white, at which point done and not-reached stop being told apart. So
   both reached states are solid and take navy text; brightness says which
   one is now. 5.1:1 and 10.3:1 at the worst corner. */
/* ---- ACTIONS ON A SUBJECT CARD, CHILD'S VIEW ONLY (2026-08-27) --------
   The strip is a dark navy panel, so none of the site's normal button
   colours apply here - btn-primary is navy on navy and btn-ghost is a grey
   border that all but vanishes. These three are measured against both ends
   of the panel's gradient (#282972 and #1B1C50):
     practice fill   #10303A on #7EE0E2   9.06:1
     test outline    #fff on navy        12.72:1  (border 4.94:1, needs 3)
     disabled label  66% white on wash    5.54:1
     quota / reason  74-78% white         7.66:1
   Nothing here relies on colour alone: a disabled control also carries
   aria-disabled and a line of text saying why. */
/* ---- THE PRACTICE AREA PICKER (2026-08-27) -----------------------------
   Inside .modal-card, so this is the light surface - none of the dark-strip
   colours from .ov-lvl-* apply here. Options are anchors styled as cards;
   they need an explicit min-height so a one-word area and a four-word one
   do not produce a ragged grid. */
.pp-opt {
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding: 13px 15px; border-radius: 12px; text-decoration: none;
  border: 1px solid var(--border, #E4E4EC); background: #fff;
  transition: border-color .12s ease, background .12s ease, transform .12s ease;
}
.pp-opt:hover, .pp-opt:focus-visible {
  border-color: var(--teal, #0F7B8A); background: #F3FBFC; transform: translateY(-1px);
}
.pp-opt-name { font-family: 'Poppins', sans-serif; font-size: 14.5px; font-weight: 600; color: var(--navy, #1B1C50); line-height: 1.3; }
.pp-opt-note { font-size: 12.5px; line-height: 1.45; color: var(--text-muted, #5F5F75); }
.pp-mix {
  margin-top: 4px; border-color: var(--teal, #0F7B8A);
  background: linear-gradient(135deg, #F3FBFC 0%, #EAF7F9 100%);
}
.pp-mix:hover { background: linear-gradient(135deg, #EAF7F9 0%, #DFF3F6 100%); }
.pp-or {
  margin: 18px 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted, #5F5F75); text-align: center;
}
.pp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr)); gap: 10px; }
.pp-area { min-height: 62px; }
/* The weakest area is marked, never auto-selected - a child choosing to work
   on something else is a legitimate choice, not a mistake to steer away from. */
.pp-area.is-weakest { border-color: var(--amber, #E8833A); background: #FFF8F2; }
.pp-area.is-weakest:hover { background: #FFF1E5; }
.pp-opt-tag {
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  color: #8A4409; /* 5.6:1 on #FFF8F2 - the tag is the smallest text here */
}
@media (max-width: 520px) { .pp-grid { grid-template-columns: 1fr; } .pp-area { min-height: 0; } }

.ov-lvl-quota {
  margin: 12px 0 0; font-size: 12px; line-height: 1.45;
  color: rgba(255, 255, 255, 0.78);
}
.ov-lvl-quota strong { color: #fff; font-weight: 700; }
.ov-lvl-actions { display: flex; gap: 8px; margin-top: 9px; }
.ov-lvl-actions .btn {
  flex: 1 1 0; min-width: 0; padding: 8px 4px; font-size: 12.5px;
  border-radius: 9px; text-align: center; white-space: nowrap;
  text-overflow: ellipsis; overflow: hidden;
}
.ov-lvl-go {
  background: #7EE0E2; border: 1px solid #7EE0E2; color: #10303A; font-weight: 600;
}
.ov-lvl-go:hover, .ov-lvl-go:focus-visible { background: #9BEBED; border-color: #9BEBED; color: #10303A; }
.ov-lvl-test {
  background: transparent; border: 1px solid rgba(255, 255, 255, 0.55); color: #fff; font-weight: 600;
}
.ov-lvl-test:hover, .ov-lvl-test:focus-visible { background: rgba(255, 255, 255, 0.12); border-color: #fff; color: #fff; }
.ov-lvl-actions .is-off {
  background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.66); font-weight: 500; cursor: not-allowed;
}
.ov-lvl-why { margin: 8px 0 0; font-size: 11.5px; line-height: 1.42; color: rgba(255, 255, 255, 0.74); }
/* Two buttons side by side stop being readable long before the card does. */
@media (max-width: 1040px) { .ov-lvl-actions { flex-direction: column; } }

.overview-levels-row .ov-step {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.75);
}
.overview-levels-row .ov-step.is-done { background: #4E9EA8; border-color: #4E9EA8; color: var(--navy-dark); }
.overview-levels-row .ov-step.is-here { background: #7EE0E2; border-color: #7EE0E2; color: var(--navy-dark); }
.overview-levels-row .overview-ladder.is-blank .ov-step { color: rgba(255, 255, 255, 0.4); }

/* ONE LENGTH, ONE COLOUR (2026-08-26). The fill ran teal to amber, so a 6%
   bar was a short multicoloured stub and a 78% bar was mostly amber - the
   hue changed with the value while meaning nothing by it, which is exactly
   what stops four bars side by side from being comparable at a glance. A
   single colour leaves length as the only variable, which is the one that
   carries the number. */
.overview-levels-row .ov-lvl-bar { background: rgba(255, 255, 255, 0.14); }
.overview-levels-row .ov-lvl-bar i { background: #7EE0E2; }

/* The delta chips are light-theme pills: teal on a 10%-white wash lands
   under 3:1 against this gradient. */
/* ON ANY DARK PANEL, not just the subject cards (widened 2026-08-27). The
   subject banner shows the same chips and was still using the light-theme
   colours: --amber-dark on a 12% amber pill over navy measures 2.84:1.
   Adding .module-hero here rather than copying the three rules - a second
   copy is exactly how the first one drifted out of date. */
.overview-levels-row .overview-delta-chip.up,
.module-hero .overview-delta-chip.up { color: #D8FAFB; background: rgba(126, 224, 226, 0.12); }
.overview-levels-row .overview-delta-chip.down,
.module-hero .overview-delta-chip.down { color: #FFDCC8; background: rgba(243, 113, 48, 0.14); }
.overview-levels-row .overview-delta-chip.steady,
.module-hero .overview-delta-chip.steady { color: rgba(255, 255, 255, 0.78); background: rgba(255, 255, 255, 0.12); }

/* Says the plan lapsed WITHOUT taking the result away with it. */
/* Says the plan lapsed without pretending the findings did. */
.obs-paused {
  margin: 0 0 14px;
  padding: 9px 13px;
  border-radius: 10px;
  background: var(--bg-light);
  border-left: 3px solid var(--amber-dark);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}

.overview-level-paused {
  margin: 6px 0 0;
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--text-muted);
}
.overview-levels-row .overview-level-paused { color: rgba(255, 255, 255, 0.58); }
/* Both of these sit on the navy hero, where the light-theme "i" is a dark
   ring on a dark ground. */
.overview-levels-row .metric-info-btn,
.asg-summary .metric-info-btn {
  color: rgba(255, 255, 255, 0.85);
  border-color: rgba(255, 255, 255, 0.45);
}
.overview-levels-row .metric-info-btn:hover,
.asg-summary .metric-info-btn:hover {
  color: #fff;
  border-color: #fff;
  background: rgba(255, 255, 255, 0.14);
}

/* --- Assignments -------------------------------------------------------
   The counts strip is this tab's headline - waiting, past its date,
   finished - so it is the one that becomes the hero. Not the suggestions
   block below it: that renders only sometimes, and a hero that appears and
   disappears is not a hero. The strip is padded to hero proportions, since
   it was three bare tiles with no ground of its own. */
.asg-summary { padding: 22px 24px; gap: 14px; }
.asg-summary .asg-sum-tile {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 14px;
}
.asg-summary .asg-sum-num { color: #fff; }
.asg-summary .asg-sum-label { color: rgba(255, 255, 255, 0.68); }
/* "Past its date" keeps its warning colour - it is the one number here a
   parent should be able to spot without reading the labels. */
.asg-summary .asg-sum-over .asg-sum-num { color: #FFC0A0; }

/* The tab's own heading sits above these in static markup. On navy it would
   be a second title competing with the hero's own - so it stays a quiet
   label rather than a headline. */
.portal-tab-panel > .profile-subsection-title + .section-sub { margin-bottom: 16px; }

@media print {
  .module-hero,
  .asg-summary,
  .overview-levels-row {
    color: var(--text-dark);
    background: #fff !important;
    border: 1px solid var(--border) !important;
    box-shadow: none;
  }
}

/* ---- "A newer version is available" bar (2026-08-25) ------------------
   Bottom-centre, not top: the top of every page here is a hero, and a bar
   that pushes it down redraws the whole layout to say something minor. */
.new-build-bar {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(100vw - 32px);
  padding: 10px 12px 10px 18px;
  border-radius: 999px;
  font-size: 13.5px;
  color: #fff;
  background: var(--navy-dark);
  box-shadow: 0 10px 30px rgba(27, 28, 80, 0.32);
}
.new-build-bar .new-build-reload {
  background: var(--amber);
  color: #fff;
  border: 0;
  border-radius: 999px;
  padding: 6px 16px;
  font-weight: 600;
  cursor: pointer;
}
.new-build-bar .new-build-reload:hover { background: var(--amber-dark); }
.new-build-dismiss {
  background: none;
  border: 0;
  color: rgba(255, 255, 255, 0.7);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.new-build-dismiss:hover { color: #fff; }
@media print { .new-build-bar { display: none !important; } }

/* ---- Level history: a progress rail (2026-08-25) ----------------------
   Dark ground, because this is the block that opens the step and the rail
   only reads as a rail against something solid. The first attempt at a dark
   treatment failed on the badges - the level badge is an SVG built for a
   white page, owl filled var(--navy) and unfilled ring var(--border), so on
   navy the owl sank into the ground and the ring read as complete. Here each
   badge gets its own white disc, which is what makes the dark ground
   workable: the SVG renders in exactly the conditions it was drawn for. */
.report-section.report-level-history {
  border: 0;
  border-radius: 18px;
  color: #fff;
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(15, 123, 138, 0.5), transparent 60%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  box-shadow: 0 14px 34px rgba(27, 28, 80, 0.24);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* Descendant, never "> h2" - attachSectionExplainer() wraps the heading in a
   .section-head-row at runtime once the section has an "i" button. */
.report-section.report-level-history h2 { color: #fff; }
.report-section.report-level-history > .section-sub { color: rgba(255, 255, 255, 0.78); }
.report-level-history .metric-info-btn,
.report-level-history .section-info-btn {
  color: rgba(255, 255, 255, 0.8);
  border-color: rgba(255, 255, 255, 0.4);
}

/* Nodes and connectors alternate in one row. Scrolls rather than crushing:
   a child with eight attempts keeps readable nodes and loses nothing. */
.lh-rail {
  display: flex;
  align-items: flex-start;
  margin-top: 24px;
  padding-bottom: 4px;
  overflow-x: auto;
}
.lh-node {
  flex: 0 0 auto;
  min-width: 92px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
}
.lh-badge {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: #fff;
  display: grid;
  place-items: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
}
.lh-now .lh-badge {
  box-shadow: 0 0 0 4px rgba(111, 220, 232, 0.35), 0 6px 18px rgba(0, 0, 0, 0.34);
}
.lh-date { font-size: 11.5px; font-weight: 600; color: rgba(255, 255, 255, 0.66); }
.lh-level { font-family: 'Poppins', sans-serif; font-size: 15px; font-weight: 700; color: #fff; }
.lh-nowtag {
  margin-top: 1px;
  padding: 2px 9px;
  border-radius: 999px;
  background: #6FDCE8;
  color: var(--navy-dark);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

/* The connector carries the change. 64px tall so its centre lines up with
   the middle of the badge discs either side of it. */
.lh-link {
  flex: 1 1 70px;
  min-width: 56px;
  position: relative;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lh-link::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 31px;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.24), rgba(111, 220, 232, 0.85));
}
.lh-delta {
  position: relative;
  z-index: 1;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  color: #fff;
}
.lh-delta.is-up { background: rgba(15, 123, 138, 0.95); }
/* --amber-dark, not --amber. White on #F37130 measures 2.91:1 and this is
   an 11px bold chip, which needs 4.5. The darker amber takes it to 5.14
   and still reads as the same colour at a glance. The rising chip was
   already fine at 4.98. */
.lh-delta.is-down { background: rgba(184, 76, 24, 0.97); }
.lh-delta.is-steady { background: rgba(255, 255, 255, 0.22); color: rgba(255, 255, 255, 0.9); }

@media (max-width: 560px) {
  .lh-node { min-width: 78px; }
  .lh-badge { width: 54px; height: 54px; }
  .lh-link { min-width: 44px; flex-basis: 44px; }
  .lh-delta { font-size: 10px; padding: 2px 7px; }
}

@media print {
  .report-section.report-level-history {
    color: var(--text-dark);
    background: #fff;
    border: 1px solid var(--border);
    box-shadow: none;
  }
  .report-section.report-level-history h2 { color: var(--navy-dark); }
  .report-section.report-level-history > .section-sub { color: var(--text-muted); }
  .lh-rail { overflow-x: visible; }
  .lh-badge { box-shadow: none; }
  .lh-date { color: var(--text-muted); }
  .lh-level { color: var(--navy-dark); }
  .lh-nowtag { background: rgba(15, 123, 138, 0.14); color: var(--teal); }
  .lh-link::before { background: var(--border); }
  .lh-delta.is-up { background: rgba(15, 123, 138, 0.14); color: var(--teal); }
  .lh-delta.is-down { background: rgba(184, 76, 24, 0.14); color: var(--amber-dark); }
  .lh-delta.is-steady { background: var(--bg-light); color: var(--text-muted); }
}

/* ---- Aggregated mock comparisons (2026-08-25) -------------------------
   The comparisons were one long scroll. Four groups, each opening on a
   hero, using the same pill-tab shape the portal already uses - one
   navigation idea in this product rather than two. */
.agg-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
}
.agg-tab {
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 999px;
  padding: 7px 16px;
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
}
.agg-tab:hover { border-color: var(--teal); color: var(--teal); }
.agg-tab.active {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}
.agg-panel { display: none; }
.agg-panel.active { display: block; }

/* One card per area, each holding its own line across every attempt. */
.report-section.report-areas-hero,
.report-section.report-span-hero {
  border: 0;
  border-radius: 18px;
  color: #fff;
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(15, 123, 138, 0.45), transparent 60%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  box-shadow: 0 14px 34px rgba(27, 28, 80, 0.22);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* Descendant, not "> h2" - the runtime wraps headings in .section-head-row
   once a section gets an "i" button. */
.report-section.report-areas-hero h2,
.report-section.report-span-hero h2 { color: #fff; }
/* .auth-sub as well as .section-sub: these heroes use whichever the
   generator happened to reach for, and only one of the two was listed -
   so the areas hero had a sub-heading at 2.0:1 on its own dark ground. */
.report-section.report-areas-hero > .section-sub,
.report-section.report-areas-hero > .auth-sub,
.report-section.report-span-hero > .section-sub,
.report-section.report-span-hero > .auth-sub { color: rgba(255, 255, 255, 0.78); }
.report-areas-hero .metric-info-btn,
.report-areas-hero .section-info-btn,
.report-span-hero .metric-info-btn,
.report-span-hero .section-info-btn {
  color: rgba(255, 255, 255, 0.8);
  border-color: rgba(255, 255, 255, 0.4);
}

.agg-area-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-top: 18px;
}
/* White cards on the dark ground: the sparkline is a teal stroke drawn for
   a light page, and the same stroke on navy is the ring problem again. */
.agg-area-card {
  background: #fff;
  border-radius: 14px;
  padding: 13px 15px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.14);
}
.agg-area-name {
  margin: 0 0 4px;
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 700;
  color: var(--navy-dark);
}
.agg-area-spark svg { display: block; width: 100%; height: 40px; }
.agg-area-figures { margin: 4px 0 0; font-size: 12.5px; color: var(--text-muted); }
.agg-area-figures strong { color: var(--navy-dark); }
.agg-area-diff {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
}
.agg-area-diff.is-up { background: rgba(15, 123, 138, 0.12); color: var(--teal); }
.agg-area-diff.is-down { background: rgba(184, 76, 24, 0.14); color: var(--amber-dark); }
.agg-area-diff.is-steady { background: var(--bg-light); color: var(--text-muted); }

@media (max-width: 900px) { .agg-area-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .agg-area-grid { grid-template-columns: 1fr; } }

/* PRINT SHOWS EVERY GROUP. A PDF containing only whichever tab happened to
   be open is a broken export - the same rule the report's stepper follows. */
@media print {
  .agg-tabs { display: none !important; }
  .agg-panel { display: block !important; }
  .report-section.report-areas-hero,
  .report-section.report-span-hero {
    color: var(--text-dark);
    background: #fff;
    border: 1px solid var(--border);
    box-shadow: none;
  }
  .report-section.report-areas-hero h2,
  .report-section.report-span-hero h2 { color: var(--navy-dark); }
  .report-section.report-areas-hero > .section-sub,
  .report-section.report-areas-hero > .auth-sub,
  .report-section.report-span-hero > .section-sub,
  .report-section.report-span-hero > .auth-sub { color: var(--text-muted); }
  .agg-area-card { box-shadow: none; border: 1px solid var(--border); }
}

/* The portal's rail uses a level RING rather than the owl badge - that SVG
   lives in report.js, which profile.html does not load. Same node, same
   connector, a ring where the badge goes. */
.lh-ring {
  width: 64px; height: 64px; border-radius: 50%;
  display: grid; place-items: center;
  background: conic-gradient(var(--teal) 0% var(--lh-pct, 0%), rgba(255, 255, 255, 0.18) var(--lh-pct, 0%) 100%);
}
.lh-ring-inner {
  width: 48px; height: 48px; border-radius: 50%;
  background: var(--navy-dark);
  display: grid; place-items: center;
  font-family: 'Poppins', sans-serif; font-size: 17px; font-weight: 700; color: #fff;
}
@media (max-width: 560px) {
  .lh-ring { width: 54px; height: 54px; }
  .lh-ring-inner { width: 40px; height: 40px; font-size: 15px; }
}

/* Tiny, muted, bottom-left, never in the way - see showBuildStamp(). */
.build-stamp {
  position: fixed;
  left: 8px;
  bottom: 6px;
  z-index: 4;
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  opacity: 0.45;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.build-stamp:hover { opacity: 0.9; }
@media print { .build-stamp { display: none !important; } }

/* ---- "This is an average" banner (2026-08-26) -------------------------
   Amber, not navy. Every other band at the top of a report is a hero; this
   one is a caveat, and it has to read as a different KIND of thing or it
   just looks like more chrome above the numbers it is qualifying. */
.report-aggregate-banner {
  border-radius: 14px;
  border-left: 4px solid var(--amber-dark);
  background: rgba(243, 113, 48, 0.08);
  padding: 16px 18px;
  margin: 0 0 18px;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.rab-eyebrow {
  margin: 0 0 5px;
  font-family: 'Poppins', sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--amber-dark);
}
.rab-text { margin: 0; font-size: 14px; line-height: 1.6; color: var(--text-dark); }
.rab-note {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* ---- "How practice is going" (2026-08-25) -----------------------------
   Three questions, in the order a parent asks them. Plain blocks separated
   by a rule rather than three cards: they are one argument read top to
   bottom, not three independent findings. */
.pq-block { padding: 16px 0; border-bottom: 1px solid var(--border); }
.pq-block:first-child { padding-top: 4px; }
.pq-block:last-of-type { border-bottom: 0; }
.pq-q {
  margin: 0 0 8px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.pq-answer {
  margin: 0 0 6px;
  font-family: 'Poppins', sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--navy-dark);
}
.pq-answer.is-good { color: var(--teal); }
.pq-answer.is-watch { color: var(--amber-dark); }
.pq-note { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-muted); }
.pq-note strong { color: var(--navy-dark); }
.pq-caveat { margin-top: 12px; }

/* ---- Three questions, three rings (2026-08-25) ------------------------
   They were three stacked blocks running most of a screen, and the answer
   to each was a sentence you had to read to get. A ring states it, and the
   detail that earns its place - which areas moved, and by how much - stays
   underneath. */
.pq-tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin: 4px 0 18px;
}
.pq-tile {
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px 16px;
  background: var(--bg-card);
  text-align: center;
}
.pq-ring {
  position: relative;
  width: 88px;
  height: 88px;
  margin: 0 auto 12px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--pq-c) calc(var(--pct) * 1%), var(--bg-light) 0);
}
.pq-ring::after {
  content: "";
  position: absolute;
  inset: 9px;
  border-radius: 50%;
  background: var(--bg-card);
}
.pq-ring-in {
  position: relative;
  z-index: 1;
  font-family: 'Poppins', sans-serif;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--navy-dark);
}
.pq-ring-in small { font-size: 13px; font-weight: 600; color: var(--text-muted); }
/* Tone carries the reading, so the ring and the answer beneath it can never
   disagree - both are set from the same word. */
.pq-good { --pq-c: var(--teal); }
.pq-mid { --pq-c: #6FDCE8; }
.pq-watch { --pq-c: var(--amber-dark); }
.pq-flat { --pq-c: var(--border); }
.pq-good-t { color: var(--teal); }
.pq-mid-t { color: var(--teal); }
.pq-watch-t { color: var(--amber-dark); }
.pq-flat-t { color: var(--text-muted); }
.pq-tile-q {
  margin: 0 0 4px;
  font-family: 'Poppins', sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.pq-tile-a { margin: 0 0 6px; font-family: 'Poppins', sans-serif; font-size: 15px; font-weight: 700; }
.pq-tile-s { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.pq-tile-s strong { color: var(--navy-dark); }

.pq-rows { margin-bottom: 4px; }
.pq-rows-h {
  margin: 0 0 6px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}

@media (max-width: 760px) { .pq-tiles { grid-template-columns: 1fr; } }

.pq-aim {
  display: flex;
  height: 24px;
  margin: 10px 0 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.pq-on { background: var(--teal); }
.pq-off { background: var(--bg-light); }
.pq-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); }
.pq-key { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
.pq-key-on { background: var(--teal); }
.pq-key-off { background: var(--border); }

/* Area rows. The grey bar behind the teal one is where the area stood a
   fortnight ago, so the movement is visible without a second chart. */
.pq-row {
  display: grid;
  grid-template-columns: minmax(0, 160px) 1fr 96px;
  gap: 14px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.pq-row:last-of-type { border-bottom: 0; }
.pq-area { font-size: 13.5px; font-weight: 600; color: var(--navy-dark); }
.pq-area small { display: block; font-weight: 400; font-size: 11.5px; color: var(--text-muted); margin-top: 1px; }
.pq-track { position: relative; height: 9px; border-radius: 999px; background: var(--bg-light); overflow: hidden; }
.pq-track i { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 999px; }
.pq-was { background: var(--border); }
.pq-now { background: var(--teal); }
.pq-val { text-align: right; font-family: 'Poppins', sans-serif; font-size: 14px; font-weight: 700; color: var(--navy-dark); }
.pq-val small { display: block; font-size: 11px; font-weight: 600; margin-top: 2px; }
.pq-val .is-up { color: var(--teal); }
.pq-val .is-down { color: var(--amber-dark); }
.pq-val .is-flat { color: var(--text-muted); }

/* One line, not a section: "did they do the thing on Tuesday" is the
   Assignments tab's job, and it answers it better. */
.pq-last {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
  font-size: 12px;
  color: var(--text-muted);
}

@media (max-width: 620px) {
  .pq-row { grid-template-columns: 1fr 72px; }
  .pq-track { grid-column: 1 / -1; }
}

/* ---- The child's view of practice (2026-08-25) ------------------------
   Dark, because this one is theirs and should feel unlike the pages their
   parent reads. No percentage anywhere on it, by design. */
/* The summary card opens BOTH views, so it needs air beneath it when the
   parent's three questions follow. */
.pc-hero + .pq-block { padding-top: 20px; }
.pc-hero { margin-bottom: 4px; 
  border-radius: 18px;
  padding: 24px 26px;
  color: #fff;
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(15, 123, 138, 0.45), transparent 60%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
}
.pc-title { margin: 0 0 4px; font-family: 'Poppins', sans-serif; font-size: 20px; font-weight: 700; color: #fff; }
.pc-sub { margin: 0 0 16px; font-size: 13px; color: rgba(255, 255, 255, 0.75); line-height: 1.5; }
.pc-top { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.pc-num { font-family: 'Poppins', sans-serif; font-size: 50px; font-weight: 700; line-height: 1; color: #6FDCE8; }
.pc-cap { margin: 0; flex: 1 1 260px; font-size: 13px; line-height: 1.55; color: rgba(255, 255, 255, 0.85); }
.pc-cards { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.pc-card {
  flex: 1 1 200px;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.16);
}
.pc-card-t { margin: 0 0 3px; font-family: 'Poppins', sans-serif; font-size: 12.5px; font-weight: 700; color: #fff; }
.pc-card-s { margin: 0; font-size: 12px; line-height: 1.45; color: rgba(255, 255, 255, 0.75); }
.pc-card-s b { color: #6FDCE8; }
.pc-week { display: flex; align-items: center; gap: 6px; margin-top: 18px; flex-wrap: wrap; }
.pc-dot {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700;
  background: rgba(255, 255, 255, 0.12); color: rgba(255, 255, 255, 0.55);
}
.pc-dot.is-on { background: #6FDCE8; color: var(--navy-dark); }
.pc-week-note { margin-left: 8px; font-size: 11.5px; color: rgba(255, 255, 255, 0.6); }

/* ---- Speed against accuracy, for a teacher ---------------------------- */
.pt-quad {
  position: relative;
  height: 270px;
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: linear-gradient(to right, rgba(243, 113, 48, 0.05), rgba(15, 123, 138, 0.05));
}
.pt-line { position: absolute; background: var(--border); }
.pt-h { left: 0; right: 0; top: 50%; height: 1px; }
.pt-v { top: 0; bottom: 0; left: 50%; width: 1px; }
.pt-lab {
  position: absolute;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted);
}
.pt-pt { position: absolute; transform: translate(-50%, -50%); text-align: center; }
.pt-pt i {
  display: block; width: 13px; height: 13px; border-radius: 50%;
  background: var(--teal); margin: 0 auto 4px;
  box-shadow: 0 0 0 4px rgba(15, 123, 138, 0.16);
}
.pt-pt.is-warn i { background: var(--amber); box-shadow: 0 0 0 4px rgba(243, 113, 48, 0.18); }
.pt-pt span { font-size: 11.5px; font-weight: 600; color: var(--navy-dark); white-space: nowrap; }
.pt-axis { display: flex; justify-content: space-between; margin-top: 8px; font-size: 11.5px; color: var(--text-muted); }
.pt-reads { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.pt-read { flex: 1 1 240px; border-radius: 12px; padding: 13px 15px; }
.pt-read p { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text-muted); }
.pt-read strong { color: var(--navy-dark); }
.pt-read-warn { background: rgba(243, 113, 48, 0.08); border-left: 3px solid var(--amber-dark); }
.pt-read-ok { background: rgba(15, 123, 138, 0.07); border-left: 3px solid var(--teal); }

@media (max-width: 620px) {
  .pc-num { font-size: 40px; }
  .pt-quad { height: 230px; }
  .pt-pt span { font-size: 10.5px; }
}
@media print { .pt-quad { break-inside: avoid; page-break-inside: avoid; } }

/* ---- Ctrl+P on the portal (2026-08-25) --------------------------------
   The report is an IFRAME inside profile.html. Printing the portal page
   prints the portal - tab strips, sidebar, subscription panel and all -
   with the report laid out at its on-screen column width rather than the
   paper's, which is what trims the right-hand edge off a page.
   There is a correct route: Downloads gives report.html?download=1, the
   standalone page, which prints properly. So printing the portal now
   prints one line saying so, rather than a clipped copy of a document
   that has a good version one click away. */
@media print {
  body.portal-print-guard > *,
  body.portal-print-guard .site-header,
  body.portal-print-guard .profile-main { display: none !important; }
  body.portal-print-guard::after {
    content: "To print or save a report, open the child's subject tab and use Downloads. That opens the report on its own page, which is laid out for paper.";
    display: block;
    padding: 40mm 10mm 0;
    font-family: 'Inter', Arial, sans-serif;
    font-size: 13pt;
    line-height: 1.6;
    color: #1D1D2B;
    text-align: center;
  }
}

/* ---- Guided practice (2026-08-25) -------------------------------------
   Deliberately quiet. The button sits below the options, smaller than
   them, because the first thing a child should do is try - an inviting
   help button beside the answers is one they press first. */
/* THE GUIDE IS ITS OWN CARD, beside the question (2026-08-25).
   First it sat under the options, where it read as the next step. Then it
   became a second column INSIDE the card, which halved the question's
   width - the prompt, the one thing a child has to read, wrapped to three
   lines while the help beside it had room to spare. Both were the same
   error: treating help as part of the question.
   It is a separate panel now. The question card keeps the width it has
   always had, and nothing about the exam changes. */
/* ---- The practice stage (2026-08-25, grid) ----------------------------
   GRID, NOT FLEX, because the question card has to sit beside a right-hand
   side that holds two cards stacked. Flex could put three cards in a row or
   wrap the third to full width underneath - it could not put the hint above
   the session rail in a shared second column, which is what this needs at
   the middle widths where there is room for two columns but not three.

   Everything the child works with is on the left; everything ABOUT the work
   - the hint, then how the session is going - is on the right. */
.practice-stage {
  display: grid;
  align-items: start;
  justify-content: center;
  gap: 22px;
  max-width: min(94vw, 1300px);
  margin: 0 auto;
  grid-template-columns: minmax(0, 700px) 280px 260px;
  grid-template-areas: 'card guide rail';
}

/* Two columns, hint over rail. 700 + 22 + 280 = 1002. */
@media (max-width: 1399px) {
  .practice-stage {
    max-width: min(94vw, 1002px);
    grid-template-columns: minmax(0, 700px) 280px;
    grid-template-areas:
      'card guide'
      'card rail';
    /* The card spans both rows, so it must not stretch to their combined
       height - it keeps its own. */
    align-items: start;
  }
}

/* One column. The hint goes under the question, the rail under that. */
@media (max-width: 900px) {
  .practice-stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      'card'
      'guide'
      'rail';
  }
}

.practice-stage > .exam-card { grid-area: card; max-width: 700px; }
.practice-stage > .guide-card { grid-area: guide; }
.practice-stage > .stage-rail { grid-area: rail; }

/* ---- The session rail (2026-08-25) ------------------------------------
   The progress strip used to hang off the bottom of the question card,
   buried under the Submit button. It is a card on the right now, below the
   hint: the question is the thing being done, and both of these are
   commentary on it, so they belong on the same side. */
.stage-rail {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 20px 18px;
  text-align: left;
}
.stage-rail-t {
  margin: 0 0 12px;
  font-family: 'Poppins', sans-serif;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-dark);
}
/* Short answers pair up (2026-08-25). At 620px the question card gave each
   option a 546px bar to hold the word "24" - four of them, stacked, with
   a long run of white underneath. Only when EVERY option is short: one
   long option in a two-column grid wraps to four lines and the cure is
   worse than the complaint, so the module adds the class after checking
   (see renderItem). auto-fit collapses back to one column when the card
   is too narrow to hold two. */
.practice-stage .exam-options-compact {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
}
.practice-stage .exam-options-compact .exam-option-btn { justify-content: center; text-align: center; }

/* What the freed room goes to. Real session numbers, not decoration -
   the alternative to white space is not more white space with a border
   around it. */
.stage-foot-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--bg-light);
  border: 1px solid var(--border);
  overflow: hidden;
}
.stage-foot-bar i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--teal), var(--amber));
}
.stage-foot-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
  margin-top: 14px;
}
/* Stacked in the rail - two tiles side by side in 260px would break
   "Right on your own" across three lines. */
@media (min-width: 1400px) {
  .stage-rail .stage-foot-tiles { flex-direction: column; gap: 16px; }
}
.sf-n {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.sf-n small { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.sf-l { display: block; margin-top: 2px; font-size: 11.5px; color: var(--text-muted); }
.stage-foot-note { margin: 12px 0 0; font-size: 12px; color: var(--text-muted); }
.stage-foot-note strong { color: var(--navy); }

/* ---- Accent picker (2026-08-25) ---------------------------------------
   Small and quiet. It sits between the Play button and the input on a page
   whose whole job is one word at a time, so it has to be reachable without
   competing with either. */
.accent-picker { margin: 14px auto 4px; max-width: 340px; }
.accent-picker-t {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.accent-opts {
  display: flex;
  gap: 6px;
  justify-content: center;
}
.acc-opt {
  flex: 1 1 0;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-card);
  color: var(--text-muted);
  font-family: 'Poppins', sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.acc-opt:hover { border-color: var(--teal); color: var(--navy); }
.acc-opt.is-on {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}
/* Still selectable when the device has no voice for it - picking it and
   reading the note is how a parent finds out what is missing, and the
   note says what will play instead. Dimmed, not disabled. */
.acc-opt.is-missing { opacity: 0.55; }
.acc-opt.is-missing.is-on { opacity: 1; background: var(--text-muted); border-color: var(--text-muted); }
.acc-dot { color: var(--amber-dark); font-weight: 700; }
.acc-opt.is-on .acc-dot { color: #FFD9B8; }
.accent-note {
  margin: 7px 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-muted);
  text-align: center;
}

.guide-card {
  /* Width comes from the grid column now. align-self: start keeps it
     hugging its content - stretched to the question card it carried 257px
     of white under the rule box, sizing itself to a neighbour rather than
     to what it had to say. */
  align-self: start;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 20px 18px;
  text-align: left;
}
/* Hidden the moment help is shown - the hint takes this slot. */
.guide-intro[hidden] { display: none; }

.guide-how-t {
  margin: 0 0 6px;
  font-family: 'Poppins', sans-serif;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-dark);
}
.guide-how {
  margin: 0 0 14px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-muted);
  text-align: left;
}
.guide-how strong { color: var(--teal); }
.guide-btn { font-size: 13px; width: 100%; }

/* Narrower panel before narrower question: the prompt and the options are
   what the child is working with, so they give up width last. */
@media (max-width: 800px) {
  .practice-stage { gap: 16px; }
  .guide-how { font-size: 12px; }
}
/* Underneath the question on a phone - and the standing advice goes with
   it, so the encouragement is never a desktop-only nicety. The grid does
   the moving (see .practice-stage); this only loosens the padding once it
   has the full width to spend. */
@media (max-width: 900px) {
  .guide-card, .stage-rail { padding: 18px 16px; }
}

.guide-box {
  margin: 12px 0 0;
  text-align: left;
  padding: 13px 16px;
  border-radius: 12px;
  border-left: 3px solid var(--teal);
  background: rgba(15, 123, 138, 0.07);
}
/* The worked explanation is a different KIND of help from a nudge, and
   looks it - a child should be able to tell which one they asked for. */
.guide-box-full { border-left-color: var(--amber-dark); background: rgba(243, 113, 48, 0.08); }
/* Absent help has to look different from help, and it has to sit AFTER
   .guide-box to do it - same specificity, so source order decides which
   background lands. Dashed and muted: "there is nothing here", not "a
   hint that came out blank". */
.guide-box-none {
  border-left: 3px dashed var(--border);
  background: var(--bg-light);
  color: var(--text-muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.guide-box-none .guide-text { color: var(--text-muted); }
.guide-label {
  margin: 0 0 4px;
  font-family: 'Poppins', sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
}
.guide-box-full .guide-label { color: var(--amber-dark); }
.guide-text { margin: 0; font-size: 14px; line-height: 1.6; color: var(--text-dark); }

/* ---- A held practice session (2026-08-25) -----------------------------
   On My tasks it is the first thing, above assigned work: a session
   already begun is the most outstanding thing on the list. Amber rather
   than teal - this is unfinished business, not an achievement. */
.held-block { border-left: 4px solid var(--amber-dark); }
.held-row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-light);
}
.held-row-body { flex: 1 1 220px; min-width: 0; }
.held-row-title {
  margin: 0 0 8px;
  font-family: 'Poppins', sans-serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--navy-dark);
}
.held-bar { height: 8px; border-radius: 999px; background: var(--border); overflow: hidden; }
.held-bar span { display: block; height: 100%; border-radius: 999px; background: var(--amber-dark); }

/* The same offer on the practice page itself, where a child who goes
   straight there still meets it before the picker. */
.held-session {
  margin: 0 0 18px;
  padding: 16px 18px;
  border-radius: 14px;
  border-left: 4px solid var(--amber-dark);
  background: rgba(243, 113, 48, 0.07);
  text-align: left;
}
.held-eyebrow {
  margin: 0 0 5px;
  font-family: 'Poppins', sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--amber-dark);
}
.held-text { margin: 0 0 12px; font-size: 13.5px; line-height: 1.6; color: var(--text-dark); }

/* ---- The last five practice sessions (2026-08-25) ---------------------
   A list of moments, each opening onto the results screen the child saw.
   The newest is open on arrival: nine times in ten that is the one being
   looked for, and a list of closed rows makes you work to find out that
   the top one is the answer. */
.rp-intro { margin: 0 0 12px; font-size: 13px; line-height: 1.55; color: var(--text-muted); }
.rp-row { border: 1px solid var(--border); border-radius: 12px; margin-bottom: 8px; background: var(--bg-card); }
.rp-row[open] { border-color: rgba(15, 123, 138, 0.35); }
.rp-row > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
}
.rp-row > summary::-webkit-details-marker { display: none; }
.rp-when {
  font-family: 'Poppins', sans-serif;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--navy-dark);
  display: flex;
  align-items: center;
  gap: 8px;
}
.rp-latest {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(15, 123, 138, 0.12);
  color: var(--teal);
}
.rp-stat { flex: 1 1 auto; font-size: 12.5px; color: var(--text-muted); }
.rp-chev { color: var(--text-muted); transition: transform 0.15s ease; }
.rp-row[open] .rp-chev { transform: rotate(180deg); }
.rp-body { padding: 0 14px 14px; }
@media (prefers-reduced-motion: reduce) { .rp-chev { transition: none; } }
@media (max-width: 560px) {
  .rp-row > summary { flex-wrap: wrap; }
  .rp-stat { flex: 1 1 100%; }
}

/* ---- The practice results screen (2026-08-25) -------------------------
   Shown in two places from one builder: at the end of a session, and in
   the portal's "Last five practice sessions". These rules were in exam.css,
   which only the practice page loads, so in the portal the whole thing
   rendered as unstyled text - "38%Accuracy" run together, and the area
   bars as bare percentages.

   Rebuilt rather than merely moved. Even styled it was a flat column: a
   number over a label three times, then a list where the bar and the
   figure did not line up, so nothing could be compared down the column. */
.practice-results-section {
  margin-top: 18px;
  padding: 20px 22px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--bg-card);
  text-align: left;
}
.practice-results-title {
  margin: 0 0 14px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Three tiles, sized to the numbers rather than stretched: at flex:1 each
   the "4.2s" tile was as wide as a sentence. */
.practice-results-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.practice-results-stat {
  background: var(--bg-light);
  border-radius: 12px;
  padding: 13px 14px;
  text-align: center;
}
.practice-results-stat .num {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 21px;
  line-height: 1.1;
  color: var(--navy-dark);
  font-variant-numeric: tabular-nums;
}
.practice-results-stat .label {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-muted);
}
.practice-results-compare { margin: 0 0 16px; font-size: 13px; color: var(--text-muted); }

/* THE AREA LIST IS A GRID, not a flex row per line. Three columns means
   every label starts at the same x, every bar spans the same track and
   every figure ends on the same right edge - so the column can be read
   down as a shape, which is the only reason to list them together. */
.practice-results-categories {
  display: grid;
  grid-template-columns: minmax(0, 148px) 1fr 44px;
  gap: 9px 12px;
  align-items: center;
  margin-bottom: 16px;
}
.practice-results-cat-row { display: contents; }
.practice-results-cat-label {
  font-size: 13px;
  line-height: 1.3;
  color: var(--navy-dark);
}
.practice-results-cat-pct {
  text-align: right;
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--navy-dark);
}
/* The bar carries the reading, so it needs the three bands the rest of the
   product uses rather than one flat colour. */
.practice-results-categories .score-bar-track {
  height: 9px;
  border-radius: 999px;
  background: var(--bg-light);
  overflow: hidden;
}
.practice-results-categories .score-bar-fill { height: 100%; border-radius: 999px; }
.practice-results-categories .score-bar-strength { background: var(--teal); }
.practice-results-categories .score-bar-typical { background: #6FDCE8; }
.practice-results-categories .score-bar-growth { background: var(--amber-dark); }

.practice-results-quota {
  margin: 0;
  padding-top: 13px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Inside the portal list the card already sits in a bordered row, so it
   drops its own frame rather than boxing a box. */
.rp-body .practice-results-section {
  margin-top: 0;
  border: 0;
  padding: 0;
  background: none;
}

@media (max-width: 560px) {
  .practice-results-stats { grid-template-columns: 1fr; }
  .practice-results-categories { grid-template-columns: minmax(0, 104px) 1fr 40px; }
  .practice-results-cat-label { font-size: 12px; }
}

/* The structural clue in a spelling explanation - letters, beats, whether
   something doubles. Set apart from the rule because it is about THIS word
   rather than the skill, and it is the part a child scans back to. */
/* The word's shape - masked at stage one, in parts at stage two. Monospaced
   and spaced out, because "k _ _ _ e" only reads as a pattern to fill in if
   the gaps are even. */
.guide-shape {
  display: block;
  margin-top: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.75);
  font-family: 'Poppins', sans-serif;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--navy-dark);
  text-align: center;
}
/* Stage two: the whole word, in parts. Bigger and warmer - it is the answer
   in all but name, and hiding that would be coy rather than kind. */
.guide-shape-full {
  font-size: 17px;
  letter-spacing: 0.04em;
  background: rgba(243, 113, 48, 0.14);
  color: var(--amber-dark);
}
.guide-sub {
  display: block;
  margin-top: 7px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* ---- The practice banner (2026-08-25, rebuilt on the portal hero) -----
   It is the portal's child header, reused rather than reinvented: same
   .child-dashboard-hero card, same rainbow top edge, same watermark, same
   three zones. A child arriving from the portal meets the header they just
   left, and there is one card design to maintain instead of two that drift.

   What changes is the MIDDLE. The portal fills that column with the brand
   promise because the portal is where a parent lands and reads. Here a
   child is two seconds from a question, and the only thing worth that
   space is how far through today they are. */
.practice-hero-band {
  /* Matches the guided column exactly, so banner and content share an
     edge in guided mode and the banner never changes width between the
     two modes. 92vw keeps it off the viewport edge on a tablet, the
     same guard .profile-inner uses on the portal. */
  max-width: min(92vw, 1000px);
  margin: 0 auto 22px;
}
/* On a guided page the stage below runs to 1300, so a 1000px banner would
   sit visibly narrower than the thing it heads. The module sets this class
   on <body> when it widens the column. */
.practice-guided .practice-hero-band { max-width: min(94vw, 1300px); }
/* Below the three-column breakpoint the stage falls back to 1002, and the
   banner has to fall with it. This has to sit AFTER the rule above rather
   than beside the stage rules it belongs with - same specificity, and the
   stage block is 400 lines further up the file. */
@media (max-width: 1399px) {
  .practice-guided .practice-hero-band { max-width: min(94vw, 1002px); }
}

/* The portal hides its middle column below 950px because a strapline is
   decorative and the space is better spent. This one is not decorative -
   it is the minutes and the questions left - so it stays, and stacks into
   its own full-width row instead of vanishing on the phone a child is most
   likely holding. */
@media (max-width: 950px) {
  .practice-hero-band .practice-band-mid {
    display: flex;
    flex: 1 1 100%;
    border-left: 0;
    border-right: 0;
    border-top: 1px solid var(--border);
    padding: 14px 0 0;
    margin-top: 4px;
  }
}

.pb-mid-eyebrow {
  margin: 0 0 6px;
  font-family: 'Poppins', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--teal);
}
.pb-mid-main {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.05;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.pb-mid-main span {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}
.pb-mid-bar {
  width: 100%;
  max-width: 200px;
  height: 6px;
  margin: 10px 0 0;
  border-radius: 999px;
  background: var(--bg-light);
  border: 1px solid var(--border);
  overflow: hidden;
}
.pb-mid-bar i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--teal), var(--amber));
}
.pb-mid-sub {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-muted);
}

/* Handed over, the card says so in a strip that cannot collapse away.
   This lived in the brand column for one draft - which is hidden below
   700px, taking the one line that says whose work this is with it. */
.pb-switch-note {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}
.pb-switch-note strong { color: var(--navy); }
.practice-hero-band.is-switched::after {
  background: linear-gradient(90deg, var(--teal) 0%, var(--amber) 100%);
}

@media print { .practice-hero-band { display: none !important; } }

/* ---- What this is / is not, and the first-read gate (2026-08-25) ------
   The same two cards in both places, so passing the gate and reading the
   section look like the same thing rather than two similar things. */
.report-section.report-purpose {
  border-left: 4px solid var(--amber-dark);
  background: linear-gradient(180deg, rgba(243, 113, 48, 0.06), transparent 120px), var(--bg-card);
}
.report-purpose .eyebrow { color: var(--amber-dark); }
/* Compound, to outrank .report-section.report-purpose above - which is two
   classes to this one, so the calm variant lost its own colour and came out
   amber, the alarm colour, on the section whose whole job is to say there
   is no alarm. */
.report-section.report-purpose-calm {
  border-left-color: var(--teal);
  background: linear-gradient(180deg, rgba(15, 123, 138, 0.06), transparent 120px), var(--bg-card);
}
.pp-answer {
  margin: 6px 0 14px;
  font-family: 'Poppins', sans-serif;
  font-size: 20px;
  font-weight: 700;
  color: var(--teal);
}
.pp-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin: 18px 0 4px;
}
@media (max-width: 620px) { .pp-grid { grid-template-columns: minmax(0, 1fr); } }
.pp-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 15px 17px;
  background: var(--bg-card);
}
.pp-card-t {
  margin: 0 0 10px;
  font-family: 'Poppins', sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.pp-is .pp-card-t { color: var(--teal); }
.pp-isnot .pp-card-t { color: var(--amber-dark); }
.pp-card ul { margin: 0; padding: 0; list-style: none; }
.pp-card li {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dark);
}
.pp-card li:last-child { margin-bottom: 0; }
.pp-mark {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.pp-mark.is-yes { background: rgba(15, 123, 138, 0.14); color: var(--teal); }
.pp-mark.is-no { background: rgba(243, 113, 48, 0.16); color: var(--amber-dark); }
.pp-lines { margin-top: 16px; }
.pp-lines p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.6; color: var(--text-dark); }
.pp-lines p:last-child { margin-bottom: 0; }
.pp-lines strong { color: var(--navy-dark); }

/* The gate. Covers the report until it is acknowledged - so it is fixed, it
   scrolls internally on a short screen, and the page behind it does not
   scroll with it. */
.rgate {
  position: fixed;
  inset: 0;
  z-index: 4000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 18px;
  overflow-y: auto;
  background: rgba(27, 28, 80, 0.62);
  backdrop-filter: blur(3px);
}
body.rgate-open { overflow: hidden; }
.rgate-card {
  width: 100%;
  max-width: 620px;
  margin: auto;
  background: var(--bg-card);
  border-radius: 18px;
  padding: 26px 26px 24px;
  box-shadow: 0 24px 60px rgba(27, 28, 80, 0.34);
}
.rgate-card .eyebrow { color: var(--amber-dark); }
.rgate-card h2 { margin: 6px 0 8px; }
.rgate-sub { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--text-muted); }
.rgate-note {
  margin: 16px 0 0;
  padding: 12px 14px;
  border-left: 3px solid var(--teal);
  border-radius: 0 10px 10px 0;
  background: var(--bg-light);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-dark);
}
.rgate-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 18px 0 16px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dark);
  cursor: pointer;
}
.rgate-check input { margin-top: 3px; width: 17px; height: 17px; flex: none; cursor: pointer; }
#rgate-go { width: 100%; }
#rgate-go:disabled { opacity: 0.45; cursor: not-allowed; }
@media (max-width: 560px) { .rgate-card { padding: 20px 18px 18px; } }
/* Never in a PDF: mountReportGate skips the download route, and this covers
   a parent printing the page by hand. */
@media print { .rgate { display: none !important; } }

/* ---- Reading measure (2026-08-25) -------------------------------------
   The column went from 976px to 1428px, which is what the tables and tile
   rows needed - and it took the prose with it, to 159 characters a line
   against a comfortable 45-75. Past about 90 the eye starts losing its
   place on the return sweep, which is a real cost paid by the one part of
   the report a parent reads word by word.

   So: prose caps, data does not. Tables, tile rows, charts, badge grids and
   the appendix keep the full width; paragraphs stop at 70ch.

   IN auth.css, NOT report.css. profile.html loads style.css and auth.css
   only, and the report is injected inline into that page - a rule in
   report.css would style the standalone report and quietly miss the embed,
   which is where most parents read it. Third time that has caught us.

   DIRECT CHILDREN ONLY. Text inside a card - .pp-card, .wf-hero,
   .stat-tile, .badge-tile - is already constrained by the card and must not
   be capped again, or it would sit in a narrow ribbon inside its own box. */
.report-section > p,
.report-section > .section-sub,
.report-section > .auth-sub,
.report-section > .report-intro-line,
.overview-block > p:not(.overview-disclaimer),
.pp-lines p,
.pg-takeaway p,
.pg-concern p,
.pg-notmean li,
/* Overview prose, found by measuring rather than by listing what I could
   think of: five blocks were still running 1300px because their parent is
   a card rather than a .report-section. */
.obs-note,
.overview-hero-impact,
.usage-hero-note,
.overview-badge-line,
.overview-narrative-card > p:not(.overview-disclaimer) { max-width: 70ch; }

/* Centred sections keep their prose centred: a capped block in a centred
   parent would otherwise hug the left edge while its text stayed centred. */
.report-at-a-glance > p,
.report-at-a-glance > .section-sub { margin-left: auto; margin-right: auto; }

/* The expired-sign-in banner. Amber rather than red: nothing is broken and
   nothing is lost, the device simply cannot reach the server as this parent
   any more. Pinned to the bottom so it cannot be scrolled past, and never
   in a print. */
.parent-reauth-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 20px;
  background: var(--amber-dark);
  color: #fff;
  font-size: 13.5px;
  line-height: 1.5;
  box-shadow: 0 -6px 20px rgba(27, 28, 80, 0.18);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.parent-reauth-banner .btn { flex: none; }
@media print { .parent-reauth-banner { display: none !important; } }

/* ---- Overview folds (2026-08-25) --------------------------------------
   The scores stay open at the top; everything below folds. The summary is
   the card - so a closed Overview reads as a short stack of headings a
   parent chooses from, not as content that has gone missing. */
/* ---- The Overview's section tabs (2026-08-27) --------------------------
   Activity / To-do list / Achievements are peers, viewed one at a time, with
   nothing below competing for the space - which is the shape a tab strip is
   for. They were tiles opening into a dialog, and before that an accordion.
   The dialog was the thing worth losing: it put the content behind an
   overlay, so closing was a dismissal rather than a move sideways.

   Deliberately lighter than .portal-tab above it. These are sections within
   a tab, and two identical strips stacked would leave a parent unsure which
   level they were moving around in. */
.ovtab-bar {
  display: flex; flex-wrap: wrap; gap: 2px;
  margin: 28px 0 0; border-bottom: 1px solid var(--border, #E4E4EC);
}
.ovtab {
  appearance: none; background: none; border: 0; margin-bottom: -1px;
  padding: 11px 16px; cursor: pointer;
  font-family: 'Poppins', sans-serif; font-size: 14px; font-weight: 600;
  color: var(--text-muted, #5F5F75);
  border-bottom: 2px solid transparent;
  transition: color .12s ease, border-color .12s ease;
}
.ovtab:hover { color: var(--navy, #1B1C50); }
.ovtab.is-active { color: var(--navy, #1B1C50); border-bottom-color: var(--teal, #0F7B8A); }
.ovtab:focus-visible { outline: 2px solid var(--teal, #0F7B8A); outline-offset: -3px; border-radius: 7px 7px 0 0; }
.ovtab-panels { margin-top: 20px; }
/* VISIBILITY IS A CLASS, NOT THE hidden ATTRIBUTE - see the note in
   syncOverviewFolds. A rule that gives an element a display outranks the
   user agent's [hidden] { display: none }, which is how a chip row on the
   subject cards once shipped permanently open. */
.ovtab-panel { display: none; }
.ovtab-panel.is-active { display: block; }
/* Paper has no tabs. Everything prints, each section under its own heading -
   which means putting back the heading the tab took over. */
@media print {
  .ovtab-bar { display: none; }
  .ovtab-panel { display: block !important; }
  .ovtab-panel .ov-fold-heading { display: block !important; }
}
@media (max-width: 520px) { .ovtab { padding: 10px 12px; font-size: 13px; } }

.ov-fold-heading { display: none; }

/* ---- The subject cards on a narrow screen (2026-08-25) ----------------
   Stacked vertically at 207x277 each, the four subjects came to 1,235px -
   the whole phone screen, four times, before a parent reached anything
   else. And these are the one block that never folds, so that height is
   paid on every visit.

   Turned on their side they are about 90px each: the ring on the left, the
   words beside it, reading as a row per subject rather than a card per
   subject. Same information, a third of the height.

   The sparkline goes. At 207px wide it was a shape rather than a trend, and
   the delta chip beside it already says which way the last attempt moved. */
/* The ring layout this replaces was turned on its side on a phone, because
   four stacked cards otherwise ate the whole screen before a parent reached
   anything else - and these never fold away, so that height is paid on every
   visit. A ladder cannot be turned on its side; it is a horizontal thing.
   It does not need to be: the card is already two paired lines and a ladder,
   so it only has to get tighter. */
@media (max-width: 620px) {
  .overview-level-card { padding: 12px 14px; }
  .overview-level-module { font-size: 11px; }
  .overview-ladder { margin: 10px 0 8px; }
  .ov-step { height: 26px; }
  .ov-lvl-bar { margin-top: 8px; }
}


/* ---- Sections as tiles that open into a dialog (2026-08-26) -------------
   Shared by the parent Overview and by every step of the report; see the note
   at the top of js/section-tiles.js for why these stopped being accordions.

   Built from the tile conventions already on this site rather than as a new
   look: the 14px radius and hairline border of .stat-tile, the 4px coloured
   left edge and Poppins tabular number of .asg-sum-tile. */
.stile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  gap: 14px;
}
.stile-store[hidden] { display: none; }

.stile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  min-height: 118px;
  text-align: left;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 4px solid var(--teal);
  border-radius: 14px;
  padding: 16px 18px;
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.3, 1), box-shadow 0.18s ease, border-color 0.18s ease;
}
/* A wash that only appears on hover, so the tile lifts rather than just
   sliding. On the ::after so it cannot tint the text. */
.stile::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, rgba(15, 123, 138, 0.06), rgba(40, 41, 114, 0.035));
  opacity: 0;
  transition: opacity 0.18s ease;
  pointer-events: none;
}
.stile:hover { transform: translateY(-3px); box-shadow: 0 12px 26px rgba(27, 28, 80, 0.13); }
.stile:hover::after { opacity: 1; }
.stile:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }
/* Invisible, not removed: the closing animation flies the panel back to where
   this tile is, so it has to keep its place in the grid the whole time. */
.stile.is-source { opacity: 0; }

.stile-top { display: flex; align-items: baseline; gap: 12px; width: 100%; }
.stile-title {
  flex: 1;
  min-width: 0;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 15px;
  line-height: 1.35;
  color: var(--navy-dark);
}
.stile-count {
  flex: 0 0 auto;
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 22px;
  line-height: 1;
  color: var(--teal);
  font-variant-numeric: tabular-nums;
}
.stile-meta { font-size: 12.5px; line-height: 1.5; color: var(--text-muted); }
.stile-open {
  margin-top: auto;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--teal);
}
.stile-open::after { content: ' \2192'; }

/* ---- the panel ---- */
dialog.stdlg {
  padding: 0;
  border: 0;
  background: transparent;
  width: 100%;
  max-width: min(1000px, calc(100vw - 32px));
  max-height: min(86vh, 860px);
  overflow: visible;
}
dialog.stdlg::backdrop { background: rgba(15, 16, 50, 0.55); backdrop-filter: blur(3px); opacity: 0; }
dialog.stdlg[open]::backdrop { animation: stdlgBackdrop 0.22s ease forwards; }
@keyframes stdlgBackdrop { to { opacity: 1; } }

.stdlg-card {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: min(86vh, 860px);
  background: var(--bg-card);
  border-radius: 18px;
  box-shadow: 0 30px 70px rgba(15, 16, 50, 0.34);
  overflow: hidden;
  transform-origin: top left;
}
.stdlg-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 17px 20px;
  border-bottom: 1px solid var(--border);
}
.stdlg-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--navy-dark);
}
.stdlg-x {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.stdlg-x:hover { border-color: var(--navy); color: var(--navy); background: var(--bg-light); }
.stdlg-x:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* THE SCROLLER IS THE FLEX CHILD ITSELF. Wrapped in a block with the body at
   height:100% inside it, the percentage resolved against the wrong box and the
   body came out taller than the frame holding it - so it never scrolled. */
.stdlg-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 20px;
  scrollbar-width: thin;
  scrollbar-color: #C6C6D4 transparent;
}
.stdlg-body::-webkit-scrollbar { width: 10px; }
.stdlg-body::-webkit-scrollbar-thumb { background: #C6C6D4; border-radius: 999px; border: 3px solid var(--bg-card); }
.stdlg-body::-webkit-scrollbar-thumb:hover { background: #A9A9BC; }
/* The block inside already carried a card of its own on the page it came
   from; the panel is that card now, so it drops its second frame. */
.stdlg-body > .stile-shown {
  background: none;
  border: 0;
  border-radius: 0;
  margin: 0;
  padding: 0;
  box-shadow: none;
}
/* Its heading is the panel title above it. */
.stdlg-body .ov-fold-heading { display: none; }

.stdlg-fade {
  position: absolute;
  left: 0;
  right: 10px;
  bottom: 58px;
  height: 46px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--bg-card));
}
.stdlg-card.has-more .stdlg-fade { opacity: 1; }
/* Offered only while there is more below it, and it retracts at the end
   rather than sitting there insisting there is further to go. */
.stdlg-more {
  position: absolute;
  left: 50%;
  bottom: 68px;
  z-index: 2;
  transform: translateX(-50%);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--navy-dark);
  border-radius: 999px;
  padding: 6px 14px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(27, 28, 80, 0.12);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.stdlg-card.has-more .stdlg-more { opacity: 1; pointer-events: auto; }
.stdlg-more:hover { border-color: var(--teal); color: var(--teal); }
.stdlg-more:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.stdlg-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  border-top: 1px solid var(--border);
}
.stdlg-foot[hidden] { display: none; }
.stdlg-pos { flex: 1; text-align: center; font-size: 12.5px; color: var(--text-muted); }
.stdlg-nav {
  border: 1px solid var(--border);
  background: transparent;
  border-radius: 999px;
  padding: 7px 14px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--navy-dark);
  cursor: pointer;
}
.stdlg-nav:hover:not(:disabled) { border-color: var(--navy); background: var(--bg-light); }
.stdlg-nav:disabled { color: #B9B9C8; cursor: default; }
.stdlg-nav:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

@media (max-width: 560px) {
  .stdlg-body { padding: 16px; }
  .stdlg-head { padding: 14px 16px; }
  .stdlg-title { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .stile, .stile::after, .stdlg-fade, .stdlg-more { transition: none; }
}

/* Stamped by refitProse in js/section-tiles.js once the panel width is known
   - see the note there for why the length of the text has to decide it.
   !important because the caps these override are a grouped selector list with
   higher specificity than a single class, and matching it here would mean a
   second copy of that list to keep in step. */
.stdlg-body .stdlg-prose-wide { max-width: none !important; }
.stdlg-body .stdlg-prose-cols {
  max-width: none !important;
  column-width: 40ch;
  column-gap: 30px;
}

/* ---- The same nudge on the stepper's numbers (2026-08-27) --------------
   A row of numbered circles reads as an indicator, not a row of controls -
   which is the reason the hint line beneath it exists at all. The hint says
   they are pressable; this shows it.

   NOT THE ACTIVE ONE, and not the ones already read. The current step draws
   its own box-shadow ring to say "you are here" and a second ring over the
   top would fight it, and a step behind you needs no inviting. So it is the
   numbers still ahead that pulse, and the row quiets down by itself as a
   parent works through the report.

   The delay is set per dot in js/report-tabs.js so the ring travels along the
   row rather than the whole line flashing at once, which looks like a fault. */
.report-stepper .report-stepper-dot:not(.active):not(.done) .rsd-num {
  animation: nudgeRing 3.6s ease-in-out infinite;
  animation-delay: var(--nudge-delay, 0s);
}

/* Everything on paper, nothing behind a tile - see the beforeprint handler in
   js/section-tiles.js, which also shuts the panel first. */
@media print {
  .stile-grid { display: none !important; }
  .stile-store, .stile-store[hidden] { display: block !important; }
  dialog.stdlg { display: none !important; }
}


/* ---- The terms a parent accepts, on step one (2026-08-27) --------------
   Beneath the tiles rather than beside them: this is the one block on the
   page that has to be read properly, and the spare grid cell is 260px wide.
   The text is in js/parent-report.js - and is NOT lawyer-reviewed. */
/* Inside the panel a tile opens, so it draws no card of its own - the panel is
   the card. The coloured edge stays, because it is the one thing here that
   says at a glance whether there is still something to do. */
.rconsent { border-left: 4px solid var(--amber); padding-left: 16px; }
/* Accepted is a record, not a demand: the amber edge that says "do something"
   goes teal, and the block stops asking. */
.rconsent.is-accepted { border-left-color: var(--teal); }
.rconsent-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.rconsent-title { margin: 2px 0 0; font-family: 'Poppins', sans-serif; font-size: 18px; font-weight: 700; color: var(--navy-dark); }
.rconsent-badge {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 700;
  /* A deeper teal than --teal: the brand tone measures 4.19:1 on its own
     10% wash over this panel, which is under the 4.5 small text needs. */
  color: #0A5C68;
  background: rgba(15, 123, 138, 0.09);
  border-radius: 999px;
  padding: 5px 12px;
  white-space: nowrap;
}
.rconsent-list { margin: 14px 0 0; padding: 0; list-style: none; }
.rconsent-list li {
  position: relative;
  margin: 0 0 10px;
  padding-left: 18px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 78ch;
}
.rconsent-list li::before { content: '•'; position: absolute; left: 4px; color: var(--teal); font-weight: 700; }
.rconsent-list li:last-child { margin-bottom: 0; }
.rconsent-list strong { color: var(--navy-dark); font-weight: 600; }
.rconsent-check { display: flex; align-items: flex-start; gap: 10px; margin: 16px 0 14px; font-size: 13.5px; line-height: 1.55; color: var(--text-dark); max-width: 78ch; cursor: pointer; }
.rconsent-check input { margin-top: 3px; width: 17px; height: 17px; flex: none; cursor: pointer; }
#rconsent-go:disabled { opacity: 0.45; cursor: not-allowed; }
.rconsent-done { margin: 14px 0 0; font-size: 13px; line-height: 1.55; color: var(--text-muted); max-width: 78ch; }

/* On paper it is the terms that matter, not the control for agreeing to them. */
@media print {
  .rconsent-check, #rconsent-go { display: none !important; }
  .rconsent { border-left-color: var(--border); }
}


/* ---- Plan and credits (2026-08-27) ------------------------------------
   Three questions in order - what did I buy, what is left, how do I spend it -
   so the headings are part of the answer rather than decoration. */
.asg-credits-head {
  margin: 22px 0 10px;
  font-family: 'Poppins', sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.asg-credits-head:first-of-type { margin-top: 18px; }
.asg-plan-list { display: grid; gap: 8px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.asg-plan {
  border: 1px solid var(--border);
  border-left: 4px solid var(--teal);
  border-radius: 12px;
  padding: 11px 14px;
  background: var(--bg-card);
}
.asg-plan-name { margin: 0; font-family: 'Poppins', sans-serif; font-size: 14px; font-weight: 600; color: var(--navy-dark); }
.asg-plan-meta { margin: 2px 0 0; font-size: 12.5px; color: var(--text-muted); }
/* What the plan actually grants, which is the line a parent is looking for
   and the one that was missing. Two rows rather than one run-on sentence,
   because tests and practice run on different clocks and saying so in a list
   is what makes that legible. */
.asg-plan-gives { margin: 8px 0 0; padding: 0; list-style: none; }
.asg-plan-gives li {
  position: relative;
  margin: 0 0 5px;
  padding-left: 16px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
}
.asg-plan-gives li:last-child { margin-bottom: 0; }
.asg-plan-gives li::before { content: '•'; position: absolute; left: 3px; color: var(--teal); font-weight: 700; }
.asg-plan-gives strong { color: var(--navy-dark); font-weight: 600; }

/* Said in words rather than as a number over a caption: "0" above "of 1" was
   read as "you own nothing" when it meant "yours, already on the list". */
.asg-left-line { margin: 6px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--text-muted); }
.asg-left-num { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 15px; color: var(--navy-dark); }
.asg-left-none { font-weight: 700; color: var(--text-muted); }
.asg-left-note { font-size: 12px; }
.asg-credits-note { margin: 12px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--text-muted); }
.asg-credits-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* The all-clear state of the paused tile. Teal edge rather than the amber
   this block wears when something is standing open: nothing to do is good
   news, and it should not wear the colour that means "look at me". */
.held-clear { border-left-color: var(--teal) !important; }

/* ---- Subscriptions as tiles (2026-08-27) ------------------------------ */
.subs-panel { font-size: 14px; line-height: 1.6; color: var(--text-dark); }
.subs-panel-lead { margin: 0 0 10px; color: var(--text-muted); }
.subs-panel-price {
  margin: 0 0 12px;
  font-family: 'Poppins', sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: var(--navy-dark);
}
.subs-panel-price span { font-size: 13px; font-weight: 600; color: var(--text-muted); }
/* Says plainly that nothing is charged, right beside the button that would
   charge it - not in a footnote a parent finds afterwards. */
.subs-panel-note {
  margin: 14px 0 16px;
  padding: 10px 13px;
  border-radius: 10px;
  background: rgba(243, 113, 48, 0.07);
  border-left: 3px solid var(--amber);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
}
.subs-panel .asg-plan-gives { margin-top: 0; }
#subs-active, #subs-available { margin-bottom: 26px; }

/* ---- A few attempts, drawn as what they are (2026-08-27) ----------------
   One bar per sitting, nothing joining them. A line needs four points before
   it is drawn - see PROGRESS_MIN_FOR_LINE - because two points always make a
   perfectly straight one and the old chart scaled to fit, so five points
   between 20% and 15% filled the card with a diagonal that read as collapse.

   THE TRACK IS THE SCALE. Every bar is measured against a fixed 0-100 track
   rather than against the other bars, so the height IS the score. Fitting the
   axis to the data is what produced the cliff, and it also made two children
   incomparable at a glance. The track being visible is the point: an empty
   80% above a bar says "there is room here" without a word. */
.atb {
  display: flex; align-items: flex-end; gap: 18px;
  padding: 6px 2px 0; margin-top: 6px;
}
.atb-col { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 0 1 76px; }
.atb-val {
  font-family: 'Poppins', sans-serif; font-size: 15px; font-weight: 700;
  color: var(--navy); line-height: 1;
}
.atb-track {
  position: relative; width: 100%; max-width: 54px; height: 132px;
  border-radius: 9px; background: var(--bg-light, #F4F5FA);
  border: 1px solid var(--border, #E4E4EC); overflow: hidden;
}
.atb-fill {
  position: absolute; left: 0; right: 0; bottom: 0;
  border-radius: 8px 8px 0 0; background: #9BD3DA;
}
/* The most recent sitting is the one being asked about, so it carries the
   full teal and the rest sit back. Not a judgement of better or worse -
   older bars are the same data, just not the question. */
.atb-col.is-latest .atb-fill { background: var(--teal, #0F7B8A); }
.atb-when { font-size: 11.5px; color: var(--text-muted, #5F5F75); white-space: nowrap; }
.atb-note {
  margin: 12px 0 0; font-size: 12.5px; line-height: 1.5;
  color: var(--text-muted, #5F5F75); max-width: 74ch;
}
@media (max-width: 460px) { .atb { gap: 12px; } .atb-track { height: 104px; } }

/* ==== THE FAMILY BOARD (2026-08-28) =====================================
   A row per child, a column per subject, above the per-child tabs. The
   grid IS the landing page: it answers "how are my children doing" before
   a parent clicks anything, which seven tabs and a child selector could
   not.

   Colour per subject follows the coding already used by .todo-gk,
   .asg-credit-gk and .sp-gk - but as ONE custom property per cell rather
   than the four copies of every rule each of those ended up with. Adding a
   subject here is one line, not one line per property.
   ==================================================================== */
.family-board { margin: 0 0 22px; display: flex; flex-direction: column; gap: 14px; }
.family-board[hidden] { display: none; }

.fb-lede .section-title { margin: 0 0 4px; }
.fb-lede .section-sub { margin: 0; max-width: 68ch; }
.fb-lede strong { color: var(--text-dark); font-weight: 600; }

.fb-board {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* The five columns. minmax(0,1fr) not 1fr: a long level name in one cell
   must not push the other three out of alignment across rows, and 1fr's
   min-content floor lets it. */
.fb-head,
.fb-row {
  display: grid;
  grid-template-columns: 190px repeat(4, minmax(0, 1fr));
  gap: 10px;
}

.fb-head {
  padding: 10px 16px;
  background: var(--bg-light);
  border-bottom: 1px solid var(--border);
  gap: 0 10px;
}
/* COLOURED PER COLUMN, NOT AS ONE BAR (2026-08-28). The obvious move was the
   navy-and-teal brand gradient this product uses for every "top of the page"
   panel - but Cognitive's own accent IS navy, so its dot would have
   disappeared into that ground, and the four accents are the whole way a
   parent tells these columns apart on a grid with no row labels. Tinting
   each header in its own subject colour adds the colour AND states the
   column-to-subject mapping the dots were carrying alone.

   `flex`, not `inline-flex`: the block has to fill its grid cell or the
   tints come out ragged, sized by how long each subject's name happens to
   be. The 10px gap between cells is the board's own ground showing through,
   which is what separates them.

   Label text is each subject's own colour on a 10% wash of itself over
   --bg-light, MEASURED against that composite rather than eyeballed: at full
   strength Spelling came out 4.09:1 and Maths 4.20:1, both under the 4.5 a
   bold 10.5px label needs. Those two are darkened to 87% and 89% of their
   token - the smallest step that clears 5:1, so they still read as the same
   teal and orange the dot beside them uses. Navy (9.93) and violet (5.17)
   pass at full strength and are left alone. */
.fb-col {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--fb-head-bg, transparent);
}
/* FILLED, NOT WASHED (2026-08-28). The 10% tints were colour in principle -
   at that strength they read as four shades of pale on a white card, and the
   subject's own colour had to be carried by a 7px dot beside the label. The
   headings are the full colour now, and the dot goes with them: a block that
   IS the subject colour does not need a swatch explaining which colour it is.

   THE TEXT COLOUR IS NOT UNIFORM, AND CANNOT BE. White carries three of the
   four comfortably, but amber is a genuinely light colour and white on it is
   2.91:1 - nowhere near the 4.5 a bold 10.5px uppercase label needs. The
   choice there was to darken the fill until white sticks (#B84C18, 5.14) or
   keep the bright amber and put the dark text on it (5.43). The brief was
   brighter, so: bright amber, navy-dark label. It is the only one of the
   four that is genuinely light, so it is the only one that flips.

   Measured, every combination, before choosing:
     navy   #282972  white 12.72 / navy-dark 1.24
     teal   #0F7B8A  white  4.98 / navy-dark 3.18
     amber  #F37130  white  2.91 / navy-dark 5.43   <- flipped
     violet #9C3587  white  6.42 / navy-dark 2.46

   A mid-tone amber was tried to keep the text white AND the fill bright:
   #D9541F measures 4.01 on white and 3.94 on navy-dark. It fails BOTH,
   which is the trap with mid-luminance fills - there is no text colour that
   works, and the only way out is to commit to light or dark. */
.fb-col-cognitive { --fb-head-bg: var(--navy);   color: #fff; }
.fb-col-spelling  { --fb-head-bg: var(--teal);   color: #fff; }
.fb-col-maths     { --fb-head-bg: var(--amber);  color: var(--navy-dark); }
.fb-col-gk        { --fb-head-bg: var(--violet); color: #fff; }

/* The dot was doing the colour-coding when the ground was a 10% wash. The
   ground does it now, and a swatch of the same colour on top of it is just
   a hole in the fill. */
.fb-col-cognitive::before,
.fb-col-spelling::before,
.fb-col-maths::before,
.fb-col-gk::before { display: none; }
/* The Child column still names no subject, so it still takes no subject
   colour - a fifth hue here would imply a fifth thing being measured. But a
   4% wash beside four saturated fills left the header row looking half
   finished, so it is a solid NEUTRAL: same weight as its neighbours, no
   opinion about what it measures. */
.fb-col-who { --fb-head-bg: #E2E2EC; color: #4A4A60; }
.fb-col-cognitive::before,
.fb-col-spelling::before,
.fb-col-maths::before,
.fb-col-gk::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 2px;
  flex: 0 0 7px;
  background: var(--fb-c);
}

.fb-row { padding: 14px 16px; align-items: stretch; }
.fb-row + .fb-row { border-top: 1px solid var(--border); }

/* ---- who ---- */
.fb-who { display: flex; flex-direction: column; justify-content: center; gap: 2px; min-width: 0; }
.fb-who-top { display: flex; align-items: center; gap: 9px; }
.fb-avatar {
  width: 31px; height: 31px; flex: 0 0 31px;
  border-radius: 9px;
  display: grid; place-items: center;
  background: linear-gradient(140deg, var(--navy), var(--teal));
  color: #fff;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 13px;
}
/* A link, because it is the way into that child's own page - the landing
   board shows every child and the per-subject detail lives one click in. */
.fb-who-name {
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 15.5px;
  color: var(--text-dark);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
a.fb-who-name:hover { color: var(--teal); border-bottom-color: currentColor; }
.fb-who-meta,
.fb-who-line { font-size: 12.5px; color: var(--text-muted); }

/* ---- one subject cell ---- */
.fb-cell {
  --fb-c: var(--navy);
  --fb-wash: transparent;
  --fb-edge: var(--border);
  background: var(--fb-wash);
  border: 1px solid var(--fb-edge);
  border-radius: 10px;
  padding: 10px 11px 11px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
  transition: border-color .14s ease, background .14s ease, transform .14s ease;
}
/* The whole cell answers to the pointer, not just the link inside it - the
   cell IS the subject, and a row of cards that stay inert under the cursor
   reads as a printed table rather than something to open. */
.fb-cell:hover { border-color: var(--fb-c); transform: translateY(-1px); }
/* Three values per subject, not one. --fb-c was carrying the whole column
   on its own and could only ever be used at full strength, which is why the
   colour stopped at the heading and the eight cells below it were grey. The
   wash and the edge are the same hue at 4% and ~20%, so a column reads as
   one band from its heading to the last row. */
.fb-cell-cognitive { --fb-c: var(--navy);       --fb-wash: rgba(40, 41, 114, 0.04);  --fb-edge: rgba(40, 41, 114, 0.20); }
.fb-cell-spelling  { --fb-c: var(--teal);       --fb-wash: rgba(15, 123, 138, 0.04); --fb-edge: rgba(15, 123, 138, 0.22); }
.fb-cell-maths     { --fb-c: var(--amber-dark); --fb-wash: rgba(184, 76, 24, 0.04);  --fb-edge: rgba(184, 76, 24, 0.22); }
.fb-cell-gk        { --fb-c: var(--violet);     --fb-wash: rgba(156, 53, 135, 0.04); --fb-edge: rgba(156, 53, 135, 0.20); }
.fb-col-cognitive  { --fb-c: var(--navy); }
.fb-col-spelling   { --fb-c: var(--teal); }
.fb-col-maths      { --fb-c: var(--amber-dark); }
.fb-col-gk         { --fb-c: var(--violet); }

/* THE SUBJECT NAME WAS PRINTED TWICE IN EVERY COLUMN (2026-08-28). The
   heading says COGNITIVE and then every cell under it said COGNITIVE again -
   three times over with two children, six with five, and the same for all
   four subjects. The heading row exists precisely so the cells do not have
   to repeat it, and with it gone each cell leads with its number instead of
   with a word the reader already has.

   IT COMES BACK BELOW 960px, where the reflow block hides the heading row
   and this label becomes the only thing naming the subject. That block's own
   comment already said as much; this is the other half of it. */
.fb-cell-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fb-c);
  display: none;
}
.fb-cell-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.fb-cell-figures { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.fb-ring {
  width: 37px; height: 37px; flex: 0 0 37px;
  border-radius: 50%;
  background: conic-gradient(var(--fb-c) 0 var(--fb-pct, 0%), var(--border) var(--fb-pct, 0%) 100%);
  display: grid; place-items: center;
}
.fb-ring i {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--bg-card);
  display: grid; place-items: center;
  font-style: normal;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 13px;
  color: var(--text-dark);
  font-variant-numeric: tabular-nums;
}
/* Even with nothing recorded the ring belongs to its column, so the blank
   track is the subject's own edge tint rather than flat grey. Five of eight
   cells on a new family are empty; grey in all five is what made the board
   look switched off. */
.fb-ring-blank { background: var(--fb-edge); }
.fb-ring-blank i { color: var(--text-muted); }

.fb-num {
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 19px;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--text-dark);
  font-variant-numeric: tabular-nums;
}
.fb-num small { font-size: 12px; font-weight: 500; color: var(--text-muted); }
.fb-level,
.fb-need { font-size: 12px; color: var(--text-muted); line-height: 1.35; }
.fb-empty { font-size: 13px; font-weight: 600; color: var(--text-muted); }

.fb-cell-empty { border-style: dashed; }
.fb-paused { font-size: 11.5px; color: var(--amber-dark); }

/* The delta chip is the existing component - it only needs to sit left
   here rather than inherit the subject strip's right alignment. */
.fb-cell .overview-delta-chip { align-self: flex-start; }

.fb-cta {
  align-self: flex-start;
  margin-top: auto;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  /* Its column's colour, not teal for all four - this is the most repeated
     element on the board (one per empty cell) and four identical teal links
     under four differently-coloured headings was the colour scheme
     contradicting itself. All four clear 4.5:1 on the cell wash. */
  color: var(--fb-c);
  text-decoration: none;
  cursor: pointer;
  border-bottom: 1px solid transparent;
}
.fb-cta:hover { border-bottom-color: currentColor; }
.fb-cta-plan { color: var(--amber-dark); }

.fb-foot {
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  background: var(--bg-light);
  font-size: 12px;
  color: var(--text-muted);
  display: flex;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

/* ---- reflow -----------------------------------------------------------
   Below 960px the matrix becomes stacked child cards, two subjects across.
   Every cell carries its own subject label, so the column headers were
   only ever a convenience on wide screens and can go entirely. */
@media (max-width: 960px) {
  .fb-head { display: none; }
  .fb-cell-label { display: block; }
  .fb-row { grid-template-columns: 1fr 1fr; padding: 14px; }
  .fb-who { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .fb-row { grid-template-columns: 1fr; }
  .fb-foot { flex-direction: column; gap: 4px; }
}

/* [hidden] is a UA rule and loses to any class that sets display, which
   .profile-section does. Said outright so hiding the portal on the family
   landing page does not depend on that. */
.profile-main-col[hidden] { display: none !important; }

/* ---- child selector on the family page --------------------------------
   The button is a prompt, not a name: no avatar, and the label reads as
   the invitation it is rather than as a child who has been chosen. */
.child-selector.is-family .child-selector-avatar { display: none; }
.child-selector.is-family .child-selector-name { color: var(--text-muted); font-weight: 500; }

/* "All children" sits above the roster with its own mark, so the way back
   to the family page is the first thing in the menu rather than a missing
   one. */
.child-selector-family-mark {
  width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(140deg, var(--navy), var(--teal));
  color: #fff;
  font-size: 14px;
}
.child-selector-family .child-selector-item-name { font-weight: 600; }

.hero-id-block[hidden] { display: none !important; }

/* ---- the banner's family-page left side -------------------------------- */
.hero-family-block {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 0 1 auto;
}
.hero-family-block[hidden] { display: none !important; }
.hero-family-count { margin: 0; }
.hero-family-stats {
  margin: 2px 0 0;
  font-size: 13.5px;
  color: var(--text-muted);
  display: flex;
  align-items: baseline;
  gap: 7px;
  flex-wrap: wrap;
}
.hero-family-stats strong {
  color: var(--navy);
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* A RULE, NOT A CHARACTER (2026-08-28). This was a middot at 40% white,
   which measured 2.80:1 on the banner's lit corner. Brightening a separator
   until it passes as text makes it compete with the numbers either side of
   it - the separator would end up as loud as the thing it separates. A 1px
   rule is decoration rather than text, so 1.4.3 does not apply to it, and
   it is the more refined typography anyway. */
.hero-family-dot {
  font-size: 0;
  line-height: 0;
  display: inline-block;
  width: 1px;
  height: 11px;
  background: currentColor;
  opacity: 0.32;
  transform: translateY(1px);
}
.hero-family-hint {
  margin: 7px 0 0;
  font-size: 13px;
  color: var(--text-muted);
  max-width: 34ch;
}

/* ---- "Select a child": the one control this page exists to have used ----
   Dark and pulsing ONLY on the family page. It is the sole route into every
   child's detail now that the portal is not on this page, so it stops being
   a quiet filter and becomes the primary action. On a child's page it goes
   back to the quiet pill it has always been - there it is a switcher, not
   an instruction. */
.child-selector.is-family .child-selector-toggle {
  padding: 8px 16px;
  border-color: transparent;
  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  animation: childPickPulse 2.6s ease-out infinite;
}
.child-selector.is-family .child-selector-name { color: #fff; font-weight: 600; }
.child-selector.is-family .child-selector-caret { color: #7EE0E2; }
.child-selector.is-family .child-selector-toggle:hover { filter: brightness(1.12); }

/* The ring expands and fades; the drop shadow underneath it does not move,
   so the button reads as lit rather than as jumping. */
/* REBUILT because the first version was invisible (2026-08-28). It went
   from spread 0 at full alpha to spread 9px at zero alpha - so at the one
   moment the ring was opaque it had no size, and by the time it had size it
   had faded out. It was only ever visible mid-transition, and never at all
   in a still.

   This keeps a ring on the button at ALL times and breathes its intensity,
   so there is no point in the cycle where the button looks ordinary. */
@keyframes childPickPulse {
  0%, 100% {
    box-shadow:
      0 0 0 3px rgba(126, 224, 226, 0.40),
      0 0 16px 1px rgba(126, 224, 226, 0.35),
      0 6px 18px rgba(27, 28, 80, 0.30);
  }
  50% {
    box-shadow:
      0 0 0 7px rgba(126, 224, 226, 0.18),
      0 0 30px 6px rgba(126, 224, 226, 0.62),
      0 6px 18px rgba(27, 28, 80, 0.30);
  }
}

/* A pulse that never stops is a real accessibility problem for some readers,
   so the lit state is kept and only the movement is dropped. */
@media (prefers-reduced-motion: reduce) {
  .child-selector.is-family .child-selector-toggle {
    animation: none;
    box-shadow:
      0 0 0 3px rgba(126, 224, 226, 0.40),
      0 0 16px 1px rgba(126, 224, 226, 0.35),
      0 6px 18px rgba(27, 28, 80, 0.30);
  }
}

/* ---- the children, as a group portrait -------------------------------- */
.hero-family-block { flex-direction: row; align-items: center; gap: 16px; }
.hero-family-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.hero-family-avatars { display: flex; align-items: center; flex-shrink: 0; }
.hero-family-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  /* The ring is the page background, so the stack reads as overlapping
     rather than as one clipped shape. */
  box-shadow: 0 0 0 3px var(--bg-card);
  position: relative;
}
.hero-family-avatars > .hero-family-avatar + .hero-family-avatar { margin-left: -15px; }
.hero-family-avatar .avatar-img { width: 100%; height: 100%; border-radius: 50%; display: block; }

.hero-family-more {
  margin-left: -15px;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg-light);
  color: var(--text-muted);
  box-shadow: 0 0 0 3px var(--bg-card);
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 14px;
}

@media (max-width: 560px) {
  .hero-family-avatar, .hero-family-more { width: 42px; height: 42px; }
}

/* ==== THE FAMILY PAGE'S FOUR PANELS (2026-08-28) =======================
   Badges, usage, the science, the plan. One shell so they read as a
   sequence rather than four unrelated widgets, and the subject colours
   carried through from the board so a parent's eye keeps the same key.
   ==================================================================== */
.fam-panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px 22px 22px;
  margin-top: 18px;
}
.fam-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.fam-head .section-title { margin: 2px 0 0; font-size: 19px; }
.fam-head .section-sub { margin: 5px 0 0; max-width: 64ch; }
.fam-head-link {
  font-size: 13px;
  font-weight: 600;
  color: var(--teal);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
}
.fam-head-link:hover { border-bottom-color: currentColor; }

/* ---- badges ----------------------------------------------------------- */
.fbg-strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
}
.fbg-badge {
  margin: 0;
  padding: 14px 12px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: linear-gradient(160deg, rgba(243, 113, 48, 0.07), rgba(255, 255, 255, 0) 62%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}
.fbg-icon { font-size: 30px; line-height: 1.1; }
.fbg-name { margin: 0; font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 13.5px; color: var(--text-dark); }
.fbg-who { margin: 0; font-size: 12px; color: var(--text-muted); }
.fbg-share {
  margin-top: 4px;
  padding: 5px 13px;
  border-radius: 999px;
  border: 1px solid var(--teal);
  background: transparent;
  color: var(--teal);
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 12px;
  cursor: pointer;
}
.fbg-share:hover { background: var(--teal); color: #fff; }
.fbg-empty { margin: 0; font-size: 13.5px; color: var(--text-muted); grid-column: 1 / -1; }

.fbg-next {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 11px;
  background: var(--bg-light);
}
.fbg-next-title {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.fbg-next ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 5px; }
.fbg-next li { font-size: 13px; color: var(--text-muted); }
.fbg-next strong { color: var(--text-dark); }
.fbg-privacy { margin: 12px 0 0; font-size: 12px; color: var(--text-muted); }

/* ---- usage ------------------------------------------------------------ */
/* ---- usage, one block per child ---------------------------------------
   Four family totals answered "has this been used" and nothing else. A
   parent with two children wants to know which of them is using it, and
   four numbers added together actively hide that. Each child gets their own
   block with their own four figures; the family total moves to one sentence
   underneath, where a sum belongs. */
.fus-children {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  gap: 12px;
}
/* A single child got one card stretched across the whole panel - 1146px of
   card with four small figures adrift in it. The cap only binds when there
   are one or two children; at three the tracks come out ~374px and this
   changes nothing. */
.fus-child {
  max-width: 420px;
  padding: 14px 15px 13px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.fus-child-head { display: flex; align-items: center; gap: 9px; }
.fus-child-avatar {
  width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--navy), var(--teal));
  color: #fff;
  display: grid; place-items: center;
  font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 12.5px;
  overflow: hidden;
}
.fus-child-avatar .avatar-img { width: 100%; height: 100%; border-radius: 50%; display: block; }
.fus-child-name {
  font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 14.5px;
  color: var(--text-dark); flex: 1 1 auto; min-width: 0;
}
.fus-child-time {
  font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 14px;
  color: var(--navy); font-variant-numeric: tabular-nums;
}
.fus-child-time small { font-size: 11.5px; font-weight: 500; color: var(--text-muted); }

.fus-track { height: 7px; border-radius: 4px; background: #E2E2EC; overflow: hidden; }
.fus-fill { display: block; height: 100%; border-radius: 4px; background: linear-gradient(90deg, #14A0B3, var(--navy)); }

/* A description list, because that is what it is: four labelled figures
   about one child. */
/* margin-top:auto pins the figures to the bottom of the card. The note above
   them is one line for most children and two for "Nothing practised this
   week - we suggest 30 minutes a day, 5 days a week", which pushed that
   child's TESTS/PRACTICE/QUESTIONS row 17px below its siblings'. The cards
   are already equal height (grid stretch), so pinning is enough - and it
   keeps working whatever length a future note runs to. */
.fus-child-stats {
  margin: 8px 0 0;
  margin-top: auto;
  padding-top: 8px;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.fus-child-stats > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fus-child-stats dt {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-muted);
}
.fus-child-stats dd {
  margin: 0;
  font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 15px;
  color: var(--text-dark); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.fus-total {
  margin: 14px 0 0;
  padding: 11px 14px;
  border-radius: 10px;
  background: var(--bg-light);
  font-size: 13px;
  color: var(--text-muted);
}
.fus-total strong { color: var(--text-dark); font-variant-numeric: tabular-nums; }
.fus-quiet { color: var(--amber-dark); }

@media (max-width: 420px) {
  .fus-child-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 10px; }
}

/* ---- the science ------------------------------------------------------ */
.fsci-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
  gap: 12px;
}
/* The subject name sat on white with only a hairline above it to say which
   subject the card was about. It gets a tinted head instead - the same
   device the board's column headings use, so a parent meets one visual rule
   for "this block belongs to this subject" and not two. */
.fsci-card {
  --fsci-c: var(--navy);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* SOLID, exactly like the board's column headings (2026-08-28). A 12% wash
   was colour in principle and pale in practice - the same objection the
   headings answered two commits ago, and answering it differently here
   would give a parent two visual rules for one idea. Same fills, same text
   colours, same reason amber flips: white on #F37130 is 2.91:1 and navy-dark
   on it is 5.43:1, so the bright amber keeps its brightness and takes the
   dark label. */
.fsci-card .fsci-name {
  padding: 8px 14px;
  background: var(--fsci-fill);
  color: var(--fsci-on-fill);
}
/* The spec sits directly under the name, in the subject's own ink, because
   "40 questions, 12-14 minutes" is the first thing a parent weighing up a
   test wants and it should not be below the theory. */
.fsci-card .fsci-spec {
  margin: 0;
  padding: 9px 14px 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--fsci-ink);
  font-variant-numeric: tabular-nums;
}
.fsci-card .fsci-lead { padding: 7px 14px 0; }
.fsci-covers-title {
  margin: 12px 0 0;
  padding: 0 14px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* What it covers is a plain list, deliberately unlike the theory pills: one
   is the contents of the test and the other is the reading behind it, and
   giving them the same treatment made a parent scan past both. */
.fsci-covers {
  margin: 5px 0 0;
  padding: 0 14px;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 2px 0;
  font-size: 12px;
  color: var(--text-dark);
}
.fsci-covers li { position: relative; padding-right: 9px; margin-right: 7px; line-height: 1.45; }
.fsci-covers li:not(:last-child)::after {
  content: '';
  position: absolute;
  right: 0; top: 6px;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--fsci-c);
  opacity: 0.5;
}
.fsci-card .fsci-tags { margin-top: 5px; padding: 0 14px 14px; }
@supports not (background: color-mix(in srgb, red 12%, transparent)) {
  .fsci-card .fsci-name { background: var(--fsci-fill); color: var(--fsci-on-fill); }
}
/* TWO VARIABLES, NOT ONE. --fsci-c tints the ground; --fsci-ink writes on
   it. They were the same value, which is what put teal at 4.24:1 and amber
   at 4.35:1 on a 12% wash of themselves - the identical trap the board's
   column headings hit, in the identical two colours, because those two are
   simply the lightest of the four.

   The darkened values are the ones that pass already established for the
   column headings (#0D6B78, #A44415) rather than a second set of
   near-identical numbers: one teal and one orange in this product, not two
   of each. Navy (10.19) and violet (5.34) pass at full strength and are
   left alone. */
/* Three variables, each with one job: --fsci-c tints the tag pills,
   --fsci-ink writes on those tints, --fsci-fill is the solid head with
   --fsci-on-fill written on it. Collapsing any two of them is what put teal
   at 4.24:1 and amber at 4.35:1 last time. */
.fsci-card { --fsci-ink: var(--fsci-c); --fsci-on-fill: #fff; }
.fsci-cognitive { --fsci-c: var(--navy);       --fsci-ink: var(--navy);   --fsci-fill: var(--navy); }
.fsci-spelling  { --fsci-c: var(--teal);       --fsci-ink: #0D6B78;       --fsci-fill: var(--teal); }
.fsci-maths     { --fsci-c: var(--amber-dark); --fsci-ink: #A44415;       --fsci-fill: var(--amber); --fsci-on-fill: var(--navy-dark); }
.fsci-gk        { --fsci-c: var(--violet);     --fsci-ink: var(--violet); --fsci-fill: var(--violet); }

.fsci-name {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.fsci-lead { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--text-dark); }
.fsci-tags { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 5px; }
/* The tags were uniform grey, which threw away the one thing they had going
   for them: they belong to a subject, and the card is already that subject's
   colour. Tinted in their own accent they read as that subject's vocabulary
   rather than as four identical lists. 12% wash carries the accent at full
   strength - the same treatment and the same measurement the board's cells
   use. */
.fsci-tags li {
  font-size: 11px;
  padding: 3.5px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fsci-c) 12%, transparent);
  color: var(--fsci-ink);
  font-weight: 500;
}
@supports not (background: color-mix(in srgb, red 12%, transparent)) {
  .fsci-tags li { background: var(--bg-light); color: var(--text-muted); }
}
/* One line on a full-width panel. The 84ch cap was a reading-measure rule
   borrowed from body copy, and this is not body copy - it is a single
   qualifying sentence, and breaking it across two lines made it look like a
   paragraph somebody had started. It still wraps on narrow screens, which is
   correct: one line is the intent, not a constraint to enforce. */
.fsci-limit {
  margin: 16px 0 0;
  padding: 11px 14px;
  border-radius: 10px;
  background: var(--bg-light);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}
.fsci-limit strong { color: var(--text-dark); }

/* ---- plans ------------------------------------------------------------ */
.fpl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(196px, 1fr));
  gap: 12px;
}
/* ONE CARD ASKS, THE OTHER THREE ANSWER (2026-08-28). Four identical
   full-width buttons is not a choice, it is a wall - and the eye has
   nowhere to land. The recommended plan keeps the button; the rest drop to
   a text link, which is what they are: a way to read more, not a fourth
   equally-weighted decision.

   AND ONE ACCENT PER CARD. The recommended card had a teal flag, a teal
   ring and an amber button - three signals for one idea, in two colours
   that then argued with each other. It is amber throughout now, amber being
   this product's action colour, so the flag, the ring and the button all
   say the same thing. */
.fpl-card {
  position: relative;
  padding: 17px 16px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
  gap: 5px;
  transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.fpl-card:hover { border-color: var(--rule-strong, #D2D2DE); transform: translateY(-2px); }

.fpl-best {
  border-color: var(--amber);
  background: linear-gradient(170deg, rgba(243, 113, 48, 0.09), rgba(255, 255, 255, 0) 58%), var(--bg-card);
  box-shadow: 0 0 0 1px var(--amber), 0 10px 26px rgba(184, 76, 24, 0.16);
  transform: translateY(-3px);
}
.fpl-best:hover { transform: translateY(-5px); }

.fpl-flag {
  position: absolute;
  top: -10px;
  left: 15px;
  padding: 3px 10px;
  border-radius: 999px;
  /* --amber-dark, not --amber: white on #F37130 is 2.91:1. Same flip the
     board's Maths heading makes, for the same reason. */
  background: var(--amber-dark);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.fpl-name { margin: 0; font-family: 'Poppins', sans-serif; font-weight: 600; font-size: 14.5px; color: var(--text-dark); }
.fpl-price {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 23px;
  letter-spacing: -0.02em;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.fpl-price span { font-size: 12px; font-weight: 500; color: var(--text-muted); letter-spacing: 0; }
/* The per-month line is the whole reason an annual plan reads as good
   value, so it sits directly under the price rather than in the feature
   list. Empty on a one-off plan, but the row still occupies its height, so
   four cards keep their prices and their lists on the same baselines - a
   ragged grid is what makes a pricing table look assembled rather than
   designed. */
.fpl-permonth {
  margin: 0;
  min-height: 17px;
  font-size: 12px;
  color: var(--amber-dark);
  font-weight: 600;
}
.fpl-lines {
  margin: 9px 0 14px;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.fpl-lines li {
  position: relative;
  padding-left: 18px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-muted);
}
.fpl-lines li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  width: 9px;
  height: 5px;
  border-left: 1.8px solid var(--teal);
  border-bottom: 1.8px solid var(--teal);
  transform: rotate(-45deg);
}
.fpl-best .fpl-lines li::before { border-color: var(--amber-dark); }

.fpl-card .fpl-cta { margin-top: auto; width: 100%; }

/* The quiet three. A link, not a button, and it sits on the same baseline
   the CTA does so the row does not stagger. */
.fpl-link {
  margin-top: auto;
  align-self: flex-start;
  padding: 6px 0 0;
  border: 0;
  background: none;
  font-family: 'Poppins', sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--teal);
  cursor: pointer;
  border-bottom: 1px solid transparent;
}
.fpl-link:hover { border-bottom-color: currentColor; }

.fpl-note { margin: 16px 0 0; font-size: 12.5px; color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .fpl-card, .fpl-best, .fpl-card:hover, .fpl-best:hover { transition: none; transform: none; }
}

@media (max-width: 560px) {
  .fam-panel { padding: 16px 15px 18px; }
  .fus-row { grid-template-columns: 64px 1fr 58px; }
}

/* ---- THE PLAN STATE BLOCK (2026-08-28) ---------------------------------
   Top of the Overview, above the subject strip: the trial countdown, a
   near-expiry warning, the first-run offer, or the lapsed notice - one at a
   time, never two (see buildOverviewPlanStateHtml in js/auth.js).

   THE SAME SHAPE AS .usage-hero, DELIBERATELY. That strip already taught
   this page what a status band is - left accent rule, tinted ground, then
   eyebrow / figure / sentence with the action on the right. A second status
   band in a different shape would read as a different kind of thing rather
   than as the same page speaking twice.

   The six tones are this palette's existing semantics, not new colours:
   amber-dark is "needs attention now", amber is "soon", teal is "for
   information", navy is neutral. Headlines are --navy-dark on their own
   tint - 12.9:1 on the lightest, 10.8:1 on the strongest - and every body
   sentence is --text-muted, 5.9:1 on the amber tint and 6.4:1 on the teal.
   Nothing here relies on the tone colour alone: each state also says in
   words what it is and how long is left. */
.plan-state {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  border: 1px solid var(--border);
  border-left: 4px solid var(--ps-accent, var(--teal));
  border-radius: 14px;
  padding: 16px 20px;
  background: var(--ps-bg, var(--bg-card));
}
.ps-start  { --ps-accent: var(--navy); --ps-bg: linear-gradient(135deg, rgba(40, 41, 114, 0.07), rgba(15, 123, 138, 0.05)); }
.ps-trial  { --ps-accent: var(--teal); --ps-bg: rgba(15, 123, 138, 0.07); }
.ps-calm   { --ps-accent: var(--teal); --ps-bg: rgba(15, 123, 138, 0.06); }
.ps-warn   { --ps-accent: var(--amber); --ps-bg: rgba(243, 113, 48, 0.08); }
.ps-urgent { --ps-accent: var(--amber-dark); --ps-bg: rgba(243, 113, 48, 0.12); }
.ps-lapsed { --ps-accent: var(--text-muted); --ps-bg: rgba(40, 41, 114, 0.05); }

.plan-state-body { flex: 1; min-width: 220px; }
.plan-state-eyebrow {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 0 0 4px;
}
/* An h2, so the site-wide `h1,h2,h3 { margin: 0 0 0.5em }` applies and has
   to be overridden - 0.5em of 19px is 9.5px, which reads as a gap rather
   than as a headline sitting on its own sentence. */
.plan-state-headline {
  font-family: 'Poppins', sans-serif;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--navy-dark);
  margin: 0 0 6px;
}
.plan-state-msg { font-size: 13px; line-height: 1.5; color: var(--text-muted); margin: 0; }
.plan-state-cta { flex-shrink: 0; white-space: nowrap; }
@media (max-width: 560px) {
  .plan-state { padding: 14px 16px; }
  .plan-state-cta { width: 100%; text-align: center; }
}

/* ---- THE CHILD'S NEXT STEP (2026-08-28) --------------------------------
   Renders into #overview-narrative, the slot the parent's "What we've
   noticed" occupies - a light ground, and one level down from the plan
   state above it. Not a second hero: one heading, one reason, at most two
   buttons. */
.child-next {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 4px solid var(--teal);
  border-radius: 14px;
  padding: 18px 20px;
}
/* Nothing to act on yet, so the accent rule drops to a plain border - an
   amber or teal edge would promise something the card does not have. */
.cn-empty { border-left-color: var(--border); }
.child-next-eyebrow {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 0 0 4px;
}
.child-next-headline {
  font-family: 'Poppins', sans-serif;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--navy-dark);
  margin: 0 0 6px;
}
.child-next-msg { font-size: 13px; line-height: 1.5; color: var(--text-muted); margin: 0; }
.child-next-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.cn-btn { padding: 9px 16px; font-size: 13px; }
.child-next-note { margin: 12px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--text-muted); }
@media (max-width: 560px) {
  .child-next-actions { flex-direction: column; }
  .cn-btn { width: 100%; text-align: center; }
}

/* The report link on a subject card. The levels strip is the navy gradient,
   so this takes the same 78% white the quota line uses (7.66:1 at the
   panel's worst corner) and goes solid on hover. Underlined at rest, not
   only on hover: it is the one link on a card otherwise made of buttons,
   and weight alone would not mark it as one. */
.ov-lvl-report {
  display: inline-block;
  margin-top: 10px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.78);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ov-lvl-report:hover,
.ov-lvl-report:focus-visible { color: #fff; }

/* ==== THE FAMILY PAGE, IN COLOUR (2026-08-28) ==========================
   The brief was "colour rich, and up market". Those pull against each
   other if colour is read as saturation - four bright panels is a toy.
   So the boldness is spent in ONE place, the banner, and everything below
   it is tint rather than fill: 3-6% washes of a colour the page already
   uses, which reads as depth instead of decoration.

   The banner uses the exact gradient .module-hero, .asg-summary and
   .overview-levels-row already use. It is not a new look - it is this
   product's existing "top of the page" panel, finally on the page a
   parent actually lands on.
   ==================================================================== */
.child-dashboard-hero.is-family {
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(15, 123, 138, 0.42), transparent 62%),
    linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%);
  border: 0;
  box-shadow: 0 16px 40px rgba(27, 28, 80, 0.26);
}
/* The watermark is barely-there on white and invisible on navy at 4.5%.
   Lifted, and it is the same lens mark the subject banners carry. */
.child-dashboard-hero.is-family::before { opacity: 0.09; }

/* Every colour below is measured against BOTH gradient stops, because a
   value that only passes on the darker half fails wherever the panel
   lightens. White at 66% is deliberately not used anywhere here - it is
   the value that measured 4.89:1 on the subject banner's lit corner. */
.child-dashboard-hero.is-family .eyebrow { color: #7EE0E2; }
.child-dashboard-hero.is-family .section-title,
.child-dashboard-hero.is-family .hero-family-count { color: #fff; }
.child-dashboard-hero.is-family .hero-family-stats { color: rgba(255, 255, 255, 0.82); }
.child-dashboard-hero.is-family .hero-family-stats strong { color: #fff; }
.child-dashboard-hero.is-family .hero-family-dot { color: rgba(255, 255, 255, 0.4); }
.child-dashboard-hero.is-family .hero-family-hint { color: rgba(255, 255, 255, 0.78); }
.child-dashboard-hero.is-family .hero-tagline-mid-main { color: #fff; }
.child-dashboard-hero.is-family .hero-tagline-mid-sub,
.child-dashboard-hero.is-family .hero-brand-tagline { color: rgba(255, 255, 255, 0.78); }
.child-dashboard-hero.is-family .hero-tagline-mid-dots { color: rgba(255, 255, 255, 0.45); }

/* The dividers either side of the tagline were --border on white. */
.child-dashboard-hero.is-family .hero-tagline-mid { border-color: rgba(255, 255, 255, 0.16); }
.child-dashboard-hero.is-family .hero-family-block { border-color: rgba(255, 255, 255, 0.16); }

/* The portrait ring was the card's white; on navy it has to be the panel. */
.child-dashboard-hero.is-family .hero-family-avatar,
.child-dashboard-hero.is-family .hero-family-more { box-shadow: 0 0 0 3px rgba(27, 28, 80, 0.55); }
.child-dashboard-hero.is-family .hero-family-more { background: rgba(255, 255, 255, 0.12); color: rgba(255, 255, 255, 0.82); }

/* ---- the panels below: tint, not fill ---------------------------------
   Each panel carries a wash of the colour its content is already about,
   so the page has four distinct grounds without four competing cards.
   Kept at 3-6%, where the shift is felt rather than seen and body text
   contrast moves by hundredths. */
.fam-badges {
  background:
    radial-gradient(110% 120% at 100% 0%, rgba(243, 113, 48, 0.07), transparent 58%),
    var(--bg-card);
}
.fam-usage {
  background:
    radial-gradient(110% 120% at 0% 0%, rgba(15, 123, 138, 0.07), transparent 58%),
    var(--bg-card);
}
.fam-science {
  background:
    radial-gradient(110% 120% at 100% 0%, rgba(156, 53, 135, 0.06), transparent 58%),
    var(--bg-card);
}
.fam-plans {
  background:
    radial-gradient(110% 120% at 0% 100%, rgba(40, 41, 114, 0.08), transparent 58%),
    var(--bg-card);
}

/* Each panel's eyebrow in its own colour, so the four sections are
   distinguishable at a glance the way the board's columns are. Values
   are the accessible variants the column headings already established
   rather than a second opinion about the same four colours. */
.fam-badges .eyebrow { color: var(--amber-dark); }
.fam-usage .eyebrow { color: var(--teal); }
.fam-science .eyebrow { color: var(--violet); }
.fam-plans .eyebrow { color: var(--navy); }

/* A hairline of the panel's own colour along the top, which is what makes
   a stack of cards read as a sequence rather than a list. */
.fam-panel { position: relative; overflow: hidden; }
.fam-panel::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--fam-rule, var(--teal));
}
.fam-badges { --fam-rule: linear-gradient(90deg, var(--amber), #FFC38A); }
.fam-usage { --fam-rule: linear-gradient(90deg, var(--teal), #7EE0E2); }
.fam-science { --fam-rule: linear-gradient(90deg, var(--violet), #D982C6); }
.fam-plans { --fam-rule: linear-gradient(90deg, var(--navy), var(--teal)); }

/* Depth under the stack. Two layers - a tight contact shadow and a wide
   soft one - is what separates a card sitting on a page from a rectangle
   drawn on it. */
.fam-panel, .fb-board {
  box-shadow: 0 1px 2px rgba(27, 28, 80, 0.05), 0 14px 34px rgba(27, 28, 80, 0.09);
}

/* The page itself. A flat grey behind white cards is the flattest thing a
   layout can do; this is the same grey with the brand's own navy pulled
   through the top of it, which the banner then sits into. */
body.has-family-page {
  background:
    linear-gradient(180deg, rgba(40, 41, 114, 0.07) 0px, rgba(40, 41, 114, 0) 420px),
    var(--bg-light);
  background-repeat: no-repeat;
  background-attachment: scroll, scroll;
}

/* The one-line explanation under a field that needs one. Only two fields do:
   class, because it decides which paper a child sits, and state, because a
   parent handing over their child's location deserves to be told what it is
   for before they pick rather than after. */
.field-note {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* The roster's way back to the family page, at the right-hand end with the
   other ways out of this page. The divider is on its RIGHT: it separates
   navigation from account identity, and the eye reads the rule as belonging
   to the boundary rather than to the link. On mobile .profile-account
   becomes space-between with the email hidden, which leaves this at one end
   and Sign out at the other - the correct pair for that width. */
.dashboard-home-link {
  padding-right: 18px;
  border-right: 1px solid var(--border);
  white-space: nowrap;
}

/* --- GK question bank (2026-08-30) --------------------------------------
   Cards, not table rows. A GK item carries a stem, up to four options and an
   explanation; a table would either clip them or grow a horizontal scrollbar,
   and this is a screen for READING a question closely enough to judge whether
   it is wrong. Namespaced gk-* rather than reusing admin-row-*, which is a
   table-cell class with its own white-space rules. */
.gk-row {
  border: 1px solid #eef0f6;
  border-radius: 12px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 0.7rem;
  background: #fff;
}
.gk-row.is-withdrawn {
  /* Muted and struck through the stem, so a withdrawn question is obvious in
     a scan of the list rather than needing its pill to be read. */
  background: #fcfcfd;
  border-color: #e7e9f2;
}
.gk-row.is-withdrawn .gk-row-stem { text-decoration: line-through; color: #94a3b8; }
.gk-row.is-editing { border-color: #c7cbe8; box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.08); }
.gk-row-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; margin-bottom: 0.45rem; }
.gk-row-id { font-size: 0.72rem; color: #94a3b8; margin-left: auto; }
.gk-row-stem { margin: 0 0 0.35rem; font-weight: 600; line-height: 1.4; }
.gk-row-answer { margin: 0 0 0.25rem; font-size: 0.85rem; color: #166534; }
.gk-row-explain { margin: 0; font-size: 0.85rem; color: #64748b; line-height: 1.45; }
.gk-row-actions { margin-top: 0.6rem; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.admin-status-pill.is-warn { background: #faeeda; color: #633806; }

/* The Content tab is gated on the `content` capability. Hiding the tab BUTTON
   is not enough on its own - the panel is reachable by its #content hash -
   so the panel is hidden too. The API refuses either way; this is so a
   refusal never has to be the first thing an admin learns. */
body.cannot-content .portal-tab[data-tab="content"],
body.cannot-content #tab-content { display: none !important; }

/* Upload dialog + bulk selection (2026-08-30) */
/* NAMED FOR THE UPLOAD, not for GK (2026-08-31). This was .gk-progress /
   .gk-progress-bar, which the GK EXAM topbar has owned since long before
   this dialog existed - same stylesheet, and this copy sits later in it, so
   it won. The exam's progress block holds the "QUESTION 34 OF 40" caption
   as well as the bar, so height:6px + overflow:hidden sliced that caption
   down to a 6px strip mid-letter, and the exam's real percentage bar was
   replaced by this indeterminate stripe. A child sitting the test saw it. */
.gk-upload-bar {
  height: 6px;
  border-radius: 999px;
  background: #eef0f6;
  overflow: hidden;
  margin: 0.6rem 0 0.8rem;
}
/* Indeterminate on purpose. The file goes up in ONE request, so there is no
   percentage to report - a bar filling to 90% and waiting would be inventing
   a number. This says "working" and nothing more. */
.gk-upload-bar-fill {
  height: 100%;
  width: 40%;
  border-radius: 999px;
  background: linear-gradient(90deg, #F37130, #282972);
  animation: gk-upload-bar-slide 1.1s ease-in-out infinite;
}
@keyframes gk-upload-bar-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
  .gk-upload-bar-fill { animation: none; width: 100%; opacity: 0.5; }
}
.gk-upload-rejects {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid #eef0f6;
  border-radius: 10px;
  padding: 0.6rem 0.8rem;
  margin: 0.6rem 0;
  background: #fcfcfd;
}
.gk-pick { margin-right: 0.15rem; vertical-align: middle; }
#gk-bulkbar { align-items: center; gap: 0.9rem; }
#gk-delete-selected:disabled { opacity: 0.45; cursor: not-allowed; }

/* A derived value, not an input. It looks like the fields around it so the
   row still reads as one, but it is flatter and dimmer because there is
   nothing to do to it - and tabindex="-1" keeps it out of the tab order,
   since landing on a box you cannot change is a small dead end. */
.child-form input.field-derived {
  background: var(--bg-light);
  color: var(--text-muted);
  border-color: var(--border);
  cursor: default;
  font-weight: 600;
}
.child-form input.field-derived.is-out-of-range {
  color: var(--amber-dark);
  border-color: var(--amber);
  background: rgba(243, 113, 48, 0.07);
}

/* The free trial leads the panel, as it leads the buy modal - it is the
   thing to do first, not the cheapest of five things to choose between, so
   it is a band above the grid rather than a fifth card in it. Teal, not the
   amber the recommended paid card uses: free and paid should not compete for
   the same signal. */
.fpl-trial {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding: 15px 17px;
  border: 1.5px solid var(--teal);
  border-radius: 14px;
  background: linear-gradient(150deg, rgba(15, 123, 138, 0.09), rgba(15, 123, 138, 0.02) 68%), var(--bg-card);
}
.fpl-trial-main { flex: 1 1 300px; min-width: 0; }
.fpl-trial-eyebrow {
  margin: 0 0 3px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--teal);
}
.fpl-trial-title {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: 15.5px;
  color: var(--text-dark);
}
.fpl-trial-sub { margin: 3px 0 0; font-size: 13px; color: var(--text-muted); }

/* The single test named in prose rather than as four more tiles: it is one
   offer bought per subject, and four identical cards said that badly. */
.fpl-single {
  margin: 14px 0 0;
  font-size: 13px;
  color: var(--text-muted);
}
.fpl-single strong { color: var(--text-dark); font-variant-numeric: tabular-nums; }
.fpl-single .fpl-link { margin-top: 0; padding: 0 0 0 4px; font-size: 13px; }

/* The one-plan detail sheet. Deliberately narrow and plain: it answers one
   question, so it gets one column and no grid. */
#plan-detail-body .section-title { margin: 2px 0 6px; }
.pd-price {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: 30px;
  letter-spacing: -0.02em;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.pd-price span { font-size: 13px; font-weight: 500; color: var(--text-muted); letter-spacing: 0; }
.pd-permonth { margin: 2px 0 0; font-size: 12.5px; font-weight: 600; color: var(--amber-dark); }
.pd-desc { margin: 12px 0 0; font-size: 14px; color: var(--text-dark); }
.pd-lines { margin: 12px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 7px; }
.pd-lines li { position: relative; padding-left: 20px; font-size: 13.5px; color: var(--text-muted); }
.pd-lines li::before {
  content: '';
  position: absolute; left: 0; top: 6px;
  width: 9px; height: 5px;
  border-left: 1.8px solid var(--teal);
  border-bottom: 1.8px solid var(--teal);
  transform: rotate(-45deg);
}
.pd-forwhom { margin: 14px 0 0; font-size: 13.5px; color: var(--text-muted); }
.pd-child { margin-top: 14px; }
.pd-child label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.pd-child select {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  font-family: inherit;
  background: #fff;
}
.pd-buy { width: 100%; margin-top: 18px; }
.pd-none {
  margin: 16px 0 0; padding: 11px 14px; border-radius: 10px;
  background: var(--bg-light); font-size: 13px; color: var(--text-muted);
}
.pd-note { margin: 14px 0 0; font-size: 12px; color: var(--text-muted); }

/* Group labels inside the plan sheet. "What you get" and "Not included" are
   the two questions a parent is actually weighing, so they are labelled
   rather than run together as one list. */
.pd-grouplabel {
  margin: 16px 0 7px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The excluded list takes a dash, not a tick. A greyed tick still reads as
   a tick at a glance, which is the one misreading that matters here. */
.pd-lines-out li::before {
  content: '';
  left: 1px;
  top: 9px;
  width: 9px;
  height: 0;
  border-left: 0;
  border-bottom: 1.8px solid var(--text-muted);
  opacity: 0.6;
  transform: none;
}
.pd-renews {
  margin: 16px 0 0;
  padding: 9px 12px;
  border-radius: 9px;
  background: var(--bg-light);
  font-size: 12.5px;
  color: var(--text-muted);
}
.pd-foot { margin: 8px 0 0; font-size: 12.5px; color: var(--text-muted); font-style: italic; }

/* The week's practice against the suggestion. Teal while short of it,
   green once past - a colour change is the only reward here, because the
   product does not score a parent on their child's habits and a red bar
   would do exactly that. */
.fus-child-note { margin: 0; font-size: 12px; line-height: 1.4; color: var(--text-muted); }
.fus-child-time.is-met { color: #1B6E52; }
.fus-fill.is-met { background: linear-gradient(90deg, #2E9E76, #1B6E52); }

/* ---- the week as five blocks --------------------------------------------
   One block per school day, each worth DAILY_PRACTICE_MIN. A parent counts
   the empty ones and knows what is missing; a percentage has to be turned
   into that first. The last partial block fills proportionally so a
   half-finished day shows as half a block. */
.fus-blocks {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 5px;
}
.fus-blk {
  height: 12px;
  border-radius: 4px;
  background: #E7E7EF;
  overflow: hidden;
}
.fus-blk-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, #14A0B3, var(--navy));
}

/* The state in a word as well as in colour, because colour alone is not a
   label - and the blocks say it a third time by being countable. */
.fus-state {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.fus-state-met   { color: #145C44; background: #DCF1E8; }
.fus-state-close { color: #0D6B78; background: #DEEFF1; }
/* #A44415, not --amber-dark: the token measures 4.42:1 on its own 
   background here, just under the 4.5 a bold 11px chip needs. Same
   darkening, and the same value, the board's Maths heading uses. */
.fus-state-short { color: #A44415; background: #FBEBE0; }
.fus-state-none  { color: var(--text-muted); background: var(--bg-light); }

/* Amber, never red. A red bar would be scoring a parent on their child's
   habits, which is not what this panel is for.

   Keyed off a class on the card rather than :has(). The fill colour carries
   meaning here, and meaning should not ride on a selector some browsers do
   not support - where :has() is missing every child's blocks would have come
   out the same teal regardless of how the week went. */
.fus-is-short .fus-blk-fill { background: linear-gradient(90deg, #E8823F, #A44415); }
.fus-is-met .fus-blk-fill { background: linear-gradient(90deg, #2E9E76, #1B6E52); }
.fus-child-note strong { color: var(--text-dark); font-variant-numeric: tabular-nums; }
/* Password / Email-code tabs on the sign-in card (2026-08-31).
   A pill pair rather than two links, because these are mutually exclusive
   ways of doing the SAME thing - signing in - and links would read as two
   different destinations. */
.signin-tabs {
  display: flex;
  gap: 0.25rem;
  background: #f1f2f7;
  padding: 0.25rem;
  border-radius: 999px;
  margin: 0 0 1rem;
}
.signin-tab {
  flex: 1;
  padding: 0.45rem 0.5rem;
  border: none;
  background: none;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: #8a8fa3;
  cursor: pointer;
}
.signin-tab.on {
  background: #fff;
  color: #282972;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

/* ---- In-place pause (2026-08-31) ---------------------------------------
   The overlay has to actually COVER the paper, not tint it: the point of
   pausing mid-test is that a child can walk away from the screen. So an
   opaque ground, not the usual translucent modal scrim.

   position:fixed and a z-index above the exam chrome. It is NOT a <dialog>
   - showModal() would put it in the top layer above everything including
   the exam's own header, which is fine, but it also traps Escape into
   closing it, and Escape must not uncover the questions. */
.exam-pause-overlay {
  position: fixed;
  inset: 0;
  z-index: 1400;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--bg-light);
}
.exam-pause-overlay[hidden] { display: none; }
/* Belt and braces with the overlay above: the questions underneath stay in
   the document, so without this a Tab press walks the focus ring straight
   into the answers a child is meant to be away from. */
body.exam-is-paused #app,
body.exam-is-paused .exam-adv-controls,
body.exam-is-paused #exam-adv-panel { visibility: hidden; }
.exam-pause-card {
  width: 100%;
  max-width: 460px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 32px 34px 30px;
  text-align: center;
  box-shadow: var(--shadow-lg);
}
.exam-pause-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
  background: rgba(15, 123, 138, 0.10);
  border-radius: 999px;
  padding: 5px 12px;
  margin-bottom: 14px;
}
.exam-pause-title {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: var(--navy-dark);
  margin: 0 0 8px;
}
.exam-pause-body {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-muted);
  margin: 0 0 24px;
}
.exam-pause-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
}
@media print { .exam-pause-overlay { display: none !important; } }
