/* Zarmed Olympiad — brand-aligned UI
 * Palette pulled directly from the Zarmed University logo (logo.png):
 *   - Blue    #1e40af  (ZARMED wordmark, shield)
 *   - Crimson #991b1b  (UNIVERSITY wordmark, horse icon)
 *   - Gold    #eab308  (seal ribbon, 2022)
 * Earlier warm-brown + Cambridge-teal palette clashed with the real brand.
 * The --ct-teal token name survives to avoid diff churn but now holds BLUE
 * so the test runner's active states match the logo.
 *
 * Two visual scopes in one file:
 *   - .zu-page — welcome / dashboard / done / admin (brand blue + crimson)
 *   - .zu-test-body — test runner (white canvas, blue actions)
 */

/* The HTML5 [hidden] attribute maps to UA-level `display: none`, which has
 * lower specificity than any class-level `display` rule. When a .zu-field
 * or .zu-candidate-section sets display:flex/grid, `hidden` stops working.
 * Force the attribute to win with a scoped !important rule. */
[hidden] { display: none !important; }

:root {
  /* Zarmed brand palette (from logo.png) */
  --zu-primary: #1e40af;         /* ZARMED blue */
  --zu-primary-dark: #1e3a8a;    /* darker blue for hover/pressed */
  --zu-primary-darker: #172554;  /* deepest navy for headings */
  --zu-primary-strong: #1d4ed8;  /* blue-700 — admin sort-header hover */
  --zu-primary-faint: #eff6ff;   /* faint blue — admin row hover/focus tint */
  --zu-accent-dark: #7f1d1d;     /* UNIVERSITY crimson (hover/pressed) */
  --zu-cream-soft: #f8fafc;      /* app background — clean neutral, not cream */
  --zu-surface: #ffffff;         /* cards, panels */
  --zu-text: #0f172a;            /* slate-900 */
  --zu-text-muted: #64748b;      /* slate-500 */
  --zu-border: #e2e8f0;          /* slate-200 */
  --zu-border-strong: #cbd5e1;   /* slate-300 */
  --zu-success: #15803d;
  --zu-danger: #b91c1c;
  --zu-focus: #60a5fa;           /* blue-400 focus ring */

  /* Test runner — white canvas, blue as primary action colour.
   * --ct-teal is a legacy var name that now holds brand blue. */
  --ct-bg: #f5f7fa;
  --ct-surface: #ffffff;
  --ct-text: #0f172a;
  --ct-text-muted: #64748b;
  --ct-border: #e2e8f0;
  --ct-border-strong: #cbd5e1;
  --ct-banner-bg: #f1f5f9;
  --ct-bg-alt: #f1f5f9;          /* alt surface tint (shares banner-bg value) */
  --ct-teal: #1e40af;
  --ct-teal-dark: #1e3a8a;
  --ct-teal-soft: #dbeafe;
  --ct-inactive: #94a3b8;
  --ct-error-bg: #fee2e2;
  --ct-error-text: #b91c1c;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  color: var(--zu-text);
  background: var(--zu-cream-soft);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ============================================================ */
/* ZARMED PAGES — welcome, dashboard, done, admin                */
/* ============================================================ */

h1, h2, h3 {
  color: var(--zu-primary-darker);
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 0.5em;
  letter-spacing: -0.01em;
}

h1 { font-size: 2.6rem; }
h2 { font-size: 1.85rem; }
h3 { font-size: 1.35rem; }

.page {
  max-width: 1120px;
  margin: 0 auto;
  padding: 2.5rem 2rem 4rem;
}
.page--narrow { max-width: 620px; }
.page--wide { max-width: 1280px; }

/* Admin login form lives inside a full-width .page (because the list view
 * needs width), so constrain just the login view to keep its form centered
 * on a narrow column instead of stretching across 1120px. */
.zu-login-wrap {
  max-width: 480px;
  margin: 0 auto;
}

/* When the admin is viewing a single submission's per-question breakdown,
 * the full-size welcome header (logo + h1 + subtitle) eats ~300px of
 * vertical space before the student's actual answers. Compact it to a
 * minimal brand strip so the table shows immediately. Toggled from
 * admin.js on entering/leaving the detail view. */
body.zu-admin-detail .zu-header {
  text-align: left;
  padding: 1rem 0 1rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--zu-border);
  display: flex;
  align-items: center;
  gap: 1.25rem;
}
body.zu-admin-detail .zu-header .zu-logo {
  width: 140px;
  margin: 0;
}
body.zu-admin-detail .zu-header h1 {
  font-size: 1.35rem;
  margin: 0;
}
body.zu-admin-detail .zu-header .zu-subtitle {
  display: none;
}

/* Welcome page is a narrow form floating on a huge screen. Vertically
 * center it so the form doesn't hug the top of a 1080p canvas with 700px
 * of empty space below. Applied via a body class added on welcome only. */
body.zu-welcome {
  min-height: 100vh;
  display: grid;
  place-items: center;
}
body.zu-welcome .page {
  padding: 3rem 2rem;
  width: 100%;
}
/* Welcome entrance — staggered fade-in for header, then form */
body.zu-welcome .zu-header {
  animation: ctFadeIn 400ms ease-out both;
}
body.zu-welcome .zu-form {
  animation: ctFadeIn 400ms ease-out 120ms both;
}
/* Resume card loading state */
.zu-resume-card.is-loading {
  opacity: 0.6;
  pointer-events: none;
  position: relative;
}
.zu-resume-card.is-loading .zu-resume-card-time::after {
  content: '';
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-left: 8px;
  border: 2px solid var(--zu-border-strong);
  border-top-color: var(--zu-primary);
  border-radius: 50%;
  animation: zuSpin 0.6s linear infinite;
  vertical-align: middle;
}
@keyframes zuSpin {
  to { transform: rotate(360deg); }
}

/* Resume section — continue where you left off */
.zu-resume-section {
  margin-bottom: 2rem;
  animation: ctFadeIn 350ms ease-out 200ms both;
}
.zu-resume-section h2 {
  text-align: center;
  color: var(--zu-primary);
  margin-bottom: 0.5rem;
}
.zu-resume-hint {
  text-align: center;
  color: var(--zu-text-muted);
  font-size: 0.95rem;
  margin: 0 0 1.25rem;
}
.zu-resume-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-height: 340px;
  overflow-y: auto;
  padding-right: 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--zu-primary) transparent;
}
.zu-resume-list::-webkit-scrollbar {
  width: 6px;
}
.zu-resume-list::-webkit-scrollbar-track {
  background: transparent;
}
.zu-resume-list::-webkit-scrollbar-thumb {
  background: var(--zu-primary);
  border-radius: 3px;
  opacity: 0.4;
}
.zu-resume-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  background: var(--zu-surface);
  border: 1.5px solid var(--zu-border);
  border-left: 4px solid var(--zu-primary);
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
  font: inherit;
  color: inherit;
  text-align: left;
  width: 100%;
}
.zu-resume-card:hover {
  border-color: var(--zu-primary);
  box-shadow: 0 4px 12px rgba(30, 64, 175, 0.15);
  transform: translateY(-2px);
}
/* (expired cards no longer rendered — server auto-finalizes them) */
.zu-resume-card-info {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.zu-resume-card-name {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--zu-primary-darker);
}
.zu-resume-card-meta {
  font-size: 0.85rem;
  color: var(--zu-text-muted);
}
.zu-resume-card-time {
  font-family: "SF Mono", "Consolas", monospace;
  font-size: 1rem;
  font-weight: 700;
  color: var(--zu-primary);
  white-space: nowrap;
}
/* (expired card time style removed — no longer needed) */
.zu-divider {
  border: none;
  border-top: 1px solid var(--zu-border);
  margin: 1.5rem 0;
}
.zu-resume-or {
  text-align: center;
  color: var(--zu-text-muted);
  font-size: 0.9rem;
  margin: 0;
}

.zu-header {
  text-align: center;
  padding: 2rem 1rem 1.75rem;
  border-bottom: 1px solid var(--zu-border);
  margin-bottom: 2.5rem;
}
.zu-logo {
  display: block;
  width: 300px;
  max-width: 80%;
  height: auto;
  margin: 0 auto 1.25rem;
  user-select: none;
  -webkit-user-drag: none;
}
/* Walk G03 — on short kiosk displays (≤820px tall, e.g. 1024×768 / 1366×768)
   the ~630px header+logo chrome pushed the module cards below the fold; at
   1024×768 the third (Writing) card sat entirely below it with no scroll cue,
   so a candidate could miss a whole module. Shrink the chrome so all three
   cards fit above the fold. */
@media (max-height: 820px) {
  .zu-header { padding: 1rem 1rem 0.75rem; margin-bottom: 1rem; }
  .zu-logo { width: 190px; margin-bottom: 0.6rem; }
  .zu-modules-section { padding-top: 0.5rem; }
}
.zu-header h1 { margin-bottom: 0.35rem; }
/* Red "IELTS" wordmark on the welcome/dashboard, matching the in-exam header (#5). */
.zu-ielts-mark { color: #e2001a; font-weight: 800; }
.zu-header .zu-subtitle {
  margin: 0;
  color: var(--zu-text-muted);
  font-size: 1.05rem;
  font-weight: 500;
}

.zu-form { display: flex; flex-direction: column; gap: 1.5rem; }
.zu-field { display: flex; flex-direction: column; }
.zu-field label {
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 0.5rem;
  color: var(--zu-primary-darker);
  letter-spacing: 0.01em;
}
.zu-field input,
.zu-field select {
  font-family: inherit;
  font-size: 1.05rem;
  padding: 0.85rem 1rem;
  border: 1.5px solid var(--zu-border-strong);
  border-radius: 6px;
  background: #fff;
  color: var(--zu-text);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.zu-field input:focus,
.zu-field select:focus {
  outline: none;
  border-color: var(--zu-primary);
  box-shadow: 0 0 0 4px rgba(30, 64, 175, 0.15);
}
/* Read-only exam label for the YADAK-only kiosk — looks like a filled field
   the candidate cannot change (intent-yadak-kiosk#P2). */
.zu-fixed-exam {
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 600;
  padding: 0.85rem 1rem;
  border: 1.5px solid var(--zu-border-strong);
  border-radius: 6px;
  background: var(--zu-surface);
  color: var(--zu-primary-darker);
}
.zu-field input[aria-invalid="true"],
.zu-field select[aria-invalid="true"] {
  border-color: var(--zu-danger);
  background: #fef2f2;
}
.zu-field input[aria-invalid="true"]:focus,
.zu-field select[aria-invalid="true"]:focus {
  border-color: var(--zu-danger);
  box-shadow: 0 0 0 4px rgba(185, 28, 28, 0.15);
}

.zu-btn {
  font-family: inherit;
  font-size: 1.1rem;
  font-weight: 600;
  padding: 0.95rem 2rem;
  border: none;
  border-radius: 6px;
  background: var(--zu-primary);
  color: #fff;
  cursor: pointer;
  transition: background 0.15s, transform 0.08s, box-shadow 0.15s;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
  letter-spacing: 0.01em;
  text-decoration: none;
  display: inline-block;
  text-align: center;
  line-height: 1.2;
}
a.zu-btn, a.zu-btn:visited { color: #fff; }
a.zu-btn.zu-btn--ghost, a.zu-btn.zu-btn--ghost:visited { color: var(--zu-primary); }
.zu-btn:hover:not(:disabled) {
  background: var(--zu-primary-dark);
  box-shadow: 0 4px 12px rgba(30, 64, 175, 0.25);
}
.zu-btn:active:not(:disabled) {
  transform: translateY(1px);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
}
.zu-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.zu-btn:focus-visible {
  outline: 3px solid var(--zu-focus);
  outline-offset: 3px;
}
.zu-btn--ghost {
  background: transparent;
  color: var(--zu-primary);
  border: 1.5px solid var(--zu-primary);
  box-shadow: none;
}
.zu-btn--ghost:hover:not(:disabled) {
  background: rgba(30, 64, 175, 0.06);
  box-shadow: none;
}

.zu-error {
  color: var(--zu-danger);
  background: var(--ct-error-bg);
  padding: 0.5rem 0.75rem;
  border-radius: 3px;
  margin-top: 0.5rem;
}

/* Dashboard module cards */
.zu-welcome-panel {
  background: var(--zu-surface);
  border: 1px solid var(--zu-border);
  border-radius: 10px;
  padding: 1.75rem 2.25rem;
  margin-bottom: 2.5rem;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
  border-left: 4px solid var(--zu-primary);
}
.zu-welcome-panel .zu-welcome-name {
  font-size: 1.55rem;
  color: var(--zu-primary-darker);
  font-weight: 700;
  margin: 0 0 0.35rem;
  letter-spacing: -0.01em;
}
.zu-welcome-panel .zu-welcome-meta {
  color: var(--zu-text-muted);
  font-size: 1rem;
}

.zu-modules-section h2 {
  text-align: center;
  margin-bottom: 1.75rem;
}
.zu-modules-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: 1.5rem;
}
.zu-module-card {
  background: var(--zu-surface);
  border: 1.5px solid var(--zu-border);
  border-radius: 10px;
  padding: 2rem 1.75rem;
  cursor: pointer;
  transition: border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  position: relative;
  animation: ctFadeIn 260ms ease-out;
  /* Reset button defaults when rendered as <button> for keyboard access */
  font: inherit;
  color: inherit;
  text-align: left;
  width: 100%;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
}
.zu-module-card:hover {
  border-color: var(--zu-primary);
  transform: translateY(-4px);
  box-shadow: 0 14px 28px rgba(30, 64, 175, 0.15);
}
.zu-module-card:active { transform: translateY(-1px); }
.zu-module-card:focus-visible {
  outline: 3px solid var(--zu-focus);
  outline-offset: 3px;
  border-color: var(--zu-primary);
}
.zu-module-card h3 {
  color: var(--zu-primary);
  margin: 0;
  font-size: 1.5rem;
}
.zu-module-card .zu-module-duration {
  color: var(--zu-text-muted);
  font-size: 1rem;
  font-weight: 500;
}
/* YADAK module cards show the frozen test version (chosen on welcome). */
.zu-module-form {
  margin-top: 0.4rem;
  display: inline-block;
  align-self: flex-start;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: var(--zu-primary);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.zu-module-card--complete {
  background: #f0fdf4;
  border-color: var(--zu-success);
  cursor: default;
}
.zu-module-card--complete h3 { color: var(--zu-success); }
.zu-module-card--complete::after {
  content: "\2713";
  position: absolute;
  top: 1.25rem;
  right: 1.5rem;
  font-size: 1.8rem;
  color: var(--zu-success);
  font-weight: 900;
}
.zu-module-card--complete:hover { transform: none; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); }
/* Locked module cards (medical single-subject mode — once any subject is
 * submitted, all others are locked). Mirrors the --complete treatment:
 * de-emphasize visually, kill the hover/click affordance. Was previously
 * applied via inline styles in dashboard.js; centralising here keeps
 * the module-card state variants discoverable in one place. */
.zu-module-card--locked { opacity: 0.4; cursor: not-allowed; }
.zu-module-card--locked:hover { transform: none; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); }
/* Sequence-locked (out-of-order) card: still tappable so a press explains the
   order, so it reads a touch stronger than the fully-inert medical lock and
   keeps a pointer cursor. */
.zu-module-card--seq-locked { opacity: 0.55; cursor: pointer; }
.zu-module-card--seq-locked .zu-module-locked-hint { color: var(--zu-warning, #b45309); font-weight: 600; }
/* Out-of-order tap message, above the module grid. */
.zu-seq-message {
  margin: 0 0 1rem; padding: 0.7rem 1rem;
  background: #fef3c7; border: 1.5px solid #f59e0b; border-radius: 8px;
  color: #92400e; font-weight: 600; text-align: center;
}

.zu-completion-banner {
  background: #f0fdf4;
  border: 2px solid var(--zu-success);
  border-radius: 6px;
  padding: 2.5rem 2rem;
  text-align: center;
}
.zu-completion-banner .zu-check {
  display: inline-block;
  font-size: 3rem;
  color: var(--zu-success);
  margin-bottom: 0.5rem;
}
.zu-completion-banner h2 {
  color: var(--zu-success);
  margin: 0 0 0.5rem;
}
.zu-completion-banner p {
  color: var(--zu-text-muted);
  margin: 0;
}
/* Results-code box on the completion banner — the candidate notes this code
   and reads their results later at your results portal/report.html. */
.zu-report-code {
  margin: 1.5rem auto 0;
  max-width: 340px;
  background: #ffffff;
  border: 2px dashed var(--zu-success, #16a34a);
  border-radius: 10px;
  padding: 1rem 1.25rem;
}
.zu-report-code__label {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--zu-text-muted) !important;
  margin: 0 0 0.35rem !important;
}
.zu-report-code__value {
  font-family: 'Courier New', monospace;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.25em;
  color: #166534 !important;
  margin: 0 0 0.35rem !important;
}
.zu-report-code__hint {
  font-size: 0.8rem;
  color: var(--zu-text-muted) !important;
  margin: 0 !important;
}

/* Admin-page table */
.zu-admin-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--zu-surface);
  margin-top: 1.5rem;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
  border: 1px solid var(--zu-border);
}
.zu-admin-table th,
.zu-admin-table td {
  text-align: left;
  padding: 0.85rem 1.1rem;
  border-bottom: 1px solid var(--zu-border);
  font-size: 1rem;
}
.zu-admin-table th {
  background: var(--zu-primary);
  color: #fff;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-size: 0.85rem;
}
.zu-admin-table th[data-sort-key] {
  cursor: pointer;
  user-select: none;
  position: relative;
  padding-right: 1.6rem;
}
.zu-admin-table th[data-sort-key]:hover { background: var(--zu-primary-strong); }
.zu-admin-table th[data-sort-key]:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -3px;
}
.zu-admin-table th[data-sort-key]::after {
  content: '';
  position: absolute;
  right: 0.6rem;
  top: 50%;
  width: 0.55rem;
  height: 0.55rem;
  margin-top: -0.275rem;
  opacity: 0.45;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(-45deg, transparent 50%, currentColor 50%);
  background-size: 0.275rem 0.275rem;
  background-position: 0 0, 0.275rem 0;
  background-repeat: no-repeat;
}
.zu-admin-table th[aria-sort="ascending"]::after {
  opacity: 1;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%);
  background-size: 0.55rem 0.55rem;
  background-position: 0 0;
}
.zu-admin-table th[aria-sort="descending"]::after {
  opacity: 1;
  background-image: linear-gradient(-45deg, transparent 50%, currentColor 50%);
  background-size: 0.55rem 0.55rem;
  background-position: 0 0;
}
.zu-admin-table tr:hover td { background: var(--zu-primary-faint); cursor: pointer; }
/* Keyboard focus ring for list rows (tabindex=0, role=button in admin.js).
 * Scoped to #rows-body so the detail view's static rows don't get a focus
 * outline when the detail wrapper is tabbed into. */
#rows-body tr:focus { outline: none; }
#rows-body tr:focus-visible td { background: var(--zu-primary-faint); box-shadow: inset 0 0 0 2px var(--zu-primary); }
.zu-admin-table tr.zu-row-correct td { background: #f0fdf4; }
.zu-admin-table tr.zu-row-wrong td { background: #fef2f2; }
.zu-admin-table tr.zu-row-blank td { background: var(--zu-cream-soft); color: var(--zu-text-muted); }
/* Left-border colour strip — fast visual scan for correct/wrong/blank */
.zu-admin-table tr.zu-row-correct td:first-child { border-left: 3px solid var(--zu-success); }
.zu-admin-table tr.zu-row-wrong td:first-child { border-left: 3px solid var(--zu-danger); }
.zu-admin-table tr.zu-row-blank td:first-child { border-left: 3px solid var(--zu-border-strong); }
.zu-admin-table tr.zu-row-pending td { background: #fffbeb; }
.zu-admin-table tr.zu-row-pending td:first-child { border-left: 3px solid #d97706; }
.zu-admin-table tr.zu-row-correct:hover td,
.zu-admin-table tr.zu-row-wrong:hover td,
.zu-admin-table tr.zu-row-blank:hover td,
.zu-admin-table tr.zu-row-pending:hover td { background: var(--zu-primary-faint); cursor: default; }
.zu-writing-cell { vertical-align: top; }
.zu-writing-text {
  white-space: pre-wrap;
  max-height: 200px;
  overflow-y: auto;
  font-size: 0.85rem;
  line-height: 1.5;
  padding: 0.5rem;
  background: #fefce8;
  border-radius: 4px;
  border: 1px solid #fde68a;
}
.zu-toolbar { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; flex-wrap: wrap; align-items: center; }
.zu-network-banner {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  padding: 0.75rem 1rem;
  background: #ecfeff;
  border: 1.5px solid #67e8f9;
  border-radius: 8px;
  color: #0e7490;
}
/* Warning variant: results held locally, not yet uploaded to the server. */
.zu-mirror-banner {
  background: #fef3c7;
  border-color: #f59e0b;
  color: #92400e;
}
.zu-network-banner__label { font-weight: 600; }
.zu-network-banner__url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.05rem;
  padding: 0.2rem 0.5rem;
  background: #fff;
  border: 1px solid #a5f3fc;
  border-radius: 4px;
  color: #0c4a6e;
  user-select: all;
}
.zu-btn--small { padding: 0.35rem 0.75rem; font-size: 0.85rem; }
.zu-search-input {
  font-family: inherit;
  font-size: 0.95rem;
  padding: 0.6rem 1rem;
  border: 1.5px solid var(--zu-border-strong);
  border-radius: 6px;
  background: #fff;
  color: var(--zu-text);
  flex: 1;
  min-width: 180px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.zu-search-input:focus {
  outline: none;
  border-color: var(--zu-primary);
  box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.12);
}
.zu-filter-select {
  font-family: inherit;
  font-size: 0.9rem;
  padding: 0.55rem 0.75rem;
  border: 1.5px solid var(--zu-border-strong);
  border-radius: 6px;
  background: #fff;
  color: var(--zu-text);
  cursor: pointer;
}
.zu-filter-select:focus {
  outline: none;
  border-color: var(--zu-primary);
}

/* Admin table gets wrapped in this container so the table scrolls
 * horizontally at narrow widths instead of pushing the entire <body>
 * into overflow. Without this, a 6-column table + long date strings
 * blow out the viewport to ~660px on a 375px mobile device. */
.zu-admin-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--zu-border-strong) transparent;
  border-radius: 8px;
  margin-top: 1.5rem;
}
.zu-admin-table-wrap::-webkit-scrollbar { height: 8px; }
.zu-admin-table-wrap::-webkit-scrollbar-track { background: transparent; }
.zu-admin-table-wrap::-webkit-scrollbar-thumb {
  background: var(--zu-border-strong);
  border-radius: 4px;
}
/* Give the table a minimum width so columns stay readable; the wrap
 * handles the overflow. */
.zu-admin-table-wrap .zu-admin-table {
  min-width: 640px;
  margin-top: 0;
}
.zu-empty-state {
  text-align: center;
  padding: 3rem 2rem;
  color: var(--zu-text-muted);
  background: #fff;
  border: 1px dashed var(--zu-border);
  border-radius: 6px;
  margin-top: 1rem;
}
.zu-empty-state p { margin: 0.25rem 0; }
.zu-empty-state p:first-child { color: var(--zu-primary-dark); font-size: 1.1rem; }
.zu-empty-state-actions {
  margin-top: 1.5rem;
  display: inline-flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
}

/* done.html — the "Please wait for your invigilator" instruction. Was an
 * inline style; now a class so all visual rules live in one place. */
.zu-done-instruction {
  text-align: center;
  font-size: 1.1rem;
  color: var(--zu-text-muted);
  margin: 0;
}

/* admin.html — submission detail body wrapper. Was an inline style. */
.zu-detail-body {
  margin-top: 1rem;
}

/* Detail view — summary stats bar */
.zu-detail-stats {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin: 1rem 0 0.75rem;
  padding: 1rem 1.25rem;
  background: var(--zu-surface);
  border: 1px solid var(--zu-border);
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
}
.zu-stat {
  display: flex;
  flex-direction: column;
  min-width: 80px;
}
.zu-stat-value {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--zu-primary-darker);
  line-height: 1.2;
}
.zu-stat-label {
  font-size: 0.82rem;
  color: var(--zu-text-muted);
  font-weight: 500;
  margin-top: 0.15rem;
}
.zu-detail-time {
  font-size: 0.9rem;
  color: var(--zu-text-muted);
  margin: 0.5rem 0 0;
}

/* Part-level section headers in the per-question table.
 * Lighter tint than the column header so they read as section dividers,
 * not duplicate headers. */
.zu-part-header td {
  background: var(--ct-teal-soft);
  color: var(--zu-primary-darker);
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  padding: 0.55rem 1.1rem;
  border-bottom: 1px solid #bfdbfe;
  border-top: 2px solid var(--zu-primary);
}
/* Remove hover on part headers — they're not clickable */
.zu-admin-table tr.zu-part-header:hover td {
  background: var(--ct-teal-soft);
  cursor: default;
}

/* Detail table — slightly smaller type column for long labels */
.zu-admin-table--detail td:nth-child(2) {
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Correct answer cell — allow wrapping but limit max-width for KWT overflow */
.zu-correct-cell {
  max-width: 320px;
  word-break: break-word;
}

/* Bottom "Back to list" — mirrors the top button at bottom of long tables */
.zu-detail-bottom-back {
  margin-top: 1.25rem;
}

/* Loading placeholder while fetching a submission detail */
.zu-loading {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--zu-text-muted);
  font-size: 1.05rem;
}
.zu-loading--error {
  color: var(--zu-danger);
}

/* List view — percentage badge next to score */
.zu-pct {
  font-size: 0.85rem;
  font-weight: 500;
}
/* Score ranges: red <30%, amber 30-69%, green ≥70% */
.zu-pct--low { color: var(--zu-danger); }
.zu-pct--mid { color: #b45309; }
.zu-pct--high { color: var(--zu-success); }

/* ============================================================ */
/* TEST PAGE — Cambridge-authentic chrome                        */
/* ============================================================ */

body.zu-test-body {
  background: var(--ct-bg);
  color: var(--ct-text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 21px;
  line-height: 1.6;
  padding-bottom: 88px; /* space for bottom nav */
  min-height: 100vh;
  /* Reserve space at scroll boundaries so the sticky header and fixed
     bottom nav don't clip the caret / focused element during auto-scroll
     (the browser doesn't know they overlay the viewport edges). */
  scroll-padding-top: 72px;
  scroll-padding-bottom: 88px;
}
html:has(body.zu-test-body) {
  scroll-padding-top: 72px;
  scroll-padding-bottom: 88px;
}
/* The generic `html, body` rule at the top of this file sets a cream
 * background on BOTH elements (for the welcome/dashboard pages). The
 * body-level override switches body to white, but <html>'s cream still
 * shows around the viewport padding. This rule ensures that while a
 * test page is open, the html root is also white so the canvas reads
 * as a solid Cambridge-authentic white sheet. */
body.zu-test-body,
html:has(body.zu-test-body) {
  background: var(--ct-bg);
}
/* Sticky header gets its own solid background to cover scrolling content */
body.zu-test-body .ct-header {
  background: var(--ct-surface);
}

/* Test runner headings — use Inter (sans) matching body text for a modern
 * consistent look, bumped up so passage titles actually read as titles. */
body.zu-test-body h1,
body.zu-test-body h2,
body.zu-test-body h3,
body.zu-test-body h4 {
  color: var(--ct-text);
  font-family: inherit;
  font-weight: 700;
  line-height: 1.3;
  margin: 0 0 14px;
  letter-spacing: -0.01em;
}
body.zu-test-body h1 { font-size: 28px; }
body.zu-test-body h2 { font-size: 25px; }
body.zu-test-body h3 { font-size: 23px; }
body.zu-test-body h4 { font-size: 20px; }

/* Consistent teal focus rings across the test runner — replaces the
 * browser-default focus outlines (which vary by OS). Any keyboard-
 * focusable element inside zu-test-body gets the same treatment. */
body.zu-test-body *:focus-visible {
  outline: 2px solid var(--ct-teal);
  outline-offset: 2px;
  border-radius: 2px;
}
body.zu-test-body button:focus-visible,
body.zu-test-body .ct-nav-num:focus-visible,
body.zu-test-body .ct-nav-arrow:focus-visible,
body.zu-test-body .ct-nav-finish:focus-visible {
  outline-offset: 1px;
}

/* Top header strip — full-width background with a content column
 * centered at 1400px max-width. The `max(28px, ...)` padding trick
 * keeps the content aligned with .ct-main and .ct-banner at wide
 * viewports without needing an inner wrapper div. */
.ct-header {
  display: flex;
  align-items: center;
  padding-block: 14px;
  padding-inline: max(28px, calc((100% - 1600px) / 2 + 28px));
  background: var(--ct-bg);
  border-bottom: 1px solid var(--ct-border);
  position: sticky;
  top: 0;
  z-index: 40;
  min-height: 72px;
  gap: 24px;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
}
/* Brand: logo on top, sub-label directly below (matches Cambridge's shield +
 * "English" stack in cae/examples/*.png). */
.ct-header-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.05;
  gap: 2px;
}
.ct-logo-ielts { display: none; }
.ct-logo {
  height: 44px;
  width: auto;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}
.ct-brand-sub {
  font-family: inherit;
  font-size: 13px;
  color: var(--zu-primary);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
}
.ct-header-id {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  /* min-width: 0 lets text-overflow: ellipsis work inside a flex item.
   * max-width prevents a 60+ char "Alexander Maximilian..." student name
   * from dominating the header and squeezing the timer. */
  min-width: 0;
  max-width: 280px;
  flex-shrink: 1;
}
.ct-header-id-label {
  font-size: 12px;
  color: var(--ct-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.ct-header-id-value {
  font-size: 16px;
  font-weight: 700;
  color: var(--ct-text);
  /* Ellipsis truncation for long names. The title attribute on the same
   * element (set from test.js) carries the full name for hover. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
  max-width: 100%;
}
/* Session-fragment code displayed below the name. Was an inline cssText
 * on a dynamically-created <small>; moved to a class so chrome lives
 * in the stylesheet. */
.ct-header-id-code {
  display: block;
  /* Match the candidate name (.ct-header-id-value) — same size, weight and
   * colour — so the Test Taker ID below the name reads as an equal-weight
   * identifier, not a muted micro-subtitle. */
  font-weight: 700;
  font-size: 16px;
  color: var(--ct-text);
  letter-spacing: 0.02em;
  text-transform: none;
  white-space: nowrap;
}
.ct-header-audio {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* ===== Listening volume control (client feedback F001) =====
   Mute toggle + level slider, shown only on listening parts. Sits beside the
   "Audio is playing" status in the header. Level/mute only — strict listening
   still forbids pause and rewind, so there is no transport control here. */
.ct-audio-volume {
  display: flex;
  align-items: center;
  gap: 6px;
}
.ct-audio-volume[hidden] { display: none; }
.ct-vol-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: #333;
  cursor: pointer;
}
.ct-vol-btn:hover { background: rgba(0, 0, 0, 0.06); }
.ct-vol-btn:focus-visible { outline: 2px solid var(--ct-teal); outline-offset: 1px; }
/* Exactly one of the two glyphs shows, driven by the --muted state class. */
.ct-vol-icon-off { display: none; }
.ct-audio-volume--muted .ct-vol-icon-on { display: none; }
.ct-audio-volume--muted .ct-vol-icon-off { display: block; }
.ct-vol-slider {
  width: 88px;
  height: 4px;
  padding: 0;
  border-radius: 2px;
  background: var(--ct-border-strong);
  accent-color: var(--ct-teal);
  cursor: pointer;
}
.ct-vol-slider:focus-visible { outline: 2px solid var(--ct-teal); outline-offset: 3px; }
@media (max-width: 880px) { .ct-vol-slider { width: 60px; } }
.ct-header-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}
/* Options hamburger button */
.ct-options-btn {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  width: 36px;
  height: 36px;
  background: none;
  border: 1.5px solid var(--ct-border-strong);
  border-radius: 6px;
  cursor: pointer;
  padding: 7px;
  transition: background 0.15s;
}
.ct-options-btn:hover { background: var(--ct-teal-soft); }

/* Options panel — slides in from the right */
/* Options is a FULL-SCREEN page (Inspera parity), not a right drawer:
   opaque white surface, centered title + × top-right, a red submission
   button, and Contrast / Text size grouped in one bordered card. */
.ct-options-overlay {
  position: fixed;
  inset: 0;
  background: #fff;
  z-index: 200;
  overflow-y: auto;
  animation: ctFadeIn 150ms ease-out;
}
.ct-options-panel {
  position: relative;
  width: 100%;
  min-height: 100vh;
  background: #fff;
  display: flex;
  flex-direction: column;
}
/* main-menu header + submenu back-header share the same centered chrome */
.ct-options-header,
.ct-options-back {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 26px 24px;
}
/* body.zu-test-body h2 forces weight 700 at specificity 0,1,2 — out-specify it */
body.zu-test-body .ct-options-header h2,
body.zu-test-body .ct-options-subheading {
  margin: 0;
  font-size: 24px;
  font-weight: 400;
  color: #1a1a1a;
  text-align: center;
  letter-spacing: normal;
}
.ct-options-close {
  position: absolute;
  right: 26px;
  top: 50%;
  transform: translateY(-50%);
  border: none;
  border-radius: 0;
  background: none;
  cursor: pointer;
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  color: #1a1a1a;
  padding: 4px 8px;
}
.ct-options-close:hover { color: #000; }
.ct-options-back-btn {
  position: absolute;
  left: 24px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  font-size: 24px;
  font-weight: 400;
  color: #1a1a1a;
  display: flex;
  align-items: center;
  gap: 6px;
}
.ct-options-back-btn:hover { color: #000; }
.ct-options-body {
  width: 100%;
  max-width: 700px;   /* Inspera's card renders exactly 700 wide at x=610 */
  margin: 0 auto;
  padding: 8px 0 40px;
  box-sizing: border-box;
}
/* Contrast + Text size grouped in one bordered card */
.ct-options-card {
  border: 1px solid #d5d8dc;
  border-radius: 6px;
  overflow: hidden;
}
/* The invigilator action sits in its own card, separated from the display-
   settings card above it. */
.ct-options-card--invig { margin-top: 16px; }
.ct-options-item {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 26.4px 44px 26.4px 35.2px;  /* Inspera row: 75.78px tall */
  border: none;
  border-bottom: 1px solid #e5e7eb;
  background: #fff;
  cursor: pointer;
  font-size: 17px;
  color: #1a1a1a;
  text-align: left;
}
.ct-options-card .ct-options-item:last-child { border-bottom: none; }
.ct-options-item:hover { background: #f0f0f0; }
.ct-options-item-icon {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  color: #555;
}
.ct-options-item-label { flex: 1; }
.ct-options-item-arrow { color: #1a1a1a; font-size: 20px; }

/* Contrast / Text size choice lists — bordered card, left checkmark,
   optional preview swatch on the right (Inspera parity). */
.ct-options-choice-list {
  border: 1px solid #d5d8dc;
  border-radius: 6px;
  overflow: hidden;
}
.ct-options-choice {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 26.4px 44px 26.4px 35.2px;  /* Inspera choice row */
  border: none;
  border-bottom: 1px solid #e5e7eb;
  background: #fff;
  cursor: pointer;
  font-size: 17px;
  color: #1a1a1a;
  text-align: left;
}
.ct-options-choice:last-child { border-bottom: none; }
.ct-options-choice:hover { background: #e8e8e8; }
.ct-options-choice-check {
  width: 22px;
  flex-shrink: 0;
  font-size: 19px;
  font-weight: 700;
  color: #1a1a1a;
  visibility: hidden;
}
.ct-options-choice--active .ct-options-choice-check { visibility: visible; }
.ct-options-choice-label { flex: 1; }
.ct-options-choice-swatch {
  width: 46px;
  height: 32px;
  border: 1px solid #999;
  border-radius: 3px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  padding: 6px 7px;
  box-sizing: border-box;
}
.ct-options-swatch-line { height: 2px; border-radius: 1px; }
.ct-swatch--bw { background: #fff; }
.ct-swatch--bw .ct-options-swatch-line { background: #1a1a1a; }
.ct-swatch--wb { background: #000; }
.ct-swatch--wb .ct-options-swatch-line { background: #fff; }
.ct-swatch--yb { background: #000; }
.ct-swatch--yb .ct-options-swatch-line { background: #ffff00; }

/* Text size classes on body — Regular (default) / Large / Extra large */
body.ct-text-large .ct-passage { font-size: 22px; }
body.ct-text-large .ct-q-prompt { font-size: 21px; }
body.ct-text-large .ct-q-option { font-size: 21px; }
body.ct-text-xlarge .ct-passage { font-size: 26px; }
body.ct-text-xlarge .ct-q-prompt { font-size: 25px; }
body.ct-text-xlarge .ct-q-option { font-size: 25px; }

/* Highlight & notes */
.ct-hl {
  background: #fff59d;
  border-radius: 2px;
  padding: 0 1px;
}
/* Overlapping highlights nest, and each layer deepens the colour, so the shared
   stretch reads darker than either highlight alone. Two levels is enough — past
   three the text stops being legible, so it clamps. */
.ct-hl .ct-hl { background: #ffcc80; }
.ct-hl .ct-hl .ct-hl,
.ct-hl .ct-hl .ct-hl .ct-hl { background: #ffab40; }
.ct-hl--note {
  background: #fff59d;
  border-bottom: 2px dotted #f57c00;
  cursor: help;
}
/* End-node icon: a note-carrying highlight ends with a small pencil marker, so a
   note is discoverable without hovering for the tooltip. Only the LAST segment
   of a highlight gets .ct-hl--end, so a note spanning several text nodes shows
   one marker, not one per fragment. */
.ct-hl--note.ct-hl--end::after {
  content: '\270E';           /* ✎ — matches the header's notes glyph */
  display: inline-block;
  margin-left: 3px;
  font-size: 0.85em;
  line-height: 1;
  color: #f57c00;
  vertical-align: baseline;
  /* Decorative marker — never part of a copy/selection of the passage. */
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/* Context menu for text selection */
.ct-context-menu {
  position: fixed;
  z-index: 300;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(15, 23, 42, 0.14);
  min-width: 160px;
  padding: 4px;
  animation: ctFadeIn 120ms ease-out;
}
.ct-ctx-item {
  padding: 10px 14px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ct-text);
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.1s;
}
.ct-ctx-item:hover { background: #f3f4f6; }
.ct-ctx-item--danger { color: #dc2626; }
.ct-ctx-item--danger:hover { background: #fef2f2; }

.ct-audio-status {
  display: none;
  align-items: center;
  gap: 8px;
  color: var(--ct-teal);
  font-size: 15px;
  font-weight: 600;
}
.ct-audio-status.ct-audio-status--visible .ct-speaker-icon {
  animation: ctAudioPulse 1.6s ease-in-out infinite;
}
@keyframes ctAudioPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
.ct-audio-status.ct-audio-status--visible { display: flex; }
.ct-audio-status .ct-speaker-icon {
  width: 18px;
  height: 18px;
  display: inline-block;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4zm-2.5-9v2.06c2.84.87 5 3.54 5 6.94s-2.16 6.07-5 6.94V21c4.01-.91 7-4.49 7-8.5s-2.99-7.59-7-8.5z'/></svg>") center/contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4zm-2.5-9v2.06c2.84.87 5 3.54 5 6.94s-2.16 6.07-5 6.94V21c4.01-.91 7-4.49 7-8.5s-2.99-7.59-7-8.5z'/></svg>") center/contain no-repeat;
}
.ct-timer {
  font-family: "SF Mono", "Menlo", "Consolas", "Courier New", monospace;
  font-size: 17px;
  font-weight: 700;
  color: var(--ct-text);
  padding: 8px 16px;
  border: 1.5px solid var(--ct-border-strong);
  border-radius: 6px;
  background: var(--ct-bg);
  min-width: 86px;
  text-align: center;
  letter-spacing: 0.02em;
  transition: color 0.2s, background 0.2s, border-color 0.2s;
  /* F004 — centre the countdown in the header (invigilator: "put timer in the
     middle"), matching the Inspera player. The header is position:sticky, so it
     is the containing block; pulling the timer out of the right-hand icon
     cluster and pinning it to 50% centres it independently of name length. The
     utility icons stay right-aligned via .ct-header-right's margin-left:auto. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
}
.ct-timer--warn {
  color: #92400e;
  background: #fef3c7;
  border-color: #f59e0b;
}
.ct-timer--urgent {
  color: var(--ct-error-text);
  background: var(--ct-error-bg);
  border-color: #dc2626;
  animation: ctTimerPulse 1s ease-in-out infinite;
}
@keyframes ctTimerPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); }
  50% { box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.18); }
}

/* Instruction banner — nested inside a max-width container to align with main.
 * The wrapper is sticky so the "Questions N-M" banner stays visible while the
 * student scrolls through a long passage. Stacks below the sticky header. */
.ct-banner-wrap {
  max-width: 1600px;
  margin: 0 auto;
  padding: 16px 40px 0;
  /* Not sticky — scrolls with content for a cleaner reading experience */
  position: relative;
  background: var(--ct-bg);
  z-index: 35;
}
.ct-banner {
  padding: 14px 22px;
  background: var(--ct-banner-bg);
  border: none;
  border-bottom: 1px solid var(--ct-border);
  border-left: 4px solid var(--ct-teal);
  border-radius: 0;
}
/* Error variant: when content load fails (test.js catch block sets
 * banner title to "Error"), the banner gets a crimson left border + a
 * crimson title color so the student immediately sees this isn't a
 * normal "Questions N-M" prompt. Previously the error banner looked
 * identical to a question banner — same blue left border, same title
 * styling — and the only visual signal was the word "Error". */
.ct-banner--error {
  border-left-color: var(--ct-error-text);
}
.ct-banner--error .ct-banner-title {
  color: var(--ct-error-text);
}
.ct-banner-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--ct-text);
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 4px;
}
/* Position counter ("2 / 3") next to the part title in the banner instead of
   floating top-right of the question card. Keeps the prompt anchored to its
   position indicator at a glance for medical multi-question parts. */
.ct-banner-counter {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--ct-text-muted);
  background: rgba(30, 64, 175, 0.08);
  padding: 3px 10px;
  border-radius: 999px;
  letter-spacing: 0.02em;
}
.ct-banner-body {
  font-size: 16px;
  color: var(--ct-text);
  line-height: 1.5;
}

/* Main content area */
.ct-main {
  max-width: 1600px;
  margin: 36px auto 0;
  padding: 0 40px;
  animation: ctFadeIn 200ms ease-out;
}
.ct-part { position: relative; }

@keyframes ctFadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Two-column layout (Parts 5, 6, 7, 8) */
.ct-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  /* align-items: start so each column sizes to its own content and doesn't
   * stretch to match the taller sibling — this lets the right column's
   * `position: sticky` work against the scrolling body. */
  align-items: start;
}
.ct-two-col--wide-left {
  grid-template-columns: 1.25fr 1fr;
}
/* Writing: give the Task 1 chart column extra width so the chart renders larger
   (client request). Collapses to one column at <=880px via the shared .ct-two-col
   rule in the stack media query below (this rule sits above it, so the media query
   wins on mobile). */
.ct-writing-two-col { grid-template-columns: 1.3fr 1fr; }
.ct-col { min-width: 0; }

/* The right column (questions) sticks to the viewport while the left
 * column (passage) scrolls. Real CAE reading tests have the student
 * reading a long passage while the answer options stay visible. */
.ct-two-col > .ct-col:nth-child(2) {
  position: sticky;
  top: 100px; /* clears the sticky header (~90px) + small gap */
  max-height: calc(100vh - 120px - 72px); /* 120 = header reserve, 72 = bottom nav */
  overflow-y: auto;
  align-self: start;
  /* Scroll affordance: force a visible thin scrollbar so Chrome/Edge
   * overlay scrollbars (which hide by default) don't disguise an
   * overflowing paragraph bank as "just 4 options". Real Part 7 has
   * 7 paragraphs, and at 1280x720 only ~4 fit the sticky window. */
  scrollbar-width: thin;
  scrollbar-color: var(--ct-border-strong) transparent;
}
.ct-two-col > .ct-col:nth-child(2)::-webkit-scrollbar {
  width: 10px;
}
.ct-two-col > .ct-col:nth-child(2)::-webkit-scrollbar-track {
  background: transparent;
}
.ct-two-col > .ct-col:nth-child(2)::-webkit-scrollbar-thumb {
  background-color: var(--ct-border-strong);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.ct-two-col > .ct-col:nth-child(2)::-webkit-scrollbar-thumb:hover {
  background-color: var(--ct-inactive);
  background-clip: content-box;
}
/* Bottom fade — sits sticky at the bottom of the scrollable column so
 * it indicates "more below" whenever content overflows. The fade
 * blends into the bank's grey background. When scrolled to the bottom
 * the fade is still there but blends with the final card's bottom
 * margin, so it reads as just a subtle edge. */
.ct-two-col > .ct-col:nth-child(2)::after {
  content: '';
  display: block;
  position: sticky;
  bottom: 0;
  left: 0;
  right: 0;
  height: 28px;
  margin-top: -28px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(243, 244, 246, 0) 0%, var(--ct-banner-bg) 85%);
  z-index: 5;
}
/* At narrow widths the two-col collapses to one column and sticky is
 * pointless — reset it so the question simply flows below the passage.
 * The grid-template-columns override IS the collapse; previously the
 * comment claimed the collapse but only the sticky reset was applied,
 * leaving passage + questions crammed into 50%-each columns (unreadable
 * at 375px, ~175px per column). Now the grid actually stacks. */
@media (max-width: 880px) {
  .ct-two-col,
  .ct-two-col--wide-left {
    grid-template-columns: 1fr;
  }
  .ct-two-col > .ct-col:nth-child(2),
  .ct-listen-two-col > .ct-col:nth-child(1),
  .ct-writing-two-col > .ct-col:nth-child(1) {
    position: static;
    max-height: none;
    overflow-y: visible;
  }
  .ct-two-col > .ct-col:nth-child(2)::after {
    display: none;
  }
}

/* Listening map + Writing stimulus: pin the LEFT column (map / chart) so it
   stays in view while the RIGHT column (questions / answer) scrolls with the
   body — a listener/writer must keep the media visible while answering. This
   is the mirror image of reading (where the questions stick and the passage
   scrolls). Above the 880px stack breakpoint only. */
.ct-listen-two-col > .ct-col:nth-child(2),
.ct-writing-two-col > .ct-col:nth-child(2) {
  position: static;
  max-height: none;
  overflow-y: visible;
}
/* The Writing answer column carries the .ct-two-col class (for shared column
   plumbing) and therefore inherited the reading passage's "more content below"
   bottom-fade — a 28px grey gradient strip that painted under the answer box.
   Inspera's answer column is flat: the box, then a plain "Words: N" beneath it,
   with no fade. The fade only makes sense on a scrolling passage, not the
   fixed-height writing field, so kill it here (and on the listening media col). */
.ct-writing-two-col > .ct-col:nth-child(2)::after,
.ct-listen-two-col > .ct-col:nth-child(2)::after {
  display: none;
}
.ct-listen-two-col > .ct-col:nth-child(1),
.ct-writing-two-col > .ct-col:nth-child(1) {
  position: sticky;
  top: 100px;
  align-self: start;
  max-height: calc(100vh - 120px - 72px);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--ct-border-strong) transparent;
}
/* Writing: FIXED height so the vertical divider is EXACTLY the same length in
   BOTH tasks, regardless of content. min-height was only a floor — a tall Task 1
   chart would still grow the row past Task 2's. A fixed height pins the divider
   identically; a tall chart scrolls inside its (capped) left column instead of
   stretching the row. Matches Inspera's always-full-height divider and makes
   Task 1 and Task 2 identical except the question content. */
/* Two classes (.ct-two-col.ct-writing-two-col) so this FIXED height beats the
   generic `body.ielts-exam .ct-two-col { height: 100% }` full-height model
   (equal specificity, defined later) — otherwise height:100% collapses to
   content on the writing shell and the divider tracks the chart again. */
/* The answer box now GROWS as the candidate writes (client request) instead of
   scrolling inside a fixed 188px field, so the row height must follow content:
   height:auto lets it expand and the body scroll, while the chart column stays
   sticky (pinned in view) below. (The old fixed calc(100vh-250px) kept the resize
   divider identically tall in both tasks — that Inspera-parity nicety is traded
   for the grow-to-fit box the client asked for.) min-height keeps a comfortable
   area for a short or empty answer. */
body.ielts-exam .ct-two-col.ct-writing-two-col { height: auto; min-height: calc(100vh - 250px); }
/* Cap the Task 1 chart column so an oversized chart scrolls within its sticky
   window rather than stretching the grid row. */
body.ielts-exam .ct-two-col.ct-writing-two-col > .ct-col:nth-child(1) { max-height: calc(100vh - 250px); }
/* Answer column: a flex column so the fixed 188px answer box pins to the TOP
   with the word counter directly beneath it — matching Inspera exactly (the box
   is a fixed 188px field, not a full-height box). The textarea keeps its own
   188px height from the writing-textarea rule below; do NOT let it flex-grow. */
body.ielts-exam .ct-writing-answer-col {
  display: flex;
  flex-direction: column;
}
body.ielts-exam .ct-writing-answer-col .ct-writing-textarea {
  margin-top: 0;
}
/* Client QA: the map used the full column height and dominated the whole part.
   It's a reference the candidate glances at, not the task — cap it to a sensible
   size so it fits comfortably beside the questions without swallowing the pane. */
.ct-listen-two-col .ct-listen-map-img { max-height: 48vh; }
.ct-listen-two-col .ct-listen-map { max-width: 440px; }
/* Inspera 1:1 — the Task 1 chart is a mid-size figure in the upper-left (measured
   576×422 on the demo), and a BARE image: no border, no radius, no card. Sized to
   the demo's scale (fits on one screen, no scrolling) rather than the old
   full-height image. */
.ct-writing-two-col .ct-writing-images .ct-writing-image { max-height: 560px; max-width: 720px; }
body.ielts-exam .ct-writing-image {
  border: none;
  border-radius: 0;
  background: transparent;
}
/* Inspera shows no "Click to enlarge" caption — the chart is a plain figure. */
body.ielts-exam .ct-writing-image-hint { display: none; }

/* Client QA F011: "Picture must be nearer to the middle" — the chart was flush
   left under a full-width prompt, leaving a wide empty gutter on its right.
   Centring it within the stimulus column closes that gap without changing the
   measured Inspera figure size above. */
body.ielts-exam .ct-writing-two-col .ct-writing-images {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Client QA F010 (6 mentions, 5 documents): Task 1's standard rubric —
   "Summarise the information by selecting and reporting the main features, and
   make comparisons where relevant." — is its own line UNDER the chart on
   Inspera, set apart from the task-specific lead-in above. Our content ships
   both as one run-on paragraph; renderWritingPart() splits them and this styles
   the second half. Medium weight (not bold) keeps it a rubric, not a heading. */
body.ielts-exam .ct-writing-rubric {
  /* Inspera renders the "Summarise…" rubric identically to the lead-in — a bold
     black 16px paragraph, one blank line under the question and above the chart.
     Match that exactly (was weight 500 / muted, sitting below the chart). */
  margin: 14px 0 18px;
  font-size: 16px;
  line-height: 1.5;
  font-weight: 700;
  color: #000;
}
body.ielts-exam.ct-text-large .ct-writing-rubric { font-size: 19px; }
body.ielts-exam.ct-text-xlarge .ct-writing-rubric { font-size: 22px; }
body.ielts-exam.ct-contrast .ct-writing-rubric { color: var(--cc-fg); }

/* Passage */
.ct-passage {
  font-size: 20px;
  line-height: 1.7;
  color: var(--ct-text);
  white-space: pre-wrap;
  max-width: none; /* let passage fill available width */
}
/* Inside a two-col layout the column width already constrains things */
.ct-two-col .ct-passage { max-width: none; }
.ct-passage h3 {
  font-family: Georgia, serif;
  color: var(--ct-text);
  font-size: 19px;
  margin: 0 0 12px;
}
/* Multiple-matching / lettered-section subheadings ("A — Joe Smedley").
   Rendered as a real heading block instead of inline bold so the passage's
   section structure reads at a glance. */
.ct-passage-subhead {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 20px 0 8px;
  font-family: Georgia, serif;
}
.ct-passage-subhead:first-child { margin-top: 0; }
.ct-passage-subhead-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7em;
  height: 1.7em;
  padding: 0 0.25em;
  border: 1.5px solid var(--ct-border);
  border-radius: 4px;
  font-weight: 700;
  font-size: 0.85em;
  color: var(--ct-text);
}
.ct-passage-subhead-name {
  font-size: 1.05em;
  font-weight: 700;
  color: var(--ct-text);
}

/* Keyword list (Part 3) */
.ct-keyword-list {
  background: var(--ct-banner-bg);
  border: 1px solid var(--ct-border);
  border-radius: 4px;
  padding: 14px 18px;
  font-size: 13px;
}
.ct-keyword-list h4 {
  font-size: 13px;
  font-weight: 700;
  margin: 0 0 10px;
  color: var(--ct-text);
}
.ct-keyword-list ol {
  list-style: none;
  padding: 0;
  margin: 0;
}
.ct-keyword-list li {
  padding: 6px 10px;
  margin: 0 -10px;
  display: flex;
  gap: 10px;
  cursor: pointer;
  border-radius: 2px;
  transition: background 0.12s;
}
.ct-keyword-list li:hover { background: var(--ct-teal-soft); }
.ct-keyword-list .ct-kw-active {
  background: var(--ct-teal-soft);
}
.ct-keyword-list .ct-kw-active .ct-kw-num,
.ct-keyword-list .ct-kw-active .ct-kw-word {
  color: var(--ct-teal-dark);
}
.ct-keyword-list .ct-kw-num { color: var(--ct-text-muted); min-width: 22px; }
.ct-keyword-list .ct-kw-word {
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* Inline gap input */
.ct-gap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 0 3px;
}
.ct-gap-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  background: var(--ct-bg);
  border: 1px solid var(--ct-border-strong);
  border-right: none;
  font-size: 12px;
  color: var(--ct-text);
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
  font-family: Arial, sans-serif;
}
.ct-gap-input {
  font-family: Arial, sans-serif;
  font-size: 17px;
  padding: 2px 8px;
  border: 1px solid var(--ct-border-strong);
  background: #fff;
  color: var(--ct-text);
  min-width: 160px;
  height: 28px;
  border-radius: 0;
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
}
.ct-gap-input:focus {
  outline: none;
  border-color: var(--ct-teal);
  box-shadow: 0 0 0 1px var(--ct-teal);
}
/* IELTS reading standalone gap (note/summary/short-answer completion): the
 * input sits on its own line under the prompt, so it reads left-to-right as a
 * full field rather than the inline pill used by CAE open-cloze. */
.ct-gap-input--block {
  display: block;
  margin-top: 6px;
  width: 100%;
  max-width: 360px;
  border-radius: 3px;
}
/* IELTS listening map/plan labelling — the map image sits above the questions. */
.ct-listen-map {
  margin: 0 0 18px;
  border: 1px solid var(--ct-border-strong, #ccc);
  border-radius: 4px;
  padding: 8px;
  background: #fff;
  overflow-x: auto;
}
.ct-listen-map-img {
  display: block;
  max-width: 100%;
  /* Walk F010 — cap the map/plan height so the labelling questions below it
     stay reachable. Without this the map (narrower than the content column, so
     max-width:100% never fires) renders at full natural height (~1093px) and
     buries the questions ~1.6 screens down. Aspect ratio is preserved. */
  max-height: 55vh;
  height: auto;
  width: auto;
  margin: 0 auto;
  cursor: zoom-in;   /* click-to-enlarge, matching other images (G08) */
}

/* IELTS "Choose TWO/THREE letters" helper line under the checkbox group. */
.ct-q-hint {
  font-size: 14px;
  color: var(--ct-text-muted, #666);
  margin-top: 6px;
  font-style: italic;
}
.ct-q-option input:disabled + .ct-q-option-key { opacity: 0.4; }

/* Per-group instruction heading inside a multi-group IELTS reading part
 * ("Questions 5–10 · Complete the notes below · Choose ONE WORD…"). Visually
 * separates each question group within one passage's right column. */
.ct-q-group-intro {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ct-text-muted, #555);
  white-space: pre-line;
  padding: 10px 14px;
  margin-top: 4px;
  border-left: 3px solid var(--ct-teal);
  background: var(--ct-teal-soft);
  border-radius: 2px;
}

/* Structured completion (intent-ielts-completion-rendering) — the note/summary/
   table/flow-chart body renders ONCE with each input inline at its blank, no
   more banner-blob + detached bare-number rows (H01). */
.ct-completion { margin: 6px 0 14px; }
.ct-completion-rubric {
  font-size: 14px;
  line-height: 1.5;
  color: var(--ct-text-muted, #555);
  padding: 8px 12px;
  border-left: 3px solid var(--ct-teal);
  background: var(--ct-teal-soft);
  border-radius: 2px;
  margin-bottom: 10px;
}
.ct-completion-body {
  font-size: 16px;
  line-height: 2.1;           /* room for the inline inputs to sit on the text line */
  color: var(--ct-text, #1a1a1a);
}
/* Inline gap: a boxed number badge + the field, kept on one line. The badge
   matches .ct-q-num-badge so every question number reads the same across the
   exam (boxed), not a mix of superscripts and boxes. */
.ct-inline-gap { white-space: nowrap; }
.ct-inline-gap-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  margin: 0 6px 0 2px;
  background: var(--ct-bg);
  border: 1px solid var(--ct-border-strong);
  border-radius: 2px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ct-text);
  vertical-align: middle;
  font-family: Arial, sans-serif;
}
.ct-inline-input {
  min-width: 56px;
  max-width: 140px;
  margin: 0 5px;              /* keep adjacent gap boxes from touching */
  padding: 1px 6px;
  border: none;
  border-bottom: 2px solid var(--ct-teal);
  background: var(--ct-teal-soft);
  border-radius: 3px 3px 0 0;
  font: inherit;
  color: inherit;
}
.ct-inline-input:focus { outline: none; border-bottom-color: var(--ct-teal); box-shadow: 0 2px 0 -1px var(--ct-teal); background: #fff; }
.ct-inline-select {
  padding: 2px 4px;
  border: 1px solid var(--ct-teal);
  border-radius: 4px;
  background: #fff;
  font: inherit;
  color: inherit;
  vertical-align: baseline;
  margin: 0 2px;
}
.ct-inline-select:focus { outline: none; box-shadow: 0 0 0 2px var(--ct-teal-soft); }

/* Drag-and-drop word bank (visible options + drag-to-place). A blank in a
   word/letter-bank completion is a drop target the candidate drags a word into,
   replacing the hidden "— choose —" dropdown. */
.ct-inline-drop {
  display: inline-flex;
  align-items: center;
  min-width: 92px;
  min-height: 26px;
  margin: 0 4px;
  padding: 1px 8px;
  border: 1.5px dashed var(--ct-teal);
  border-radius: 4px;
  background: var(--ct-teal-soft);
  vertical-align: middle;
  cursor: pointer;
  font: inherit;
  color: inherit;
  white-space: normal;
}
.ct-inline-drop:focus { outline: none; box-shadow: 0 0 0 2px var(--ct-teal); }
.ct-inline-drop-hint { color: var(--ct-muted, #7a8896); font-style: italic; font-size: 13px; }
.ct-inline-drop-val { font-weight: 600; }
.ct-inline-drop--filled {
  border-style: solid;
  background: #fff;
}
.ct-inline-drop--over {
  border-color: var(--ct-teal);
  background: var(--ct-teal-soft);
  box-shadow: 0 0 0 2px var(--ct-teal);
}
.ct-inline-drop--filled .ct-inline-drop-hint { display: none; }
.ct-inline-drop:not(.ct-inline-drop--filled) .ct-inline-drop-val { display: none; }
/* Block variant used by the reuse-mode statement→letter matcher: a full-row
   drop box under each statement rather than an inline blank in prose. */
.ct-inline-drop--block {
  display: flex;
  min-height: 40px;
  margin: 6px 0 2px;
  padding: 8px 12px;
  font-size: 15px;
}

/* Reuse-mode matcher: statement rows above the shared letter bank. */
.ct-match--reuse .ct-match-dd-row { margin-bottom: 12px; }
.ct-match--reuse .ct-match-dd-head { margin-bottom: 2px; }

.ct-wordbank-wrap { margin: 16px 0 8px; }
.ct-wordbank-hint { font-size: 13px; color: var(--ct-muted, #7a8896); margin-bottom: 8px; }
.ct-wordbank {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--ct-border, #d7dde3);
  border-radius: 8px;
  background: #fbfcfd;
}
.ct-wordbank-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--ct-border-strong, #b7c0c9);
  border-radius: 6px;
  background: #fff;
  cursor: grab;
  font-size: 15px;
  line-height: 1.3;
  user-select: none;
  touch-action: none;                 /* let Pointer Events drive the drag on touch */
  transition: box-shadow .12s, border-color .12s, opacity .12s;
}
.ct-wordbank-chip:hover { border-color: var(--ct-teal); box-shadow: 0 1px 4px rgba(0,0,0,.12); }
.ct-wordbank-chip-key { font-weight: 700; color: var(--ct-teal); }
.ct-wordbank-chip--selected { border-color: var(--ct-teal); box-shadow: 0 0 0 2px var(--ct-teal-soft); }
.ct-wordbank-chip--used { opacity: .45; }
.ct-wordbank-chip--used .ct-wordbank-chip-key { color: var(--ct-muted, #7a8896); }
.ct-wordbank-chip--dragging { opacity: .35; }

/* High-contrast scheme: keep the bank/blanks legible on the dark background. */
body.ielts-exam.ct-contrast .ct-wordbank { background: #111; border-color: #666; }
body.ielts-exam.ct-contrast .ct-wordbank-chip { background: #000; color: #fff; border-color: #888; }
body.ielts-exam.ct-contrast .ct-wordbank-chip-key { color: #fff59d; }
body.ielts-exam.ct-contrast .ct-inline-drop { background: #000; color: #fff; border-color: #fff59d; }
body.ielts-exam.ct-contrast .ct-inline-drop--filled { background: #000; }

/* Static option reference list shown once above a matching group (P2/P3). */
.ct-reference-list {
  margin: 8px 0 12px;
  padding: 10px 14px;
  border: 1px solid var(--ct-border, #d7dde3);
  border-radius: 6px;
  background: #fbfcfd;
}
.ct-reference-item { display: flex; gap: 8px; align-items: baseline; padding: 2px 0; font-size: 15px; line-height: 1.45; }
.ct-reference-key { font-weight: 700; color: var(--ct-teal); min-width: 1.6em; }
.ct-reference-text { color: var(--ct-text, #1a1a1a); }
.ct-match-inline { line-height: 2.0; }

/* Question blocks (Parts 5, 6, 8 — two-column radio questions).
 * Active question gets a subtle BRAND-BLUE background (pulled from the
 * --ct-teal-soft token which now holds Zarmed brand blue-100 / #dbeafe
 * per the round 19b palette migration). Softer than a full border outline
 * — Cambridge's authentic look leans toward understated highlighting,
 * and the brand-aligned token keeps the whole test chrome consistent. */
.ct-question-list { display: flex; flex-direction: column; gap: 18px; padding-bottom: 48px; }
.ct-question {
  padding: 12px 14px;
  border: 1px solid transparent;
  border-radius: 2px;
}
.ct-question--active {
  background: var(--ct-teal-soft);
  border-color: var(--ct-teal);
}
.ct-q-prompt {
  font-size: 19px;
  font-weight: 700;
  margin-bottom: 8px;
  color: var(--ct-text);
  display: block;
  line-height: 1.5;
}
/* Cambridge-authentic question number badge (cae/examples/l1.png, 5.png,
 * 8.png): a small bordered square holding the question number, placed
 * inline before the bold question text. Replaces the prior "1. Text..."
 * dot-prefix rendering. Active question gets a filled brand-blue badge. */
.ct-q-num-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  margin-right: 8px;
  background: var(--ct-bg);
  border: 1px solid var(--ct-border-strong);
  border-radius: 2px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ct-text);
  vertical-align: middle;
  font-family: Arial, sans-serif;
}
.ct-question--active .ct-q-num-badge {
  border-color: var(--ct-teal);
  background: var(--ct-teal);
  color: #fff;
}
.ct-q-options { display: flex; flex-direction: column; gap: 6px; }
.ct-q-option {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 10px;
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
  font-size: 19px;
}
.ct-q-option:hover { background: #f9fafb; }
.ct-q-option input[type="radio"],
.ct-q-option input[type="checkbox"] {
  margin-top: 3px;
  accent-color: var(--ct-teal);
}
.ct-q-option-key { font-weight: 700; margin-right: 4px; }

/* ── Cambridge-style multiple-matching (drag-and-drop) ─────────────────────
 * Replaces the old "repeat all A-H options under every speaker" layout
 * (5 speakers x 8 options = 40 stacked radio rows, several screens tall) with
 * ONE compact two-column widget: prompt slots on the left, a single draggable
 * option bank on the right. Drag a letter into a slot, or tap a letter then a
 * slot. Mirrors the authentic Cambridge B2 First Listening Part 3 layout. */
.ct-match { margin-top: 2px; }
/* Nothing inside the drag surface is selectable. Only the CHIP carried
   user-select:none, so a drag that began on a chip still ran a native text
   selection across everything it passed over — the slot heads, the number
   badges, the "Drop a letter here" hints and the other chips all lit up blue
   mid-drag and the widget looked broken. The passage keeps its own selection
   (highlighting) — this is scoped to the drag grid only, and the dropdown
   variant of the widget is untouched. */
/* REVISED (F004). The blanket rule above was correct about the drag artefact but
   too broad about when: applying it permanently also made the question
   STATEMENTS un-selectable, and therefore un-highlightable — which is what the
   reviewers reported as "cannot highlight questions" on every drag-based part.
   Highlighting the stem is core IELTS technique, so this cost more than the
   artefact it prevented.
   Now scoped to the DURATION of a drag: test.js adds body.ct-chip-dragging on
   pointerdown and removes it on pointerup, the same shape as body.ct-resizing
   for the column splitter. Between drags the statements select normally; mid-drag
   nothing lights up blue. The chips keep their own permanent user-select:none —
   they are the drag handle, and a mousedown inside one must mean "pick up", not
   "select". */
.ct-match-ghost {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
}
body.ct-chip-dragging .ct-match-grid,
body.ct-chip-dragging .ct-heading-bank,
body.ct-chip-dragging .ct-passage {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
}
.ct-match-grid {
  display: grid;
  grid-template-columns: minmax(210px, 1fr) minmax(280px, 1.25fr);
  gap: 28px;
  align-items: start;
}
.ct-match-slots { display: flex; flex-direction: column; gap: 14px; }
.ct-match-slot-head {
  display: flex; align-items: center; gap: 6px;
  font-weight: 700; font-size: 18px; color: var(--ct-text);
  margin-bottom: 6px;
}
.ct-match-drop {
  min-height: 50px;
  border: 1px dashed var(--ct-border-strong);
  border-radius: 4px;
  background: var(--ct-surface);
  display: flex; align-items: center;
  padding: 5px 8px;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.ct-match-drop-hint {
  color: var(--ct-inactive);
  font-size: 14px; font-style: italic;
  pointer-events: none;
}
.ct-match-drop--filled { border-style: solid; border-color: var(--ct-teal); background: var(--ct-teal-soft); }
.ct-match-drop--filled .ct-match-drop-hint { display: none; }
.ct-match-drop--over { border-style: solid; border-color: var(--ct-teal); background: var(--ct-teal-soft); }

.ct-match-bank {
  display: flex; flex-direction: column; gap: 8px;
  min-height: 50px;
  padding: 10px;
  border: 1px solid var(--ct-border);
  border-radius: 6px;
  background: #fbfcfe;
}
.ct-match-chip {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  background: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: 4px;
  font-size: 17px; line-height: 1.35;
  cursor: grab;
  user-select: none;
  touch-action: none;
  box-shadow: 0 1px 1px rgba(15, 23, 42, .04);
}
.ct-match-chip:hover { border-color: var(--ct-teal); }
.ct-match-chip-key {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; height: 24px;
  background: var(--ct-bg); border: 1px solid var(--ct-border-strong);
  border-radius: 3px; font-weight: 700; font-size: 13px;
  font-family: Arial, sans-serif; flex: none;
}
.ct-match-chip--selected {
  border-color: var(--ct-teal); background: var(--ct-teal-soft);
  box-shadow: 0 0 0 2px var(--ct-teal-soft);
}
.ct-match-chip--dragging { opacity: .3; }
/* A chip sitting inside a slot reads as the filled answer, not a floating box */
.ct-match-drop .ct-match-chip {
  width: 100%; border: none; box-shadow: none; background: transparent; padding: 2px 2px; cursor: grab;
}
.ct-match-drop--filled .ct-match-chip-key { border-color: var(--ct-teal); background: var(--ct-teal); color: #fff; }
/* IELTS "List of Headings": the numbered answer gap sits ABOVE its paragraph in
   the passage (Inspera parity), so the drop box is a compact full-width strip
   with the question number to its left — not a tall slot-column cell. */
.ct-heading-slot {
  display: flex; align-items: center; gap: 8px;
  margin: 16px 0 4px;
}
.ct-heading-slot .ct-q-num-badge { flex: none; }
.ct-heading-drop { flex: 1; min-height: 34px; padding: 3px 10px; }
.ct-heading-drop .ct-match-chip { font-size: 15px; padding: 2px; }
.ct-heading-bank .ct-match-chip { font-size: 15px; }
.ct-match-ghost {
  position: fixed; z-index: 9999; pointer-events: none;
  transform: translate(-50%, -50%);
  opacity: .95;
  box-shadow: 0 8px 22px rgba(15, 23, 42, .28);
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; background: #fff;
  border: 1px solid var(--ct-teal); border-radius: 4px; font-size: 17px;
}
body.ct-text-large .ct-match-chip { font-size: 20px; }
@media (max-width: 760px) {
  .ct-match-grid { grid-template-columns: 1fr; gap: 16px; }
}
/* Reuse-mode multiple-matching (Reading Part 4: 10 statements, 4 reusable
 * writers) uses a compact dropdown per statement instead of drag-and-drop —
 * far tidier than 10 large drop zones, and the conventional pattern. */
.ct-match-dd-list { display: flex; flex-direction: column; gap: 6px; }
.ct-match-dd-row {
  display: flex; align-items: center; gap: 14px;
  padding: 8px 12px;
}
.ct-match-dd-head {
  display: flex; align-items: baseline; gap: 8px; flex: 1 1 auto;
  font-size: 18px; line-height: 1.45; color: var(--ct-text);
}
.ct-match-dd-select {
  flex: 0 0 auto;
  min-width: 184px; max-width: 240px;
  padding: 8px 10px;
  font-size: 16px;
  color: var(--ct-text);
  background: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: 4px;
  cursor: pointer;
}
.ct-match-dd-select:focus { outline: none; border-color: var(--ct-teal); box-shadow: 0 0 0 2px var(--ct-teal-soft); }
.ct-question--active .ct-match-dd-select { border-color: var(--ct-teal); }
body.ct-text-large .ct-match-dd-head { font-size: 22px; }
body.ct-text-large .ct-match-dd-select { font-size: 22px; }

/* ── Invigilator control (F004): header button + PIN modal ─────────────── */
.ct-invig-btn {
  display: inline-flex; align-items: center; gap: 6px;
  margin-right: 10px;
  padding: 7px 12px;
  background: var(--ct-surface); color: var(--ct-text);
  border: 1px solid var(--ct-border-strong); border-radius: 4px;
  font-size: 14px; font-weight: 600; cursor: pointer;
}
.ct-invig-btn:hover { border-color: var(--ct-teal); color: var(--ct-teal); }
.ct-invig-modal {
  position: fixed; inset: 0; z-index: 11000;
  background: rgba(15, 23, 42, .55);
  display: flex; align-items: center; justify-content: center;
}
.ct-invig-card {
  width: min(420px, 92vw);
  background: var(--ct-surface);
  border-radius: 8px; padding: 24px 24px 20px;
  box-shadow: 0 18px 50px rgba(15, 23, 42, .35);
}
.ct-invig-card h3 { margin: 0 0 8px; font-size: 20px; color: var(--ct-text); }
.ct-invig-card p { margin: 0 0 14px; color: var(--ct-text-muted); line-height: 1.5; }
.ct-invig-input {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px; font-size: 20px; letter-spacing: 4px; text-align: center;
  border: 1px solid var(--ct-border-strong); border-radius: 6px;
}
.ct-invig-input:focus { outline: none; border-color: var(--ct-teal); box-shadow: 0 0 0 2px var(--ct-teal-soft); }
.ct-invig-err { color: var(--ct-error-text); margin: 10px 0 0 !important; font-size: 14px; }
.ct-invig-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
.ct-invig-act {
  padding: 10px 16px; font-size: 15px; font-weight: 600;
  border-radius: 6px; cursor: pointer; border: 1px solid transparent;
}
.ct-invig-act--ghost { background: var(--ct-surface); border-color: var(--ct-border-strong); color: var(--ct-text); }
.ct-invig-act--primary { background: var(--ct-teal); color: #fff; }
.ct-invig-act:disabled { opacity: .55; cursor: default; }
/* Invigilator station proctor-check result line (intent proctor-check-ui#P1) */
.ct-invig-check { margin: 12px 0 0; font-size: 15px; text-align: center; }
.ct-invig-check--ok { color: var(--zu-success); }
.ct-invig-check--warn { color: var(--zu-danger); font-weight: 600; }

/* ── F001 focus-loss deterrent: blocking "return to exam" overlay ──────── */
.ct-focus-overlay {
  position: fixed; inset: 0; z-index: 12000;
  background: rgba(15, 23, 42, .92);
  display: flex; align-items: center; justify-content: center;
}
.ct-focus-card {
  width: min(540px, 92vw); text-align: center;
  background: var(--ct-surface); border-radius: 10px;
  padding: 36px 32px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
}
.ct-focus-icon { font-size: 44px; line-height: 1; margin-bottom: 12px; }
.ct-focus-card h2 { margin: 0 0 12px; font-size: 24px; color: var(--ct-text); }
.ct-focus-card p { margin: 0 0 22px; font-size: 17px; line-height: 1.55; color: var(--ct-text-muted); }
.ct-focus-btn {
  padding: 13px 28px; font-size: 17px; font-weight: 700;
  background: var(--ct-teal); color: #fff;
  border: none; border-radius: 6px; cursor: pointer;
}
.ct-focus-btn:hover { background: var(--ct-teal-dark); }

/* Part 1 inline MC cloze — Cambridge-authentic bordered box. Number prefix
 * sits inside the left of the box; <select> fills the right. No visible
 * dropdown arrow; no placeholder text; box appears blank until chosen. */
.ct-inline-mc {
  display: inline-flex;
  align-items: stretch;
  vertical-align: middle;
  margin: 0 3px;
  border: 1px solid var(--ct-text);
  border-radius: 2px;
  background: #fff;
  height: 24px;
  overflow: hidden;
  line-height: 1;
}
.ct-inline-mc-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 0 6px;
  font-family: Arial, sans-serif;
  font-size: 12px;
  font-weight: 600;
  color: var(--ct-text);
  border-right: 1px solid var(--ct-border-strong);
  background: #fff;
}
.ct-inline-mc-select {
  font-family: Arial, sans-serif;
  font-size: 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0 10px 0 6px;
  min-width: 90px;
  outline: none;
  color: var(--ct-text);
  /* Hide the native dropdown arrow — the whole box IS the dropdown. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
.ct-inline-mc:focus-within {
  border-color: var(--ct-teal);
  box-shadow: 0 0 0 1px var(--ct-teal);
}

/* Part 4 — key word transformation (left-aligned, inline input)
 * Single card per page, matching cae/examples/4.png. In the real CAE
 * the KWT block shares the same LEFT EDGE with the "Questions 25–30"
 * banner above it — content flows flush-left, empty canvas fills the
 * right. Earlier `margin: 0 auto` centered the 960px block inside the
 * 1152px .ct-part which produced a ~100px indent relative to the banner;
 * that read as "the question is floating off to the right" and broke
 * the visual lock between the instructions banner and the question it
 * describes. Left-align with margin-left 0 + minimal padding-left so
 * the teal active accent sits flush next to the text. */
.ct-kwt-block {
  max-width: 960px;
  margin: 0 0 40px 0;
  text-align: left;
  /* Override the generic .ct-question--active background so selected KWT
   * blocks don't render as big pale pills. Subtle left-accent only. */
  background: transparent !important;
  border: none !important;
  border-left: 4px solid transparent;
  padding: clamp(32px, 6vh, 72px) 24px clamp(28px, 5vh, 60px) 4px;
  transition: border-color 0.15s;
}
.ct-kwt-block.ct-question--active {
  border-left-color: var(--ct-teal);
}
.ct-kwt-first {
  font-size: 17px;
  color: var(--ct-text);
  margin: 16px 0 8px;
  line-height: 1.55;
}
.ct-kwt-keyword {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.1em;
  margin: 18px 0 14px;
  color: var(--ct-teal);
}
.ct-kwt-lead-row {
  font-size: 17px;
  color: var(--ct-text);
  line-height: 2.2;
  position: relative;
  padding-right: 32px;
}
/* Inline KWT gap — bordered box with the question number inside at left */
.ct-kwt-gap {
  display: inline-flex;
  align-items: stretch;
  vertical-align: middle;
  margin: 0 5px;
  border: 1.5px solid var(--ct-text);
  border-radius: 5px;
  background: #fff;
  height: 34px;
  overflow: hidden;
}
.ct-kwt-gap-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  margin-right: 8px;
  font-family: Arial, sans-serif;
  font-size: 12px;
  font-weight: 700;
  color: var(--ct-text);
  border: 1px solid var(--ct-border-strong);
  border-radius: 2px;
  background: var(--ct-bg);
}
.ct-kwt-input {
  font-family: inherit;
  font-size: 16px;
  padding: 0 12px;
  border: none;
  min-width: 300px;
  height: 100%;
  background: transparent;
  color: var(--ct-text);
  outline: none;
}
.ct-kwt-gap:focus-within {
  border-color: var(--ct-teal);
  box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.18);
}
.ct-kwt-bookmark {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

/* Listening Part 2 sentence-completion rows reuse the Part 4 KWT inline
 * input pattern. Override the generic `.ct-question--active` pale teal
 * pill — the authentic Cambridge look is borderless with just a subtle
 * teal left accent on the active row. */
.ct-listen-sc-row {
  background: transparent !important;
  border: none !important;
  border-left: 3px solid transparent;
  padding: 8px 0 8px 12px;
  transition: border-color 0.15s;
}
.ct-listen-sc-row.ct-question--active {
  border-left-color: var(--ct-teal);
}
.ct-listen-sc-row .ct-kwt-lead-row {
  line-height: 2.2;
  padding-right: 32px;
  font-size: 17px;
}

/* Part 7 gapped text — clean SOLID-border rectangle with the question
 * number inside. The click-to-fill flow is discovered via hover + active
 * state. */
.ct-slot {
  display: block;
  min-height: 56px;
  padding: 14px 18px;
  margin: 16px 0;
  border: 1.5px solid var(--ct-text);
  border-radius: 6px;
  background: #fff;
  font-size: 19px;
  color: var(--ct-text);
  cursor: pointer;
  transition: background 0.12s, box-shadow 0.12s, border-color 0.12s;
}
.ct-slot:hover { background: var(--zu-cream-soft); border-color: var(--ct-teal); }
.ct-slot--filled {
  background: var(--ct-teal-soft);
  border-color: var(--ct-teal);
}
.ct-slot--active {
  box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.2);
  background: var(--ct-teal-soft);
  border-color: var(--ct-teal);
}
.ct-slot-number {
  display: inline-block;
  font-weight: 700;
  color: var(--ct-teal);
  margin-right: 14px;
  padding: 0 10px 0 0;
  border-right: 1.5px solid var(--ct-border-strong);
}

.ct-paragraph-bank {
  background: var(--ct-banner-bg);
  border: 1px solid var(--ct-border);
  border-radius: 8px;
  padding: 18px 20px;
  font-size: 18px;
}
.ct-paragraph-bank h4 {
  margin: 0 0 14px;
  font-size: 14px;
  color: var(--ct-text);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
}
.ct-para-card {
  padding: 14px 16px;
  margin-bottom: 10px;
  background: #fff;
  border: 1.5px solid var(--ct-border-strong);
  border-radius: 6px;
  cursor: pointer;
  font-size: 18px;
  line-height: 1.55;
  transition: border-color 0.12s, background 0.12s, transform 0.12s;
}
.ct-para-card:hover {
  border-color: var(--ct-teal);
  background: var(--ct-teal-soft);
  transform: translateX(2px);
}
.ct-para-card--used { opacity: 0.4; cursor: not-allowed; }
.ct-para-card--used:hover { transform: none; }
.ct-para-key {
  font-weight: 700;
  color: var(--ct-teal);
  margin-right: 10px;
  font-size: 16px;
}

/* Listening — task-group labels and pre-play modal */
.ct-task-group {
  margin-bottom: 36px;
  padding-bottom: 36px;
  border-bottom: 1px solid var(--ct-border);
}
.ct-task-group:last-child { border-bottom: none; margin-bottom: 8px; padding-bottom: 0; }
.ct-task-group-instructions {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 18px;
  padding: 14px 18px;
  background: var(--ct-banner-bg);
  border-left: 4px solid var(--ct-teal);
  border-radius: 4px;
  color: var(--ct-text);
  line-height: 1.55;
}

/* Two-column task layout: speakers on the left, options on the right
 * (matches cae/examples/l5.png exactly) */
.ct-task-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
.ct-task-speakers {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ct-task-speaker {
  display: grid;
  grid-template-columns: 100px 1fr 32px;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  border: 1.5px solid transparent;
  border-radius: 6px;
  transition: background 0.12s, border-color 0.12s;
}
.ct-task-speaker:hover { background: #f9fafb; }
.ct-task-speaker--active {
  background: var(--ct-teal-soft);
  border-color: var(--ct-teal);
}
.ct-task-speaker-label {
  font-weight: 600;
  font-size: 16px;
  color: var(--ct-text);
}
.ct-task-speaker-select {
  font-family: inherit;
  font-size: 16px;
  padding: 8px 14px;
  border: 1.5px solid var(--ct-text);
  background: #fff;
  color: var(--ct-text);
  border-radius: 5px;
  width: 100%;
  min-width: 0;
  /* Chromeless: match the authentic Cambridge l5.png look where the speaker
   * dropdown appears as a plain bordered input, not a native select. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  cursor: pointer;
}
.ct-task-speaker-select:focus {
  outline: none;
  border-color: var(--ct-teal);
  box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.18);
}
.ct-task-speaker-num {
  font-size: 13px;
  color: var(--ct-text-muted);
  text-align: right;
  font-family: inherit;
  font-weight: 600;
}

/* Options reference panel — right column */
.ct-task-options-panel {
  background: var(--ct-banner-bg);
  border: 1px solid var(--ct-border);
  border-radius: 8px;
  padding: 18px 22px;
  font-size: 15px;
  align-self: start;
  line-height: 1.6;
}
.ct-task-options-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.ct-task-options-list li {
  padding: 8px 0;
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 10px;
  color: var(--ct-text);
}
.ct-task-options-list .ct-task-opt-key {
  font-weight: 700;
  color: var(--ct-teal);
  font-size: 16px;
}

/* Pre-play modal */
/* Listening audio gate — a dark scrim (0.7 black) behind a compact white pop-up
   card (per design direction: a small pop-up with a distinct background, rather
   than Inspera's card-less overlay with content floated straight on the scrim). */
.ct-preplay-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  animation: ctFadeIn 240ms ease-out;
}
.ct-preplay-card {
  background: #fff;
  padding: 36px 40px 32px;
  max-width: 460px;
  width: calc(100% - 48px);
  text-align: center;
  border-radius: 10px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
  animation: ctPopIn 200ms ease-out;
}
@keyframes ctPopIn {
  from { opacity: 0; transform: translateY(8px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.ct-preplay-icon {
  width: 72px;
  height: 72px;
  margin: 0 auto 22px;
  color: #000;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z'/></svg>") center/contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z'/></svg>") center/contain no-repeat;
}
.ct-preplay-text {
  font-size: 16px;
  color: #000;
  line-height: 1.6;
  margin: 0 0 28px;
}
.ct-preplay-btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px;
  font-family: inherit;
  font-size: 17px;
  font-weight: 700;
  background: #000;
  color: #fff;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  transition: background 0.15s, transform 0.08s;
}
.ct-preplay-btn:hover {
  background: #222;
}
.ct-preplay-btn:active { transform: translateY(1px); }
.ct-preplay-btn::before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-left: 10px solid #fff;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
}

/* Bottom navigator */
.ct-bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 76px;
  background: var(--ct-bg);
  border-top: 1px solid var(--ct-border);
  display: flex;
  align-items: stretch;
  z-index: 30;
  font-size: 14px;
  box-shadow: 0 -1px 3px rgba(15, 23, 42, 0.04);
}
.ct-nav-parts {
  display: flex;
  flex: 1;
  align-items: stretch;
  /* min-width: 0 lets the flex container actually shrink to fit its parent
   * instead of growing to its content's intrinsic width. Without this, at
   * narrow viewports the parts push the arrows container off-screen. */
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: thin;
}
.ct-nav-parts::-webkit-scrollbar { height: 3px; }
.ct-nav-parts::-webkit-scrollbar-thumb { background: var(--ct-border-strong); }
.ct-nav-part {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 14px;
  border-right: 1px solid var(--ct-border);
  cursor: pointer;
  min-width: 110px;
  transition: background 0.12s;
  font: inherit;
  color: inherit;
  background: var(--ct-bg);
  border-top: none;
  border-left: none;
  border-bottom: none;
}
.ct-nav-part:hover { background: var(--zu-cream-soft); }
.ct-nav-part-label {
  font-weight: 700;
  color: var(--ct-text);
  margin-right: 12px;
  white-space: nowrap;
  font-size: 15px;
  /* Under space pressure (long RU labels at 1280px) truncate the label with an
   * ellipsis instead of letting it push the counter behind the arrow cluster. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ct-nav-part-count {
  /* L13 — darkened from --ct-text-muted (#64748b, 4.39:1) to clear WCAG AA */
  color: #475569;
  font-size: 13px;
  white-space: nowrap;
  /* Keep the "N of M" counter fully visible — only the label truncates. */
  flex-shrink: 0;
}
.ct-nav-part--active {
  flex: 3.5;
  background: var(--zu-cream-soft);
  padding: 8px 16px;
}
.ct-nav-part--active .ct-nav-part-label {
  color: var(--ct-text);
}
/* IELTS Writing footer: only two single-question parts (no number pills), so the
   flex:3.5 active boost (sized for Reading/Listening number tabs) balloons the
   active part and shoves the other to the far right. Match Inspera's balanced
   footer — equal-width parts, labels left-aligned so "Part 1" sits far-left and
   "Part 2" lands at centre. The active tint is dropped to a plain rail (Inspera
   marks the active part with a top rail, not a filled block). */
body.ielts-exam--writing .ct-nav-part,
body.ielts-exam--writing .ct-nav-part--active { flex: 1; }
/* Two body classes + :not() so this beats `body.ielts-exam .ct-nav-part:not(
   .ct-nav-part--active) { justify-content: center }` (defined later) — otherwise
   the inactive part stays centred and only "Part 1" left-aligns. */
body.ielts-exam.ielts-exam--writing .ct-nav-part:not(.ct-nav-part--active),
body.ielts-exam.ielts-exam--writing .ct-nav-part--active { justify-content: flex-start; }
body.ielts-exam--writing .ct-nav-part--active { background: transparent; }
/* F010 — completed part (every question answered) turns green. A 3px green top
   rule + green label reads as "finished" without disturbing the segment's right
   divider or the active tint (the two classes can co-exist on the active part). */
.ct-nav-part--done {
  box-shadow: inset 0 3px 0 0 var(--zu-success, #15803d);
}
.ct-nav-part--done .ct-nav-part-label,
.ct-nav-part--done .ct-nav-part-count {
  color: var(--zu-success, #15803d);
  font-weight: 700;
}
body.ielts-exam.ct-contrast .ct-nav-part--done {
  box-shadow: inset 0 3px 0 0 var(--cc-done-bar);
}
body.ielts-exam.ct-contrast .ct-nav-part--done .ct-nav-part-label,
body.ielts-exam.ct-contrast .ct-nav-part--done .ct-nav-part-count {
  color: var(--cc-done-fg);
}
.ct-nav-part-numbers {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: center;
  max-width: 100%;
}
.ct-nav-num {
  min-width: 32px;
  height: 32px;
  padding: 0 10px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  border: 1.5px solid var(--ct-border-strong);
  background: var(--ct-bg);
  color: var(--ct-text);
  cursor: pointer;
  border-radius: 5px;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.ct-nav-num:hover { border-color: var(--ct-teal); color: var(--ct-teal); }
.ct-nav-num--answered {
  background: var(--ct-teal-soft);
  border-color: var(--zu-focus);
  color: var(--ct-teal-dark);
}
.ct-nav-num--active {
  background: var(--ct-teal);
  border-color: var(--ct-teal);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 2px 6px rgba(30, 64, 175, 0.3);
}
.ct-nav-num--active:hover { color: #fff; }

.ct-nav-arrows {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 22px;
  border-left: 1px solid var(--ct-border);
}
/* Finish is only shown on the last section (test.js gates this). On earlier
   parts it is removed from the nav so the student advances with Next only. */
.ct-nav-finish--hidden { display: none !important; }
.ct-nav-arrow,
.ct-nav-finish {
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  font-weight: 700;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: opacity 0.12s, transform 0.08s, box-shadow 0.15s;
}
.ct-nav-arrow {
  background: var(--ct-text-muted);
  color: #fff;
}
.ct-nav-arrow--next {
  background: var(--ct-teal);
  box-shadow: 0 2px 6px rgba(30, 64, 175, 0.25);
}
.ct-nav-arrow:hover:not(:disabled),
.ct-nav-finish:hover:not(:disabled) { opacity: 0.92; transform: translateY(-1px); }
.ct-nav-arrow:active:not(:disabled),
.ct-nav-finish:active:not(:disabled) { transform: translateY(0); }
.ct-nav-arrow:disabled,
.ct-nav-finish:disabled { opacity: 0.4; cursor: not-allowed; }
/* Finish (✓) button matches the brand's primary action colour (brand blue),
 * per the intent plan and cae/examples/*.png where the check is teal/primary,
 * not a separate green "success" accent. Keeps finish consistent with the
 * next arrow (→) so the bottom-right corner reads as one primary-action
 * cluster. */
.ct-nav-finish {
  background: var(--ct-teal);
  color: #fff;
  box-shadow: 0 2px 6px rgba(30, 64, 175, 0.25);
}

/* Bookmark icon — clickable toggle for flagging questions */
.ct-bookmark {
  display: inline-block;
  width: 16px;
  height: 20px;
  background: var(--ct-text-muted);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z'/></svg>") center/contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z'/></svg>") center/contain no-repeat;
  opacity: 0.35;
  float: right;
  margin-left: 8px;
  cursor: pointer;
  transition: opacity 0.15s, background 0.15s;
}
.ct-bookmark:hover { opacity: 0.7; }
.ct-bookmark--active {
  background: #c8a000;
  opacity: 1;
}
.ct-nav-num--bookmarked {
  position: relative;
}
.ct-nav-num--bookmarked::after {
  content: '';
  position: absolute;
  top: 2px;
  right: 2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #c8a000;
}

/* Modal overlay — full-screen scrim for the error modal (blue-tinted blur
 * backdrop, centered card). */
.ct-error-modal {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.75);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 110;
  animation: ctFadeIn 220ms ease-out;
}
.ct-error-card p {
  font-size: 16px;
  color: var(--ct-text);
  line-height: 1.55;
  margin: 0 0 26px;
}

/* Error modal — same overlay as above; the
 * card adds a crimson top border to signal "something went wrong". */
.ct-error-card {
  background: #fff;
  padding: 40px 44px 32px;
  max-width: 480px;
  border-radius: 12px;
  text-align: center;
  box-shadow: 0 24px 56px rgba(0, 0, 0, 0.4);
  border-top: 4px solid var(--ct-error-text);
}
.ct-error-card h3 {
  color: var(--ct-error-text);
  font-size: 20px;
  font-weight: 700;
  margin: 0 0 12px;
  letter-spacing: -0.01em;
}
.ct-error-card button {
  padding: 12px 32px;
  background: var(--ct-error-text);
  color: #fff;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  font-size: 15px;
  box-shadow: 0 4px 12px rgba(185, 28, 28, 0.25);
  transition: background 0.15s, transform 0.08s;
}
.ct-error-card button:hover { background: var(--zu-accent-dark); }
.ct-error-card button:active { transform: translateY(1px); }

/* Responsive fallback for narrow screens */
@media (max-width: 880px) {
  .ct-two-col { grid-template-columns: 1fr; }
  .ct-nav-part--active { flex: 3; }
}

/* Medium viewports (881px–1399px): common lab-PC range covering 1024x768,
 * 1280x720, and 1366x768. Originally shipped in round 28b for 1024 only
 * (when students on Part 1 saw only the "Part 1" label with all 8 question
 * numbers clipped below the 76px fixed nav bar). Round 29b extended the
 * upper bound to 1399px because 1280/1366 — while not clipping — still
 * rendered the question numbers as a cramped 2-row stack, while authentic
 * Cambridge CAE (cae/examples/*.png) shows all numbers on a single row at
 * those widths. At 1400+ desktop rules take over and the natural flex:3.5
 * width is already wide enough for 10 numbers on one row.
 *
 * Active cell gets an explicit 440px min-width — enough for 10 numbered
 * buttons on one row (10 × 32 + 9 × 6 gap + label ~60 + padding ~32 ≈
 * 444px). Inactive cells shrink to 90px. Below ~1230px the total exceeds
 * the nav container and overflow-x: auto kicks in; round 14's
 * scrollIntoView on the active segment keeps it visible. */
@media (min-width: 881px) and (max-width: 1399px) {
  .ct-nav-part { min-width: 90px; padding: 10px 10px; }
  .ct-nav-part--active {
    flex: 4;
    /* 480 = label "Part 8" (~50) + margin-right (12) + 10 nums (320)
     * + 9 gaps (54) + inner padding (32) + breathing (12) */
    min-width: 480px;
  }
  .ct-nav-part-numbers { flex-wrap: nowrap; }
}

/* Mobile (iPhone-class and below): tighten header padding + gap so
 * logo, candidate ID, and timer don't fight for the same 375px. The
 * candidate ID label goes smaller and the header itself collapses
 * its side padding. Also tighten the main content and bottom nav. */
@media (max-width: 540px) {
  .ct-header {
    padding-inline: 12px;
    padding-block: 8px;
    gap: 10px;
    min-height: 60px;
  }
  .ct-logo { height: 36px; }
  .ct-brand-sub { font-size: 11px; }
  .ct-header-id { max-width: 150px; }
  .ct-header-id-label { font-size: 10px; letter-spacing: 0.04em; }
  .ct-header-id-value { font-size: 13px; }
  .ct-header-id-code { font-size: 13px; }
  .ct-timer {
    font-size: 14px;
    padding: 6px 10px;
    min-width: 70px;
  }
  .ct-banner-wrap { padding: 14px 14px 0; }
  .ct-banner { padding: 12px 16px; }
  .ct-banner-title { font-size: 15px; }
  .ct-banner-body { font-size: 14px; }
  .ct-main { padding: 0 14px; margin-top: 18px; }
  .ct-passage { font-size: 16px; line-height: 1.7; }
  .ct-bottom-nav { height: 68px; }
  .ct-nav-arrows { padding: 0 12px; gap: 6px; }
  .ct-nav-arrow, .ct-nav-finish { width: 42px; height: 42px; font-size: 17px; }

  /* Writing area — tighter padding on mobile */
  .ct-writing-part { padding: 1rem 0.25rem; }
  .ct-writing-textarea { padding: 1.25rem 1rem; min-height: 45vh; font-size: 1rem; }

  /* .zu-page pages (dashboard / admin / done + welcome form) — tighten
   * the page shell so 375px feels less padded. Applies to all pages that
   * use .page without the narrow-welcome vertical centering. */
  .page { padding: 1.5rem 1rem 3rem; }
  .zu-header { padding: 1rem 0.5rem 1.25rem; margin-bottom: 1.5rem; }
  .zu-header h1 { font-size: 1.9rem; }
  .zu-welcome-panel { padding: 1.25rem 1.25rem; }
  .zu-welcome-panel .zu-welcome-name { font-size: 1.25rem; }
  .zu-module-card { padding: 1.5rem 1.25rem; }
  .zu-module-card h3 { font-size: 1.25rem; }

  /* Admin toolbar buttons are chunky at mobile — shrink them so
   * Refresh / Export CSV / Export JSON all fit in one row without
   * overflow, or gracefully wrap to two rows without taking 30% of
   * the viewport per button. */
  .zu-toolbar .zu-btn {
    font-size: 0.95rem;
    padding: 0.65rem 1rem;
    flex: 1 1 auto;
    min-width: 0;
  }
}

/* Reduced motion — for users with vestibular disorders or motion
 * sensitivity. Follows WCAG 2.3.3 (Animation from Interactions) and
 * the standard web-a11y pattern of reducing animation/transition
 * duration to near-zero instead of removing them entirely (state
 * changes still fire, they just don't animate).
 *
 * This disables:
 *   - ctAudioPulse (1.6s infinite speaker-icon opacity loop)
 *   - ctTimerPulse (1s infinite glow on urgent timer state)
 *   - ctFadeIn (entrance on main content, modals, module cards)
 *   - Every button hover translateY + shadow transition
 *   - Table row hover background fades
 *
 * Users who have OS-level reduced-motion enabled still see:
 *   - All state changes (active question, answered nav nums, etc.)
 *   - All color changes (focus rings, hover highlights)
 *   - Timer urgent styling (crimson bg without the pulse animation)
 *   - Error modal appearance (instant, no fade-in)
 */
/* ---------- AI evaluation display (admin) ---------- */
.zu-ai-status { font-weight: 600; font-size: 0.85rem; }
.zu-ai-evaluating { color: #2563eb; }
.zu-ai-pending { color: #d97706; }
.zu-ai-eval {
  margin: 1.25rem 0;
  padding: 1rem 1.25rem;
  background: var(--zu-cream-soft);
  border: 1px solid var(--zu-border);
  border-radius: 8px;
}
.zu-ai-eval h3 {
  font-size: 0.95rem;
  margin: 0 0 0.75rem;
  color: var(--zu-primary);
}
.zu-ai-criterion {
  margin-bottom: 0.75rem;
}
.zu-ai-criterion-header {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}
.zu-ai-bar {
  height: 8px;
  background: var(--zu-border);
  border-radius: 4px;
  overflow: hidden;
}
.zu-ai-bar-fill {
  height: 100%;
  background: #2563eb;
  border-radius: 4px;
  transition: width 0.3s;
}
.zu-ai-feedback {
  font-size: 0.82rem;
  color: #475569;
  margin: 0.25rem 0 0;
  line-height: 1.4;
}
.zu-ai-comment {
  font-size: 0.85rem;
  color: #334155;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--zu-border);
  line-height: 1.5;
}
.zu-ai-wordcount {
  font-size: 0.85rem;
  margin: 0 0 0.75rem;
  padding: 0.4rem 0.75rem;
  border-radius: 0.375rem;
}
.zu-ai-wc-ok {
  background: #f0fdf4;
  color: #166534;
}
.zu-ai-wc-penalty {
  background: #fef2f2;
  color: #991b1b;
}
.zu-ai-penalty {
  font-weight: 600;
}

/* Admin submission-detail: question prompt box + image gallery. Per-question
 * markup used by the detail view; previously lived as inline `style="..."`
 * attributes in admin.js template strings. */
.zu-admin-prompt {
  margin-bottom: 10px;
  padding: 10px 12px;
  background: var(--zu-cream-soft);
  border-left: 3px solid var(--zu-primary);
  border-radius: 4px;
  white-space: pre-wrap;
  font-weight: 500;
  color: #1e293b;
}
.zu-admin-images {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 10px 0;
}
.zu-admin-images img {
  max-width: 420px;
  max-height: 420px;
  border: 1px solid #d0d0d0;
  border-radius: 4px;
  background: #fff;
}
.zu-admin-reference {
  margin-top: 10px;
}
.zu-admin-reference summary {
  cursor: pointer;
  font-weight: 500;
  color: #166534;
}
.zu-admin-reference-body {
  margin-top: 6px;
  padding: 8px 10px;
  background: #f0fdf4;
  border-left: 3px solid #22c55e;
  border-radius: 4px;
  white-space: pre-wrap;
  font-size: 0.9em;
}

/* ---------- writing part ---------- */
.ct-writing-part {
  max-width: none;            /* fill the content width (Inspera-style), not a narrow center column */
  margin: 0;
  padding: 1.25rem 0.25rem 3rem;
}
.ct-writing-prompt {
  font-size: 0.88rem;
  line-height: 1.65;
  margin-bottom: 1.25rem;
  color: var(--ct-text-muted);
  padding: 0;
  max-height: 35vh;
  overflow-y: auto;
  white-space: pre-wrap;
}
/* Writing canvas — calm, paper-like feel */
.ct-writing-textarea {
  width: 100%;
  min-height: 52vh;
  padding: 1.75rem 2rem;
  font-family: inherit;
  font-size: 1.05rem;
  line-height: 1.85;
  color: #1e293b;
  background: var(--ct-surface);
  border: 1px solid var(--ct-border);
  border-radius: 8px;
  resize: vertical;
  box-sizing: border-box;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.ct-writing-textarea::placeholder {
  color: var(--ct-inactive);
  font-style: italic;
}
.ct-writing-textarea:focus {
  outline: none;
  border-color: #93c5fd;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 0 0 3px rgba(147, 197, 253, 0.18);
}
/* Word counter — subtle, right-aligned */
.ct-writing-counter {
  margin-top: 0.625rem;
  font-size: 0.78rem;
  color: var(--ct-inactive);
  text-align: right;
  letter-spacing: 0.01em;
}

/* Walk F004 — cap the Task 1 image height so the answer textarea stays
   reachable without scrolling past a full-height figure. Scoped to writing
   images only (the shared .ct-medical-image cap of 520px still applies to
   medical questions). Independent of image sourcing: a properly-cropped
   short chart never hits this cap. */
.ct-writing-images .ct-writing-image {
  /* H02 — sources are now tightly-cropped charts (not full-page scans), so
     size to fit width first and allow more height; square/stacked charts stay
     legible inline, and click-to-enlarge (M10) covers fine detail. */
  max-width: 100%;
  max-height: 55vh;
  width: auto;
  height: auto;
}
/* M10 — unobtrusive "Click/Tap to enlarge" caption under the Task-1 image */
.ct-writing-image-hint {
  width: 100%;
  margin-top: 0.375rem;
  font-size: 0.75rem;
  color: var(--ct-text-muted);
  text-align: center;
}

/* Walk F008 — on wide kiosk displays the writing column was pinned to
   740px, wasting width and cramping the essay area. Widen it past the
   reading-line-length cap only on large viewports. */
@media (min-width: 1200px) {
  .ct-writing-part { max-width: none; }
}
.ct-writing-counter--under .ct-writing-counter-num {
  color: #d97706;
}
.ct-writing-counter--over .ct-writing-counter-num {
  color: #dc2626;
  font-weight: 600;
}
.ct-writing-counter--ok .ct-writing-counter-num {
  color: #22c55e;
}
.ct-writing-counter-range {
  color: var(--ct-text-muted, #64748b);
}
/* M03 — the reskin's light-blue active card eroded the word-counter contrast;
   darken the live-count feedback so "min 150 / met / under" clears WCAG AA.
   The base counter colour lives once in the residual-sweep rule below; this
   block only tunes the per-state count-number colours. */
body.ielts-exam .ct-writing-counter--ok .ct-writing-counter-num { color: #15803d; }
body.ielts-exam .ct-writing-counter--under .ct-writing-counter-num { color: #b45309; }

/* ---------- Medical Olympiad — admin per-part rubric breakdown ---------- */
.zu-part-header--medical td {
  background: linear-gradient(0deg, #eef4ff, #f5f9ff);
  border-bottom: none;
}
.zu-med-rubric-host td {
  background: #f8fbff;
  padding: 10px 18px 14px;
  border-top: none;
}
.zu-med-rubric {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.93em;
}
.zu-med-rubric td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--ct-teal-soft);
  vertical-align: middle;
}
.zu-med-rubric tr:last-child td { border-bottom: none; }
.zu-med-crit-label {
  width: 40%;
  color: #1e293b;
  font-weight: 500;
}
.zu-med-crit-bar {
  width: 40%;
}
.zu-med-bar-track {
  height: 9px;
  background: var(--zu-border);
  border-radius: 999px;
  overflow: hidden;
}
.zu-med-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #3b82f6, var(--zu-primary));
  border-radius: 999px;
}
.zu-med-crit-score {
  width: 20%;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--zu-primary);
}
.zu-med-feedback-row td {
  background: #fffbeb;
  border-top: 1px dashed #fde68a;
  padding: 10px 12px;
  font-size: 0.92em;
  color: #422006;
}
.zu-med-q-idx {
  display: inline-block;
  background: var(--ct-banner-bg);
  color: #475569;
  font-size: 0.82em;
  padding: 2px 8px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

/* ---------- Medical Olympiad question rendering ---------- */
/* Image cards (rasmli). Click to enlarge in lightbox. */
.ct-medical-images {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 16px 0;
}
.ct-medical-image {
  max-width: 100%;
  max-height: 520px;
  border: 1px solid #d0d0d0;
  border-radius: 6px;
  background: #fff;
  cursor: zoom-in;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.ct-medical-image:hover {
  transform: scale(1.005);
  box-shadow: 0 4px 18px rgba(15, 23, 42, 0.12);
}

/* Lightbox modal — full-screen overlay with the image at native size. */
.ct-img-lightbox {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.85);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: ctLightboxFadeIn 160ms ease-out;
}
@keyframes ctLightboxFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
.ct-img-lightbox-inner {
  position: relative;
  max-width: 95vw;
  max-height: 95vh;
}
.ct-img-lightbox-inner img {
  display: block;
  max-width: 95vw;
  max-height: 95vh;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}
.ct-img-lightbox-close {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: #fff;
  color: #1e293b;
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.ct-img-lightbox-close:hover {
  background: var(--ct-banner-bg);
}

/* Vaziyat (clinical) prescription block — distinguishes the Rp./Acidi/M.f.
   protocol from the surrounding situation narrative. */
.ct-prescription {
  font-family: 'SF Mono', Menlo, Consolas, monospace;
  font-size: 0.95rem;
  line-height: 1.6;
  background: #fff8e1;
  border-left: 3px solid #ca8a04;
  padding: 14px 18px;
  margin: 0 0 16px 0;
  border-radius: 4px;
  white-space: pre-wrap;
  color: #422006;
}
.ct-prescription-narrative-label {
  font-weight: 600;
  color: var(--ct-teal);
  margin: 6px 0 4px 0;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ct-prescription-narrative {
  color: #1e293b;
  line-height: 1.65;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Medical Olympiad pre-registered candidate picker ---------- */
.zu-candidate-section {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-top: 1rem;
}
.zu-candidate-section #f-candidate-number {
  font-size: 1.8rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-align: center;
  padding: 1rem;
  font-family: "SF Mono", Menlo, Consolas, monospace;
}
.zu-candidate-card {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1.25rem;
  padding: 1.5rem;
  border: 2px solid var(--zu-primary);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 6px 24px rgba(30, 64, 175, 0.12);
}
.zu-candidate-photo-wrap {
  width: 120px;
  height: 150px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--ct-bg-alt);
  display: flex;
  align-items: center;
  justify-content: center;
}
.zu-candidate-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.zu-candidate-photo-placeholder {
  color: var(--zu-border-strong);
  font-size: 3rem;
  font-weight: 700;
}
.zu-candidate-details {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}
.zu-candidate-number {
  font-family: "SF Mono", Menlo, Consolas, monospace;
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--zu-primary-dark);
  letter-spacing: 0.1em;
}
.zu-candidate-name {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--zu-text);
  line-height: 1.3;
}
.zu-candidate-org {
  font-size: 0.95rem;
  color: var(--zu-text);
  opacity: 0.8;
  line-height: 1.4;
}
.zu-candidate-subject {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--zu-border);
  font-size: 0.95rem;
}
.zu-candidate-subject-label {
  color: var(--zu-text);
  opacity: 0.7;
  margin-right: 0.35rem;
}
.zu-candidate-subject strong {
  color: var(--zu-primary-darker);
}
.zu-candidate-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: 0.75rem;
  margin-top: 0.5rem;
}
.zu-candidate-actions .zu-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.2;
  padding: 0.85rem 1rem;
}
.zu-btn--primary { /* alias for default .zu-btn — exists so HTML can be explicit */ }
.zu-invigilator-gate {
  grid-column: 1 / -1;
  margin-top: 0.75rem;
  padding: 1rem;
  border-top: 1px dashed var(--zu-border);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.zu-invigilator-prompt {
  margin: 0;
  font-size: 0.95rem;
  color: var(--zu-primary-darker);
  font-weight: 600;
  text-align: center;
}
.zu-field-hint {
  margin: 0.25rem 0 0 0;
  font-size: 0.8rem;
  color: var(--zu-text);
  opacity: 0.65;
}
@media (max-width: 520px) {
  .zu-candidate-card {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
  }
  .zu-candidate-actions { flex-direction: column; }
}

/* ---------- Medical three-screen flow (language picker + override) ---------- */
.zu-med-screen {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.zu-med-prompt {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--zu-primary-darker);
  text-align: center;
  margin: 0 0 0.25rem 0;
}
.zu-lang-buttons {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.75rem;
}
.zu-lang-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.4rem 0.5rem;
  border: 2px solid var(--zu-border);
  border-radius: 10px;
  background: #fff;
  color: var(--zu-text);
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.05s;
}
.zu-lang-btn:hover {
  border-color: var(--zu-primary);
  background: var(--zu-primary-faint);
}
.zu-lang-btn:active { transform: translateY(1px); }
.zu-lang-btn:focus-visible {
  outline: 2px solid var(--zu-primary);
  outline-offset: 2px;
}
.zu-lang-flag { font-size: 2rem; line-height: 1; }
.zu-lang-label { font-size: 1rem; }

.zu-back-link {
  align-self: flex-start;
  background: transparent;
  border: none;
  color: var(--zu-primary);
  font-family: inherit;
  font-size: 0.95rem;
  cursor: pointer;
  padding: 0.25rem 0;
  margin-bottom: -0.25rem;
}
.zu-back-link:hover { text-decoration: underline; }

.zu-override-hint {
  text-align: center;
  font-size: 0.9rem;
  color: var(--zu-text);
  opacity: 0.75;
  margin: 0.5rem 0 0 0;
}
.zu-override-hint a {
  color: var(--zu-primary);
  text-decoration: underline;
  cursor: pointer;
}
.zu-override-note {
  font-size: 0.9rem;
  color: var(--zu-text);
  opacity: 0.8;
  margin: 0 0 0.5rem 0;
  line-height: 1.4;
}

@media (max-width: 520px) {
  .zu-lang-buttons { grid-template-columns: 1fr; }
  .zu-lang-btn { flex-direction: row; padding: 1rem; }
}

/* ---- Admin cohort summary (P2: intent-admin-results-integrity) ---- */
.zu-summary-title { color: var(--zu-primary-darker); margin: 0.5rem 0 0.25rem; }
.zu-summary-body { margin-top: 1rem; }
.zu-summary-h3 { color: var(--zu-primary-darker); margin: 1.75rem 0 0.75rem; font-size: 1.05rem; }
.zu-summary-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.75rem;
}
.zu-stat {
  background: var(--zu-surface); border: 1px solid var(--zu-border);
  border-radius: 10px; padding: 1rem 1.25rem; text-align: center;
}
.zu-stat-num { font-size: 1.75rem; font-weight: 700; color: var(--zu-primary); line-height: 1.1; }
.zu-stat-label { font-size: 0.8rem; color: var(--zu-text-muted); margin-top: 0.35rem; }
.zu-summary-table { margin-top: 0.25rem; }
.zu-summary-note { margin-top: 1rem; font-style: italic; }
.zu-histogram {
  display: flex; align-items: flex-end; gap: 0.4rem;
  background: var(--zu-surface); border: 1px solid var(--zu-border);
  border-radius: 10px; padding: 1rem 0.75rem 0.75rem; overflow-x: auto;
}
.zu-histobar { flex: 1 1 0; min-width: 42px; display: flex; flex-direction: column; align-items: center; }
.zu-histobar-track {
  width: 100%; height: 120px; display: flex; align-items: flex-end;
  background: var(--zu-cream-soft); border-radius: 4px;
}
.zu-histobar-fill { width: 100%; background: var(--zu-primary); border-radius: 4px 4px 0 0; min-height: 2px; }
.zu-histobar-n { font-size: 0.75rem; font-weight: 600; color: var(--zu-text); margin-top: 0.25rem; }
.zu-histobar-label { font-size: 0.65rem; color: var(--zu-text-muted); margin-top: 0.15rem; white-space: nowrap; }

/* ============================================================================
 * IELTS Academic skin — mirrors the official computer-delivered IELTS test.
 * Scoped to body.ielts-exam so YADAK / Goethe / medical keep their own look.
 * Applied by test.js when content.level === 'IELTS'. Design cues:
 *   - Arial throughout, black text on white
 *   - clean top bar; grey "Part N" header strip
 *   - split reading view (passage left / questions right) with a divider
 *   - review strip: answered = underlined number, current = solid dark box
 * ==========================================================================*/
body.ielts-exam {
  --ct-bg: #ffffff;
  --ct-surface: #ffffff;
  --ct-banner-bg: #eceff1;
  --ct-bg-alt: #f4f6f7;
  --ct-teal: #2a6b7c;          /* muted IELTS teal-blue accent */
  --ct-teal-dark: #1d4e5a;
  --ct-teal-soft: #e3eef1;
  --ct-border: #d7dce0;
  --ct-border-strong: #b7c0c6;
  --ct-text: #1a1a1a;
  background: #ffffff;
  font-family: Arial, Helvetica, "Segoe UI", sans-serif;
  /* Inspera's player runs on a 16px/1.5 base. Our runner default is 21px/1.6,
     which inherited into the header (76.5px vs 56px), the bottom nav (76px vs
     53px) and every un-sized label. Measured on the live player. */
  font-size: 16px;
  line-height: 1.5;
  /* bottom nav is 53px here, not the runner's 76px */
  padding-bottom: 53px;
}
body.ielts-exam,
body.ielts-exam .ct-passage,
body.ielts-exam .ct-q-prompt,
body.ielts-exam .ct-q-option,
body.ielts-exam .ct-banner-title,
body.ielts-exam .ct-banner-body,
body.ielts-exam .ct-writing-prompt,
body.ielts-exam textarea,
body.ielts-exam input,
body.ielts-exam select,
body.ielts-exam button {
  font-family: Arial, Helvetica, "Segoe UI", sans-serif;
}

/* Top bar — clean white, thin bottom rule.
   Inspera measures exactly 1920x56 with an edge-to-edge topbar: no header
   padding, the brand inset 16px, and the icon cluster flush to the right
   (rightmost button ends at 1904 = 1920-16). Ours was 76.5px tall with a
   188px inline padding (max(28px, (100%-1600px)/2+28px)), which inset the
   whole bar and pushed all content down. */
body.ielts-exam .ct-header {
  background: #ffffff;
  border-bottom: 1px solid #c1c1c1;
  box-shadow: none;
  padding-block: 0;
  padding-inline: 16px;
  min-height: 56px;
  gap: 16px;
}
body.ielts-exam .ct-header-id-value { color: var(--ct-text); }
/* Inspera's ID label is plain sentence-case bold black at 16px — not an
   all-caps grey micro-eyebrow. */
body.ielts-exam .ct-header-id-label {
  font-size: 16px;
  color: #000;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
}
/* Inspera's header icons are chromeless full-height glyphs (no border, no
   radius, no background) — 24px icon + 16px margin each side. Ours drew
   small bordered rounded 36px boxes. */
body.ielts-exam .ct-options-btn,
body.ielts-exam .ct-notes-btn {
  border: none;
  border-radius: 0;
  background: transparent;
  height: 56px;
  width: auto;
  padding: 0 16px;
  color: #333;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
body.ielts-exam .ct-notes-btn { font-size: 24px; line-height: 24px; }
/* Inspera's glyphs are 24px FontAwesome icons in rgb(51,51,51) with a 16px
   margin each side, giving an effective 32px between adjacent icons. Ours were
   hand-built CSS bars (18x14) and a raw Unicode pencil at 20px — visibly
   smaller and darker. Now real 24px icons at #333. */
body.ielts-exam .ct-icon { width: 24px; height: 24px; display: block; color: #333; }
/* Connection status — Inspera's leftmost header glyph. */
body.ielts-exam .ct-conn-status,
body.ielts-exam .ct-notif-status {
  display: inline-flex;
  align-items: center;
  height: 56px;
  padding: 0 16px;
  color: #333;
}
body.ielts-exam .ct-conn-status--offline { color: #b3261e; }
body.ielts-exam.ct-contrast .ct-notif-status { color: var(--cc-fg); }
/* Notifications bell is now a real button (opens the list, clears the dot). */
body.ielts-exam button.ct-notif-status {
  position: relative;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
}
body.ielts-exam button.ct-notif-status:focus-visible {
  outline: 2px solid #2f6feb;
  outline-offset: -2px;
}
/* Unread indicator — a red dot on the bell, shown only when there is something
   new. This is the signal the candidate was missing (client QA). */
body.ielts-exam .ct-notif-badge {
  position: absolute;
  top: 10px;
  right: 12px;
  min-width: 9px;
  height: 9px;
  padding: 0 3px;
  border-radius: 9px;
  background: #d3200f;
  border: 2px solid #fff;
  box-sizing: content-box;
  color: #fff;
  font-size: 10px;
  line-height: 9px;
  font-weight: 700;
  text-align: center;
  display: none;
}
body.ielts-exam .ct-notif-status--unread .ct-notif-badge { display: block; }
body.ielts-exam.ct-contrast .ct-notif-badge { border-color: var(--cc-bg); }
/* Small popover listing the notifications, anchored under the header. */
body.ielts-exam .ct-notif-pop {
  position: fixed;
  top: 60px;
  right: 12px;
  width: 300px;
  max-width: calc(100vw - 24px);
  max-height: 60vh;
  overflow-y: auto;
  background: #fff;
  color: #1a1a1a;
  border: 1px solid #d5d5d5;
  border-radius: 6px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
  z-index: 1200;
  padding: 6px 0;
}
body.ielts-exam .ct-notif-pop-title {
  padding: 6px 14px 8px;
  font-weight: 700;
  font-size: 14px;
  border-bottom: 1px solid #ededed;
}
body.ielts-exam .ct-notif-item {
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.4;
  border-bottom: 1px solid #f2f2f2;
}
body.ielts-exam .ct-notif-item:last-child { border-bottom: 0; }
body.ielts-exam .ct-notif-item-time { display: block; color: #777; font-size: 11px; margin-top: 2px; }
body.ielts-exam .ct-notif-empty { padding: 14px; font-size: 13px; color: #777; }
body.ielts-exam.ct-contrast .ct-notif-pop { background: var(--cc-bg); color: var(--cc-fg); border-color: var(--cc-fg); }
body.ielts-exam .ct-timer {
  background: #1a1a1a;
  color: #ffffff;
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}
body.ielts-exam .ct-timer--warn { background: #b26a00; color: #fff; }
body.ielts-exam .ct-timer--urgent { background: #b3261e; color: #fff; }

/* "Part N" header strip — grey band, bold title, instruction beneath */
body.ielts-exam .ct-banner {
  /* M05 — the beige is on .ct-banner-wrap; keep the inner box transparent so
     the banner reads as a single panel, not a box-in-a-box. */
  background: transparent;
  border-left: none;
  border-bottom: none;
  border-radius: 0;
}
/* Inspera's banner title is 16px at line-height:normal (an 18px box). Ours
   inherited 17px/25.5 — a 25.5px box. That 7.5px was the ENTIRE banner-height
   delta (79.5 vs 72), which in turn pushed both scroll panes 8.5px down and made
   them 8.5px shorter than Inspera's. One line fixes the whole cascade. */
body.ielts-exam .ct-banner-title { font-weight: 700; color: #1a1a1a; font-size: 16px; line-height: normal; }
body.ielts-exam .ct-banner-body { color: #333; }

/* Inspera uses the FULL viewport width — main spans 0→1920 and the passage
   column is ~928px wide. Our default caps content at 1600px with 160px gutters,
   which made the reading area visibly narrower than the live player. Widen the
   IELTS skin to match, scoped so medical / yadak / german keep their 1600px
   centred column. Inspera insets content 16px from each edge:
     • main: full width, 16px side padding (passage inner lands at x=16)
     • banner: full width minus a 16px margin (beige box 16→1904), 16px padding
       so the title lands at x≈32 (Inspera x=33). */
body.ielts-exam .ct-main { max-width: none; padding: 0 16px; }
body.ielts-exam .ct-banner-wrap { max-width: none; margin: 16px; padding: 12px 16px; }
body.ielts-exam .ct-banner { padding: 0; }

/* ── Fixed app shell: two INDEPENDENT scroll panes ─────────────────────────
   Inspera's reading view never scrolls the page — measured at 1080, its
   document scrollHeight === clientHeight. Instead the passage and the question
   list are two independent panes of equal fixed height (both 867px, y=160→1027)
   between the banner and the footer, each carrying its own scrollbar.

   Ours gave a pane only to the QUESTION column; the passage grew the document
   instead, so scrolling the passage scrolled the whole PAGE — dragging the
   header, banner, footer and the divider grip along with it. That is exactly why
   the grip started high and only drifted toward the middle as you scrolled.

   Rebuilt as a flex-column shell: the chrome stays put and each zone scrolls on
   its own. Scoped to body.ielts-exam, so medical / yadak / german keep the
   classic scrolling-page layout. */
body.ielts-exam {
  height: 100vh;
  overflow: hidden;
  padding-bottom: 0;        /* the nav is in flow now, not fixed-overlaid */
  display: flex;
  flex-direction: column;
}
body.ielts-exam .ct-header,
body.ielts-exam .ct-banner-wrap { flex: 0 0 auto; }
body.ielts-exam .ct-main {
  flex: 1 1 auto;
  min-height: 0;            /* lets the panes, not the page, take the overflow */
  /* margin must be 0, NOT the inherited `36px auto`: in a flex column an `auto`
     cross-axis margin makes the item shrink-to-fit and centre instead of
     stretching, which centred the listening notes. */
  margin: 0;
  align-self: stretch;
  overflow-y: auto;         /* single-column parts (listening) scroll here */
}
/* Two-column parts hand the scrolling down to the panes themselves. */
body.ielts-exam .ct-main:has(.ct-two-col) { overflow: hidden; }
body.ielts-exam .ct-part:has(.ct-two-col) { height: 100%; }
body.ielts-exam .ct-two-col {
  height: 100%;
  align-items: stretch;     /* both panes take the full shell height */
}
body.ielts-exam .ct-two-col > .ct-col {
  height: 100%;
  min-height: 0;
  overflow-y: auto;
  /* Each zone carries its OWN scrollbar — that's the indicator Inspera shows
     per pane. It leaves them on the platform default (scrollbar-width:auto);
     our legacy question pane forced a thin custom one. */
  scrollbar-width: auto;
  scrollbar-color: auto;
}
/* The legacy design pinned the question column with position:sticky against a
   scrolling body. In the fixed shell it is simply a pane.
   overflow-y:scroll (not auto) matches Inspera, which permanently reserves the
   question pane's scrollbar gutter — so the questions don't shift sideways by a
   scrollbar-width the moment the content grows past the fold. */
body.ielts-exam .ct-two-col > .ct-col:nth-child(2) {
  position: static;
  top: auto;
  max-height: none;
  overflow-y: scroll;
}
body.ielts-exam .ct-bottom-nav { position: static; flex: 0 0 auto; }
/* With a fixed shell the grip no longer has to chase the scroll with
   position:sticky — it sits dead-centre of the divider, permanently. */
body.ielts-exam .ct-col-resizer-grip {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
}

/* Split reading view — clear vertical divider between passage and questions */
/* IELTS parity: no per-question bookmark flag (see addBookmark() early-return).
   The "bookmark-only" prompt row now holds nothing — collapse it — and suppress
   any nav dot from a stale localStorage flag written before this change. */
body.ielts-exam .ct-q-prompt--bookmark-only { display: none; }
body.ielts-exam .ct-nav-num--bookmarked::after { display: none; }

body.ielts-exam .ct-two-col {
  gap: 0;
  border-top: 1px solid #e3e7ea;
}
body.ielts-exam .ct-two-col > .ct-col:first-child {
  padding-right: 16px;   /* Inspera: passage content 16→944, 16px gap to divider */
  border-right: 1px solid #cfd6da;
}
body.ielts-exam .ct-two-col > .ct-col:nth-child(2) { padding-left: 16px; }
/* Inspera passage: Arial 16px / line-height 24px (1.5), pure black #000. */
body.ielts-exam .ct-passage { font-size: 16px; line-height: 1.5; color: #000; }
body.ielts-exam .ct-passage h3,
body.ielts-exam .ct-passage-subhead-name { color: #000; font-family: Arial, Helvetica, sans-serif; }
/* Inspera's passage title measures 17.6px / 21.12px line-height, no tracking. */
body.ielts-exam .ct-passage h3,
body.ielts-exam .ct-two-col > .ct-col > h3 {
  font-size: 17.6px;
  line-height: 21.12px;
  font-weight: 700;
  letter-spacing: normal;
}
/* Passage standfirst — the line between the title and the first lettered
   paragraph ("Ideal Homes" / "New ideas and some old ones"). Client QA F012:
   "change the font: subtitle has the same font as the text". It used to fall
   into the body and render as ordinary copy, flattening title -> subtitle ->
   body into one level. Sits between the two: heavier than body, lighter and
   smaller than the title. A weight-only cue (600 vs body 400) read too close to
   body at the same size/family/colour — the client re-reported it still "has the
   same font as the text". Add an italic style so the subtitle is unmistakably a
   distinct level (the Cambridge IELTS standfirst is set in italic), keep it
   subordinate to the bold upright title. */
body.ielts-exam .ct-passage-standfirst,
body.ielts-exam .ct-two-col > .ct-col > .ct-passage-standfirst {
  margin: 0 0 14px;
  font-size: 16px;
  line-height: 1.4;
  font-weight: 600;
  font-style: italic;
  color: #000;
}
body.ielts-exam.ct-text-large .ct-passage-standfirst { font-size: 19px; }
body.ielts-exam.ct-text-xlarge .ct-passage-standfirst { font-size: 22px; }
body.ielts-exam.ct-contrast .ct-passage-standfirst { color: var(--cc-fg); }
/* Note/table completion bodies run at 16px/24px in Inspera; ours was 2.1 ratio. */
/* Inspera 1:1 — the Listening note lines are SPACIOUS on the demo (measured ~48px
   per line; text and gap lines alike), not tightly stacked. Ours shipped at 1.5
   (24px), which the owner flagged as too tight vs the demo's generous spacing.
   Match ~48px per line. Note lines are short and don't wrap at exam width, so
   driving this via line-height is safe. */
body.ielts-exam .ct-completion-body { line-height: 3.0; }
/* delta map: Inspera's note container is 16px (.container.partWidth); ours
   inherited a 21px ambient from .ct-part, inflating any child without an
   explicit size by ~31%. */
body.ielts-exam .ct-completion { font-size: 16px; }

/* Questions — plain numbered squares, IELTS-flat radios */
body.ielts-exam .ct-question { border-radius: 2px; }
/* (Active-question styling is defined authoritatively in the Inspera-match
   block below — transparent card, blue number box only.) */
/* ============================================================
   Inspera-match skin (see audits/inspera-ref/GUIDE.md)
   Emulates the official IELTS on-screen player's look: neutral
   beige part banner, plain rubric, active-blue accents, and the
   completion number shown INSIDE the input box.
   ============================================================ */
body.ielts-exam { --ct-active: #418ec8; }   /* Inspera interaction blue (measured on the live player) */

/* D4 — question number: plain bold by default, blue box only when active.
   Inspera: 16px/700, active box = 2px solid #418ec8 on a transparent fill. */
body.ielts-exam .ct-q-num-badge {
  background: transparent;
  border: 2px solid transparent;
  color: #000;
  border-radius: 2px;
  font-size: 16px;
  font-weight: 700;
  padding: 0 4px 0 0;
  min-width: auto;
}
body.ielts-exam .ct-question--active .ct-q-num-badge {
  background: transparent;
  border-color: var(--ct-active);
  padding: 3px 2px;
  min-width: 21px;
}
/* D3 — the completion number lives INSIDE the input box (placeholder),
   so the separate badges are hidden and the box shows a centered number. */
/* Text-input gaps carry their number in the placeholder, so the .ct-kwt-gap-num
   badge is hidden. Dropdown gaps CAN'T hold a placeholder number, so
   .ct-inline-gap-num stays visible as a badge before the <select> (H01). */
body.ielts-exam .ct-kwt-gap-num { display: none; }
/* The .ct-kwt-gap wrapper draws its OWN border + fixed 34px height so that in
   the CAE/Yadak skin (where .ct-kwt-input is border:none) it forms the gap box.
   In the IELTS skin the input carries its own 1px border (rule below), so the
   wrapper's box became a SECOND, taller rectangle nested around it — the number
   pinned to the top of an oversized double-box ("weird placeholder with extra
   lines"). Neutralise the wrapper here so the input's own border is the single
   box, matching the reading-cloze (.ct-inline-input) gaps. */
body.ielts-exam .ct-kwt-gap {
  border: none;
  border-radius: 0;
  height: auto;
  background: transparent;
  overflow: visible;
  align-items: center;
  margin: 0 4px;
}
body.ielts-exam .ct-kwt-gap:focus-within { border-color: transparent; box-shadow: none; }
/* Gap-fill boxes. Inspera: 22.4px tall, 104px wide, 1px #535353 dark-grey
   border at rest, switching to the blue #418ec8 (+1px ring) only when active.
   Ours were 37.6px tall, 140px wide and a light grey that never changed. */
body.ielts-exam .ct-inline-input,
body.ielts-exam .ct-kwt-input,
body.ielts-exam .ct-gap-input--block {
  text-align: center;
  border: 1px solid #535353;
  border-radius: 3px;
  background: #fff;
  height: 22.4px;
  padding: 0 8px;            /* delta map: Inspera 0px 8px (was 0 4px) */
  font-size: 16px;
  line-height: normal;       /* delta map: Inspera line-height normal (was 20px) */
  box-sizing: border-box;
}
/* Reading inline (cloze/open-cloze): compact 104px like the demo. */
body.ielts-exam .ct-inline-input { width: 104px; min-width: 56px; max-width: 140px; }
/* Listening KWT (sentence/note completion): wider box — Inspera listening gaps
   are visibly wider than reading cloze gaps because answers can be longer words. */
body.ielts-exam .ct-kwt-input { width: 180px; min-width: 80px; max-width: 220px; }
/* Inspera 1:1 — tight note lines. The listening sentence-completion rows shipped
   at line-height 2.2 + 8px vertical padding (≈71px per line); the demo stacks the
   note lines tightly (≈24px). Match the compact rhythm the other forms already use. */
body.ielts-exam .ct-listen-sc-row { padding-top: 1px; padding-bottom: 1px; margin: 0; }
body.ielts-exam .ct-listen-sc-row .ct-kwt-lead-row { line-height: 1.5; }
body.ielts-exam .ct-inline-input::placeholder,
body.ielts-exam .ct-kwt-input::placeholder,
body.ielts-exam .ct-gap-input--block::placeholder { color: #64748b; font-weight: 700; opacity: 1; }
body.ielts-exam .ct-inline-input:focus,
body.ielts-exam .ct-kwt-input:focus,
body.ielts-exam .ct-gap-input--block:focus { border-color: var(--ct-active); box-shadow: 0 0 0 1px var(--ct-active); outline: none; }
/* Kill the global `body.zu-test-body *:focus-visible { outline: 2px …;
   outline-offset: 2px }` on gap inputs. That offset outline sat OUTSIDE the box's
   own blue border + 1px ring and read as a "double box" around the number. The
   blue border+ring is already a clear (WCAG-visible) focus indicator and matches
   Inspera, which shows no outer outline on the gap. Covers :focus-visible, which
   the :focus rule above doesn't. */
body.ielts-exam .ct-inline-input:focus-visible,
body.ielts-exam .ct-kwt-input:focus-visible,
body.ielts-exam .ct-gap-input--block:focus-visible { outline: none; outline-offset: 0; }
/* delta map: Inspera rings the gap of the ACTIVE (current) question blue
   (border rgb(65,142,200) + 1px inset shadow) even before it is focused;
   ours applied no visual change on .ct-question--active. */
body.ielts-exam .ct-question--active .ct-inline-input,
body.ielts-exam .ct-question--active .ct-kwt-input,
body.ielts-exam .ct-question--active .ct-gap-input--block { border-color: var(--ct-active); box-shadow: 0 0 0 1px var(--ct-active); }

/* D1 — neutral beige part banner (was a blue-gray tint). */
body.ielts-exam .ct-banner-wrap { background: #f1f2ec; border: 1px solid #d5d5d5; border-radius: 4px; }

/* Column divider. Inspera draws a 2px rgba(0,0,0,.5) rule and a 32x32 grip
   bordered ONLY on its left and right edges (2px #535353) — no top/bottom
   border, no radius, no shadow. Ours was a 1px light rule with a fully
   boxed, rounded, shadowed 22x40 grip. */
body.ielts-exam .ct-col-resizer::before { width: 2px; background: rgba(0, 0, 0, 0.5); }
body.ielts-exam .ct-col-resizer-grip {
  width: 32px;
  height: 32px;
  border: none;
  border-left: 2px solid #535353;
  border-right: 2px solid #535353;
  border-radius: 0;
  box-shadow: none;
}

/* D2 — group rubric / completion rubric as plain text, no colored box. */
body.ielts-exam .ct-q-group-intro,
body.ielts-exam .ct-completion-rubric {
  background: transparent;
  border: none;
  border-left: none;
  padding: 2px 0 6px;
  color: #1a1a1a;
  font-size: 15px;
}
/* Inspera 1:1 — the "Questions X–Y" heading above each listening group's rubric
   is bold 16px, tight to the rubric below it (see the two-tier banner note in
   tryRenderInlineCompletion). */
body.ielts-exam .ct-completion-qrange {
  font-size: 16px;
  font-weight: 700;
  color: #1a1a1a;
  margin: 0;
  padding: 2px 0 2px;
}
/* Active question: Inspera highlights ONLY the question number (blue box,
   see rule above) — the card itself has no fill or border. Match exactly. */
body.ielts-exam .ct-question--active {
  background: transparent;
  border-color: transparent;
}

/* IELTS exam header brand. Uses the GENUINE IELTS wordmark artwork
   (assets/ielts-logo.svg) rather than a hand-styled red Arial "IELTS" text.
   Inspera renders its logo at 92.5x27.19 with the image sitting at x=16 and the
   logo wrapper carrying its own 16px right padding ON TOP of the header's
   inter-item gap — that extra 16px is why our ID block sat 16.7px too far left. */
body.ielts-exam .ct-logo { display: none; }
body.ielts-exam .ct-brand-sub { display: none; }
body.ielts-exam .ct-logo-ielts {
  display: block;
  width: 92.5px;
  height: 27.19px;
  padding-right: 16px;
  box-sizing: content-box;
}
body.ielts-exam .ct-header-brand { padding: 0; gap: 0; }

/* #3/#6 — bold paragraph/section labels in the passage. */
.ct-para-label { font-weight: 800; color: #1a1a1a; }
body.ielts-exam .ct-para-label { color: #000; }

/* Matching reference list: Inspera shows the option letters in plain dark
   (our default teal is a CAE-skin leftover). */
body.ielts-exam .ct-reference-key { color: #1a1a1a; }
body.ielts-exam .ct-reference-list { background: #fafafa; border-color: #d7dde3; }
/* ...but that #fafafa fill is a LIGHT-THEME literal, and this rule never had a
   high-contrast counterpart. In the dark schemes the catch-all correctly flips the
   option text to white/yellow while the box kept its near-white fill — measured
   white #ffffff on #fafafa, a contrast ratio of 1.04:1. That is the "empty pale
   rectangle" in six client screenshots across four QA documents (F003): the whole
   A–G answer bank was invisible, making every matching question unanswerable for
   anyone using the accessibility contrast modes.
   Consumes the --cc-* tokens like every other boxed surface (banner, notes panel),
   so it now follows the theme instead of restating a hex. Matches the Inspera
   reference, where boxed panels go dark with a light hairline
   (audits/inspera-ref/chrome-03-contrast-white-on-black.png). */
body.ielts-exam.ct-contrast .ct-reference-list {
  background: var(--cc-panel);
  border-color: var(--cc-border);
}
body.ielts-exam.ct-contrast .ct-reference-key,
body.ielts-exam.ct-contrast .ct-reference-text { color: var(--cc-fg); }
/* Same defect, second surface — found by the contrast sweep that confirmed F003,
   not by the client. The pre-play modal card is a hard-coded #fff with no
   high-contrast counterpart, so in the dark schemes the catch-all turns its
   instructions white and leaves the card white: the "You will be listening to an
   audio clip… you will not be permitted to pause or rewind" text is invisible.
   That modal is the gate every listening part opens with, and its whole job is
   to be read once before audio that cannot be replayed. */
body.ielts-exam.ct-contrast .ct-preplay-card {
  background: var(--cc-panel);
  border: 1px solid var(--cc-border);
}
body.ielts-exam.ct-contrast .ct-preplay-text { color: var(--cc-fg); }
/* The Play button keeps a solid fill so it stays the obvious affordance, but takes
   the theme accent rather than the light-theme blue. */
body.ielts-exam.ct-contrast .ct-preplay-btn {
  background: var(--cc-badge);
  color: #000;
}
/* IELTS matching uses dropdowns; keep any drop highlight on the active-blue. */
body.ielts-exam .ct-match-drop--filled,
body.ielts-exam .ct-match-drop--over { border-color: var(--ct-active); background: #eef5fe; }

/* Writing: Inspera renders the Task prompt in bold pure black. Match it. */
body.ielts-exam .ct-writing-prompt { font-weight: 700; color: #000; }
/* Inspera renders the question statement at NORMAL weight (400) — only the
   number badge is bold. Ours bolded the whole prompt row. */
body.ielts-exam .ct-q-prompt { font-weight: 400; font-size: 16px; margin-bottom: 4px; } /* delta map: prompt→control gap 4px (was 8px) */
/* Same rule, third surface (client QA F025: "Text is too bald and must start with
   capital letter" — reading "bald" as bold). A DRAG question's statement inherited
   .ct-match-slot-head's 700, while the DROPDOWN variant's .ct-match-dd-text sat at
   400 — the same kind of content rendered at two weights depending on which widget
   happened to render it. Inspera puts every question statement at normal weight and
   bolds only the number badge, which .ct-q-num-badge still does. */
body.ielts-exam .ct-match-slot-label { font-weight: 400; }
/* Answer options. Inspera: white row, 43.2px tall, and a light-blue
   rgb(187,216,240) fill on the SELECTED option (we had no selected fill). */
body.ielts-exam .ct-q-option {
  font-size: 16px;
  color: #000;
  background: #fff;
  padding: 9.6px 8px;
  border-radius: 0;
  min-height: 43.2px;
  align-items: center;
  /* Inspera fades the row background over 0.2s ease-in. */
  transition: background-color 0.2s ease-in;
}
/* Hover greys the row. Live-measured on the player: white -> rgb(228,228,228)
   on hover -> rgb(187,216,240) when selected. Our base hover was #f9fafb, a
   near-white that read as no feedback at all — this is the tactile cue that
   makes the options feel responsive.
   A SELECTED row keeps its blue on hover (verified live). No ordering trick is
   needed: :has(input:checked) scores 0-3-1 against :hover's 0-3-0, so the blue
   wins on specificity. */
body.ielts-exam .ct-q-option:hover { background: rgb(228, 228, 228); }
body.ielts-exam .ct-q-option:has(input:checked) { background: rgb(187, 216, 240); }
/* Bottom review strip — the signature IELTS navigator.
   Inspera: 53px tall, white, no top border, no shadow. */
body.ielts-exam .ct-bottom-nav {
  background: #ffffff;
  border-top: none;
  box-shadow: none;
  height: 53px;
  font-size: 16px;
}
body.ielts-exam .ct-nav-part { background: transparent; }
body.ielts-exam .ct-nav-part--active { background: #ffffff; border-color: #cfd6da; }
body.ielts-exam .ct-nav-part-label { color: #333; font-weight: 400; text-transform: none; }
/* Inactive-part "N of M" counts: Inspera 16px in a neutral grey. */
body.ielts-exam .ct-nav-part-count { font-size: 16px; color: #757575; }
/* Inspera's number chips measure 21x27 on a 21px pitch (no gap), 16px text
   in #333; the active chip is a WHITE fill with a 2px blue border. */
body.ielts-exam .ct-nav-num {
  min-width: 21px;
  height: 27px;
  /* Inspera's chip has 4px horizontal padding, so single digits render at
     20.9px but DOUBLE digits grow to ~29.8px (padding keeps the number off the
     box edge). With padding:0 our double-digit chips were cramped at 21.8px;
     the 4px matches Inspera's per-digit growth exactly. */
  padding: 0 4px;
  border: 2px solid transparent;
  border-radius: 3px;       /* Inspera button.subQuestion border-radius: 3px */
  background: transparent;
  color: #333;
  font-size: 16px;
  font-weight: 400;
  box-shadow: none;
}
body.ielts-exam .ct-nav-num:hover { border-color: #1a1a1a; color: #1a1a1a; }
body.ielts-exam .ct-nav-part-numbers { gap: 0; }

/* ── Footer progress strip ─────────────────────────────────────────────────
   Inspera draws a 3px dash along the very top edge of the footer (y=1027): one
   above the ACTIVE part's label cell, and one above EACH of that part's question
   chips. A question's dash turns GREEN the moment it's answered — that's the
   "finished" cue. Inactive parts carry no dashes.

   Live-measured: grey rgb(215,215,215), green rgb(53,142,20), height 3px; the
   chip dash is (chip width - 3px), leaving a 3px gap between neighbours, and the
   label-cell dash is 98.5px. `calc(100% + 1px)` reproduces the measured widths
   exactly: an absolutely-positioned ::before resolves 100% against the chip's
   PADDING box (border-box 21 - 4 border = 17 -> 18px, vs Inspera's 17.9; and the
   double-digit chip 29.8 - 4 = 25.8 -> 26.8, matching Inspera's 26.8). */
body.ielts-exam .ct-nav-part--active { position: relative; }
body.ielts-exam .ct-nav-part--active::before {
  content: '';
  position: absolute;
  top: 0;            /* the cell's padding box already starts at the footer top */
  left: 1px;
  width: 98.5px;
  height: 3px;
  background: #d7d7d7;
}
body.ielts-exam .ct-nav-num { position: relative; }
body.ielts-exam .ct-nav-num::before {
  content: '';
  position: absolute;
  top: -15px;        /* lifts the dash from the chip up onto the footer's top edge */
  left: 0;
  width: calc(100% + 1px);
  height: 3px;
  background: #d7d7d7;
}
/* Answered -> green. The --answered class is still applied by renderBottomNav;
   the chip glyph itself stays undecorated (Inspera parity), so the dash is the
   sole progress cue — exactly as the live player does it. */
body.ielts-exam .ct-nav-num--answered::before { background: #358e14; }
/* Answered chips carry NO distinct styling — verified against the live player:
   Inspera's button.subQuestion.attempted (answered, not active) renders
   identically to an untouched chip (400 weight, #333, no underline, no fill).
   The footer's only styled state is the ACTIVE chip. So answered inherits the
   base chip look; no rule needed. */
/* current → blue-outline box (white fill, BOLD dark number). Verified live:
   the active chip's inner span is font-weight 700 (my earlier "Inspera does
   not bold the active chip" note was a mismeasurement). */
body.ielts-exam .ct-nav-num--active,
body.ielts-exam .ct-nav-num--active:hover {
  background: #ffffff;
  border-color: var(--ct-active);
  color: #333;              /* Inspera chip text is rgb(51,51,51) */
  font-weight: 700;         /* Inspera bolds the active chip's number */
  box-shadow: none;
}
body.ielts-exam .ct-nav-arrow,
body.ielts-exam .ct-nav-finish {
  background: #1a1a1a;
  border-radius: 3px;
}
body.ielts-exam .ct-nav-finish { background: var(--ct-active); }

/* D8 — Inspera-style draggable column divider. A thin line down the gap with
   a centered ↔ grip that stays near the viewport middle while the passage
   scrolls. Only in the IELTS skin; JS wires the drag (initColumnResizer). */
body.ielts-exam .ct-two-col { position: relative; }
.ct-col-resizer {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 24px;
  transform: translateX(-50%);
  z-index: 6;
  cursor: col-resize;
  touch-action: none;
}
.ct-col-resizer::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 1px;
  background: var(--ct-border-strong);
  transform: translateX(-50%);
}
.ct-col-resizer-grip {
  position: sticky;
  top: calc(50vh - 60px);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 40px;
  margin: 0 auto;
  background: #fff;
  border: 1px solid var(--ct-border-strong);
  border-radius: 4px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}
.ct-col-resizer-grip::before { content: '\2194'; color: #6b7280; font-size: 13px; line-height: 1; }
.ct-col-resizer:hover .ct-col-resizer-grip { border-color: var(--ct-active); }
.ct-col-resizer:hover .ct-col-resizer-grip::before { color: var(--ct-active); }
body.ct-resizing { cursor: col-resize; user-select: none; }
@media (max-width: 880px) { .ct-col-resizer { display: none; } }

/* ===== Contrast themes (Options → Contrast): Inspera parity =====
   .ct-contrast = dark scheme (white text); .ct-contrast-yb = yellow text.
   Charts/images are left untouched, matching Inspera. */
/* Per-theme accent palette — measured from the live player. Inspera themes the
   two dark schemes with DIFFERENT accent hues: white-on-black is amber, while
   yellow-on-black uses cyan (badge), olive (active chip) and blue (divider).
   Exposed as per-property vars so each rule reads the right hue per theme. */
body.ielts-exam.ct-contrast {
  /* Neutral scaffolding tokens (shared by both dark schemes; the .ct-contrast-yb
     block overrides only the accent tokens below). A new box surface consumes
     these instead of re-enumerating a hex, so it can't miss the theme (F007). */
  --cc-bg: #000000;              /* primary surface: page, header, nav, inputs, boxes, panels */
  --cc-panel: #1a1a1a;           /* raised panel: banner, notes sidebar, options-row hover */
  --cc-raised: #222222;          /* elevated chip: timer, active nav-part cell */
  --cc-border: #555555;          /* standard hairline border */
  --cc-border-strong: #777777;   /* stronger border: match chips/drops, active nav-part */
  --cc-border-input: #999999;    /* form-field border */
  --cc-border-soft: #666666;     /* invigilator / paused-curtain border */
  --cc-muted: #bbbbbb;           /* muted text: placeholders, hints */
  --cc-danger: #ff6b6b;          /* offline / error accent */
  --cc-arrow-disabled-bg: #515151; /* disabled prev-arrow plate */
  --cc-arrow-disabled-fg: #1b1b1b; /* disabled prev-arrow glyph */
  --cc-done-fg: #7bd88a;         /* completed nav-part label */
  --cc-done-bar: #4caf50;        /* completed nav-part top bar */
  --cc-fg: #ffffff;
  --cc-badge: #be7137;      /* active question badge border */
  --cc-chip-bd: #be7137;    /* active footer chip border */
  --cc-chip-bg: #000000;    /* active footer chip fill */
  --cc-chip-fg: #ffffff;    /* active footer chip number */
  --cc-div: #d59369;        /* passage/question divider */
  --cc-sel: #44270f;        /* selected answer-option row fill */
}
body.ielts-exam.ct-contrast.ct-contrast-yb {
  --cc-fg: #ffff00;
  --cc-badge: #00ffff;
  --cc-chip-bd: #b3b300;
  --cc-chip-bg: #808000;
  --cc-chip-fg: #000000;
  --cc-div: #2a6c96;
  --cc-sel: #002b2b;        /* selected row: dark teal (live-measured) */
}
body.ielts-exam.ct-contrast,
body.ielts-exam.ct-contrast .ct-main { background: var(--cc-bg); color: var(--cc-fg); }
/* Uniform foreground for all main content text (so yellow-on-black is truly
   uniform), excluding form controls, number badges and the divider grip. */
body.ielts-exam.ct-contrast .ct-main *:not(input):not(select):not(textarea):not(.ct-q-num-badge):not(.ct-nav-num):not(.ct-col-resizer-grip):not(.ct-hl):not(.ct-hl--note) { color: var(--cc-fg); }
/* Highlights keep a LIGHT yellow background, so the catch-all's white/yellow
   foreground rendered highlighted text invisible on it (measured: white
   rgb(255,255,255) on rgb(255,245,157)). Excluded above and pinned to black
   here — a highlight must stay readable in the high-contrast themes, which is
   exactly where a candidate relies on it. */
/* Answer options in the dark themes. The light-blue selected fill left white/
   yellow text unreadable; measured on the live player, an UNselected row is
   transparent and the SELECTED row takes a dark theme fill (white-on-black:
   dark amber #44270f; yellow-on-black keeps it transparent — the yellow radio
   conveys selection). Text always follows --cc-fg. */
body.ielts-exam.ct-contrast .ct-q-option {
  background: transparent;
  color: var(--cc-fg);
}
/* The light hover (#e4e4e4) would be a blinding slab on the black themes, and
   it would strand white/yellow text on a near-white row. Use a faint lift of the
   theme foreground instead — same cue, theme-correct. */
body.ielts-exam.ct-contrast .ct-q-option:hover { background: rgba(255, 255, 255, 0.14); }
body.ielts-exam.ct-contrast .ct-q-option:has(input:checked) {
  background: var(--cc-sel);
  color: var(--cc-fg);
}
/* Inspera swaps to a white IELTS wordmark in the dark themes; our single SVG is
   inverted to pure white instead of shipping a second asset. */
body.ielts-exam.ct-contrast .ct-logo-ielts { filter: brightness(0) invert(1); }
/* Radio/checkbox dots follow the theme accent (amber on wb) so the selected
   control doesn't stay stock-blue against the amber scheme. */
body.ielts-exam.ct-contrast input[type="radio"],
body.ielts-exam.ct-contrast input[type="checkbox"] { accent-color: var(--cc-badge); }
body.ielts-exam.ct-contrast .ct-hl,
body.ielts-exam.ct-contrast .ct-hl--note { color: #000; background: #fff59d; }
/* Keep the overlap depth in the dark themes: the rule above (0-3-0) would
   otherwise flatten the nested layers (0-2-0) back to plain yellow. Text stays
   pinned black, so the deeper fills remain readable. */
body.ielts-exam.ct-contrast .ct-hl .ct-hl { background: #ffcc80; }
body.ielts-exam.ct-contrast .ct-hl .ct-hl .ct-hl,
body.ielts-exam.ct-contrast .ct-hl .ct-hl .ct-hl .ct-hl { background: #ffab40; }
body.ielts-exam.ct-contrast .ct-header,
body.ielts-exam.ct-contrast .ct-bottom-nav { background: var(--cc-bg); border-color: var(--cc-border); color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-header-id-label,
body.ielts-exam.ct-contrast .ct-header-id-value,
/* Header glyphs are #333 by default (Inspera's grey); in the high-contrast
   themes that is near-invisible on black, so they follow --cc-fg like every
   other foreground. This is the mode a low-vision candidate actually uses. */
/* Question number badges. The catch-all excludes .ct-q-num-badge because the
   badge USED to carry a white fill (so black text was correct). Inspera-parity
   made the fill transparent, which left the number black-on-black — invisible
   in both dark themes. Give it the theme foreground explicitly. */
body.ielts-exam.ct-contrast .ct-q-num-badge { color: var(--cc-fg); background: transparent; }
body.ielts-exam.ct-contrast .ct-question--active .ct-q-num-badge {
  border-color: var(--cc-badge);   /* amber (wb) / cyan (yb) — matches live */
  background: transparent;
  color: var(--cc-fg);
}
body.ielts-exam.ct-contrast .ct-icon,
body.ielts-exam.ct-contrast .ct-conn-status,
body.ielts-exam.ct-contrast .ct-options-btn,
body.ielts-exam.ct-contrast .ct-notes-btn { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-conn-status--offline { color: var(--cc-danger); }
body.ielts-exam.ct-contrast .ct-audio-status { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-timer { background: var(--cc-raised); color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-banner-wrap,
body.ielts-exam.ct-contrast .ct-banner { background: var(--cc-panel); border-color: var(--cc-border); }
body.ielts-exam.ct-contrast .ct-banner-title,
body.ielts-exam.ct-contrast .ct-banner-body,
body.ielts-exam.ct-contrast .ct-passage,
body.ielts-exam.ct-contrast .ct-passage-subhead-name,
body.ielts-exam.ct-contrast .ct-para-label,
body.ielts-exam.ct-contrast .ct-question,
body.ielts-exam.ct-contrast .ct-completion-body,
body.ielts-exam.ct-contrast .ct-completion-rubric,
body.ielts-exam.ct-contrast .ct-q-group-intro,
body.ielts-exam.ct-contrast label,
body.ielts-exam.ct-contrast p,
body.ielts-exam.ct-contrast li { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-inline-input,
body.ielts-exam.ct-contrast .ct-kwt-input,
body.ielts-exam.ct-contrast .ct-gap-input--block,
body.ielts-exam.ct-contrast select,
body.ielts-exam.ct-contrast textarea,
body.ielts-exam.ct-contrast .ct-writing-textarea { background: var(--cc-bg); color: var(--cc-fg); border-color: var(--cc-border-input); }
body.ielts-exam.ct-contrast .ct-inline-input::placeholder,
body.ielts-exam.ct-contrast .ct-kwt-input::placeholder,
body.ielts-exam.ct-contrast .ct-gap-input--block::placeholder,
body.ielts-exam.ct-contrast .ct-writing-textarea::placeholder { color: var(--cc-muted); }
body.ielts-exam.ct-contrast .ct-nav-num,
body.ielts-exam.ct-contrast .ct-nav-part-label,
body.ielts-exam.ct-contrast .ct-nav-part-count { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-nav-part--active { background: var(--cc-raised); border-color: var(--cc-border-strong); }
body.ielts-exam.ct-contrast .ct-nav-num--active {
  background: var(--cc-chip-bg);   /* wb: black fill / yb: olive fill */
  border-color: var(--cc-chip-bd);
  color: var(--cc-chip-fg);        /* wb: white / yb: black on the olive fill */
}
body.ielts-exam.ct-contrast .ct-col-resizer-grip { background: var(--cc-bg); border-color: var(--cc-div); }
/* The base divider line is a half-opacity BLACK hairline — invisible on the
   black dark-mode page. Repaint it in the theme's divider hue (amber / blue). */
body.ielts-exam.ct-contrast .ct-col-resizer::before { background: var(--cc-div); }
/* Options full-screen page in contrast mode */
body.ielts-exam.ct-contrast .ct-options-overlay,
body.ielts-exam.ct-contrast .ct-options-panel { background: var(--cc-bg); color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-invig-btn { color: var(--cc-fg); border-color: var(--cc-border-soft); background: var(--cc-bg); }
body.ielts-exam.ct-contrast .ct-options-panel h2,
body.ielts-exam.ct-contrast .ct-options-panel .ct-options-subheading,
body.ielts-exam.ct-contrast .ct-options-panel .ct-options-close,
body.ielts-exam.ct-contrast .ct-options-panel .ct-options-back-btn { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-options-card,
body.ielts-exam.ct-contrast .ct-options-choice-list { border-color: var(--cc-border); }
body.ielts-exam.ct-contrast .ct-options-item,
body.ielts-exam.ct-contrast .ct-options-choice {
  background: var(--cc-bg); color: var(--cc-fg); border-color: var(--cc-border);
}
body.ielts-exam.ct-contrast .ct-options-item-icon,
body.ielts-exam.ct-contrast .ct-options-item-arrow,
body.ielts-exam.ct-contrast .ct-options-choice-check { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-options-item:hover,
body.ielts-exam.ct-contrast .ct-options-choice:hover { background: var(--cc-panel); }
/* the red submission button keeps its brand red in contrast mode (Inspera does) */

/* Notes panel (pen icon → Inspera "Show notes") */
.ct-notes-btn {
  background: transparent;
  border: none;
  font-size: 20px;
  line-height: 1;
  color: var(--ct-text, #1a1a1a);
  cursor: pointer;
  padding: 6px 8px;
  border-radius: 6px;
}
.ct-notes-btn:hover { background: rgba(0, 0, 0, 0.06); }
.ct-notes-empty { text-align: center; color: var(--ct-text-muted, #64748b); padding: 48px 20px; }
.ct-notes-empty-title { font-size: 1rem; font-weight: 600; margin: 0 0 8px; color: var(--ct-text, #1a1a1a); }
.ct-notes-empty-hint { font-size: 0.9rem; margin: 0; }
.ct-note-item { border: 1px solid var(--ct-border, #e2e8f0); border-radius: 8px; padding: 10px 12px; margin-bottom: 10px; }
.ct-note-quote { font-size: 0.9rem; color: var(--ct-text, #1a1a1a); font-style: italic; line-height: 1.45; }
.ct-note-text { font-size: 0.9rem; color: var(--ct-active, #2b7de0); margin-top: 6px; }
body.ielts-exam.ct-contrast .ct-notes-btn { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-note-item { border-color: var(--cc-border); }
body.ielts-exam.ct-contrast .ct-notes-empty-title,
body.ielts-exam.ct-contrast .ct-note-quote { color: var(--cc-fg); }

/* F007 — matching/heading answer banks + paragraph bank in the dark themes.
   These "box" surfaces ship a near-white fill (.ct-match-bank #fbfcfe,
   .ct-match-chip / --key white, .ct-paragraph-bank the light banner tint). The
   catch-all above (4074) forces their TEXT to white/yellow, so on the untouched
   light fill the answer letters render white-on-white — invisible. Reported live
   (feedback F007: "Choose FOUR answers" box unreadable in black contrast). Pin the
   fills to the theme so the enumerated boxes match the rest of the scheme. */
body.ielts-exam.ct-contrast .ct-match-bank,
body.ielts-exam.ct-contrast .ct-paragraph-bank { background: var(--cc-bg); border-color: var(--cc-border); }
body.ielts-exam.ct-contrast .ct-match-chip {
  background: transparent; border-color: var(--cc-border-strong); box-shadow: none;
}
body.ielts-exam.ct-contrast .ct-match-chip:hover { border-color: var(--cc-badge); }
body.ielts-exam.ct-contrast .ct-match-chip-key {
  background: var(--cc-bg); border-color: var(--cc-border-strong); color: var(--cc-fg);
}
body.ielts-exam.ct-contrast .ct-match-chip--selected {
  background: var(--cc-sel); border-color: var(--cc-badge);
  box-shadow: 0 0 0 2px var(--cc-sel);
}
body.ielts-exam.ct-contrast .ct-match-drop {
  background: var(--cc-bg); border-color: var(--cc-border-strong);
}
body.ielts-exam.ct-contrast .ct-match-drop--filled,
body.ielts-exam.ct-contrast .ct-match-drop--over {
  background: var(--cc-sel); border-color: var(--cc-badge);
}
body.ielts-exam.ct-contrast .ct-match-drop--filled .ct-match-chip-key {
  background: var(--cc-badge); border-color: var(--cc-badge); color: var(--cc-chip-fg);
}
body.ielts-exam.ct-contrast .ct-match-drop-hint { color: var(--cc-muted); }

/* Dropdowns / gap inputs — flat IELTS field style */
body.ielts-exam .ct-match-dd-select,
body.ielts-exam .ct-gap-input,
body.ielts-exam .ct-kwt-input {
  border-radius: 2px;
  border-color: #8a959c;
}
/* delta map: Inspera's gap-fill border is a dark neutral grey rgb(83,83,83);
   the rule above wrongly re-lightened the .ct-kwt-input border to #8a959c,
   clobbering the #535353 set earlier. Restore the dark border for kwt. */
body.ielts-exam .ct-kwt-input { border-color: #535353; }

/* Writing — full-width prompt, chart, and a generous response area */
body.ielts-exam .ct-writing-prompt { font-size: 16px; color: #000; }
/* Inspera's answer box: 12px inner padding all round, 1px #595959 border,
   black text at 16px/24px. Ours had ~30px/34px padding and a lighter border. */
body.ielts-exam .ct-writing-textarea {
  border: 1px solid #595959;
  border-radius: 3px;        /* delta map: Inspera 3px (was 2px) */
  font-size: 16px;
  line-height: normal;       /* delta map: Inspera line-height normal ~18.4px (was 1.5 = 24px) */
  padding: 12px;
  color: #000;
  box-shadow: none;          /* delta map: Inspera field is flat (was 0 1px 3px rgba(0,0,0,.04)) */
  resize: none;              /* delta map: Inspera has no resize handle (was vertical) */
  /* The answer box GROWS with the candidate's text (client request):
     autoGrowTextarea() sets the height from content on every keystroke, so no
     fixed height here. flex:none stops the flex answer column from stretching it;
     min-height gives a comfortable starting box before the first keystroke. */
  flex: none;
  align-self: flex-start;
  min-height: 45vh;
}
body.ielts-exam .ct-listen-map { border-color: #b7c0c6; }

/* Listen-once notice shown when the audio replay cap is reached (G06). */
.ct-audio-consumed {
  margin: 0 0 16px;
  padding: 10px 14px;
  border-left: 3px solid var(--ct-teal, #2b8a8a);
  background: var(--ct-teal-soft, #eef5f7);
  color: #333;
  font-size: 14px;
  border-radius: 2px;
}

/* ===== Text size (Options → Text size) — Inspera's measured ladder =====
   Inspera offers Regular / Large / Extra large and scales body text on a clean
   +3px step at a constant 1.5 line-height (measured on the live player):
       Regular 16px/24px · Large 19px/28.5px · Extra large 22px/33px
   Ours had Small/Medium/Large with Large=22px (i.e. Inspera's EXTRA large), and
   after the rename "Extra large" was inert: body.ct-text-xlarge ties on
   specificity with body.ielts-exam .ct-passage and lost on source order. These
   body.ielts-exam.ct-text-* rules outrank the skin so both tiers actually apply.
   picker can no longer set it (applyTextSize migrates small/medium -> regular).

   Kept from earlier walks: Text Size must reach the writing prompt/textarea
   (F006), the passage/prompts/options (G04), the listening completion renderer's
   own classes (H05), and the reference list — none of which inherit a base size. */

/* --- Large: 19px --- */
body.ielts-exam.ct-text-large .ct-passage,
body.ielts-exam.ct-text-large .ct-q-prompt,
body.ielts-exam.ct-text-large .ct-q-option,
body.ielts-exam.ct-text-large .ct-q-group-intro,
body.ielts-exam.ct-text-large .ct-q-group-headline,
body.ielts-exam.ct-text-large .ct-q-group-rubric,
body.ielts-exam.ct-text-large .ct-kwt-lead-row,
body.ielts-exam.ct-text-large .ct-kwt-input,
body.ielts-exam.ct-text-large .ct-kwt-gap-num,
body.ielts-exam.ct-text-large .ct-completion-body,
body.ielts-exam.ct-text-large .ct-completion-rubric,
body.ielts-exam.ct-text-large .ct-reference-item,
body.ielts-exam.ct-text-large .ct-inline-input,
body.ielts-exam.ct-text-large .ct-gap-input,
body.ielts-exam.ct-text-large .ct-gap-input--block,
body.ielts-exam.ct-text-large .ct-writing-prompt,
body.ielts-exam.ct-text-large .ct-writing-textarea,
body.ielts-exam.ct-text-large .ct-match-chip,
body.ielts-exam.ct-text-large .ct-match-chip-text,
body.ielts-exam.ct-text-large .ct-match-drop-hint,
body.ielts-exam.ct-text-large .ct-match-dd-head,
body.ielts-exam.ct-text-large .ct-match-dd-select {
  font-size: 19px;
  line-height: 1.5;
}
body.ielts-exam.ct-text-large .ct-completion-head,
body.ielts-exam.ct-text-large .ct-passage h3 { font-size: 20.9px; line-height: 1.2; }

/* --- Extra large: 22px --- */
body.ielts-exam.ct-text-xlarge .ct-passage,
body.ielts-exam.ct-text-xlarge .ct-q-prompt,
body.ielts-exam.ct-text-xlarge .ct-q-option,
body.ielts-exam.ct-text-xlarge .ct-q-group-intro,
body.ielts-exam.ct-text-xlarge .ct-q-group-headline,
body.ielts-exam.ct-text-xlarge .ct-q-group-rubric,
body.ielts-exam.ct-text-xlarge .ct-kwt-lead-row,
body.ielts-exam.ct-text-xlarge .ct-kwt-input,
body.ielts-exam.ct-text-xlarge .ct-kwt-gap-num,
body.ielts-exam.ct-text-xlarge .ct-completion-body,
body.ielts-exam.ct-text-xlarge .ct-completion-rubric,
body.ielts-exam.ct-text-xlarge .ct-reference-item,
body.ielts-exam.ct-text-xlarge .ct-inline-input,
body.ielts-exam.ct-text-xlarge .ct-gap-input,
body.ielts-exam.ct-text-xlarge .ct-gap-input--block,
body.ielts-exam.ct-text-xlarge .ct-writing-prompt,
body.ielts-exam.ct-text-xlarge .ct-writing-textarea,
body.ielts-exam.ct-text-xlarge .ct-match-chip,
body.ielts-exam.ct-text-xlarge .ct-match-chip-text,
body.ielts-exam.ct-text-xlarge .ct-match-drop-hint,
body.ielts-exam.ct-text-xlarge .ct-match-dd-head,
body.ielts-exam.ct-text-xlarge .ct-match-dd-select {
  font-size: 22px;
  line-height: 1.5;
}
body.ielts-exam.ct-text-xlarge .ct-completion-head,
body.ielts-exam.ct-text-xlarge .ct-passage h3 { font-size: 24.2px; line-height: 1.2; }

/* The lettered key inside a match chip ("i", "ii", "A") is a fixed 13px badge
   that never entered the ladder, so at Extra large the heading list read as a
   22px line with a 13px marker. Client feedback F013: "Size is not changing in
   Extra Large mode in part 2 headings" — the chip TEXT above and this key are
   the two halves of that one complaint. Kept a step below the body size, which
   is the badge's proportion at Regular (13/15). */
body.ielts-exam.ct-text-large .ct-match-chip-key { font-size: 16px; }
body.ielts-exam.ct-text-xlarge .ct-match-chip-key { font-size: 19px; }

/* Gap boxes grow with the text so the number stays legible inside them. */
body.ielts-exam.ct-text-large .ct-inline-input,
body.ielts-exam.ct-text-large .ct-kwt-input { height: 26px; }
body.ielts-exam.ct-text-xlarge .ct-inline-input,
body.ielts-exam.ct-text-xlarge .ct-kwt-input { height: 30px; }

/* ---------- Admin: candidate reports + speaking assessment ---------- */
.zu-reports-body { margin-top: 1rem; }
.zu-reports-table td { vertical-align: middle; }
.zu-code-chip {
  font-family: 'Courier New', monospace;
  font-weight: 700;
  letter-spacing: 0.12em;
  background: #eef2ff;
  color: #1e40af;
  padding: 0.15rem 0.5rem;
  border-radius: 6px;
}
.zu-muted { color: var(--zu-text-muted); }
.zu-reports-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.zu-reports-actions .zu-btn--small { text-decoration: none; }
.zu-speaking-editor-row td { background: #f8fafc; padding: 1rem 1.25rem; }
.zu-speaking-editor h4 { margin: 0 0 0.75rem; color: #1e40af; }
.zu-speaking-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem 1rem;
  margin-bottom: 0.75rem;
}
.zu-field--inline { margin: 0; }
.zu-field--inline span { display: block; font-size: 0.85rem; color: var(--zu-text-muted); margin-bottom: 0.2rem; }
.zu-speaking-editor .zu-field { margin: 0 0 0.75rem; }
.zu-speaking-editor .zu-field > span { display: block; font-size: 0.85rem; color: var(--zu-text-muted); margin-bottom: 0.2rem; }
.zu-speaking-editor input[type="text"],
.zu-speaking-editor textarea {
  width: 100%; box-sizing: border-box;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border, #cbd5e1);
  border-radius: 8px;
  font: inherit;
}
.zu-speaking-actions { display: flex; align-items: center; gap: 0.75rem; }

/* ===== Bottom-nav parity with the live Inspera player (measured @1920x1080) =====
   Inspera FLOATS the prev/next arrows above the nav bar (56x55 at y=937, i.e.
   88px off the viewport bottom; prev right:90px, next right:32px) and keeps
   only the "Review your answers" check button inside the 53px bar at the far
   right (77x53, #efefef bg, #535353 glyph). Ours had 48x48 rounded arrows
   docked inside the bar, a blue check, and — worst — a BLACK disabled prev
   where Inspera uses a light #dddddd. */
body.ielts-exam .ct-nav-arrows {
  border-left: none;
  padding: 0;
  gap: 0;
}
body.ielts-exam .ct-nav-arrow {
  position: fixed;
  bottom: 88px;
  width: 56px;
  height: 55px;
  border-radius: 0;
  font-size: 22px;
  background: #000;
  color: #fff;
  box-shadow: none;
  transition: none;
}
body.ielts-exam .ct-nav-arrow--prev { right: 90px; }
body.ielts-exam .ct-nav-arrow--next { right: 32px; background: #000; box-shadow: none; }
body.ielts-exam .ct-nav-arrow:hover:not(:disabled) { opacity: 1; transform: none; }
/* Inspera's disabled arrow is a light grey plate with a white glyph — not a
   dimmed black one. */
body.ielts-exam .ct-nav-arrow:disabled {
  background: #dddddd;
  color: #fff;
  opacity: 1;
}
body.ielts-exam .ct-nav-finish {
  width: 77px;
  height: 53px;
  border-radius: 0;
  background: #efefef;
  color: #535353;
  box-shadow: none;
  font-size: 20px;
}
body.ielts-exam .ct-nav-finish:hover:not(:disabled) { opacity: 1; transform: none; background: #e4e4e4; }

.ct-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Question-group instruction. Inspera splits it into a bold "Questions N–M"
   headline (700) on its own line and a normal-weight rubric block beneath —
   ours rendered one flat 400-weight line at 15px. */
body.ielts-exam .ct-q-group-intro { font-size: 16px; }
body.ielts-exam .ct-q-group-headline {
  font-size: 16px;
  font-weight: 700;
  color: #000;
  margin: 0 0 4px;
  line-height: 24px;
}
body.ielts-exam .ct-q-group-rubric { font-size: 16px; font-weight: 400; line-height: 24px; }
body.ielts-exam.ct-contrast .ct-q-group-headline,
body.ielts-exam.ct-contrast .ct-q-group-rubric { color: var(--cc-fg); }

/* ===== Listening: audio status (Inspera "Audio is Playing") =====
   Inspera renders it as plain black body text at regular weight with a dark
   grey fa-volume-up glyph. Ours was branded teal at semibold with a teal mask,
   which read as a badge rather than a status line. */
body.ielts-exam .ct-audio-status {
  color: #000;
  font-size: 16px;
  font-weight: 400;
}
body.ielts-exam .ct-audio-status .ct-speaker-icon {
  background: #333;
  color: #333;
  width: 20px;
  height: 20px;
}
body.ielts-exam.ct-contrast .ct-audio-status { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-audio-status .ct-speaker-icon { background: var(--cc-fg); }
/* Volume control in the high-contrast themes. The default #333 glyph is
   near-invisible on black and the slider's light track disappears, so both
   follow the theme tokens — this is the mode a low-vision candidate uses, and
   it is exactly where a mis-levelled headset is hardest to diagnose (F001,
   same class of miss as F003's option boxes). */
body.ielts-exam.ct-contrast .ct-vol-btn { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-vol-btn:hover { background: rgba(255, 255, 255, 0.14); }
body.ielts-exam.ct-contrast .ct-vol-slider {
  background: var(--cc-border-strong);
  accent-color: var(--cc-badge);
}

/* Note/table completion headings — Inspera renders the note title and each
   section label as a bold 17.6px heading (h2), giving the notes hierarchy.
   Ours was one flat body-weight run with no emphasis. */
body.ielts-exam .ct-completion-head {
  display: inline-block;
  font-size: 17.6px;
  font-weight: 700;
  line-height: 21.12px;
  color: #000;
}
body.ielts-exam.ct-contrast .ct-completion-head { color: var(--cc-fg); }

/* ===== Notes sidebar (Inspera "Show notes") =====
   Measured on the live player: a 300px sidebar docked right at x=1620, y=0,
   300x1080, bg rgb(232,232,232), sitting in the LAYOUT FLOW so the exam narrows
   to 1620px and stays visible/usable. Previously this reused
   .ct-options-overlay/.ct-options-panel, which the Options rewrite turned into a
   full-screen page — so opening notes blanked the whole exam behind an opaque
   1920x1080 sheet. These are its own styles now; it shares nothing with Options. */
.ct-notes-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 300px;
  height: 100vh;
  background: #e8e8e8;
  z-index: 45;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
.ct-notes-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  flex: 0 0 56px;
  padding: 0 8px 0 16px;
  background: #fff;
  border-bottom: 1px solid #c1c1c1;
}
.ct-notes-title { margin: 0; font-size: 16px; font-weight: 400; color: #1a1a1a; }
.ct-notes-close {
  border: none; background: none; cursor: pointer;
  font-size: 20px; font-weight: 700; line-height: 1; color: #1a1a1a;
  padding: 8px 12px;
}
.ct-notes-list { flex: 1; overflow-y: auto; padding: 16px; }
/* The exam narrows beside the sidebar instead of being covered (Inspera: 1620px).
   The header is sticky (width follows the body box) but the bottom nav and the
   floating arrows are position:fixed, so they need explicit right offsets. */
body.ct-notes-open { padding-right: 300px; }
body.ct-notes-open .ct-bottom-nav { right: 300px; }
body.ielts-exam.ct-notes-open .ct-nav-arrow--prev { right: 390px; }
body.ielts-exam.ct-notes-open .ct-nav-arrow--next { right: 332px; }

/* Contrast themes: the sidebar follows --cc-fg like every other surface. */
body.ielts-exam.ct-contrast .ct-notes-panel { background: var(--cc-panel); }
body.ielts-exam.ct-contrast .ct-notes-header { background: var(--cc-bg); border-color: var(--cc-border); }
body.ielts-exam.ct-contrast .ct-notes-title,
body.ielts-exam.ct-contrast .ct-notes-close,
body.ielts-exam.ct-contrast .ct-note-text,
body.ielts-exam.ct-contrast .ct-notes-empty-hint { color: var(--cc-fg); }

/* Note editor — replaces window.prompt() (unsupported in the Electron kiosk and
   it froze the exam timer by blocking the event loop). */
.ct-note-modal {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(15, 23, 42, 0.55);
  display: flex; align-items: center; justify-content: center;
}
.ct-note-card {
  background: #fff; border-radius: 8px; padding: 24px;
  width: 460px; max-width: 90vw;
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.35);
}
.ct-note-card-title { margin: 0 0 12px; font-size: 16px; font-weight: 700; color: #1a1a1a; }
.ct-note-input {
  width: 100%; box-sizing: border-box; padding: 10px;
  border: 1px solid #595959; border-radius: 3px;
  font: inherit; font-size: 16px; line-height: 1.5; color: #000; resize: vertical;
}
.ct-note-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }
.ct-note-btn {
  font-size: 15px; padding: 10px 22px; border-radius: 6px;
  cursor: pointer; border: 1px solid transparent;
}
.ct-note-btn--ghost { background: #fff; border-color: #cbd5e1; color: #1a1a1a; }
.ct-note-btn--primary { background: #1a1a1a; color: #fff; }
body.ielts-exam.ct-contrast .ct-note-card { background: var(--cc-bg); border: 1px solid var(--cc-border); }
body.ielts-exam.ct-contrast .ct-note-card-title { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-note-input { background: var(--cc-bg); color: var(--cc-fg); border-color: var(--cc-border-input); }
body.ielts-exam.ct-contrast .ct-note-btn--ghost { background: var(--cc-bg); color: var(--cc-fg); border-color: #888; }
body.ielts-exam.ct-contrast .ct-note-btn--primary { background: var(--cc-fg); color: #000; }

/* ===== Bottom-nav part cells — Inspera's measured layout =====
   Inspera divides the bar into EQUAL THIRDS (612/612/612 at 1920) regardless of
   which part is active, and left-aligns the active part's label + chips hard
   against the bar edge (label x=21, first chip x=101). Ours gave the active cell
   flex:3.5 and centred every cell, so Part 1 took 62% of the bar and our label
   landed ~398px right of Inspera's — the most visible difference on this surface.
   Inspera also draws NO vertical separators between cells; we drew divider lines. */
body.ielts-exam .ct-nav-part {
  flex: 1 1 0%;
  border-right: none;
  min-width: 0;
  padding: 0 20px;
}
body.ielts-exam .ct-nav-part--active {
  flex: 1 1 0%;
  justify-content: flex-start;
  border-right: none;
  border-color: transparent;
  padding: 0 21px;   /* Inspera's active label starts at x=21 */
}
body.ielts-exam .ct-nav-part:not(.ct-nav-part--active) { justify-content: center; }

/* Narrow-viewport guard (the 1366x768 / 1024x768 kiosks).
   The equal-third cells assume the active part's chips fit inside a third. A
   13-double-digit part (e.g. Part 2 = questions 14–26) needs ~380px, which
   exceeds a third below roughly 1500px. The LABEL was what gave way: with
   min-width:0 + text-overflow:ellipsis it collapsed to width:0 and disappeared
   entirely at 1366/1024, and narrower still truncated to "Par…" while the chips
   wrapped onto a second row — where each wrapped chip's ::before dash then
   struck through the numbers on the row above.
   Fix: the label never shrinks, the chips never wrap, and the ACTIVE cell may
   claim its own content width, shrinking the two inactive cells (which only
   carry "Part N  0 of 13", ~104px). At 1920 the active cell's min-content is
   well under a third, so the equal-third layout — and Inspera's x=101 chip
   origin — is completely unchanged. */
body.ielts-exam .ct-nav-part-label { flex-shrink: 0; }
body.ielts-exam .ct-nav-part-numbers { flex-wrap: nowrap; }
body.ielts-exam .ct-nav-part--active { min-width: min-content; }
/* Inspera's label: 16px, normal weight even when active, 33.8px box. */
/* Gap sized so the first number chip lands at Inspera's x=101. Inspera splits
   its label into prefix+number spans (33.8px box); ours is one 42.7px span, so
   the margin is tuned to the chip position rather than copied blindly. */
body.ielts-exam .ct-nav-part-label { font-size: 16px; line-height: normal; margin-right: 37px; }
/* Inspera semibolds the ACTIVE part's label (section-prefix + sectionNr both
   font-weight 600); inactive part labels stay 400. Verified live. */
body.ielts-exam .ct-nav-part--active .ct-nav-part-label { font-weight: 600; }

/* Header icon cluster: Inspera's buttons are FLUSH (Options 1795.4–1848, notes
   1848–1904 — each button's right edge meets the next one's left). The 16px
   breathing room is the icons' own margins INSIDE each 56px button, not a gap
   between them. Our 14px flex gap pushed Options 17.4px left of Inspera's. */
body.ielts-exam .ct-header-right { gap: 0; }
/* Inspera's 56px bar draws its 1px rgb(193,193,193) separator as an inset
   ::after rather than a real border, so the bar totals 56px and content starts
   at y=56. Our border-bottom made it 57px, shifting everything below down 1px. */
body.ielts-exam .ct-header {
  border-bottom: none;
  position: sticky;
}
body.ielts-exam .ct-header::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: #c1c1c1;
}
body.ielts-exam.ct-contrast .ct-header::after { background: var(--cc-border); }

/* ===== Residual-sweep fixes (each value measured on the live player) ===== */

/* Words counter: RIGHT-aligned. Inspera's counter is display:flex;
   justify-content:flex-end — its text paints at 1797→1861, hard against the
   textarea's right edge (1864). An earlier pass read `text-align: start` off
   that element and wrongly flipped ours to the left; text-align is a red
   herring there, the flex alignment does the work. */
body.ielts-exam .ct-writing-counter {
  /* Single source of truth for the IELTS word counter — consolidated from three
     drifted rules that carried contradictory colour/text-align (audit F002). */
  display: flex;
  justify-content: flex-end;
  text-align: right;
  color: #000;               /* Inspera paints "Words: N" pure black (was #1a1a1a) */
  font-size: 16px;
  margin-top: 3.2px;         /* delta map: Inspera seats the counter 3.2px under the field (was 10.625px) */
}

/* Radio dots: Inspera uses the browser default (accent-color:auto), not our
   branded teal. */
body.ielts-exam .ct-q-option input[type="radio"],
body.ielts-exam .ct-q-option input[type="checkbox"] { accent-color: auto; }

/* Gap inputs: Inspera left-aligns the entry (text-align:start) and paints the
   number placeholder solid black — ours centred it in a muted grey. */
body.ielts-exam .ct-inline-input,
body.ielts-exam .ct-kwt-input,
body.ielts-exam .ct-gap-input--block { text-align: start; }
body.ielts-exam .ct-inline-input::placeholder,
body.ielts-exam .ct-kwt-input::placeholder,
body.ielts-exam .ct-gap-input--block::placeholder { color: #000; opacity: 1; }

/* Drag handle: a closer measurement than the first pass — Inspera borders ALL
   FOUR edges (2px solid #535353) and uses content-box, so it renders 36x36
   (32 + 2px border each side), not the left/right-only 32x32 shipped earlier. */
body.ielts-exam .ct-col-resizer-grip {
  border: 2px solid #535353;
  box-sizing: content-box;
  width: 32px;
  height: 32px;
  background: #f9f9f9;   /* Inspera grip fill is rgb(249,249,249), not pure white */
}
/* Divider handle hover — Inspera uses a DARKER blue for the resize control
   (rgb(42,108,150) = #2a6c96) than its general interaction blue (--ct-active
   #418ec8, which drives input focus + active question badges — verified those
   DO use #418ec8), and darkens the handle fill to #e1e1e1 on hover. Ours
   reused --ct-active for the divider, so the handle went a brighter blue than
   the demo. Scope the darker blue + fill to the resize handle only. */
body.ielts-exam .ct-col-resizer:hover .ct-col-resizer-grip {
  border-color: #2a6c96;
  background: #e1e1e1;
}
body.ielts-exam .ct-col-resizer:hover .ct-col-resizer-grip::before { color: #2a6c96; }

/* Question number badge: Inspera keeps a CONSTANT 28.8x27 box for active AND
   inactive (only the border colour changes). Ours resized 21x22 -> 16.9, so the
   prompt text shifted as the active question moved. */
body.ielts-exam .ct-q-num-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 28.8px;
  height: 27px;
  padding: 0;
  margin-right: 4px;
}
body.ielts-exam .ct-question--active .ct-q-num-badge { min-width: 28.8px; height: 27px; padding: 0; }

/* Group intro: Inspera leaves 16px between the rubric and the first prompt. */
body.ielts-exam .ct-q-group-intro { margin-bottom: 16px; }

/* Audio status: Inspera's label is 13.33px with a 12x13 glyph. */
body.ielts-exam .ct-audio-status { font-size: 13.3333px; }
body.ielts-exam .ct-audio-status .ct-speaker-icon { width: 12px; height: 13px; }

/* Dark themes: Inspera's disabled Previous arrow is a mid-grey plate with a
   DARK glyph (bg rgb(81,81,81), glyph rgb(27,27,27)) — ours kept the light-mode
   #ddd plate with a white glyph, which glared against the black page. */
body.ielts-exam.ct-contrast .ct-nav-arrow:disabled { background: var(--cc-arrow-disabled-bg); color: var(--cc-arrow-disabled-fg); }

/* Passage paragraphs — Inspera uses real <p> whose 16px margins collapse to a
   16px gap. Our pre-wrap blank line produced a full 24px line box (8px too wide
   on every break). paragraphizePassage() now emits real paragraphs; pre-wrap is
   retained so single newlines inside a paragraph still break as before. */
body.ielts-exam .ct-passage p.ct-passage-p { margin: 0 0 16px; }
body.ielts-exam .ct-passage p.ct-passage-p:last-child { margin-bottom: 0; }

/* Options UI scales with the Text size tier — Inspera zooms the whole Options
   page, it isn't fixed chrome. Measured on the live player: row font 17.6px /
   height 75.8 at Regular, 24.2px / 100.6 at Extra large — a 1.375 ratio, the
   same 16→22 step as the body ladder. Ours was frozen at every tier.
   17.6 = 1.1 x the tier base (16/19/22 → 17.6/20.9/24.2). */
/* Inspera's rows use line-height:normal; ours inherited the body's 1.5, which
   added ~4px per row. Padding (26.4 / 36.3) already matched exactly. */
body.ielts-exam .ct-options-item,
body.ielts-exam .ct-options-choice { font-size: 17.6px; line-height: normal; }
body.ielts-exam .ct-options-header h2,
body.ielts-exam .ct-options-subheading { font-size: 24px; }

/* Large: x 19/16 */
body.ielts-exam.ct-text-large .ct-options-item,
body.ielts-exam.ct-text-large .ct-options-choice {
  font-size: 20.9px;
  padding: 31.35px 52.25px 31.35px 41.8px;
}
body.ielts-exam.ct-text-large .ct-options-header h2,
body.ielts-exam.ct-text-large .ct-options-subheading { font-size: 28.5px; }

/* Extra large: x 22/16 */
body.ielts-exam.ct-text-xlarge .ct-options-item,
body.ielts-exam.ct-text-xlarge .ct-options-choice {
  font-size: 24.2px;
  padding: 36.3px 60.5px 36.3px 48.4px;
}
body.ielts-exam.ct-text-xlarge .ct-options-header h2,
body.ielts-exam.ct-text-xlarge .ct-options-subheading { font-size: 33px; }

/* Row height is driven by the icon box, not the text: 52.8 padding + 26px icon
   = 79.8, where Inspera's content band is 23px (row 75.8). Size the glyph to the
   content band and let it scale with the tier like everything else. */
body.ielts-exam .ct-options-item-icon { width: 23px; height: 23px; }
body.ielts-exam.ct-text-large .ct-options-item-icon { width: 27.3px; height: 27.3px; }
body.ielts-exam.ct-text-xlarge .ct-options-item-icon { width: 31.6px; height: 31.6px; }

/* Build stamp in the Options panel footer (parity-recurrence P0). Deliberately
   quiet — it is diagnostic metadata for a QA screenshot, not candidate-facing
   information, so it must not compete with the actual options. */
.ct-options-build {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--ct-border);
  font-size: 12px;
  color: var(--ct-text-muted);
  text-align: center;
  letter-spacing: 0.02em;
}
body.ielts-exam.ct-contrast .ct-options-build {
  color: var(--cc-muted);
  border-top-color: var(--cc-border);
}

/* ── Invigilator: paused-exam curtain ──────────────────────────────────────
   While an invigilator has the clock stopped, the candidate must not be able to
   read or answer anything — otherwise a pause would be free thinking time. This
   covers the whole exam; only the server's clock (via the client's /time poll)
   can lift it. */
.ct-paused-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.92);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;            /* above every drawer, modal and the nav */
  animation: ctFadeIn 180ms ease-out;
}
.ct-paused-card {
  background: #fff;
  border-radius: 10px;
  padding: 40px 56px;
  text-align: center;
  max-width: 460px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.ct-paused-icon { font-size: 44px; line-height: 1; margin-bottom: 12px; }
.ct-paused-card h3 { margin: 0 0 8px; font-size: 22px; color: #1a1a1a; }
.ct-paused-card p { margin: 0; font-size: 16px; color: #444; line-height: 1.5; }
/* The only door out of the curtain — still PIN-gated, so a candidate gains
   nothing from it. Understated, because it's the invigilator's control and not
   an invitation to the candidate. */
.ct-paused-invig {
  margin-top: 22px;
  padding: 9px 22px;
  border: 1px solid #cfd6da;
  border-radius: 6px;
  background: #fff;
  color: #1a1a1a;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.ct-paused-invig:hover { background: #f0f0f0; }
body.ielts-exam.ct-contrast .ct-paused-card { background: var(--cc-bg); border: 1px solid var(--cc-border-soft); }
body.ielts-exam.ct-contrast .ct-paused-card h3,
body.ielts-exam.ct-contrast .ct-paused-card p { color: var(--cc-fg); }
body.ielts-exam.ct-contrast .ct-paused-invig { background: var(--cc-bg); color: var(--cc-fg); border-color: var(--cc-border-soft); }

/* Invigilator time controls inside the PIN modal */
.ct-invig-time { margin-top: 18px; border-top: 1px solid #e5e7eb; padding-top: 16px; }
.ct-invig-time-title { font-size: 13px; font-weight: 700; color: #444; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
.ct-invig-time-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.ct-invig-time-btn {
  flex: 1 1 auto;
  min-width: 92px;
  padding: 9px 12px;
  border: 1px solid #cfd6da;
  border-radius: 6px;
  background: #fff;
  color: #1a1a1a;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.ct-invig-time-btn:hover { background: #f0f0f0; }
.ct-invig-time-btn--pause { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
.ct-invig-time-btn--pause:hover { background: #333; }
.ct-invig-time-status { margin-top: 10px; font-size: 13px; color: #555; min-height: 18px; text-align: center; }

/* --- white-label / no-logo demo --- */
.zu-logo,.ct-logo,.ct-logo-ielts{display:none !important;}
