/* Smashing English.
   ---------------------------------------------------------------------------
   Teal leads, white does the heavy lifting. The brand's other four colours are
   real but none of them is safe for body text — measured against white:

     #2d5568 navy   8.0:1   fine anywhere
     #427c99 blue   4.6:1   scrapes AA; links and UI, not paragraphs
     #fc3612 orange 3.7:1   FAILS AA at body size. Large text and fills only
     #5d9ebe blue   2.9:1   decorative
     #ffde59 yellow 1.3:1   a background, never a foreground

   So the loud ones are surfaces and fills, and text on them is white or a
   darkened variant. Her own site puts white nav links on #ec2f2f, which is
   4.2:1 and fails — the red here is deepened to #d92b2b (4.9:1) so it passes.
   Same colour to the eye, legible to everyone.

   smashingenglish.com is a red nav, teal sections with white type, a cyan band
   and Laura photographed into the hero. That's reproduced on the pages a
   student meets. The admin centre keeps the palette but not the volume — it is
   a screen full of margins and cost-per-turn, and a red bar over it would make
   it harder to read and easier to distrust. */

/* fonts.css is <link>ed from base.html rather than @imported here — an @import
   can't start downloading until this file has already arrived, which puts the
   webfonts a full round trip behind. */

:root {
  /* --- the brand's colours, all five of them -----------------------------
     Sampled from smashingenglish.com rather than the brand sheet, because the
     two disagree and the site is what her audience already knows. */
  --red: #d92b2b;           /* the masthead and the loud accent */
  --red-bright: #ec2f2f;    /* her exact site red — large fills only, see below */
  --red-deep: #a81c1c;
  --red-soft: #fdeaea;

  --teal: #33657d;          /* her hero band */
  --teal-logo: #195964;     /* the artwork's own teal */
  --teal-deep: #143f49;
  --teal-soft: #e2edf1;

  --cyan: #52adbd;          /* the band below her hero */
  --cyan-soft: #e0f1f4;

  --yellow: #ffde59;
  --yellow-soft: #fff3c8;
  --yellow-ink: #6b4e00;    /* 7.0:1 on --yellow-soft */

  --navy: #2d5568;

  /* --- surfaces ----------------------------------------------------------
     A warm cream rather than a cool near-white. Cool grey next to teal reads
     as "white, teal and grey"; the cream is picked out of the logo's own oval
     and gives the teal and red something to sit against. */
  --paper: #f8f3ea;
  --card: #ffffff;

  --ink: #1c3b45;
  --ink-soft: #63757c;
  --line: #e6ded0;
  --off: #bdb3a4;

  /* --- interactive -------------------------------------------------------- */
  --accent: var(--teal-logo);
  --accent-deep: var(--teal-deep);
  --accent-soft: var(--teal-soft);

  /* reversed out on a dark brand surface */
  --on-teal: #ffffff;
  --on-teal-soft: #c8d6e2;  /* her own subtitle colour */

  /* --- semantics ---------------------------------------------------------
     Caution is YELLOW, not red. Red is the brand's accent, so spending it on
     warnings would mean the masthead, the primary buttons and "you have run
     out of credits" were all the same colour and none of them would register.
     Yellow is a brand colour, it is the universal caution colour, and
     --yellow-ink on --yellow-soft clears AA comfortably.

     Red is kept for the genuinely wrong: a broken payment, an insecure
     default, a withdrawn consent. */
  --warn: var(--yellow-ink);
  --warn-soft: var(--yellow-soft);
  --danger: #a81c1c;
  --danger-soft: var(--red-soft);

  --radius: 14px;
  --shadow: 0 1px 2px rgba(40, 32, 20, .05), 0 8px 24px rgba(40, 32, 20, .07);

  --font: "League Spartan", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Bebas Neue", "League Spartan", Impact, sans-serif;
  --font-hero: "Titan One", "League Spartan", Impact, sans-serif;
}

* { box-sizing: border-box; }

html { overflow-x: hidden; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 16px/1.6 var(--font);
  -webkit-font-smoothing: antialiased;
}

/* Links were never styled, so every one of them was browser blue. */
a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-deep); }

/* Bebas Neue is condensed all-caps — brilliant for a label or a big number,
   unreadable as prose. Everything that uses it is short by construction. */
.eyebrow {
  font-family: var(--font-display);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* --- masthead ---------------------------------------------------------- */

/* Her site's signature: a red bar across the top. The logo survives on it
   because the artwork is a cream-filled oval with teal type — it reads as a
   sticker stuck on the red, which is exactly the brand's own idiom. */
.masthead {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  padding: 14px clamp(16px, 4vw, 40px);
  background: var(--red);
  border-bottom: 4px solid var(--teal);
}

.brand { display: flex; align-items: center; gap: 14px; text-decoration: none; }

/* The oval alone, not the full lockup: "LEARN ENGLISH the fun way!" sits in a
   tab below the oval and turns to mush under about 120px wide. The auth pages
   have room for the whole thing; a masthead doesn't. */
.brand-logo { display: block; height: 46px; width: auto; }
.brand:hover .brand-logo { opacity: .85; }

h1 { margin: 0; font-size: 1.15rem; letter-spacing: -.01em; }
.brand-sub { margin: 0; font-size: .8rem; color: var(--ink-soft); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.status-strip {
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  margin: 0; padding: 0; list-style: none;
  font-size: .78rem; color: var(--ink-soft);
}
.status-strip li { display: flex; align-items: center; gap: 7px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--off); flex: none; }
.status-strip li.live .dot { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.status-strip li.live { color: var(--ink); }

/* --- layout ------------------------------------------------------------ */

main {
  display: grid;
  gap: clamp(16px, 3vw, 28px);
  grid-template-columns: minmax(0, 1fr);
  padding: clamp(16px, 4vw, 40px);
  max-width: 1180px;
  margin: 0 auto;
}

@media (min-width: 900px) {
  main { grid-template-columns: minmax(340px, 420px) minmax(0, 1fr); align-items: start; }
}

/* The UA sheet's `[hidden] { display: none }` loses to any author rule that
   sets `display` — and `.field` sets `display: grid`. That put the guardian
   fieldset on screen for every visitor to /register, telling adults they were
   under 18. Anything carrying the attribute is meant to be gone, whatever else
   it is; nothing in this app hides an element and then expects to see it. */
[hidden] { display: none !important; }

section {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
}

/* --- stage ------------------------------------------------------------- */

.avatar-frame {
  aspect-ratio: 4 / 3;
  border-radius: 10px;
  overflow: hidden;
  /* The brand teal, so Laura reads against it the way she does on her own
     site. A pale tint left her floating on nearly-white. */
  background: var(--teal);
  display: grid;
  place-items: center;
  margin-bottom: 20px;
}
.avatar-frame iframe { width: 100%; height: 100%; border: 0; }

/* Laura stands in the frame until Tavus is wired up, rather than a letter in a
   circle. She is the product; a placeholder that looks like a placeholder was
   always going to undersell it. */
.avatar-placeholder { position: relative; width: 100%; height: 100%; overflow: hidden; }

.avatar-photo {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  height: 97%; width: auto; max-width: none;
  /* Anchored to the bottom so the crop falls at the waist rather than across
     her face, and just under full height so her hair isn't touching the edge. */
}

.avatar-caption {
  position: absolute; inset: auto 0 0; z-index: 1;
  padding: 34px 16px 14px; text-align: center;
  background: linear-gradient(to top, rgba(51, 101, 125, .97) 42%, rgba(51, 101, 125, 0));
}
#avatar-note { margin: 0 0 8px; font-size: .82rem; color: var(--on-teal-soft); }
.avatar-caption .ghost { color: var(--on-teal); border-color: rgba(255, 255, 255, .55); }
.avatar-caption .ghost:hover { background: rgba(255, 255, 255, .14); }

.controls { display: grid; gap: 14px; }

.field { display: grid; gap: 6px; font-size: .82rem; color: var(--ink-soft); }
.field em { font-style: normal; opacity: .7; }

/* Every text-like input, not just [type=text]. The old rule named one type, so
   email, password, number and date fields fell through to the browser default
   and sat next to styled ones looking like a different form. */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="number"],
input[type="date"],
input[type="tel"],
input[type="url"] {
  font: inherit;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--paper);
  color: var(--ink);
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
input::placeholder { color: var(--off); }

/* The one action the whole product exists for, so it gets the brand red. */
.mic {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  width: 100%; padding: 17px;
  /* Longhands, not the `font` shorthand. `inherit` is a CSS-wide keyword and
     cannot stand in for the family inside the shorthand, so `font: 700 1.05rem/1
     inherit` was invalid and thrown away whole — the one button this product
     exists for has been rendering in the browser's default 13px face. #57 */
  font: inherit; font-weight: 700; font-size: 1.05rem; line-height: 1;
  letter-spacing: .01em;
  color: #fff; background: var(--red);
  border: 0; border-radius: 11px; cursor: pointer;
  transition: transform .08s ease, background .15s ease;
}
.mic:hover { background: var(--red-deep); }
.mic:active { transform: translateY(1px); }
.mic:disabled { background: var(--off); cursor: not-allowed; }

.mic-icon {
  width: 14px; height: 14px; border-radius: 50%;
  background: currentColor; flex: none;
}
/* Listening is a *state*, not a warning — yellow with dark type, so it reads
   as "we're recording you" rather than "something has gone wrong". */
.mic.listening { background: var(--yellow); color: var(--teal-deep); }
.mic.listening .mic-icon { animation: pulse 1.2s ease-in-out infinite; }
.mic.thinking { background: var(--teal); color: #fff; }

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .45; transform: scale(1.35); }
}

.hint { margin: -4px 0 0; font-size: .78rem; color: var(--ink-soft); }

.typed { display: flex; gap: 8px; }
.typed input { flex: 1; }

.ghost {
  font: inherit; padding: 10px 14px;
  color: var(--accent); background: transparent;
  border: 1px solid var(--accent); border-radius: 9px; cursor: pointer;
}
.ghost:hover { background: var(--accent-soft); }

/* --- transcript -------------------------------------------------------- */

.transcript-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
/* Scoped to the transcript header. Bebas Neue has no lowercase at all — it
   renders every string as caps — so it can only go where the text is a label by
   design, never on a heading someone might later write a sentence into. */
.transcript-head h2 {
  margin: 0; font: 1.2rem/1 var(--font-display);
  letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft);
}
h2 { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -.01em; }

.latency { text-align: right; line-height: 1.2; }
.latency-value { font: 1.5rem/1 var(--font-display); font-variant-numeric: tabular-nums; }
.latency-label { display: block; font-size: .7rem; color: var(--ink-soft); }
.latency.over .latency-value { color: var(--warn); }
.latency.under .latency-value { color: var(--accent); }

.log { display: grid; gap: 16px; }
.empty { margin: 0; color: var(--ink-soft); font-size: .9rem; }

.exchange { display: grid; gap: 10px; padding-bottom: 16px; border-bottom: 1px solid var(--line); }
.exchange:last-child { border-bottom: 0; padding-bottom: 0; }

.said { margin: 0; font-size: 1.02rem; }
.said .who { display: block; font: .82rem/1 var(--font-display); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 4px; }
.said.student { color: var(--ink); }
.said.laura {
  padding: 12px 14px; border-radius: 10px;
  background: var(--teal); color: var(--on-teal);
  border-left: 4px solid var(--cyan);
}
.said.laura .who { color: var(--on-teal-soft); }

.verdict {
  display: inline-flex; align-items: center; gap: 6px; align-self: start;
  font: .82rem/1 var(--font-display); letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px;
}
.verdict.ok { background: var(--teal); color: #fff; }
.verdict.fix { background: var(--yellow); color: var(--yellow-ink); }
/* Held on the same line. Cyan rather than yellow: being asked to say something
   again is not a correction, and colouring it like one turns "let's hear that
   once more" into a telling-off. */
.verdict.again { background: var(--cyan-soft); color: var(--teal-deep); border: 1px solid var(--cyan); }

.errors { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
/* A correction is a nudge, not a failure. Yellow, and Laura's persona is
   written the same way — see prompts.py. */
.error {
  border: 1px solid var(--yellow); border-left: 4px solid var(--yellow);
  border-radius: 10px; padding: 10px 12px;
  background: var(--yellow-soft);
}
.error-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.error-type {
  font: .76rem/1.35 var(--font-display); letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-soft); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
}
.was { text-decoration: line-through; color: var(--danger); }
.arrow { color: var(--yellow-ink); }
.now { font-weight: 700; color: var(--teal-deep); }
.why { margin: 0; font-size: .88rem; color: var(--yellow-ink); }
.error-type {
  background: rgba(255, 255, 255, .6);
}

.scores { display: flex; flex-wrap: wrap; gap: 6px; }
.score {
  font-size: .72rem; padding: 3px 8px; border-radius: 6px;
  background: var(--yellow-soft); color: var(--yellow-ink);
}
.score.good { background: var(--teal-soft); color: var(--teal-deep); }

.meta { font-size: .72rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.error-banner {
  padding: 10px 12px; border-radius: 9px;
  background: var(--danger-soft); color: var(--danger); font-size: .88rem;
}
