/* Lexend (reading text/headings) + Inter (UI: buttons, nav, labels, tables) —
   a deliberate second voice alongside the phonetic font below, so headings
   and the practice text itself read differently from buttons and controls
   instead of the whole page being one undifferentiated typeface. */
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');

/* --- phonetic type ---------------------------------------------------------
 * IPA coverage in ordinary UI fonts is poor: Ubuntu and Cantarell have no
 * U+025D/U+025A (the r-coloured vowels in "bird" and "teacher"), so they fall
 * back to whatever else is installed, mid-word. Both faces below are the same
 * subset file — the browser downloads it once.
 *
 *   Phonetic      restricted to non-ASCII IPA, so stray /θ/ inside prose is
 *                 fixed without changing the look of ordinary text.
 *   PhoneticFull  unrestricted, for elements that are entirely phonetic —
 *                 a chip reading "θ ɪ ŋ k" must not render /k/ from a
 *                 different font than /θ/.
 */
@font-face {
  font-family: "Phonetic";
  src: url("fonts/phonetic.woff2") format("woff2");
  unicode-range: U+00E6, U+00F0, U+014B, U+03B8,
                 U+0250-02AF, U+02B0-02FF, U+0300-036F, U+1D00-1D7F;
  font-display: swap;
}
@font-face {
  font-family: "PhoneticFull";
  src: url("fonts/phonetic.woff2") format("woff2");
  font-display: swap;
}

:root {
  /* "Phonetic" trails both stacks purely as an IPA safety net: it only
     declares coverage for IPA codepoints (see the unicode-range above), so
     it never overrides Inter/Lexend for ordinary text — it just catches a
     stray phoneme symbol that ends up outside a dedicated --font-phonetic
     element, where Inter/Lexend have no glyph for it. */
  --font-ui: "Inter", "Phonetic", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-reading: "Lexend", "Phonetic", system-ui, sans-serif;
  --font-phonetic: "PhoneticFull", "Phonetic", system-ui, sans-serif;

  --bg: #ffffff;
  --surface: #f6f7f9;
  --border: #d8dce3;
  --text: #1c1f24;
  --muted: #5c6470;
  --accent: #2f5fd0;
  --good: #1c7a4a;
  --good-bg: #e3f4ea;
  --warn: #8a6100;
  --warn-bg: #fdf0d5;
  --bad: #b3261e;
  --bad-bg: #fbe4e2;
  --bar: #6b7280;
  --radius: 8px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 16px/1.55 var(--font-ui);
  color: var(--text);
  background: var(--bg);
}

/* The bar itself spans the full page edge-to-edge (a common, clean pattern),
   but its content is centered to the same column as .card/main below —
   otherwise the brand/nav sit flush left while everything under it is
   centered in an 820px column, and the two edges never line up. */
.site-header { border-bottom: 1px solid var(--border); }
.site-header-inner {
  display: flex;
  align-items: center;
  gap: 0.6rem 1.25rem;
  max-width: 820px;
  margin: 0 auto;
  padding: 0.85rem 1.5rem;
}
.brand { font-weight: 700; text-decoration: none; color: var(--text); }
.site-header nav { display: flex; gap: 1rem; }
.site-header nav a { color: var(--muted); text-decoration: none; }
.site-header nav a.current { color: var(--accent); font-weight: 600; }
.who { margin-left: auto; color: var(--muted); font-size: 0.9rem; }
.signout { margin: 0; }
.signout button { padding: 0.3rem 0.7rem; font-size: 0.85rem; }

main { max-width: 820px; margin: 0 auto; padding: 1.5rem; }

h1, h2 { font-family: var(--font-reading); font-weight: 600; }
h1 { font-size: 1.4rem; margin: 0 0 1rem; }
h2 { font-size: 1.1rem; margin: 0 0 0.75rem; }

section.card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  margin-bottom: 1.25rem;
}

label { display: block; font-weight: 600; margin-bottom: 0.35rem; }

textarea, select, input[type="text"] {
  width: 100%;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  background: var(--bg);
  color: var(--text);
}
/* Read aloud from this while speaking: eyes leave the screen and come back,
   so it wants to be larger and looser than the surrounding UI. */
textarea {
  min-height: 7rem;
  resize: vertical;
  font-size: 1.25rem;
  line-height: 1.7;
  font-family: var(--font-reading);
}
/* A saved text shown here for reading (not editing) — a different fill
   says "this isn't yours to type into" without greying the text itself,
   which still needs to stay legible while recording. */
textarea[readonly] {
  background: var(--surface);
  cursor: default;
}

.field { margin-bottom: 0.9rem; }
.hint { color: var(--muted); font-size: 0.85rem; margin-top: 0.3rem; }

button {
  font: inherit;
  padding: 0.5rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}
button:hover:not(:disabled) { border-color: var(--muted); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.danger { color: var(--bad); border-color: var(--bad); background: var(--bad-bg); }
.button-row { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }

.timer {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--muted);
}
.timer.recording { color: var(--bad); }
.timer.recording::before {
  content: "";
  display: inline-block;
  width: 0.6rem; height: 0.6rem;
  margin-right: 0.4rem;
  border-radius: 50%;
  background: var(--bad);
  animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) {
  .timer.recording::before { animation: none; }
}

audio { width: 100%; margin-top: 0.75rem; }

.notice {
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  margin-bottom: 1rem;
}
.notice.error { border-color: var(--bad); background: var(--bad-bg); color: var(--bad); }
.notice.info { border-color: var(--accent); background: #eaf0fc; }
[hidden] { display: none !important; }

.spinner {
  display: inline-block;
  width: 1rem; height: 1rem;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  vertical-align: -2px;
  margin-right: 0.5rem;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- results --- */

.overall {
  font-family: var(--font-reading);
  font-size: 2.4rem;
  font-weight: 700;
  line-height: 1;
}
.overall small { font-size: 0.9rem; font-weight: 400; color: var(--muted); }

.bars { margin: 1rem 0 0; display: grid; gap: 0.55rem; }
.bar-row { display: grid; grid-template-columns: 8rem 1fr 3rem; align-items: center; gap: 0.6rem; }
/* Both are <span>s. `display: block` is load-bearing, not decoration: width
   and height do not apply to non-replaced inline elements, so without it the
   fill is a zero-width empty inline box and only the track is visible. */
.bar-track {
  display: block;
  height: 0.55rem;
  background: var(--surface);
  border-radius: 99px;
  overflow: hidden;
}
/* Neutral, not the accent colour: accent means "interactive" everywhere else,
   and a blue bar reads as something you can click. */
.bar-fill {
  display: block;
  height: 100%;
  min-width: 2px;          /* a 0% score still reads as a bar, not a gap */
  background: var(--bar);
  border-radius: 99px;
}
.bar-value { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }

.transcript { font-size: 1.25rem; line-height: 2.2; margin-top: 0.5rem; }
.word {
  border: 0;
  padding: 0.15rem 0.35rem;
  border-radius: 4px;
  font: inherit;
  background: transparent;
  cursor: default;
}
/* Colour is reinforcement, not the signal: ~8% of men cannot separate the
   green and red backgrounds, so each band also carries a distinct underline
   that survives in greyscale. */
.word.good { background: var(--good-bg); color: var(--good); }
.word.warn {
  background: var(--warn-bg); color: var(--warn);
  text-decoration: underline wavy var(--warn);
  text-underline-offset: 3px;
}
.word.bad {
  background: var(--bad-bg); color: var(--bad);
  text-decoration: underline double var(--bad);
  text-underline-offset: 3px;
}
.word.flagged { cursor: pointer; }
.word.flagged[aria-expanded="true"] { outline: 2px solid var(--accent); }
.word.omission { text-decoration: line-through; opacity: 0.75; }
.word.insertion { font-style: italic; }

/* --- reference playback --- */

.listen-controls { margin-top: 0.9rem; align-items: center; }
button.listen { font-weight: 600; }
button.slow-toggle[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
button.loading { opacity: 0.6; cursor: progress; }
/* Playing → Stop: red rather than the Slow toggle's blue, so it doesn't
   read as just another pressed option — it's telling you a click will
   interrupt something, the same signal --bad already carries for the
   recording indicator elsewhere on this page. */
button.listen[aria-pressed="true"] {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}
/* select's base rule (shared with textarea/input) is width: 100%, wrong
   for something sitting inline in a .button-row next to buttons. */
.voice-select { width: auto; padding: 0.4rem 0.55rem; font-size: 0.9rem; }

/* Word-by-word highlighting synced to reference playback (tts spec §8). A
   fresh render of the reference text, not the ASR transcript below it —
   those can disagree word-for-word on an omission/insertion, this can't. */
.read-along {
  margin-top: 0.6rem;
  font-size: 1.1rem;
  line-height: 1.9;
  color: var(--muted);
  font-family: var(--font-reading);
}
.read-word { padding: 0.05rem 0.2rem; border-radius: 3px; }
.read-word.active { background: var(--accent); color: #fff; }

.word-wrap { display: inline-flex; align-items: center; gap: 0.15rem; }
button.speak {
  border: 0;
  background: transparent;
  padding: 0 0.15rem;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.65;
  /* The phoneme-chip icon sits inside .chip, which sets font-family to
     --font-phonetic for its IPA text. --font-phonetic's first face,
     PhoneticFull, declares no unicode-range, so it's a candidate for every
     codepoint including 🔊 (U+1F50A) — and being a narrow IPA subset font,
     it has no glyph there, which some browsers render as blank/invisible
     rather than falling through. Reset explicitly so the icon always
     resolves through a real emoji-capable font, regardless of ancestor. */
  font-family: var(--font-ui);
}
button.speak:hover:not(:disabled) { opacity: 1; }
button.speak:disabled { cursor: progress; }
button.speak[aria-pressed="true"] { opacity: 1; color: var(--bad); }

/* The legend shows real .word samples, so it demonstrates the underline
   treatment rather than only naming a colour. */
.legend {
  display: flex; gap: 1.1rem; flex-wrap: wrap; align-items: center;
  font-size: 0.85rem; color: var(--muted); margin-top: 0.75rem;
}
.legend .word { font-size: 0.85rem; cursor: default; }

.phoneme-detail {
  margin-top: 0.75rem;
  padding: 0.75rem 0.9rem;
  background: var(--surface);
  border-radius: var(--radius);
}
.phoneme-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.5rem; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.2rem 0.4rem 0.2rem 0.5rem;
  border-radius: 99px;
  font-size: 0.95rem;
  border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
  font-family: var(--font-phonetic);
}
/* Border style doubles as the non-colour channel here. */
.chip.good { background: var(--good-bg); color: var(--good); border-color: var(--good); }
.chip.warn {
  background: var(--warn-bg); color: var(--warn);
  border-color: var(--warn); border-style: dashed;
}
.chip.bad {
  background: var(--bad-bg); color: var(--bad);
  border-color: var(--bad); border-style: double; border-width: 3px;
}

.tips { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.8rem; }
.tips li { border-left: 3px solid var(--accent); padding-left: 0.8rem; }
.tips .ipa { font-weight: 700; font-size: 1.15rem; font-family: var(--font-phonetic); }
.tips .examples { color: var(--muted); font-size: 0.9rem; }
.tips .missing { color: var(--muted); font-style: italic; }

/* --- history --- */

.table-scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--border); }
th { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
td.title {
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
td.score { font-variant-numeric: tabular-nums; font-weight: 600; }
td.actions { text-align: right; white-space: nowrap; }
td.actions button { padding: 0.3rem 0.6rem; font-size: 0.9rem; }
/* Full width: label only. The icon exists in the DOM for the narrow
   breakpoint below, where it swaps places with the label. */
.row-action-icon { display: none; }
.status-failed { color: var(--bad); font-size: 0.9rem; }
.status-pending { color: var(--muted); font-size: 0.9rem; }
.empty { color: var(--muted); padding: 1.5rem 0; text-align: center; }
td.score.good { color: var(--good); }
td.score.warn { color: var(--warn); }
td.score.bad  { color: var(--bad); }

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

/* --- narrow viewports ------------------------------------------------------
 * One breakpoint, not a grid of them: everything above already reflows on
 * its own (flex rows wrap, the practice/results layout is a single column).
 * What's left is a handful of fixed sizes that were sized for desktop and
 * eat too much of a ~360px phone screen.
 */
@media (max-width: 480px) {
  /* Row layout was tried with flex-wrap first: brand + nav + username +
     sign-out don't reliably wrap as whole units (the auto-margin on .who
     that pins it right in the row layout skews the browser's line-breaking,
     so "History" ended up cut off rather than moved to its own line —
     confirmed with a real 320px screenshot, not just reasoned about).
     Column stacking has no such ambiguity: every child gets the full width
     and its own line, unconditionally. */
  .site-header-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
  }
  .who { margin-left: 0; }        /* only meant to push right in a row */
  .site-header nav { gap: 0.85rem; }

  main { padding: 1rem; }
  section.card { padding: 0.9rem 1rem; }

  /* 8rem for the label alone was a third of the viewport. 5.5rem clipped
     "Completeness" to "Completenes" (confirmed by screenshot) — it's the
     longest label, so it sets the floor for this column. */
  .bar-row { grid-template-columns: 6.2rem 1fr 2.2rem; gap: 0.4rem; }

  /* Every table cell gives a little back: at 375px the row still needed
     this after every column-specific cut below (measured: 403px of
     content in ~343px available). Spread thin across four cells, this
     alone is worth ~25px. */
  th, td { padding: 0.5rem 0.3rem; }

  /* white-space: normal alone doesn't reliably help here: table auto-layout
     sizes a column to its content's own minimum width (a button's text
     doesn't wrap internally), so the column still claims enough width to
     keep all three buttons on one line and the table just grows wider —
     confirmed with a real 320px screenshot, same as the header bug above.
     Forcing a row of buttons removes the ambiguity the same way. */
  td.actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.2rem;
    white-space: normal;
  }
  /* Even a column of three text buttons didn't leave room for Date + Text
     next to it at 375px CSS width (confirmed by screenshot: still clipped).
     Icons instead of labels is what a phone-sized data table needs — three
     ~2.2rem circles fit where three "Replay"-width buttons did not. */
  td.actions button {
    margin: 0;
    padding: 0.25rem;
    width: 1.9rem;
    height: 1.9rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .row-action-icon { display: inline; font-size: 1rem; }
  .row-action-label { display: none; }

  /* Prevented from wrapping mid-date into five stacked lines; nowrap plus
     the smaller font keeps it to one line. The year is the least useful of
     the three parts for telling same-week attempts apart, so it's what
     goes — confirmed by measuring actual rendered column widths that
     shrinking the buttons alone wasn't enough (below), the date column was
     the single largest, at 158px of a ~340px available row width. */
  td.date { white-space: nowrap; font-size: 0.8rem; }
  .date-year { display: none; }

  td.title { max-width: 4.5rem; }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a;
    --surface: #1e2126;
    --border: #333941;
    --text: #e8eaed;
    --muted: #9aa3af;
    --accent: #7aa2f7;
    --good: #6ecf9a; --good-bg: #14301f;
    --warn: #e8c27a; --warn-bg: #33280f;
    --bad: #f28b82;  --bad-bg: #3a1c1a;
    --bar: #8b93a1;
  }
  .notice.info { background: #1b2436; }
}
