/* ==========================================================================
   JLPT exam player + exam browser
   Builds on the tokens defined in styles.css
   ========================================================================== */

.exam-root,
.exams-root {
  --level-color: var(--blue);
  padding: 0 0 72px;
}
/* The exam page starts as a spinner and the footer sat in the first screen;
   when the setup card rendered the footer dropped 900px, a layout shift of
   0.32. Tall from the start, the footer begins below the fold and the render
   moves nothing that was visible. The setup card is always taller than a
   screen, so nothing is added under it. The library page is not treated
   this way: its level cards are written into the markup by the build, so
   nothing moves there, and a page that ended with a screen of empty space
   before the footer was the complaint. */
.exam-root { min-height: 100vh; }

/* Content gets its own breathing room so the sticky exam bar can sit flush
   against the site header instead of floating below a gap. */
.exam-root > .container,
/* The same rhythm token every other page's <main> uses. It was a hardcoded
   28px, so the exams library started its content eighteen pixels higher than
   Levels, Study, About and the statistics page - close enough to read as a
   wobble rather than a decision. */
.exams-root > .container { padding-top: var(--sp-section); }
.exam-root > .container.exam-body { padding-top: 26px; }

/* ------------------------------------------------------------- utilities */

/* The .btn system lives in styles.css, which every page loads. It used to be
   redefined here as well, with a different radius and padding, so the same
   button was one shape on the home page and another inside a paper. Nothing
   exam-only needs adding. */

.exam-level-chip {
  display: inline-grid;
  place-items: center;
  min-width: 44px;
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--level-color);
  color: #fff;
  font-weight: 800;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
}

/* --------------------------------------------------------- loading/error */

.exam-loading {
  display: grid;
  place-items: center;
  gap: 16px;
  padding: 90px 0;
  color: var(--muted);
}
.spinner {
  width: 42px;
  height: 42px;
  border: 3px solid var(--line);
  border-top-color: var(--blue);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.exam-error {
  max-width: 620px;
  margin: 60px auto;
  padding: 38px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  text-align: center;
}
.exam-error h2 { margin: 0 0 12px; color: var(--navy); }
.exam-error p { color: var(--muted); line-height: 1.6; }
.exam-error-detail {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  background: var(--panel-soft);
  padding: 10px 14px;
  border-radius: 10px;
  word-break: break-all;
}
.exam-error-links { display: flex; gap: 10px; justify-content: center; margin-top: 20px; flex-wrap: wrap; }

/* ----------------------------------------------------------- setup screen */

.exam-hero {
  position: relative;
  overflow: hidden;
  padding: 34px 38px;
  border-radius: var(--radius);
  color: #fff;
  background:
    linear-gradient(135deg, rgba(17, 45, 78, 0.94), rgba(17, 45, 78, 0.62)),
    linear-gradient(135deg, var(--level-color), var(--level-color));
  box-shadow: var(--shadow);
}
.exam-hero-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.exam-hero .exam-level-chip { background: rgba(255, 255, 255, 0.22); }
.exam-hero-sub { font-size: 0.86rem; opacity: 0.85; }
.exam-hero h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4vw, 2.7rem);
  line-height: 1.15;
}
.exam-hero-meta { margin: 10px 0 0; opacity: 0.85; font-size: 0.92rem; }

.exam-setup {
  margin-top: 22px;
  padding: 32px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.setup-block + .setup-block { margin-top: 34px; padding-top: 30px; border-top: 1px solid var(--line); }
.setup-title {
  margin: 0 0 6px;
  font-size: 1.06rem;
  color: var(--navy);
}
.setup-hint { color: var(--muted); font-size: 0.87rem; margin: 0 0 16px; }

/* Names the section this sitting does not have. Under the cards, because it
   is about what is not among them, and quiet, because it is a fact about the
   paper rather than something to act on. */
.setup-absent {
  margin: 14px 0 0;
  padding: 9px 12px;
  border-left: 3px solid var(--line);
  color: var(--muted);
  font-size: 0.84rem;
  line-height: 1.5;
}

.part-grid,
.mode-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.part-card,
.mode-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px;
  border: 1.5px solid var(--line);
  border-radius: 18px;
  background: var(--panel);
  cursor: pointer;
  transition: 0.18s ease;
}
.part-card:hover, .mode-card:hover { border-color: var(--blue); background: var(--panel-soft); }
.part-card input, .mode-card input { position: absolute; opacity: 0; pointer-events: none; }
.part-card.is-on, .mode-card.is-on {
  border-color: var(--level-color);
  background: color-mix(in srgb, var(--level-color) 8%, #fff);
  box-shadow: 0 8px 20px rgba(17, 45, 78, 0.08);
}
.part-text { display: flex; flex-direction: column; gap: 3px; }
.part-label { font-weight: 700; color: var(--navy); }
.part-sub { font-size: 0.8rem; color: var(--muted); }
/* A hollow ring when unticked. It used to be a solid grey disc, which is
   what a disabled control looks like - so four unticked sections read as
   four things you were not allowed to choose. */
.part-check {
  margin-left: auto;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: transparent;
  border: 2px solid var(--line);
  color: transparent;
  font-size: 0.75rem;
  font-weight: 800;
  transition: 0.18s ease;
}
.part-card:hover .part-check { border-color: var(--blue); }
.part-card.is-on .part-check {
  background: var(--level-color);
  border-color: var(--level-color);
  color: #fff;
}

.mode-card { flex-direction: column; align-items: flex-start; gap: 6px; }
.mode-card strong { color: var(--navy); font-size: 1rem; }
.mode-card span {
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
  /* Restated, not inherited: a stale copy of these rules on the other
     stylesheet used to shout this sentence in tracked-out bold capitals. */
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.timer-row { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-weight: 600; color: var(--navy); }
.switch input { position: absolute; opacity: 0; }
.switch-track {
  width: 46px; height: 26px;
  border-radius: 999px;
  background: var(--line);
  padding: 3px;
  transition: 0.2s ease;
}
.switch-dot {
  display: block;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
  transition: 0.2s ease;
}
.switch input:checked + .switch-track { background: var(--success); }
.switch input:checked + .switch-track .switch-dot { transform: translateX(20px); }

.minutes-field { display: inline-flex; align-items: center; gap: 10px; color: var(--muted); font-size: 0.88rem; }
.minutes-field input {
  width: 84px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  text-align: center;
  font-weight: 700;
  color: var(--navy);
}
.minutes-field input:disabled { opacity: 0.5; }

.setup-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 32px; }
.setup-note { margin: 16px 0 0; font-size: 0.82rem; color: var(--muted); }

/* ------------------------------------------------------------- exam shell */

.exam-bar {
  position: sticky;
  top: var(--header-h, 74px);
  z-index: 40;
  background: rgba(255, 255, 255, 0.94);
  /* Same prefix and same fallback as the site header - this bar sits over a
     paper of dense Japanese, where bleed-through is at its worst. */
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 6px 18px rgba(17, 45, 78, 0.06);
}
@supports not ((backdrop-filter: blur(1px)) or
               (-webkit-backdrop-filter: blur(1px))) {
  .exam-bar { background: #fff; }
}

.exam-bar-inner {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 14px 0;
}
.exam-bar-id { display: flex; align-items: center; gap: 10px; font-weight: 700; color: var(--navy); font-size: 0.9rem; }

/* The way out of the paper. Sits first in the bar because that is where a
   back control is looked for, and keeps a real hit area rather than being a
   bare arrow glyph. */
.exam-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 13px 7px 10px;
  margin-right: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--navy);
  font-size: 0.84rem;
  font-weight: 700;
  white-space: nowrap;
  transition: 0.16s ease;
}
.exam-back:hover {
  background: #f7fafd;
  border-color: var(--blue);
  color: var(--blue);
  box-shadow: 0 6px 14px -5px rgba(45, 110, 180, 0.35);
}
.exam-back-arrow { font-size: 1rem; line-height: 1; }
.exam-bar-period { white-space: nowrap; }
/* No min-width:0 here. With it, this group could be squeezed narrower than
   the count inside it - and the count is nowrap, so instead of shrinking it
   printed over the section label to its right. Between 900px and 1200px
   "0 / 106 answered" and "問題1 · Vocabulary" were drawn on top of each
   other. The group now refuses to go below its own contents; the bar takes
   the pressure on the progress track, which is the one part of it that can
   lose width without losing meaning. */
.exam-bar-progress { flex: 1 1 auto; display: flex; align-items: center; gap: 12px; }
.exam-bar-progress span { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.progress-track { flex: 1 1 40px; height: 8px; border-radius: 999px; background: var(--line); overflow: hidden; min-width: 40px; }

/* And a floor under the whole thing: if a language sets a section label long
   enough to run the bar out of room anyway, the label gives way rather than
   the layout. */
.exam-bar-tools { min-width: 0; }
.bar-section {
  min-width: 0;
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.progress-fill { height: 100%; width: 0; border-radius: 999px; background: linear-gradient(90deg, var(--blue), var(--success)); transition: width 0.3s ease; }
.exam-bar-tools { display: flex; align-items: center; gap: 12px; }

.exam-timer {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--navy);
  font-size: 0.95rem;
}
.exam-timer strong { font-size: 1.05rem; }
.exam-timer.is-urgent { background: rgba(200, 74, 82, 0.12); border-color: var(--danger); color: var(--danger); animation: pulse 1s ease-in-out infinite; }
/* Counting up, not down. Quieter than the exam clock because nothing is
   running out - it is there to answer "how long am I taking?", not to
   hurry anyone. */
.exam-timer.is-stopwatch {
  background: var(--panel-soft);
  color: var(--muted);
  border-color: var(--line);
}
.exam-timer.is-stopwatch strong { color: var(--navy); font-weight: 600; }

.exam-timer.is-off { color: var(--muted); font-size: 0.85rem; }
@keyframes pulse { 50% { opacity: 0.62; } }

/* One column. There was a sticky question-map column here; it is gone, and
   the paper has the width back - which reading passages and four-option
   listening questions both wanted. */
.exam-body {
  display: block;
  padding-top: 26px;
}
/* ------------------------------------------------------------- the paper */

/* The exam is one continuous page. Each 問題 becomes a section, each reading
   passage a block that keeps its questions beside it. */
.exam-paper {
  min-width: 0;
  display: grid;
  gap: 20px;
}

/* No `overflow: hidden` here: it would make this element a scroll container
   and stop the reading passage inside from sticking. The head rounds its own
   top corners instead. */
.paper-section {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.section-head {
  padding: 22px 28px;
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--level-color) 9%, #fff), var(--panel));
  border-bottom: 1px solid var(--line);
}

.section-head-top {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.section-tag {
  padding: 5px 14px;
  border-radius: 999px;
  background: var(--level-color);
  color: #fff;
  font-weight: 800;
  font-size: 0.86rem;
}

.section-kind {
  font-weight: 800;
  color: var(--navy);
  font-size: 0.95rem;
}
.section-kind em { font-style: normal; font-weight: 600; color: var(--muted); }

.section-instruction {
  font-size: 0.97rem;
  line-height: 1.9;
  color: var(--navy);
}
.section-instruction center { text-align: center; }
.section-instruction img { margin: 10px auto; }

.paper-block {
  padding: 24px 28px;
  border-top: 1px solid var(--line);
}
.paper-block:first-of-type { border-top: 0; }

.paper-block.has-passage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 28px;
  align-items: start;
}

/* The gap that separates one question from the next. It has to beat the
   biggest gap *inside* a question (stem to choices, 18px optical) by enough
   that the eye groups without being asked to: at 26px the two were 22 and 33,
   a ratio of 1.5, and the paper read as one even column of parts rather than
   as a list of questions. 32px puts the ratio at 2.2. Measured optically -
   from the last glyph of the stem, not from its line box, which carries
   7.5px of half-leading that the eye does not see. */
.block-questions {
  min-width: 0;
  display: grid;
  gap: 32px;
}

/* -- reading passage: stays in view while its questions scroll -- */

.q-passage {
  position: sticky;
  top: calc(var(--header-h, 74px) + var(--bar-h, 62px) + 20px);
  padding: 20px 22px;
  background: #fffdf6;
  border: 1px solid #f0e4c4;
  border-radius: 18px;
  max-height: calc(100vh - var(--header-h, 74px) - var(--bar-h, 62px) - 56px);
  overflow-y: auto;
}
.q-passage-label {
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 12px;
}
.q-passage-body { font-size: 1rem; line-height: 2; color: var(--text); }
.q-passage-body img { margin: 12px auto; border-radius: 8px; }
.q-passage-body table { border-collapse: collapse; margin: 12px 0; width: 100%; }
.q-passage-body td, .q-passage-body th {
  border: 1px solid var(--line);
  padding: 8px 10px;
}

/* -- one question -- */

.paper-question {
  scroll-margin-top: calc(var(--header-h, 74px) + var(--bar-h, 62px) + 18px);
  border-radius: 16px;
  transition: background 0.5s ease, box-shadow 0.5s ease;
}
/* The flash that marks the question you jumped to.

   The signal is the ring, not the wash. A tint of the level colour towards
   white carries almost no luminance when the level colour is already light:
   N1's gold at 10% came out at 1.08:1 against the card, which is quieter
   than the question band itself, so on every N1 paper the flash was the
   faintest thing in a question rather than the loudest. Raising the
   percentage does not fix it - gold only reaches 1.18:1 even at 22%.

   So the ring carries it. It is the level colour at real strength rather
   than a mix towards white, which means it is as visible on gold as on
   navy, and it does not depend on luminance at all. The wash stays a wash. */
.paper-question.is-target {
  background: color-mix(in srgb, var(--level-color) 14%, #fff);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--level-color) 42%, transparent);
}

.pq-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-bottom: 10px;
}

/* The question, on a tint.

   The answers already sit in bordered boxes, so before this the stem was the
   only unboxed thing in a question and the eye had nothing to catch on it
   scanning down a 45-question paper. A band gives the question a shape of
   its own and reads as an exam paper rather than a form.

   The band is on the whole head row rather than on the stem alone, so it runs
   from the booklet number to the flag instead of starting somewhere in the
   middle of the row.

   15px of horizontal padding, which is the same padding an answer box gives
   its own number. So the band's outer edges sit on the answer boxes' outer
   edges, and the booklet number inside it lands on exactly the same left edge
   as the numbered circles underneath: one line from 24 down through 1, 2, 3,
   4. Without it the pills were flush against the band's own edge, which read
   as the tint being cropped around them rather than containing them.

   1.32:1 against the card. It began at 1.12, which measured out to almost
   exactly the answer box's own border colour - and a fill the same value as
   a hairline reads weaker than the hairline, because it is spread over a
   hundred times the area. So the question had no surface, only a suggestion
   of one, and a page of them looked flat.

   Darkening it used to be blocked by .paper-question.is-target, the flash
   marking the question you jumped to, which had to stay the loudest thing on
   the page. That constraint is gone: the flash carries its signal in a ring
   of the level colour now rather than in a wash whose luminance the band
   could out-shout. Navy on the band is still 10.5:1.

   A listening stem is a label and a picture - "1ばん" and four drawings. A
   band there is a wide grey field with three characters in the corner, so
   those rows are marked has-media and left alone. */
.pq-head:not(.has-media) {
  /* 15px is the answer box's own padding and 1.5px is its border, so this
     lands the booklet number on precisely the same left edge as the numbered
     circles beneath it rather than a pixel off. */
  padding: 7px 16.5px;
  background: #d8e1ec;
  border-radius: 12px;
}

/* The booklet's own number - 23, or 3番 in listening. A circle could not hold
   two characters plus 番, so it is a pill that grows with what is in it. */
.pq-num {
  flex: none;
  min-width: 34px;
  height: 30px;
  padding: 0 11px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--navy);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.01em;
  white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums;
}

.pq-prompt {
  flex: 1;
  /* 16.8px, not 17.6. A stem is unfurigana'd kanji that the reader has to
     identify - that is the test - so this is as far down as it goes; 16px is
     the usual floor for CJK body text and there is no margin below it. Most
     of the height came out of the leading instead, which was set for prose
     at 1.9 and only had to hold one or two lines. */
  font-size: 1.05rem;
  line-height: 1.78;
  color: var(--navy);
  font-weight: 600;
  min-width: 0;
}
.pq-prompt u { text-decoration-thickness: 2px; text-underline-offset: 3px; }
.pq-prompt img { margin: 14px 0; border-radius: 8px; }
/* A listening question is a picture, and the picture is the last thing in the
   stem. Its own 14px bottom margin then stacked on .pq-head's 10px, so the
   answers sat 24px below the image while a text stem holds its answers at 18.
   The image edge is hard - no half-leading to discount - so 8 here lands it
   at the same optical 18px, and listening keeps the same grouping rhythm as
   every other section. */
.pq-prompt img:last-child { margin-bottom: 8px; }

.flag-btn {
  flex: none;
  margin-left: auto;
  /* 30, matching .pq-num, because the flag was the tallest thing in the row
     and so decided the band's height. A one-line question is a 29.9px line
     box next to a 30px number pill, and the flag's extra 4px could only go
     somewhere: with align-items:flex-start it all fell below the text, so the
     band had 13.1px above the words and 17.2px below. At 30 the row is the
     height of its own contents again and the text sits centred in it.
     Multi-line questions were never affected - the text was already the
     tallest thing there.

     The hit area does not change. .flag-btn::after below is a 44px square
     centred on the button, independent of the button's own size. */
  width: 30px;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
  color: var(--muted);
  cursor: pointer;
  transition: 0.18s ease;
}
.flag-btn:hover { border-color: var(--gold); color: var(--gold); }
/* The drawn button is 34px, which is the right size beside a 30px number
   pill but five pixels under the 44px a thumb needs. Grow the hit area, not
   the button: an overlay centred on it reaches 44 without the flag becoming
   the loudest thing in the row. The 5px it spills sits in .pq-head's 14px
   gap and in the 12px above the answers, so it takes no press meant for
   anything else - verified by hit-testing the choice boxes underneath. */
.flag-btn, .words-btn { position: relative; }
.flag-btn::after, .words-btn::after {
  content: "";
  position: absolute;
  inset: 50% 0 0;
  height: 44px;
  transform: translateY(-50%);
}
.flag-btn::after { inset-inline: 50%; width: 44px; transform: translate(-50%, -50%); }
.flag-btn.is-on {
  background: rgba(217, 166, 58, 0.16);
  border-color: var(--gold);
  color: #8a6413;
}

/* -- audio -- */

/* The listening player, pinned above the questions it belongs to.

   One recording covers a whole 問題 - six questions in sequence - and there
   is no way to cut it into six from the browser. Pinning it is the next best
   thing: it stays above whichever of those six you are looking at, so going
   back to catch 4番 again is a press, not a scroll.

   Opaque, not the old translucent gradient: questions now pass underneath it.
   It stops travelling at the end of its own 問題, because that is where its
   parent .paper-section ends. */
.q-audio {
  position: sticky;
  /* Flush against the command bar, not ten pixels below it. That slot was
     a letterbox: the questions scrolled through it, so a strip of moving
     page showed between the bar and the pinned player. Docked to the bar it
     is one continuous edge, which is also what a docked control should look
     like - square on top, rounded below. */
  top: calc(var(--header-h, 74px) + var(--bar-h, 62px) + var(--full-h, 0px));
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 18px;
  padding: 10px 16px;
  background: #eef4fb;
  border: 1px solid rgba(45, 110, 180, 0.28);
  border-top: 0;
  border-radius: 0 0 14px 14px;
  box-shadow: 0 10px 22px -12px rgba(17, 45, 78, 0.4);
}
.q-audio-label {
  flex: none;
  display: flex;
  align-items: baseline;
  gap: 7px;
  color: var(--navy);
  font-size: 0.86rem;
}
.q-audio-label b { font-weight: 700; }
.q-audio-label i {
  font-style: normal;
  font-size: 0.76rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
/* The window onto Drive's player. See buildSection() in exam-player.js: the
   iframe is 76px because that is what Drive lays out for, and it draws only
   in the top 48 of it, so this crops the empty quarter off the bottom and
   leaves the controls centred in what you see. */
.q-audio-frame {
  flex: 1;
  min-width: 0;
  height: 60px;
  overflow: hidden;
  border-radius: 10px;
  background: #fff;
}
.q-audio-frame iframe {
  display: block;
  width: 100%;
  height: 76px;
  border: 0;
}

/* Everything about the audio that is not a control: the note, and the way
   out when it will not play. Left in the section head so the pinned bar
   stays one line tall on a phone. */
.q-audio-aside { margin-top: 14px; }
.q-audio-note { margin: 0; font-size: 0.76rem; color: var(--muted); }

/* -- choices -- */

.q-choices { display: grid; gap: 10px; }
.q-choices.is-numeric {
  grid-template-columns: repeat(auto-fit, minmax(min(80px, 100%), 1fr));
}

/* Short answers go across the line, the way the printed paper sets them, and
   only full sentences get a line each. auto-fit rather than a fixed column
   count so the same rule gives four across on a desktop and two on a phone
   without a second breakpoint. See longestChoice() in exam-player.js. */
/* min() inside the minmax, not a bare pixel value: a grid track with a fixed
   minimum cannot shrink below it, so on a 320px screen the 300px medium
   track forced its whole section 18px past the viewport and scrolled the
   page sideways. min(Npx, 100%) lets the track collapse to the column when
   the column is the smaller of the two.

   146 rather than 158 on the short tier so a 390px phone still gets two
   across - which is where halving the scroll matters most - while a 320px
   one falls back to one and keeps the answers readable. */
.q-choices.is-short {
  grid-template-columns: repeat(auto-fit, minmax(min(146px, 100%), 1fr));
}
.q-choices.is-medium {
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
/* A marked paper adds a tick or a cross to the answer it belongs to. The row
   used to be allowed to wrap so that label could drop underneath, and was
   indented 38px so it would land under the answer rather than the number -
   but a margin counts towards the line, so that indent was itself what made
   the line too long. It wrapped on every screen, desktop included, and the
   marked box stood half again as tall as the three beside it.

   A glyph is a dozen pixels wide and needs none of that. When an answer is
   long enough to fill the column it now wraps within itself and the mark
   stays beside it, which keeps every box in the row the same height. */


.choice {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  width: 100%;
  /* 11px, not 13. With a 28px number and a 28px line box this holds the row
     at 50px, which is still above the 44px minimum touch target with the
     border counted. */
  padding: 11px 15px;
  text-align: left;
  background: var(--panel);
  border: 1.5px solid var(--line);
  border-radius: 14px;
  cursor: pointer;
  transition: 0.16s ease;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--text);
}
.choice:hover:not(:disabled) {
  border-color: var(--blue);
  background: var(--panel-soft);
}
.choice:disabled { cursor: default; }
.q-choices.is-numeric .choice { justify-content: center; padding: 18px 0; }

.choice-num {
  flex: none;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  font-weight: 800;
  font-size: 0.88rem;
  color: var(--muted);
  transition: 0.16s ease;
}
/* Only as wide as the words. It used to be flex: 1, which stretched the text
   box across the whole button and left "CORRECT" stranded at the far edge -
   325px from the end of the sentence on a full-width answer, and on the
   narrow four-across answers it pushed the label onto a second line and made
   the button half again as tall. It still shrinks when a long answer needs to
   wrap; it just no longer claims room it is not using. */
.choice-text { flex: 0 1 auto; }
/* Beside the answer it belongs to, not against the far wall. A tick or a
   cross now, so it takes a dozen pixels instead of seventy and fits on the
   line whatever the answer says - see the mark in exam-player.js for why it
   is a glyph and not the word. */
.choice-mark {
  flex: none;
  align-self: center;
  font-size: 1.05rem;
  font-weight: 800;
  line-height: 1;
  color: var(--muted);
}

/* Read aloud, never drawn. */
.vis-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The answer you picked, and there must be no doubt which one it is. A 1.5px
   tint was easy to lose halfway down a page of fifty identical boxes, and the
   number beside it stayed grey, so nothing about a chosen row looked chosen.
   The ring is an inset shadow rather than a thicker border, so the box does
   not shift by half a pixel when you press it. */
.choice.is-chosen {
  border-color: var(--blue);
  background: rgba(45, 110, 180, 0.11);
  box-shadow: inset 0 0 0 1.5px var(--blue);
}
.choice.is-chosen .choice-num {
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
}
.choice.is-chosen .choice-num { background: var(--blue); border-color: var(--blue); color: #fff; }
.choice.is-correct { border-color: var(--success); background: rgba(47, 125, 87, 0.1); }
.choice.is-correct .choice-num { background: var(--success); border-color: var(--success); color: #fff; }
.choice.is-correct .choice-mark { color: var(--success); }
.choice.is-wrong { border-color: var(--danger); background: rgba(200, 74, 82, 0.09); }
.choice.is-wrong .choice-num { background: var(--danger); border-color: var(--danger); color: #fff; }
.choice.is-wrong .choice-mark { color: var(--danger); }

.q-blank-note { margin: 12px 0 0; font-size: 0.8rem; color: var(--muted); }

/* -- feedback -- */

.q-feedback:empty { display: none; }
.q-feedback {
  margin-top: 16px;
  padding: 16px 18px;
  border-radius: 14px;
}
.q-feedback strong { display: block; margin-bottom: 6px; font-size: 0.98rem; }
.q-feedback p { margin: 0; font-size: 0.9rem; }
.q-feedback.is-right {
  background: rgba(47, 125, 87, 0.1);
  border: 1px solid rgba(47, 125, 87, 0.3);
  color: #1d5d3e;
}
.q-feedback.is-wrong {
  background: rgba(200, 74, 82, 0.09);
  border: 1px solid rgba(200, 74, 82, 0.28);
  color: #93353c;
}
.q-feedback.is-neutral {
  background: var(--panel-soft);
  border: 1px solid var(--line);
  color: var(--muted);
}

.q-explain {
  margin-top: 12px;
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 12px;
  color: var(--text);
  font-size: 0.93rem;
  line-height: 1.9;
}
.q-explain-label {
  display: block;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

/* -- marked-up state after submitting -- */

.is-reviewed .paper-question {
  padding-left: 16px;
  border-left: 4px solid var(--line);
}
.is-reviewed .paper-question.is-right { border-left-color: var(--success); }
.is-reviewed .paper-question.is-wrong { border-left-color: var(--danger); }
.is-reviewed .paper-question.is-blank { border-left-color: var(--gold); }

/* -- end of paper -- */

.paper-finish {
  padding: 32px;
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.paper-finish h2 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 1.5rem;
  color: var(--navy);
}
/* :not() so the flagged notice keeps its own auto margins - this rule is
   one specificity point heavier and was silently resetting them to 0, which
   left the notice pinned to the left edge of a centred block. */
.paper-finish p:not(.finish-flagged) { margin: 0 0 20px; color: var(--muted); }

/* ------------------------------------------------- one section's result ---
   Shown when a single paper has been marked. Deliberately lighter than the
   full scorecard: no verdict badge, no pass mark, no total out of 180 -
   those need every section, and dressing one section up to look like a
   finished result would be a lie in a large typeface.
   -------------------------------------------------------------------------- */

.section-card {
  padding: var(--card-pad);
  margin-bottom: 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 4px solid var(--level-color, var(--blue));
  border-radius: 18px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.06);
}

.section-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.section-card-head h2 {
  margin: 0;
  font-size: 1.15rem;
  color: var(--navy);
}
.section-card-raw {
  margin: 4px 0 0;
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

.section-card-score { text-align: right; }
.section-card-score strong {
  display: block;
  font-family: var(--font-display);
  font-size: 2.2rem;
  line-height: 1;
  color: var(--level-color, var(--navy));
  font-variant-numeric: lining-nums tabular-nums;
}
/* The denominator is context, not the number - half the size and grey, so
   the eye lands on the score. */
.section-card-score strong i {
  font-style: normal;
  font-size: 0.5em;
  color: var(--muted);
  margin-left: 1px;
}
.section-card-score span {
  display: block;
  font-size: 0.72rem;
  color: var(--muted);
}

/* Cleared the sectional minimum, or not. Stated plainly rather than implied
   by a colour, because this single number can fail an otherwise good paper. */
.section-card-min {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 14px 0 0;
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
}
.section-card-min b { font-size: 0.9rem; line-height: 1; }
.section-card-min.is-ok {
  background: #eaf6ef;
  color: var(--success);
}
.section-card-min.is-below {
  background: #fdeeef;
  color: var(--danger);
}

.section-card-skills {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  margin-top: 16px;
}
.section-card-skill {
  padding: 10px 12px;
  background: var(--panel-soft);
  border-left: 3px solid var(--accent, var(--blue));
  border-radius: 0 10px 10px 0;
}
.section-card-skill span {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.section-card-skill b {
  font-size: 1.2rem;
  color: var(--accent, var(--navy));
  font-variant-numeric: lining-nums tabular-nums;
}
.section-card-skill i {
  font-style: normal;
  margin-left: 6px;
  font-size: 0.76rem;
  color: var(--muted);
}

.section-card-note {
  margin: 14px 0 0;
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--muted);
}

.section-card .btn { margin-top: 14px; }

/* The question map that used to live here - a sticky grid of 101 numbered
   cells beside the paper - was removed rather than repaired. Below 960px it
   dropped to the bottom of the page, so on a phone it was a hundred shortcuts
   you only reached after scrolling past everything they pointed at; and it
   printed a second set of numbers next to a paper that now shows its own.
   "How much is left?" is answered by the progress bar in the command bar. */

.bar-section {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--level-color);
  white-space: nowrap;
}

/* ------------------------------------------------------------- scorecard */

.scorecard { display: grid; gap: 16px; }

.result-flash {
  padding: 14px 20px;
  border-radius: 14px;
  background: rgba(217, 166, 58, 0.16);
  border: 1px solid var(--gold);
  color: #7a5810;
  font-weight: 600;
}

.result-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 30px;
  padding: 32px 36px;
  border-radius: var(--radius);
  color: #fff;
  box-shadow: var(--shadow);
  flex-wrap: wrap;
}
.result-head.is-pass { background: linear-gradient(135deg, #1d5d3e, #2f7d57); }
.result-head.is-fail { background: linear-gradient(135deg, #123, #3a4d66); }
.result-badge {
  display: inline-block;
  padding: 5px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.2);
  font-weight: 800;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  margin-bottom: 12px;
}
.result-verdict h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.6vw, 2.3rem);
  line-height: 1.2;
}
.result-verdict p { margin: 8px 0 0; opacity: 0.86; }
.result-score { text-align: right; flex: none; }
.result-score-num {
  font-size: 3.2rem;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: lining-nums tabular-nums;
}
.result-score-num span {
  font-size: 1.05rem;
  font-weight: 600;
  opacity: 0.7;
  margin-left: 6px;
}
.result-score-label { font-size: 0.8rem; opacity: 0.8; margin-top: 6px; }
.result-score-pass { font-size: 0.8rem; opacity: 0.7; margin-top: 3px; }

.result-warn, .result-note {
  margin: 0;
  padding: 14px 18px;
  border-radius: 14px;
  font-size: 0.88rem;
  line-height: 1.6;
}
.result-warn {
  background: rgba(200, 74, 82, 0.1);
  border: 1px solid rgba(200, 74, 82, 0.3);
  color: #93353c;
}
.result-note {
  background: var(--panel-soft);
  border: 1px solid var(--line);
  color: var(--muted);
}

.result-sections {
  padding: 26px 30px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.result-sections h2 { margin: 0 0 20px; font-size: 1.1rem; color: var(--navy); }
.section-rows { display: grid; gap: 16px; }
.section-row {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr) 92px;
  gap: 16px;
  align-items: center;
}
.section-name {
  font-weight: 700;
  color: var(--navy);
  font-size: 0.94rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.section-name span { font-weight: 500; font-size: 0.78rem; color: var(--muted); }
.section-bar {
  position: relative;
  height: 14px;
  border-radius: 999px;
  background: var(--line);
}
.section-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--blue), var(--success));
}
.section-row.is-below .section-bar-fill {
  background: linear-gradient(90deg, #d98a8f, var(--danger));
}
.section-bar-min {
  position: absolute;
  top: -4px;
  width: 2px;
  height: 22px;
  background: var(--navy);
  opacity: 0.55;
}
.section-val {
  text-align: right;
  font-weight: 800;
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}
.section-val span { font-weight: 500; font-size: 0.8rem; color: var(--muted); }
.section-legend { margin: 20px 0 0; font-size: 0.8rem; color: var(--muted); }

/* filter + actions strip above the marked paper */
.review-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 16px 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.06);
}
.review-filters { display: flex; gap: 8px; flex-wrap: wrap; }
.review-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.review-actions .btn { padding: 10px 18px; font-size: 0.85rem; }

.chip {
  padding: 9px 18px;
  border: 1px solid #dbe4f0;
  border-radius: 999px;
  background: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--muted);
  box-shadow: 0 1px 3px rgba(17, 45, 78, 0.06);
}
.chip:hover {
  border-color: var(--blue);
  color: var(--blue);
  background: #f7fafd;
}
/* The selected chip is a filled navy pill with its own shadow, so which
   level you are looking at is legible from across the room rather than being
   a one-pixel border change. */
.chip.is-on {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
  box-shadow: 0 4px 12px -2px rgba(17, 45, 78, 0.4);
}
.chip.is-on:hover { background: var(--navy-2); color: #fff; }

/* ------------------------------------------------------- exam browser */

/* Copy on the left at a readable measure, the counts on the right. As one
   column the paragraph stopped at 62ch and left the right-hand half of a
   1120px card empty, with four counts crowded along the bottom-left. */
.exams-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  align-items: center;
  gap: clamp(24px, 3.4vw, 52px);
  padding: clamp(24px, 3vw, 40px) clamp(22px, 3vw, 42px);
  border-radius: var(--radius);
  background: linear-gradient(135deg, var(--navy), var(--blue));
  color: #fff;
  box-shadow: var(--shadow);
}
.exams-hero h1 { margin: 0 0 10px; font-family: var(--font-display); font-size: clamp(1.9rem, 4vw, 2.8rem); }
.exams-hero p { margin: 0; opacity: 0.86; line-height: 1.7; }

.exams-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px 24px;
  padding-left: clamp(0px, 2vw, 32px);
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}
.exams-stat strong {
  display: block;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: lining-nums tabular-nums;
}
.exams-stat span {
  font-size: 0.75rem;
  opacity: 0.78;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.35;
  display: block;
  margin-top: 6px;
}

@media (max-width: 900px) {
  .exams-hero { grid-template-columns: minmax(0, 1fr); }
  .exams-stats {
    padding-left: 0;
    border-left: 0;
    padding-top: 22px;
    border-top: 1px solid rgba(255, 255, 255, 0.16);
  }
}

/* The level chips and the search share one row. The chips are hidden until a
   level is open; the search never is, so it used to sit on a line of its own
   underneath, costing a row of vertical space to hold one field. */
/* The way back to the level picker: its own line, above the switcher. */
.exams-back { margin-top: clamp(18px, 2vw, 26px); }
.exams-back[hidden] { display: none; }

.exams-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 14px 0 20px;
}
.exams-search[hidden] { display: none; }
.exams-filters {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.exams-filters[hidden] { display: none; }
.exams-filters .chip.is-on { background: var(--level-color); border-color: var(--level-color); }
.exams-search {
  margin-left: auto;
  padding: 11px 18px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  /* The placeholder spells out an example year, so give it room to finish. */
  width: clamp(240px, 22vw, 320px);
}

.exam-group-title {
  margin: 30px 0 14px;
  font-size: 1.05rem;
  color: var(--navy);
  display: flex;
  align-items: center;
  gap: 12px;
}
/* The count beside the level, not the level chip itself - which this used to
   grey out, printing white-on-green as muted grey on green. */
.exam-group-title > span:not(.exam-level-chip) {
  font-size: 0.8rem; font-weight: 500; color: var(--muted);
}

/* ------------------------------------------------------- the paper list

   A list of dates, not a wall of cards. Twenty-eight sittings of one level
   differ by their date and almost nothing else, so the date leads each row,
   the identical booklet make-up is one quiet line, and the columns that do
   vary - anything missing, and your own best - line up down the page where
   they can be compared at a glance. */
.exam-rows {
  display: grid;
  gap: 8px;
  margin-bottom: 8px;
}
.exam-row {
  --level-color: var(--blue);
  display: grid;
  /* Date sized to its own content, everything the paper is made of taking
     the rest, then the three fixed columns. Giving the make-up a fraction
     instead left it wrapping to two lines beside an acre of empty gap.

     Each row is its own grid, so the date column is a fixed width rather
     than sized to its content: with auto, "December 2018" and "July 2019"
     produced different column widths and nothing lined up down the page. */
  grid-template-columns: 190px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: clamp(10px, 1.4vw, 22px);
  padding: 12px clamp(14px, 1.6vw, 20px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  transition: 0.18s ease;
}
.exam-row:hover {
  border-color: var(--level-color);
  box-shadow: 0 8px 20px -10px rgba(17, 45, 78, 0.35);
}
/* A paper you have already sat carries a hairline in the level colour, so a
   run down the list shows where you have been without reading a word. */
.exam-row.is-sat { box-shadow: inset 3px 0 0 0 var(--level-color); }

.exam-row-when { display: flex; align-items: center; gap: 10px; min-width: 0; }
.exam-row-title {
  font-weight: 700;
  color: var(--navy);
  font-size: 1rem;
  font-variant-numeric: lining-nums;
  white-space: nowrap;
}

.exam-row-makeup {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  /* Wide enough that the three scored sections read as three things. They
     used to be separated by a middot at nine pixels, which ran them into one
     stream you had to parse rather than scan.

     10px between the two rows rather than the original 4: the break below is
     deliberate now, and at 4 the second line read as one that had wrapped by
     accident. 7 fixed that and was still tight - this is 16px of optical gap
     against 13. It can exceed the 8px between the cards themselves without
     the rows running together, because each row is a bordered card and the
     border, not the space, is what separates one from the next. */
  gap: 10px 22px;
  min-width: 0;
  font-size: 0.78rem;
  color: var(--muted);
}
.exam-row-parts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 22px;
  min-width: 0;
}
.exam-row-parts > span { white-space: nowrap; }
/* flex-basis:100% takes a line of its own, which is what makes every row the
   same two-line shape in all twelve languages instead of wrapping only when
   it ran out of room. A separate zero-height break element did the same job
   but sat on a flex line of its own, so the cell paid the row gap twice.

   It keeps its wrap. Pinning it to one line with nowrap did stop a ragged
   third line, but three Filipino rows at 1100px then ran past the cell and
   the end of the list was simply cut off - and losing "No Listening" off the
   right edge is worse than an extra line. With a whole line to itself the
   wrap almost never fires now; when it does, everything is still readable. */
.exam-row-parts { flex-basis: 100%; }
.exam-row-parts b {
  color: var(--ink-soft);
  font-variant-numeric: lining-nums tabular-nums;
}
.exam-row-count {
  font-weight: 700;
  color: var(--ink-soft);
  font-variant-numeric: lining-nums tabular-nums;
}
.exam-row-origin {
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.exam-row-origin.is-practice { color: var(--blue); opacity: 1; }

.exam-part-tag {
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

/* What a paper is missing. Written like the rest of the line it sits in -
   same size, same weight, no box - and simply red. As a bordered pill it
   read as a control, and a control is the one thing an absence is not. */
.exam-row-missing {
  color: var(--danger);
  font-weight: 600;
  white-space: nowrap;
}

/* Replaces the red "Audio 1/5" on a paper whose listening can be heard end
   to end after all. Green, not red and not grey: this row is answering "can
   I sit this?", and the answer is yes. */
.exam-row-full {
  color: var(--success);
  font-weight: 600;
  white-space: nowrap;
}

.exam-row-best {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 74px;
  justify-content: flex-end;
  color: var(--gold);
  font-size: 0.86rem;
}
.exam-row-best b {
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}
.exam-row-best b i {
  font-style: normal;
  font-weight: 600;
  font-size: 0.76rem;
  color: var(--muted);
}
/* Only hold the column open when some row in view actually has a score in
   it; otherwise it is 74px of nothing on every row. */
.exam-row-best.is-empty { min-width: 0; }
.exam-rows.has-bests .exam-row-best.is-empty { min-width: 74px; }

.exam-row-go { display: flex; gap: 8px; justify-self: end; }
.exam-row-go .btn { padding: 9px 18px; font-size: 0.82rem; }

@media (max-width: 1040px) {
  .exam-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 10px;
  }
  .exam-row-when { grid-column: 1; }
  .exam-row-best { grid-column: 2; grid-row: 1; }
  .exam-row-makeup { grid-column: 1 / -1; grid-row: 2; }
  .exam-row-go { grid-column: 1 / -1; justify-self: stretch; }
  .exam-row-go .btn { flex: 1; justify-content: center; }
}

.exams-empty { padding: 50px 0; text-align: center; color: var(--muted); }

/* On a phone the label and the player cannot share a line: the Drive player
   was squeezed to a third of the width and its controls fell off. Stack, and
   keep the whole pinned bar under a seventh of the viewport. */
@media (max-width: 620px) {
  .q-audio {
    display: block;
    padding: 8px 12px 10px;
  }
  .q-audio-label { margin-bottom: 4px; font-size: 0.78rem; }
  .q-audio-frame { display: block; width: 100%; }
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 960px) {
  /* Not enough width to keep a passage beside its questions: stack them and
     let the passage stay pinned at a readable height while scrolling. */
  .paper-block.has-passage { grid-template-columns: minmax(0, 1fr); gap: 20px; }
  .q-passage { max-height: 46vh; top: calc(var(--header-h, 74px) + var(--bar-h, 62px) + 12px); }
}

@media (max-width: 860px) {
  /* On a phone the timer and Submit matter more than the site nav, so let the
     header scroll away and give the command bar the top of the screen. */
  .exam-page .site-header { position: static; }
  .exam-page .exam-bar { top: 0; }
  .exam-page .paper-question { scroll-margin-top: calc(var(--bar-h, 62px) + 14px); }
  /* The site header scrolls away here, so the pinned player must not leave a
     header's worth of gap above itself. */
  .exam-page .q-audio { top: calc(var(--bar-h, 62px) + var(--full-h, 0px)); }
  .exam-page .q-passage {
    top: calc(var(--bar-h, 62px) + 12px);
    max-height: 42vh;
  }

  .exam-bar-inner { flex-wrap: wrap; gap: 12px; }
  .exam-bar-progress { order: 3; width: 100%; flex-basis: 100%; }
  .bar-section { display: none; }

  .section-head { padding: 18px 18px; }
  .paper-block { padding: 20px 18px; }
  .exam-hero, .exams-hero { padding: 26px 22px; }
  .exam-setup, .result-sections { padding: 22px 18px; }
  .paper-finish { padding: 26px 20px; }
  .result-head { padding: 26px 22px; flex-direction: column; align-items: flex-start; }
  .result-score { text-align: left; }
  .section-row { grid-template-columns: minmax(0, 1fr) 82px; }
  .section-row .section-bar { grid-column: 1 / -1; order: 3; }
  .review-bar { flex-direction: column; align-items: stretch; }
  .exams-search { margin-left: 0; width: 100%; }
}

@media (max-width: 560px) {
  .setup-actions .btn, .paper-finish .btn { width: 100%; }
  .pq-head { flex-wrap: wrap; }
  .is-reviewed .paper-question { padding-left: 12px; }
}

/* A sitting whose listening paper was never archived says so on its card. */
.exam-part-tag.is-missing {
  background: rgba(200, 74, 82, 0.08);
  border-color: rgba(200, 74, 82, 0.3);
  color: #93353c;
}

/* ============================================================ word meanings
   The glossary for one question or one reading passage: the words a learner
   at this level would not already know, with furigana over the kanji and a
   short English meaning. Collapsed until asked for, so a paper still reads
   like a paper. */

.q-words { margin-top: 12px; }

.words-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px 6px 8px;
  font: inherit; font-size: .82rem; font-weight: 600;
  color: var(--ink-soft); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
.words-btn:hover { color: var(--ink); border-color: var(--level-color, #2d6eb4); }
.words-icon {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  font-size: .72rem; line-height: 1;
  color: #fff; background: var(--level-color, #2d6eb4);
}
.words-n {
  padding: 1px 7px; border-radius: 999px;
  font-size: .74rem; font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink-soft); background: var(--panel);
  border: 1px solid var(--line);
}
.q-words.is-open .words-btn {
  color: var(--ink); border-color: var(--level-color, #2d6eb4);
}

.wordbox { display: none; margin-top: 12px; }
.q-words.is-open .wordbox { display: block; }

.wordbox-head {
  margin: 0 0 8px; font-size: .78rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft);
}

.wordlist {
  margin: 0; padding: 0; list-style: none;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2); overflow: hidden;
}

/* word | level + part of speech | meaning */
.gw {
  display: grid; gap: 4px 14px; align-items: baseline;
  grid-template-columns: minmax(88px, max-content) max-content 1fr;
  padding: 9px 14px;
  border-top: 1px solid var(--line);
  font-size: .9rem;
}
.gw:first-child { border-top: 0; }

.gw-w { font-weight: 600; color: var(--ink); font-size: 1.02rem; }
/* Ruby needs room above the line or the furigana clips into the row before. */
.gw-w ruby { ruby-position: over; }
.gw-w rt { font-size: .55em; font-weight: 500; color: var(--ink-soft); }

.gw-meta { display: inline-flex; gap: 6px; align-items: baseline; }
.gw-lv {
  padding: 1px 6px; border-radius: 4px;
  font-size: .68rem; font-weight: 700; letter-spacing: .03em;
  color: #fff; background: var(--level-color, #2d6eb4);
}
.gw-p { font-size: .72rem; font-style: italic; color: var(--ink-soft); }
.gw-g { color: var(--ink-soft); }

/* The bar button opens every panel on the paper at once. */
.words-all { white-space: nowrap; }
.words-all.is-on {
  color: #fff; background: var(--level-color, #2d6eb4);
  border-color: var(--level-color, #2d6eb4);
}

@media (max-width: 760px) {
  .gw {
    grid-template-columns: 1fr;
    gap: 2px;
  }
  .gw-meta { order: 3; }
}

/* The command bar already drops its progress meter onto its own row on small
   screens. With the word-meanings button added to the tools group, that group
   has to be able to wrap as well, or it pushes the page sideways.

   From 1024px, not 860. Between those two the group had run out of room
   without being allowed to do anything about it, so "Show word meanings"
   was drawn over the section label beside it. Wrapping is a last resort here
   and mostly does not happen: the smaller button usually buys back enough
   for one row. */
@media (max-width: 1024px) {
  .exam-bar-tools {
    flex-wrap: wrap; justify-content: flex-end;
    gap: 8px; min-width: 0;
  }
  .words-all { padding: 6px 10px; font-size: .78rem; }
}

/* Below 900 the tools were taking two rows of their own while the progress
   meter still sat beside them, which cost more height than simply giving the
   meter its own row - the arrangement the phone layout already uses. 899
   rather than 900 so a 900px window keeps its single row. */
@media (max-width: 899px) {
  .exam-bar-inner { flex-wrap: wrap; gap: 12px; }
  .exam-bar-progress { order: 3; width: 100%; flex-basis: 100%; }
}

/* On a reading passage the button sits above the text, so cap the open list
   and let it scroll rather than pushing the passage off the screen. */
.q-passage .q-words { margin: 10px 0 4px; }
.q-passage .wordbox { max-height: 40vh; overflow-y: auto; }

.words-all-short { display: none; }
@media (max-width: 860px) {
  /* 語 alone keeps the tools group on one row. */
  .words-all-full { display: none; }
  .words-all-short { display: inline; font-size: 1rem; line-height: 1; }
  .words-all { padding: 7px 12px; }
}

.exam-part-tag.is-words {
  color: #1f6b45; background: #e6f4ec; border-color: #bfe0cd;
}

/* The one recording the archive lost upstream: named, with the way on. */
.q-audio-aside.is-failed .q-audio-note {
  color: #8a4b1e; background: #fdf3e7;
  border: 1px solid #f0d8b8; border-radius: 8px; padding: 10px 12px;
}
.q-audio-aside.is-failed .q-audio-note a { color: #8a4b1e; font-weight: 600; }

/* "Not playing?" - the way out, offered before a failure we cannot detect:
   a cross-origin iframe fires no error event. */
.q-audio-help { margin-top: 6px; font-size: .8rem; }
.q-audio-help summary {
  cursor: pointer; color: var(--muted); font-weight: 600;
  width: fit-content;
}
.q-audio-help summary:hover { color: var(--ink); }
.q-audio-help p { margin: 8px 0 0; color: var(--ink-soft); line-height: 1.55; }
.q-audio-help a { color: var(--blue, #2d6eb4); font-weight: 600; }

/* ==========================================================================
   Laptop height

   Sitting an exam is the one screen a user stares at for two hours, and on a
   14" laptop the sticky bar plus the section head plus the paper padding left
   very little of the actual question visible. Trim the chrome, not the type:
   the question text and its options keep their size.
   ========================================================================== */

@media (min-width: 1024px) and (max-height: 900px) {
  .exam-root, .exams-root { padding-bottom: 48px; }
  .exam-root > .container,
  .exams-root > .container { padding-top: 18px; }
  .exam-root > .container.exam-body { padding-top: 18px; }

  .exam-body { padding-top: 18px; }
  .paper-block { padding: 18px 22px; }
  .exams-hero { padding: 22px 26px; }
  .exams-filters { margin: 18px 0 16px; }
  .exam-group-title { margin: 22px 0 12px; }
}

/* On a wide laptop or desktop the paper column is over 1000px, which turned
   every answer into a 1000px-wide button for three characters of kana and put
   the stem and its options on very different scales. Cap the measure instead:
   long grammar and reading options still wrap at a comfortable line, short
   ones stop being a hallway. The passage layout is already narrower than this,
   so it is unaffected. */
.paper-question { max-width: min(100%, 78ch); }


/* One column, centred, for everything that is not a reading block.

   The card spans the container so it lines up with the command bar above it,
   but its contents stop at a reading measure - which left a third of the
   card empty on the right of every question and read as a layout that had
   failed rather than one that had chosen. Centred, the same whitespace
   becomes a margin on both sides, and the heading, the instruction and the
   questions still share one left edge.

   Reading blocks are exempt: the passage and its questions need the width. */
.paper-section:not(.has-passage) .section-head > *,
.paper-section:not(.has-passage) > .q-audio,
.exam-paper > .q-audio-full-bar,
.paper-block:not(.has-passage) .block-questions {
  max-width: 78ch;
  margin-inline: auto;
}

/* ==========================================================================
   Phones: keep the sticky chrome small

   Sitting a paper on a phone, the site header and the exam bar between them
   were taking about a third of the screen before the first question. The bar
   keeps everything that is needed mid-exam - a way out, the level, the
   glossary, the clock, submit - and drops what is not.
   ========================================================================== */

@media (max-width: 860px) {
  /* The arrow alone is understood here, and the label is still on the button
     for a screen reader through aria-label. */
  .exam-back-text { display: none; }
  .exam-back { padding: 7px 11px; }

}

/* Which sitting of the exam this is does not change while you answer it, and
   the bar runs out of room for it well before the phone breakpoint. */
@media (max-width: 1200px) {
  .exam-bar-period { display: none; }
  /* The paper's name is on the selected tab a few pixels below. The 問題
     number is not anywhere else once the section head has scrolled off, so
     that is the half that stays. */
  .bar-section-cat { display: none; }
}

/* The identity and the tools were 7px wider than a 390px phone, so they wrapped
   onto separate lines and the bar cost three rows instead of two. Take the
   padding down until they share one. */
@media (max-width: 560px) {
  .exam-bar-inner { gap: 10px; }
  .exam-bar-tools { gap: 8px; }
  .exam-bar-id { gap: 8px; }

  .exam-timer { padding: 7px 11px; font-size: 0.85rem; }
  .exam-timer strong { font-size: 0.95rem; }
  .exam-bar-tools .btn { padding: 9px 14px; font-size: 0.85rem; }
  .exam-bar-tools .words-all { padding: 9px 11px; }
}

/* ==========================================================================
   Paper switcher

   A sitting is three papers - 言語知識, 読解, 聴解 - and they are the same
   three blocks the result is scored in. Showing all of them in one column
   meant scrolling past forty vocabulary questions to reach the reading, so
   they get a tab each. The clock and the submit button stay shared: it is
   still one sitting, not three.
   ========================================================================== */

.paper-tabs {
  display: flex;
  gap: 8px;
  padding: 6px;
  /* Sized to its tabs rather than to the column: stretched across a desktop
     paper, two tabs became two 550px slabs with a word floating in each. */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
}

.paper-tab {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 11px 14px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  /* Readable rather than greyed out. At --muted, next to a white pill, the
     two sections you were not on read as unavailable rather than as the
     other two papers of the sitting. */
  color: var(--ink-soft);
  font-weight: 700;
  font-size: 0.92rem;
  cursor: pointer;
  transition: 0.16s ease;
}

.paper-tab:hover { background: rgba(255, 255, 255, 0.85); color: var(--navy); }

.paper-tab.is-on {
  background: #fff;
  border-color: var(--line);
  color: var(--navy);
  box-shadow: 0 4px 12px rgba(17, 45, 78, 0.08);
}

.paper-tab-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.paper-tab-count {
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

/* The count is coloured by progress, not by level.

   It used to take the level's own colour, which at N3 is green - so a tab
   reading "0 / 58" was printed in the colour of a finished thing before a
   single question had been answered. The level is already named in the chip
   two lines above; what this number can usefully say is how far in you are.

   Grey at nothing, navy once started, green with a tick when the section is
   fully answered. */
.paper-tab.is-started .paper-tab-count { color: var(--navy); }
.paper-tab.is-done .paper-tab-count { color: var(--success); }
.paper-tab-count b { font-weight: 800; }

/* On a phone the three names do not fit side by side as one word each, so the
   pill becomes a row of stacked labels that still reads at a glance. */
@media (max-width: 560px) {
  /* Fill the width here - the names are what is left to tap. */
  .paper-tabs { gap: 4px; padding: 4px; width: 100%; }
  .paper-tab { flex: 1 1 0; padding: 9px 5px; font-size: 0.76rem; }
  .paper-tab-count { font-size: 0.66rem; }

  /* Let the name wrap instead of clipping it. "Language K..." told the reader
     nothing; two short lines tell them which paper they are on. */
  .paper-tab-name {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.2;
    hyphens: auto;
  }
}

/* ==========================================================================
   Accuracy by skill

   Sits under the official three-part result, not instead of it. Vocabulary
   and grammar are marked together inside 言語知識, so a good half can carry a
   bad one all the way to a pass; these rows are where that shows.
   ========================================================================== */

.result-skills {
  margin-top: 18px;
  padding: clamp(18px, 1.8vw, 26px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.result-skills h2 { margin: 0 0 18px; font-size: 1.1rem; color: var(--navy); }

.skill-rows { display: grid; gap: 14px; }

.skill-row {
  display: grid;
  grid-template-columns: 170px minmax(0, 1fr) 86px;
  gap: 16px;
  align-items: center;
}

.skill-name { font-weight: 700; color: var(--navy); font-size: 0.94rem; }

.skill-bar {
  height: 10px;
  border-radius: 999px;
  background: var(--panel-soft);
  overflow: hidden;
}
.skill-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, var(--blue));
  transition: width 0.4s ease;
}

.skill-val { text-align: right; }
.skill-val strong {
  display: block;
  font-size: 1.1rem;
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
  line-height: 1.1;
}
.skill-val span { font-size: 0.76rem; color: var(--muted); }

@media (max-width: 860px) {
  .skill-row { grid-template-columns: minmax(0, 1fr) 76px; }
  .skill-bar { grid-column: 1 / -1; order: 3; }
}

/* ==========================================================================
   Level picker (exam library front page)

   The library opens on five levels rather than eighty-six papers. One card
   per level, carrying the two numbers that decide whether it is the right
   level to open: how many papers, and how many questions in them.
   ========================================================================== */

/* Five levels, five columns, one row. auto-fit with a 215px minimum meant
   the fifth card dropped to a line of its own the moment the container went
   under 1,100px, and a lone N5 sitting below four siblings reads as if it
   were a different kind of thing. They are one scale, so they line up. */
.level-picker {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(10px, 1.1vw, 16px);
  margin-top: 6px;
}

/* A column layout, not a row: five of these sit side by side, and a row
   layout squeezed the description into three words per line. */
.lvcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: clamp(15px, 1.4vw, 20px);
  text-align: left;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 4px solid var(--level-color, var(--blue));
  border-radius: 18px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.05);
  overflow: hidden;
}

/* The level's own colour washes up from the bottom on hover, so the card the
   pointer is on is unmistakable among five near-identical ones. */
.lvcard::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 45%,
              color-mix(in srgb, var(--level-color, #2d6eb4) 12%, transparent));
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.lvcard:hover {
  transform: translateY(-3px);
  border-color: var(--level-color, var(--blue));
  border-top-color: var(--level-color, var(--blue));
  box-shadow: 0 18px 34px -10px rgba(17, 45, 78, 0.28);
}
.lvcard:hover::before { opacity: 1; }

.lvcard-code {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 2.2vw, 2.5rem);
  font-weight: 700;
  line-height: 1;
  color: var(--level-color, var(--navy));
}

.lvcard-desc {
  color: var(--navy);
  font-weight: 600;
  font-size: 0.88rem;
  line-height: 1.4;
}

/* Plain inline text, not flex: as flex items the numbers and their words
   became separate boxes and the space between them collapsed away. */
.lvcard-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 14px;
  margin-top: 2px;
  margin-bottom: 4px;
  color: var(--muted);
  font-size: 0.78rem;
}
/* Each fact is one unbreakable phrase, so a line break can only fall between
   them - never between "2,760" and "questions", which is where it used to. */
.lvcard-facts > span { white-space: nowrap; }
.lvcard-facts b { color: var(--navy); font-variant-numeric: lining-nums tabular-nums; }

/* The way in. It was a bare arrow glyph in the corner - the one part of the
   card that actually does something, drawn as the faintest thing on it. Now
   a filled pill in the level's colour that widens under the pointer: it
   looks pressable standing still, and it answers the press. */
/* The way in. It was a bare arrow glyph in the corner - the one part of the
   card that actually does something, drawn as the faintest thing on it, and
   saying only "something happens" rather than what. Now a full-width filled
   bar in the level's colour that names the action. The arrow steps right
   under the pointer; the bar answers the press. */
.lvcard-go {
  /* Five descriptions of two to four lines each left five buttons sitting at
     five different heights across the row. */
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: auto;
  padding: 11px 14px;
  border-radius: 12px;
  background: var(--level-color, var(--blue));
  color: #fff;
  font-weight: 700;
  font-size: 0.85rem;
  line-height: 1.2;
  text-align: center;
  box-shadow: 0 5px 14px -4px
              color-mix(in srgb, var(--level-color, #2d6eb4) 70%, transparent);
  transition: box-shadow 0.2s ease, transform 0.2s ease, filter 0.2s ease;
}
.lvcard-go i {
  font-style: normal;
  transition: transform 0.2s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.lvcard:hover .lvcard-go {
  filter: brightness(1.08);
  box-shadow: 0 10px 22px -5px
              color-mix(in srgb, var(--level-color, #2d6eb4) 80%, transparent);
}
.lvcard:hover .lvcard-go i { transform: translateX(4px); }
.lvcard:active .lvcard-go { transform: scale(0.97); }

@media (prefers-reduced-motion: reduce) {
  .lvcard-go, .lvcard-go i { transition: none; }
  .lvcard:hover .lvcard-go i { transform: none; }
}

/* Five across needs about 900px of container. Below that the cards get
   narrower than their own descriptions, so step down rather than squeeze:
   three, then two, then one. Never four - a 4+1 split puts N5 alone on a
   line, which is the thing this layout exists to avoid. */
@media (max-width: 1080px) {
  .level-picker { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .level-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 460px) {
  /* One card per row, still stacked. A horizontal card here squeezed the
     action bar to 70px, which is not enough for its label in any language -
     and the label is the whole point of it. */
  .level-picker { grid-template-columns: minmax(0, 1fr); }
  .lvcard { padding: 16px 18px; }
  .lvcard-code { font-size: 1.8rem; }
}

/* The way back out of a level, sitting first in the filter row. */
/* .exams-filters sets display:flex, which outranks the browser's own rule for
   the hidden attribute, so the row stayed on screen while empty. */
.exams-filters[hidden] { display: none; }

/* .chip-back moved to styles.css: the levels, study and stats pages use it
   for their back link too, and they never load this file. */


@media (max-width: 640px) {
  .exams-search { margin-left: 0; width: 100%; }
}

/* The back link above the setup hero. Its label always shows here - there is
   room, unlike the sticky bar on a phone. */
.exam-setup-back { margin-bottom: 14px; }
.exam-setup-back .exam-back-text { display: inline; }
@media (max-width: 860px) {
  .exam-setup-back .exam-back span:not(.exam-back-arrow) { display: inline; }
}

/* ====================================================== study mode: try again

   The undo for a question study mode has locked. Deliberately quiet and
   deliberately inside the feedback box: it belongs to that one question, and
   it should not compete with the answer it is sitting under. */
.q-again {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 12px;
  padding: 7px 15px;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  opacity: 0.78;
  cursor: pointer;
  transition: 0.18s cubic-bezier(.34, 1.56, .64, 1);
}
.q-again:hover { opacity: 1; transform: translateY(-1px); }
.q-again:active { transform: scale(0.96); }
.q-again span { font-size: 0.95rem; line-height: 1; }
.q-again:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* ============================================================ setup screen

   "Whole paper" beside the heading, because nothing is ticked on arrival any
   more and one press is the right cost for the one choice that used to be
   made for you. */
.setup-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
/* On a phone the button wraps under the title and sat on the hint below
   with no gap at all: the hint's margin is below it, not above. */
@media (max-width: 520px) {
  .setup-title-row { row-gap: 8px; margin-bottom: 10px; }
}
/* Select all / Clear all.

   This was a bare outline pill with small grey text, which read as a status
   chip rather than something to press - so the shortcut sat unnoticed beside
   the boxes it was there to fill in. It is a real .btn now, in the same
   ghost tier as "Other exams", so it inherits the shape, the lift on hover,
   the press, and the focus ring every other button on the site has. Just
   smaller, because it is a shortcut for one step rather than an action on
   the page. */
.setup-all {
  flex: none;
  gap: 7px;
  padding: 9px 18px;
  font-size: 0.82rem;
  font-weight: 700;
}
.setup-all-icon {
  display: grid;
  place-items: center;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  line-height: 1;
  transition: background 0.16s ease;
}
.setup-all:hover .setup-all-icon { background: var(--navy); }

/* Clearing is the destructive half of the toggle, so it does not wear the
   same inviting blue as the half that fills the form in. */
.setup-all.is-clear .setup-all-icon { background: var(--muted); }
.setup-all.is-clear:hover { border-color: var(--muted); color: var(--ink); }
.setup-all.is-clear:hover .setup-all-icon { background: var(--danger); }

.setup-all:active { transform: scale(0.96); }
.setup-all:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* Pressing Start with nothing chosen. Says what is missing where the button
   is, and nudges once so it is noticed even mid-scroll. */
.setup-error {
  margin: 18px 0 0;
  padding: 13px 16px;
  border-radius: 12px;
  background: rgba(200, 74, 82, 0.09);
  border: 1px solid rgba(200, 74, 82, 0.34);
  color: #93353c;
  font-size: 0.88rem;
  font-weight: 600;
}
.setup-error.is-shake { animation: setup-shake 0.42s ease; }
@keyframes setup-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}
@media (prefers-reduced-motion: reduce) {
  .setup-error.is-shake { animation: none; }
}

/* ========================================================= personal bests

   What you have already scored on this paper, on the way in. Two numbers per
   section: the best, which is what is kept, and the last attempt, which is
   what makes the keeping rule visible instead of looking like a bug. */
.best-panel {
  margin-top: 22px;
  padding: 24px clamp(18px, 2.4vw, 30px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.06);
}
.best-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.best-panel-head h2 { margin: 0; font-size: 1.02rem; color: var(--navy); }
.best-panel-when {
  font-size: 0.78rem;
  color: var(--muted);
  font-variant-numeric: lining-nums;
}
.best-rows { display: grid; gap: 14px; }
.best-row {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
}
.best-name {
  font-weight: 700;
  color: var(--navy);
  font-size: 0.9rem;
  min-width: 0;
}
.best-name span {
  display: block;
  font-weight: 500;
  font-size: 0.74rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.best-bar {
  height: 10px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  overflow: hidden;
}
.best-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--blue), var(--success));
}
.best-val { text-align: right; white-space: nowrap; }
.best-val strong {
  display: block;
  font-size: 1.24rem;
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}
.best-val strong i {
  font-style: normal;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
}
.best-last {
  font-size: 0.74rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.best-last em { font-style: normal; font-weight: 700; }
.best-last em.is-up { color: var(--success); }
.best-last em.is-down { color: var(--danger); }
.best-note {
  margin: 18px 0 0;
  font-size: 0.79rem;
  line-height: 1.65;
  color: var(--muted);
}

@media (max-width: 620px) {
  .best-row { grid-template-columns: minmax(0, 1fr) auto; }
  .best-row .best-bar { grid-column: 1 / -1; order: 3; }
}

/* Where this attempt stands against the record, under the section's score. */
/* Same shape as the sectional-minimum badge beside it, so the two sit on one
   line whichever of them is showing. */
.section-card-best {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 0;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.section-card-best.is-new {
  background: rgba(217, 166, 58, 0.16);
  border-color: rgba(217, 166, 58, 0.4);
  color: #7a5709;
}
.section-card-best.is-new b { font-size: 0.9rem; }

/* The section switcher carries each section's record, so you can see where
   you stand before you open it. */
.paper-tab-best {
  font-style: normal;
  margin-left: 8px;
  color: var(--gold);
  font-weight: 700;
}

/* The running total of what step 1 has selected. */
.setup-count {
  margin: 14px 0 0;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

/* The flagged count and the way back to them, at the foot of the paper where
   you decide whether to submit. */
/* Its own line, above the decision.

   As an inline-flex box it sat in the button row, so a note about what you
   flagged looked like a third button beside "Mark this section" and "Submit
   exam". It is information with a shortcut in it, not a peer of those two. */
.finish-flagged {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 14px;
  margin: 16px auto 4px;
  padding: 10px 10px 10px 18px;
  border-radius: 999px;
  background: rgba(217, 166, 58, 0.14);
  border: 1px solid rgba(217, 166, 58, 0.42);
  color: #7a5709;
  font-size: 0.86rem;
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
}
.finish-flagged[hidden] { display: none; }
.finish-flagged .btn { padding: 8px 16px; font-size: 0.8rem; margin: 0; }

/* The buttons are appended straight to a text-align:center block with no
   whitespace between them, so they were touching edge to edge - which reads
   as one segmented control rather than two separate decisions, and put
   "Submit exam" a stray pixel away from "Mark this section". One of those
   ends the whole sitting. */
.paper-finish > .btn { margin: 14px 7px 0; }

@media (max-width: 560px) {
  .finish-flagged { width: auto; border-radius: 16px; }
  .paper-finish .finish-flagged .btn { width: auto; }
  /* Full-width stacked buttons supply their own separation. */
  .paper-finish > .btn { margin: 12px 0 0; }
}

/* ==========================================================================
   Exam figures

   The drawings a listening question offers you, and the diagrams a reading
   passage refers to, are not stored here - they are other people's material,
   so the pages link them from the Internet Archive instead of keeping a copy.
   The archive takes ten to twenty seconds to answer and sometimes never
   answers at all, which makes these the slowest thing on any page.

   Two things follow. The <img> carries width and height, measured once by
   tools/fetch_figure_sizes.py, so the browser holds the right shape open from
   the first paint and the question does not jump when the picture lands. And
   a figure that errors, or that is still missing long after the browser began
   fetching it, says so in the space it was given rather than leaving a gap
   the reader cannot interpret.
   ========================================================================== */

.fig { display: block; position: relative; }
.fig .fig-note { display: none; }

/* A floor under a figure whose size is not known. Every figure in the corpus
   has been measured, so the <img> normally reserves its own exact box and
   this never applies; it is here for a figure added later, and to give the
   wrapper an area so the watcher in exam-player.js can tell whether it is on
   screen. */
.fig.is-pending { min-height: 120px; }
.choice .fig.is-pending { min-height: 64px; }

/* A figure that never arrived keeps the space it was promised: the picture is
   hidden rather than removed, so the hole stays exactly the size the picture
   would have been and nothing on the page moves when we give up on it. The
   message sits inside that hole. */
.fig.is-failed {
  min-height: 92px;
  margin: 12px 0;
  border: 1px dashed rgba(17, 45, 78, 0.28);
  border-radius: 10px;
  background: rgba(17, 45, 78, 0.02);
}

.fig.is-failed img { visibility: hidden; }

.fig.is-failed .fig-note {
  /* Column flex, not grid: place-items on a grid made the sentence and the
     link two rows and pushed them to opposite ends of a tall box. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  position: absolute;
  inset: 0;
  padding: 10px 14px;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
}

/* Inside an answer button the note is all there is, and the button is
   already small: no second border around it. */
.choice .fig.is-failed {
  min-height: 0;
  margin: 0;
  border: 0;
  background: none;
}

/* A line saying whose the recording is - a reader handed somebody else's
   upload should be told so before they press play, not after. */
.q-audio-whose {
  display: inline-block;
  margin-top: 2px;
  font-size: 0.82rem;
  color: var(--muted);
}

/* The whole-test recording: the paper's own audio bar, wearing the paper's
   own chrome, so a reader does not have to work out that this row is a
   player too. Same blue, same dock under the command bar, same pin.

   It never offsets itself: --full-h is this bar's height, set on #examPaper
   so the paper's own players pin below it, and zeroed here so the bar does
   not push itself down by its own height. */
.q-audio-full-bar {
  --full-h: 0px;
  flex-wrap: wrap;
  row-gap: 4px;
}

/* Idle it is a button and nothing more - a 16:9 hole pinned to the top of
   every listening paper for a recording most readers never start is a lot of
   screen to reserve. It takes the shape of a player on the click that makes
   one. */
.q-audio-full-media { flex: none; }

.q-audio-full-bar.is-playing .q-audio-full-media {
  width: min(320px, 100%);
  aspect-ratio: 16 / 9;
}

.q-audio-play {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  background: #fff;
  color: var(--navy);
  font-weight: 700;
  font-size: 0.86rem;
  cursor: pointer;
  transition: 0.16s ease;
}

.q-audio-play:hover { border-color: var(--blue); }

.q-audio-play-icon {
  font-size: 1.05rem;
  line-height: 1;
  color: var(--blue);
}

.q-audio-yt {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 10px;
  display: block;
}

/* Whose it is, and the way out of it. */
.q-audio-full-side {
  flex: 1 1 160px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.q-audio-full-side .q-audio-whose { display: block; margin: 0; }

.q-audio-stop {
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

.q-audio-stop:hover { border-color: var(--blue); color: var(--navy); }

/* Two pinned players is one too many. While the whole-test recording is
   running, the paper's own player for this 問題 is both redundant and in the
   way - between the header, the command bar, a 180px frame and a 60px player
   there was more chrome than paper. It goes back to the flow until Stop. */
.exam-paper.is-full-playing .paper-section .q-audio { position: static; }

@media (max-width: 620px) {
  /* .q-audio goes display:block on a phone, so the row rules above stop
     applying; give the pieces their own stacking instead. */
  .q-audio-full-bar .q-audio-play { width: 100%; justify-content: center; }
  /* Not full width: a pinned 16:9 frame across a phone is a third of the
     screen, and what is on screen is a title card - the questions are on the
     page, not in the picture. Wide enough to keep YouTube's own controls
     usable, no wider. */
  .q-audio-full-bar.is-playing .q-audio-full-media { width: min(240px, 100%); }
  .q-audio-full-side { margin-top: 6px; }
}
