/* Practice screen — landing tokens verbatim, light only.
   Geometry follows the "Landing / Practice — Clip" frame (Jam8v).
   Phone-first at 390px; 320px is the width that has broken this project
   before, so every rule below is written to survive it. */

:root{
  --paper:   #F2F0EC;
  --band:    #E5E1D8;
  --surface: #FFFFFF;   /* cards sit on paper as white, per the design frame */
  --quiet:   #E9E6E0;   /* the fill of a secondary, unbordered control */
  --ink:     #14130F;
  --ink-2:   #56534B;
  --ink-3:   #6E6A62;
  --accent:  #FF6B00;   /* fills and glyphs only — 2.5:1, never for text */
  --accent-t:#A34300;   /* text-safe accent */
  --accent-w:#FCEBDD;   /* accent wash, for icon chips */
  --rule:    #DAD5CB;

  /* the scored line, borrowed from the app. The rules under the words are
     graphics, so they take the design's colours as-is; the score *text* needs
     4.5:1 on white, which amber and red at those values do not have, hence a
     second, darker set used only for type. */
  --good:    #1E7F33;
  --weak:    #D98A00;
  --missed:  #C0241A;
  --good-t:  #1E7F33;
  --weak-t:  #8A5600;
  --missed-t:#B3221A;
  --good-w:  #E7F4EA;

  --shadow-card: 0 10px 28px -14px rgba(0,0,0,.28);
  --shadow-pill: 0 6px 24px -14px rgba(0,0,0,.45);

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

*{ box-sizing:border-box; }

html,body{ height:100%; }

body{
  margin:0;
  min-height:100dvh;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.5;
  -webkit-text-size-adjust:100%;
  overflow-wrap:anywhere;   /* the only value that also lowers min-content size */
}

button{ font:inherit; color:inherit; cursor:pointer; }

/* No border-radius here: setting one changes the ELEMENT's radius, and every
   control on this screen is a 999px pill — the focus ring's corners were being
   cut square. Browsers already draw the outline following the element's own
   radius. */
:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; }

.ic{ width:22px; height:22px; display:block; flex:none; }

/* --- player --------------------------------------------------------------- */

.stage{
  position:relative;
  aspect-ratio:16/9;
  background:var(--ink);
  overflow:hidden;
  border-radius:16px;
}
.stage iframe{ width:100%; height:100%; display:block; border:0; }

/* The embed's own controls are off; taps on it must not pause/seek behind our
   back, so a transparent shield sits over it and drives our own play toggle. */
.stage__shield{
  position:absolute; inset:0;
  background:transparent; border:0; padding:0;
  -webkit-tap-highlight-color:transparent;
}

.preroll{
  position:absolute; inset:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px;
  /* The video's own YouTube thumbnail fills this now, not a gradient wash. The
     ink underneath only shows in the moment before the image decodes, and
     through the letterbox edges of a 4:3 fallback. */
  background:var(--ink);
  pointer-events:none;   /* the shield underneath still takes the tap */
  text-align:center; padding:16px;
}
.preroll[hidden]{ display:none; }
.preroll__poster{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;     /* crops hqdefault's baked-in 4:3 bars back off */
  display:block;
}
.preroll__play{
  /* position:relative so it paints over the absolutely-positioned poster: an
     in-flow static child otherwise loses to any positioned sibling. */
  position:relative; z-index:1;
  width:78px; height:78px; border-radius:50%;
  background:rgba(255,255,255,.95); color:var(--accent);
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 8px 24px -6px rgba(0,0,0,.25);
}
.preroll__play .ic{ width:34px; height:34px; margin-inline-start:3px; }
/* White on the warm end of the gradient is 3.1:1, and a text-shadow is not a
   contrast fix — it is a blur. The label gets its own dark plate instead, which
   takes white to ~10:1 wherever on the wash it happens to sit. */
.preroll__label{
  position:relative; z-index:1;
  color:#fff; font-size:.92rem; font-weight:600; max-width:28ch;
  background:rgba(0,0,0,.46);
  padding:7px 14px; border-radius:999px;
}
@media (max-width:400px){
  .preroll__play{ width:60px; height:60px; }
  .preroll__play .ic{ width:26px; height:26px; }
}

/* A 3px `.progress` bar sat under the player, driven from the engine's tick. It
   is not in the design frame and it measured the whole video, which is the one
   span this screen never works in — everything else here is one line. Removed
   with its markup and its listener rather than left unreferenced. */

/* --- the pager ------------------------------------------------------------ */

/* It lives in the card's top row now (see .focus__top), not in a strip of its
   own above the card. */
.focus__pager{ display:flex; gap:10px; margin-inline-start:auto; flex:none; }

.rarrow{
  flex:none;
  width:44px; height:44px; border-radius:50%;
  border:1px solid var(--rule); background:var(--surface);
  color:var(--ink);
  display:flex; align-items:center; justify-content:center;
}
.rarrow .ic{ width:18px; height:18px; }
.rarrow:hover:not(:disabled){ background:var(--band); }
.rarrow:disabled{ opacity:.32; cursor:not-allowed; }
/* RTL: the layout already mirrors, the glyphs must too. */
[dir="rtl"] .rarrow{ transform:scaleX(-1); }

/* --- the line being practised -------------------------------------------- */

/* The card is the default view. A learner watching the player does not want a
   list scrolling itself under their eyes on every line — the full transcript
   is further down the document, and this is one line at a time, swiped. */
.focus{
  position:relative;
  overflow:hidden;
  touch-action:pan-y;   /* horizontal drags are ours, vertical scroll is not */
}
.focus__card{
  position:relative;
  display:flex; flex-direction:column; gap:12px;
  background:var(--surface);
  border:1px solid var(--rule);
  border-radius:20px;
  padding:20px 18px;
  box-shadow:var(--shadow-card);
  will-change:transform;
  transition:transform .18s ease, opacity .18s ease;
}
@media (min-width:560px){
  .focus__card{ padding:34px; gap:22px; }
}
.focus__card--dragging{ transition:none; }
@media (prefers-reduced-motion:reduce){
  .focus__card{ transition:none; }
}

.focus__top{
  display:flex; align-items:center; gap:10px;
  min-height:44px;
}
.focus__n{ color:var(--ink-3); font-size:.88rem; font-weight:500; }

/* The score is the design's pill: a check, a number, a tinted plate, in the
   same colour language as the rules under the words.
   No `margin-inline-start:auto` here: the pager below claims the row's free
   space, and two auto margins in one flex row SPLIT it — the score drifted to
   the middle of the row instead of sitting beside the counter. */
.focus__score{
  display:inline-flex; align-items:center; gap:6px;
  border-radius:999px; padding:5px 12px;
  border:1px solid currentColor;
  font-size:.82rem; font-weight:700; white-space:nowrap;
  color:var(--accent-t);
}
.focus__score--good{   color:var(--good-t);   background:var(--good-w); }
.focus__score--weak{   color:var(--weak-t);   background:#FBF1DF; }
.focus__score--missed{ color:var(--missed-t); background:#FBE9E7; }
.focus__score[hidden]{ display:none; }

/* Announced, never shown. The clip-rect form rather than display:none, which
   removes an element from the accessibility tree along with the screen. */
.sr-only{
  position:absolute; width:1px; height:1px;
  margin:-1px; padding:0; border:0;
  clip-path:inset(50%); overflow:hidden; white-space:nowrap;
}

.line{
  font-size:clamp(1.2rem, 4.8vw, 1.56rem);
  font-weight:600;
  line-height:1.5;
  letter-spacing:-.01em;
  margin:0;
  display:flex; flex-wrap:wrap; align-items:flex-end; gap:0 .38em;
}

.w{
  position:relative;
  /* Only as much padding as the rule under the word needs. `min-height:44px`
     lived here once and inflated every line box to ~55px, so a wrapped line read
     as two unrelated bands instead of one sentence.
     There was also a ::before overlay giving each word a 44px touch target; the
     words are not interactive any more, so both are gone. */
  padding-bottom:7px;
  color:var(--ink);
  transition:color .12s;
}
/* The scored line: a rule under the word, drawn only once scored. */
.w::after{
  content:""; position:absolute; inset-inline:0; bottom:2px;
  height:5px; border-radius:2px;
  background:transparent;
  transition:background .18s;
}
.w--playing{ color:var(--accent-t); }
.w--playing::after{ background:var(--accent); }
.w--good::after{   background:var(--good); }
.w--weak::after{   background:var(--weak); }
.w--missed::after{ background:var(--missed); }
.w--skipped{ color:var(--ink-3); }

.translation{
  margin:0;
  color:var(--ink-2);
  font-size:1rem;
  line-height:1.4;
}
.translation[hidden]{ display:none; }


/* --- the card's own controls --------------------------------------------- */

/* A two-column grid of equal halves, not a flex row.
   The first pill's label swaps Listen ↔ Pause as the line plays, and a
   content-sized pill (`flex:1 0 auto`) resized itself on every toggle — the pair
   jumped sideways under the thumb that had just tapped it. Grid columns are
   1fr 1fr whatever is written in them, so nothing moves.
   That also retires the old wrap-vs-ellipsis problem rather than re-solving it:
   the label is one word now ("Listen", "Слушать"), which fits a 126px half of a
   320px card with room to spare, so there is no second row to fall to. */
.focus__listen{
  display:grid; grid-template-columns:1fr 1fr; gap:10px; padding-top:2px;
}
/* Hidden until a take exists — see renderListenBack(). `display:none` explicitly
   because the element is a grid container, and a grid container's `display` beats
   the UA's `[hidden]` rule. */
.focus__listen[hidden]{ display:none; }
.lbtn{
  min-width:0;
  min-height:48px; border-radius:999px;
  border:1px solid transparent; background:var(--quiet);
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:0 16px;
  font-size:.9rem; font-weight:700;
  color:var(--ink);
}
.lbtn .ic{ width:16px; height:16px; }
/* No ellipsis and no mid-word break: a label this button cannot show in full is a
   label that has to wrap, and the only thing left of "Listen to t…" is a guess.
   (body sets overflow-wrap:anywhere, which would split "Слушать".) */
.lbtn__label{ overflow-wrap:normal; text-align:center; }
.lbtn:hover:not(:disabled){ background:var(--band); }
.lbtn[aria-pressed="true"]{
  border-color:var(--accent);
  background:var(--accent-w);
  color:var(--accent-t);
}
/* The design outlines the pill that plays YOU. It is a 1px stroke, not a fill, so
   #FF6B00 is doing what CLAUDE.md allows it to do — the label stays ink. */
.lbtn--mine{ border-color:var(--accent); }
/* .4 opacity on ink is 2.6:1 — under 4.5:1, i.e. a disabled control nobody can
   read the state of. The disabled pair keeps a legible label and loses the
   pointer instead: it is showing the visitor WHY it is off (title + notice), and
   an unreadable explanation is not one. */
.lbtn:disabled{ background:var(--paper); color:var(--ink-3); cursor:not-allowed; }
.lbtn--mine:disabled{ border-color:var(--rule); }

/* Wraps for the same reason the pair above it does: at 320 the two settings and
   Speak wanted 300px of a 264px card, and the previous answer was to strip
   `Manual` down to a bare 40×46 timer glyph — under 44px and unreadable. Speak
   takes a row of its own instead. */
.focus__bar{
  display:flex; align-items:center; flex-wrap:wrap; gap:10px;
  padding-top:2px;
}
.focus__set{ display:flex; gap:8px; margin-inline-end:auto; min-width:0; }

/* The design's round disc at the start of the bottom row: icon-only Replay.
   48×48 is both the drawn size and the tap target, so no ::before overlay is
   needed the way the words need one. */
.disc{
  flex:none;
  width:48px; height:48px; border-radius:50%;
  border:0; background:var(--quiet);
  display:inline-flex; align-items:center; justify-content:center;
  padding:0;
  color:var(--ink);
}
.disc .ic{ width:19px; height:19px; }
.disc:hover:not(:disabled){ background:var(--band); }
.disc:disabled{ opacity:.4; cursor:not-allowed; }
/* The glyph is a counter-clockwise arrow: in RTL the layout mirrors, and a
   mirrored "back" arrow points forward. It stays as drawn. */

.spill{
  flex:none;
  min-height:48px; border-radius:999px;
  border:0; background:var(--quiet);
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  padding:0 14px;
  font-size:.82rem; font-weight:600;
  color:var(--ink-2);
  white-space:nowrap;   /* body sets overflow-wrap:anywhere, which splits "0.5×" */
}
.spill .ic{ width:16px; height:16px; color:var(--ink-3); }
.spill:hover:not(:disabled){ background:var(--band); }
.spill[aria-pressed="true"]{ color:var(--accent-t); }
.spill[aria-pressed="true"] .ic{ color:var(--accent-t); }
.spill:disabled{ opacity:.4; cursor:not-allowed; }

/* The accent is FILLED in exactly one place below — the action button at the
   handover. ("Your voice" carries the same accent as a 1px stroke, which is a
   different weight of the same signal, and the design frame draws both.) */

/* --- the mode switch ------------------------------------------------------- */

/* Two radios drawn as one switch. The checked half is white on the quiet track,
   which is the same figure/ground the rest of the card uses; shadow additionally
   takes the accent, mirroring the app's mic lighting up when shadow is armed.
   No sliding highlight here on purpose: the slide used to be the handover cue,
   and the handover is now the action button's job. A mode switch that animates on
   every press competes with the one animation that carries information. */
.mode{
  position:relative;
  /* Shrinks, never stretches: on a 1440 card the free space belongs to the row,
     not to a switch of two words. The min keeps both halves readable at 320
     (measured: ja "シャドーイング" is the widest label and fits 127px of it). */
  flex:0 1 auto;
  display:grid; grid-template-columns:1fr 1fr;
  gap:4px; padding:4px;
  border-radius:999px;
  background:var(--quiet);
  min-width:min(100%, 208px);
  max-width:320px;
}
/* Out of the grid flow (position:absolute), still focusable — the ring is drawn
   on the label. `opacity:0` rather than `display:none`, which would take the
   radios out of the tab order and off the a11y tree with them. */
.mode__i{
  position:absolute; width:1px; height:1px;
  opacity:0; margin:0; pointer-events:none;
}
.mode__b{
  min-height:48px; border-radius:999px;
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  padding:0 12px;
  font-size:.88rem; font-weight:700;
  color:var(--ink-2);
  cursor:pointer;
  transition:background-color .18s ease, color .18s ease;
}
.mode__b .ic{ width:17px; height:17px; color:var(--ink-3); }
/* body sets overflow-wrap:anywhere, which would split "Shadowing"/"Слушать". */
.mode__label{ overflow-wrap:normal; text-align:center; }
.mode__i:checked + .mode__b{
  background:var(--surface);
  color:var(--ink);
  box-shadow:0 2px 8px -3px rgba(0,0,0,.28);
}
/* Shadow armed: the glyph takes the accent fill and the label the text-safe
   accent — #FF6B00 is a fill and a glyph colour, never type (CLAUDE.md). */
#modeShadow:checked + .mode__b{ color:var(--accent-t); }
#modeShadow:checked + .mode__b .ic{ color:var(--accent); }
.mode__i:focus-visible + .mode__b{ outline:2px solid var(--ink); outline-offset:2px; }
/* Karaoke mode: no recognition in this browser, so Shadow is not on offer. The
   label stays legible — the notice under the card is what says why. */
.mode__i:disabled + .mode__b{ color:var(--ink-3); cursor:not-allowed; }
@media (prefers-reduced-motion:reduce){
  .mode__b{ transition:none; }
}

/* --- the action ------------------------------------------------------------ */

/* One button, one job at a time. `data-stage` is the whole of its state:
   idle | listening | turn | recording | scored.
   The stage that matters is `turn`: the button ignites accent and says "Your
   turn" the instant the line ends, which is how this screen says it without a
   sentence of instruction — the app does the same by igniting its Speak half. */
.act{
  flex:none;
  margin-inline-start:auto;          /* the row's end, wrapped or not */
  min-height:48px; min-width:118px;
  border:0; border-radius:999px;
  background:var(--ink); color:var(--paper);
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:0 18px;
  font-size:.92rem; font-weight:700;
  transition:background-color .2s ease, color .2s ease;
}
.act .ic{ width:18px; height:18px; }
.act__label{ overflow-wrap:normal; text-align:center; }
.act:hover:not(:disabled){ background:#000; }
.act:disabled{ opacity:.45; cursor:not-allowed; }
/* Your turn. Ink on #FF6B00 computes 6.60:1 — the same exception, and the same
   arithmetic, as `.mark` on the one-pager; white on it would be 2.84:1. */
.act[data-stage="turn"]{ background:var(--accent); color:var(--ink); }
/* Live. Red is reserved for a hot microphone, and nothing else on this screen
   uses it. White on #C0241A is 6.0:1; ink on it is 3.10:1, so it is white here
   and ink on the orange — the contrast decides, not consistency. */
.act[data-stage="recording"]{
  background:var(--missed); color:#FFFFFF;
  animation:pulse 1.1s ease-in-out infinite;
}
.act[data-stage="recording"]:hover{ background:var(--missed); }

@keyframes pulse{ 50%{ opacity:.72; } }
/* A pulsing control at the exact moment someone has to speak is a distraction
   they did not ask for. The colour and the label still change — that is
   information, not decoration. */
@media (prefers-reduced-motion:reduce){
  .act{ transition:none; }
  .act[data-stage="recording"]{ animation:none; }
}

/* Narrow phones: the row gives up padding, never words. There was a chip row
   (`.focus__more`) and a wait-mode pill (`#wait`) below this that each needed
   their own narrow-screen escape hatch; both are gone with the design's
   three-row card, so what is left is the two rows the design draws. */
/* Below 440 the three groups also cannot share one line: measured at 320 and 375
   in en, ru and ja, the switch alone wants ~200px of a 264px card. So the
   settings keep the first row and the switch and the action take one each — full
   width, never a stub, because a 40px-wide "Your turn" is not a cue. Above it,
   the row is the design's single line. */
@media (max-width:439px){
  .focus__card{ padding:16px 14px; }
  .spill{ padding:0 12px; }
  .spill__label{ font-size:.78rem; }
  .lbtn{ font-size:.86rem; padding:0 10px; gap:6px; }
  /* ja "シャドーイング" is 7 CJK characters and does not fit a 125px half of a
     320px card on one line. It wraps rather than shrinking to a stub or losing
     its glyph — hence the tightened leading, which keeps two lines inside the
     48px target instead of letting them touch its edges. The app solves the same
     label with minimumScaleFactor(0.8). */
  .mode__b{ font-size:.84rem; padding:0 10px; gap:6px; }
  .mode__label{ line-height:1.2; }
  .mode{ flex:1 0 100%; }
  .act{ flex:1 0 100%; margin-inline-start:0; }
}

/* --- notice (karaoke mode, errors) ---------------------------------------- */

.notice{
  margin-top:12px;
  padding:10px 12px;
  background:var(--band);
  border-inline-start:3px solid var(--accent);
  border-radius:10px;
  font-size:.9rem; line-height:1.45; color:var(--ink-2);
}
.notice[hidden]{ display:none; }
.notice b{ color:var(--ink); }

/* The transcript sheet — a <dialog> holding a second, scrollable copy of every
   line — was styled here. It went with the chip row that opened it: the design's
   card is three rows and has none to spare, and the document's own transcript
   section (page.css, `.tcard`) is both the way to reach a line and the copy a
   crawler reads. */
