/* One palette for every Vila page.
 *
 * The side panel, the word book, review and options each carried their own copy
 * of these variables, which meant four independent dark-mode implementations
 * that drifted apart. The names here are the ones the existing pages already
 * use — renaming them would touch every rule in sidepanel.css for no gain.
 *
 * Sizes are deliberately few: two radii, four type steps. A page that needs a
 * fifth value usually needs a different layout, not a new token.
 */

:root {
  --bg: #ffffff;
  --surface: #f6f7f9;
  --border: #e3e6ea;
  --text: #1b1d21;
  --muted: #6b7280;
  --accent: #2563eb;
  --accent-soft: #eff4ff;

  /* Reserved for real success and real failure. A finished collection or a
     completed step is not "success" — it stays on --accent. */
  --ok: #16a34a;
  --bad: #dc2626;

  --radius: 8px;
  --radius-card: 12px;
  --radius-pill: 999px;

  --font: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "PingFang SC", "Microsoft YaHei", sans-serif;
  --font-mono: "SF Mono", "Cascadia Mono", Consolas, monospace;

  --size-body: 14px;
  --size-sm: 13px;
  --size-xs: 12px;
  --size-2xs: 11px;

  /* Keyboard reachability is part of the interaction, not an accessibility
     add-on. Every focusable control uses this same ring. */
  --focus-ring: 2px solid var(--accent);

  --dur: 150ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #17181b;
    --surface: #202226;
    --border: #303338;
    --text: #f2f3f5;
    --muted: #9aa1ab;
    --accent: #6ea8ff;
    --accent-soft: #1b2740;
    --ok: #4ade80;
    --bad: #f87171;
  }
}

/* Shared primitives: reset, page frame, and the five controls every Vila page
 * draws. Not a component library — anything used by exactly one page stays in
 * that page's own stylesheet.
 *
 * Every selector is wrapped in :where(), so these rules carry zero specificity
 * and a page rule always wins without needing !important. That is what makes it
 * safe to drop this file under stylesheets that were written before it existed.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--size-body);
  line-height: 1.6;
}

/* Centred document pages (word book, options). The side panel and review lay
   themselves out and do not use this. */
:where(.page) {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

:where(a) {
  color: var(--accent);
  text-decoration: none;
}

:where(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: 1px;
}

:where(button) {
  padding: 9px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: border-color var(--dur), background var(--dur);
}

:where(button:hover) {
  border-color: var(--accent);
}

/* Checkboxes and radios draw their own control. Handing them a text field's
   padding and border wrapped every one of them in a 40px box the size of an
   input, which read as a stray outline sitting next to its own label. */
:where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
}

:where(.card) {
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
}

:where(.pill) {
  display: inline-block;
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: var(--size-xs);
}

/* Secondary copy: hints under a field, a subtitle under a heading, the metadata
   at the end of a row. One class instead of three near-identical ones. */
:where(.muted) {
  color: var(--muted);
  font-size: var(--size-sm);
}

:where(code, kbd) {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface);
  font-size: var(--size-xs);
}

/* 「这是什么」 — a question mark beside something whose name is not enough, and
   the two lines that explain it.
 *
 * A component rather than a base element, so it is not wrapped in :where(): five
 * hand-rolled tooltips would drift into five shapes, and this panel has more than
 * five places that need one.
 *
 * It opens on hover *and* on keyboard focus. Hover-only help is help that only
 * exists for people using a mouse, and docs/ui-plan.md counts keyboard reach as
 * part of 交互方便. It is never a link and never performs anything — it can sit
 * beside a heading without competing with the action at the other end of the row.
 */
.help-wrap {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
}

.help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--muted);
  cursor: help;
  font-size: 10px;
  line-height: 1;
}

.help:hover,
.help:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

.help-bubble {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  width: max-content;
  max-width: min(250px, calc(100vw - 32px));
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--text) 14%, transparent);
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.65;
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur), visibility var(--dur);
}

/* The lead is what the feature gets you; the rest is how it works. Ranked by
   colour and size rather than separated by a blank line, so the answer to
   「这是什么」 is the first thing read and the mechanics stay available under it. */
.help-lead,
.help-line {
  display: block;
}

.help-lead {
  margin-bottom: 6px;
  color: var(--text);
  font-size: var(--size-xs);
}

/* For an icon sitting near the right edge, where a bubble hung from its left
   would run off the panel. */
.help-bubble.is-end {
  left: auto;
  right: 0;
}

.help-wrap:hover .help-bubble,
.help:focus-visible + .help-bubble {
  opacity: 1;
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Palette, reset and control primitives come from ../ui/tokens.css and
   ../ui/base.css. Only side-panel-specific rules belong in this file. */

/* Tighter than the document pages on purpose: the panel is narrow and every
   extra line of leading costs a line of subtitles. */
/* The panel is a fixed-height column, not a scrolling document: the tab row stays
   put, and whichever view is open scrolls inside itself. That also means each tab
   keeps its own scroll position, which is what anyone expects when they come back
   to a list they were halfway down. */
html,
body {
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  line-height: 1.5;
}

/* Everything needed mid-video stays here. Playback state, learning actions and
   utilities are separate groups; at narrow widths the groups wrap as units. */
/* The reserved scrollbar gutter is mirrored here so both edges of the panel line
   up. The list scrolls and its scrollbar sits inside its right padding, which put
   the subtitles 29px from the edge while this bar — which does not scroll — kept
   14px, so the two sides disagreed by half a gutter all the way down.
 *
 * `scrollbar-gutter` only applies to a scroll container, hence `overflow: hidden`.
 * That is safe because nothing in this header floats out of it: the problem
 * report expands in the flow, and the difficulty popover is a sibling of the
 * header rather than a child of it (see .tier-pop). Declared rather
 * than padded by a hardcoded 15px, so a platform with overlay scrollbars reserves
 * nothing here either and the two edges still agree. */
.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  flex: 0 0 auto;
  overflow: hidden;
  scrollbar-gutter: stable;
  padding: 8px 14px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.bar-main {
  display: flex;
  align-items: center;
  gap: 8px;
}

.bar-state {
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.bar-tools {
  display: flex;
  align-items: center;
  margin-left: auto;
  gap: 4px;
}

.problem-feedback {
  position: relative;
}

/* In the flow under the header. At 400px a popover has nowhere to go that is not
   on top of the only content there is — the difficulty popover is the one
   exception, because it is set once and always closes (see .tier-pop). */
.problem-menu {
  padding: 8px 0 4px;
  border-top: 1px dashed var(--border);
  margin-top: 8px;
}

.problem-menu[hidden] {
  display: none;
}

/* The report form fills the menu that opened it; the menu supplies the frame. */
.report {
  display: grid;
  gap: 2px;
}

.tool-group {
  display: flex;
  align-items: center;
  gap: 5px;
}

.tool-group-links {
  padding-left: 9px;
  border-left: 1px solid var(--border);
}

/* The gap, not the divider, is what keeps these apart. The divider only renders
   once there is a timestamp to divide from, so without a gap the two spans ran
   together into "SubtitlesWaiting for video..." on every page with no video —
   which, now that the panel opens everywhere, is most of them. */
.bar-sub {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 10.5px;
  line-height: 1.45;
  white-space: nowrap;
}

.bar-sub #subs-count {
  color: color-mix(in srgb, var(--muted) 92%, var(--text));
}

.bar-sub .playback-time:not([hidden]) + #subs-count::before {
  display: inline-block;
  width: 1px;
  height: 11px;
  margin: 0 8px 0 0;
  background: var(--border);
  content: '';
  vertical-align: -1px;
}

.bar-sub .retry {
  margin-left: auto;
}

.playback-time,
#clock-meta {
  font-variant-numeric: tabular-nums;
}

/* Opens inside the saved line, in the flow, pushing what follows down.
 *
 * As a popover it covered three of the six subtitles on screen — and a 400px
 * column has nowhere for a popover to go that is not on top of the only content
 * there is. Reviews of the nearest competitor make the same complaint about its
 * floating boxes drifting over the video.
 *
 * In the flow there is also nothing to position: no anchor, no edge detection, no
 * outside-click trap.
 */
.goal-menu {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 7px;
  padding-top: 8px;
  border-top: 1px dashed var(--border);
}

.goal-menu[hidden] {
  display: none;
}

/* A row in that list: the paragraph's name, and how many sentences it already
   holds pushed to the far end. */
/* Chips, not rows: six of them wrap into two lines of about 50px, where six
   full-width rows would be 250px of pushed-down subtitles. The list is short and
   its items are two to four characters — a column would be mostly empty. */
.goal-row {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
  white-space: nowrap;
}

.goal-row.goal-row-new {
  border: 1px dashed var(--accent);
  color: var(--accent);
}

.goal-row.goal-row-new:hover {
  background: var(--accent-soft);
}

.goal-row:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Where a star is going right now. Marked rather than merely highlighted: this
   list is read to answer "where does the next one land". */
/* Where the next star lands. Filled rather than ticked: among chips a checkmark
   is a glyph competing with the label beside it. */
.goal-row.is-current {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--bg);
}

/* The ways of declining the list above — not two more chips. A dashed pill is
   still a pill; these are actions, and docs/ui-plan.md gives a low-frequency
   action plain accent text rather than a box. Under a rule, so the separation is
   a boundary rather than a change of decoration. */
.goal-menu-foot {
  flex-basis: 100%;
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

/* docs/ui-plan.md: accent 色 = 能点，灰色 = 只读. In grey these read as two labels
   printed under a rule — which is precisely what that rule says grey means. */
.goal-out {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}

.goal-out:hover {
  border: 0;
  text-decoration: underline;
}

/* Where the sentence already is. Pressing it again does nothing, so it is grey
   and not a pointer — here that is the honest reading rather than a mistake.
   Ticked rather than filled: filled in accent, 不收进文稿 became the loudest thing
   in the list it declines. */
.goal-out.is-current {
  color: var(--muted);
  cursor: default;
}

.goal-out.is-current:hover {
  text-decoration: none;
}

.goal-out.is-current::before {
  content: '✓ ';
}

.goal-row-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-row-meta {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--size-2xs);
  font-variant-numeric: tabular-nums;
}

/* The templates, offered only when there is nothing to file into. Same chips as
   the paragraphs above them, one per line: a template's name is a sentence
   (「做一次英文 pitch」) and three of those wrapped mid-phrase read as rubble. */
.goal-menu-templates {
  flex-basis: 100%;
  display: grid;
  justify-items: start;
  gap: 5px;
  margin-top: 2px;
}

.goal-row-empty {
  margin: 0;
  padding: 7px 9px;
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.6;
}

/* Names the document the chips below belong to. `flex-basis: 100%` because the
   picker is a wrapping row — without it the heading would queue up as the first
   chip. */
.goal-menu-title {
  flex-basis: 100%;
  min-width: 0;
  overflow: hidden;
  margin: 0 0 2px;
  color: var(--muted);
  font-size: var(--size-2xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-menu-row {
  display: block;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font-size: 12px;
  text-align: left;
}

.goal-menu-row:hover {
  border: 0;
  background: var(--surface);
}

.goal-menu-row[aria-pressed='true'] {
  color: var(--accent);
}

.goal-menu-row.link {
  color: var(--accent);
}

.goal-menu-current {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 2px;
  padding: 9px 10px;
  border-radius: 8px;
  background: var(--surface);
}

.goal-menu-current-copy {
  display: grid;
  flex: 1;
  min-width: 0;
  gap: 1px;
}

.goal-menu-current-copy small {
  color: var(--muted);
  font-size: 10px;
}

.goal-menu-name-button {
  width: fit-content;
  max-width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  text-align: left;
  overflow: hidden;
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-menu-name-button:hover {
  border: 0;
  color: var(--accent);
}

.goal-menu-name-input {
  min-width: 0;
  width: 100%;
  padding: 2px 5px;
  font-size: 13px;
  font-weight: 700;
}

.goal-menu-switch,
.goal-menu-back {
  padding: 3px 5px;
  border: 0;
  background: transparent;
  color: var(--accent);
  font-size: 11px;
}

.goal-menu-switch:hover,
.goal-menu-back:hover {
  border: 0;
  text-decoration: underline;
}

.goal-menu-picker-head {
  display: flex;
  align-items: center;
  min-height: 29px;
}

.goal-menu-picker-head .goal-menu-title {
  margin-left: auto;
  margin-right: 8px;
}

.goal-section-row,
.goal-picker-row {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  align-items: center;
  gap: 7px;
}

.goal-picker-row {
  grid-template-columns: 16px minmax(0, 1fr) auto;
}

.goal-menu-check {
  display: inline-grid;
  width: 15px;
  height: 15px;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: #fff;
  font-size: 9px;
  line-height: 1;
}

.goal-menu-row[aria-pressed='true'] .goal-menu-check {
  border-color: var(--accent);
  background: var(--accent);
}

.goal-picker-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-picker-meta {
  color: var(--muted);
  font-size: 10px;
}

.goal-picker-off {
  margin-top: 4px;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding-top: 9px;
}

.goal-menu-note {
  margin: 4px 8px 7px;
  color: var(--muted);
  font-size: 11px;
}

.goal-menu-row-emphasis {
  color: var(--accent);
  font-weight: 600;
}

.goal-menu-secondary {
  color: var(--muted);
}

.goal-menu-footer {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}

/* The receipt on a starred line: where it went, and the way to change it. */
/* Feedback, not a control: it says where the line went. A bordered dropdown here
   advertised work that was never required. */
/* Now carries 「文稿名 · 段落」, so it is the one thing on this line that can be
   any length: it gives up width before the timestamp or the star do, and ellipses
   rather than wrapping the row onto two lines. */
.goal-tag {
  min-width: 0;
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font-size: var(--size-2xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-tag::before {
  content: '→ ';
}

.goal-tag:hover {
  border: 0;
  color: var(--accent);
}

.goal-tag-offer {
  color: var(--accent);
}

.goal-tag-offer::before {
  content: '';
}

@media (max-width: 640px) {
  .tool-group {
    flex-wrap: wrap;
  }
}

/* One row: time, then the difficulty readout and the four actions at the far end.
 *
 * This was two rows on purpose — "context first, actions second" — because
 * 「播放到 0:20，共 4:00」 was 150px and the three groups came to 413px in a 372px
 * line. But two rows meant the second one held four right-aligned buttons over an
 * empty left half, and the header cost 76px of a panel whose subject is the
 * subtitles under it. Shortening the readout to `0:20 / 4:00` buys back 80px, and
 * everything lands on one line with room to spare.
 *
 * Four columns rather than three: the flexible middle belongs to the status text,
 * which is the only thing here that can be any length. */
@media (max-width: 520px) {
  .bar-main {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    column-gap: 8px;
    row-gap: 6px;
  }

  .bar-main > .playback-time {
    grid-column: 1;
    white-space: nowrap;
  }

  .bar-state {
    grid-column: 3;
    grid-row: 1;
  }

  /* Its own row under the header, not a fourth thing in the first one. Sharing the
     status column it dropped to a second row on its own and, once the panel was
     narrow, was squeezed to a few pixels under the difficulty button. */
  .bar-main > .retry {
    grid-column: 1 / -1;
    grid-row: 2;
    justify-self: start;
  }

  /* The one column allowed to give up width. The side panel is resizable, and an
     `auto` track will not shrink below its content — so below about 372px the
     difficulty readout kept its full width, ran under the tools and was then
     clipped by the header's `overflow: hidden`, which is what put the AI button
     on top of it. Everything else here is either short or a row of fixed icons. */
  /* Time and difficulty are the same kind of thing — what is true right now —
     so they sit together at the left and the flexible column moves between them
     and the tools. Before this the status column was in the middle, and while a
     video plays it is empty: the header carried a block of space that reported
     nothing, with the difficulty readout marooned on the far side of it. */
  .bar-main {
    grid-template-columns: auto minmax(0, auto) minmax(0, 1fr) auto;
  }

  .bar-main > .tier-picker {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
  }

  .bar-tools {
    grid-column: 4;
    grid-row: 1;
    justify-self: end;
    margin-left: 0;
    flex-wrap: nowrap;
  }
}

.goal-tag:hover {
  border: 0;
  color: var(--accent);
}

.goal-create-compact {
  padding: 5px 7px 7px;
}

.goal-create-title {
  margin: 2px 0 10px;
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
}

.goal-create-label {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0 5px;
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
}

.goal-create-step {
  display: inline-grid;
  width: 17px;
  height: 17px;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
}

.goal-create-hint {
  margin: -2px 0 6px 23px;
  color: var(--muted);
  font-size: 10px;
}

.goal-create-compact > input {
  width: 100%;
  padding: 8px 9px;
  border-color: var(--border-strong, var(--border));
  background: var(--bg);
  font-size: 12px;
}

.goal-create-compact > input:focus {
  border-color: var(--accent);
  outline: 2px solid var(--accent-soft);
  outline-offset: 1px;
}

.goal-template-list {
  display: grid;
  gap: 5px;
  margin: 0 0 8px;
}

.goal-template-button {
  display: grid;
  gap: 1px;
  width: 100%;
  padding: 7px 8px;
  border-color: var(--border);
  background: transparent;
  color: var(--text);
  font-size: 12px;
  text-align: left;
}

.goal-template-button span {
  overflow: hidden;
  color: var(--muted);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-template-button[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.goal-create-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
}

.goal-menu-primary {
  padding: 6px 10px;
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  white-space: nowrap;
}

.goal-menu-primary:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.tier-picker {
  position: relative;
}

/* 「已更新」 beside the difficulty button after a choice closes the
   popover. Laid over the empty status column rather than taking a grid track of
   its own, so it cannot push the tools about for the second and a half it shows. */
.tier-flash {
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  transform: translateY(-50%);
  padding: 0 4px;
  background: var(--bg);
  color: var(--ok);
  font-size: var(--size-xs);
  white-space: nowrap;
  pointer-events: none;
}

.tier-flash[hidden] {
  display: none;
}

/* Over the subtitles, under the header. The header keeps working — a press on
   any other tool closes the popover and does its own thing — and the scrim is
   light, because the subtitles re-colouring behind it is what the choice does. */
.tier-scrim {
  position: absolute;
  inset: var(--tier-pop-top, 0) 0 0 0;
  z-index: 30;
  background: rgba(15, 17, 21, 0.12);
}

.tier-pop {
  position: absolute;
  top: calc(var(--tier-pop-top, 0px) - 4px);
  right: 14px;
  left: 14px;
  z-index: 31;
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: calc(100% - var(--tier-pop-top, 0px) - 10px);
  overflow-y: auto;
  padding: 12px 10px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
  box-shadow: 0 12px 32px rgba(15, 17, 21, 0.18);
}

.tier-scrim[hidden],
.tier-pop[hidden] {
  display: none;
}

.tier-pop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 6px;
}

.tier-pop-title {
  font-size: var(--size-body);
}

.tier-pop-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -6px -4px -6px 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.tier-pop-close:hover {
  border: 0;
  background: var(--surface);
  color: var(--text);
}

.tier-pop-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-width: 2;
}

.tier-pop-lead,
.tier-pop-note {
  margin: 2px 6px 0;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.5;
}

.tier-pop-note {
  margin: 10px 6px 4px;
}

.tier-pop .level-row {
  margin: 10px 6px 0;
}

.tier-pop .marked-row {
  margin: 10px 6px 4px;
}

.tier-pop-nudge {
  margin: 8px 6px 0;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--surface);
  font-size: var(--size-xs);
  line-height: 1.5;
}

.tier-rows {
  display: grid;
  gap: 1px;
}

.tier-pop-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 6px;
  padding: 8px 6px 0;
  border-top: 1px solid var(--border);
}

.tier-pop-link {
  padding: 4px 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-size: var(--size-xs);
  cursor: pointer;
}

.tier-pop-link:hover {
  border: 0;
  text-decoration: underline;
}

.tier-pop-done,
.tour-next {
  height: 32px;
  padding: 0 16px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-size: var(--size-sm);
  font-weight: 600;
  cursor: pointer;
}

.tier-pop-done:hover,
.tour-next:hover {
  border-color: var(--accent);
  color: #fff;
  filter: brightness(1.08);
}

/* 我的词汇水平 as one segmented row — 不确定 plus the ladder — shared by the
   popover and the tour's third step. One press answers it, so it is buttons
   rather than a select. */
.level-row {
  display: grid;
  grid-template-columns: repeat(var(--level-count, 6), minmax(0, 1fr));
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.level-choice {
  min-width: 0;
  height: 32px;
  padding: 0 2px;
  overflow: hidden;
  border: 0;
  border-left: 1px solid var(--border);
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-size: var(--size-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.level-choice:first-child {
  border-left: 0;
}

.level-choice:hover {
  border-color: var(--border);
  color: var(--text);
}

.level-choice[aria-pressed='true'] {
  background: var(--bg);
  color: var(--accent);
  font-weight: 600;
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

.marked-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: var(--size-xs);
}

.marked-row-label {
  white-space: nowrap;
}

.marked-row-none {
  color: var(--text);
}

/* The same frame a marked word wears in the subtitles, so the legend is the
   thing it describes rather than a picture of it. */
.tier-chip {
  padding: 1px 4px;
  border: 1px solid var(--tier);
  border-radius: 5px;
  background: color-mix(in srgb, var(--tier) 10%, transparent);
  color: var(--tier);
  font-size: var(--size-xs);
  font-weight: 600;
}

.tier-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px 5px 6px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
}

.tier-row:hover {
  background: var(--surface);
}

.tier-swatch {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border: 1px solid var(--tier);
  border-radius: 3px;
  background: color-mix(in srgb, var(--tier) 25%, transparent);
}

.tier-desc {
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--size-2xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tier-check {
  width: 14px;
  margin-left: auto;
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  text-align: center;
}

.tier-row input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.tier-row input:focus-visible + .tier-swatch {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* Not a seventh difficulty: the rungs above are Vila's guess at what is hard,
   this is the learner's own list. Hence a line of its own and a dashed
   accent line instead of a colour chip — a chip would read as one more rung. */
.tier-saved {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 8px 10px 6px 6px;
  border-top: 1px solid var(--border);
  border-radius: 6px;
  margin-top: 3px;
  font-size: 13px;
  cursor: pointer;
}

.tier-saved:hover {
  background: var(--surface);
}

.tier-saved input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.tier-saved input:focus-visible + .tier-saved-swatch {
  outline: var(--focus-ring);
  outline-offset: 3px;
}

.tier-saved-swatch {
  flex: 0 0 auto;
  width: 11px;
  height: 8px;
  border-bottom: 2px dashed var(--accent);
}

.tier-saved-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tier-saved-note {
  color: var(--muted);
  font-size: 11px;
  line-height: 1.5;
}

.icon-button {
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  line-height: 1.5;
  cursor: pointer;
}

.icon-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.icon-button[data-on='true'] {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

.icon-button[data-on='false'] {
  opacity: 0.6;
  text-decoration: line-through;
}

/* A control that cannot do anything must not respond as though it can — the
   strike-through of data-on='false' means "switched off, click to switch on",
   which is a different statement from "unavailable here". */
.icon-button:disabled,
.icon-button:disabled:hover {
  border-color: var(--border);
  background: none;
  color: var(--muted);
  opacity: 0.5;
  text-decoration: none;
  cursor: default;
}

.icon-button[data-due='true'] {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

.retry {
  padding: 1px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.retry:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* --------------------------------------------------------------- panel */

/* The scrollbar is laid out, not overlaid on the padding. Without this the list
   sat 14px from the left and 29px from the right — the bar above it keeps its
   14px on both sides because it does not scroll, so the two edges disagreed by
   half a gutter down the whole panel. */
.panel {
  min-height: 0;
  flex: 1 1 auto;
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding: 10px 14px 20px;
}

.bar-tools .icon-button {
  position: relative;
  display: inline-flex;
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.bar-tools .icon-button svg {
  width: 17px;
  height: 17px;
  fill: currentColor;
}

.bar-tools .icon-button .toolbar-line-icon {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

.tool-text {
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.tool-text-ai {
  font-size: 12px;
  letter-spacing: 0.01em;
}

.bar-tools .icon-button .toolbar-star-icon {
  width: 19px;
  height: 19px;
  stroke-width: 1.65;
}

.bar-tools .icon-button[aria-expanded='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.bar-tools .icon-button[data-on='false']:not(:disabled)::after {
  content: '';
  position: absolute;
  width: 21px;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  transform: rotate(-45deg);
}

/* Same height as the four buttons it stands beside. `padding: 0 7px` replaced
   .icon-button's padding outright, collapsing this one to the height of its own
   text — 15px against their 32px — so it read as a thinner, slightly mis-centred
   thing in a row of equals. */
.bar-main #tier-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: auto;
  max-width: min(124px, 100%);
  height: 32px;
  overflow: hidden;
  padding: 0 9px;
}

/* Filtered is a fact about a preference, not an action to take, so it is said
   with weight rather than with the accent colour. docs/ui-plan.md: 单一强调色 —
   accent 色 = 能点; a filled accent pill made a set-once setting the loudest
   control on a screen whose subject is the subtitles. */
.bar-main #tier-button[data-filtered='true'] {
  border-color: color-mix(in srgb, var(--text) 26%, var(--border));
  color: var(--text);
}

.tier-caret {
  flex: 0 0 auto;
  color: currentColor;
  font-size: 9px;
  opacity: 0.7;
}

/* `min-width: 0` is what makes the ellipsis reachable: a flex item defaults to
   `min-width: auto` and refuses to shrink below its own text, so the label pushed
   the button past its max-width instead of truncating. */
#tier-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tool-badge {
  min-width: 13px;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  text-align: center;
}

#collection-count:not([hidden]) {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 16px;
  padding: 3px 4px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
}

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

.subtitle-search {
  display: flex;
  gap: 6px;
  margin-top: 7px;
}

.subtitle-search[hidden] {
  display: none;
}

.subtitle-search input {
  flex: 1;
  min-width: 0;
  padding: 6px 9px;
  font-size: 12px;
}

.search-close {
  padding: 5px 8px;
  color: var(--muted);
  font-size: 11px;
}

.search-results ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

.search-summary {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 12px;
}

.search-result {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 9px;
  width: 100%;
  padding: 9px 5px;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  text-align: left;
}

.search-result:hover {
  border-color: var(--border);
  background: var(--surface);
}

.search-result time {
  color: var(--accent);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.search-result mark {
  border-radius: 3px;
  background: var(--accent-soft);
  color: inherit;
}

.practice-bar {
  z-index: 12;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
  min-height: 50px;
  padding: 6px 10px;
  border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(10px);
}

.practice-hint {
  min-height: 16px;
  padding: 0 2px;
  margin: 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 16px;
  text-align: center;
}

.practice-hint[hidden] {
  display: none;
}

.practice-hint[data-kind='error'] {
  color: var(--danger, #c9362b);
}

.practice-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}

.practice-transport,
.sentence-nav,
.sentence-end,
.practice-tools {
  display: flex;
  align-items: center;
}

.practice-transport {
  flex: 1 1 auto;
  justify-content: flex-start;
  gap: 4px;
  min-width: 0;
}

.sentence-nav {
  flex: 1 1 116px;
  max-width: 136px;
  min-width: 100px;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

.practice-tools {
  flex: 0 0 auto;
  gap: 4px;
  padding-left: 9px;
  border-left: 1px solid var(--border);
}

.practice-bar button {
  position: relative;
  min-width: 36px;
  min-height: 32px;
  padding: 4px 8px;
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
}

.practice-transport > button:not(.play-toggle) {
  min-width: 72px;
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 11px;
}

.sentence-nav button {
  flex: 1 1 50%;
  min-width: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: 11px;
}

.sentence-nav button + button {
  border-left: 1px solid var(--border);
}

/* 循环 and 每句停 both decide what happens at the end of the sentence, and only
   one of them can be on, so they are drawn as one control — the same joined
   shape as 上一句／下一句 next to them.
   No `overflow: hidden` here, unlike .sentence-nav: this group holds the 「N 遍」
   badge, and clipping the box cut the top off it. The joined outline comes from
   the end buttons' own corners instead, which needs no clipping at all. */
.sentence-end {
  flex: 1 1 118px;
  max-width: 140px;
  min-width: 104px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

.sentence-end button {
  flex: 1 1 50%;
  min-width: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: 11px;
}

/* 9px, not 10px: inside a 1px border the container's own curve is a radius
   smaller, and matching 10px would leave the hover fill overhanging the corner. */
.sentence-end button:first-child {
  border-radius: 9px 0 0 9px;
}

.sentence-end button:last-child {
  border-radius: 0 9px 9px 0;
}

.sentence-end button + button {
  border-left: 1px solid var(--border);
}

/* A column, so the 「N 遍」 count can sit under the label instead of on top of
   it. With the count hidden this is one centred line, exactly as before. */
#loop-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-top: 1px;
  padding-bottom: 1px;
}

.practice-transport button:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Where the 播放中 / 已暂停 readout used to sit, at close to the same width. A
   readout there was the one thing on this bar that could not be pressed, while
   the move it described — pausing — was the only one that sent the learner back
   to the page. The icon carries the state and the label carries the action, the
   way 循环这句 and 遮挡译文 do beside it. */
.play-toggle {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 62px;
  min-height: 32px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  white-space: nowrap;
}

.play-toggle-icon {
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
  fill: currentColor;
}

.play-toggle[data-playing='true'] .play-toggle-play,
.play-toggle[data-playing='false'] .play-toggle-pause {
  display: none;
}

.play-toggle[data-error='true'] {
  border-color: var(--danger, #c9362b);
  color: var(--danger, #c9362b);
}

/* Playing is the state where the button's job is to stop, so it is the state that
   gets colour — the same rule the readout followed before it became a control. */
.play-toggle[data-playing='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.practice-tools button[data-error='true'] {
  border-color: var(--danger, #c9362b);
  color: var(--danger, #c9362b);
}

.practice-tools button[aria-disabled='true'] {
  opacity: 0.55;
  cursor: help;
}

.practice-bar button[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.practice-bar #speed-button {
  min-width: 42px;
  font-variant-numeric: tabular-nums;
}

/* 遮挡 opens two switches instead of being one, so it carries a caret and the
   row opens inside the bar — the same in-flow disclosure the difficulty menu
   uses, for the same reason: nothing may float over the subtitles. */
#mask-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.mask-caret {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  margin-left: 3px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 150ms ease;
}

#mask-button[aria-expanded='true'] .mask-caret {
  transform: rotate(180deg);
}

.mask-menu {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 2px 6px;
  border-bottom: 1px dashed var(--border);
  margin-bottom: 3px;
}

.mask-menu[hidden] {
  display: none;
}

.mask-menu-label {
  color: var(--muted);
  font-size: 11px;
}

.mask-menu-pills {
  display: flex;
  align-items: center;
  gap: 4px;
}

.mask-menu-pills button {
  min-width: 44px;
  min-height: 26px;
  padding: 3px 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font-size: 11px;
}

.mask-menu-keys {
  margin-left: auto;
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
}

/* Inside the button and in the flow, not hanging off a corner. A corner badge
   only survives in a box that never clips, and this one sits in the 循环／每句停
   group, which cut the top half of 「3 遍」 off. Tucking it into the button's own
   top-right corner instead still overprinted the label: 「停止循环」 fills the
   59px half-button at 400px on its own, measured 16px of overlap there and the
   whole badge at 360px. Under the label, both fit — 27px of the 30px the button
   has — and nothing has to overflow anything to be seen. */
.loop-count {
  min-width: 16px;
  padding: 2px 3px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 9px;
  line-height: 1;
  white-space: nowrap;
}

@media (max-width: 460px) {
  .practice-bar {
    padding-right: 7px;
    padding-left: 7px;
  }

  .practice-controls {
    gap: 5px;
  }

  .practice-transport {
    gap: 2px;
  }

  .sentence-nav {
    flex-basis: 102px;
    max-width: 116px;
    min-width: 92px;
  }

  .sentence-nav button,
  .practice-transport > button {
    padding-right: 5px;
    padding-left: 5px;
  }

  .play-toggle {
    min-width: 52px;
    gap: 4px;
  }

  .sentence-end {
    flex-basis: 104px;
    max-width: 118px;
    min-width: 96px;
  }

  .mask-menu-keys {
    font-size: 10px;
  }

  .practice-tools {
    gap: 2px;
    padding-left: 6px;
  }

  .practice-tools button {
    min-width: 34px;
    padding-right: 6px;
    padding-left: 6px;
  }
}

body.masked .cue-zh:not(.revealed) {
  color: transparent !important;
  border-radius: 4px;
  background: color-mix(in srgb, var(--muted) 22%, transparent);
  cursor: pointer;
  user-select: none;
}

body.masked .cue-zh:not(.revealed) * {
  color: transparent !important;
  background: transparent;
}

/* Hiding the subtitle is the other exercise: the translation is still there, and
   the question is whether you heard the line at all. Same grey block as above so
   the two switches do not look like two different features. */
body.masked-source .cue-text:not(.revealed) {
  position: relative;
  color: transparent !important;
  border-radius: 4px;
  background: color-mix(in srgb, var(--muted) 22%, transparent);
  cursor: pointer;
  user-select: none;
}

/* Marked words carry their own colour, border and underline, every one of which
   would draw the shape of the sentence back onto the block. */
body.masked-source .cue-text:not(.revealed) .word {
  border-color: transparent;
  background: transparent;
  color: transparent !important;
  text-decoration-color: transparent;
}

/* Only on the sentence 每句停 is holding: it says what the key under the
   learner's thumb will do, in the one place they are looking. */
.reveal-tip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted) !important;
  font-size: 11px;
  pointer-events: none;
  user-select: none;
}

@media (prefers-reduced-transparency: reduce) {
  .practice-bar {
    background: var(--bg);
    backdrop-filter: none;
  }
}

/* ---------------------------------------------------------- voice course */

/* A course temporarily replaces the subtitle list instead of becoming another
   little card inside it. Speaking needs one clear focal surface; the original
   video continues playing in the tab beside it, so keeping this panel narrow and
   self-contained is more useful than trying to show every subtitle at once. */
/* ------------------------------------------------------------------- tabs
 *
 * The panel's only navigation. Five slots, four permanent; "在看" appears only
 * while a video is playing. Underline rather than a filled pill: this row sits
 * directly under Chrome's own side-panel header, and two stacked bars of
 * chrome in a 400px column is already one more than the content can spare.
 */

/* Written as :not([hidden]) for the same reason #view-watch is: a class with
   `display` beats the user agent's [hidden] rule, so the tab row would stay on
   screen through the first-run questions no matter what the attribute said. */
.tabs:not([hidden]) {
  z-index: 12;
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 4px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  overflow-x: auto;
  scrollbar-width: thin;
}

.tab {
  padding: 8px 6px;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-sm);
  transition: color var(--dur);
  flex: 1 0 auto;
  min-height: 36px;
  border-radius: var(--radius);
  white-space: nowrap;
}

.tab[hidden] {
  display: none;
}

.tab:hover {
  color: var(--text);
}

.tab[aria-selected='true'] {
  color: var(--accent);
  font-weight: 600;
  background: var(--accent-soft);
  box-shadow: inset 0 -2px var(--accent);
}

.view {
  min-height: 0;
  flex: 1 1 auto;
  overflow-y: auto;
  min-width: 0;
  overscroll-behavior-y: contain;
}

.view[hidden] {
  display: none;
}

/* Written as :not([hidden]) on purpose. An id selector beats .view[hidden], so a
   plain `#view-watch { display: flex }` silently un-hides this view and stacks it
   under whichever tab is actually open. */
#view-watch:not([hidden]) {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* The frame fills the view and scrolls itself. A fixed 1100px height inside a
   scrolling view gave the settings form two scrollbars — the panel's, moving a
   frame that was already too short for its contents, and the frame's own — and
   whichever one you grabbed, the other stayed where it was. */
#view-settings:not([hidden]) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.settings-onboarding-return {
  flex: 0 0 auto;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}

.settings-onboarding-return[hidden] { display: none; }

.settings-onboarding-back {
  padding: 5px 7px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

.settings-onboarding-back:hover { color: var(--text); }

.settings-frame {
  display: block;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  border: 0;
  background: var(--bg);
}

/* ------------------------------------------------------------------ shelf
 *
 * 词句 and 文稿: the two tabs that hold what has been kept. Rows are separated by
 * rules rather than boxed into cards — at 400px a card's own padding duplicates
 * the container's and eats the space that made the card readable in the first
 * place.
 */

/* Review is not in this list. It is a frame filling its view, not a scrolling
   column of rows, and padding under the frame is a strip the card can never
   reach — see #view-review below. */
#view-notes,
#view-drafts {
  padding-bottom: 32px;
}

/* The one filled block in this tab, because review is the only thing here with a
   deadline. */
.review-band {
  display: flex;
  width: calc(100% - 28px);
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border: 0;
  border-radius: var(--radius-card);
  background: var(--accent-soft);
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  text-align: left;
  margin: 0 14px 4px;
  flex-wrap: wrap;
}

.review-band[hidden] {
  display: none;
}

.review-band-title {
  font-size: var(--size-sm);
  font-weight: 600;
}

.review-band-meta {
  font-size: var(--size-xs);
}

.shelf-tools {
  padding: 8px 14px 10px;
}

/* The panel writes its controls at 12px — the tab row, the shelf bar, the
   category chips. A 14px field with a full page's padding stood a third taller
   than everything around it and read as an imported control. */
.shelf-tools input {
  width: 100%;
  padding: 9px 11px;
  font-size: var(--size-xs);
  min-height: 36px;
}

.shelf-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px 0;
  font-size: var(--size-xs);
  flex-wrap: wrap;
}

/* A trough with the two shelves in it. They were two loose buttons that only
   differed by a tinted background when selected, which reads as one button
   highlighted rather than as a choice of two. */
.shelf-segment {
  display: flex;
  flex: 1;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.shelf-tab {
  flex: 1;
  padding: 6px 9px;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  border-radius: 6px;
  min-height: 30px;
}

.shelf-tab[aria-selected='true'],
.shelf-title {
  color: var(--text);
  font-weight: 500;
}

.shelf-count {
  color: var(--muted);
  font-size: var(--size-2xs);
}

.shelf-bar select {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 5px 7px;
  font-size: var(--size-xs);
  min-height: 36px;
  max-width: 100%;
  color: var(--muted);
}

/* The one action a shelf owns, at the far end of its own bar. Accent text and
   nothing else: an outlined button here would read as a block of its own beside
   a row of 12px labels, and this is an inline action. */
/* Sits with the name it explains, not spaced away from it by the bar's own gap. */
.shelf-title + .help-wrap {
  margin-left: -8px;
}

.shelf-action {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
}

.shelf-action:hover {
  border: 0;
  text-decoration: underline;
}

.shelf-langs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.shelf-langs[hidden] {
  display: none;
}

.shelf-summary {
  margin: 0;
  padding: 0 14px 8px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

#view-notes #list {
  margin: 0;
  padding: 0;
  list-style: none;
}

#view-notes #list > li {
  padding: 14px;
  border-top: 1px solid var(--border);
  overflow-wrap: anywhere;
}

/* The meaning belongs to the word above it. As a bare <p> it kept the browser's
   1em margin — 14px of air on both sides of a single line, which read as two
   unrelated rows rather than a word and what it means. */
#view-notes #list .sense {
  margin: 6px 0 0;
}

/* One empty state for the whole panel.
 *
 * Three tabs had three treatments for the same moment — a dashed box in 文稿,
 * plain text in 词句, a centred block in the workbench — which read as three
 * unrelated screens rather than one product with nothing in it yet. This is the
 * 词句 one, because its reasoning was right: left-aligned and unboxed, since a
 * framed sentence in a 400px column reads as an error notice rather than an
 * invitation, and centred text in a narrow column reads as a dead end.
 *
 * Always the same three parts: what is not here, how it gets here, and the
 * button that starts that.
 */
#view-notes .empty,
.blank,
.draft-empty {
  display: block;
  padding: 16px 14px 22px;
  border: 0;
  border-radius: 0;
  color: var(--muted);
  font-size: var(--size-xs);
}

#view-notes .empty strong,
.blank strong,
.draft-empty strong {
  display: block;
  color: var(--text);
  font-size: var(--size-sm);
  font-weight: 600;
}

#view-notes .empty p,
.blank p,
.draft-empty p {
  margin: 5px 0 0;
  line-height: 1.6;
}

#view-notes .empty[hidden] {
  display: none;
}

/* The same shape as #view-settings, and for the same reason — written as
   :not([hidden]) so an id selector cannot un-hide the view. The frame fills what
   the head leaves and the review document scrolls itself. It used to be a fixed
   620px frame inside a scrolling view, which was tall enough while the card was
   a word and its senses; once the card grew a 16:9 clip it was not, and the two
   scrollbars that appeared then — the view's, dragging a frame that was already
   too short, and the frame's own — put the rating buttons below a fold that
   looked from the outside like the panel cutting the card off. */
#view-review:not([hidden]) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.review-head {
  flex: 0 0 auto;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
}

.review-back {
  padding: 2px 8px 2px 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}

.review-back::before {
  content: '‹ ';
}

.review-back:hover {
  color: var(--accent);
}

.review-frame {
  display: block;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  border: 0;
  background: var(--bg);
}

/* A view, not an overlay. Written as an id selector with :not([hidden]) for the
   same reason #view-watch is: a class-level display rule would un-hide the
   section and stack it under whichever tab is actually open. The inner layout is
   unchanged — header, monitor, scrolling .voice-body, actions — because it was
   already a flex column filling its box. */
#voice-course:not([hidden]) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg);
}

.voice-target-section h2,
.voice-results h2 {
  margin: 0;
  font-size: 15px;
}

/* What is being practised. First line of the standing band, in text colour: it
   is the only line here that says what this screen is about, and it used to be
   a subtitle under a 「口语练习」 heading that said less. */
.voice-context {
  margin: 0;
  color: var(--text);
  font-size: var(--size-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.voice-progress {
  margin: 2px 0 0;
  color: var(--accent);
  font-size: var(--size-xs);
  font-weight: 600;
}

.voice-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 14px;
}

/* This band is a flex sibling of the only scrolling region (.voice-body), so
   what it holds stays visible throughout a lesson: what is being practised,
   timing, pause state and coach speed. It absorbed the header above it, which
   was a heading restating the selected tab plus a 返回 button left over from
   when this screen was an overlay. */
.voice-monitor {
  flex: 0 0 auto;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.voice-status {
  margin: 2px 0 0;
  color: var(--muted);
}

.voice-status[data-kind='error'] {
  color: var(--bad);
}

.voice-status[data-kind='loading'] {
  color: var(--accent);
  font-weight: 600;
}

/* One row: a caption and the five stops, which is the whole scale. As a slider
   it spent a label line, a track and a pair of end captions — 64px of standing
   band — to say what the pressed chip says by being pressed. */
.voice-speed-control {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-top: 8px;
  color: var(--muted);
  font-size: 12px;
}

.voice-speed-label {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.voice-speed-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.voice-speed-chip {
  min-height: 28px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--muted);
  font: inherit;
  font-size: var(--size-2xs);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.voice-speed-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.voice-speed-chip[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
}

.voice-speed-note {
  flex: 1 0 100%;
  color: var(--accent);
  font-size: 11px;
}

.voice-stage {
  margin: 7px 0 0;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

/* The timing card is intentionally more visible than a status line: the target
   duration is a choice the learner just made, and the countdown has to remain
   findable without scanning the transcript. */
.voice-live-stats {
  margin: 8px 0 0;
  padding: 8px 10px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
}

.voice-live-stats::before {
  display: block;
  margin-bottom: 2px;
  color: var(--accent);
  content: attr(data-caption);
  font-size: 11px;
  font-weight: 800;
}

/* A paused lesson is deliberately the calmest of the four states: it is a
   normal thing to do, not a warning, and it must not look like time running
   out. The changed caption is the signal that the clock has stopped. */
.voice-live-stats[data-kind='paused'] {
  border-color: var(--border);
  background: var(--surface);
  color: var(--muted);
  font-weight: 600;
}

.voice-live-stats[data-kind='paused']::before {
  color: var(--muted);
}

.voice-live-stats[data-kind='ending-soon'],
.voice-live-stats[data-kind='expired'] {
  border-color: #f59e0b;
  background: #fff7ed;
  color: #b45309;
  font-weight: 700;
}

.voice-live-stats[data-kind='expired'] {
  border-color: #dc2626;
  background: #fef2f2;
  color: #dc2626;
}

@media (prefers-color-scheme: dark) {
  .voice-live-stats[data-kind='ending-soon'] {
    border-color: #fbbf24;
    background: #422006;
    color: #fbbf24;
  }

  .voice-live-stats[data-kind='expired'] {
    border-color: #fca5a5;
    background: #450a0a;
    color: #fca5a5;
  }
}

/* 练习内容 and 练习时长 were two blocks with a 13px heading each, answering one
   question between them: what is this round going to be. One card, two rows,
   each captioned at 11px — a heading per block is a heading per question, and
   there was only ever one. */
.voice-setup {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
}

.voice-setup-row + .voice-setup-row {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.voice-setup-label {
  display: block;
  margin-bottom: 5px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.voice-setup select {
  width: 100%;
  min-height: 34px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: var(--size-sm);
}

.voice-profile-picker {
  display: flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

/* Round steps around a named button, on one 28px line. It was a 34px four-track
   grid where the two arrows were as tall as the thing they step through. */
.voice-chapter-transport {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.voice-chapter-transport button {
  min-height: 28px;
  padding: 4px 10px;
  font-size: 11px;
}

.voice-chapter-transport .voice-chapter-playback {
  border-color: var(--accent);
  background: transparent;
  color: var(--accent);
  font-weight: 600;
}

.voice-chapter-transport .voice-chapter-step {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 28px;
  min-height: 28px;
  padding: 0;
  border-color: var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  line-height: 0;
}

.voice-chapter-transport .voice-chapter-step:not(:disabled):hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.voice-chapter-step-icon {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.voice-chapter-loop-control {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 28px;
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
  cursor: pointer;
}

.voice-chapter-loop-control input {
  width: 14px;
  height: 14px;
  margin: 0;
  accent-color: var(--accent);
}

.voice-chapter-loop-control:has(input:checked) {
  color: var(--accent);
}

.voice-chapter-loop-control:has(input:disabled) {
  cursor: default;
  opacity: 0.52;
}

/* No video: the select and its transport describe nothing, so the picker shows
   only the way to 发现. */
.voice-setup.is-no-video #voice-chapter-select,
.voice-setup.is-no-video .voice-chapter-transport,
.voice-setup.is-no-video .voice-scope-note {
  display: none;
}

.voice-go-discover {
  display: block;
  width: 100%;
  min-height: 32px;
  padding: 6px 10px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  text-align: center;
}

.voice-go-discover[hidden] {
  display: none;
}

.voice-scope-note {
  margin: 7px 0 0;
  color: var(--muted);
  font-size: 11px;
}

/* 约懂 for the chosen chapter, with the words in it the learner probably does not
   have yet. Under the picker and above 练习时长, because it is the last thing
   that should change the choice of chapter — and the words are a look, not a
   list to work through, so they are the same chips the word card uses. */
.voice-scope-know {
  margin: 9px 0 0;
  font-size: var(--size-xs);
}

.voice-scope-know-note {
  margin: 7px 0 0;
  color: var(--muted);
  font-size: 11px;
}

/* 查释义 on a 生词 with no offline gloss. docs/ui-plan.md: an inline secondary
   action is 12px text on a 6px radius, the same weight as the metadata it sits
   beside — 纯 accent 文字, because this is the low-frequency third button and a
   filled one next to every wordless chip would out-shout the words themselves. */
.voice-scope-know .phrases button.voice-scope-lookup {
  margin-left: 6px;
  padding: 2px 3px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--accent);
  font-size: 12px;
  cursor: pointer;
}

.voice-scope-know .phrases button.voice-scope-lookup:disabled {
  color: var(--muted);
  cursor: default;
}

.voice-scope-lookup-error {
  display: block;
  color: var(--muted);
  font-size: 11px;
}

/* Two 72px filled cards for a two-way choice, one of which was a solid accent
   block with a 「✓ 已选择」 line inside it — the loudest thing on a screen whose
   subject is the lesson, not its length. A segment: the pressed half is lifted
   off the trough, and the 「✓」 goes, because in a segment being pressed is what
   says selected. */
.voice-profile-picker .voice-profile-option {
  display: grid;
  gap: 1px;
  align-content: center;
  flex: 1;
  min-height: 44px;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: var(--size-xs);
  transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
}

.voice-profile-picker .voice-profile-option[aria-pressed='false']:not(:disabled):hover {
  color: var(--accent);
}

.voice-profile-picker .voice-profile-option[aria-pressed='true'] {
  background: var(--bg);
  color: var(--accent);
  font-weight: 600;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--text) 10%, transparent);
}

.voice-profile-detail {
  color: var(--muted);
  font-size: var(--size-2xs);
  font-weight: 400;
}

.voice-profile-option[aria-pressed='true'] .voice-profile-detail {
  color: var(--accent);
}

.voice-profile-picker .voice-profile-option:disabled {
  cursor: default;
  opacity: 0.52;
}

.voice-profile-picker .voice-profile-option[aria-pressed='true']:disabled {
  opacity: 1;
}

.voice-target-section,
.voice-results {
  margin-top: 18px;
}

.voice-targets {
  display: grid;
  gap: 8px;
  margin: 9px 0 0;
  padding: 0;
  list-style: none;
}

.voice-target {
  position: relative;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
  transition: border-color 0.4s ease, background-color 0.4s ease;
}

.voice-target-expression {
  display: block;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}

.voice-target-meaning {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
}

/* The challenge card has three dots for noticed / cued / produced-or-better.
   `transferred` deliberately fills all three and adds the badge below instead
   of creating a fourth progress step. */
.voice-target-dots {
  display: flex;
  gap: 5px;
  margin-top: 7px;
}

.voice-target-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: transparent;
  transition: background-color 0.4s ease, border-color 0.4s ease, transform 0.4s ease;
}

.voice-target-dot[data-filled='true'] {
  border-color: var(--accent);
  background: var(--accent);
}

.voice-target-badge {
  display: inline-block;
  margin-top: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10px;
  font-weight: 600;
}

.voice-target-badge[hidden] {
  display: none;
}

/* Fires once, on the card whose mastery just went up (see
   renderTargets()/triggerUpgradeMoment() in voice-course.js). Cards that
   did not change never get this class, so they never replay it. */
.voice-target--upgraded {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.voice-target--upgraded .voice-target-dot[data-filled='true'] {
  transform: scale(1.25);
}

.voice-target-moment {
  height: 0;
  margin: 0;
  overflow: hidden;
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.voice-target-moment--visible {
  height: auto;
  margin-top: 6px;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .voice-target,
  .voice-target-dot,
  .voice-target--upgraded .voice-target-dot[data-filled='true'],
  .voice-target-moment {
    transition: none;
  }

  .voice-target--upgraded .voice-target-dot[data-filled='true'] {
    transform: none;
  }
}

/* The "back to the source clip" control. Shared by the live target cards and
   the after-lesson report — docs/PLAN.md calls timestamped source playback
   the product's one real moat, and the speaking course is its best use case. */
.voice-source {
  margin-top: 6px;
}

.voice-source-toggle {
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--accent);
  font-size: 11px;
  line-height: 1.4;
  cursor: pointer;
}

.voice-source-toggle:hover {
  border-color: var(--accent);
}

/* Collapsed by default: the source sentence can run long, and three target
   cards showing their full quote at once would dominate the panel. */
.voice-source-quote {
  margin: 6px 0 0;
  padding: 7px 9px;
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  color: var(--text);
  font-size: 12px;
  font-style: italic;
}

.voice-source-hint {
  display: block;
  margin-top: 4px;
  color: #dc2626;
  font-size: 11px;
}

.voice-transcript {
  display: grid;
  gap: 9px;
  margin-top: 18px;
}

.voice-turn {
  max-width: 90%;
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--radius-card);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  line-height: 1.65;
  overflow-wrap: anywhere;
}

.voice-turn[data-role='user'] {
  justify-self: end;
  background: var(--accent-soft);
}

.voice-turn-role {
  display: block;
  margin-bottom: 2px;
  color: var(--muted);
  font-size: 11px;
}

/* Per-turn chips. They can attach after the coach has already replied
   (evaluation is async and does not block the conversation), so they must
   stay quiet: small text, a thin colour bar, no motion competing with the
   next bubble that may already be on screen. */
.voice-turn-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}

.voice-turn-chip {
  padding: 3px 7px;
  border-left: 2px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
}

.voice-turn-chip[data-kind='correct'] {
  border-left-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
}

.voice-turn-chip[data-kind='issue'] {
  border-left-color: var(--border);
  background: var(--surface);
  color: var(--muted);
}

.voice-turn-chip[data-kind='guidance'] {
  border-left-color: #d97706;
  background: color-mix(in srgb, #d97706 12%, var(--surface));
  color: var(--text);
}

.voice-turn-chip[data-kind='pending'],
.voice-turn-chip[data-kind='neutral'],
.voice-turn-chip[data-kind='unavailable'] {
  color: var(--muted);
}

.voice-results > p {
  margin: 8px 0 0;
}

.voice-result-targets,
.voice-issues {
  display: grid;
  gap: 7px;
  margin-top: 10px;
}

.voice-result-row,
.voice-issue {
  padding: 8px 10px;
  border-left: 2px solid var(--border);
  background: var(--surface);
  font-size: 12px;
}

.voice-result-row b,
.voice-issue b {
  color: var(--text);
}

.voice-result-uses {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: 11px;
}

.voice-stats {
  margin: 6px 0 0;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

/* The learner's own sentences. They get more room than anything else in the
   report on purpose: a mastery label is our judgement of the lesson, this is
   evidence the learner can check for themselves. */
.voice-highlights {
  display: grid;
  gap: 7px;
  margin-top: 10px;
}

.voice-highlight {
  margin: 0;
  padding: 9px 10px;
  border-left: 2px solid var(--border);
  background: var(--surface);
  font-size: 13px;
}

.voice-highlight[data-independent='true'] {
  border-left-color: var(--accent);
  background: var(--accent-soft);
}

/* Not clamped. buildLessonReport() already cuts evidence at 240 characters,
   which is four or five lines here, and a half-shown sentence would defeat the
   point of quoting the learner back to themselves. */
.voice-highlight-text {
  display: block;
  color: var(--text);
}

.voice-highlight-tag {
  display: block;
  margin-top: 5px;
  color: var(--muted);
  font-size: 11px;
}

/* Same visual language as the highlight quotes above (this reuses the
   .voice-highlight block on purpose), but the "closing" row is marked with
   the accent border to draw the eye from "before" to "after". */
.voice-turns-compare .voice-highlight[data-side='closing'] {
  border-left-color: var(--accent);
  background: var(--accent-soft);
}

.voice-next {
  padding: 9px 10px;
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  color: var(--text);
}

.voice-review-note {
  margin: 10px 0 0;
  padding: 8px 10px;
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  color: var(--text);
  font-size: 12px;
}

.voice-actions {
  display: flex;
  gap: 8px;
  padding: 11px 14px;
  border-top: 1px solid var(--border);
  flex: 0 0 auto;
  flex-wrap: wrap;
  background: var(--bg);
}

.voice-primary,
.voice-secondary {
  flex: 1;
  padding: 9px 12px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  min-height: 38px;
}

.voice-primary:disabled {
  border-color: var(--border);
  background: var(--surface);
  color: var(--muted);
  cursor: default;
}

.voice-secondary {
  background: transparent;
  color: var(--accent);
}

/* 先跟我说 —— 低难度模式的三步。这一段整块放在口语样式的末尾。 */

/* One line, three stops, in the standing band under the status line. It is a
   position indicator, not a set of buttons: nothing here is pressable, so it
   carries no borders, no fills of its own, and no hover. Only the step the
   learner is in is filled, because that is the only one they need to find. */
.voice-steps {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 7px 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--size-2xs);
}

.voice-step {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  color: var(--muted);
  white-space: nowrap;
}

/* A short rule between the stops rather than a text hyphen: at 11px a 「-」 sits
   on the baseline and reads as part of the label next to it. Drawn on the item
   so the three steps stay three list items. */
.voice-step:not(:last-child)::after {
  content: '';
  width: 8px;
  border-top: 1px solid var(--border);
}

.voice-step[data-state='current'] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

/* Done steps step back rather than light up: a second filled pill would make
   the current one harder to find, which is the one thing this row is for. */
.voice-step[data-state='done'] {
  color: var(--muted);
}

.voice-step-check {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The line the learner is being asked to repeat, inside the coach's bubble.
   The whole bubble cannot carry it: the coach also greets, explains and asks
   around it, and 「跟着我说这一句」 is only true of one sentence. Accent-soft
   and bold, the same pairing the learner's own bubble and the current step
   already use for "this is the thing right now". */
.voice-turn-model {
  display: block;
  margin-top: 6px;
  padding: 7px 10px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 600;
  quotes: none;
}

/* In the sticky header now, so it stays with the disabled 词级 button it explains
   rather than scrolling away with the first few subtitles. */
.vocab-note {
  margin: 6px 0 0;
  padding: 6px 10px;
  border-left: 2px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--surface);
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
}

/* Left-aligned, no dashed box: a centred sentence in a narrow column reads as an
   error notice rather than an invitation.

   The whole dead end is drawn in the flow where the subtitles would have been —
   it is the only content this screen has left, so there is nothing for a dialog
   to sit on top of, and nothing behind it worth going back to. */
.subs-deadend {
  margin: 0;
  padding: 15px 14px 20px;
}

.deadend-title {
  margin: 0 0 5px;
  font-size: var(--size-sm);
}

.deadend-detail {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.6;
}

/* The apology's own line, and after it is sent, the acknowledgement in its place.
   Both are the same paragraph, so the block does not change height on send. */
.deadend-report {
  margin: 0 0 18px;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.6;
}

/* A text link, not a button. It was the only thing on this screen with a button's
   outline, which put it above the three rows underneath — and those rows are what
   gets the learner out of the dead end. Reporting is the favour they do us on the
   way past. The position does not move: right after the apology is where someone
   who has just been stopped wants to say so. */
.deadend-report-send {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

.deadend-report-send:hover {
  border: 0;
  text-decoration: underline;
}

.deadend-report-send:disabled {
  color: var(--muted);
  cursor: default;
  text-decoration: none;
}

.deadend-next {
  display: grid;
  gap: 2px;
}

.deadend-label {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--size-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
}

/* The shelf rows are 发现's own, minus the feed's padding and rules: here they sit
   inside a paragraph of explanation, not in a scrolling list of their own. */
.subs-deadend .discovery-row {
  padding: 6px 0;
  border-bottom: 0;
}

.subs-deadend .discovery-row:hover {
  background: none;
}

.deadend-more {
  margin-top: 14px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

.cue-skeleton {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 9px 0;
  list-style: none;
}

.cue-skeleton span {
  height: 12px;
  border-radius: 4px;
  background: var(--surface);
}

.cue-skeleton span:last-child {
  height: 11px;
}

.subs-message {
  margin: 0;
  padding: 24px 16px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  text-align: center;
}

/* ------------------------------------------------------------ subtitles
 *
 * These rules belong to the watch view. Keep them beside its empty and loading
 * states: dropping this block makes the ordered list, caption paragraphs and
 * star buttons fall back to browser defaults even though subtitle data loaded.
 */

.subs {
  margin: 0;
  padding: 0;
  list-style: none;
}

.cue {
  position: relative;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}

.cue + .cue {
  margin-top: 6px;
}

.cue:hover {
  background: var(--surface);
}

/* The block gets neither: the fill on the sentence being spoken is the one place
   the eye should land. It used to get an outline as well, which made two — a blue
   rectangle around the paragraph and a blue panel inside it, competing for the
   same glance. The border stays in the box model so nothing shifts on activation. */
.cue.is-active {
  border-color: transparent;
}

.cue.is-active .part.is-now .cue-text {
  font-weight: 600;
}

.cue-speaker {
  display: block;
  margin-bottom: 2px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* One sentence: the unit you track by ear. The block around it is the unit you
   read. Keeping them separate is what stops the eye having to search. */
.part {
  padding: 3px 6px;
  margin: 0 -6px;
  border-radius: 6px;
}

.part + .part {
  margin-top: 8px;
}

.cue.is-active .part.is-now {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}

.cue-text {
  margin: 0;
  line-height: 1.7;
  overflow-wrap: anywhere;
}

.cue-zh {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.65;
  overflow-wrap: anywhere;
}

.cue.is-active .part.is-now .cue-zh {
  color: var(--text);
}

.zh-hit {
  padding: 0 2px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: inherit;
  font-weight: 600;
}

.phrases {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

.phrases li {
  display: inline-block;
  margin: 3px 6px 0 0;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 12px;
}

.phrases b {
  color: var(--text);
}

/* Two groups, not three evenly spaced things: the timestamp is what this line is,
   and 「→ 痛点」 plus the star are one thought — the action and the receipt it just
   produced. Spread by `space-between`, the receipt floated alone in the middle of
   the row and the star sat marooned at the far edge with air on both sides.
 *
 * Positioned, because the paragraph picker is moved in here when a saved line
 * asks where it should go.
 */
.cue-foot {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.cue-foot .cue-time {
  margin-right: auto;
}

/* The timestamp has always been the way back into the video at this line, and
   has always looked like metadata. It is a button now, with the player's own
   triangle — but only on the sentence being spoken and on hover: five rows with
   five triangles is five invitations, which is none. */
.cue-time {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.cue-time svg {
  width: 10px;
  height: 10px;
  flex: 0 0 auto;
  fill: currentColor;
  visibility: hidden;
}

.cue.is-active .cue-time { color: var(--accent); }
.cue.is-active .cue-time svg,
.cue:hover .cue-time svg,
.cue-time:focus-visible svg { visibility: visible; }
.cue-time:hover { color: var(--accent); }

/* A bare star, always visible. The same control appears on the word card, so
   saving a word and saving a sentence look and cost the same. */
/* Sized to the row it sits in. At 26px with a 18px glyph it read as a block of
   its own beside 11px metadata — the one control on the line, but not the
   subject of it. */
.cue-save,
.card-save {
  display: inline-grid;
  width: 22px;
  height: 22px;
  place-items: center;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.star-icon {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.65;
}

.cue-save:hover,
.card-save:hover,
.cue-save[data-saved='true'],
.card-save[data-saved='true'] {
  color: var(--accent);
}

/* Exact-sentence speaking is offered only on the sentence currently following
   the video. The toolbar speech icon keeps its chapter-level meaning; this
   labelled action makes the smaller scope explicit without repeating itself on
   every subtitle row. */
.cue-practice-sentence {
  display: none;
  align-items: center;
  min-height: 28px;
  padding: 3px 9px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
  font-weight: 600;
}

.cue.is-active .cue-practice-sentence {
  display: inline-flex;
}

.cue-practice-sentence:hover {
  background: var(--accent-soft);
}

.card-save {
  margin-left: auto;
}

/* Every lexical word can be looked up, but ordinary words remain visually quiet.
   Only a difficulty recommendation receives the bordered tier treatment below. */
.word {
  border-bottom: 1px solid transparent;
  color: inherit;
  cursor: pointer;
}

.word:not([data-tier]):hover,
.word:not([data-tier]):focus-visible {
  border-bottom-color: var(--accent);
  color: var(--accent);
  outline: none;
}

/* A word the learner has collected, in whatever form it turns up here. Drawn
   under the word rather than around it so it can sit on a difficulty box without
   the two marks fighting: the box says Vila thinks this is hard, the dashed line
   says you kept this one. */
.word.is-saved {
  text-decoration-line: underline;
  text-decoration-style: dashed;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

.word[data-tier] {
  --tier: #2563eb;
  padding: 1px 4px;
  border: 1px solid var(--tier);
  border-radius: 5px;
  background: color-mix(in srgb, var(--tier) 10%, transparent);
  color: var(--tier);
  font-weight: 600;
}

.word[data-tier='1'], .tier-swatch[data-tier='1'], .tier-chip[data-tier='1'], .discovery-row-signal[data-tier='1'] { --tier: #16a34a; }
.word[data-tier='2'], .tier-swatch[data-tier='2'], .tier-chip[data-tier='2'], .discovery-row-signal[data-tier='2'] { --tier: #0891b2; }
.word[data-tier='3'], .tier-swatch[data-tier='3'], .tier-chip[data-tier='3'], .discovery-row-signal[data-tier='3'] { --tier: #7c3aed; }
.word[data-tier='4'], .tier-swatch[data-tier='4'], .tier-chip[data-tier='4'], .discovery-row-signal[data-tier='4'] { --tier: #d97706; }
.word[data-tier='5'], .tier-swatch[data-tier='5'], .tier-chip[data-tier='5'], .discovery-row-signal[data-tier='5'] { --tier: #dc2626; }

[data-lang='fr'] .word[data-tier='3'], [data-lang='fr'] .tier-swatch[data-tier='3'], [data-lang='fr'] .tier-chip[data-tier='3'], [data-lang='fr'] .discovery-row-signal[data-tier='3'] { --tier: #2563eb; }
[data-lang='fr'] .word[data-tier='4'], [data-lang='fr'] .tier-swatch[data-tier='4'], [data-lang='fr'] .tier-chip[data-tier='4'], [data-lang='fr'] .discovery-row-signal[data-tier='4'] { --tier: #7c3aed; }
[data-lang='fr'] .word[data-tier='5'], [data-lang='fr'] .tier-swatch[data-tier='5'], [data-lang='fr'] .tier-chip[data-tier='5'], [data-lang='fr'] .discovery-row-signal[data-tier='5'] { --tier: #d97706; }
[data-lang='fr'] .word[data-tier='6'], [data-lang='fr'] .tier-swatch[data-tier='6'], [data-lang='fr'] .tier-chip[data-tier='6'], [data-lang='fr'] .discovery-row-signal[data-tier='6'] { --tier: #dc2626; }

[data-lang='zh-Hans'] .word[data-tier='1'], [data-lang='zh-Hans'] .tier-swatch[data-tier='1'], [data-lang='zh-Hans'] .tier-chip[data-tier='1'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='1'] { --tier: #16a34a; }
[data-lang='zh-Hans'] .word[data-tier='2'], [data-lang='zh-Hans'] .tier-swatch[data-tier='2'], [data-lang='zh-Hans'] .tier-chip[data-tier='2'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='2'] { --tier: #0d9488; }
[data-lang='zh-Hans'] .word[data-tier='3'], [data-lang='zh-Hans'] .tier-swatch[data-tier='3'], [data-lang='zh-Hans'] .tier-chip[data-tier='3'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='3'] { --tier: #2563eb; }
[data-lang='zh-Hans'] .word[data-tier='4'], [data-lang='zh-Hans'] .tier-swatch[data-tier='4'], [data-lang='zh-Hans'] .tier-chip[data-tier='4'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='4'] { --tier: #7c3aed; }
[data-lang='zh-Hans'] .word[data-tier='5'], [data-lang='zh-Hans'] .tier-swatch[data-tier='5'], [data-lang='zh-Hans'] .tier-chip[data-tier='5'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='5'] { --tier: #c2410c; }
[data-lang='zh-Hans'] .word[data-tier='6'], [data-lang='zh-Hans'] .tier-swatch[data-tier='6'], [data-lang='zh-Hans'] .tier-chip[data-tier='6'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='6'] { --tier: #dc2626; }
[data-lang='zh-Hans'] .word[data-tier='7'], [data-lang='zh-Hans'] .tier-swatch[data-tier='7'], [data-lang='zh-Hans'] .tier-chip[data-tier='7'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='7'] { --tier: #be123c; }
[data-lang='zh-Hans'] .word[data-tier='8'], [data-lang='zh-Hans'] .tier-swatch[data-tier='8'], [data-lang='zh-Hans'] .tier-chip[data-tier='8'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='8'] { --tier: #64748b; }

@media (prefers-color-scheme: dark) {
  .word[data-tier='1'], .tier-swatch[data-tier='1'], .tier-chip[data-tier='1'], .discovery-row-signal[data-tier='1'] { --tier: #4ade80; }
  .word[data-tier='2'], .tier-swatch[data-tier='2'], .tier-chip[data-tier='2'], .discovery-row-signal[data-tier='2'] { --tier: #22d3ee; }
  .word[data-tier='3'], .tier-swatch[data-tier='3'], .tier-chip[data-tier='3'], .discovery-row-signal[data-tier='3'] { --tier: #c084fc; }
  .word[data-tier='4'], .tier-swatch[data-tier='4'], .tier-chip[data-tier='4'], .discovery-row-signal[data-tier='4'] { --tier: #fbbf24; }
  .word[data-tier='5'], .tier-swatch[data-tier='5'], .tier-chip[data-tier='5'], .discovery-row-signal[data-tier='5'] { --tier: #f87171; }

  [data-lang='fr'] .word[data-tier='3'], [data-lang='fr'] .tier-swatch[data-tier='3'], [data-lang='fr'] .tier-chip[data-tier='3'], [data-lang='fr'] .discovery-row-signal[data-tier='3'] { --tier: #6ea8ff; }
  [data-lang='fr'] .word[data-tier='4'], [data-lang='fr'] .tier-swatch[data-tier='4'], [data-lang='fr'] .tier-chip[data-tier='4'], [data-lang='fr'] .discovery-row-signal[data-tier='4'] { --tier: #c084fc; }
  [data-lang='fr'] .word[data-tier='5'], [data-lang='fr'] .tier-swatch[data-tier='5'], [data-lang='fr'] .tier-chip[data-tier='5'], [data-lang='fr'] .discovery-row-signal[data-tier='5'] { --tier: #fbbf24; }
  [data-lang='fr'] .word[data-tier='6'], [data-lang='fr'] .tier-swatch[data-tier='6'], [data-lang='fr'] .tier-chip[data-tier='6'], [data-lang='fr'] .discovery-row-signal[data-tier='6'] { --tier: #f87171; }

  [data-lang='zh-Hans'] .word[data-tier='1'], [data-lang='zh-Hans'] .tier-swatch[data-tier='1'], [data-lang='zh-Hans'] .tier-chip[data-tier='1'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='1'] { --tier: #4ade80; }
  [data-lang='zh-Hans'] .word[data-tier='2'], [data-lang='zh-Hans'] .tier-swatch[data-tier='2'], [data-lang='zh-Hans'] .tier-chip[data-tier='2'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='2'] { --tier: #2dd4bf; }
  [data-lang='zh-Hans'] .word[data-tier='3'], [data-lang='zh-Hans'] .tier-swatch[data-tier='3'], [data-lang='zh-Hans'] .tier-chip[data-tier='3'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='3'] { --tier: #6ea8ff; }
  [data-lang='zh-Hans'] .word[data-tier='4'], [data-lang='zh-Hans'] .tier-swatch[data-tier='4'], [data-lang='zh-Hans'] .tier-chip[data-tier='4'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='4'] { --tier: #c084fc; }
  [data-lang='zh-Hans'] .word[data-tier='5'], [data-lang='zh-Hans'] .tier-swatch[data-tier='5'], [data-lang='zh-Hans'] .tier-chip[data-tier='5'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='5'] { --tier: #fb923c; }
  [data-lang='zh-Hans'] .word[data-tier='6'], [data-lang='zh-Hans'] .tier-swatch[data-tier='6'], [data-lang='zh-Hans'] .tier-chip[data-tier='6'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='6'] { --tier: #f87171; }
  [data-lang='zh-Hans'] .word[data-tier='7'], [data-lang='zh-Hans'] .tier-swatch[data-tier='7'], [data-lang='zh-Hans'] .tier-chip[data-tier='7'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='7'] { --tier: #fb7185; }
  [data-lang='zh-Hans'] .word[data-tier='8'], [data-lang='zh-Hans'] .tier-swatch[data-tier='8'], [data-lang='zh-Hans'] .tier-chip[data-tier='8'], [data-lang='zh-Hans'] .discovery-row-signal[data-tier='8'] { --tier: #94a3b8; }
}

/* Dimmed against accent-soft, not erased. Clearing it left the marked words in
   the sentence being spoken with nothing but a hairline border on a tinted
   ground — least legible exactly where legibility matters most. */
.cue.is-active .part.is-now .word[data-tier] {
  background: color-mix(in srgb, var(--tier) 6%, transparent);
}

/* ------------------------------------------------------- 词句 and 文稿 lists
 *
 * goal-list.js and wordbook.js came from desk.html and kept their markup when
 * they became the panel's 文稿 and 词句 tabs — but their stylesheet did not come
 * with them, so both rendered as unstyled markup in the panel for as long as the
 * desk was still around to make it look fine somewhere else. These are
 * desk.css's rules for them.
 */

/* --------------------------------------------------------------------- goals */

.goal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 9px 14px;
}

.chevron {
  padding: 0;
  width: 18px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
}

.chevron:hover {
  border: 0;
  color: var(--text);
}

/* The way into the document, so it is accent text — not a box. It declared only
   its layout, which left `:where(button)` in ui/base.css supplying padding, a
   border, a radius and a surface fill: the document's name was drawn as a chunky
   grey field and read as a text input you could type in. docs/ui-plan.md: accent
   colour means you can press it, and inline actions must not look like blocks. */
.goal-name {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text);
  overflow: hidden;
  font-size: var(--size-body);
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goal-name:hover {
  border: 0;
  color: var(--accent);
}

.gbar {
  display: flex;
  gap: 3px;
  flex: 0 0 auto;
}

.gbar i {
  display: block;
  width: 14px;
  height: 3px;
  border-radius: 2px;
  background: var(--border);
}

.gbar i.on {
  background: var(--accent);
}

.gbar i.skip {
  background: transparent;
  border: 1px dashed var(--border);
}

.goal-status {
  color: var(--muted);
  font-size: var(--size-xs);
  white-space: nowrap;
}

.goal-tally {
  color: var(--muted);
  font-size: var(--size-xs);
  white-space: nowrap;
}

.gsections {
  border-top: 1px solid var(--border);
  padding: 5px 0 8px;
  background: color-mix(in srgb, var(--surface) 52%, var(--bg));
}

/* A `<button>` now, so it has to shed `:where(button)`'s box: these are rows in
   a list, not controls in a toolbar. */
.gsection {
  position: relative;
  display: flex;
  align-items: flex-start;
  width: 100%;
  min-height: 38px;
  padding: 7px 13px 7px 48px;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}

.gsection:hover {
  border: 0;
  background: var(--surface);
}

.gsection:hover .gsection-title {
  color: var(--accent);
}

.gsection::before {
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: 31px;
  width: 2px;
  border-radius: 2px;
  background: transparent;
  content: '';
}

.gsection.has-content::before {
  background: var(--accent);
}

.gsection.has-content .gsection-title {
  font-weight: 500;
}

.gsection.has-content .gsection-count {
  color: var(--accent);
}

.gsection.is-skipped::before {
  background: var(--border-strong, var(--border));
}

.gsection-body {
  flex: 1;
  min-width: 0;
}

.gsection-title {
  font-size: var(--size-sm);
}

.gsection-hint {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.5;
}

.gsection-count {
  color: var(--muted);
  font-size: var(--size-xs);
  white-space: nowrap;
}

.goal-new {
  padding: 8px 0;
  border: 0;
  border-radius: 0;
  margin: 0 14px 20px;
}

.goal-new-title {
  margin: 0 0 10px;
  font-size: 17px;
  font-weight: 600;
}

.goal-new-label {
  display: block;
  margin: 12px 0 6px;
  font-size: var(--size-xs);
  font-weight: 600;
}

.goal-new-hint {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: var(--size-sm);
  line-height: 1.6;
}

.template-grid {
  display: grid;
  gap: 10px;
  margin-bottom: 10px;
}

.template {
  display: block;
  padding: 14px;
  border-radius: var(--radius-card);
  text-align: left;
  background: var(--bg);
}

.template[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.template-name {
  display: block;
  font-size: var(--size-sm);
  color: var(--accent);
  font-weight: 600;
}

.template-sections {
  display: block;
  margin-top: 5px;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.6;
}

.goal-new input {
  width: 100%;
}

.goal-new-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.collection-sort {
  margin-left: auto;
}

.collection-sort select {
  width: auto;
  min-width: 108px;
  border-color: transparent;
  background: transparent;
  color: var(--muted);
}

.collection-summary {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: var(--size-xs);
}

/* The head line of a saved row: the word, whatever qualifies it, and when it was
   saved pushed to the far end. The rule was lost when the word book moved out of
   words/words.js into the panel, and nothing replaced it — so the line was a
   plain block, `margin-left: auto` on the time had nothing to push against, and
   every row read as 「runbook今天 15:43」. */
#list .row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
}

/* What qualifies the row, beside the thing it qualifies: the pronunciation, the
   part of speech, and — on a saved sentence — 收藏片段 or the speaker's name. All
   of them quieter than the word or the sentence they sit with. */
#list .ipa,
#list .pos {
  color: var(--muted);
  font-size: var(--size-xs);
}

/* The translation, under the line it translates. */
#list .zh {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: var(--size-sm);
  line-height: 1.6;
}

#list .saved-time {
  margin-left: auto;
  color: var(--muted);
  font-size: var(--size-2xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#list .lemma {
  color: var(--muted);
  font-size: var(--size-xs);
}

#list .word-sense + .word-sense {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

#list .phrase {
  display: inline-block;
  margin-top: 6px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: var(--size-xs);
}

#list .word-example,
#list .source {
  margin-top: 10px;
  padding-left: 12px;
  border-left: 2px solid var(--border);
  color: var(--muted);
  font-size: var(--size-xs);
}

#list .word-example p,
#list .source p {
  margin: 2px 0 0;
  line-height: 1.6;
}

#list .context-label {
  color: var(--muted);
  font-size: var(--size-2xs);
}

#list .example-zh {
  color: var(--muted);
}

#list .related-words,
#list .mnemonic {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: var(--size-xs);
}

#list .related-label,
#list .mnemonic .context-label {
  margin-right: 7px;
  color: var(--text);
  font-weight: 600;
}

#list .word-grammar,
#list .word-more {
  margin-top: 10px;
  color: var(--muted);
  font-size: var(--size-xs);
}

#list .word-grammar summary,
#list .word-more summary {
  width: fit-content;
  color: var(--accent);
  cursor: pointer;
}

/* Inside the disclosure the first block has the summary directly above it, so
   its own top margin would double the gap the summary already leaves. */
#list .word-more > :nth-child(2) {
  margin-top: 6px;
}

#list .word-forms,
#list .conjugation-rows {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 4px 14px;
  margin: 9px 0 0;
}

#list .word-forms dt,
#list .word-forms dd,
#list .conjugation-rows dt,
#list .conjugation-rows dd {
  margin: 0;
}

#list .word-forms dd,
#list .conjugation-rows dd {
  color: var(--text);
}

#list .conjugation-group h4 {
  margin: 12px 0 0;
  color: var(--text);
  font-size: var(--size-xs);
}

#list .source mark {
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: inherit;
}

#list .actions {
  display: flex;
  align-items: center;
  gap: 8px 14px;
  margin-top: 12px;
  flex-wrap: wrap;
}

#list .actions a,
#list .actions button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-size: var(--size-sm);
}

#list .actions button:hover {
  border: 0;
  text-decoration: underline;
}

#list .actions .sentence-practice {
  min-height: 28px;
  padding: 3px 9px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--bg);
  color: var(--accent);
  font-size: var(--size-xs);
  font-weight: 600;
}

#list .actions .sentence-practice:hover {
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  text-decoration: none;
}

/* Where a saved sentence went, and the way to move it. Sized like the links
   beside it rather than like a form field: it is an inline action on a row, and a
   full-height select would read as the row's main event. */
#list .actions .goal-place {
  max-width: 46%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-sm);
  text-overflow: ellipsis;
}

#list .actions .goal-place.is-filed { color: var(--accent); }

#list .actions .goal-place:hover { color: var(--accent); }

#list .actions .remove-action {
  margin-left: auto;
  color: var(--muted);
}

#list .actions .remove-action[data-confirm='true'] {
  color: var(--bad);
}

.empty-action {
  margin-top: 15px;
  border-color: var(--accent);
  color: var(--accent);
}

/* ------------------------------------------------------------------ 稿子
 *
 * Picking, not typing. Every control here is something to tap: a sentence to
 * borrow, a slot to fill, a word you already collected. The only text input is
 * the width of a noun phrase, because that is the unit of writing this screen
 * asks for.
 */

#view-draft {
  padding-bottom: 28px;
  overflow-y: auto;
}

.draft-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 16px 14px;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}

/* The document's name is also the way to any other one. There is no parent
   screen to go back to any more, so the header holds a picker instead of a back
   button. */
.draft-name {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  max-width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-weight: 600;
}

.draft-name:hover {
  border: 0;
  color: var(--accent);
}

.draft-name-caret {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--size-2xs);
}

/* The state of the document, on the line that names it. It had a card of its own
   headed 「你的稿子」, which was a heading introducing an empty box while this row
   held a name and 250px of nothing. */
.draft-head-meta {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--muted);
  font-size: var(--size-2xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.draft-switch {
  padding: 4px 8px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.draft-switch-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 7px 6px;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  text-align: left;
}

.draft-switch-row:hover {
  border: 0;
  background: var(--bg);
}

.draft-switch-row.is-new { color: var(--accent); }

.draft-switch-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.draft-switch-meta {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--size-2xs);
}

/* The draft so far, as continuous prose. No card and no heading: it is not a
   section of the screen, it is what the screen is about.
 *
 * Shut it is two lines; open it is the whole thing. It used to be a 9.5em box
 * that scrolled inside a panel that also scrolls, so a 400px column had two
 * scrollbars for one gesture — and it spent that height whether or not anyone
 * was reading it. */
.draft-preview {
  padding: 11px 14px 12px;
  border-bottom: 1px solid var(--border);
  font-size: var(--size-sm);
  line-height: 1.75;
  background: var(--surface);
  overflow-wrap: anywhere;
}

.draft-preview p { margin: 0; }
.draft-preview p + p { margin-top: 6px; }

.draft-preview[data-open='false'] .draft-preview-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.draft-preview-more {
  margin-top: 6px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  min-height: 28px;
}

.draft-preview-more:hover {
  border: 0;
  text-decoration: underline;
}

.draft-para {
  padding: 18px 14px;
  border-bottom: 1px solid var(--border);
}

/* What this paragraph is for, in the template's own words. The list you leave to
   get here was the only screen that showed it. */
.draft-para-hint {
  margin: -3px 0 8px;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.6;
}

.draft-para-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 7px;
}

/* The anchor of the block, so it is the strongest thing in it. It was grey while
   the action under it was accent, which inverted the two: the paragraph name is
   what the screen is scanned by, and the action is available once you have found
   the paragraph. */
.draft-para-title {
  color: var(--text);
  font-size: var(--size-body);
  font-weight: 500;
}

/* Whether this paragraph is done — the question the screen is scanned for. It
   reported how much material was filed under the section, which is a number
   that matters while picking a sentence, and which now rides on the control
   that offers them. */
.draft-para-state {
  color: var(--muted);
  font-size: var(--size-2xs);
}

.draft-para-state.is-done {
  color: var(--accent);
}

/* Your text, and a sentence you could borrow, were the same 1px-bordered white
   box. Two things that read as one kind, stacked in one column, where the whole
   act of this screen is telling them apart. Written text gets a rule down its
   left edge and no box: it is the paragraph, not an offer in it. */
.draft-piece {
  margin-bottom: 8px;
  padding: 2px 0 2px 12px;
  border-left: 2px solid var(--accent);
  overflow-wrap: anywhere;
}

.draft-piece-text,
.draft-piece-free {
  font-size: var(--size-body);
  line-height: 1.8;
}

.draft-piece-free:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }

.draft-piece-free:empty::before {
  color: var(--muted);
  content: attr(data-placeholder);
}

.draft-piece-tools {
  display: flex;
  gap: 12px;
  margin-top: 4px;
  flex-wrap: wrap;
}

.draft-piece-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  min-height: 28px;
}

.draft-piece-link:hover { color: var(--accent); }

/* A slot is a control, so it looks like one whether or not it has been filled —
   an unfilled slot showing its hint and a filled one showing a value are the same
   affordance and must not become a label the moment they are answered.

   The one difference the two states do carry is ownership: an untouched slot
   still wears the words it borrowed, so it is muted and dashed. Filled, it is
   your text, in text colour, underlined solid. */
.draft-slot {
  padding: 1px 7px;
  border: 0;
  border-bottom: 1.5px dashed var(--accent);
  border-radius: 5px;
  background: var(--accent-soft, rgba(37, 99, 235, 0.1));
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: inherit;
}

.draft-slot:hover { color: var(--accent); }

.draft-slot.is-filled {
  border-bottom-style: solid;
  color: var(--text);
}

.draft-slot-edit { display: inline-flex; flex-direction: column; }

.draft-slot-input {
  padding: 1px 6px;
  border: 1px solid var(--accent);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: inherit;
}

.draft-slot-input:focus { outline: none; }

/* Words the learner already saved. Most slot values are one of them, which is the
   whole point — the vocabulary being studied is the vocabulary being used. */
.draft-slot-words {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 5px;
}

.draft-slot-word {
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}

.draft-slot-word:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.draft-picks {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* The offer. What transfers is the way of saying it, but a sentence with its
   content deleted is read as a broken sentence rather than as a shape — so the
   words stay and the replaceable ones are marked instead. */
/* Dashed and tinted: on offer, not yet yours. Solid-bordered it was the same
   shape as the writing above it. */
.draft-pick {
  display: flex;
  flex-direction: column;
  padding: 8px 10px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  cursor: pointer;
  font: inherit;
  gap: 3px;
  text-align: left;
  transition: border-color var(--dur), background var(--dur);
}

.draft-pick:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* What is on offer here, named once above the offers rather than counted in the
   paragraph's title line. */
.draft-picks-label {
  margin: 0 0 2px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.draft-pick-shape {
  font-size: var(--size-sm);
  line-height: 1.6;
}

.draft-pick-slot {
  border-bottom: 1.5px dashed var(--accent);
  color: var(--accent);
}

/* What the sentence lets you say. At the moment of picking that is the whole
   question; which talk it came from is a question for 原片, afterwards. */
.draft-pick-gloss,
.draft-pick-meta {
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.6;
}

.draft-pick-whole {
  align-self: flex-start;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.draft-pick-more,
.draft-pick-own,
.draft-pick-empty {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
  text-align: left;
}

.draft-pick-empty {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Quiet at rest, accent on the way in. docs/ui-plan.md: 色相表达含义，面积和饱和度
   表达优先级 — five paragraphs each offering the same thing put five accent links
   down one column, and a screen where everything is emphasised has no emphasis
   left to spend. None of these five is more primary than the others. */
.draft-pick-empty-action {
  color: var(--text);
  font-size: var(--size-xs);
}

.draft-pick-empty:hover .draft-pick-empty-action,
.draft-pick-empty:focus-visible .draft-pick-empty-action {
  color: var(--accent);
}

/* The section's own `lookFor`: what to listen for. A section name cannot say it,
   and this is the line that makes the trip to 发现 worth taking. */
.draft-pick-empty-hint {
  color: var(--muted);
  line-height: 1.6;
}

.draft-pick-more:hover,
.draft-pick-own:hover { color: var(--accent); }

.draft-foot {
  display: flex;
  gap: 16px;
  padding: 13px 14px;
  flex-wrap: wrap;
}

.draft-foot-action {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

.draft-foot-action:disabled {
  color: var(--muted);
  cursor: default;
}

/* AI's one pass. Sits at the bottom of the draft it just read, and closes — it is
   a reader's note, not a mode. No number: a score out of a hundred invites
   chasing the score, and what is being judged is phrasing. */
.review-panel {
  margin: 0 14px 16px;
  padding: 12px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.review-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 9px;
}

.review-panel-title { font-size: var(--size-xs); }

.review-panel-close {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}

.review-panel-note {
  margin: 0;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.review-block { margin-bottom: 11px; }

.review-block-label {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.review-line {
  margin: 0 0 3px;
  font-size: var(--size-2xs);
  line-height: 1.6;
}

.review-note {
  padding-top: 9px;
  border-top: 1px solid var(--border);
}

.review-note-where {
  margin: 0 0 2px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.review-note-why {
  margin: 0 0 5px;
  font-size: var(--size-2xs);
  line-height: 1.6;
}

.review-note-text {
  margin: 0 0 5px;
  padding-left: 9px;
  border-left: 2px solid var(--accent);
  font-size: var(--size-xs);
  line-height: 1.6;
}

.review-note-take {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}


/* ---------------------------------------------------------------- welcome
 *
 * First run, in the panel. Two questions and a button — the same weight as the
 * rest of the panel, not a marketing page: this is the product starting, not an
 * advertisement for it.
 */

#view-welcome {
  padding: 20px 16px 28px;
  overflow-y: auto;
}

.welcome-head { margin-bottom: 22px; }

.welcome-title {
  margin: 0 0 5px;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.35;
}

.welcome-lead {
  margin: 0;
  color: var(--muted);
  font-size: var(--size-xs);
}

.welcome-account {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 10px;
  margin: 0 0 22px;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.welcome-account:hover { border-color: var(--accent); }

.welcome-account:disabled {
  cursor: default;
  opacity: .65;
}

.welcome-account-avatar {
  display: grid;
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  place-items: center;
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--size-xs);
  font-weight: 600;
}

.welcome-account-body {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: 2px;
}

.welcome-account-body strong {
  font-size: var(--size-xs);
  font-weight: 600;
}

.welcome-account-body small {
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.45;
}

.welcome-account-arrow {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: var(--size-2xs);
  font-weight: 600;
}

.welcome-group { margin-bottom: 20px; }

.welcome-label {
  margin: 0 0 8px;
  font-size: var(--size-xs);
  font-weight: 500;
}

.welcome-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.welcome-choice {
  padding: 7px 13px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  transition: background var(--dur), border-color var(--dur), color var(--dur);
}

.welcome-choice:hover { border-color: var(--accent); }

.welcome-choice[aria-pressed='true'] {
  border-color: var(--text);
  background: var(--text);
  color: var(--bg);
}

.welcome-hint {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.6;
}

.welcome-conflict {
  margin: 0 0 14px;
  color: var(--danger, #c0392b);
  font-size: var(--size-2xs);
}

.welcome-start {
  width: 100%;
  padding: 11px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  font: inherit;
  font-size: var(--size-body);
}

.welcome-start:disabled {
  background: var(--surface);
  color: var(--muted);
  cursor: default;
}

.welcome-foot {
  margin: 12px 0 0;
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.6;
  text-align: center;
}

/* -------------------------------------------------------------- discovery
 *
 * One step up the type scale from the rest of the panel, on purpose. Everywhere
 * else the text annotates a video that is already on screen; here the text *is*
 * the screen — a list of titles is the thing being read, and 12px was asking
 * someone to choose between 295 talks in caption type.
 *
 * The panel with no video. One job: get you into one. Rules between groups, no
 * cards — and exactly one accent, on review, because it is the only thing here
 * with a deadline.
 */

#view-discover { padding-bottom: 32px; }

.discovery-stats {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: var(--size-xs);
  flex-wrap: wrap;
  background: var(--surface);
}

.discovery-stats-left { color: var(--muted); }

/* The action leads the row, so it is the thing that carries weight. The count
   behind it is context and stays quiet. */
.discovery-stats-go { font-weight: 600; }

.discovery-stats-go {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  white-space: nowrap;
}

.discovery-section {
  padding: 16px 14px;
  border-bottom: 1px solid var(--border);
}

.discovery-section:last-child { border-bottom: 0; }

.discovery-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 7px;
  color: var(--text);
  font-size: var(--size-sm);
  gap: 8px;
  font-weight: 600;
}

/* 接着上次. A card rather than a row: it is the strongest commitment on the
   screen — already chosen, already part-watched — and it sat lighter than the
   suggestions below it, which are talks nobody has seen yet. */
.resume-card {
  display: flex;
  width: 100%;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: inherit;
  cursor: pointer;
  font: inherit;
  text-align: left;
  overflow-wrap: anywhere;
  transition: border-color var(--dur);
}

.resume-body {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: 6px;
}

.resume-card:hover { border-color: var(--accent); }
.resume-card:hover .resume-title { color: var(--accent); }

.resume-title {
  font-size: var(--size-body);
  line-height: 1.5;
  font-weight: 600;
  transition: color var(--dur);
}

.resume-native { color: var(--muted); font-size: var(--size-sm); line-height: 1.5; }

/* What a line of text cannot say at a glance: how much of it is left. */
.resume-progress {
  height: 3px;
  margin-top: 2px;
  overflow: hidden;
  border-radius: 999px;
  background: color-mix(in srgb, var(--muted) 24%, transparent);
}

.resume-progress i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
}

.resume-meta { color: var(--muted); font-size: var(--size-xs); line-height: 1.5; }

.discovery-lead {
  margin: 0 0 10px;
  color: var(--muted);
  font-size: var(--size-xs);
  line-height: 1.6;
}

/* 我的词汇水平, asked on the shelf because that is where answering it changes
   something. A row of the shelf rather than a settings panel dropped into one:
   the sentence is the same muted lead the definition line is, and the control is
   the panel's own select from ui/base.css, inheriting this line's size instead of
   bringing its own.

   It wraps rather than shrinks. At 320px the sentence takes the whole line and
   the select drops below it — a select squeezed into whatever is left over is one
   whose current value cannot be read, which is the only thing it has to show. */
.discovery-level-ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  margin: 0;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: var(--size-xs);
}

.discovery-level-ask .discovery-lead { flex: 1 1 20em; min-width: 0; margin: 0; }
.discovery-level-ask .discovery-lead { color: var(--muted); }

/* The definition line's ? bubble, hung from the paragraph rather than from the
   icon, so it spans the text column wherever the copy wraps. In English the
   icon lands at the right edge, and a bubble opening rightwards from there
   widened the view — hidden, it still counts toward the scroll width, so 发现
   scrolled sideways. */
/* Off the filter bar's divider, which it otherwise sits flush against. */
.discovery-lead:has(> .help-wrap) { position: relative; margin-top: 10px; }
.discovery-lead > .help-wrap { position: static; }
.discovery-lead > .help-wrap .help-bubble {
  right: 14px;
  left: 14px;
  width: auto;
  max-width: none;
}

/* The same quiet pill as 难度 on the row above: one question on one line, not a
   form field sitting under the categories. */
.discovery-level-ask select {
  flex: 0 1 auto;
  field-sizing: content;
  max-width: 100%;
  padding: 6px 24px 6px 12px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  appearance: none;
  background: var(--bg)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat right 10px center / 9px 6px;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

/* A feed row: the video's own frame, then its words. The frame is fetched from
   ytimg, which tells Google that Vila is open even on a page that has nothing
   to do with YouTube — a deliberate choice, stated in the privacy policy, taken
   because without it the shelf read as sentences rather than videos. */
.discovery-row {
  display: flex;
  width: 100%;
  min-width: 0;
  align-items: flex-start;
  padding: 14px 14px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  gap: 12px;
  text-align: left;
  transition: background var(--dur);
  border-radius: 0;
  overflow-wrap: anywhere;
}

.discovery-row:hover { background: var(--surface); }

/* Inside a labelled section the row is not a feed item — the section already
   supplies the padding and the rule below it. */
.discovery-section .discovery-row { padding: 4px 0; border-bottom: 0; }
.discovery-section .discovery-row:hover { background: none; }
.discovery-row:hover .discovery-row-title { color: var(--accent); }

.discovery-row-title {
  overflow: hidden;
  font-size: var(--size-body);
  line-height: 1.55;
  transition: color var(--dur);
  font-weight: 600;
}

/* 16:9, and a third of a narrow panel at most, so the title keeps the width it
   needs to be read. The surface fill shows only while the picture is loading; one
   that fails removes the whole box (discovery.js). */
.discovery-thumb {
  position: relative;
  flex: 0 0 auto;
  width: clamp(88px, 34%, 144px);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface);
}

.discovery-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.discovery-row-body {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: 5px;
}

/* Three lines at most beside a picture: past that the row is a paragraph, and
   the whole title is one click away on YouTube. */
.discovery-row-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* The title in the reader's own language. One step down from the original and
   grey, because it is the gloss and not the thing — but above the channel line,
   because it is what the decision to open a talk is actually made on. */
.discovery-row-native {
  overflow: hidden;
  color: var(--muted);
  font-size: var(--size-sm);
  line-height: 1.55;
}

.discovery-row-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  color: var(--muted);
  font-size: var(--size-xs);
  margin-top: 3px;
  line-height: 1.5;
}

/* The difficulty of this talk, in the same colours the subtitles use for the
   same ladder — a learner who has seen 中级 words boxed in teal for an hour
   should not have to learn a second vocabulary for the same fact.

   It leads the line because it is the only part of it that is about the reader.
   What it displaced is 播放量: every talk in this catalogue is a千万级 TED talk,
   so the number sounded like information and separated nothing. */
.discovery-row-signal {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--tier, var(--muted));
  font-weight: 600;
}

.discovery-row-signal i {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border: 1px solid var(--tier);
  border-radius: 2px;
  background: color-mix(in srgb, var(--tier) 30%, transparent);
}

.discovery-row-dot { opacity: 0.45; }

/* 约懂. Four rising bars with the reached one lit, then the band word and the
   number — the graphic docs/ui-plan.md asks a bare percentage to sit on, so the
   position is readable before the figure is.

   One accent throughout, getting no second hue per band: the bands are an
   ordered ladder, and colouring 太难 red would make it a warning about the video
   rather than a statement about the reader. What separates them is how many bars
   are lit. */
.comprehension-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  font-weight: 600;
}

.comprehension-bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 1px;
  height: 10px;
}

.comprehension-bars i {
  width: 3px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--muted) 30%, transparent);
}

.comprehension-bars i[data-lit] { background: var(--accent); }
.comprehension-bars i:nth-child(1) { height: 4px; }
.comprehension-bars i:nth-child(2) { height: 6px; }
.comprehension-bars i:nth-child(3) { height: 8px; }
.comprehension-bars i:nth-child(4) { height: 10px; }

/* 太难 recedes rather than alarms: a talk out of reach is information about the
   fit, and the row still has a title worth reading. */
.comprehension-chip[data-band='hard'] { color: var(--muted); }
.comprehension-chip[data-band='hard'] .comprehension-bars i[data-lit] { background: var(--muted); }

.comprehension-label { font-variant-numeric: tabular-nums; }

.discovery-topics {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.discovery-chip {
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
}

.discovery-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* The category strip: one line, no sideways scrolling. Categories belong above
   what they filter, but a wall tall enough to hold thirty of them would push the
   first video off the screen — so the few biggest sit here and the rest are one
   tap away. */
/* One line, always. Wrapping put 更多 on a row of its own — and 更多 is the way to
   everything the strip did not fit, so the moment the strip overflows is the
   moment that button matters most. Counting chips cannot guarantee this: the
   names come from the catalogue and 「Personal Growth」 is twice the width of
   「推荐」. The category shrinks instead; the fixed three never do. */
/* The category row and the way out of it, on one line. The rule moved from the
   strip to the bar so the divider runs the full width under both. */
/* 难度, at the end of the category row. A native select so the keyboard, screen
   readers and the OS picker all work without us; drawn as a quiet pill with its
   own chevron so it reads as a filter on this row, not as a form field. It never
   shrinks — the category strip beside it is what gives up width. Capped at
   half the row rather than at a fixed width: 96px cut 「Intermediate」 and
   「Intermédiaire」 to an ellipsis. */
.discovery-level-select {
  flex: 0 0 auto;
  align-self: center;
  max-width: 50%;
  /* As wide as the chosen option, not the longest one: a native select sizes
     itself for 「Intermediate」 even while it reads 「Difficulty」. */
  field-sizing: content;
  margin: 0 8px 0 0;
  padding: 6px 22px 6px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  appearance: none;
  background: var(--bg)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat right 9px center / 9px 6px;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: border-color var(--dur), color var(--dur);
}

.discovery-level-select:hover { border-color: var(--accent); }

/* The pill's own border is the focus mark, with a soft halo for the keyboard.
   The global ring drew a second blue outline 1px outside the first. */
.discovery-level-select:focus-visible,
.discovery-level-ask select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Filtered: the same accent the selected category uses, as an outline rather
   than a fill, so one lit chip and one set filter never look like two picks
   from the same row. */
.discovery-level-select.is-active {
  border-color: var(--accent);
  color: var(--accent);
}

.discovery-filter-bar {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--border);
}

.discovery-filters {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  padding: 9px 14px;
}

/* 更多, opened. Wraps, because reading all of them at once is the reason to ask. */
.discovery-wall {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px 14px 11px;
  border-bottom: 1px solid var(--border);
}

.discovery-filter {
  display: inline-flex;
  align-items: baseline;
  flex: 0 0 auto;
  gap: 5px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  transition: background var(--dur), border-color var(--dur), color var(--dur);
  white-space: nowrap;
  min-height: 32px;
}

.discovery-filter:hover { border-color: var(--accent); }

/* The catalogue's own names, so their width is not ours to predict. They stay
   whole: one that does not fit is taken out of the row by fitStrip rather than
   cut to 「科」. Only the selected one may shrink, when it is the last one left
   and still too wide. The wall's chips wrap and are never cut. */
.discovery-filter.is-topic.is-active {
  flex: 0 1 auto;
  min-width: 0;
}

.discovery-filters.is-measuring .discovery-filter.is-topic { flex-shrink: 0; }

/* inline-flex above would otherwise outrank the attribute fitStrip sets. */
.discovery-filter[hidden] { display: none; }

.discovery-filter.is-topic > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* docs/ui-plan.md, 视觉规范: 单一强调色 —— 色相表达含义. A black pill was a second
   emphasis colour saying the same thing accent already says. */
.discovery-filter.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--bg);
}

/* The number is the promise: that many talks behind this tag. */
.discovery-filter-count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.discovery-filter.is-active .discovery-filter-count { color: var(--bg); opacity: 0.6; }

/* 更多 is a disclosure, not a category — it never carries the selected look, and
   it follows the last tag rather than being pushed to the right edge, where a
   wrapped row would leave it stranded on a line of its own. */
.discovery-filter.is-more {
  border-color: transparent;
  color: var(--muted);
}

.discovery-filter.is-more:hover,
.discovery-filter.is-more.is-open { color: var(--accent); }

/* Where the next batch is asked for, by being scrolled to. Nothing is drawn: a
   spinner on a local array is theatre, and a 看更多 button asked you to confirm
   that you wanted to keep reading what you were already reading. */
.discovery-more {
  height: 1px;
}

/* Going to look for something yourself, at the end of the row that is already
   about finding something. It used to be a full-width button under the feed —
   but the feed loads another eight rows every time you reach the bottom, so that
   was an exit at the end of a list that has no end.

   Outside the scrolling strip and behind its own divider: it keeps its place
   while the categories change, and the width it gives back is width the strip
   spends on category names. */
.discovery-search {
  display: inline-flex;
  flex: 0 0 auto;
  width: 42px;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--border);
  border-radius: 0;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
}

.discovery-search svg { width: 16px; height: 16px; fill: currentColor; }
.discovery-search:hover { border-color: var(--border); color: var(--accent); }

/* The same errand on the screen that has no category row to hang an icon on.
   There the sentence is the offer, so it keeps the shape it always had. */
.discovery-search-line {
  width: 100%;
  padding: 13px 14px;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-xs);
  text-align: left;
}

.discovery-search-line:hover { color: var(--accent); }

/* Reporting a broken video from where the learner notices it. */
.report-title {
  margin: 2px 4px 7px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

.report-option {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 4px;
  font-size: var(--size-xs);
  cursor: pointer;
}

.report-option input {
  margin: 0;
}

.report-description {
  display: block;
  box-sizing: border-box;
  width: calc(100% - 8px);
  margin: 3px 4px 6px;
  padding: 6px 7px;
  resize: vertical;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--size-2xs);
  line-height: 1.4;
}

.report-description[hidden] {
  display: none;
}

.report-send {
  width: 100%;
  margin-top: 6px;
  padding: 5px 0;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--size-2xs);
}

.report-send:disabled {
  border-color: var(--border);
  color: var(--muted);
  cursor: default;
}

.report-error {
  margin: 6px 4px 0;
  color: var(--muted);
  font-size: 10px;
  line-height: 1.4;
}

.report-error {
  color: var(--danger, #b42318);
}

.report-done {
  margin: 4px;
  color: var(--muted);
  font-size: var(--size-2xs);
}

/* ----------------------------------------------------------- word card */

/* Readable but not obtrusive. A native tooltip is ~11px and clips; the first pass
   at this went the other way and covered the line underneath. Text stays at 14px —
   the size that was asked for — and the room comes out of padding and width. */
.word-card {
  position: fixed;
  z-index: 30;
  width: min(258px, calc(100vw - 16px));
  padding: 9px 11px 8px;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--bg);
  box-shadow: 0 8px 24px rgb(0 0 0 / 24%);
  font-size: 14px;
  line-height: 1.45;
}

.card-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.card-word {
  font-size: 17px;
  font-weight: 700;
}

.card-pronounce {
  padding: 0 3px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 15px;
}

.card-pronounce:hover,
.card-pronounce:focus-visible { color: var(--accent); }

.word-pronounce {
  border: 0;
  padding: 2px 4px;
  background: transparent;
  cursor: pointer;
}

.card-original {
  border: 0;
  padding: 2px 4px;
  border-radius: 4px;
  background: var(--accent-soft);
  color: var(--accent);
  cursor: pointer;
  font-size: 11px;
}

.card-pronunciation {
  color: var(--muted);
  font-size: 13px;
  overflow-wrap: anywhere;
}

.card-voice-variant {
  margin-top: 3px;
  padding: 2px 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
}

.card-voice-variant:hover,
.card-voice-variant:focus-visible { color: var(--accent); border-color: var(--accent); }

.card-audio-status {
  color: var(--muted);
  font-size: 11px;
}

.card-head .lemma {
  color: var(--muted);
  font-size: 12px;
}

/* One stored fact, not a claim about how often the word has come back: counting
   later sightings would need a per-video record Vila does not keep. */
.card-seen {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.card-seen[hidden] {
  display: none;
}

.card-senses {
  margin-top: 5px;
}

.sense + .sense {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}

.sense-head {
  margin: 0;
}

.sense-pos {
  margin-right: 5px;
  color: var(--muted);
  font-style: italic;
}

/* Reading hierarchy shared by the collection and discovery landing pages. */
.view-heading { padding: 20px 14px 16px; }
.view-heading h1 { margin: 0 0 6px; font-size: 17px; line-height: 1.4; }
.view-heading p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.65; }
.shelf-tab[aria-selected='true'] { background: var(--bg); color: var(--accent); font-weight: 600; box-shadow: 0 1px 2px color-mix(in srgb, var(--text) 10%, transparent); }
.shelf-tab:hover { color: var(--accent); }
.shelf-action, .discovery-stats-go, .review-back { min-height: 28px; }
.template:hover { border-color: var(--accent); background: var(--accent-soft); }
.voice-status { font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
.voice-target-section h2, .voice-results h2 { font-size: 14px; }
.draft-para-head { gap: 8px; }
.draft-para-title { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }
.draft-para-state { flex: 0 0 auto; }
#list .actions a, #list .actions button { display: inline-flex; align-items: center; min-height: 28px; }
#view-discover > .discovery-lead { padding: 0 14px; }
@media (max-width: 360px) {
  .tabs:not([hidden]) { gap: 2px; }
  .tab { padding-right: 4px; padding-left: 4px; }
  .discovery-filter { padding-right: 6px; padding-left: 6px; }
  .discovery-filters { gap: 4px; padding-right: 10px; padding-left: 10px; }
  .discovery-search { width: 36px; }
  .draft-head { flex-wrap: wrap; }
  .draft-head-meta { margin-left: 0; }
}
@media (prefers-color-scheme: dark) {
  .voice-primary:not(:disabled) { color: var(--bg); }
}

.shelf-filters { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; padding: 6px 14px; color: var(--muted); font-size: var(--size-xs); }
.shelf-filters select { padding: 6px; font-size: inherit; }

.goal-row.goal-row-confirm {
  margin-left: auto;
  border-color: transparent;
  color: var(--accent);
}
.goal-row.goal-row-confirm:disabled {
  opacity: .45;
  cursor: default;
}
.goal-tag[hidden] { display: none; }

.goal-collection-heading {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
}
.goal-collection-heading > .goal-menu-title {
  flex: 0 1 auto;
  margin: 0;
}
.goal-collection-heading > .goal-row {
  flex: 0 0 auto;
}

.goal-collection-actions { display: flex; align-items: center; width: 100%; gap: 8px; margin-top: 6px; }

.goal-row.goal-row-switch { color: var(--accent); border: 0; background: transparent; padding: 0; border-radius: 0; }
.goal-row.goal-row-switch:hover { background: transparent; text-decoration: underline; }

.goal-document-list { width: 100%; display: flex; flex-direction: column; }
.goal-document-item { border-bottom: 1px solid var(--border); }
.goal-document-item:last-child { border-bottom: 0; }
.goal-row.goal-document-choice { display: flex; width: 100%; padding: 10px 8px; border: 0; border-radius: 4px; text-align: left; white-space: normal; overflow-wrap: anywhere; }
.goal-row.goal-document-choice.is-current { color: var(--accent); background: var(--accent-soft); }
.goal-document-choice .goal-row-label { min-width: 0; }
.goal-document-choice.is-current::after { content: '✓'; margin-left: auto; padding-left: 8px; }
.goal-collection-actions { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 10px; }

.goal-collection-heading.goal-selected-draft { padding: 10px 12px; background: var(--accent-soft); border-left: 3px solid var(--accent); border-radius: 6px; margin-bottom: 8px; box-sizing: border-box; }
.goal-selected-copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.goal-selected-status { color: var(--accent); font-size: var(--size-2xs); }
.goal-selected-name { color: var(--text); overflow-wrap: anywhere; }
.goal-selected-draft > .goal-row-switch { margin-left: auto; }
.goal-tag.goal-tag-completed { color: var(--accent); font-weight: 600; }
.goal-tag.goal-tag-completed::before { content: '✓ '; }

/* ------------------------------------------------------------------ tour */

/* First run on 在看 only — see sidepanel/tour.js. The layer catches every press
   outside the card, so the walk-through cannot be half-used as a screen. */
.tour {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

.tour-spot {
  position: fixed;
  border-radius: 10px;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 200vmax rgba(15, 17, 21, 0.58);
  pointer-events: none;
  transition: left var(--dur), top var(--dur), width var(--dur), height var(--dur);
}

.tour-spot[hidden] {
  display: none;
}

.tour-card {
  position: fixed;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  padding: 16px 16px 12px;
  border-radius: var(--radius-card);
  background: var(--bg);
  box-shadow: 0 12px 32px rgba(15, 17, 21, 0.28);
}

.tour-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: var(--size-2xs);
  font-variant-numeric: tabular-nums;
}

.tour-count {
  white-space: nowrap;
}

.tour-pips {
  display: flex;
  gap: 3px;
}

.tour-pip {
  width: 14px;
  height: 3px;
  border-radius: 2px;
  background: var(--border);
}

.tour-pip.on {
  background: var(--accent);
}

.tour-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
}

.tour-text {
  margin: 0;
  font-size: var(--size-sm);
  line-height: 1.65;
}

.tour-foot {
  margin: 0;
  color: var(--muted);
  font-size: var(--size-2xs);
  line-height: 1.5;
}

.tour-legend,
.tour-level {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tour-level {
  flex-direction: column;
  gap: 8px;
}

.tour-keys {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 10px;
  align-items: center;
  font-size: var(--size-xs);
}

.tour-key {
  justify-self: start;
  padding: 2px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  font-size: var(--size-2xs);
  white-space: nowrap;
}

.tour-key-text {
  min-width: 0;
  color: var(--text);
}

.tour-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.tour-skip {
  margin-right: auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: var(--size-sm);
  cursor: pointer;
}

.tour-skip:hover {
  border: 0;
  color: var(--text);
}

.tour-back {
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: var(--size-sm);
  cursor: pointer;
}

@media (prefers-color-scheme: dark) {
  .tier-scrim {
    background: rgba(0, 0, 0, 0.35);
  }

  .tier-pop,
  .tour-card {
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  }
}

.dw { color: var(--text); font-size: var(--size-body); }
#view-draft.dw { padding: 10px 14px; }
#view-draft.dw[data-screen=picker] { display: flex; flex-direction: column; overflow: hidden; }
#view-draft.dw[data-screen=picker][hidden] { display: none; }
#view-draft[data-screen=picker] > * { flex-shrink: 0; }
#view-draft[data-screen=picker] > button { align-self: flex-start; }
#view-draft[data-screen=picker] > .dw-pool { flex: 1 1 auto; overflow-y: auto; min-height: 0; }
#view-draft[data-screen=picker] > .dw-footer { position: static; }
.dw .dw-title { font-size: var(--size-body); font-weight: 500; }
.dw .dw-compare { justify-content: flex-start; background: var(--surface); border-radius: var(--radius); padding: 3px; width: fit-content; margin: 12px 0; }
.dw h2 { font-size: var(--size-body); font-weight: 500; margin: 8px 0 12px; }
.dw h3 { font-size: var(--size-body); font-weight: 500; line-height: 1.5; margin: 4px 0; }
.dw button { cursor: pointer; white-space: normal; }
.dw button:disabled { cursor: default; opacity: .5; }
.dw input:not([type=checkbox]), .dw textarea { display: block; width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: inherit; padding: 7px 10px; font: inherit; margin: 10px 0; }
.dw textarea { min-height: 90px; resize: vertical; }
.dw .dw-editor { min-height: 280px; line-height: 1.8; }
.dw .dw-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.dw .dw-button, .dw .dw-primary { border-radius: var(--radius); padding: 6px 9px; font: inherit; font-size: var(--size-xs); line-height: 1.5; }
.dw .dw-button { border: 1px solid transparent; background: transparent; color: var(--muted); }
.dw .dw-primary { background: var(--accent); color: white; border: 1px solid transparent; }
.dw button[aria-pressed=true] { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.dw .dw-types { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 16px 0; }
.dw .dw-types button { border: 1px solid var(--border); text-align: left; padding: 12px; }
.dw .dw-card { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; padding: 12px 0; margin: 0; }
.dw .dw-unfinished { background: var(--surface); border-radius: var(--radius); padding: 12px; margin: 12px 0; }
.dw .dw-card>.dw-row { padding-top: 6px; }
.dw .dw-muted, .dw .dw-preview { color: var(--muted); font-size: var(--size-xs); }
.dw .dw-preview { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.dw .dw-coverage { display: flex; flex-wrap: wrap; gap: 8px; font-size: var(--size-xs); color: var(--muted); margin: 10px 0; }
.dw .is-covered { color: var(--accent); }
.dw .dw-select { display: inline-flex; align-items: center; font-size: var(--size-xs); color: var(--muted); margin: 6px 0; }
.dw select { max-width: 150px; border: 1px solid var(--border); border-radius: 5px; color: inherit; background: var(--bg); padding: 6px; font: inherit; }
.dw .dw-material { padding: 14px 0; border-bottom: 1px solid var(--border); }
.dw .dw-check { display: flex; align-items: flex-start; gap: 9px; }
.dw .dw-check input { flex-shrink: 0; margin-top: 4px; accent-color: var(--accent); }
.dw .dw-added { color: var(--muted); }
.dw .dw-added input { accent-color: var(--muted); }
.dw .dw-section { margin: 20px 0; }
.dw .dw-body { white-space: pre-wrap; line-height: 1.9; margin: 24px 0; }
.dw .dw-footer { position: sticky; bottom: 0; background: var(--bg); padding: 12px 0; border-top: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; z-index: 2; }
.show-manuscripts .shelf-filters, .show-manuscripts .shelf-tools, .show-manuscripts #collection-sort, .show-manuscripts #lang-tabs, .show-manuscripts #list, .show-manuscripts #empty, .show-manuscripts #collection-total, .show-manuscripts #collection-summary, .show-manuscripts #review-band { display: none !important; }
.dw input.dw-title { font-size: var(--size-body); font-weight: 500; }
@media (pointer: coarse) { .dw button, .dw select { min-height: 44px; } .dw input, .dw textarea { font-size: 16px; } }
/* Match the existing shelf: compact controls and divided rows. */
.draft-library.dw { padding: 0 14px; }
.dw p { margin: 6px 0; }
.dw input[type=search] { font-size: var(--size-xs); background: var(--surface); }
.dw .dw-list-label { margin: 0; font-size: var(--size-xs); font-weight: 500; }
.dw .dw-card .dw-primary { color: var(--accent); background: transparent; padding-left: 0; }
.dw .dw-select { gap: 6px; }
.dw .dw-coverage { gap: 4px; margin: 6px 0; }
.dw .dw-coverage span + span::before { content: "→"; margin-right: 4px; color: var(--muted); }
.dw .dw-card .dw-muted { font-size: var(--size-2xs); }
.dw .dw-button:hover { color: var(--accent); background: var(--accent-soft); }

/* The shared creation form fills the inline collection menu. */
.goal-menu > .dw { width: 100%; min-width: 0; }

/* Website boundary only; panel layout and controls use extension styles. */
#voice-status { white-space: normal; }
#voice-body > .voice-speed-chips { margin-bottom: 16px; }

/* Translated controls need their text's width, rather than the extension's
   equal-width Chinese labels. Wrap whole groups on narrow screens. */
.practice-controls,
.practice-transport { flex-wrap: wrap; }
.sentence-nav,
.sentence-end {
  flex: 1 0 auto;
  min-width: max-content;
  max-width: none;
}
.sentence-nav button,
.sentence-end button {
  flex: 1 0 auto;
  min-width: max-content;
}

/* Website-only coach marks. The hole leaves the real control clickable. */
.demo-tour-overlay { position: fixed; inset: 0; z-index: 10000; pointer-events: none; }
.demo-tour-overlay[hidden] { display: none; }
.demo-tour-shade { position: fixed; background: rgba(15, 23, 42, .62); pointer-events: auto; touch-action: none; }
.demo-tour-ring { position: fixed; border: 2px solid #b7ccff; border-radius: 7px; box-shadow: 0 0 0 2px #315ac3; pointer-events: none; }
.demo-tour-ring[hidden], .demo-tour-arrow[hidden] { display: none; }
.demo-tour-arrow { position: fixed; width: 12px; height: 12px; background: #fff; transform: rotate(45deg); pointer-events: none; }
.demo-tour { position: fixed; margin: 0; box-sizing: border-box; padding: 16px; border: 1px solid #d8e2f5; border-radius: 12px; background: #fff; color: #243453; box-shadow: 0 10px 30px #07132940; pointer-events: auto; overflow: auto; overscroll-behavior: contain; }
.demo-tour-progress { display: inline-block; padding: 2px 8px; border-radius: 20px; background: #eff4ff; font-size: 11px; font-weight: 700; color: #315ac3; }
.demo-tour p { margin: 10px 0 14px; font-size: 14px; line-height: 1.65; }
.demo-tour-actions { display: flex; justify-content: flex-end; }
.demo-tour-actions button { padding: 4px 0; border: 0; border-radius: 2px; background: transparent; color: #315ac3; font-size: 12px; text-decoration: underline; text-underline-offset: 3px; }
.demo-tour-actions button:last-child { color: #64748b; }
.demo-tour-actions button:focus-visible { outline: 2px solid #315ac3; outline-offset: 3px; }
