@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=Inter:wght@400;500;600;700&display=swap');

/* ---------- reset ---------- */
* { box-sizing: border-box; margin: 0; padding: 0; }
img { max-width: 100%; display: block; }

/* ---------- theme ---------- */
:root {
  /* This site only ever has the one dark palette below (no light mode to
     switch between), but without this the browser still renders native
     controls — a date/datetime-local input's calendar-and-time popup,
     scrollbars — in its own default light appearance regardless, clashing
     with everything else on the page. */
  color-scheme: dark;
  --bg: #0b0d12;
  --bg-elev: #14171f;
  --bg-elev-2: #1c202b;
  --bg-elev-3: #262b38;
  --border: #2a2f3d;
  --border-soft: #1f2330;
  --text: #edeef2;
  --text-dim: #9aa0b4;
  --text-faint: #6b7288;

  --accent: #ff6f59;
  --accent-2: #ffb648;
  --accent-soft: rgba(255, 111, 89, 0.14);
  --accent-text: #ffffff;

  --success: #3ddc97;
  --success-soft: rgba(61, 220, 151, 0.14);
  --warning: #ffb648;
  --warning-soft: rgba(255, 182, 72, 0.14);
  --danger: #ff5c72;
  --danger-soft: rgba(255, 92, 114, 0.14);
  --info: #5bb8ff;
  --info-soft: rgba(91, 184, 255, 0.14);

  --radius-sm: 8px;
  --radius: 14px;
  --radius-lg: 20px;
  --shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.45);

  --font-head: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, sans-serif;
}

html, body {
  height: 100%;
}

body {
  background:
    radial-gradient(1200px 600px at 85% -10%, rgba(255, 111, 89, 0.10), transparent 60%),
    radial-gradient(900px 500px at -10% 10%, rgba(91, 184, 255, 0.06), transparent 55%),
    var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  min-height: 100%;
}

h1, h2, h3, h4 {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

a { color: inherit; text-decoration: none; }

.container {
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 24px;
}

.container-narrow {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px;
}

/* ---------- brand ---------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-head);
  font-weight: 800;
  font-size: 1.15rem;
  color: var(--text);
}
.brand-mark {
  height: 28px;
  width: auto;
  display: block;
  flex-shrink: 0;
}

/* ---------- navbar ---------- */
.navbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(11, 13, 18, 0.85);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-soft);
}
.navbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 24px;
  max-width: 1120px;
  margin: 0 auto;
}
.navbar-links {
  display: flex;
  align-items: center;
  gap: 6px;
}
.navbar-link {
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text-dim);
  transition: background 0.15s, color 0.15s;
}
.navbar-link:hover { color: var(--text); background: var(--bg-elev-2); }
.navbar-link.active { color: var(--text); background: var(--bg-elev-2); }

.navbar-user {
  display: flex;
  align-items: center;
  gap: 12px;
}
.user-chip {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 14px 6px 6px;
  border-radius: 999px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
}
.avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #1a0f0a;
  font-weight: 700;
  font-size: 0.8rem;
  flex-shrink: 0;
}
.user-chip > div:last-child {
  min-width: 0;
}
.user-chip .name {
  font-size: 0.86rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 4px;
  max-width: 160px;
}
/* Name text truncates with an ellipsis before it ever wraps; the flag
   (fixed-size, never shrinks) always stays on the same line right after
   it, instead of getting pushed to a second line. */
.user-chip .name #user-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.user-chip .name #user-flag {
  flex-shrink: 0;
  display: flex;
}
.user-chip .role-tag {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* Admin-only toggle between the admin page and the account's normal
   dashboard — sits right before the user-chip pill. Gold, matching the
   partner-card/streak/REAL-letter gold elsewhere, since it's also an
   exclusive, "you've unlocked something" kind of marker. Hollow by
   default; .solid (added only while on admin.html) fills it in. */
.crown-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1.5px solid var(--warning);
  background: transparent;
  color: var(--warning);
  cursor: pointer;
  transition: background 0.15s ease;
}
.crown-toggle:hover { background: var(--warning-soft); }
.crown-toggle.solid { background: var(--warning-soft); }
.crown-toggle svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.crown-toggle.solid svg { fill: currentColor; stroke: none; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.92rem;
  padding: 11px 20px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.1s ease, filter 0.15s ease, background 0.15s ease, border-color 0.15s ease;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }

.btn-primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #1a0f0a;
}
.btn-primary:hover { filter: brightness(1.07); }

.btn-secondary {
  background: var(--bg-elev-2);
  color: var(--text);
  border-color: var(--border);
}
.btn-secondary:hover { background: var(--bg-elev-3); }

.btn-ghost {
  background: transparent;
  color: var(--text-dim);
}
.btn-ghost:hover { color: var(--text); background: var(--bg-elev-2); }

.btn-danger {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: rgba(255, 92, 114, 0.3);
}
.btn-danger:hover { filter: brightness(1.1); }

.btn-outline-pill {
  background: var(--bg-elev);
  color: var(--text);
  border: 1.5px solid var(--accent);
  border-radius: 999px;
}
.btn-outline-pill:hover {
  background: var(--accent-soft);
  border-color: var(--accent-2);
}

.btn-sm { padding: 7px 14px; font-size: 0.82rem; }
.btn-lg { padding: 14px 30px; font-size: 1rem; }
.btn-icon { padding: 0; width: 44px; height: 44px; flex-shrink: 0; }
.btn-icon svg { display: block; }
.btn-block { width: 100%; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* Homepage feature-card CTAs (Phase 5 trial + teacher deep link) */
.btn-cta-orange {
  background: var(--accent-2);
  color: #000;
  border-radius: 999px;
  font-weight: 700;
}
.btn-cta-orange:hover { filter: brightness(1.08); }

.btn-cta-teacher {
  background: #000;
  color: var(--accent-2);
  border: 1.5px solid var(--accent-2);
  border-radius: 999px;
}
.btn-cta-teacher:hover { background: rgba(255, 182, 72, 0.1); }

.feature-card .btn { margin-top: 16px; }

/* ---------- forms ---------- */
label {
  display: block;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 7px;
}
.field { margin-bottom: 18px; }
.field-hint {
  font-size: 0.78rem;
  color: var(--text-faint);
  margin-top: 6px;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
textarea,
select {
  width: 100%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.94rem;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:focus, textarea:focus, select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea { resize: vertical; min-height: 110px; line-height: 1.55; }
select { appearance: none; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%239aa0b4' stroke-width='2'><path d='M4 6l4 4 4-4'/></svg>"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 36px; }

.file-drop {
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-sm);
  padding: 18px;
  text-align: center;
  background: var(--bg-elev);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.file-drop:hover { border-color: var(--accent); background: var(--bg-elev-2); }
.file-drop input[type="file"] { display: none; }
.file-drop .file-drop-label { font-size: 0.88rem; color: var(--text-dim); }
.file-drop .file-drop-name { font-size: 0.88rem; color: var(--accent-2); font-weight: 600; margin-top: 4px; }

.error-text {
  color: var(--danger);
  font-size: 0.85rem;
  margin-top: 4px;
  min-height: 1.1em;
}

.role-picker {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.role-option {
  border: 1.5px solid var(--border);
  background: var(--bg-elev);
  border-radius: var(--radius);
  padding: 16px;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.role-option:hover { border-color: var(--text-faint); }
.role-option.selected { border-color: var(--accent); background: var(--accent-soft); }
.role-option .role-title { color: var(--text); font-weight: 700; font-family: var(--font-head); margin-bottom: 3px; }
.role-option .role-desc { font-size: 0.75rem; color: var(--text-dim); }

.duration-picker {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.duration-option {
  border: 1.5px solid var(--border);
  background: var(--bg-elev);
  border-radius: var(--radius-sm);
  padding: 12px 6px;
  cursor: pointer;
  text-align: center;
  font: inherit;
  font-size: 0.86rem;
  color: var(--text-dim);
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.duration-option:hover { border-color: var(--text-faint); }
.duration-option.selected { border-color: var(--accent); background: var(--accent-soft); color: var(--text); font-weight: 600; }

/* Same .duration-option buttons, just 2 columns instead of 4 — the
   AI Conversation + Customized Exercises presets carry a much longer
   composite label ("15 minutes (12 min talk + 3 min exercises)") than the
   plain duration picker's short "15 minutes", and 4-across would squeeze
   that onto an unreadably narrow column. */
.split-duration-picker {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

/* Same visual language as .duration-picker/.duration-option on purpose
   (square, rounded, dark background matching the date fields next to it),
   kept as its own separate ruleset rather than reusing those classes
   directly so the already-working duration picker's CSS/JS can't be
   affected by anything done here. Flex rather than grid specifically so
   the info-open button below can grow wider by changing its own flex
   value, pushing into its sibling's space, instead of both staying a
   fixed equal width the way a grid column would hold them. */
.session-type-picker {
  display: flex;
  gap: 10px;
  align-items: stretch;
}
.session-type-option {
  position: relative;
  flex: 1;
  min-width: 0;
  border: 1.5px solid var(--border);
  background: var(--bg-elev);
  border-radius: var(--radius-sm);
  padding: 14px 26px 14px 10px;
  cursor: pointer;
  text-align: center;
  font: inherit;
  font-size: 0.86rem;
  color: var(--text-dim);
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease, flex 0.2s ease;
  overflow: hidden;
}
.session-type-option:hover { border-color: var(--text-faint); }
.session-type-option.selected { border-color: var(--accent); background: var(--accent-soft); color: var(--text); font-weight: 600; }
/* Expands out the side by claiming a bigger share of the flex row —
   its sibling shrinks in response since both sit in the same row. */
.session-type-option.info-open { flex: 2.3; text-align: left; }
/* position:relative (no z-index needed) lifts these into the same paint
   step as .session-type-icon below — plain static-flow text otherwise
   paints BEHIND an absolutely-positioned sibling regardless of DOM order,
   which would put the watermark icon on top of the readable label. */
.option-label { display: block; position: relative; }
.option-info-text {
  display: none;
  position: relative;
  margin-top: 8px;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--text-dim);
  line-height: 1.45;
}
.session-type-option.info-open .option-info-text { display: block; }

/* Large, faint decorative icon per session type — bleeds off the corner
   on purpose, clipped by the button's own overflow:hidden, purely
   atmospheric, never meant to compete with the label for attention. */
.session-type-icon {
  position: absolute;
  bottom: -10px;
  left: -6px;
  width: 60px;
  height: 60px;
  color: var(--text);
  opacity: 0.18;
  pointer-events: none;
}
.session-type-option.selected .session-type-icon { opacity: 0.28; color: var(--accent); }

.option-info-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-faint);
  font-size: 0.62rem;
  font-style: italic;
  font-family: Georgia, serif;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.option-info-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- cards ---------- */
.card {
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 24px;
}
.card-lg { padding: 32px; }
.card-hover { transition: border-color 0.15s ease, transform 0.15s ease; }
.card-hover:hover { border-color: var(--border); transform: translateY(-2px); }

.auth-card {
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 36px;
  box-shadow: var(--shadow-lg);
}

/* ---------- badges / status pills ---------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 4px 10px;
  border-radius: 999px;
}
.badge::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.badge-open { background: var(--success-soft); color: var(--success); }
.badge-upcoming { background: var(--info-soft); color: var(--info); }
.badge-closed { background: rgba(255,255,255,0.06); color: var(--text-faint); }
.badge-done { background: var(--warning-soft); color: var(--warning-2, var(--warning)); }
.badge-subject { background: var(--bg-elev-3); color: var(--text-dim); }

/* ---------- page shell ---------- */
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin: 40px 0 28px;
  flex-wrap: wrap;
}
.page-header h1 { font-size: 1.7rem; }
.page-header p { color: var(--text-dim); margin-top: 6px; font-size: 0.95rem; }

/* ---------- onboarding fork (first-visit "Join a Class" / "Start Solo
   Learning" chooser) and the My Classes / Solo Practice dashboard tabs —
   same two colors throughout, accent orange for the classes side and
   info blue for the solo side, the same pairing the REAL diagram on the
   homepage uses for R and E. ---------- */
.onboarding-fork {
  max-width: 760px;
  margin: 80px auto;
  text-align: center;
}
.onboarding-fork h1 { font-size: 1.9rem; margin-bottom: 10px; }
.onboarding-fork > p { color: var(--text-dim); margin-bottom: 36px; }
.fork-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.fork-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 40px 24px;
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  border: 1.5px solid var(--border);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s ease, transform 0.15s ease, background 0.15s ease;
}
.fork-btn:hover { transform: translateY(-2px); }
.fork-btn.tint-accent:hover { border-color: var(--accent); background: var(--accent-soft); }
.fork-btn.tint-info:hover { border-color: var(--info); background: var(--info-soft); }
.fork-icon {
  width: 56px; height: 56px;
  border-radius: 16px;
  display: grid; place-items: center;
}
.fork-btn.tint-accent .fork-icon { background: var(--accent-soft); color: var(--accent); }
.fork-btn.tint-info .fork-icon { background: var(--info-soft); color: var(--info); }
.fork-btn h3 { font-size: 1.1rem; }
.fork-btn p { color: var(--text-dim); font-size: 0.86rem; }

.dash-tabs {
  display: flex;
  gap: 10px;
  margin: 36px 0 0;
  border-bottom: 1px solid var(--border-soft);
}
.dash-tab {
  appearance: none;
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.98rem;
  padding: 12px 6px 14px;
  cursor: pointer;
  border-bottom: 2.5px solid transparent;
  margin-bottom: -1px;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.dash-tab:hover { color: var(--text); }
.dash-tab.active.tint-accent { color: var(--accent); border-color: var(--accent); }
.dash-tab.active.tint-info { color: var(--info); border-color: var(--info); }

/* ---------- practice stats (streak + total minutes) ---------- */
/* Stat cards rather than little inline pills — the number is the thing
   meant to grab a student's eye (Duolingo-style), the label underneath
   is just there to say what it's a count of. */
.practice-stats { display: flex; gap: 14px; flex-wrap: wrap; }
.practice-stat-chip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 22px;
  border-radius: var(--radius);
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
}
.practice-stat-chip .stat-value {
  font-family: var(--font-head);
  font-size: 2.1rem;
  font-weight: 800;
  line-height: 1.1;
  color: var(--text);
}
.practice-stat-chip .stat-label { font-size: 0.78rem; color: var(--text-dim); }
.practice-stat-chip .stat-sublabel { font-size: 0.7rem; color: var(--text-faint); margin-top: 2px; }
.practice-stat-chip.streak-active { border-color: var(--warning); background: var(--warning-soft); }
.practice-stat-chip.streak-active .stat-value,
.practice-stat-chip.streak-active .stat-label { color: var(--warning); }

/* ---------- solo practice ---------- */
.session-type-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  margin: 24px 0;
}
.session-type-card {
  padding: 22px;
  border-radius: var(--radius);
  background: var(--bg-elev);
  border: 1.5px solid var(--border);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.session-type-card:hover { border-color: var(--info); }
.session-type-card.selected { border-color: var(--info); background: var(--info-soft); }
.session-type-card h3 { font-size: 1rem; margin-bottom: 6px; }
.session-type-card p { color: var(--text-dim); font-size: 0.84rem; }
.session-type-card.locked {
  opacity: 0.55;
  cursor: not-allowed;
}
.session-type-card.locked:hover { border-color: var(--border); }
/* The partner card is the "premium" option on the solo practice page, so
   it gets its own gold tint instead of the plain blue/gray every other
   card uses — reusing --warning, the same gold the REAL acronym on the
   home page and the streak chip already use. Stacked with .locked above:
   that rule's opacity:0.55 is what makes the gold read as dim before the
   15-minute gate opens, and full opacity plus the glow below is what
   makes it read as bright gold once it's removed on unlock. */
.session-type-card.partner-gold {
  border-color: var(--warning);
  background: var(--warning-soft);
}
.session-type-card.partner-gold:hover,
.session-type-card.partner-gold.locked:hover { border-color: var(--warning); }
.session-type-card.partner-gold.selected { border-color: var(--warning); background: var(--warning-soft); }
.session-type-card.partner-gold:not(.locked) { box-shadow: 0 0 16px -4px rgba(255, 182, 72, 0.35); }
.session-type-card .badge { margin-bottom: 10px; }
.badge-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; }
/* Real medal art (img/medals/medal-*.png) instead of a flat colored
   circle — see response.md for how those were cleaned up/recolored from
   the source image. Locked medals are grayscaled and dim rather than
   hidden, so a student can see what's still ahead of them; unlocked
   medals get their full color and a small lift/shadow to read as
   "earned" rather than just decorative. */
.badge-medal {
  width: 40px;
  opacity: 0.35;
  filter: grayscale(1);
  cursor: default;
  transition: opacity 0.15s ease, filter 0.15s ease, transform 0.15s ease;
}
.badge-medal img { width: 100%; height: auto; display: block; }
.badge-medal.unlocked {
  opacity: 1;
  filter: none;
  transform: translateY(-2px);
}
.gate-progress-track { height: 6px; border-radius: 999px; background: var(--bg-elev-3); margin-top: 12px; overflow: hidden; }
.gate-progress-fill { height: 100%; border-radius: 999px; background: var(--info); }
.gate-progress-note { font-size: 0.76rem; color: var(--text-faint); margin-top: 6px; }

/* ---------- admin page ---------- */
.admin-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin: 24px 0 40px; }
.admin-stat-card { background: var(--bg-elev); border: 1px solid var(--border-soft); border-radius: var(--radius); padding: 20px; }
.admin-stat-value { font-family: var(--font-head); font-size: 1.8rem; font-weight: 800; color: var(--text); }
.admin-stat-label { font-size: 0.82rem; color: var(--text-dim); margin-top: 4px; }
.report-row { padding: 16px 0; border-bottom: 1px solid var(--border-soft); }
.report-row:last-child { border-bottom: none; }
.report-row-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.report-reason { margin-top: 8px; font-size: 0.9rem; color: var(--text-dim); white-space: pre-wrap; }
.report-meta { font-size: 0.78rem; color: var(--text-faint); }

/* ---------- post-call partner rating ---------- */
.rating-card { background: var(--bg-elev-2); text-align: left; max-width: 420px; margin: 0 auto; }
.rating-card h3 { font-size: 0.95rem; margin-bottom: 14px; text-align: center; }
.rating-stars { display: flex; justify-content: center; gap: 8px; margin-bottom: 16px; }
.rating-star {
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: 2rem;
  color: var(--border);
  transition: color 0.1s ease, transform 0.1s ease;
}
.rating-star:hover { transform: scale(1.1); }
.rating-star.filled { color: var(--warning); }
.rating-note-field { margin-bottom: 14px; }
.rating-note-field textarea { min-height: 60px; }
.solo-start-card {
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
  /* The duration/rounds fields inside swap height instantly when the
     selected session type changes. Without this, the browser's scroll
     anchoring tries to "compensate" by nudging scrollY so whatever's
     under the pointer stays put, which reads as the whole page jumping.
     Opting this card out of anchoring lets the layout just grow/shrink
     in place, like normal page content, with no scroll position change. */
  overflow-anchor: none;
}
.solo-start-card .trial-language-field { max-width: none; margin: 0 auto 16px; text-align: left; }
.eyebrow {
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-2);
  margin-bottom: 8px;
}

/* ---------- class grid ---------- */
.grid-classes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 18px;
}
.class-card {
  position: relative;
  overflow: hidden;
}
.class-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
}
.class-card h3 { font-size: 1.05rem; margin: 10px 0 4px; }
.class-card .meta { color: var(--text-dim); font-size: 0.85rem; }
.class-card .stats { display: flex; gap: 16px; margin-top: 16px; font-size: 0.82rem; color: var(--text-dim); }
.class-card .stats b { color: var(--text); font-family: var(--font-head); }
.join-code-pill {
  display: inline-block;
  margin-top: 14px;
  font-family: 'Courier New', monospace;
  letter-spacing: 0.05em;
  font-size: 0.78rem;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  padding: 5px 10px;
  border-radius: 6px;
  color: var(--accent-2);
}

.add-class-card {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 150px;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  font-weight: 600;
  cursor: pointer;
  background: transparent;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.add-class-card:hover { border-color: var(--accent); color: var(--text); background: var(--accent-soft); }

.empty-state {
  text-align: center;
  padding: 64px 24px;
  color: var(--text-dim);
  border: 1.5px dashed var(--border);
  border-radius: var(--radius);
}
.empty-state h3 { color: var(--text); margin-bottom: 6px; font-size: 1.1rem; }
.empty-state p { font-size: 0.9rem; max-width: 380px; margin: 0 auto; }

/* ---------- assignments ---------- */
.assignment-card { margin-bottom: 18px; position: relative; overflow: hidden; }
.assignment-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--success);
}
/* Closed (window has passed) turns the top bar red instead of green —
   upcoming/completed are left as the same green as open, unchanged, since
   only open-vs-closed was asked for. */
.assignment-card.status-closed::before { background: var(--danger); }
.assignment-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}
/* Only the teacher's version (always has a body to expand) and a
   student's version with rules/a PDF to reveal get this — a student
   assignment with neither has no toggle at all, so it shouldn't look
   clickable. */
.assignment-head.toggleable { cursor: pointer; }
.assignment-head h3 { font-size: 1.15rem; }
.assignment-head .meta { color: var(--text-faint); font-size: 0.8rem; margin-top: 4px; }
.assignment-body { margin-top: 14px; }
.assignment-expand-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  padding: 4px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
}
.assignment-expand-btn:hover { background: var(--bg-elev-3); color: var(--text); }
.assignment-expand-btn svg { transition: transform 0.15s ease; }
.assignment-expand-btn.open svg { transform: rotate(180deg); }
.assignment-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

.rules-box {
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 14px 16px;
  font-size: 0.88rem;
  color: var(--text-dim);
  white-space: pre-wrap;
  line-height: 1.6;
  margin-bottom: 16px;
}

.pdf-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 16px;
}
.pdf-chip svg { color: var(--danger); flex-shrink: 0; }

.session-list { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.session-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
}
.session-row .session-info { display: flex; align-items: center; gap: 12px; }
.session-index {
  width: 30px; height: 30px;
  border-radius: 8px;
  background: var(--bg-elev-3);
  display: grid; place-items: center;
  font-weight: 700; font-size: 0.85rem; color: var(--text-dim);
  flex-shrink: 0;
}
.session-dates { font-size: 0.88rem; }
.session-dates .label { color: var(--text-faint); font-size: 0.76rem; display: block; margin-bottom: 1px; }
.session-actions { display: flex; align-items: center; gap: 10px; }

/* ---------- kebab (three-dot) menu, e.g. per-student row actions ---------- */
.kebab-wrap { position: relative; display: inline-flex; }
.kebab-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
}
.kebab-btn:hover { background: var(--bg-elev-3); color: var(--text); }

.connection-issue-bubble {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: none;
  background: var(--warning-soft);
  color: var(--warning);
  font-size: 0.72rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0;
}
.connection-issue-bubble:hover { filter: brightness(1.15); }

/* ---------- roster dropdown (teacher-class.html, under the class name) ---------- */
.roster-toggle-wrap { position: relative; display: inline-block; }
.roster-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  margin-top: 2px;
  color: var(--text-dim);
  font-size: 0.92rem;
  font-family: inherit;
  cursor: pointer;
}
.roster-toggle:hover { color: var(--text); }
.roster-toggle svg { transition: transform 0.15s ease; flex-shrink: 0; }
.roster-toggle.open svg { transform: rotate(180deg); }
.roster-dropdown {
  display: none;
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  min-width: 240px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  z-index: 30;
}
.roster-dropdown.open { display: block; }
/* A row is a plain div, not a button — it holds two separate buttons
   side by side (the clickable name/avatar, and the kebab menu), and a
   button can't contain another button. */
.roster-student-row { display: flex; align-items: center; }
.roster-student-row:hover { background: var(--bg-elev-3); }
.roster-student-info {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  padding: 9px 14px;
  background: none;
  border: none;
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.roster-student-info span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.roster-student-row .avatar { width: 26px; height: 26px; font-size: 0.76rem; flex-shrink: 0; }
.roster-student-row .kebab-wrap { margin-right: 8px; flex-shrink: 0; }
.roster-dropdown-empty { padding: 12px 14px; font-size: 0.85rem; color: var(--text-dim); }

.join-code-wrap { position: relative; display: inline-block; }
.copy-code-popover {
  position: absolute;
  top: -42px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  animation: modal-in 0.15s ease;
}
.copy-code-popover .btn { padding: 6px 14px; font-size: 0.78rem; white-space: nowrap; }
.kebab-menu {
  display: none;
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  min-width: 210px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  z-index: 30;
  overflow: hidden;
}
.kebab-menu.open { display: block; }
.kebab-menu button, .kebab-menu a {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  background: none;
  border: none;
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
  text-decoration: none;
  font-family: inherit;
}
.kebab-menu button:hover, .kebab-menu a:hover { background: var(--bg-elev-3); }
.kebab-menu .kebab-danger { color: var(--danger); }

.builder-session-row {
  display: grid;
  grid-template-columns: 34px 1fr 1fr;
  gap: 12px;
  align-items: end;
  margin-bottom: 14px;
  padding: 14px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
}
.builder-session-row .session-index { margin-bottom: 12px; }

/* ---------- modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(6, 7, 10, 0.65);
  backdrop-filter: blur(3px);
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 20px;
  overflow-y: auto;
  z-index: 100;
}
.modal-overlay.open { display: flex; }
.modal {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 560px;
  padding: 30px;
  box-shadow: var(--shadow-lg);
  animation: modal-in 0.16s ease;
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 22px;
}
.modal-head h2 { font-size: 1.25rem; }
.modal-close {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
  background: transparent;
  border: none;
}
.modal-close:hover { background: var(--bg-elev-2); color: var(--text); }
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 24px;
}

/* The minute-medal celebration popup — same .modal shell as every other
   modal, just centered instead of left-aligned, and with its own big
   medal image up top rather than a form. */
.medal-modal { text-align: center; max-width: 380px; }
.medal-modal .eyebrow { text-align: center; }
.medal-modal h2 { font-size: 1.4rem; margin-bottom: 10px; }
.medal-modal p { color: var(--text-dim); line-height: 1.5; }
.medal-modal .modal-actions { justify-content: center; }
.medal-modal-img {
  width: 140px;
  height: 140px;
  margin: 0 auto 16px;
  display: block;
  filter: drop-shadow(0 8px 24px rgba(255, 182, 72, 0.25));
}

/* Confetti burst, spawned alongside the medal popup — a plain overlay of
   small falling rectangles, not a canvas/library, since it only needs to
   run for a couple of seconds and then get thrown away. Sits above the
   modal (z-index 100) so it falls over the popup, not behind it. */
.confetti-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 150;
}
.confetti-piece {
  position: absolute;
  top: -24px;
  width: 8px;
  height: 14px;
  opacity: 0.95;
  animation: confetti-fall linear forwards;
}
@keyframes confetti-fall {
  0% { transform: translateY(0) rotate(0deg); }
  100% { transform: translateY(112vh) rotate(600deg); }
}

/* ---------- landing page ---------- */
.hero {
  padding: 140px 0 60px;
  text-align: center;
}
.lang-rotator {
  min-height: clamp(3.8rem, 11vw, 8.4rem);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  margin-bottom: 68px;
}
.lang-rotator-word {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(3.2rem, 9vw, 7.5rem);
  line-height: 1.15;
  color: var(--text);
  display: inline-block;
  max-width: 100%;
  text-align: center;
  overflow-wrap: break-word;
}
.lang-char {
  display: inline-block;
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .lang-char { will-change: auto; }
}
.hero-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }

/* Below ~569px, .lang-rotator-word's font-size clamp is pinned at its
   3.2rem floor (9vw stops winning), so every phrase renders at the same
   size there — some of the longer rotating phrases (e.g. "Una Palabra
   Nueva") wrap to a second line at that size while shorter ones don't.
   Reserve room for two lines at that fixed size so the box itself never
   changes height as the words rotate, instead of the content below
   jumping up and down as it goes from a one-line word to a two-line one. */
@media (max-width: 600px) {
  .lang-rotator { min-height: 7.4rem; }
}

.features {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  padding: 40px 0 100px;
}
.feature-card {
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 26px;
}
.feature-icon {
  width: 42px; height: 42px;
  border-radius: 11px;
  display: grid; place-items: center;
  background: var(--accent-soft);
  color: var(--accent);
  margin-bottom: 16px;
}
.feature-icon.tint-info { background: var(--info-soft); color: var(--info); }
.feature-icon.tint-success { background: var(--success-soft); color: var(--success); }
.feature-icon.tint-warning { background: var(--warning-soft); color: var(--warning); }
.feature-card h3 { font-size: 1.02rem; margin-bottom: 8px; }
.feature-card p { font-size: 0.88rem; color: var(--text-dim); }
/* Two columns rather than three — at this card's actual width (roughly a
   third of the page container), three columns squeezed longer names like
   "Mandarin Chinese" and "Indonesian" into an awkward wrap; two gives each
   one enough room to sit on one line in the common case. */
.lang-list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px 12px;
  margin: 2px 0 12px;
  padding: 0;
}
.lang-list li {
  position: relative;
  padding-left: 14px;
  font-size: 0.88rem;
  color: var(--text-dim);
}
.lang-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--success);
}

.features-intro { text-align: center; max-width: 620px; margin: 0 auto; padding: 40px 0 0; }
.features-intro .eyebrow { display: flex; justify-content: center; }
.features-intro h1, .features-intro h2 { font-family: var(--font-head); font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: 10px; }
.features-intro p { color: var(--text-dim); font-size: 0.95rem; }

/* .features is also used for the second row below the REAL diagram — these
   two just trim the top/bottom padding on the half that now butts up
   against a features-intro block instead of open page padding, .features
   itself (and every one of its other rules) is untouched. */
.features-row-1 { padding-bottom: 16px; }
.features-row-2 { padding-top: 16px; }

/* ---------- REAL diagram ---------- */
.real-section {
  padding: 16px 0 48px;
  display: flex;
  align-items: center;
  gap: 40px;
}
.real-rows { flex: 1 1 480px; min-width: 0; }
.real-graphic {
  flex: 0 0 auto;
  width: clamp(200px, 25vw, 300px);
  align-self: center;
}
.real-graphic svg { display: block; width: 100%; height: auto; }
.real-row {
  display: grid;
  grid-template-columns: auto minmax(150px, 210px) 1fr;
  align-items: center;
  gap: 28px;
  padding: 30px 0;
  border-bottom: 1px solid var(--border-soft);
}
.real-row:last-child { border-bottom: none; }
.real-letter {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: clamp(2.6rem, 6vw, 4.2rem);
  line-height: 1;
}
.real-r .real-letter { color: var(--accent); }
.real-e .real-letter { color: var(--info); }
.real-a .real-letter { color: var(--success); }
.real-l .real-letter { color: var(--warning); }
.real-word { font-family: var(--font-head); font-weight: 700; font-size: 1.15rem; }
.real-desc { color: var(--text-dim); font-size: 0.92rem; max-width: 520px; }

/* ---------- research / stats section ---------- */
.stats-section { padding: 16px 0 100px; }
.stat-card {
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 40px clamp(20px, 5vw, 56px) 32px;
  max-width: 760px;
  margin: 8px auto 0;
}
.stat-bar-row {
  display: grid;
  grid-template-columns: 200px 1fr 64px;
  align-items: center;
  gap: 16px;
  padding: 12px 0;
}
.stat-bar-label { font-size: 0.88rem; color: var(--text-dim); }
.stat-bar-track {
  height: 16px;
  border-radius: 999px;
  background: var(--bg-elev-3);
  overflow: hidden;
}
.stat-bar-fill { height: 100%; border-radius: 999px; background: var(--border); }
.stat-bar-value {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.92rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.stat-bar-row.is-highlight .stat-bar-label { color: var(--text); font-weight: 700; }
.stat-bar-row.is-highlight .stat-bar-value { font-size: 1.05rem; }
.stat-bar-row.is-highlight.tint-accent .stat-bar-fill { background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
.stat-bar-row.is-highlight.tint-accent .stat-bar-value { color: var(--accent); }
.stat-bar-row.is-highlight.tint-info .stat-bar-fill { background: var(--info); }
.stat-bar-row.is-highlight.tint-info .stat-bar-value { color: var(--info); }
.stat-bar-row.is-highlight.tint-success .stat-bar-fill { background: var(--success); }
.stat-bar-row.is-highlight.tint-success .stat-bar-value { color: var(--success); }
.stat-scale-note { font-size: 0.78rem; color: var(--text-faint); margin-top: 18px; }
.stat-blurb { max-width: 680px; margin: 28px auto 0; text-align: center; color: var(--text-dim); font-size: 0.95rem; }
.stat-source { max-width: 680px; margin: 14px auto 0; text-align: center; color: var(--text-faint); font-size: 0.78rem; }

.site-footer {
  border-top: 1px solid var(--border-soft);
  padding: 26px 0;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.82rem;
}

/* ---------- auth pages ---------- */
.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
}
.auth-wrap { width: 100%; max-width: 420px; }
.auth-wrap .brand { display: flex; justify-content: center; margin-bottom: 28px; }
.auth-card h1 { font-size: 1.4rem; margin-bottom: 6px; }
.auth-card .sub { color: var(--text-dim); font-size: 0.88rem; margin-bottom: 26px; }
.auth-switch { text-align: center; margin-top: 20px; font-size: 0.88rem; color: var(--text-dim); }
.auth-switch a { color: var(--accent-2); font-weight: 600; }
.demo-note {
  margin-top: 18px;
  font-size: 0.78rem;
  color: var(--text-faint);
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  line-height: 1.5;
}
.demo-note b { color: var(--text-dim); }

/* ---------- conversation / chat ---------- */
.chat-shell {
  max-width: 780px;
  margin: 0 auto;
  padding: 24px;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 70px);
}
/* Just holds the End Conversation button now — used to also hold a
   partner-name/avatar header (chat-partner/partner-avatar), removed once
   the real animated NovaPal character made that generic stand-in
   redundant. justify-content is flex-end rather than the old
   space-between since there's only ever the one child here now. */
/* Not justify-content: space-between — Back and End Conversation are never
   actually both visible at once (one hides exactly when the other shows),
   and space-between pins a single remaining flex item to the START of the
   row, not the end, so End Conversation would land on the left the moment
   Back disappeared. margin-left: auto on end-btn instead, which pushes it
   to the right regardless of whether anything else is present to space
   against. */
/* Sticky, same idea as .navbar above it — conversation.html's own End
   Conversation button lives inside .navbar-user instead of a plain
   in-page row, so it's pinned automatically and can't ever scroll out of
   view; trial.html has no equivalent navbar slot, so this row needs its
   own sticky pin to behave the same way once a call is connected.
   top: 70px sits it directly below the navbar's own height (see
   .chat-shell's identical "100vh - 70px" assumption above). */
.chat-topbar {
  display: flex;
  align-items: center;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 16px;
  position: sticky;
  top: 70px;
  z-index: 30;
  background: rgba(11, 13, 18, 0.85);
  backdrop-filter: blur(10px);
}
.chat-topbar #end-btn { margin-left: auto; }

.chat-window {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 8px 4px 24px;
}
.bubble-row { display: flex; }
.bubble-row.from-ai { justify-content: flex-start; }
.bubble-row.from-me { justify-content: flex-end; }
.bubble {
  max-width: 72%;
  padding: 12px 16px;
  border-radius: 16px;
  font-size: 0.94rem;
  line-height: 1.5;
}
.bubble-row.from-ai .bubble {
  background: var(--bg-elev-2);
  border: 1px solid var(--border-soft);
  border-bottom-left-radius: 4px;
}
.bubble-row.from-me .bubble {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #1a0f0a;
  border-bottom-right-radius: 4px;
  font-weight: 500;
}
.typing-dots { display: inline-flex; gap: 4px; padding: 4px 2px; }
.typing-dots span {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-faint);
  animation: bounce 1.2s infinite ease-in-out;
}
.typing-dots span:nth-child(2) { animation-delay: 0.15s; }
.typing-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes bounce { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }

.chat-input-bar {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  border-top: 1px solid var(--border-soft);
  padding-top: 16px;
}
.chat-input-bar textarea {
  min-height: 48px;
  max-height: 140px;
}
/* Three columns, not two — Back pinned left, the assignment info truly
   centered in the middle column regardless of Back's own width, and an
   empty third column on the right balancing it out so the middle column
   actually sits in the visual center of the bar, not just the center of
   the leftover space after Back. */
.chat-meta-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  margin-bottom: 14px;
  font-size: 0.82rem;
  color: var(--text-faint);
}
.chat-meta-bar #back-btn { justify-self: start; }
.chat-meta-info {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
}

/* ---------- partner video call ---------- */
/* Breaks #call-state out of the page's normal container-narrow (720px)
   width so the call itself can be as wide as the navbar/header content
   (1120px), without widening every other state on this page (ready,
   completed, not-paired) that's still meant to read as a narrow centered
   card. Centers on the viewport the same way container-narrow's own
   margin:auto already does, just wider. */
.call-shell {
  max-width: 1120px;
  width: calc(100vw - 48px);
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}
.call-layout { display: flex; gap: 16px; }
.call-main { flex: 1; min-width: 0; }
.call-video-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.video-tile { position: relative; }
.video-tile video { display: block; width: 100%; border-radius: var(--radius); background: #000; aspect-ratio: 4/3; object-fit: cover; }
.video-avatar-overlay {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: #000;
  border-radius: var(--radius);
}
.video-avatar-overlay.show { display: flex; }
.video-avatar-overlay .video-waiting-text { color: var(--text-dim); font-size: 0.88rem; text-align: center; padding: 0 20px; }
.video-avatar-overlay img {
  width: 30%;
  aspect-ratio: 1;
  min-width: 56px;
  max-width: 110px;
  border-radius: 50%;
  object-fit: cover;
}
/* Same orange gradient + dark text as the plain .avatar fallback used
   everywhere else (navbar, roster) — just bigger, so it reads as the
   same default avatar, not a different, greyer one only used here. */
.video-avatar-overlay .avatar-fallback {
  width: 30%;
  aspect-ratio: 1;
  min-width: 56px;
  max-width: 110px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #1a0f0a;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.6rem;
}
/* Was position:fixed overlaying the page — now a normal flex sibling of
   .call-main inside .call-layout, so opening it actually shrinks the
   video column (flexbox reflows .call-main automatically once this takes
   up its own space) instead of floating on top of it. Height comes from
   the default flex stretch (matches .call-main's height) rather than a
   fixed value. */
.call-chat-panel {
  display: none;
  flex-direction: column;
  flex: 0 0 340px;
  background: var(--bg-elev-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 16px;
}
@media (max-width: 760px) {
  .call-video-grid { grid-template-columns: 1fr; }
  .call-layout { flex-direction: column; }
  .call-chat-panel { flex-basis: auto; width: 100%; height: 380px; }
}

/* ---------- voice conversation stage (Phase 5) ---------- */
.voice-stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  text-align: center;
  padding: 24px;
}
/* No circle, no glow, no background — the character sits directly on the
   page's own black, same as it was drawn against. A circular "orb" made
   sense when this was a plain pulsing shape standing in for Nova Pal;
   now that the actual character lives here, a ring drawn around it on an
   already-black page was just an extra shape with nothing left to do.
   The connecting/listening/speaking states are entirely the character's
   own job now (see "NOVAPAL AVATAR" below) — breathing, blinking,
   thinking, the talking mouth/arm — not this container's. */
.voice-orb {
  display: grid;
  place-items: center;
}
.voice-orb svg.np-root { width: 300px; height: auto; display: block; }

/* ---------- NOVAPAL AVATAR ----------
   The animated character mounted into .voice-orb by js/novapal-avatar.js
   — that file builds the SVG and toggles these classes/attributes, this
   is purely the visual side: what each state/class actually looks like.
   Four independent animation layers, deliberately kept separate rather
   than combined into one, so they can run at once without fighting each
   other the way stacking transforms on one element would: breathing
   (np-breathe, a slow scale pulse, always running) targets the whole
   character as one group; blinking (np-eyes.is-blinking) targets only
   the eyes; waving (np-arm-pivot.is-greeting-wave, a rotation around the
   shoulder) and the talk-bob (np-arm-bob, a plain translateY driven
   directly by setTalkLevel()) each need their own nested <g> too, even
   from EACH OTHER, since a single element's `transform` can only ever
   hold one animation/value at a time — stacking a rotation and a slide
   on the same element would mean one silently overwrites the other
   instead of both applying together. */
.np-root { overflow: visible; }
.np-body, .np-arm, .np-spines line, .np-arm-spikes line { stroke: var(--success); fill: none; stroke-linecap: round; stroke-linejoin: round; }
.np-body { stroke-width: 8; }
.np-arm { stroke-width: 8; }
.np-arm-spikes line { stroke-width: 5; }
/* A single smooth lift when speaking starts, settling back down once it
   ends — see setState() in js/novapal-avatar.js. Deliberately NOT
   updated continuously per animation frame the way the mouth is; doing
   that here read as the arm twitching on every syllable instead of a
   natural, occasional gesture. */
.np-arm-bob { transition: transform 0.4s ease-out; }
.np-spines line { stroke-width: 5; }
.np-eye-outline { fill: none; stroke: var(--success); stroke-width: 6; }
.np-pupil { fill: var(--success); transition: transform 0.25s ease; }
.np-sparkle { fill: #eafff5; }
/* The smile morphs its own `d` attribute between resting and curled-up
   shapes (see MOUTH_SMILE_D/MOUTH_GREET_D and setMouthGreeting() in
   js/novapal-avatar.js) rather than crossfading two separate shapes —
   crossfading briefly showed both curves at once mid-fade, which read as
   a glitch; animating `d` directly, which modern browsers can interpolate
   when both shapes share the same path-command structure, reads as one
   curve actually deepening in place. mouthSmile's own display property is
   still set elsewhere, by setState(), to swap against np-mouth-talk; that
   one stays an instant swap since it's a bigger, more different shape
   change where easing between them wouldn't read as natural anyway. */
/* Two different speeds, not one — CSS resolves which transition-duration
   applies by the element's class state AFTER the triggering change, so
   the plain/resting rule below (no .is-curled) governs reverting BACK to
   resting, and .is-curled's own rule governs curling IN, each read at the
   moment js/novapal-avatar.js's setMouthGreeting() toggles the class
   alongside `d` itself. Quick in, slower out. */
.np-mouth-smile { fill: none; stroke: var(--success); stroke-width: 7; stroke-linecap: round; transition: d 1.2s ease-in-out; }
.np-mouth-smile.is-curled { transition: d 0.35s ease-in-out; }
.np-mouth-talk { fill: #05060a; stroke: var(--success); stroke-width: 6; }

/* Breathing: always running, every state, origin near the base so the
   character reads as gently rising rather than scaling symmetrically
   from its own center. */
.np-breathe { transform-origin: 120px 290px; animation: np-breathe 3.6s ease-in-out infinite; }
@keyframes np-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.018); }
}

/* Blinking: the motion itself is this one CSS rule; js/novapal-avatar.js
   only ever adds/removes the class, on a randomized timer (a plain
   infinite CSS animation can't vary its own interval, see that file). */
.np-eyes { transform-origin: 120px 103px; transition: transform 0.09s ease-out; }
.np-eyes.is-blinking { transform: scaleY(0.05); }

/* Thinking: pupils glance up and to the side — set directly off the
   root's data-state (written by setState()) rather than a second class,
   since state is already mutually exclusive by nature (never both
   thinking and speaking at once) where blinking can overlap any of them. */
.np-root[data-state="thinking"] .np-pupil { transform: translate(3px, -6px); }

/* Looking away: the resting gaze between idle-cycle waves, toggled by
   startIdleWave()/stopIdleWave() in js/novapal-avatar.js, not tied to
   data-state the way thinking is, since it needs to turn on and off
   independently while the character stays in the 'idle' state the whole
   time. Same up-and-to-the-right direction as the old static default
   gaze this replaced. */
.np-eyes.is-looking-away .np-pupil { transform: translate(5px, -8px); }

/* Waving: rotates around the shoulder point where the arm actually meets
   the body, not the arm's own bounding-box center, so it swings like a
   real arm rather than spinning in place. Stays however the animation's
   final frame leaves it (forwards) — playGreetWave() (see
   js/novapal-avatar.js) is what removes the class afterward on the next
   call, not a timer in here. 5 quick reps over 1.5s total, used both by
   the repeating pre-connect idle cycle and the one-shot connect-time
   greeting, so both look identical. */
.np-arm-pivot.is-greeting-wave { transform-origin: 170px 195px; animation: np-wave 0.3s ease-in-out 5; }
@keyframes np-wave {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(-18deg); }
}
.voice-status-text {
  color: var(--text-faint);
  font-size: 0.94rem;
  max-width: 420px;
}
.voice-error-text {
  color: var(--danger);
  font-size: 0.88rem;
  max-width: 420px;
}
.debug-transcript {
  width: 100%;
  border-top: 1px solid var(--border-soft);
  margin-top: 8px;
  padding-top: 12px;
  font-size: 0.82rem;
  color: var(--text-faint);
}
.debug-transcript summary { cursor: pointer; }
#debug-transcript-log {
  margin-top: 8px;
  max-height: 160px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
}

/* ---------- free trial page (Phase 5) ---------- */
.trial-language-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  max-width: 260px;
}
.trial-language-field label {
  font-size: 0.82rem;
  color: var(--text-faint);
}
.trial-note {
  font-size: 0.8rem;
  color: var(--text-faint);
}
/* session-ended/ai-feedback-* are shared between trial.html and
   conversation.html — both pages show a "session's over" screen with the
   same loading-then-pop-in AI feedback shape, just with different content
   and different container ids for the bubbles. */
.session-ended {
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
  align-self: center;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.session-ended h2 { font-family: var(--font-head); font-size: 1.3rem; }
.session-ended p { color: var(--text-dim); font-size: 0.94rem; }

.ai-feedback { width: 100%; }
.ai-feedback-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 0.86rem;
  color: var(--text-faint);
}
.ai-spinner {
  display: inline-block;
  width: 34px;
  height: 34px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: ai-spin 0.8s linear infinite;
}
@keyframes ai-spin { to { transform: rotate(360deg); } }
.ai-feedback-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  animation: modal-in 0.25s ease;
}
.ai-feedback-result p { margin: 0; }
.ai-feedback-example {
  width: 100%;
  text-align: left;
  background: var(--bg-elev);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 16px 18px;
}
/* Standalone, not scoped to .ai-feedback-example, so the teacher review
   page's AI analysis panel can reuse the same "one or more example
   excerpts" layout without needing that same end-screen wrapper. */
.bubbles-list { display: flex; flex-direction: column; gap: 10px; }
.bubbles-list + .bubbles-list { margin-top: 14px; }
.bubbles-list .bubble { max-width: 85%; font-size: 0.88rem; }
/* The corrected-version bubble reuses the shared .bubble/.bubble-row
   styles (same look as the teacher review page's transcript) but in the
   app's existing success color, and deliberately as specific a selector
   as .bubble-row.from-me .bubble already is, so it reliably overrides
   that rule's gradient background regardless of stylesheet order. */
.bubble-row.from-me .bubble.bubble-corrected {
  background: var(--success-soft);
  border: 1px solid var(--success);
  color: var(--text);
  font-weight: 500;
}

/* ---------- exercise phase (conversation_plus_exercises) ---------- */
/* .exercise-stage reuses .voice-stage's centered flex layout (see
   conversation.html) — this just adds the left-aligned question/results
   list and action-bar layout on top of it. */
.exercise-questions {
  width: 100%;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  text-align: left;
}
.exercise-question-block { display: flex; flex-direction: column; gap: 10px; }
.exercise-question-prompt { font-size: 0.94rem; font-weight: 600; }

/* ---------- exercise results (rendered as an annotated copy of the
   question list above, not a separate visual style) ---------- */
.exercise-result-label {
  font-size: 0.74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.exercise-result-label.correct { color: var(--success); }
.exercise-result-label.incorrect { color: var(--danger); }
.exercise-result-answer {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.9rem;
}
.exercise-result-answer.correct-answer { color: var(--success); }
.exercise-result-icon.correct { color: var(--success); }
.exercise-result-icon.incorrect { color: var(--danger); margin-right: 2px; }
/* Same visual language as .duration-option/.session-type-option on
   purpose — a pill-style pick, just laid out to wrap for a variable
   number of options instead of a fixed grid. */
.exercise-options { display: flex; flex-wrap: wrap; gap: 10px; }
.exercise-option-btn {
  border: 1.5px solid var(--border);
  background: var(--bg-elev);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  cursor: pointer;
  font: inherit;
  font-size: 0.88rem;
  color: var(--text-dim);
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.exercise-option-btn:hover { border-color: var(--text-faint); }
.exercise-option-btn.selected { border-color: var(--accent); background: var(--accent-soft); color: var(--text); font-weight: 600; }
.exercise-text-input {
  width: 100%;
  min-height: 44px;
}
textarea.exercise-text-input { min-height: 80px; }
.exercise-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 4px;
}

/* ---------- toast ---------- */
.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--bg-elev-3);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  font-weight: 600;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 200;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.error { border-color: rgba(255,92,114,0.4); color: var(--danger); }

/* ---------- misc ---------- */
.divider { height: 1px; background: var(--border-soft); margin: 24px 0; }
.text-dim { color: var(--text-dim); }
.text-faint { color: var(--text-faint); }
.flex-row { display: flex; align-items: center; gap: 10px; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }

@media (max-width: 760px) {
  .features { grid-template-columns: 1fr; }
  .role-picker { grid-template-columns: 1fr; }
  .navbar-links { display: none; }
  .builder-session-row { grid-template-columns: 1fr; }
  .builder-session-row .session-index { display: none; }

  .real-section { flex-direction: column; gap: 8px; }
  .real-rows { width: 100%; }
  .real-graphic { width: 190px; order: -1; }
  .real-row { grid-template-columns: auto 1fr; row-gap: 8px; }
  .real-desc { grid-column: 1 / -1; max-width: none; }

  .stat-bar-row { grid-template-columns: 108px 1fr 48px; gap: 10px; }
  .stat-bar-label { font-size: 0.78rem; }
  .stat-card { padding: 32px 20px 26px; }
}
