/* ============================================================
   Tandem — design system
   Deep near-black + jade accent; Bricolage Grotesque display,
   Inter body, Noto Serif SC for all hanzi.
   ============================================================ */
:root {
  color-scheme: dark;   /* native controls (select dropdowns, scrollbars) render dark */
  --bg:            #0B0D12;
  --surface:       #14171F;
  --surface-2:     #1B1F29;
  --surface-hover: #20252F;
  --border:        #262B37;
  --border-strong: #333A49;
  --text:          #EDEFF4;
  --text-muted:    #9AA3B2;
  --accent:        #38D9A9;   /* jade */
  --accent-strong: #2BC496;
  --accent-weak:   rgba(56, 217, 169, 0.12);
  --danger:        #FF6B6B;
  --success:       #4FD6A0;

  --font-display: "Bricolage Grotesque", system-ui, sans-serif;
  --font-body:    "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-zh:      "Noto Serif SC", var(--font-body);

  --r-sm: 8px; --r-md: 12px; --r-lg: 18px; --r-pill: 999px;
  --sidebar-w: 248px;
  --maxw: 760px;
  --maxw-wide: 1180px;   /* data tables; 760px is a prose/form width */
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }   /* must beat the input{display:block} rule below */
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.015em; line-height: 1.15; margin: 0 0 10px; }
h1 { font-size: clamp(22px, 3.5vw, 28px); }
h2 { font-size: 20px; }
p  { margin: 0 0 12px; }
a  { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-strong); }
strong { font-weight: 600; }
:lang(zh) { font-family: var(--font-zh); }

.muted   { color: var(--text-muted); }
.error   { color: var(--danger); }
.success { color: var(--success); }

/* ---------- Ambient watermark ---------- */
.watermark {
  /* vmin sizing keys off the SMALLER viewport axis, so the glyph can never be
     wider than the screen (a fixed element's overflow isn't clipped by
     body{overflow-x:hidden}). Pinned flush to the corner, no negative offset. */
  position: fixed; right: 0; bottom: 0;
  font-family: var(--font-zh); font-size: 40vmin; line-height: 0.8;
  color: #fff; opacity: 0.025; pointer-events: none; user-select: none; z-index: 0;
}

/* ---------- App shell ----------
   The shell owns the viewport (height:100vh, overflow:hidden); the sidebar is
   a fixed-height flex child and the main column scrolls internally. This is the
   app-shell model — the document itself never scrolls, so the sidebar can never
   drift (the old position:sticky broke because html/body overflow-x:hidden had
   promoted them to the scroll container). */
.shell { display: flex; height: 100vh; overflow: hidden; position: relative; z-index: 1; }

.sidebar {
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w);
  background: var(--surface); border-right: 1px solid var(--border);
  padding: 22px 16px; display: flex; flex-direction: column; gap: 6px;
  height: 100vh; overflow-y: auto;
}
.brand {
  font-family: var(--font-display); font-weight: 700; font-size: 20px;
  color: var(--text); letter-spacing: -0.02em; padding: 4px 8px 18px;
  display: flex; align-items: baseline; gap: 8px;
}
.brand:hover { color: var(--text); }
.brand-zh { color: var(--accent); font-size: 18px; }

.nav { display: flex; flex-direction: column; gap: 4px; }
.nav-item {
  display: flex; align-items: center; gap: 12px;
  color: var(--text-muted); font-weight: 700; font-size: 12.5px;
  text-transform: uppercase; letter-spacing: .04em;
  padding: 9px 12px; border-radius: var(--r-md); border: 1px solid transparent;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.nav-item .nav-ico { width: 26px; height: 26px; flex: 0 0 auto; }
.nav-item:hover { background: var(--surface-hover); color: var(--text); }
.nav-item.active { color: var(--text); background: var(--accent-weak); border-color: var(--accent); }
.logout { margin-top: auto; padding-top: 16px; }

/* Interface-language switch (sidebar footer) */
.lang-switch { display: flex; gap: 4px; margin-top: 12px; }
.lang-switch button {
  flex: 1; padding: 6px 4px; font-family: var(--font-body);
  font-size: 12px; font-weight: 600; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: transparent; color: var(--text-muted); cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.lang-switch button:hover { color: var(--text); border-color: var(--border-strong); }
.lang-switch button[aria-current="true"] { color: var(--text); border-color: var(--accent); background: var(--accent-weak); }

/* Icons8 attribution (license requirement) */
.credit { margin-top: auto; padding-top: 16px; text-align: center; font-size: 11px; color: var(--text-muted); opacity: .55; }
.credit a { color: var(--text-muted); text-decoration: none; }
.credit a:hover { color: var(--text); text-decoration: none; }

.main-col { flex: 1 1 auto; min-width: 0; height: 100vh; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; }
.content { flex: 1; padding: 40px 32px; }
.content-inner { max-width: var(--maxw); margin: 0 auto; }

/* Mobile top bar (hidden on desktop) */
.topbar { display: none; }
.burger {
  display: inline-flex; flex-direction: column; gap: 4px; justify-content: center;
  width: 40px; height: 40px; padding: 10px; border-radius: var(--r-sm); cursor: pointer;
}
.burger span { height: 2px; background: var(--text); border-radius: 2px; transition: .2s; }
.topbar-brand { font-family: var(--font-display); font-weight: 700; font-size: 17px; color: var(--text); }
.topbar-brand span { color: var(--accent); }

/* ---------- Centered (login) ---------- */
.content-centered {
  min-height: 100vh; display: grid; place-items: center; padding: 24px; position: relative; z-index: 1;
}

/* ---------- Cards ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 26px; width: 100%; max-width: var(--maxw);
}
.content-inner { width: 100%; }
.lib-list a, .lib-name { min-width: 0; }
.card + .card { margin-top: 20px; }
/* A wide card (data tables) also has to lift the page shell's cap, or
   .content-inner keeps it at 760px. Cards without the modifier keep --maxw.
   Browsers without :has() simply keep the old width and its scrollbar. */
.card--wide { max-width: var(--maxw-wide); }
.content-inner:has(.card--wide) { max-width: var(--maxw-wide); }
.auth-card { width: 100%; max-width: 400px; }
.auth-card h1 { display: flex; align-items: baseline; gap: 10px; }
.auth-card h1::after { content: "双"; font-family: var(--font-zh); font-size: 20px; color: var(--accent); font-weight: 500; }

.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.section-head__title { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.count-badge {
  font-family: var(--font-body); font-size: 12px; font-weight: 600; color: var(--accent);
  background: var(--accent-weak); padding: 2px 9px; border-radius: var(--r-pill);
}

/* Page header band: one h1 + subtitle per page (A3) */
.page-head { margin-bottom: 22px; }
.page-head h1 { margin-bottom: 4px; }
.page-head .subtitle { color: var(--text-muted); margin: 0; }
.quiet { opacity: .85; }
.card.quiet { background: transparent; border-style: dashed; }
.friend-code { font-size: 15px; letter-spacing: .05em; }

.copy-chip {
  display: inline-flex; align-items: center; gap: 14px; margin-top: 10px;
  padding: 10px 16px; background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); cursor: pointer; color: var(--text);
  transition: border-color .15s, background .15s; font-family: var(--font-body);
}
.copy-chip:hover { border-color: var(--accent); background: var(--surface-hover); }
.copy-chip .friend-code { background: none; padding: 0; color: var(--text); font-size: 18px; letter-spacing: .12em; }
.copy-chip__hint { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.copy-chip.is-copied { border-color: var(--accent); }
.copy-chip.is-copied .copy-chip__hint { color: var(--accent); }

/* ---------- Gamification chip row (B1 / Visual #5) ---------- */
.chip-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; }
.chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--border);
  font-size: 13px; color: var(--text-muted);
}
.chip b { color: var(--text); font-weight: 700; }
.chip .icon-img { width: 20px; height: 20px; }
.goal-ring {
  --pct: 0; width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; position: relative;
  background: conic-gradient(var(--accent) calc(var(--pct) * 1%), var(--surface-hover) 0);
}
.goal-ring::after { content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--surface); }

/* ---------- Due-count CTA tile (Home) ---------- */
.cta-card { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; }
.cta-card__num { font-family: var(--font-display); font-weight: 700; font-size: clamp(36px, 8vw, 52px); line-height: 1; color: var(--accent); }

/* ---------- Stat tiles (Profile) ---------- */
.stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; margin-top: 6px; }
.stat-tile { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-md); padding: 16px; text-align: center; }
.stat-tile__num { display: block; font-family: var(--font-display); font-weight: 700; font-size: 30px; color: var(--text); }
.stat-tile__label { display: block; font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }

/* ---------- Mastery distribution bar (Home) ---------- */
.dist-bar { display: flex; height: 16px; border-radius: var(--r-pill); overflow: hidden; background: var(--surface-hover); margin-top: 4px; }
.dist-seg { height: 100%; }
.dist-seg--new { background: var(--text-muted); }
.dist-seg--learning { background: #4e9e86; }
.dist-seg--known { background: var(--accent); }
.dist-seg--mastered { background: var(--accent-strong); }
.dist-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 14px; }
.dist-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-muted); }
.dist-legend b { color: var(--text); }
.dist-dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }

/* Wide language switch (Profile) — full language names */
.lang-switch--wide { max-width: 360px; }
.lang-switch--wide button { padding: 9px 10px; font-size: 13px; }

/* ---------- Forms ---------- */
label { display: block; margin: 16px 0 0; color: var(--text-muted); font-size: 13px; font-weight: 500; }
input, textarea {
  display: block; width: 100%; margin-top: 7px; padding: 11px 13px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-family: var(--font-body); font-size: 15px; transition: border-color .15s, box-shadow .15s;
}
input:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
textarea { font-family: ui-monospace, "SF Mono", Menlo, monospace; resize: vertical; }
::placeholder { color: #5a6373; }
/* Checkboxes/radios must not inherit the full-width input rule above */
input[type="checkbox"], input[type="radio"] { width: auto; display: inline-block; margin: 0; accent-color: var(--accent); }

/* Search field with a leading icon */
.search-field { position: relative; margin: 4px 0 18px; }
.search-field__icon { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--text-muted); pointer-events: none; }
.search-field input { margin: 0; padding-left: 40px; }

/* ---------- Alerts / toasts ---------- */
.alert {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--surface-2);
  margin-bottom: 16px; font-size: 14px;
}
.alert .icon { margin-top: 1px; flex: 0 0 auto; }
.alert--success { border-color: rgba(79,214,160,.4); background: rgba(79,214,160,.10); color: var(--success); }
.alert--error   { border-color: rgba(255,107,107,.4); background: rgba(255,107,107,.10); color: var(--danger); }
.alert--info    { border-color: var(--border-strong); color: var(--text); }

/* Input + inline action (e.g. "suggest translation" beside the German field) */
.field-suggest { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin-top: 7px; }
.field-suggest input { margin-top: 0; width: 100%; }
.field-suggest .btn { margin-top: 0; white-space: nowrap; }

/* ---------- Icons ---------- */
.icon { width: 20px; height: 20px; display: inline-block; vertical-align: -0.15em; flex: 0 0 auto; }
.icon-img { display: inline-block; vertical-align: middle; flex: 0 0 auto; }

/* ---------- Buttons ----------
   Canonical system: `.btn` + a `.btn--*` modifier. The signature look is the
   chunky tactile primary — a solid darker under-lip (box-shadow) that
   compresses on :active. Legacy `.btn-primary` / `.btn-ghost` are kept working
   and inherit the tactile look, so forms/grade rows not yet reworked still
   render correctly during the phased overhaul. */
.btn, .btn-primary, .btn-ghost {
  font-family: var(--font-body); font-size: 14.5px; font-weight: 600;
  border: 1px solid transparent; border-radius: var(--r-md); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 20px; line-height: 1.1; text-decoration: none; white-space: nowrap;
  -webkit-user-select: none; user-select: none;
  transition: transform .06s ease, box-shadow .06s ease,
              background .15s ease, color .15s ease, border-color .15s ease;
}
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; }

/* Primary — filled jade, tactile under-lip (uppercase on the new class) */
.btn--primary, .btn-primary {
  background: var(--accent); color: #06231A; border-color: var(--accent);
  font-weight: 700; box-shadow: 0 4px 0 var(--accent-strong);
}
.btn--primary { text-transform: uppercase; letter-spacing: .03em; }
.btn--primary:hover, .btn-primary:hover { background: var(--accent-strong); color: #06231A; }
.btn--primary:active, .btn-primary:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--accent-strong); }
.btn-primary { margin-top: 18px; }            /* legacy: sits under a form field */

/* Secondary — outlined surface, subtler lip (the old ghost) */
.btn--secondary, .btn-ghost {
  background: var(--surface-2); color: var(--text); border-color: var(--border-strong);
  box-shadow: 0 3px 0 var(--border);
}
.btn--secondary:hover, .btn-ghost:hover { background: var(--surface-hover); color: var(--text); }
.btn--secondary:active, .btn-ghost:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--border); }
.btn-ghost { width: 100%; }                    /* legacy: full-width in sidebar/reveal */

/* Subtle — genuine tertiary, no lip */
.btn--subtle {
  background: transparent; color: var(--text-muted); border-color: transparent; padding: 8px 12px;
  box-shadow: none;
}
.btn--subtle:hover { background: var(--surface-hover); color: var(--text); }

/* Link — reads as a link but is a real element in the button system */
.btn--link {
  background: transparent; border-color: transparent; color: var(--accent);
  padding: 4px 6px; box-shadow: none;
}
.btn--link:hover { color: var(--accent-strong); text-decoration: underline; }

.btn--danger { color: var(--danger); }
.btn--sm { font-size: 13px; padding: 8px 13px; }
.btn--block { width: 100%; }
/* Form submit buttons sit under a field — clear the tactile under-lip shadow.
   Study grade buttons are exempt (their own reset zeroes this). */
form > .btn--primary { margin-top: 18px; }

/* Icon-only button (row actions, toolbars) */
.btn--icon {
  padding: 8px; border-radius: var(--r-sm);
  background: transparent; border-color: transparent; color: var(--text-muted); box-shadow: none;
}
.btn--icon:hover { background: var(--surface-hover); color: var(--text); }
.btn--icon.btn--danger:hover { background: rgba(255,107,107,.14); color: var(--danger); }

/* Icons sit a touch smaller inside buttons */
.btn .icon, .btn-primary .icon, .btn-ghost .icon { width: 18px; height: 18px; }
.btn .icon-img, .btn-primary .icon-img, .btn-ghost .icon-img { width: 20px; height: 20px; }

/* ---------- Button groups / toolbars ---------- */
.btn-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.btn-group--end { justify-content: flex-end; }

/* ---------- Vocabulary table ---------- */
.vocab-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.vocab-table th, .vocab-table td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--border); }
.vocab-table thead th { color: var(--text-muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.vocab-table tbody tr { transition: background .12s; }
.vocab-table tbody tr:hover { background: var(--surface-2); }
.vocab-table .hanzi { font-family: var(--font-zh); font-size: 20px; }
/* Kept as a safety net only — the wide card plus cell clipping below should
   mean it never actually engages. */
.table-scroll { overflow-x: auto; }
/* Overlong translations clip instead of widening the table. The full string is
   in a title attribute, and the word-detail modal shows it in full. The cap is
   on the cell so the trailing "shared" tag is never clipped with the text.
   Sized for the 1180px table: the other six columns take ~750px, so this
   spends the remaining slack rather than clipping while the table has room. */
.vocab-table .german-cell { max-width: 460px; }
.vocab-table .german-cell > .cell-clip {
  display: inline-block; max-width: 100%; vertical-align: bottom;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tag { font-size: 11px; color: var(--accent); background: var(--accent-weak); padding: 2px 8px; border-radius: var(--r-sm); }

/* Multi-translation chips. One chip per `;`-separated translation, so three
   meanings read as three things rather than one long string.
   Deliberately NOT called `.chip` — that name is already the home page's
   gamification chip row (app.php), and these need to work in the vocab table,
   the study card back, word detail and the friend lobby, so they cannot be
   scoped to one container either. */
.tchip-row { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.tchip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  background: var(--chip-bg, rgba(127, 127, 127, .14));
  font-size: .92em; line-height: 1.5; white-space: nowrap;
}
.tchip--removable { padding-right: 4px; }
.tchip__x {
  border: 0; background: none; cursor: pointer; padding: 0 4px;
  font-size: 1.05em; line-height: 1; color: inherit; opacity: .6;
}
.tchip__x:hover, .tchip__x:focus-visible { opacity: 1; }

/* Rows carrying a per-user override — visible at a glance which words you
   have diverged from the shared canonical version. */
.tag--edited { opacity: .7; font-style: italic; }

/* Inline edit mode. */
.vocab-table tr.is-editing { background: var(--row-edit-bg, rgba(127, 127, 127, .06)); }
.edit-input { width: 100%; max-width: 32ch; }
.edit-input--add { margin-top: 4px; }
.edit-hint { margin: 4px 0 0; font-size: .85em; }
.edit-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }

@media (max-width: 700px) {
  .tchip { white-space: normal; }
  .edit-input { max-width: 100%; }
}

/* Selectable rows (library import, vocab deck multi-select) */
.check-cell { width: 1%; white-space: nowrap; }
.check-cell input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; box-sizing: border-box; flex: 0 0 auto;
  width: 18px; height: 18px; min-width: 18px; padding: 0; margin: 0; cursor: pointer;
  border: 1px solid var(--border-strong); border-radius: 5px; background: var(--surface-2);
  position: relative; transition: background .15s, border-color .15s;
}
.check-cell input[type="checkbox"]:hover { border-color: var(--accent); }
.check-cell input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); }
.check-cell input[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 5px; top: 2px; width: 4px; height: 8px;
  border: solid #06231A; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.sr-select { display: inline-flex; padding: 8px; margin: -8px; cursor: pointer; }
.vocab-table tbody tr:has(input[type="checkbox"][name="ids[]"]) { cursor: pointer; }
.vocab-table tbody tr.is-selected { background: var(--accent-weak); }
.vocab-table tbody tr.is-selected:hover { background: var(--accent-weak); }
/* No-JS selected highlight for browsers that support :has() */
@supports selector(:has(*)) {
  .vocab-table tbody tr:has(input[name="ids[]"]:checked) { background: var(--accent-weak); }
}

/* Deliberately NOT display:flex — flexing a <td> wraps it in an anonymous
   table-cell box, which stops `width: 1%` from shrink-to-fitting the column.
   The two controls flow inline instead; `.row-edit` carries the gap. */
.vocab-table .row-actions {
  text-align: right; width: 1%; white-space: nowrap;
}
.vocab-table .row-actions .row-edit { margin-right: 4px; vertical-align: middle; }
.delete-form { display: inline; margin: 0; }

/* Bulk action bar (vocab deck multi-select) */
.bulk-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 12px;
  background: var(--surface-2); border: 1px solid var(--accent); border-radius: var(--r-md);
}
.bulk-bar__count { font-size: 13px; color: var(--text-muted); }
.bulk-bar__count b { color: var(--text); }
.bulk-bar__select {
  width: auto; margin: 0; padding: 8px 10px; font-size: 13px;
  background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--r-sm); color: var(--text);
}
.bulk-bar .btn--icon.btn--danger { margin-left: auto; gap: 6px; padding: 8px 12px; font-size: 13px; }

/* Group picker (add-word) + group chips */
.group-picker { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 7px; }
.group-chip {
  display: inline-flex; align-items: center; gap: 6px; margin: 0;
  padding: 6px 12px; border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-size: 13px; color: var(--text-muted); cursor: pointer; transition: border-color .15s, color .15s;
}
.group-chip:has(input:checked) { border-color: var(--accent); color: var(--text); background: var(--accent-weak); }
.group-chip:has(input:indeterminate) { border-color: var(--accent); color: var(--accent); }
/* Name-only toggle pill: hide the native checkbox, show state via the pill itself. */
.group-chip input { position: absolute; opacity: 0; width: 0; height: 0; padding: 0; margin: 0; }
.group-chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Mastery meter (deck rows, PLAN.md Decision 1) ---------- */
.mastery { display: inline-flex; align-items: center; gap: 8px; }
.mastery__meter { display: inline-flex; gap: 3px; }
.mastery__meter i { width: 7px; height: 15px; border-radius: 2px; background: var(--surface-hover); }
.mastery__label { font-size: 12px; font-weight: 600; color: var(--text-muted); white-space: nowrap; }
.mastery--new      .mastery__meter i.on { background: var(--text-muted); }
.mastery--learning .mastery__meter i.on { background: #4e9e86; }
.mastery--known    .mastery__meter i.on { background: var(--accent); }
.mastery--mastered .mastery__meter i.on { background: var(--accent-strong); }
.mastery--known .mastery__label,
.mastery--mastered .mastery__label { color: var(--accent); }

/* ---------- Vocab table: stacked cards on narrow screens (B2) ----------
   1500px is not arbitrary: the sidebar is 248px and the shell adds ~64px of
   padding, so a 1500px viewport leaves ~1188px of content — the first width at
   which the table can actually reach its --maxw-wide 1180px. Below that the
   table is squeezed under its design width and the columns crowd, so stacking
   beats a cramped table. */
@media (max-width: 1500px) {
  .table-scroll { overflow-x: visible; }   /* stacked cards fit the width — no horizontal scroll */
  /* Stacked cards have a full row each, so nothing needs clipping here. */
  .vocab-table--stack .german-cell { max-width: none; }
  .vocab-table--stack .german-cell > .cell-clip {
    max-width: none; overflow: visible; text-overflow: clip; white-space: normal;
  }
  .vocab-table--stack, .vocab-table--stack tbody { display: block; width: auto; }
  .vocab-table--stack thead { display: none; }
  .vocab-table--stack tr {
    display: flex; flex-wrap: wrap; align-items: center;
    border: 1px solid var(--border); border-radius: var(--r-md);
    background: var(--surface-2); padding: 6px 14px; margin-bottom: 10px;
    /* Skip layout and paint for rows scrolled out of view. At 800 rows the
       deck is ~20k DOM nodes and the browser was laying out all of them.
       This works HERE and not in the desktop table because size containment
       does not apply to internal table elements — in this block the row is a
       flex container, not a table-row.
       This class is shared by tables of DIFFERENT row shapes (vocab deck,
       learn-pick, library-category), so it carries the compact estimate and
       .vocab-table--deck below overrides it for the taller deck row. Compact
       value here is learn-pick/library-category's 4-cell row (check, hanzi,
       pinyin, german, no row-actions, no groups/mastery): tr padding+border
       14px + check-only header line 22px (18px checkbox + 2px×2 padding) +
       hanzi line 51px (24px font × 1.55 line-height + 7px×2 padding) +
       pinyin line 37px + german line 37px (both 15px body font × 1.55 +
       7px×2 padding) = 161px. The `auto` keyword makes the browser remember
       each row's true height after its first render, so this figure only
       governs rows that have never been laid out. If you add a cell to
       either row shape, recompute its number here. */
    content-visibility: auto;
    contain-intrinsic-size: auto 161px;
  }
  /* Deck row is taller: adds a row-actions header line and mastery/groups
     lines on top of the shared 4-cell shape above. ~208px bare, ~243px with
     a group tag (see Task 5/6 history); 220px leans low so late-measured
     rows push content down rather than yanking scroll position backwards. */
  .vocab-table--deck tr { contain-intrinsic-size: auto 220px; }
  /* each cell is its own full-width label:value row (order 2), wrapping below the header */
  .vocab-table--stack td {
    order: 2; flex: 1 1 100%; border: 0; padding: 7px 0;
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 14px;
  }
  .vocab-table--stack td > * { min-width: 0; }
  .vocab-table--stack td::before {
    content: attr(data-label); color: var(--text-muted);
    font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  }
  .vocab-table--stack td.hanzi { font-size: 24px; }
  .vocab-table--stack td.groups-cell:not(:has(.tag)) { display: none; }
  /* header row: checkbox (left, grows) + delete (right) share ONE flex line */
  .vocab-table--stack td.check-cell { order: 0; flex: 1 1 auto; justify-content: flex-start; padding: 2px 0; }
  .vocab-table--stack td.check-cell::before { content: none; }
  /* Edit + delete must stay on ONE line. The generic stacked-td rule above
     gives every cell `flex-wrap: wrap`, which since the edit control was added
     let this cell break into two lines — and because `tr` centres its items,
     the checkbox then floated against a double-height cell. */
  .vocab-table--stack td.row-actions {
    order: 1; flex: 0 0 auto; padding: 2px 0;
    flex-wrap: nowrap; justify-content: flex-end; gap: 8px;
    /* `width: 1%` on the base rule is a table shrink-to-fit hint; in flex
       layout it becomes the flex-basis and collapses the cell to ~11px, so
       its contents overflow and sit on top of the checkbox cell. */
    width: auto;
  }
  /* Undo the generic `td > * { min-width: 0 }` for these two: it lets them
     collapse below their content width and overlap each other. */
  .vocab-table--stack td.row-actions > * { flex: 0 0 auto; min-width: max-content; }
  .vocab-table--stack td.row-actions .row-edit { margin-right: 0; white-space: nowrap; }
  /* Edit mode in a stacked card: the generic `space-between` spreads label,
     input and hint across the row and strands the buttons. Stack them
     vertically under the label instead, each on its own full-width line. */
  .vocab-table--stack tr.is-editing td[data-german-cell],
  .vocab-table--stack tr.is-editing td[data-pinyin-cell] {
    justify-content: flex-start; align-items: flex-start; gap: 6px;
  }
  .vocab-table--stack tr.is-editing .edit-input { flex: 1 1 100%; max-width: none; }
  .vocab-table--stack tr.is-editing .edit-hint,
  .vocab-table--stack tr.is-editing .edit-actions { flex: 1 1 100%; margin: 0; }
  .vocab-table--stack td.row-actions::before { content: none; }
  .vocab-table--stack td.row-actions .btn--icon {
    width: 32px; height: 32px; padding: 0; border-radius: var(--r-sm);
    background: var(--surface); border: 1px solid var(--border); color: var(--text-muted);
  }
  .vocab-table--stack td.row-actions .btn--icon .icon { width: 18px; height: 18px; }
  .vocab-table--stack td.row-actions .btn--icon:hover { color: var(--danger); border-color: var(--danger); background: rgba(255,107,107,.12); }
}

/* ---------- Library list ---------- */
.lib-list { list-style: none; padding: 0; margin: 16px 0 0; display: grid; gap: 10px; }
.lib-list a {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 18px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text); transition: .15s ease;
}
.lib-list a:hover { border-color: var(--accent); background: var(--surface-hover); transform: translateY(-1px); }
.lib-list .lib-name { font-family: var(--font-display); font-weight: 600; font-size: 16px; }
.lib-list .lib-meta { color: var(--text-muted); font-size: 13px; }
.lib-list .chev { color: var(--text-muted); }

/* ---------- Choice cards (big icon + label, "pick one of several") ----------
   Reused by library categories (B4), study direction/mode pickers, onboarding. */
.choice-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); margin-top: 16px; }
.choice-card {
  display: flex; align-items: center; gap: 16px; width: 100%; text-align: left;
  padding: 16px 18px; border: 2px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface-2); color: var(--text); cursor: pointer;
  box-shadow: 0 4px 0 var(--border);
  transition: transform .06s ease, box-shadow .06s ease, border-color .15s ease;
}
.choice-card:hover { border-color: var(--accent); }
.choice-card:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--border); }
.choice-card--selected { border-color: var(--accent); box-shadow: 0 4px 0 var(--accent-strong); }
.choice-card__icon { flex: 0 0 auto; width: 44px; height: 44px; display: grid; place-items: center; }
.choice-card__icon .icon-img { width: 44px; height: 44px; }
.choice-card__body { min-width: 0; flex: 1; }
.choice-card__title { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.choice-card__meta { color: var(--text-muted); font-size: 13px; }
.choice-card .chev { color: var(--text-muted); margin-left: auto; }

/* ---------- Flashcard (study) ---------- */
.study-card { text-align: center; }
.study-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; font-size: 13px; }
.flash-front { font-size: clamp(28px, 6vw, 40px); font-weight: 600; font-family: var(--font-display); padding: 28px 0; }
.flash-front:lang(zh), .hanzi-big { font-family: var(--font-zh); }
.flash-back { padding: 6px 0 4px; border-top: 1px dashed var(--border-strong); margin-top: 8px; }
.hanzi-big { font-size: clamp(40px, 9vw, 56px); }
.german-big { font-size: clamp(26px, 6vw, 34px); font-weight: 600; font-family: var(--font-display); }
.grade-row { display: flex; gap: 12px; justify-content: center; margin-top: 24px; flex-wrap: wrap; }
.grade-row .btn-primary, .grade-row .btn-ghost { width: auto; margin-top: 0; padding: 11px 24px; }

code { font-family: ui-monospace, Menlo, monospace; font-size: 13px; background: var(--surface-2); padding: 2px 6px; border-radius: 5px; color: var(--text); }
.actions-row { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; }

/* ---------- Study focus mode (dedicated layout, B3 / Decision 2) ----------
   Study lives outside the shared shell: a full-viewport column with a top
   progress row, a centred card, and controls pinned to the bottom. Built as the
   integration point for friend mode (partner slot + data-study-controls seam). */
.focus {
  position: fixed; inset: 0; z-index: 5;
  display: flex; flex-direction: column;
  background: var(--bg);
}
.focus-top {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 20px; max-width: 900px; width: 100%; margin: 0 auto;
}
.focus-exit { flex: 0 0 auto; }
.focus-progress {
  flex: 1 1 auto; height: 12px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-pill); overflow: hidden;
}
.focus-progress__bar { height: 100%; background: var(--accent); border-radius: var(--r-pill); transition: width .35s ease; }
.focus-partner { flex: 0 0 auto; }

.seg-toggle {
  display: inline-flex; flex: 0 0 auto; gap: 2px; padding: 3px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-pill);
}
.seg-toggle__opt {
  padding: 6px 12px; border-radius: var(--r-pill); white-space: nowrap;
  font-size: 12.5px; font-weight: 700; color: var(--text-muted);
}
.seg-toggle__opt:hover { color: var(--text); }
.seg-toggle__opt.is-active { background: var(--accent-weak); color: var(--text); }

.focus-main { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: grid; place-items: center; padding: 20px; }
.focus-card { width: 100%; max-width: 520px; text-align: center; }
.focus-card .flash-front { font-size: clamp(30px, 7vw, 46px); }

.focus-bottom { border-top: 1px solid var(--border); background: var(--surface); }
.focus-bottom__inner { max-width: 520px; margin: 0 auto; padding: 18px 20px calc(18px + env(safe-area-inset-bottom)); }
.focus-bottom .grade-row { display: flex; gap: 12px; margin: 0; }
.focus-bottom .grade-row form { flex: 1 1 0; display: flex; margin: 0; }
.focus-bottom .grade-row .btn { margin: 0; }

.study-done { display: flex; flex-direction: column; align-items: center; }
.study-done__art { margin-bottom: 8px; }


/* Narrow phones: progress bar gets its own full-width row above exit + toggle */
@media (max-width: 520px) {
  .focus-top { flex-wrap: wrap; justify-content: space-between; padding: 14px 16px; }
  .focus-progress { order: -1; flex-basis: 100%; }
  .focus-bottom__inner { padding-left: 16px; padding-right: 16px; }
}

/* ---------- Accessibility ---------- */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 860px) {
  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 40;
    width: 82vw; max-width: 300px; height: 100vh;
    transform: translateX(-100%); transition: transform .22s ease;
    box-shadow: 0 0 0 100vmax rgba(0,0,0,0);
  }
  .nav-toggle:checked ~ .shell .sidebar { transform: translateX(0); box-shadow: 0 20px 60px rgba(0,0,0,.6), 0 0 0 100vmax rgba(0,0,0,.55); }
  .topbar {
    display: flex; align-items: center;
    padding: 10px 14px; border-bottom: 1px solid var(--border);
    background: var(--surface); position: sticky; top: 0; z-index: 20;
  }
  /* Absolutely centre the brand in the full bar — immune to the burger's width. */
  .topbar-brand { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
  .content { padding: 22px 18px; }
  .card { padding: 20px; }
}

.study-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; font-size: 13px; }
.study-card { text-align: center; }
.flash-front { font-size: 34px; font-weight: 600; padding: 24px 0; }
.flash-back { padding: 8px 0 4px; }
.hanzi-big { font-size: 44px; }
.german-big { font-size: 30px; font-weight: 600; }
.grade-row { display: flex; gap: 12px; justify-content: center; margin-top: 20px; }
.grade-row .btn-primary, .grade-row .btn-ghost { width: auto; padding: 10px 22px; }
/* Study speaker: a centered cluster below the card word (normal + slow). */
.speak-cluster { display: flex; gap: 10px; justify-content: center; align-items: center; margin-top: 18px; }
.speak-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  color: var(--text); cursor: pointer; padding: 8px 14px; line-height: 1;
  transition: border-color .15s, background .15s;
}
.speak-btn:hover { border-color: var(--accent); background: var(--surface-hover); }
.speak-btn .icon { width: 20px; height: 20px; }
.speak-btn__tag { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.speak-btn--slow:hover .speak-btn__tag { color: var(--text); }
.speak-error { opacity: 0.45; }

/* ---------- Feature 7a: friends card (Profile) ---------- */
.friend-list { list-style: none; margin: 0 0 14px; padding: 0; }
.friend-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--border);
}
.friend-row__name { font-weight: 600; flex: 1; min-width: 0; }
.friend-row form { margin-left: auto; }
.friend-add { margin-top: 14px; }

/* ---------- Feature 7a: learn entry / select-practice ---------- */
/* Category/group starters are POST forms whose submit button IS the card;
   display:contents keeps the form transparent to the grid layout. */
.choice-grid > form { display: contents; }
button.choice-card { font: inherit; }
.choice-card--disabled { opacity: .45; pointer-events: none; }
.choice-card--disabled:hover { border-color: var(--border); }
.choice-card__title .tag { margin-left: 6px; vertical-align: middle; }

/* ---------- Friend sessions (feature 7b) ----------
   Presence dot + invite rows + lobby bits reuse the shell components; the
   session additions (answer input, waiting note, suggested grade) live inside
   the existing study focus layout. */
.presence-dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 50%;
  background: var(--text-muted); vertical-align: middle; margin-right: 6px;
  transition: background 0.3s;
}
.presence-dot.is-online { background: var(--success); }

.invite-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 0; flex-wrap: wrap;
}
.invite-row + .invite-row { border-top: 1px solid var(--border); }
.invite-row__name { font-weight: 600; }

.focus-partner { display: flex; align-items: center; gap: 6px; }
.focus-partner__name { font-weight: 600; font-size: 0.9rem; }

.answer-input {
  width: 100%; padding: 12px 14px; margin-bottom: 10px;
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  font: inherit; background: var(--surface-2); color: var(--text);
}
.answer-input:disabled { opacity: 0.6; }

.waiting-note { text-align: center; padding: 14px; opacity: 0.75; }

.grade-row .is-suggested { outline: 3px solid currentColor; outline-offset: 2px; }

.friend-summary__scores p { margin: 4px 0; }

/* ---------- Friend session polish (feature 7b-polish) ---------- */
.friend-lobby, .friend-picker { display: flex; flex-direction: column; gap: 14px; }

.lobby-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.lobby-who { display: flex; align-items: center; gap: 8px; font-weight: 600; }

.word-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.word-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 8px 6px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-2);
}
.word-chip__hz { font-weight: 600; }
.word-chip__de { color: var(--text-muted); font-size: 0.9rem; }
.word-chip__x {
  border: none; background: transparent; color: var(--text-muted);
  font-size: 1.1rem; line-height: 1; cursor: pointer; padding: 0 4px;
}
.word-chip__x:hover { color: var(--text); }
.word-chip form { display: inline; }
/* One-sided words (only one partner knows them) */
.word-chip--one-sided { border-style: dashed; }
.word-chip__tag { font-size: 10.5px; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.03em; }
.word-chip__adopt { border: 0; background: transparent; color: var(--accent); font-family: inherit; font-size: 12px; font-weight: 700; cursor: pointer; padding: 0 4px; }
.word-chip__adopt:hover { text-decoration: underline; }

.pool-list { list-style: none; margin: 0; padding: 0; max-height: 320px; overflow-y: auto; }
.pool-item { display: flex; align-items: center; gap: 10px; padding: 7px 4px; }
.pool-item + .pool-item { border-top: 1px solid var(--border); }
.pool-item__hz { font-weight: 600; }

/* Lobby multi-select (post-roadmap): checkbox rows + per-pool bulk bar. */
.pool-bulk { margin-bottom: 10px; }
.pool-bulk__all { user-select: none; }
[data-pool-form] .pool-item { cursor: pointer; }
[data-pool-form] .pool-item:has(.row-select:checked) {
  background: var(--accent-weak); border-radius: var(--r-md);
}
.pool-item .row-select { flex: 0 0 auto; margin-right: 4px; accent-color: var(--accent); }

/* Lobby direction toggle (buttons rendered as segments) + conflict state */
button.seg-toggle__opt { border: 0; background: transparent; cursor: pointer; font-family: inherit; }
button.seg-toggle__opt.is-active { background: var(--accent-weak); }
.lobby-dir__partner { margin: 8px 0 0; }
.lobby-dir__error { color: var(--danger); font-weight: 600; margin: 8px 0 0; }
.lobby-dir.is-conflict { border-color: var(--danger); }
.invite-card__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

/* Reveal-phase hint (partner already pressed Show answer) */
.reveal-hint { color: var(--accent); font-weight: 600; text-align: center; margin: 0 0 12px; }
/* Lobby ready-section spacing (breathing room around the ready button + notes) */
.lobby-ready form p { margin: 0 0 12px; }
.lobby-ready > p { margin-top: 14px; }
/* Narrow screens: stack the invite card's name over a full-width button */
@media (max-width: 520px) {
  .invite-card__row { flex-direction: column; align-items: stretch; }
  .invite-card__row .btn { width: 100%; }
}

.lobby-ready { text-align: center; }

.invite-card { display: flex; flex-direction: column; gap: 10px; padding: 14px 0; }
.invite-card + .invite-card { border-top: 1px solid var(--border); }
.invite-card__name { font-weight: 700; font-size: 1.05rem; }
.invite-card__dir { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.invite-card__help { font-size: 0.85rem; margin: 0; }

.focus-exit-form { margin: 0; }

/* ---------- Word-detail modal (feature 8) ----------
   One scroll region only: the dialog is a fixed-height flex column and ONLY
   .word-modal__body scrolls (thin, themed bar). display:flex is scoped to
   [open] so the UA's closed-dialog display:none keeps working. */
.word-modal {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); color: var(--text);
  padding: 0; width: min(560px, calc(100vw - 32px));
  max-height: min(86vh, 760px); overflow: hidden;
}
.word-modal[open] { display: flex; flex-direction: column; }
.word-modal::backdrop { background: rgba(6, 8, 12, .65); backdrop-filter: blur(2px); }
.word-modal__body {
  padding: 26px; overflow-y: auto; min-height: 0;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
.word-modal__body::-webkit-scrollbar { width: 8px; }
.word-modal__body::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-pill); }
.word-modal__body::-webkit-scrollbar-track { background: transparent; }

/* Sections stack on a single rhythm; no per-section ad-hoc margins. */
.word-detail { display: grid; gap: 22px; }

.word-detail__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.word-detail__hanzi { font-family: var(--font-zh); font-size: 44px; line-height: 1.15; }
.word-detail__pinyin { font-size: 15px; margin-top: 2px; }
.word-detail__german { font-size: 19px; font-weight: 600; margin-top: 8px; }
.word-detail__h { font-size: 13px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; margin: 0 0 10px; }

/* Main grid: two matching cards — stroke box left (content width), audio/state
   panel right (grows). One column on phones; the panel card takes the full
   row both when JS hides the stroke box (no stroke data / offline) and when
   word_detail.php never rendered it at all (sentences, >4 Han characters). */
.word-card {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 16px;
}
.word-detail__main { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 16px; align-items: stretch; }
/* Collapses when the stroke box is hidden by JS (no stroke data / offline) AND
   when it was never rendered at all — word_detail.php omits it for sentences
   and for anything over 4 Han characters, so :has(.stroke-box[hidden]) alone
   would leave the panel beside an empty column. */
.word-detail__main:has(.stroke-box[hidden]),
.word-detail__main:not(:has(.stroke-box)) { grid-template-columns: 1fr; }
.word-detail__panel { min-width: 0; align-content: start; }

/* Panel rows on one rhythm: heading, then divider-separated rows —
   audio per language (chip | speaker | slow), then the mastery state. */
.word-detail__panel .panel-row { padding: 11px 0; }
.word-detail__panel .panel-row:first-of-type { padding-top: 4px; }
.word-detail__panel .panel-row + .panel-row { border-top: 1px solid var(--border); }
.word-detail__panel .panel-row:last-child { padding-bottom: 2px; }
.audio-row { display: grid; grid-template-columns: 30px auto; align-items: center; column-gap: 12px; justify-content: start; }
.audio-row__lang {
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .04em; text-align: center;
}
.audio-row .speak-cluster { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0; }
.word-detail__state .muted { margin: 0; }

.stroke-box__stage { position: relative; width: 196px; height: 196px; margin: 0 auto; }
.stroke-box__grid { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--border); z-index: 0; }
.stroke-box__writer { position: absolute; inset: 8px; z-index: 1; }
.stroke-box__nav { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 8px; }
.stroke-box__dots { display: inline-flex; gap: 6px; }
.stroke-box__dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--border-strong); }
.stroke-box__dots i.is-on { background: var(--accent); }
.stroke-box > [data-stroke-replay] { display: flex; margin: 10px auto 0; }

/* Action footer: full-width stacked buttons. Rendered inline in the fragment,
   then lifted by app.js into .word-modal__foot — a fixed row of the dialog's
   flex column below the scroll body, so content never slides underneath. */
.word-modal__foot { flex: 0 0 auto; padding: 14px 26px 18px; background: var(--surface); border-top: 1px solid var(--border); }
.word-modal__foot:empty { display: none; }
.word-detail__actions { display: grid; gap: 10px; }
.action-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 12px 16px; font: inherit; font-size: 15px; font-weight: 600;
  border: 1px solid var(--border-strong); border-radius: var(--r-md); cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.action-btn .icon { width: 18px; height: 18px; flex: 0 0 auto; }
.action-btn .action-btn__on { display: none; }
.action-btn.is-on .action-btn__on { display: inline-flex; }
.action-btn.is-on .action-btn__off { display: none; }
.action-btn--fav { background: var(--accent-weak); border-color: rgba(56, 217, 169, .45); color: var(--accent); }
.action-btn--fav:hover { border-color: var(--accent); }
.action-btn--fav.is-on { background: var(--accent); border-color: var(--accent); color: #06231A; }
.action-btn--hard { background: rgba(255, 107, 107, .10); border-color: rgba(255, 107, 107, .45); color: var(--danger); }
.action-btn--hard:hover { border-color: var(--danger); }
.action-btn--hard.is-on { background: var(--danger); border-color: var(--danger); color: #2A0B0B; }
.action-btn--close { background: var(--surface-2); color: var(--text); }
.action-btn--close:hover { background: var(--surface-hover); }

.example-list { list-style: none; margin: 0; padding: 0; }
.example-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.example-row__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.example-row__text [lang="zh"] { font-family: var(--font-zh); }
.example-empty { margin: 0; }
/* Add-form: both language inputs side by side, action button below-right. */
.example-form { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.example-form input { width: 100%; margin: 0; }
.example-form button { grid-column: 2; justify-self: end; }

/* General notes: full-width textarea, save button below-right with an
   inline "saved" confirmation that app.js flashes after a successful POST. */
.note-form textarea { width: 100%; margin: 0; resize: vertical; }
.note-form__row { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 8px; }
.import-warnings { margin: 6px 0 0; padding-left: 18px; }

/* Learn-card note block: collapsed to one row so the card stays scannable. */
.card-note { width: 100%; max-width: 420px; margin: 4px auto 0; }
.card-note__toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; background: none; border: 0; border-top: 1px solid var(--border);
  color: var(--text-muted); font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.card-note__toggle:hover { color: var(--text); }
.card-note__label { display: inline-flex; align-items: center; gap: 6px; flex: none; }
/* The preview is the only shrinking child, so a long note truncates
   instead of pushing the chevron off the card. */
.card-note__preview {
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis; opacity: 0.75; font-style: italic;
}
.card-note__chev { flex: none; transition: transform 0.15s ease; }
.card-note__toggle[aria-expanded="true"] .card-note__chev { transform: rotate(180deg); }
.card-note__body { padding: 4px 0 8px; }

/* Narrow phones: modal hugs the viewport, main grid + form stack. */
@media (max-width: 560px) {
  .word-modal { width: calc(100vw - 16px); max-height: 92vh; }
  .word-modal__body { padding: 18px; }
  .word-detail { gap: 18px; }
  .word-detail__hanzi { font-size: 38px; }
  .word-detail__main { grid-template-columns: 1fr; gap: 14px; }
  .word-modal__foot { padding: 12px 18px 14px; }
  .example-form { grid-template-columns: 1fr; }
  .example-form button { grid-column: 1; justify-self: stretch; }
}

/* Clickable hanzi in tables (vocab deck, library) */
.hanzi-link {
  font: inherit; font-family: var(--font-zh); color: inherit; background: none; border: none;
  padding: 0; cursor: pointer; border-bottom: 1px dashed var(--border-strong);
}
.hanzi-link:hover { color: var(--accent); border-bottom-color: var(--accent); }
.flag-ind { display: inline-flex; vertical-align: middle; margin-left: 6px; }
.flag-ind .icon { width: 14px; height: 14px; }
.flag-ind--fav { color: var(--accent); }
.flag-ind--hard { color: var(--danger); }
/* Difficulty stages: colour ramp plus flame count, so the signal survives
   greyscale and colour-vision deficiency. Stage 0 renders nothing at all. */
.flag-ind--hard-1 { color: #F5A623; }
.flag-ind--hard-2 { color: #FF6B35; }
.flag-ind--hard-3 { color: var(--danger); }
.flag-ind--hard .icon + .icon { margin-left: -4px; }   /* overlap, so 3 flames stay compact */

/* Study-card info button (inside #flash-back, so hidden until reveal) */
.word-info-btn { margin-top: 10px; }

/* Favourites / hard-words choice-card icons on the select page */
.choice-card__icon--fav { color: var(--accent); }
.choice-card__icon--fav .icon { width: 32px; height: 32px; }
.choice-card__icon--hard { color: var(--danger); }
.choice-card__icon--hard .icon { width: 32px; height: 32px; }

/* Auto-hard summary on the study done screen */
.study-done__hardened { margin-top: 16px; padding: 12px 16px; border-radius: 12px; background: var(--surface-2, rgba(0,0,0,.04)); }
.study-done__hardened > p { margin: 0 0 8px; font-size: .95rem; }
.hardened-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.hardened-list__hanzi { font-weight: 600; margin-right: 8px; }

/* End-of-session difficulty movement: escalated / cooled / mastered. */
.study-done__diff { margin-top: 12px; padding: 12px 16px; border-radius: 12px; background: var(--surface-2, rgba(0,0,0,.04)); }
.study-done__diff > p { margin: 0 0 8px; font-size: .95rem; }
.study-done__diff--escalated { border-left: 3px solid #FF6B35; }
.study-done__diff--cooled    { border-left: 3px solid #4e9e86; }
.study-done__diff--mastered  { border-left: 3px solid var(--accent); }

/* ---------- AI assistant (feature 9) ---------- */
.ai-panel { display: grid; gap: 10px; justify-items: start; margin-top: 12px; }
.ai-panel .example-list { width: 100%; margin: 0; padding: 0; list-style: none; }
.ai-panel .example-list:empty { display: none; }
/* AI rows stack: full-width text lines with the speaker pinned to the line
   end, then a right-aligned tag+save row — avoids the squeezed 3-column look
   on phones. */
.example-row--ai { flex-direction: column; align-items: stretch; gap: 6px; }
.example-row--ai .example-row__text { gap: 4px; }
.example-row--ai .example-row__zh,
.example-row--ai .example-row__de { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* Compact circular variant: the base .speak-btn is a padded pill whose 20px
   icon would overflow a small square — zero the padding and center instead. */
.example-row--ai .speak-btn {
  width: 32px; height: 32px; padding: 0; justify-content: center; flex: 0 0 auto;
}
.example-row--ai .speak-btn .icon { width: 16px; height: 16px; }
.example-row__actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.ai-panel__error { margin: 0; font-size: 13px; }
[data-ai-save].is-saved { color: var(--success); border-color: transparent; pointer-events: none; }
.ai-ask { width: 100%; margin-top: 6px; }
.ai-ask__row { display: flex; gap: 8px; }
.ai-ask__row input { flex: 1; min-width: 0; }
.ai-chat {
  display: flex; flex-direction: column; gap: 6px;
  max-height: 40vh; overflow-y: auto; margin-bottom: 8px;
}
.ai-chat__q, .ai-chat__a {
  padding: 8px 12px; border-radius: var(--r-md); font-size: 14px; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere; max-width: 85%; text-align: left;
}
.ai-chat__q { align-self: flex-end; background: var(--accent-weak); }
.ai-chat__a { align-self: flex-start; background: var(--surface-2); border: 1px solid var(--border); }
.ai-panel--card { justify-items: center; text-align: left; max-width: 420px; margin-inline: auto; }

/* --- Label management modal (labels feature) --- */
.label-manage__list { list-style: none; padding: 0; margin: 0 0 14px; display: flex; flex-direction: column; gap: 8px; }
.label-manage__row { display: flex; gap: 8px; align-items: center; }
.label-manage__name { flex: 1; min-width: 0; margin: 0; }
.label-manage__count { min-width: 2ch; text-align: right; }
.label-manage__create { display: flex; gap: 8px; }
.label-manage__create input { flex: 1; min-width: 0; margin: 0; }

/* --- Vocab filter/sort toolbar --- */
.vocab-toolbar { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
.vocab-toolbar__row { display: flex; gap: 8px; align-items: stretch; }
.vocab-toolbar__search { flex: 1; min-width: 0; margin: 0; }   /* zero the global input margin-top so it stretches level with the select */
/* Selects: native look doesn't fit the theme — restyle like the inputs, with
   our own chevron (the popup itself stays native but renders dark via
   color-scheme). Shared with the bulk bar's group select. */
.vocab-toolbar__sort, .bulk-bar__select {
  -webkit-appearance: none; appearance: none;
  margin: 0; padding: 11px 38px 11px 13px;
  background-color: var(--bg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239AA3B2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 16px;
  color: var(--text); border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-family: var(--font-body); font-size: 15px; cursor: pointer;
  transition: border-color .15s, box-shadow .15s;
}
.bulk-bar__select { padding: 8px 32px 8px 10px; font-size: 13px; background-position: right 10px center; }
.vocab-toolbar__sort:focus, .bulk-bar__select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak);
}
select option { background-color: var(--surface-2); color: var(--text); }
.vocab-toolbar__chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.vocab-toolbar__chips .group-chip .icon { width: 14px; height: 14px; margin-right: 4px; vertical-align: -2px; }
.vocab-toolbar__count { margin-left: auto; font-size: .85rem; white-space: nowrap; }
.vocab-toolbar__empty { display: flex; align-items: center; gap: 10px; margin: 0; }

/* --- Long-press TTS regenerate --- */
.speak-btn { -webkit-user-select: none; user-select: none; touch-action: manipulation; }
.speak-btn.is-holding { animation: speak-hold .6s ease-in forwards; }
.speak-btn.is-loading { opacity: .5; pointer-events: none; }
@keyframes speak-hold {
  from { transform: scale(1); }
  to   { transform: scale(1.2); color: var(--accent); }
}
.audio-hint { font-size: .8rem; margin: 2px 0 0; }

/* Global TTS volume dot (premium). Collapsed: a small fixed circle; open: a
   vertical slider pill above it. Lifted higher in focus mode so it never
   covers the full-width reveal/grade buttons on mobile. */
.vol-dot { position: fixed; right: 16px; bottom: 16px; z-index: 60; }
body.is-focus .vol-dot { bottom: 96px; }
.vol-dot__toggle {
  position: relative; width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--border-strong);
  color: var(--text-muted); cursor: pointer; transition: color .18s, border-color .18s, transform .18s;
}
.vol-dot__toggle:hover, .vol-dot.is-open .vol-dot__toggle {
  color: var(--text); border-color: var(--accent); transform: scale(1.08);
}
/* Corner badge on the collapsed bubble. Suppressed while open, where the
   readout pill takes over — otherwise the percentage renders twice. */
.vol-dot__pct {
  position: absolute; top: -7px; right: -10px; font-size: 9px; font-weight: 700;
  background: var(--accent); color: var(--bg); border-radius: var(--r-pill); padding: 1px 4px;
}
.vol-dot.is-open .vol-dot__pct { display: none; }
/* Width is pinned to the toggle's 34px: 22px slider + 5px padding either side,
   inside a 1px border (border-box is set globally). */
.vol-dot__panel {
  position: absolute; bottom: 42px; right: 0; display: none;
  width: 34px; flex-direction: column; align-items: center; padding: 12px 5px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r-pill); box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.vol-dot.is-open .vol-dot__panel { display: flex; animation: vol-pop .15s ease-out; }
@keyframes vol-pop { from { opacity: 0; transform: translateY(6px) scale(.95); } }
/* Pill to the left of the bubble, visible only alongside the open slider. */
.vol-dot__readout {
  position: absolute; top: 50%; right: calc(100% + 8px); transform: translateY(-50%);
  display: none; font-size: 11px; font-weight: 700; white-space: nowrap;
  background: var(--accent); color: var(--bg);
  border-radius: var(--r-pill); padding: 2px 8px;
}
.vol-dot.is-open .vol-dot__readout { display: block; }
.vol-dot__track { position: relative; display: flex; }
.vol-dot__tick {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(100% / 3); width: 14px; height: 2px;
  background: var(--border-strong); pointer-events: none;
}
/* Vertical via the standardized writing-mode/direction pair. The old
   -webkit-appearance: slider-vertical is deprecated in Chrome and was redundant
   once writing-mode landed, so it's gone. */
.vol-dot__slider {
  writing-mode: vertical-lr; direction: rtl;
  width: 22px; height: 140px; accent-color: var(--accent);
}

/* Error toast. Fixed above everything, including the focus-mode card and the
   word modal (which is a <dialog> in the top layer, hence the z-index). */
.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 12px);
  z-index: 1000; max-width: min(92vw, 420px);
  padding: 11px 16px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--danger); border-radius: var(--r-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  font-size: 14px; line-height: 1.4; text-align: center;
  opacity: 0; transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}

/* Learn-mode picker: a segmented control on the three learn entry screens. */
.kind-picker { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.kind-picker__legend {
  color: var(--text-muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; margin-right: 4px;
}
.kind-picker__opt {
  display: inline-flex; align-items: center; padding: 7px 14px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-2); cursor: pointer;
  font-size: 14px; font-weight: 600; transition: background .12s, border-color .12s;
}
.kind-picker__opt input { position: absolute; opacity: 0; pointer-events: none; }
.kind-picker__opt.is-on { background: var(--accent-weak); border-color: var(--accent); }
.kind-picker__opt:hover { border-color: var(--accent); }

/* ---- Spruch des Tages ------------------------------------------------- */
.daily-slot { display: grid; gap: .5rem; }
.daily__skeleton { min-height: 3rem; display: flex; align-items: center; }

.daily-card__zh,
.daily__zh { font-family: "Noto Serif SC", serif; font-size: 1.75rem; line-height: 1.35; margin: 0; }
.daily__zh { font-size: 2.25rem; }
.daily-card__py,
.daily__py { margin: 0; font-size: .9rem; }
.daily-card__meaning,
.daily__meaning { margin: .25rem 0 .5rem; font-weight: 600; }
.daily__explain { margin: .5rem 0 1rem; line-height: 1.6; }
.daily__h { margin: 1rem 0 .5rem; font-size: 1rem; }

.daily__adds { display: flex; flex-wrap: wrap; gap: .4rem; }
.daily__word { display: inline-flex; gap: .4rem; align-items: baseline; }
.daily__word .muted { font-size: .8rem; }
[data-daily-add].is-added { opacity: .6; }

.daily-archive { list-style: none; margin: 0; padding: 0; }
.daily-archive__row + .daily-archive__row { border-top: 1px solid var(--border); }
.daily-archive__row a { display: flex; align-items: baseline; gap: .75rem;
                        padding: .6rem .25rem; text-decoration: none; color: inherit; }
.daily-archive__row a:hover { background: var(--surface-2); }
.daily-archive__zh { font-family: "Noto Serif SC", serif; font-size: 1.1rem; }
.daily-archive__day { margin-left: auto; font-size: .8rem; white-space: nowrap; }

@media (max-width: 480px) {
  .daily__zh { font-size: 1.75rem; }
  .daily-archive__day { display: none; }
}
