/* Dark is the default for everyone, whatever the OS says: Byron prefers it,
   and a phone left in light mode shouldn't overrule that. Light is an explicit
   choice, <html data-theme="light">, set by theme.js from the topbar toggle. */
:root {
  color-scheme: dark;
  --page:           #14161b;
  --surface-1:      #1d2027;
  --surface-2:      #262a33;
  --text-primary:   #eef0f4;
  --text-secondary: #b7bdc9;
  --muted:          #838a99;
  --border:         rgba(238, 240, 244, 0.12);
  --accent:         #6c9bff;
  --accent-text:    #0b1220;
  --chip-noun:      #24352a; --chip-noun-text: #a9d3b6;
  --chip-verb:      #3a2c19; --chip-verb-text: #e7bf8b;
  --chip-adjective: #2c2740; --chip-adjective-text: #c7bceb;
  --chip-other:     #2a2e37; --chip-other-text: #c4c9d4;
  --box-low:        #e8776e;
  --box-mid:        #e0b155;
  --box-high:       #4cb87c;
  --verdict-good:   #6fd39a; --verdict-good-bg:   #1d3328;
  --verdict-close:  #e0b155; --verdict-close-bg:  #352c19;
  --verdict-poor:   #f0897f; --verdict-poor-bg:   #3a2321;
  --verdict-unknown:#b7bdc9; --verdict-unknown-bg:#2a2e37;

  --radius:    12px;
  --radius-sm: 8px;
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
}

:root[data-theme="light"] {
  color-scheme: light;

  --page:           #f7f8fa;
  --surface-1:      #ffffff;
  --surface-2:      #eef0f4;
  --text-primary:   #1c2027;
  --text-secondary: #565d6b;
  --muted:          #8a91a0;
  --border:         rgba(28, 32, 39, 0.12);
  --accent:         #2f6fed;
  --accent-text:    #ffffff;
  --chip-noun:      #dce9df; --chip-noun-text: #2c5136;
  --chip-verb:      #f3ddc7; --chip-verb-text: #7a4a12;
  --chip-adjective: #ded7ee; --chip-adjective-text: #4a3a72;
  --chip-other:     #e6e8ee; --chip-other-text: #444b5a;
  --box-low:        #e2574c;
  --box-mid:        #d69a2d;
  --box-high:       #2f9e5c;
  --verdict-good:   #2f9e5c; --verdict-good-bg:   #e3f3e9;
  --verdict-close:  #b3791a; --verdict-close-bg:  #f8eed9;
  --verdict-poor:   #c8362b; --verdict-poor-bg:   #fae0dd;
  --verdict-unknown:#565d6b; --verdict-unknown-bg:#e6e8ee;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: var(--font);
  padding-bottom: 2rem;
}

a { color: var(--accent); }

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  padding: 0.9rem 1.25rem;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 5;
}

.topbar h1 { font-size: 1.15rem; margin: 0; }
.brand { color: var(--text-primary); text-decoration: none; }

.rosetta-link {
  display: inline-flex;
  align-items: center;
  font-size: 1.3rem;
  text-decoration: none;
  line-height: 1;
}

.nav-links { display: flex; gap: 1rem; }

/* Controls that can only fail for this role. `!important` because every one of
   them has a class of its own that sets display (.btn, .back-link, ...), and
   this has to win over all of them — the same fight as the [hidden] trap. The
   server refuses the write regardless; this only stops the page offering it.
   <html data-role> starts as "guest" in index.html, so nothing flashes up. */
html:not([data-role='owner']) .needs-owner,
html[data-role='guest'] .needs-contributor { display: none !important; }

.auth-control { position: relative; display: flex; align-items: center; font-size: 0.88rem; }
.auth-control:empty { display: none; }

/* Signed in: a round initial, Google-style, opening a small account menu. */
.avatar-btn {
  display: inline-flex;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  cursor: pointer;
}
.avatar-btn:hover, .avatar-btn[aria-expanded="true"] { box-shadow: 0 0 0 3px var(--surface-2); }
.avatar-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The initial is always there; a photo, when there is one, covers it. If the
   photo fails to load, app.js removes the <img> and the initial shows. */
.avatar-circle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  overflow: hidden;
  background: var(--accent);
  color: var(--accent-text);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
}
.avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.avatar-circle.large { width: 2.6rem; height: 2.6rem; font-size: 1.2rem; }

.user-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 20;
  width: max-content;
  max-width: min(20rem, calc(100vw - 2rem));
  padding: 0.35rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-1);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
/* No display of its own today, but the [hidden] trap is policy here. */
.user-menu[hidden] { display: none; }
.user-menu-who {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.5rem 0.55rem 0.65rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.3rem;
}
.user-menu-id { min-width: 0; }
.user-menu-email {
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.user-menu-role { font-size: 0.8rem; color: var(--text-secondary); margin-top: 0.15rem; }
.user-menu-item {
  display: block;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
}
.user-menu-item:hover { background: var(--surface-2); }
.user-menu-item:disabled { color: var(--muted); cursor: default; }
/* What "AI review" just did, under it in the account menu. */
.user-menu-note {
  margin: 0.1rem 0.6rem 0.4rem;
  max-width: 16rem;
  font-size: 0.8rem;
  color: var(--text-secondary);
}
.user-menu-note[hidden] { display: none; }
.auth-btn {
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-primary);
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.auth-btn:hover { border-color: var(--accent); }
.nav-links a { text-decoration: none; font-weight: 600; font-size: 0.95rem; white-space: nowrap; }
.nav-links a[aria-current="page"] {
  color: var(--text-primary);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.35em;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.theme-toggle:hover { border-color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* The language picker: a button plus a listbox, standing in for a <select>
   that couldn't show a flag. See flags.js. */
.lang-picker { margin-left: auto; position: relative; }

.lang-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
}
.lang-btn:disabled { cursor: default; color: var(--muted); }

/* Drawn rather than a ▾ glyph, so it matches the chevron on the rule sections. */
.lang-caret {
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  opacity: 0.7;
}

.lang-menu {
  position: absolute;
  top: calc(100% + 0.35rem);
  right: 0;
  z-index: 20;
  min-width: 100%;
  padding: 0.25rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-1);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
/* .lang-menu sets its own display, which at equal specificity beats the
   browser's [hidden]{display:none} — the trap this project keeps hitting. */
.lang-menu[hidden] { display: none; }

.lang-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
}
.lang-option:hover { background: var(--surface-2); }
.lang-option[aria-selected="true"] { font-weight: 700; }

.lang-flag {
  flex: none;
  border-radius: 2px;
  /* Russia and Indonesia are white along one edge, so without an outline they
     bleed into a light background. */
  box-shadow: 0 0 0 1px var(--border);
}

/* A form field whose value isn't editable — the entry form's language. */
.field { display: flex; flex-direction: column; gap: 0.3rem; }
.field-label { font-size: 0.85rem; color: var(--text-secondary); }
.field-readonly {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-weight: 600;
}
.field-hint { font-size: 0.8rem; font-weight: 400; color: var(--muted); }

.filters { display: flex; flex-wrap: wrap; gap: 0.5rem; width: 100%; }
.filters[hidden] { display: none; }

.filters select, .filters input {
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
}

#app {
  max-width: 920px;
  margin: 0 auto;
  padding: 1.25rem;
}

/* After .filters and #app, which it overrides at equal specificity.
   A phone. Unchanged, the topbar wrapped to four rows (~250px of a ~700px
   screen) and stayed stuck there. So: the brand goes (Browse is the home link
   anyway), the controls share row one, the tabs get row two with finger-sized
   targets, the filters a tidy grid — and the bar scrolls away instead of
   sticking, which gives the sticky word bars below it top:0 (see
   .detail-head / .builder-head). */
@media (max-width: 560px) {
  .topbar {
    position: static;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem 0.6rem;
  }
  .topbar h1 { display: none; }
  .rosetta-link { padding: 0.35rem; margin-left: -0.35rem; }
  .nav-links {
    order: 1;
    flex-basis: 100%;
    justify-content: space-between;
    gap: 0.25rem;
    overflow-x: auto;       /* a phone narrower than ~360px scrolls the tabs */
    scrollbar-width: none;
    margin: 0 -0.3rem;
  }
  .nav-links a { padding: 0.55rem 0.3rem; }
  .filters {
    order: 2;
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
  .filters #search { grid-column: 1 / -1; }
  .filters select, .filters input { min-width: 0; width: 100%; }
  #app { padding: 0.9rem 0.75rem; }
}

.loading, .empty { color: var(--muted); padding: 2rem 0; text-align: center; }

.entry-list { display: flex; flex-direction: column; gap: 0.6rem; }

.entry-group + .entry-group { margin-top: 1.1rem; }
.entry-group-heading {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.45rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.entry-group-count {
  font-weight: 600;
  letter-spacing: 0;
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
}

.entry-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}

/* A phrase the sentence builder can assemble, and the way through to it.
   The wrapper exists because the card is itself an <a> and this is a second
   link: it cannot go inside (see entryCardHTML), so the two are stacked as one
   item of the list instead.

   Indented under the card and in small type on purpose — the phrase's own page
   is still the destination of the click, and this is the other thing you can
   do with it, not a rival to it. */
/* Card, star, and (for a phrase the builder can assemble) the way into it.
   A grid rather than a column, because the star belongs beside the card while
   the builder link belongs under it: the card takes (1,1), the star (1,2), and
   the link is pushed onto its own full-width row. */
.entry-card-wrap {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  column-gap: 0.25rem;
}
.entry-card-wrap > .builder-link { grid-column: 1 / -1; }

/* "english - word" to the clipboard (copyButtonHTML in core.js). Quiet until
   used: it sits beside every word, so it must not compete with the word. */
.copy-btn {
  font-family: inherit;
  font-size: 0.8rem;
  padding: 0.35rem 0.6rem;
  min-height: 2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}
.copy-btn:hover { color: var(--text-primary); background: var(--surface-2); }
.copy-btn.copied { color: var(--accent); border-color: var(--accent); }
.listen-row .copy-btn { margin-left: 0.5rem; }
.practice-answer .listen-row { justify-content: center; }
.copy-scratch { position: fixed; top: 0; left: 0; opacity: 0; pointer-events: none; }
.builder-link {
  align-self: flex-start;
  margin: 0.3rem 0 0 1rem;
  font-size: 0.82rem;
  color: var(--accent);
  text-decoration: none;
}
.builder-link:hover { text-decoration: underline; }
/* On the entry page it sits under the forms rather than under a card, so it
   gets the row to itself with a little more air. */
.detail-card .builder-link { margin: 0.75rem 0 0; display: inline-block; }

/* The stressed vowel of a word whose language doesn't write its stress —
   Russian ма́льчик. Used everywhere a target word renders (browse card, form
   row, conjugation cell, practice card), so it lives here rather than beside
   any one of them.

   The combining acute is already in the text and is the convention a learner
   reads; this only makes it findable at a glance, since the mark itself is a
   few pixels on a phone. Colour is therefore the *second* signal and never the
   only one — var(--accent) has a dark-theme value, so it follows the theme
   without a token of its own. */
.stressed-vowel { color: var(--accent); }

/* The favorites star. Unstarred it is a muted outline that only fills in on
   hover, so a list of two hundred words isn't two hundred competing glyphs;
   starred it takes the accent colour AND a different glyph (★ vs ☆), since
   colour is never allowed to be the only signal here.

   Sized in rem and padded rather than given a width, so it is a real tap
   target on a phone without the card having to make room for a box. */
.fav-toggle {
  flex-shrink: 0;
  padding: 0.25rem 0.4rem;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius);
}
.fav-toggle:hover { color: var(--accent); background: var(--surface-2); }
.fav-toggle.on { color: var(--accent); }
.fav-toggle:disabled { cursor: default; opacity: 0.5; }
.fav-toggle.fav-static { cursor: default; }
.fav-toggle.fav-static:hover { background: none; }
/* The sticky head aligns on the baseline, and a 1.15rem glyph on the baseline
   of a 1rem gloss sits low; this puts it back on the line of the chips. */
.detail-head .fav-toggle { align-self: center; margin-right: 0.1rem; }

/* The starred group sits above the subtype headings, so it needs a little more
   air under it than one subtype group needs from the next. */
.entry-group-favorites { margin-bottom: 1.1rem; }
.entry-group-favorites .entry-group-heading { color: var(--accent); }
.entry-group-review { margin-bottom: 1.1rem; }
.entry-group-review .entry-group-heading { color: var(--verdict-close); }
/* Always last on the page, so it needs air above it, whatever comes before. */
.entry-group-bad { margin-top: 1.6rem; }
.entry-group-bad[hidden] { display: none; }
.entry-group-bad .entry-group-heading { color: var(--verdict-poor); }

/* "Show bad words": a checkbox drawn as a slider. The input stays in the
   accessibility tree (role="switch") and takes focus; only its box is hidden. */
.bad-word-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.9rem;
  font-size: 0.9rem;
  color: var(--text-secondary);
  cursor: pointer;
  user-select: none;
}
.bad-word-switch input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.switch-track {
  position: relative;
  flex: none;
  width: 2.3rem;
  height: 1.3rem;
  border-radius: 999px;
  background: var(--border);
  transition: background 0.15s;
}
.switch-track::after {
  content: '';
  position: absolute;
  top: 0.15rem;
  left: 0.15rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--text-primary);
  transition: transform 0.15s;
}
.bad-word-switch input:checked + .switch-track { background: var(--verdict-poor); }
.bad-word-switch input:checked + .switch-track::after { transform: translateX(1rem); }
.bad-word-switch input:focus-visible + .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }

.entry-main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.entry-target { font-size: 1.05rem; font-weight: 600; }
.entry-english { color: var(--text-secondary); font-size: 0.92rem; }
.entry-meta { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }

.chip {
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}
.chip.noun { background: var(--chip-noun); color: var(--chip-noun-text); }
.chip.verb { background: var(--chip-verb); color: var(--chip-verb-text); }
.chip.adjective { background: var(--chip-adjective); color: var(--chip-adjective-text); }
.chip.other, .chip.pronoun, .chip.adverb, .chip.preposition, .chip.conjunction,
.chip.article, .chip.phrase, .chip.letter, .chip.number, .chip.interjection {
  background: var(--chip-other); color: var(--chip-other-text);
}

.lesson-tag { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }

/* entries.subtype — a Tagalog verb's affix family ("mag- verb"), an article's
   definite/indefinite. Deliberately louder than .lesson-tag: on a verb list
   the family is the thing worth scanning for. */
.subtype-tag {
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  background: var(--surface-2);
  color: var(--text-secondary);
  border: 1px solid var(--border);
}
.form-count-tag { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }

.page-title { margin: 0 0 0.25rem; font-size: 1.5rem; text-transform: capitalize; }
.page-subtitle { margin: 0 0 1.25rem; color: var(--text-secondary); font-size: 0.92rem; }

/* One titled block on a page — the grammar rules for a word type, the words
   of that type, the rules shown under an entry. */
.panel { margin-bottom: 1.5rem; }
.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.65rem;
}
.panel-head h3 { margin: 0; font-size: 1rem; }
.panel-head .back-link { margin-bottom: 0; white-space: nowrap; }

/* Words | Grammar rules on a word-type page: a segmented control, stuck under
   the topbar (same --topbar-h offset as .detail-head) so the other side is one
   tap away however far down the list you are. The strip behind it is the page
   colour, padded, so cards scrolling under it don't show through the gaps. */
.view-switch {
  position: sticky;
  top: var(--topbar-h, 3.25rem);
  z-index: 3;
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  margin: 0 0 1rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 -0.6rem 0 var(--page);
}
.view-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  padding: 0.4rem 0.75rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
}
.view-tab:hover { color: var(--text-primary); }
.view-tab[aria-selected="true"] { background: var(--accent); color: var(--accent-text); }
.view-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.view-tab-count {
  min-width: 1.5rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--surface-1);
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-weight: 700;
}
.view-tab[aria-selected="true"] .view-tab-count { background: rgba(0, 0, 0, 0.18); color: inherit; }
.panel[hidden] { display: none; }
@media (max-width: 560px) {
  /* The topbar scrolls away on a phone (see .topbar), so nothing to sit under. */
  .view-switch { top: 0.4rem; }
}

.detail-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
}

/* The entry page's title block: the word and its meaning, kept on screen while
   the forms and the grammar rules scroll past underneath. It is a sibling of
   .detail-card rather than a child -- see the comment at its call site in
   renderDetail; a sticky element only sticks inside its own parent.

   `top` is the topbar's current height, published as --topbar-h by
   trackTopbarHeight() in app.js: the topbar is itself sticky at top:0 and
   wraps to two or three rows on a narrow window, so the offset cannot be a
   constant. The fallback covers the first frame and a browser with no
   ResizeObserver, and errs small for the same reason trackTopbarHeight floors:
   too small tucks this bar under the topbar, which paints over it, while too
   large leaves a gap with the page scrolling through it. */
.detail-head {
  position: sticky;
  top: var(--topbar-h, 3.25rem);
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  padding: 0.7rem 1.25rem;
  margin-bottom: 0.75rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* A locked bar that eats the screen locks you out of the page it is heading.
     The longest gloss in the vocabulary today runs to two or three lines on a
     phone and stays well inside this; the cap is for the one that doesn't, and
     it scrolls rather than truncating, since the half it would cut is the
     meaning. */
  max-height: 32vh;
  overflow-y: auto;
}

.detail-head-word {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  min-width: 0;
}
.detail-headword { font-size: 1.35rem; font-weight: 700; line-height: 1.25; }
/* The meaning. Lighter than the word rather than muted: it is half of what
   this bar exists to keep in front of you. */
.detail-headgloss { font-size: 1rem; color: var(--text-secondary); font-style: italic; }
.detail-head .entry-meta { flex-wrap: wrap; flex-shrink: 1; }

/* A phone has little enough height that a fat bar under an already-wrapped
   topbar would leave hardly anything to scroll. */
@media (max-width: 560px) {
  /* The topbar scrolls away on a phone (see .topbar), so nothing to sit under. */
  .detail-head { top: 0; padding: 0.55rem 0.85rem; gap: 0.2rem 0.6rem; margin-bottom: 0.6rem; }
  .detail-headword { font-size: 1.15rem; }
  .detail-headgloss { font-size: 0.9rem; }
}

.form-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}
.form-row:last-child { border-bottom: none; }
.form-label { color: var(--muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; }
.form-text { font-weight: 600; }
.form-pron { color: var(--text-secondary); font-size: 0.9rem; }
.form-english { color: var(--text-secondary); font-size: 0.9rem; font-style: italic; }

/* --- Verb conjugation table (aspect x focus) -------------------------------
   Replaces the stacked Base/Actor/Object panels this used to render: eight
   forms down a column meant scrolling past most of the paradigm to compare
   any two of them. A grid puts the whole verb on one screen, with the two
   focuses side by side — which is the comparison that actually teaches the
   -um-/-in- pairing. */

.conj-base {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.6rem 0.85rem;
  margin-bottom: 0.85rem;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}
.conj-base-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.conj-base-word { font-size: 1.15rem; font-weight: 700; }

/* Only ever scrolls on a narrow screen: the table's min-width is comfortably
   under #app's content width on a desktop. */
.conj-scroll { overflow-x: auto; }

/* ‹ / › at an edge while the table has more to show that way (updateScrollHint
   in app.js sets .more-left / .more-right). Each is a full-height column that
   fades the card colour over the table's edge; the glyph inside is sticky, so
   it stays level with the middle of the screen while you read down a table
   taller than the screen, rather than sitting at the top out of view.
   pointer-events: none, so a swipe that starts on one still scrolls. */
.scroll-hint { position: relative; }
.scroll-hint-arrow {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 1;
  width: 2rem;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
}
.scroll-hint-left { left: 0; background: linear-gradient(to right, var(--surface-1) 20%, transparent); }
.scroll-hint-right { right: 0; background: linear-gradient(to left, var(--surface-1) 20%, transparent); }
.scroll-hint.more-left .scroll-hint-left,
.scroll-hint.more-right .scroll-hint-right { opacity: 1; }
.scroll-hint-arrow > span {
  position: sticky;
  top: 45vh;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  margin-top: 2.5rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--accent);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  padding-bottom: 0.15rem;   /* the guillemet sits high in its line box */
  opacity: 0.9;
}
.scroll-hint-right > span { margin-left: auto; }

.conj-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
}

.conj-focus-head {
  text-align: left;
  padding: 0 0.75rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--focus-accent, var(--muted));
  border-bottom: 2px solid var(--focus-accent, var(--border));
}
.conj-focus-actor { --focus-accent: var(--chip-verb-text); }
.conj-focus-object { --focus-accent: var(--accent); }
.conj-aspect-head { width: 8.5rem; border-bottom: 2px solid var(--border); }

.conj-aspect {
  text-align: left;
  vertical-align: top;
  padding: 0.85rem 0.75rem 0.85rem 0;
  border-bottom: 1px solid var(--border);
}
.conj-aspect-name { font-size: 0.85rem; font-weight: 700; }
.conj-aspect-sub { font-size: 0.78rem; color: var(--muted); }

.conj-cell {
  vertical-align: top;
  padding: 0.85rem 0.75rem;
  border-bottom: 1px solid var(--border);
}
.conj-table tbody tr:last-child .conj-aspect,
.conj-table tbody tr:last-child .conj-cell { border-bottom: none; }

.conj-word { font-size: 1.05rem; font-weight: 600; }
.conj-gloss { color: var(--text-secondary); font-size: 0.85rem; font-style: italic; }
.conj-cell-empty { color: var(--muted); }
/* The listen controls sit under the word inside a cell, so they lead rather
   than trail — the .listen-link margin that separates them on a form row
   would leave the first one indented here. */
.conj-cell .listen-row { margin-top: 0.3rem; }
.conj-cell .listen-link:first-child { margin-left: -0.5rem; }  /* its padding, so the icon lines up with the word */

/* Byron's own spreadsheet notes: kept, but folded away — on a verb they're
   several lines of half-sentences about affixes that the grammar rules below
   now say properly. */
.entry-notes { margin-top: 1.25rem; }
.entry-notes summary { color: var(--muted); font-size: 0.85rem; cursor: pointer; }
.entry-notes p { color: var(--text-secondary); white-space: pre-line; font-size: 0.9rem; }

/* Sized for a finger: at 0.85rem with no padding it was an 18px-tall target
   on a phone. The padding makes it ~36px; the negative margin takes that
   padding back out of the layout so the word above doesn't move. */
.listen-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.9rem;
  text-decoration: none;
  margin-left: 0.5rem;
  padding: 0.4rem 0.5rem;
  margin-block: -0.2rem;
  border-radius: var(--radius-sm);
}
.listen-icon { font-size: 1.1rem; line-height: 1; }

/* font-family, never the `font` shorthand: `font` also resets font-size, and
   at equal class specificity this later rule would beat .listen-link's
   0.85rem, rendering the speaker icon at body size. */
.listen-btn {
  border: none;
  background: none;
  color: var(--accent);
  font-family: inherit;
  cursor: pointer;
}
.listen-btn:hover { background: var(--surface-2); }
.listen-btn:disabled { opacity: 0.6; cursor: default; }

/* min-width: 0 so this can actually shrink. It holds the 23rem listen panel,
   and a flex item won't go below its content's minimum width without this —
   so on a narrow window the row grew past the viewport and took the panel's
   right-hand end, the ⋮ menu included, off the side of the screen. The
   panel's own max-width: 100% only binds once its parent can give way. */
.form-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 0.35rem; min-width: 0; }
.listen-row { display: flex; align-items: center; }

.forvo-result { font-size: 0.8rem; color: var(--muted); }
.forvo-result:not(:empty) { margin-top: 0.2rem; }
.forvo-result audio { height: 28px; max-width: 220px; vertical-align: middle; }
.forvo-credit { margin-left: 0.4rem; }

/* One row per word of a phrase, each stacked: the word, then its player, then
   who recorded it. Laid out side by side, the label and the credit were flex
   items competing with the player for the panel's width — so every player came
   out a different size (as wide as its word was short) and a longer word was
   clipped under the bar, which is no way to read a word you're learning. */
.forvo-word-row { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.35rem 0; }
.forvo-word-label { font-weight: 600; color: var(--text-primary); }
.forvo-none { color: var(--muted); font-style: italic; }

.back-link { display: inline-block; margin-bottom: 1rem; font-size: 0.9rem; }

/* The entry detail page's top row. Back and Edit are pushed to opposite ends
   on purpose: they used to be two identical text links side by side, and the
   one you press on the way out of a word was a few pixels from the one that
   opens the editor. Back stays a link (the arrow is what people look for),
   Edit becomes a bordered button -- different shape, different place, so
   neither is hit by reaching for the other. */
.detail-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
/* Every class in this file that sets `display` and can also be toggled with
   the hidden attribute needs this -- see CLAUDE.md. */
.detail-nav[hidden] { display: none; }
.detail-nav .back-link { margin-bottom: 0; }
/* Keeps Edit at the right-hand end on a narrow screen too: a long enough
   back label wraps the row, and space-between would then put Edit back on
   the left where the thumb is. */
.detail-nav .edit-link { margin-left: auto; }

/* A back link that is the page's main way out gets a real target rather than
   a line of 0.9rem text: padded, boxed on hover, and comfortably thumb-sized
   on a phone. */
.back-link-lead {
  font-weight: 600;
  padding: 0.4rem 0.7rem 0.4rem 0.5rem;
  margin-left: -0.5rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
}
.back-link-lead:hover { background: var(--surface-2); text-decoration: underline; }

/* .btn is written for <button>; an anchor wearing it keeps the underline and
   the baseline of a line of text without these. */
a.btn { display: inline-block; text-decoration: none; }

.practice-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2.5rem 1.5rem;
  text-align: center;
  max-width: 480px;
  margin: 2rem auto;
}
.practice-prompt { font-size: 1.6rem; font-weight: 700; margin-bottom: 0.5rem; }
.practice-answer { font-size: 1.2rem; color: var(--text-secondary); margin: 1rem 0; min-height: 1.5rem; }
.practice-progress { color: var(--muted); font-size: 0.85rem; margin-bottom: 1rem; }

.btn {
  padding: 0.6rem 1.2rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.btn.primary { background: var(--accent); color: var(--accent-text); border-color: transparent; }
.btn.correct { background: var(--box-high); color: #fff; border-color: transparent; }
.btn.incorrect { background: var(--box-low); color: #fff; border-color: transparent; }

/* A quieter button for a row inside a panel, where a full-size one would be
   the loudest thing on screen. .danger reuses the same token the recorder's
   armed delete does — there is one 'are you sure' colour in this app. */
.btn.small { padding: 0.35rem 0.7rem; font-size: 0.82rem; }
.btn.danger { color: var(--danger, #c0392b); border-color: var(--danger, #c0392b); }

.btn-row { display: flex; gap: 0.75rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap; }
.btn-row[hidden] { display: none; }
/* These three were inline style="" attributes, which the Content-Security-Policy
   (style-src 'self', see src/server.js) refuses to apply. Keep it that way:
   a new one-off style goes here as a class, not onto the element. */
.btn-row.start { justify-content: flex-start; }
.btn-row.end { justify-content: flex-end; }

/* Deleting a word (edit page only). Set well apart from Save, under a rule,
   so it is never the button you reach for on the way to saving. */
.danger-zone {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
/* The one solid red button: the dialog's final yes. Everywhere else .danger
   is an outline. */
.btn.danger-solid { background: var(--box-low); color: #fff; border-color: transparent; }
.confirm-dialog {
  max-width: min(26rem, calc(100vw - 2rem));
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--text-primary);
}
.confirm-dialog::backdrop { background: rgba(0, 0, 0, 0.55); }
.confirm-dialog h2 { margin: 0 0 0.5rem; font-size: 1.15rem; overflow-wrap: anywhere; }
.confirm-dialog p { margin: 0 0 0.5rem; color: var(--text-secondary); }
.confirm-list { margin: 0.5rem 0 0; padding-left: 1.2rem; color: var(--text-secondary); font-size: 0.9rem; }
.panel.spaced-panel { margin-top: 1rem; }
h2.form-title { margin-top: 0; }

/* public/privacy.html — was an inline <style> block, same reason. */
.privacy { max-width: 42rem; margin: 2rem auto; padding: 0 16px; line-height: 1.55; }
.privacy h1 { font-size: 1.5rem; }
.privacy h2 { font-size: 1.1rem; margin-top: 1.6rem; }

.direction-toggle { display: flex; gap: 0.5rem; justify-content: center; max-width: 480px; margin: 0 auto 1rem; flex-wrap: wrap; }
.direction-toggle .btn { padding: 0.4rem 0.8rem; font-size: 0.85rem; font-weight: 500; }
.direction-toggle .btn.primary { font-weight: 700; }

.entry-form {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  max-width: 640px;
}
.entry-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.entry-form input, .entry-form select, .entry-form textarea {
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
}
.entry-form textarea { min-height: 4rem; resize: vertical; }
/* A checkbox and its words on one line; `.entry-form label` stacks, and
   `.entry-form input` pads, which a checkbox wants neither of. */
.entry-form .check-field { flex-direction: row; align-items: center; gap: 0.5rem; }
.entry-form .check-field input { padding: 0; }

/* The fields a source answer reveals: name + where we met for a person, unit +
   lesson for a Rosetta lesson, one line of text for anything else. Indented
   under the dropdown with a rule down the side, so it reads as belonging to
   the answer above it rather than as three more top-level fields.

   The second rule is not redundant. A class that sets its own `display` beats
   the browser's own [hidden] { display: none } at equal specificity, so an
   element toggled by the `hidden` attribute stays visible without it — the
   trap this project has now hit three times (see CLAUDE.md). */
.source-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.6rem 0.75rem;
  margin: -0.35rem 0 0;
  padding: 0.1rem 0 0.1rem 0.85rem;
  border-left: 3px solid var(--border);
}
.source-fields[hidden] { display: none; }
/* The "both optional" hint belongs to the group, not to the last field in it. */
.source-fields .field-hint { grid-column: 1 / -1; }

/* Where a word came from, on the entry detail page — only for a person or an
   "other"; a Rosetta lesson has its own tag up in the meta row. */
.source-line {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* The last AI review's summary, under the source line. */
.ai-review-line {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
  white-space: pre-line;
}

.form-rows-editor { display: flex; flex-direction: column; gap: 0.5rem; }
.form-row-editor {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding: 0.5rem;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}
.form-row-editor input { flex: 1 1 120px; min-width: 0; }
.primary-radio {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}

.stats-bar {
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.word-type-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.75rem;
}

.word-type-card {
  display: block;
  padding: 1rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}

.word-type-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.word-type-count { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.word-type-desc { margin: 0; color: var(--text-secondary); font-size: 0.9rem; }

/* --- Grammar rules: sections of collapsed cards ----------------------------
   Twenty verb rules is too many to read as twenty open cards, and exactly the
   right number to *skim* as twenty headings under five section titles — which
   is what <details> gets for free, keyboard and find-in-page included. */

/* A <summary> laid out with display:flex loses the browser's disclosure
   triangle entirely (it's the ::marker, and a flex container drops it), so
   draw one: a bordered square rotated into a chevron, turned a quarter turn
   when its <details> is open. Without this there's nothing on the row saying
   it opens. */
.rule-summary, .rule-section-summary { list-style: none; }
.rule-summary::-webkit-details-marker,
.rule-section-summary::-webkit-details-marker { display: none; }

.rule-summary::before, .rule-section-summary::before {
  content: '';
  flex: none;
  align-self: center;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.12s ease;
  opacity: 0.65;
}
details[open] > .rule-summary::before,
details[open] > .rule-section-summary::before { transform: rotate(45deg); }

.rule-section { margin-bottom: 1rem; }
.rule-section-summary {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  cursor: pointer;
  padding: 0.35rem 0;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.rule-section-count {
  font-weight: 600;
  letter-spacing: 0;
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
}
.rule-section > .rule-list { margin-top: 0.5rem; }

.rule-list { display: flex; flex-direction: column; gap: 0.5rem; }

.rule-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
}
.rule-card[open] { padding-bottom: 1rem; }

.rule-summary {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  cursor: pointer;
  list-style-position: outside;
}
.rule-title { font-size: 0.98rem; font-weight: 600; }
.rule-card[open] .rule-title { font-weight: 700; }

/* Rules Claude wrote rather than ones off Byron's own sheet — he should be
   able to tell which is which before trusting one. */
/* The "added by Claude" pill. `.rule-source` is the one on a grammar rule;
   `.claude-tag` is the same pill on a vocabulary entry (card and detail page).
   Same styling on purpose — it's the same claim, "this text isn't from your
   notes", and it should look identical wherever it appears. */
.rule-source,
.claude-tag {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
/* Same pill shape, red: hidden from its word type's page by default. */
.bad-word-tag {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--verdict-poor);
  background: var(--verdict-poor-bg);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
/* Same pill shape, amber: "not finished yet" rather than "not yours". */
.review-tag {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--verdict-close);
  background: var(--verdict-close-bg);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}

.rule-body { margin-top: 0.75rem; }
.rule-edit { margin-bottom: 0; margin-top: 0.85rem; font-size: 0.82rem; }

.rule-explanation p, .rule-examples p {
  margin: 0 0 0.4rem;
  color: var(--text-secondary);
}
.rule-explanation p:last-child, .rule-examples p:last-child { margin-bottom: 0; }

.rule-examples-label {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-top: 0.75rem;
  margin-bottom: 0.35rem;
}

.rule-examples p {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 0.85rem;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
}

/* --- Sentence builder ------------------------------------------------------
   The assembled sentence sits above the controls and stays put while you
   change them; each part of it is a column with the words on top and the
   explanation underneath, so the annotation reads as a label on the phrase
   rather than a footnote somewhere else on the page. */

.builder-output-panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
}

.sentence-target {
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.35;
  margin-bottom: 0.35rem;
}
.sentence-english {
  color: var(--text-secondary);
  font-size: 1rem;
  font-style: italic;
  margin-bottom: 0.75rem;
}

/* The sentence and its meaning, locked to the top of the page. Every control
   below it changes the sentence, so scrolling down to reach one used to mean
   scrolling the result off the screen — watching what your change did was the
   one thing this page is for.

   Sticky offset is the topbar's LIVE height (--topbar-h, published by
   trackTopbarHeight in app.js), floored, for the reasons written out over
   .detail-head — the topbar wraps, so no constant is right, and rounding up
   leaves a crack the page scrolls through.

   It holds the sentence and the gloss only. The 🔊 control stays in the panel
   below: a .listen-panel opens in flow, and one opened in here would push the
   bar to half the viewport and leave it there. */
.builder-head {
  position: sticky;
  top: var(--topbar-h, 3.25rem);
  z-index: 3;
  padding: 0.7rem 1.25rem;
  margin-bottom: 0.75rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* A two-clause sentence with a long gloss under it must not eat the screen
     it is pinned to; it scrolls inside the bar instead. */
  max-height: 32vh;
  overflow-y: auto;
}
.builder-head[hidden] { display: none; }
.builder-head .sentence-english { margin-bottom: 0; }

.builder-incomplete { color: var(--muted); margin: 0; }

/* What each word of a part means. Paired word-and-gloss so a three-word part
   doesn't leave you matching meanings to words by eye; wraps rather than
   stacking, since a part card is narrow. */
.part-glosses {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  margin-bottom: 0.4rem;
}
.part-gloss { font-size: 0.82rem; line-height: 1.4; }
.part-gloss-word { color: var(--text-secondary); }
.part-gloss-en { color: var(--muted); font-style: italic; }

.sentence-parts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1rem;
}
.sentence-part {
  flex: 1 1 11rem;
  min-width: 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.7rem;
}
/* The topic is the thing the whole sentence is organised around, so it gets
   the one piece of colour — what moves when you flip focus is the point. */
.sentence-part-topic { border-color: var(--accent); }
.sentence-part-enclitics { border-style: dashed; }

.part-words { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.3rem; }
.sentence-word { color: var(--text-primary); text-decoration: none; }
a.sentence-word { border-bottom: 1px dotted var(--muted); }
a.sentence-word:hover { color: var(--accent); border-bottom-color: var(--accent); }
.sentence-word-marker, .sentence-word-linker { color: var(--text-secondary); }

.part-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.part-note { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.45; }

.sentence-notes {
  margin: 1rem 0 0;
  padding-left: 1.1rem;
  color: var(--text-secondary);
  font-size: 0.85rem;
  line-height: 1.5;
}
.sentence-notes li { margin-bottom: 0.35rem; }

/* Keeping a built sentence as a phrase. A quiet row under the listen control,
   not a primary action: saving is worth doing deliberately and isn't what the
   page is for. */
.sentence-save {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-top: 0.75rem;
}
.sentence-save-status { font-size: 0.85rem; color: var(--text-secondary); }
.sentence-save-failed { color: var(--danger, #c0392b); }

/* Where the builder says which phrase it was opened from — and, in the
   drifted state, that the sentence on screen is no longer that phrase. */
.builder-from {
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.7rem;
  border-left: 3px solid var(--accent);
  background: var(--surface-2, transparent);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
  line-height: 1.5;
}
.builder-from-drifted { border-left-color: var(--danger, #c0392b); }
.builder-from a { margin-left: 0.35rem; white-space: nowrap; }

.builder-clause, .builder-joiner {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.25rem 1.25rem;
}
.builder-pattern-summary {
  margin: 0.5rem 0 1rem;
  color: var(--text-secondary);
  font-size: 0.85rem;
  line-height: 1.5;
}

.builder-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.85rem;
}
.builder-slot { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.builder-slot select {
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
  max-width: 100%;
}
.builder-slot-label { font-size: 0.85rem; color: var(--text-secondary); }
.builder-optional { color: var(--muted); font-size: 0.75rem; }

/* What the grammar knows about the word in this slot. Hidden rather than
   omitted so the change handler has something to repaint — and it carries the
   [hidden] override by policy, not because this class sets `display` today:
   .btn-row, .lang-menu and .source-fields each needed it after the fact. */
.builder-slot-note { margin: 0; font-size: 0.75rem; line-height: 1.4; color: var(--muted); }
.builder-slot-note[hidden] { display: none; }

.builder-checks { display: flex; flex-wrap: wrap; gap: 0.85rem 1.25rem; margin-top: 0.85rem; }
.builder-check {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--text-secondary);
  cursor: pointer;
}
.builder-slot .builder-check { margin-top: 0.1rem; }

@media (max-width: 560px) {
  .sentence-target { font-size: 1.3rem; }
  .builder-head { top: 0; padding: 0.55rem 0.85rem; margin-bottom: 0.6rem; }
  .builder-head .sentence-english { font-size: 0.9rem; }
  .sentence-part { flex-basis: 100%; }

  /* Word above its 🔊 control rather than beside it. Side by side on a phone,
     the two split the row and the listen panel was left with about 200px —
     enough to draw a player, not enough to use one. Stacked, the panel gets
     the whole width. */
  .form-row { flex-direction: column; align-items: stretch; gap: 0.35rem; }
  .form-actions { align-items: flex-start; }
  .form-actions .listen-link { margin-left: -0.5rem; }  /* its padding: line the icon up with the word */
}

/* --- The listen menu and the recorder -------------------------------------- */

/* The count of human recordings a word has, on the button itself — the whole
   point of the walkthrough is knowing at a glance what still needs a voice. */
.listen-count {
  display: inline-block;
  min-width: 1.1rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 0.7rem;
  line-height: 1.3;
  text-align: center;
}

/* No `display` here on purpose, so the browser's own [hidden] { display: none }
   still applies — see CLAUDE.md on the `[hidden]` trap. The override below is
   belt and braces for anything that later gives this a display of its own. */
.listen-panel {
  margin-top: 0.4rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  text-align: left;
  width: 23rem;
  max-width: 100%;
}
.listen-panel[hidden] { display: none; }
.listen-loading { color: var(--muted); font-size: 0.85rem; margin: 0; }

.listen-sources { list-style: none; margin: 0; padding: 0; }
.listen-source {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.listen-source:last-child { border-bottom: none; }
.listen-source-btn {
  border: none;
  background: none;
  padding: 0;
  color: var(--accent);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.listen-source-btn:disabled { opacity: 0.6; cursor: default; }
.listen-source-note { display: block; color: var(--muted); font-size: 0.78rem; }
.listen-source-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.listen-speaker { font-weight: 600; }
.listen-delete {
  border: none;
  background: none;
  padding: 0 0.2rem;
  color: var(--muted);
  font-family: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}
.listen-delete-armed { color: var(--danger, #c0392b); font-weight: 600; }
.listen-correction {
  margin-top: 0.2rem;
  padding: 0.3rem 0.45rem;
  border-left: 3px solid var(--accent);
  background: var(--surface-1);
  font-size: 0.8rem;
}
.listen-source-recording audio { height: 30px; width: 100%; margin-top: 0.25rem; }
.listen-record-btn { font-size: 0.95rem; }

.recorder { margin-top: 0.6rem; border-top: 1px dashed var(--border); padding-top: 0.6rem; }
.recorder-fields { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.5rem; }
.recorder-field { display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 9rem; }
.recorder-field span { color: var(--muted); font-size: 0.75rem; }
.recorder-field em { font-style: normal; opacity: 0.8; }
.recorder-field input {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.85rem;
}
.recorder .btn { padding: 0.35rem 0.7rem; font-size: 0.85rem; }
.recorder-controls { align-items: center; }
/* Two classes, same as .btn.primary, so this has to stay later in the file. */
.rec-toggle.recording { background: var(--box-low); color: #fff; border-color: transparent; }
/* The one control someone else has to hit must not move: min-width holds it still
   when the label swaps, and the status line below is what keeps the elapsed-time
   ticker from dragging it sideways ten times a second. */
.rec-toggle { min-width: 7rem; }
.rec-status {
  color: var(--muted);
  font-size: 0.78rem;
  text-align: center;
  margin: 0.4rem 0 0.5rem;
  min-height: 1.1rem;
  font-variant-numeric: tabular-nums;
}
.rec-preview:not(:empty) { margin: 0.4rem 0; }
.rec-preview audio { height: 30px; width: 100%; }
.rec-size { color: var(--muted); font-size: 0.75rem; }

/* --- The #/record walkthrough ---------------------------------------------- */

.record-header { margin-bottom: 1rem; }
.record-intro { color: var(--text-secondary); font-size: 0.9rem; margin: 0.25rem 0 0.5rem; }
.record-toggle { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--text-secondary); }

.record-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  max-width: 520px;
  margin: 0 auto;
  text-align: center;
}
.record-progress { color: var(--muted); font-size: 0.85rem; }
.record-word { font-size: 2rem; font-weight: 700; margin: 0.4rem 0 0.2rem; }
.record-english { color: var(--text-secondary); font-size: 1.05rem; }
.record-pron { color: var(--muted); font-size: 0.9rem; margin-top: 0.15rem; }
.record-links { display: flex; align-items: center; justify-content: center; gap: 0.6rem; margin: 0.6rem 0 0.2rem; }
.record-links .back-link { margin-bottom: 0; }
/* The walkthrough's panel is the page's main content, not a popover off a
   button, so it fills the card rather than sitting in a 23rem column. */
.record-panel { width: 100%; margin-top: 0.8rem; }
.record-nav { justify-content: center; margin-top: 1rem; }

/* A conjugation cell is a table column, so a full-width panel would push the
   whole table into its horizontal scroll. Narrower there. */
.conj-cell .listen-panel { width: 14rem; }

/* Inside the menu the panel is narrow, so the player fills it and its credit
   sits under rather than beside — the inline version overflowed the panel. */
.listen-panel .forvo-result audio { width: 100%; max-width: none; }
.listen-panel .forvo-credit { display: block; margin-left: 0; }

/* Save stays disabled until there is a take to save, so it has to look it —
   a blue primary button that does nothing reads as broken. */
.btn:disabled { opacity: 0.45; cursor: default; }

/* --- Pronunciation checker ------------------------------------------------ */

/* The word you are being asked to say, big enough to read at arm's length —
   this is a "hold the phone up and speak" screen, not a reading screen. */
.pron-checker { margin-top: 0.6rem; border-top: 1px dashed var(--border); padding-top: 0.6rem; }
.pron-prompt { text-align: center; margin-bottom: 0.6rem; }
.pron-prompt-label { display: block; color: var(--muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; }
.pron-word { font-size: 1.6rem; font-weight: 700; line-height: 1.3; }
/* How Byron wrote the word down as being said — "buh-sar" under besar. Its own
   line below the word rather than beside it, because it is an aside about the
   word above and not part of it; muted and small for the same reason. Shown
   because the Indonesian table now SCORES that vowel, and asking for a schwa
   while displaying only the spelling withholds the one thing the spelling
   never carried. */
.pron-prompt-said { display: block; color: var(--muted); font-size: 0.85rem; margin-top: 0.15rem; }

/* Same flip-in-place rule as the take recorder: stop is pressed where record
   just was, and the colour is what makes the state readable at a glance. Must
   stay after .btn.primary, whose background it overrides. */
.pron-controls { justify-content: center; }
.pron-toggle { min-width: 7rem; }
.pron-toggle.recording { background: var(--box-low); color: #fff; border-color: transparent; }

/* The elapsed time reflows every 100ms, so it gets its own centred line rather
   than sharing the button row — text that rewrites ten times a second drags a
   centred button sideways with it. Same lesson as .rec-status above. */
.pron-status { text-align: center; color: var(--muted); font-size: 0.8rem; margin-top: 0.4rem; min-height: 1.2em; }

.pron-verdict { margin-top: 0.7rem; text-align: center; }
/* .pron-verdict is toggled with the hidden attribute and sets no display of its
   own, so it needs no [hidden] override — but anything given display here does.
   That trap has bitten this file three times (.btn-row, .lang-menu,
   .source-fields); assume the next toggled class needs it. */

.pron-why { margin-top: 0.4rem; color: var(--text-primary); font-size: 0.9rem; }
.pron-hint { margin-top: 0.15rem; color: var(--text-secondary); font-size: 0.82rem; }
.pron-readout { margin-top: 0.45rem; color: var(--muted); font-size: 0.78rem; }

/* The verdict pill. Colour is never the only signal — every state carries its
   own mark (✓ ≈ ✗ ?), because red/green is the commonest colour-vision gap
   there is and this pill is the one thing on the screen meant to be read in a
   glance. Same reasoning as .stressed-vowel keeping the accent glyph. */
.verdict-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  border: 1px solid transparent;
}
.verdict-mark { font-size: 1.05rem; line-height: 1; }
.verdict-good    { color: var(--verdict-good);    background: var(--verdict-good-bg);    border-color: var(--verdict-good); }
.verdict-close   { color: var(--verdict-close);   background: var(--verdict-close-bg);   border-color: var(--verdict-close); }
.verdict-poor    { color: var(--verdict-poor);    background: var(--verdict-poor-bg);    border-color: var(--verdict-poor); }
.verdict-unknown { color: var(--verdict-unknown); background: var(--verdict-unknown-bg); border-color: var(--verdict-unknown); }

/* Per-piece marks on the word. Underlined as well as coloured, for the same
   reason the pill has a glyph. No marks exist until there is a model to
   produce them; the styles are here so the component has one rendering rather
   than two written months apart. */
.pron-mark { border-bottom: 3px solid currentColor; }
.pron-mark-good    { color: var(--verdict-good); }
.pron-mark-close   { color: var(--verdict-close); }
.pron-mark-poor    { color: var(--verdict-poor); }
.pron-mark-unknown { color: var(--verdict-unknown); }

/* The model's install panel, at the bottom of the checker. It is deliberately
   the quietest thing in there — the feature is saying the word, not managing a
   download — but it has to state the size before offering the button, because
   303 MB over a ship's wifi is a decision and not a detail.

   No display is set on .pron-model itself, so it needs no [hidden] override.
   Its .pron-model-actions rows are .btn-row, which already carries one. */
.pron-model {
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
  font-size: 0.8rem;
  color: var(--text-secondary);
}
.pron-model-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
}
.pron-model-state { font-weight: 600; color: var(--text-primary); }
.pron-model-ready { color: var(--verdict-good); }
.pron-model-size { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pron-model-note { margin-top: 0.25rem; color: var(--muted); font-size: 0.76rem; }
.pron-model-warn { color: var(--verdict-poor); }
/* The row of a progress bar and a button both want to be full width on a phone;
   .btn-row wraps, and this keeps the bar from being the thing that doesn't. */
.pron-model-bar { width: 100%; height: 0.5rem; margin-top: 0.4rem; }
.pron-model-actions { margin-top: 0.5rem; gap: 0.5rem; }

/* Practice's "Show bad words" switch, centred under the direction toggle. */
.practice-bad-words { display: flex; justify-content: center; }

/* The account menu's "Ask AI" is a link; it borrows the menu button's look. */
a.user-menu-item { text-decoration: none; box-sizing: border-box; }

/* #/ask — questions to Claude about the picked language, newest first. */
.ask-page { max-width: 720px; }
.ask-title { display: flex; align-items: center; gap: 0.5rem; text-transform: none; }
.ask-form { max-width: none; }
.ask-note { margin: 0; font-size: 0.85rem; color: var(--text-secondary); }
.ask-note[hidden] { display: none; }
.ask-list-title { margin: 1.75rem 0 0.75rem; font-size: 1rem; }
.ask-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.ask-item {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  /* The question (or its <details>) beside the delete button. */
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
}
.ask-body { flex: 1; min-width: 0; }
.ask-question { min-width: 0; margin: 0; font-weight: 600; white-space: pre-line; overflow-wrap: anywhere; }
/* Same chevron as the grammar rules: a flex <summary> loses its ::marker. */
.ask-summary { display: flex; gap: 0.6rem; align-items: baseline; cursor: pointer; list-style: none; }
.ask-summary::-webkit-details-marker { display: none; }
.ask-summary::before {
  content: '';
  flex: none;
  align-self: center;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.12s ease;
  opacity: 0.65;
}
details[open] > .ask-summary::before { transform: rotate(45deg); }
.ask-meta { margin: 0.3rem 0 0; font-size: 0.78rem; color: var(--muted); }
/* Plain text from the routine: escaped, with its line breaks kept. */
.ask-answer { margin-top: 0.75rem; white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.5; }
.ask-pending { margin: 0.6rem 0 0; font-size: 0.85rem; color: var(--text-secondary); }
.ask-pending .btn { margin-left: 0.4rem; }
