:root {
  --paper: #f7f0e4;
  --card: #fffaf2;
  --ink: #191512;
  --muted: #76695d;
  --red: #b5312d;
  --gold: #c79542;
  --brown: #8b641f; /* the darker gold that borders enrolled learn controls, where plain gold is too pale to carry white text */
  --line: #d8c8b5;
  --seen: #b7bec4;
  --unseen: #c4b7a4;
  --indigo: #33577b; /* the Japanese (JLPT) accent, vs the red Chinese (HSK) one */
  --space-section: 16px;
  --pad-control-y: 7px;
  --pad-control-x: 12px;
  --pad-card: 14px;
  --radius-control: 10px;
  --radius-card: 16px;
  --sans: ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  --han: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "Heiti SC", var(--sans);
  --kana: "Noto Sans JP", "Hiragino Kaku Gothic ProN", "Yu Gothic", Meiryo, var(--sans);
}

* { box-sizing: border-box; }

/* Japanese text is marked lang="ja" so unified Han codepoints render with
   Japanese glyph variants instead of the simplified-Chinese default. */
[lang="ja"] { font-family: var(--kana); }

/* Always reserve room for the vertical scrollbar so navigating between pages of
   different heights doesn't shift the layout left/right. Fallback: force the
   scrollbar to always show. Modern browsers instead reserve a stable gutter, so
   the scrollbar can still hide when content fits without any horizontal jump. */
html {
  scroll-behavior: smooth;
  overflow-y: scroll;
  /* iOS paints the standalone status-bar/safe-area surround from the page's
     explicit background-color. Do not leave the root canvas transparent: the
     body may not be composited there until the first scroll. */
  background-color: var(--paper);
}
@supports (scrollbar-gutter: stable) {
  html {
    overflow-y: auto;
    scrollbar-gutter: stable;
  }
}

/* Keep the page itself the only thing that scrolls on touch screens: no
   horizontal panning past the layout edge, and no browser overscroll actions
   (pull-to-refresh, back/forward swipe glow) chaining off the document edge.
   Vertical in-page scrolling is untouched. overflow-x: clip cuts overflow
   without making html/body horizontal scroll containers (hidden would); the
   plain hidden line is the fallback for engines without clip. */
html, body {
  overflow-x: hidden;
  overflow-x: clip;
  overscroll-behavior: none;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
}

/* The viewport extends under the iPhone notch / status bar / home indicator
   (viewport-fit=cover), so the reading column pads itself back into the safe
   area; on anything without insets the env() terms are 0 and this is the
   plain 28px 22px 120px it always was. --pad-x is shared with the sticky
   bars, which cancel it to go full-bleed. */
.wrap {
  --pad-x: max(22px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  max-width: 1080px;
  margin: 0 auto;
  padding: calc(24px + env(safe-area-inset-top, 0px)) var(--pad-x) calc(96px + env(safe-area-inset-bottom, 0px));
}
/* Board view breaks out of the reading column to use the full viewport width,
   fitting as many characters on screen as possible. The site header/nav stays
   pinned to the normal reading column (see .wrap.full .nav) so switching to the
   board doesn't widen the chrome. */
.wrap.full {
  max-width: none;
}
.wrap.full .nav {
  /* the reading column: base max-width (1080) minus the wrap's side padding */
  max-width: 1036px;
  margin-left: auto;
  margin-right: auto;
}

.loading, .empty, .hint {
  color: var(--muted);
  padding: 40px 0;
}
/* First-visit dataset download meter (js/app.js): live byte progress in the
   content area while nothing else can render yet. */
.data-progress { color: var(--muted); padding: 40px 0; font-variant-numeric: tabular-nums; }
.empty, .hint { padding: 24px 0; }

a { color: var(--red); text-decoration: none; }
a:hover { text-decoration: underline; opacity: 0.8; }

/* Disable double-tap zoom across the mobile app, including plain text and
   whitespace, while retaining one-finger scrolling and two-finger pinch zoom. */
html { touch-action: manipulation; }

/* ---- nav ---- */
.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px 22px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 14px;
  margin-bottom: 20px;
}
.brand { display: flex; align-items: baseline; gap: 12px; margin-right: auto; }
.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--ink);
  font-size: 24px;
  font-weight: 700;
}
.logo-mark {
  flex: none;
  color: var(--red);
  font-family: var(--han);
  font-size: 1.15em;
  line-height: 1;
  user-select: none;
}
.logo:hover { text-decoration: none; color: var(--red); }
.tabs { display: flex; gap: 6px; }
.tab {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  cursor: pointer;
  padding: 7px 14px;
  border-radius: 6px;
  color: var(--muted);
  font-weight: 600;
  user-select: none;
}
.tab:hover { color: var(--ink); background: rgba(181, 49, 45, 0.08); }
.tab.active { color: #fff; background: var(--red); }
.tab:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.tab-count {
  display: inline-flex;
  min-width: 18px;
  height: 18px;
  margin-left: 5px;
  padding: 0 5px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.tab.active .tab-count {
  background: #fff;
  color: var(--red);
}

/* ---- shared ---- */
.view { animation: fade 0.15s ease; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.subsection { margin: var(--space-section) 0; }
.subsection label, .notes-panel label {
  display: block;
  font-size: 12px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
  margin-bottom: 8px;
}

.btn {
  display: inline-block;
  padding: var(--pad-control-y) var(--pad-control-x);
  margin: 0 8px 8px 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-weight: 600;
}
.btn:hover { border-color: var(--red); text-decoration: none; opacity: 1; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: var(--pad-control-y) var(--pad-control-x);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
  cursor: pointer;
  font-family: var(--han);
  font-size: 18px;
}
.chip:hover { border-color: var(--red); }
.chip.static { cursor: default; color: var(--muted); }
.chip.static:hover { border-color: var(--line); }

.link { color: var(--red); cursor: pointer; }
.link:hover { text-decoration: underline; }
.muted { color: var(--muted); }
.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;
}

input, textarea, button { font-family: inherit; font-size: 16px; color: var(--ink); }
input[type="search"], input[type="text"] {
  width: 100%;
  padding: 10px var(--pad-card);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--gold); outline-offset: 1px; }

button {
  cursor: pointer;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  padding: var(--pad-control-y) var(--pad-control-x);
  font-weight: 700;
}
button:hover { border-color: var(--red); }
button.danger { color: var(--red); }
button:disabled { cursor: default; opacity: 0.5; border-color: var(--line); }

/* ---- search ---- */
.search-box { margin-bottom: 8px; }
/* The blank-input hint inherits .hint's 24px empty-state padding on top of
   the paragraph's own margin, which opened a ~60px hole right under the
   search input (worst on phones, where the input is the whole viewport
   width). It is a caption, not an empty state - keep it tight. The first
   results subsection gets the same trim so typed results also start near
   the input. */
.results .search-hint { padding: 2px 0; margin: 8px 0; }
/* The sentence-fallback caption ("words found in your text") sits between the
   query chrome and the extracted results - same tight trim as the blank-input
   hint, it is a caption too, not an empty state. */
.results .extract-hint { padding: 2px 0; margin: 8px 0 0; font-size: 13px; }
.results .subsection:first-child { margin-top: 12px; }
/* On its own each row is an independent grid, so its auto / max-content tracks
   (the hanzi cell, and the freq / level cells) size to that row's own content -
   a one-hanzi word and a four-hanzi word get different first columns, and the
   rest drift with them. To keep columns aligned regardless of content shape the
   column tracks must be shared across every row, so .result-words owns the grid
   and each row adopts its tracks via subgrid: now every auto / max-content track
   sizes to the widest cell across ALL rows and the columns line up. The optional
   property columns (frequency, level) are toggled in Settings; the container's
   has-freq / has-level classes append exactly the trailing tracks the rows
   render. The pre-subgrid fallback keeps the per-row grid (functional, just not
   cross-row aligned). */
.result-words {
  display: grid;
  grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr);
  column-gap: 16px;
}
.result-words.has-freq,
.result-words.has-level {
  grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr) max-content;
}
.result-words.has-freq.has-level {
  grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr) max-content max-content;
}
.result-word {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  column-gap: 16px;
  align-items: baseline;
  padding: 9px 6px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
/* Fallback for engines without subgrid: each row is its own grid (columns may
   drift between rows, but nothing overflows). */
@supports not (grid-template-columns: subgrid) {
  .result-words { display: flex; flex-direction: column; }
  .result-word {
    grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr);
    gap: 16px;
  }
  .result-words.has-freq .result-word,
  .result-words.has-level .result-word {
    grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr) max-content;
  }
  .result-words.has-freq.has-level .result-word {
    grid-template-columns: minmax(72px, auto) minmax(96px, 1fr) minmax(0, 3fr) max-content max-content;
  }
}
.result-word:hover { background: rgba(181, 49, 45, 0.05); }
.rw-hanzi { font-family: var(--han); font-size: 22px; }
.rw-trad { font-size: 15px; color: var(--muted); margin-left: 8px; }
.rw-py { color: var(--gold); font-weight: 600; }
.rw-m { color: var(--ink); min-width: 0; }
/* Frequency rank bucket; a dash (muted) when the word carries no corpus rank. */
.rw-freq {
  white-space: nowrap;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.rw-freq.blank { color: var(--unseen); }
/* Layer badge: HSK band (red) vs extended CC-CEDICT (gold), so the two result
   groups are never mistaken for one another. */
.rw-badge {
  justify-self: end;
  align-self: center;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--red);
  color: var(--red);
}
.rw-badge.ext { border-color: var(--gold); color: var(--gold); background: rgba(199, 149, 66, 0.1); }
/* JLPT level badge: indigo, so Japanese results are never mistaken for HSK. */
.rw-badge.jlpt { border-color: var(--indigo); color: var(--indigo); }
/* Ungraded (non-HSK) word: the level cell is a quiet placeholder, not a badge,
   so the HSK band reads as an occasional tag rather than a category marker. */
.rw-badge.none { border-color: transparent; color: var(--unseen); background: none; font-weight: 400; }
.attribution { color: var(--muted); font-size: 12px; margin: 10px 0 0; }

/* Word-page example sentences (js/word.js, data/examples.json.gz): the
   sentence reads in the script font at body-plus size and doubles as a
   search anchor for its full text (.review-example-search, shared with the
   review reveal); its English gloss stays small and muted with the
   row-level Tatoeba attribution inline. */
.word-example { margin: 8px 0; }
.word-example-s { font-family: var(--han); font-size: 20px; line-height: 1.45; overflow-wrap: anywhere; }
.word-example-s[lang="ja"] { font-family: var(--kana); }
.word-example-g { font-size: 13px; margin-top: 1px; }

/* Chinese/Japanese result tabs over combined-language results (js/search.js):
   true tabs, only the selected language's list is rendered. The bar rides
   just below the pinned nav (--nav-h is 0 where the nav scrolls away, i.e.
   desktop) so the other language is always one tap away from anywhere in a
   long list. Flat solid paper - no translucency, blur, or gradient
   show-through - and both tabs pin in the standard red. */
.lang-tabs {
  position: sticky;
  top: var(--nav-h, 0px);
  z-index: 9;
  padding: 8px 0;
  background: var(--paper);
}
.lang-count {
  margin-left: 7px;
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.seg-btn.active .lang-count { color: rgba(255, 255, 255, 0.85); }

/* word chip (detail examples) */
.word-chip {
  font-family: var(--sans);
  font-size: 14px;
  align-items: baseline;
  max-width: min(100%, 42rem);
}
.wc-hanzi { font-family: var(--han); font-size: 18px; flex: 0 0 auto; }
.wc-py { color: var(--gold); font-weight: 600; flex: 0 0 auto; }
.wc-m {
  color: var(--muted);
  display: inline-block;
  max-width: clamp(12rem, 34vw, 24rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
/* extended CC-CEDICT badge on import chips */
.wc-badge.ext {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--gold);
  color: var(--gold);
  background: rgba(199, 149, 66, 0.1);
}
.word-chip.ext { border-color: var(--gold); }
.word-chip.srs-word {
  border-color: var(--srs-color);
  box-shadow: inset 3px 0 var(--srs-color);
}
.word-chip.srs-word .wc-hanzi {
  color: var(--srs-color);
}
.word-chip.srs-word:hover,
.word-chip.srs-word:focus-visible {
  border-color: var(--srs-color);
  outline-color: var(--srs-color);
}

/* ---- board ---- */
.board-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  margin-bottom: 10px;
}
.board-filter { flex: 1 1 240px; }
.board-controls { display: inline-flex; align-items: center; gap: 12px 16px; flex-wrap: wrap; }
/* Segmented hanzi/kanji switch, shown when both languages are enabled. */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; background: var(--card); }
.seg-btn {
  display: inline-flex;
  align-items: center;
  border: none;
  border-radius: 0;
  background: none;
  padding: 8px 14px;
  font-weight: 600;
  color: var(--muted);
}
.seg-btn:hover { color: var(--ink); }
.seg-btn.active { background: var(--red); color: #fff; }
/* Counts carried by a segmented button (the review header, js/review.js).
   Only a queue a tap works through gets one, borrowing the nav due badge's
   red pill, and it inverts on the red active button the way .tab.active does.
   Unlike the nav badge these survive the phone breakpoint - with the summary
   line gone they are the review page's only counts. */
.seg-count {
  display: inline-flex;
  min-width: 18px;
  height: 18px;
  margin-left: 6px;
  padding: 0 5px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.seg-btn.active .seg-count { background: #fff; color: var(--red); }
.board-order-wrap {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
}
.board-order {
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
  font-weight: 600;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
}
.board-order:focus { outline: 2px solid var(--gold); outline-offset: 1px; }
.legend { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 5px 10px; color: var(--muted); font-size: 13px; }
.legend-key { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.legend .sample { cursor: default; font-size: 20px; }

.band { margin-bottom: 10px; }
.band-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 15px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--red);
  border-bottom: 1px solid var(--line);
  padding-bottom: 4px;
  margin: 0 0 6px;
}
.band-count { color: var(--muted); font-size: 12px; font-weight: 400; }
.band-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(27px, 1fr));
  gap: 1px;
}
.cell {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  font-family: var(--han);
  font-size: 18px;
  color: var(--unseen);
  cursor: pointer;
  border-radius: 4px;
  user-select: none;
  transition: color 0.1s, background 0.1s;
  text-decoration: none;
}
.cell:hover { background: rgba(181, 49, 45, 0.1); color: var(--red); text-decoration: none; opacity: 1; }
.cell:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
.cell.visited { color: var(--seen); font-weight: 600; }
/* Cross-trained: SRS evidence only in the other board language. Weighted
   between visited and learning, like its color. js/maturity.js owns the color
   itself and passes it in as --srs-color. */
.cell.cross-trained { color: var(--srs-color); font-weight: 600; }
.cell.srs,
.cell.burned { color: var(--srs-color); font-weight: 700; }
.cell.sample { display: inline-flex; width: 1.4em; height: 1.4em; aspect-ratio: auto; }
.legend .cell.sample:hover { background: none; color: inherit; }
.legend .cell.sample.cross-trained,
.legend .cell.sample.srs,
.legend .cell.sample.burned { color: var(--srs-color); }
.legend .cell.sample.visited { color: var(--seen); }
.legend .cell.sample.unseen { color: var(--unseen); }
.legend .cell.sample.learning { color: var(--learning-green); }
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .legend .cell.sample.learning {
    background-image: linear-gradient(to right, var(--learning-green), var(--learning-yellow), var(--learning-orange), var(--learning-red));
    background-clip: text;
    -webkit-background-clip: text;
    color: transparent;
  }
}
@media (forced-colors: active) {
  .legend .cell.sample.learning {
    background-image: none;
    background-clip: border-box;
    -webkit-background-clip: border-box;
    color: CanvasText;
  }
}
/* Its own block: the rule above is matched verbatim by build/test_no_gradients.py
   as the sole audited gradient exception.

   Forced colors discard every board hue, so the cross-training state would
   otherwise vanish into the rest of the grid. A dotted underline carries it
   without color; the legend sample takes the same mark, so the key still
   explains the grid. */
@media (forced-colors: active) {
  .cell.cross-trained { text-decoration: underline dotted; text-underline-offset: 2px; }
}

/* ---- detail ---- */
.hero {
  display: flex;
  gap: 22px;
  align-items: center;
  padding: 8px 0 4px;
}
.hero-char {
  font-family: var(--han);
  font-size: clamp(54px, 13vw, 104px);
  line-height: 1;
  color: var(--red);
}
/* Reading and definition share one leading so the baseline row they sit on
   keeps a single rhythm however the sizes differ. The definition is not
   measure-limited: it is a gloss list, not prose, and clipping it to a column
   narrower than the page only forced ragged wraps beside a wide hero. */
.hero-py { font-size: 22px; line-height: 1.35; color: var(--gold); font-weight: 700; }
/* The pitch-accent display riding a kana reading (js/pitch.js): the first
   attested pattern is a stepped contour directly over the morae, with a
   line-only miniature for each further attested pattern. Deliberately quieter
   than the reading itself: presentation metadata, not part of the
   pronunciation the learner must produce. */
.pitch-kana { cursor: help; }
/* Each mora cell reserves its own headroom, so a reading that wraps keeps a
   contour over every line. The high and low lines sit in that headroom at
   fixed offsets from the cell top; a step connector spans exactly the gap
   between them at the cell's right edge, marking the initial rise and - the
   accent itself - the downstep. Everything is em-sized off the kana and drawn
   with borders, which forced-colors modes repaint (CanvasText) rather than
   strip the way they would a background image. */
.pk-m { position: relative; display: inline-block; padding-top: 0.52em; }
.pk-m::before { content: ""; position: absolute; left: 0; right: 0; border-top: max(1px, 0.07em) solid var(--muted); }
.pk-h::before { top: 0.04em; }
.pk-l::before { top: 0.36em; }
.pk-step::after { content: ""; position: absolute; right: 0; top: 0.04em; height: 0.32em; border-right: max(1px, 0.07em) solid var(--muted); }
/* The conceptual following particle - drawn, never written: a short dotted
   continuation showing whether the pitch holds after the final mora (heiban)
   or falls off it (odaka), the one distinction the word's own morae cannot
   carry. Its cell holds only a zero-width space, so it stays out of screen
   readers, search, and copied text. */
.pk-particle { width: 0.6em; }
.pk-particle::before { border-top-style: dotted; }
/* Line-only miniature for a further attested pattern: same cells, no kana. */
.pitch-mini { font-size: 0.6em; margin-left: 0.55em; }
.pitch-mini .pk-m { width: 0.75em; }
@media (forced-colors: active) {
  .pk-m::before,
  .pk-step::after { border-color: CanvasText; }
  .pk-particle::before { border-color: GrayText; }
}
.hero-m { font-size: 18px; line-height: 1.35; margin: 0; }
/* Tags summarize the entry rather than continue it, so they always take a row
   of their own instead of trailing a short definition on some pages and
   wrapping under a long one on others. */
.hero-tags { display: flex; flex-basis: 100%; flex-wrap: wrap; gap: 8px; align-items: center; }
.tag {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card);
  font-size: 13px;
  color: var(--muted);
}
.band-tag { border-color: var(--red); color: var(--red); font-weight: 700; }
.band-tag.jlpt { border-color: var(--indigo); color: var(--indigo); }
/* The two language blocks of a shared character hero. The small-caps
   Hanzi/Kanji (or Chinese word/Japanese word) label appears whenever the
   combined mode is active, or when both blocks are shown. */
.hero-lang {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 5px 14px;
  margin-bottom: 14px;
}
.hero-lang:last-child { margin-bottom: 0; }
/* A spelling that covers several words gets one block per form instead of a
   single reading line over a single meaning line: each reading keeps its own
   traditional spelling and its own glosses directly beneath it, so the page
   never implies every reading of 脏 carries every meaning of it. */
.hero-senses { flex-basis: 100%; display: flex; flex-direction: column; gap: 7px; }
.hero-sense-form { margin-left: 8px; font-family: var(--han); font-weight: 400; color: var(--muted); }
/* The hero's two micro-labels - the language note and a reading's kun/on
   suffix - are the same style as every other section label in the app
   (.subsection label), with the leading tightened so the note sits with the
   lines it introduces rather than floating above them. */
.lang-note, .reading-kind {
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}
.lang-note { flex-basis: 100%; }
.reading-kind { margin-left: 10px; }
.hero-py.on { color: var(--indigo); }
.decomp {
  font-family: var(--han);
  font-size: 22px;
  color: var(--muted);
  margin-bottom: 10px;
}
.etymology { max-width: 60ch; }

.panel-title { font-size: 20px; margin: 6px 0; }

/* ---- recent searches (blank search input) ---- */
.recent-searches { margin-top: var(--space-section); }
.recent-query { font-size: 16px; gap: 4px; }
/* Per-chip remove: visually compact, but padding + negative margins keep a
   finger-sized hit target without inflating the chip. */
.chip-remove {
  border: 0;
  background: none;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 10px 8px;
  margin: -10px -10px -10px -4px;
}
.chip-remove:hover { color: var(--red); }
.recent-clear { margin: 12px 0 0; }
.recent-clear .link { color: var(--muted); font-size: 13px; }
.recent-clear .link:hover { color: var(--red); }

/* ---- import ---- */
.import-lang { margin: 6px 0 10px; }
.import-input {
  width: 100%;
  min-height: 180px;
  padding: var(--pad-card);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
  resize: vertical;
  line-height: 1.7;
  font-family: var(--han);
  font-size: 18px;
}
.import-actions { display: flex; gap: 10px; margin: 12px 0 4px; }
.import-summary { margin-top: 8px; }
.import-stats { line-height: 1.7; }
.import-stats strong { color: var(--red); }
.import-chip { cursor: pointer; }
.import-chip-count {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.import-more { margin: 10px 0 0; }
.import-section { margin-top: 4px; }

/* ---- settings ---- */
.settings-list { display: flex; flex-direction: column; gap: 2px; }
/* Scoped above `.subsection label` so these rows keep normal (not the uppercase
   heading) typography. */
.settings-list .setting-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  padding: 10px 6px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 16px;
  color: var(--ink);
}
.setting-check {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  flex: none;
  cursor: pointer;
  accent-color: var(--red);
}
.setting-text { display: flex; flex-direction: column; gap: 2px; }
.setting-label { font-weight: 600; }
.setting-hint { color: var(--muted); font-size: 13px; }
.settings-hint { font-size: 13px; margin: 0 0 10px; }
.dictionary-pack-list { display: grid; gap: 8px; }
.dictionary-pack {
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
}
.dictionary-pack-title { font-weight: 700; }
.dictionary-pack-description { margin: 3px 0; font-size: 13px; }
.dictionary-pack-status { margin: 5px 0 9px; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.dictionary-pack-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.dictionary-pack-actions button { min-height: 34px; padding: 5px 11px; font-size: 13px; }
.sync-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.sync-key { flex: 1 1 280px; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.sync-status { margin: 10px 0 4px; }
.sync-secret-hint { margin: 0; font-size: 11px; color: var(--muted); }

/* ---- word detail ---- */
/* The hero slot holds a whole word, so it is smaller than a single-character
   hero and allowed to wrap. */
.hero-char.word { font-size: clamp(32px, 8vw, 64px); line-height: 1.05; word-break: break-word; }
/* The alternate script sits on the same baseline row as the reading and the
   definition, so its spacing comes from that row's gap like every other item;
   it carries no margin of its own. */
.hero-trad { font-family: var(--han); color: var(--muted); font-size: 16px; line-height: 1.35; }
.ext-tag { border-color: var(--gold); color: var(--gold); font-weight: 700; }

/* ---- SRS enrollment ---- */
/* Independent language groups: inside each group the Read button sits inset
   within the Recall frame (selecting recall implies read, so the wider
   capability visibly contains the one it includes). The frame takes its
   state classes and colors from Recall, but only the buttons themselves are
   interactive: no button nests inside another. */
.learn-panel { margin: var(--space-section) 0; }
.learn-options {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.learn-languages {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  width: min(100%, 620px);
}
.learn-languages.with-stats { grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.learn-language-group {
  position: relative;
  display: grid;
  align-content: start;
  padding: 24px 8px 8px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--paper);
}
.learn-language-label {
  position: absolute;
  top: 6px;
  left: 13px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1.2;
  text-transform: uppercase;
}
.learn-capability-set {
  display: grid;
  align-content: start;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
}
.learn-capability-set.enrolled { border-color: #8b641f; background: #edc66f; }
.learn-capability-set.suspended { border-style: dashed; border-color: var(--muted); }
.learn-options > .learn-capability-set,
.learn-single > .learn-capability-set {
  flex: 1 1 170px;
  min-width: min(170px, 100%);
  max-width: 280px;
}
.learn-single { flex: 1 1 280px; max-width: 320px; }
.learn-capability-set > .learn-option[data-profile="recall"] {
  border: none;
  border-radius: 11px 11px 0 0;
}
.learn-capability-set > .learn-option[data-profile="read"] {
  margin: 2px 8px 8px;
  /* Do not inherit the global pill radius: this is nested inside two softly
     rounded frames and should use the same control-radius family. */
  border-radius: var(--radius-control);
}
/* The borderless strip buttons lose the app's border hover cue; restore it
   on the frame they fill. */
.learn-capability-set:has(> [data-profile="recall"]:hover) { border-color: var(--red); }
.learn-option {
  display: flex;
  min-height: 44px;
  padding: 5px 12px;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  line-height: 1.15;
  text-align: left;
}
.learn-option.enrolled { border-color: #8b641f; background: #edc66f; color: var(--ink); }
.learn-option.suspended { border-style: dashed; border-color: var(--muted); color: #594f46; }
.learn-option-name { font-size: 14px; }
.srs-facts.learn-stats {
  grid-template-columns: 1fr;
  margin-top: 8px;
  font-size: 13px;
}
.learn-stats .srs-fact dt { min-width: 92px; }
.hint.compact { padding: 6px 0; }

/* ---- SRS review and management ---- */
.review-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: var(--space-section); }
.review-counts { margin: 0; font-variant-numeric: tabular-nums; }
.review-head-controls { display: flex; flex: none; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.review-switcher, .review-lang { flex: none; }
.review-card {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(18px, 4vw, 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: rgba(255, 250, 242, 0.82);
  box-shadow: 0 8px 32px rgba(25, 21, 18, 0.06);
  text-align: center;
}
.review-request { margin: 0; color: var(--muted); }
.review-prompt { margin: 8px 0; font-family: var(--han); font-size: clamp(38px, 10vw, 72px); line-height: 1.15; overflow-wrap: anywhere; }
.review-card[data-qtype^="definition-"] .review-prompt,
.review-card[data-qtype^="cloze-"] .review-prompt { font-family: var(--sans); font-size: clamp(25px, 6vw, 40px); }
.review-card[data-qtype^="pronunciation-"] .review-prompt { font-family: var(--sans); color: var(--gold); font-size: clamp(26px, 7vw, 46px); }
/* Assisted recall: a sourced usage example with the target blanked out,
   under the definition prompt. The example reads at glyph size (the blanks
   must be as legible as the characters around them); its gloss stays small. */
.review-cloze { margin: 2px 0 6px; }
.review-cloze-example { font-family: var(--han); font-size: clamp(26px, 7vw, 40px); line-height: 1.2; overflow-wrap: anywhere; }
.review-cloze-example[lang="ja"] { font-family: var(--kana); }
.review-cloze-gloss { font-size: 14px; }
.review-cloze-provenance { font-style: italic; white-space: nowrap; }
.review-answer-example { font-size: 13px; margin-top: 6px; }
.review-answer-example [lang] { font-family: var(--han); font-size: 15px; }
.review-answer-example [lang="ja"] { font-family: var(--kana); }
/* Sentence-to-search anchor, shared by the review reveal and word-page
   examples (js/review.js, js/word.js). */
.review-example-search { color: inherit; text-decoration: underline; text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); text-underline-offset: 0.14em; }
.review-example-search:hover { opacity: 1; text-decoration-color: currentColor; }
.review-example-search:focus-visible { outline: 2px solid var(--red); outline-offset: 3px; border-radius: 3px; }
.review-options { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: var(--space-section); }
.review-options button { min-height: 58px; border-radius: var(--radius-control); overflow-wrap: anywhere; }
/* Chinese/Japanese answer choices (written forms, kana readings) read at
   glyph size in the Han/kana fonts; pinyin and English options stay at the
   ordinary button size. */
.review-options.cjk button { font-family: var(--han); font-size: clamp(24px, 6vw, 32px); font-weight: 500; }
.review-options.cjk[lang="ja"] button { font-family: var(--kana); }
.review-reveal, .review-next { min-width: 160px; margin-top: 10px; }
.review-spike { display: block; min-width: 160px; margin: 10px auto 0; color: var(--muted); }
.review-spike:hover { color: var(--ink); }
/* Once answered, the reviewed surface links to its entry page. It keeps its
   glyph styling - the link is navigation, not a highlighted control - and
   announces itself only on hover/keyboard focus, so the card stays a quiz, not
   a hyperlink. Before the answer the prompt is a plain .review-prompt-text
   span instead (js/review.js), which needs no rule of its own: an anchor here
   inherits colour and carries no underline at rest, so the two render
   identically and only navigability changes. */
.review-entry-link { color: inherit; }
.review-entry-link:hover { opacity: 1; text-decoration-thickness: 2px; text-underline-offset: 0.14em; }
.review-entry-link:focus-visible { outline: 2px solid var(--red); outline-offset: 4px; border-radius: 4px; }
.review-choice-link { color: inherit; }
.review-answer { margin: var(--space-section) 0 12px; padding: var(--pad-card); border-radius: var(--radius-control); background: rgba(199, 149, 66, 0.1); }
.review-answer strong { display: block; color: var(--red); }
/* The full-item reveal: the written form leads at glyph size in the Han
   font, the pronunciation and English meaning follow at reading sizes. */
.review-answer-written { font-family: var(--han); font-size: clamp(34px, 9vw, 54px); line-height: 1.2; overflow-wrap: anywhere; }
.review-answer-written[lang="ja"] { font-family: var(--kana); }
.review-answer-pron { color: var(--gold); font-weight: 600; font-size: clamp(17px, 4.5vw, 22px); }
.review-answer-def { margin-top: 2px; }
/* A spelling that covers several words reveals one line per form instead of
   a reading list over a meaning list: the reading, the traditional spelling
   that reading is written with when it differs, then that form's own senses.
   The pairing is the information, so the line never wraps them apart. */
.review-answer-senses { margin-top: 2px; }
.review-answer-sense { margin-top: 4px; }
.review-answer-sense-pron { color: var(--gold); font-weight: 600; font-size: clamp(16px, 4vw, 20px); }
.review-answer-sense-written { margin-left: 6px; font-family: var(--han); font-size: clamp(18px, 4.5vw, 22px); line-height: 1; }
.review-answer-sense-def { display: block; }
/* A reviewed word's individual Hanzi/Kanji, each linking to its character
   page. Glyphs render a step larger than the label so they stay legible. */
.review-answer-chars { font-size: 13px; margin-top: 6px; }
.review-char-link { margin-left: 6px; font-family: var(--han); font-size: 17px; line-height: 1; }
.review-char-link[lang="ja"] { font-family: var(--kana); }
.review-char-link:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; border-radius: 3px; }
/* Pre-answer recency ("Last asked: never / 3.0d ago") inside the reveal. */
.review-last-asked { font-size: 13px; margin-top: 6px; font-variant-numeric: tabular-nums; }
/* Exact persisted schedule reported after an immediately committed wrong MCQ. */
.review-next-shown { margin: -4px 0 2px; text-align: center; font-size: 14px; font-variant-numeric: tabular-nums; }
/* Compact post-answer actions: burn (mark known forever) and, on recall
   items, drop recall practice. Quiet by default so they never compete with
   the grade or spike buttons. */
.review-burn, .review-drop-recall { display: block; margin: 10px auto 0; padding: 4px 14px; font-size: 13px; color: var(--muted); }
.review-burn:hover { color: var(--red); }
.review-drop-recall:hover { color: var(--ink); }
.review-drop-note { display: block; margin-top: 10px; text-align: center; font-size: 13px; }
.review-grades { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.review-grades button { border-radius: var(--radius-control); }
.review-failed-aspect { margin: 12px 0 0; padding: 0; border: 0; min-width: 0; }
.review-failed-aspect legend { margin-bottom: 8px; font-weight: 600; }
.review-failed-aspect-options { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.review-failed-aspect-options button { min-height: 44px; padding-inline: 8px; border-radius: var(--radius-control); }
/* The projected next interval on Good/Easy. Quieter and tabular so the grade
   word stays the button; nowrap keeps "· 12d" as one token, and the button
   may wrap between word and preview on narrow phones instead of overflowing. */
.grade-preview { font-size: 12px; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.grade-again { color: var(--red); }
/* Tucked-away link from the revealed answer to the item's drilldown page. */
.review-details { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; font-size: 13px; margin-top: 4px; }
.review-details-link { color: var(--muted); }
.review-details-link:hover { color: var(--ink); }
.review-context-toggle { min-height: 0; padding: 0; border: 0; background: none; color: var(--muted); font: inherit; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 0.18em; }
.review-context-toggle:hover, .review-context-toggle.active { color: var(--ink); }
.review-context-toggle.active::after { content: " · on"; text-decoration: none; }
.review-empty { padding: 32px 18px; border: 1px dashed var(--line); border-radius: var(--radius-card); text-align: center; }
.review-empty h2 { margin: 0; }

/* Manage insights: exact inventory/schedule figures and small semantic,
   dependency-free charts. Every plotted value is also exposed as text or an
   aria-label; colour is only a secondary distinction. Cards reuse the
   review/dictionary paper treatment and collapse to one column on phones. */
.manage-insights { margin-bottom: 28px; }
.insights-heading { margin-bottom: 12px; }
.insights-heading h2, .srs-list-head h2 { margin: 0; font-size: 20px; }
.insights-heading p, .srs-page-summary { margin: 2px 0 0; font-size: 13px; }
.insight-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 8px;
  margin: 0 0 10px;
}
.insight-stat {
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
}
.insight-stat dt {
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.insight-stat dd { margin: 1px 0 0; }
.insight-stat-value {
  display: block;
  color: var(--brown);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.insight-stat small { display: block; overflow: hidden; color: var(--muted); font-size: 11px; font-weight: 400; white-space: nowrap; text-overflow: ellipsis; }
.insights-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.insight-card {
  min-width: 0;
  margin: 0;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--card);
  box-shadow: 0 5px 18px rgba(25, 21, 18, 0.035);
}
.insight-card h3 { margin: 0; font-size: 16px; }
.insight-card figcaption > p { margin: 1px 0 0; font-size: 12px; }
.insight-card .insight-note { margin-top: 5px; color: var(--brown); }
.insight-columns {
  display: grid;
  grid-template-columns: repeat(15, minmax(22px, 1fr));
  gap: 4px;
  margin: 12px 0 0;
  padding: 0 0 3px;
  overflow-x: auto;
  list-style: none;
  scrollbar-width: thin;
}
.activity-columns { grid-template-columns: repeat(14, minmax(22px, 1fr)); }
.insight-columns li {
  display: grid;
  grid-template-rows: 17px 112px 18px;
  min-width: 0;
  text-align: center;
}
.chart-value, .chart-tick { color: var(--muted); font-size: 9px; font-variant-numeric: tabular-nums; }
.chart-value { align-self: end; }
.chart-tick { padding-top: 3px; overflow: hidden; white-space: nowrap; }
.chart-column-slot {
  position: relative;
  display: block;
  min-width: 0;
  border-bottom: 1px solid var(--line);
  background: rgba(216, 200, 181, 0.16);
}
.chart-bar {
  position: absolute;
  right: 3px;
  bottom: 0;
  left: 3px;
  display: block;
  min-height: 2px;
  background: var(--brown);
}
.chart-bar.chart-empty, .strength-fill.chart-empty { min-height: 0; min-width: 0; }
.forecast-bar { border-radius: 3px 3px 0 0; }
.forecast-columns li:first-child .forecast-bar { background: var(--red); }
.activity-pass { background: var(--brown); }
.activity-again { background: var(--red); }
.chart-legend {
  display: flex;
  gap: 14px;
  justify-content: flex-end;
  margin: 7px 0 0;
  padding: 0;
  color: var(--muted);
  font-size: 11px;
  list-style: none;
}
.chart-legend li { display: inline-flex; align-items: center; gap: 5px; }
.legend-swatch { width: 9px; height: 9px; border-radius: 2px; }
.strength-card { grid-column: 1 / -1; }
.strength-bars {
  display: grid;
  grid-template-columns: repeat(2, minmax(260px, 1fr));
  gap: 7px 24px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}
.strength-bars li {
  display: grid;
  grid-template-columns: minmax(90px, auto) minmax(80px, 1fr) 3ch;
  gap: 8px;
  align-items: center;
  font-size: 12px;
}
.strength-label { color: var(--muted); }
.strength-track { height: 9px; overflow: hidden; border-radius: 999px; background: rgba(216, 200, 181, 0.45); }
.strength-fill { display: block; min-width: 3px; height: 100%; border-radius: inherit; background: var(--brown); }
.strength-fill.out-of-queue { background: var(--unseen); }
.strength-fill.strength-burned { background: var(--gold); }
.strength-count { text-align: right; font-variant-numeric: tabular-nums; }

/* Manage: one dense semantic table row per item. The written form is the
   only visible label; schedule state rides beside it as a compact token and
   the full detail (profile, learning step, lifetime counters) lives in the
   row title. Recency columns are tabular numerals so they scan vertically.
   The wrapper only becomes a horizontal scroller on narrow screens (below):
   an overflow container would also capture the header's sticky top, so on
   desktop the table sits in normal flow and the header pins to the page. */
.srs-list { margin-bottom: 24px; }
.srs-list-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.srs-sort { display: inline-flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; font-weight: 700; }
.srs-sort select {
  max-width: 220px;
  padding: 7px 30px 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  text-transform: none;
}
.srs-table { width: 100%; border-collapse: collapse; }
.srs-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--paper);
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.srs-table td { padding: 3px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.srs-table .srs-word { font-size: 20px; }
.srs-item-link { font-family: var(--han); }
.srs-row.suspended .srs-item-link { color: var(--muted); }
/* Compact schedule-state token beside the written form. */
.srs-status { margin-left: 8px; font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); }
.srs-status.srs-due { color: var(--red); }
.srs-when { font-size: 13px; font-variant-numeric: tabular-nums; }
.srs-when.never, .srs-when.na { color: var(--unseen); }
/* Item-level burn state token. */
.srs-burned { color: var(--gold); font-weight: 600; }
.srs-actions { text-align: right; }
.srs-actions button { padding: 4px 10px; margin-left: 4px; font-size: 12px; min-height: 32px; }
.srs-pagination { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 14px; }
.srs-pagination span { min-width: 88px; text-align: center; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.srs-pagination button { min-height: 38px; min-width: 88px; }

/* Below the manage table's natural width it scrolls sideways inside its
   wrapper with the Word column pinned; a scroll container disables the
   header's sticky top, so the pinned column takes over as the row anchor. */
@media (max-width: 760px) {
  .srs-table-wrap { overflow-x: auto; }
  .srs-table th:first-child, .srs-table .srs-word { position: sticky; left: 0; z-index: 1; background: var(--paper); }
  .srs-table th:first-child { z-index: 2; }
}
.srs-db-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.srs-db-actions input[type="file"] { max-width: 250px; }
.srs-db-actions select { max-width: 280px; padding: var(--pad-control-y) 10px; border: 1px solid var(--line); border-radius: var(--radius-control); background: var(--card); }
.srs-message { color: var(--red); }

/* Per-item drilldown (#/review/item/<key>): the item's identity up top, then
   labelled subsections for profile, schedule facts, per-direction evidence,
   and the persisted answer ledger. Facts are label/value pairs that wrap into
   columns where the width allows and stack on phones. */
.srs-item-detail { max-width: 720px; margin: 0 auto; }
.srs-detail-word { font-family: var(--han); font-size: clamp(34px, 9vw, 54px); line-height: 1.2; overflow-wrap: anywhere; }
.srs-detail-word[lang="ja"] { font-family: var(--kana); }
.srs-detail-word .srs-status { font-size: 12px; vertical-align: middle; }
.srs-detail-gloss, .srs-detail-kind { margin: 2px 0 0; font-size: 14px; }
.srs-facts { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 4px 20px; margin: 0; }
.srs-fact { display: flex; gap: 8px; align-items: baseline; }
.srs-fact dt { flex: none; min-width: 108px; font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.srs-fact dd { margin: 0; font-variant-numeric: tabular-nums; }
.srs-detail-actions { margin-top: 10px; }
.srs-detail-actions button { padding: 4px 12px; font-size: 13px; min-height: 32px; }
.srs-context-toggle { padding: 6px 12px; font-size: 13px; min-height: 34px; }
.srs-context-toggle.active { border-color: var(--gold); background: rgba(199, 149, 66, 0.14); }
/* The ledger's question prose may wrap; the recency column stays one token. */
.srs-hist-table td { white-space: normal; }
.srs-hist-table .srs-when { white-space: nowrap; }
.srs-hist-grade { font-variant-numeric: tabular-nums; }
.srs-hist-grade[data-grade="again"] { color: var(--red); }
.srs-hist-grade[data-grade="easy"], .srs-hist-grade[data-grade="elapsed"] { color: var(--gold); }
.srs-hist-failed { min-width: 116px; }
.srs-hist-note { margin: 0 0 6px; font-size: 13px; }

@media (max-width: 560px) {
  .hero { flex-direction: column; align-items: flex-start; gap: 8px; text-align: left; }
  /* On narrow screens the result row becomes a wrapping flex line: hanzi,
     pinyin and the property columns sit together and wrap as needed (so nothing
     overflows), while the meaning drops to its own full-width line. This works
     regardless of how many property columns are enabled. */
  .result-word {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
  }
  .rw-m { flex-basis: 100%; order: 5; color: var(--muted); }
  /* Sticky primary nav. The header box dissolves (display: contents) so the
     brand is ordinary page content that scrolls away, while the tabs bar pins
     to the top of the viewport on its own. Pinning the tabs rather than the
     whole header keeps the sticky bar compact without any height jump when it
     engages. The bar goes full-bleed by cancelling the wrap's side padding,
     and owns the safe-area top inset: the extra padding keeps the tab pills
     below the iPhone status bar while stuck, and the matching negative margin
     cancels it again in normal flow so the resting layout is unchanged. */
  .nav { display: contents; }
  .brand { width: 100%; margin: 0 0 6px; }
  .tabs {
    position: sticky;
    z-index: 10;
    top: 0;
    margin: 0 calc(-1 * var(--pad-x)) 20px;
    margin-top: calc(-1 * env(safe-area-inset-top, 0px));
    padding: 8px var(--pad-x);
    padding-top: calc(8px + env(safe-area-inset-top, 0px));
    border-bottom: 1px solid var(--line);
    gap: 1px;
    justify-content: space-between;
  }
  .tab { min-height: 34px; padding: 6px 8px; font-size: 14px; }
  /* The due badge rides along on phones too: away from the review page it is
     the only count on screen, and the queue it reports is the one thing the
     nav is for. Four tabs plus a pill are tight at 320px, so the pill goes
     compact - it still has to hold a four-digit inbox without clipping. */
  .tab-count { min-width: 16px; height: 16px; margin-left: 4px; padding: 0 4px; font-size: 10px; }
  /* Chrome that only makes sense while pinned over scrolling content; the
     .stuck class is toggled by an IntersectionObserver in js/navbar.js.
     Flat solid paper, like the search tabs - no blur or gradient
     show-through. */
  .tabs.stuck {
    background: var(--paper);
    box-shadow: 0 1px 10px rgba(25, 21, 18, 0.1);
  }
  /* The language switcher goes full-bleed under the pinned nav, like the nav
     itself, so results scroll cleanly under it edge to edge. */
  .lang-tabs {
    margin: 0 calc(-1 * var(--pad-x));
    padding: 8px var(--pad-x);
  }
  /* The title plus two segmented controls (language, review/manage) do not
     fit one phone row; wrap the controls onto their own full-width line,
     where their count badges wrap them again into two comfortable rows
     rather than squeezing four labels into one. */
  .review-head { flex-wrap: wrap; align-items: flex-start; row-gap: 10px; }
  .review-head-controls { width: 100%; justify-content: flex-start; }
  .review-head-controls .seg-btn { padding: 7px 12px; font-size: 14px; }
  .review-card { margin: 0 calc(-1 * min(8px, var(--pad-x))); padding: 18px 14px; }
  .review-options { grid-template-columns: 1fr; }
  .insight-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .insights-grid { grid-template-columns: 1fr; }
  .insight-card { padding: 12px; }
  .strength-card { grid-column: auto; }
  .strength-bars { grid-template-columns: 1fr; }
  .srs-list-head { align-items: flex-start; flex-direction: column; }
  .srs-sort { width: 100%; justify-content: space-between; }
  .srs-sort select { flex: 1; }
  /* Phone-width manage table: tighter cells; the horizontal scroller and
     pinned Word column come from the 760px rule above. */
  .srs-table th, .srs-table td { padding-left: 6px; padding-right: 6px; }
  .srs-table .srs-word { font-size: 18px; }
}

/* Narrow phones (iPhone SE and the like). Four tabs plus the due pill do not
   fit while the bar also keeps the reading column's 22px side padding, and
   the count is worth more there than the alignment: the full-bleed bar gives
   up some of its padding first (never the safe-area inset), then the tabs
   give up a little of their own. The label size and 34px tap height are the
   two things that do not move. */
@media (max-width: 380px) {
  .tabs {
    padding-left: max(14px, env(safe-area-inset-left, 0px));
    padding-right: max(14px, env(safe-area-inset-right, 0px));
    gap: 0;
  }
  .tab { padding-left: 6px; padding-right: 6px; }
}

/* Offline/update status pill (js/pwa.js): a small corner notice for the
   initial offline download and for a new version being ready. */
.pwa-pill {
  position: fixed;
  right: max(14px, env(safe-area-inset-right, 0px));
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  z-index: 20;
  padding: 6px 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(25, 21, 18, 0.14);
  color: var(--muted);
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
}
.pwa-pill.show { opacity: 1; pointer-events: auto; }
.pwa-pill.clickable { cursor: pointer; color: var(--ink); }
