/* ============================================================
   Lexicon — unified dictionary UI. Design system.
   Warm "manuscript" palette (gold accent, cream text, serif
   headings). Dark by default; follows OS or the theme toggle.
   ============================================================ */
:root {
  --bg: #131009;
  --bg-glow: #1c160c;
  --surface: #1c170f;
  --surface-2: #241d12;
  --input: #17130c;
  --border: #3a3021;
  --border-strong: #4d4029;
  --text: #f3ead9;
  --text-dim: #a8967a;
  --text-faint: #7c6d57;
  --accent: #dba847;
  --accent-ink: #241a05;
  --good: #6fb8ac;
  --warn: #ffd166;
  --danger: #ff8b7c;
  --info: #6fb8ac;
  --radius: 14px;
  --radius-sm: 9px;
  --mono: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  /* soft tints derive from the accents, so they follow the theme automatically */
  --accent-soft: color-mix(in srgb, var(--accent) 15%, transparent);
  --good-soft: color-mix(in srgb, var(--good) 17%, transparent);
  --warn-soft: color-mix(in srgb, var(--warn) 16%, transparent);
  --shadow: 0 1px 2px rgba(0,0,0,.28), 0 10px 34px rgba(0,0,0,.32);
  color-scheme: dark;
}
/* ---- light "parchment" ---- */
:root[data-theme="light"] {
  --bg: #f6f0e2; --bg-glow: #ece2c9; --surface: #fffcf4; --surface-2: #f3ead4;
  --input: #fffef9; --border: #ddceac; --border-strong: #cbb888; --text: #2a2115;
  --text-dim: #7c6b4c; --text-faint: #96876a; --accent: #a3701c; --accent-ink: #fff8ea;
  --good: #2d6e63; --warn: #8a6412; --danger: #b4392c; --info: #2d6e63;
  --shadow: 0 1px 2px rgba(120,90,30,.08), 0 10px 30px rgba(120,90,30,.10);
  color-scheme: light;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg: #f6f0e2; --bg-glow: #ece2c9; --surface: #fffcf4; --surface-2: #f3ead4;
    --input: #fffef9; --border: #ddceac; --border-strong: #cbb888; --text: #2a2115;
    --text-dim: #7c6b4c; --text-faint: #96876a; --accent: #a3701c; --accent-ink: #fff8ea;
    --good: #2d6e63; --warn: #8a6412; --danger: #b4392c; --info: #2d6e63;
    --shadow: 0 1px 2px rgba(120,90,30,.08), 0 10px 30px rgba(120,90,30,.10);
    color-scheme: light;
  }
}
:root[data-theme="dark"] { color-scheme: dark; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--sans);
  background:
    radial-gradient(circle at 12% 0%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 34rem),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition: background .2s, color .2s;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font-family: inherit; }
/* serif headings give the scholarly, dictionary feel */
.brand, h1, h2, h3, .entry-lemma, .tile .tval, .empty .big + div { font-family: var(--serif); }

/* ---------- Layout ---------- */
.app { max-width: 1120px; margin: 0 auto; padding: 0 20px 80px; }
header.top {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
  margin: 0 -20px 24px;
  padding: 0 20px;
}
.top-row { max-width: 1120px; margin: 0 auto; display: flex; align-items: center; gap: 14px; height: 58px; }
.brand { font-weight: 700; font-size: 17px; letter-spacing: -.02em; display: flex; align-items: center; gap: 8px; }
.brand .logo { color: var(--accent); font-size: 20px; }
.top-spacer { flex: 1; }
.status-pill { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text-dim);
  background: var(--surface); border: 1px solid var(--border); padding: 5px 11px; border-radius: 999px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.dot.ok { background: var(--good); box-shadow: 0 0 0 3px var(--good-soft); }
.dot.bad { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.icon-btn { background: var(--surface); border: 1px solid var(--border); color: var(--text-dim);
  width: 34px; height: 34px; border-radius: 9px; cursor: pointer; font-size: 15px; display: grid; place-items: center; }
.icon-btn:hover { color: var(--text); border-color: var(--border-strong); }

/* ---------- Tabs ---------- */
nav.tabs { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; }
nav.tabs::-webkit-scrollbar { display: none; }
.tab {
  background: transparent; border: none; color: var(--text-dim);
  padding: 9px 15px; border-radius: 9px; cursor: pointer; font-size: 14px; font-weight: 550;
  white-space: nowrap; display: inline-flex; align-items: center; gap: 7px;
}
.tab:hover { background: var(--surface-2); color: var(--text); }
.tab.active { background: var(--accent); color: var(--accent-ink); }
.tab .ico { font-size: 15px; }

/* ---------- Cards / surfaces ---------- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
.section-title { font-size: 13px; font-weight: 650; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); margin: 0 0 12px; }
.muted { color: var(--text-dim); }
.faint { color: var(--text-faint); }

/* ---------- Stat tiles (Overview) ---------- */
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.tile {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 17px; text-align: left; cursor: default; position: relative; transition: transform .08s, border-color .12s, box-shadow .12s;
}
.tile.clickable { cursor: pointer; }
.tile.clickable:hover { border-color: var(--accent); box-shadow: var(--shadow); transform: translateY(-1px); }
.tile .tval { font-size: 26px; font-weight: 720; letter-spacing: -.02em; line-height: 1.1; }
.tile .tlabel { font-size: 13px; color: var(--text-dim); margin-top: 3px; font-weight: 560; }
.tile .tsub { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.tile .go { position: absolute; top: 14px; right: 14px; color: var(--text-faint); font-size: 13px; opacity: 0; transition: opacity .12s; }
.tile.clickable:hover .go { opacity: 1; }

/* ---------- Badges ---------- */
.badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; background: var(--surface-2); color: var(--text-dim); border: 1px solid var(--border); }
.badge.accent { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.badge.good { background: var(--good-soft); color: var(--good); border-color: transparent; }
.badge.warn { background: var(--warn-soft); color: var(--warn); border-color: transparent; }
.badge.pos { font-family: var(--mono); text-transform: lowercase; }

/* ---------- Inputs ---------- */
.input, select.input, textarea.input {
  width: 100%; background: var(--input); border: 1px solid var(--border-strong); color: var(--text);
  padding: 11px 13px; border-radius: var(--radius-sm); font-size: 15px; font-family: inherit; outline: none;
}
.input:focus, select.input:focus, textarea.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* ONE focus ring for the whole app. Five tabs each appended their own :focus-visible rule
   listing that tab's controls, which left everything nobody enumerated with no visible
   focus at all — the header tabs, the theme toggle, the 🔊 buttons and related-word links
   inside every entry card, and every link. The outline follows each element's own
   border-radius, so it must not set one (a rule that did turned .chip-btn from a pill into
   a rounded rectangle on focus). */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
textarea.input { resize: vertical; min-height: 110px; line-height: 1.6; }
.btn { background: var(--accent); color: var(--accent-ink); border: none; padding: 11px 18px; border-radius: var(--radius-sm);
  font-size: 14.5px; font-weight: 600; cursor: pointer; }
.btn:hover { filter: brightness(1.06); }
.btn.ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
.btn.ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.grow { flex: 1; min-width: 160px; }

/* ---------- Entry card (renderEntry) ---------- */
.entry { padding: 18px 20px; }
.entry + .entry { margin-top: 12px; }
.entry-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.entry-lemma { font-size: 23px; font-weight: 700; letter-spacing: -.01em; }
.entry-lemma.rtl { direction: rtl; }
.entry-trad { font-size: 18px; color: var(--text-dim); }
.entry-pinyin { font-family: var(--mono); font-size: 13.5px; color: var(--info); }
.entry-meta { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; margin-top: 2px; }
.entry-block { margin-top: 15px; }
.entry-block h4 { font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); margin: 0 0 8px; }
.sense { padding: 8px 0; border-top: 1px solid var(--border); }
.sense:first-of-type { border-top: none; }
.sense-def { font-size: 15px; }
.sense-gloss { color: var(--text-dim); font-size: 14px; }
.sense .dom { font-size: 11.5px; color: var(--info); font-style: italic; margin-left: 6px; }
.pron { display: inline-flex; align-items: center; gap: 6px; margin-right: 14px; font-family: var(--mono); font-size: 14px; }
.pron .play { cursor: pointer; color: var(--accent); border: none; background: none; font-size: 15px; padding: 0; }
.tr-lang { margin-bottom: 8px; }
.tr-lang .lname { font-size: 12px; font-weight: 600; color: var(--text-faint); }
.tr-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 3px; }
.tr-chip { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 3px 9px; font-size: 14px;
  display: inline-flex; align-items: center; gap: 5px; }
.ex { padding: 7px 0; border-top: 1px solid var(--border); font-size: 14px; }
.ex:first-of-type { border-top: none; }
.ex .ex-tr { color: var(--text-dim); font-size: 13px; }
.rel-group { margin-bottom: 6px; }
.rel-group .rname { font-size: 12px; color: var(--text-faint); font-weight: 600; text-transform: capitalize; }
.rel-words { display: inline; }
/* real <button>s so the keyboard can reach them; strip the UA chrome so they still read
   as inline links inside the sentence */
.rel-word { color: var(--accent); cursor: pointer; background: none; border: none; padding: 0;
  font: inherit; text-align: left; }
.rel-word:not(:last-child)::after { content: ", "; color: var(--text-faint); }
.src-list { display: flex; flex-direction: column; gap: 5px; }
.src-item { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-dim); }
.src-item .sname { font-weight: 600; color: var(--text); }

/* ---------- Tables ---------- */
table.grid { width: 100%; border-collapse: collapse; font-size: 14px; }
table.grid th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint);
  font-weight: 600; padding: 9px 12px; border-bottom: 1px solid var(--border); position: sticky; top: 58px; background: var(--surface); }
table.grid td { padding: 9px 12px; border-bottom: 1px solid var(--border); }
table.grid tr:hover td { background: var(--surface-2); }

/* ---------- Language pills ---------- */
.lang-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.lang-pill { display: flex; justify-content: space-between; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: 9px 12px; cursor: pointer; }
.lang-pill:hover { border-color: var(--accent); }
.lang-pill .lp-name { font-weight: 560; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lang-pill .lp-code { font-family: var(--mono); font-size: 11px; color: var(--text-faint); }
.lang-pill .lp-count { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* ---------- Misc ---------- */
.empty { text-align: center; padding: 48px 20px; color: var(--text-dim); }
.empty .big { font-size: 34px; margin-bottom: 10px; }
.spinner { width: 22px; height: 22px; border: 2.5px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite; margin: 40px auto; }
@keyframes spin { to { transform: rotate(360deg); } }
.pager { display: flex; gap: 10px; align-items: center; justify-content: center; margin-top: 18px; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip-btn { background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); border-radius: 999px;
  padding: 5px 12px; font-size: 13px; cursor: pointer; }
.chip-btn:hover, .chip-btn.on { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.hint { font-size: 13px; color: var(--text-faint); }
.divider { height: 1px; background: var(--border); margin: 18px 0; }
[hidden] { display: none !important; }

/* ============================================================
   Translate tab (views/translate.js) — tab-scoped.
   Every selector below starts with a .translate-* class that the
   view introduces; nothing here can leak into another tab.
   ============================================================ */
.translate-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.translate-word { position: relative; flex: 1 1 240px; min-width: 0; }
.translate-pair { display: flex; gap: 8px; align-items: center; flex: 1 1 280px; min-width: 0; }
.translate-lang { flex: 1 1 0; min-width: 0; }     /* min-width:0 lets the selects shrink below 360px */
.translate-swap { flex: 0 0 auto; }
/* below ~560px the word and the language pair each take a full row instead of squeezing */
@media (max-width: 560px) {
  .translate-word, .translate-pair { flex: 1 1 100%; }
}

.translate-drop { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 50;
  max-height: 300px; overflow-y: auto; overflow-x: hidden; padding: 4px; }
.translate-drop .sug-row { display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 6px 8px; border-radius: 6px; cursor: pointer; }
.translate-drop .sug-row.on { background: var(--surface-2); }
/* a long headword must ellipse inside the row, never widen the page */
.translate-drop .sug-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* reserve room for the tallest early state so loading -> loaded never moves the page */
.translate-results { min-height: 300px; }
.translate-busy { opacity: .5; transition: opacity .12s; }

.translate-chip { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 11px; font-family: inherit; font-size: 15px; color: var(--text); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%; overflow-wrap: anywhere;
  text-align: left; }
.translate-chip:hover { border-color: var(--accent); color: var(--accent); }
.translate-chip.rtl { direction: rtl; }

.translate-head-word { font-family: var(--serif); font-size: 23px; font-weight: 700;
  letter-spacing: -.01em; overflow-wrap: anywhere; min-width: 0; }
.translate-head-word.rtl { direction: rtl; }
.translate-sense { padding: 9px 0; border-top: 1px solid var(--border); }
.translate-sense:first-of-type { border-top: none; }
.translate-gloss { color: var(--text-dim); font-size: 14px; overflow-wrap: anywhere; }
/* the .entry-block h4 look, minus the uppercasing — these headers contain real words */
.translate-step { font-size: 12.5px; font-weight: 650; letter-spacing: .02em; color: var(--text-faint);
  margin: 0 0 8px; overflow-wrap: anywhere; }
.translate-status { margin: 0 0 12px; min-height: 19px; }   /* reserved: status text must not shift the results */
.translate-alt { background: none; border: none; padding: 0; font: inherit; font-size: 13px;
  color: var(--accent); cursor: pointer; text-decoration: underline; }

/* focus rings: see the single :focus-visible rule in the base section */

/* ============================================================
   Sources tab (views/sources.js) — tab-scoped; every selector
   below starts with a .sources- class owned by that view.
   ============================================================ */
/* Reserve the table's height while loading so the page does not snap taller
   when the 585-row payload lands; dropped once real content sizes the tab. */
.sources-loading { min-height: min(68vh, 620px); }
.sources-toolbar .input { margin-bottom: 0; }
.sources-search { max-width: 260px; }
.sources-pick { max-width: 200px; }
/* The catalogue is long: scroll it inside the card instead of the page, which also
   keeps the sticky header useful and stops a 585-row table from owning the scrollbar. */
.sources-tablewrap { max-height: min(68vh, 620px); overflow: auto; overscroll-behavior: contain; }
/* table.grid th sticks to top:58px for the page header; inside this scrollport the
   offset must be 0 or the header hovers a row's height below the top edge. */
.sources-tablewrap table.grid th { top: 0; z-index: 2; }
.sources-tablewrap table.grid td { vertical-align: top; }
.sources-sort { background: none; border: none; padding: 0; margin: 0; color: inherit; cursor: pointer;
  font: inherit; letter-spacing: inherit; text-transform: inherit; display: inline-flex; align-items: center; gap: 4px; }
.sources-sort:hover { color: var(--text); }
.sources-sort .ind { font-size: 9px; color: var(--accent); }

.sources-name { font-weight: 600; overflow-wrap: anywhere; }
.sources-slug { font-family: var(--mono); font-size: 11px; color: var(--text-faint); overflow-wrap: anywhere; }
.sources-wrap { overflow-wrap: anywhere; }
.sources-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sources-num .sources-sort { justify-content: flex-end; }
.sources-zero { color: var(--text-faint); }
@media (max-width: 560px) {
  .sources-search, .sources-pick { max-width: none; width: 100%; }
  .sources-tablewrap table.grid { font-size: 13px; }
  .sources-tablewrap table.grid th, .sources-tablewrap table.grid td { padding: 8px 9px; }
  .sources-name { min-width: 108px; }
}

/* ============================================================
   Overview tab (views/overview.js) — tab-scoped. Every selector
   below starts with an .overview-* class owned by that view, so
   nothing here can reach another tab.
   ============================================================ */

/* Counters reach 11 digits (365,167,072 / 256,554,480). At 26px those overflowed
   the 190px tile and pushed the whole page into horizontal scroll around 420px
   wide, so the value scales with the viewport and the columns start narrower. */
.overview-tiles { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); }
.overview-tile .tval { font-size: clamp(19px, 4.4vw, 26px); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.overview-tile .tlabel, .overview-tile .tsub { overflow-wrap: anywhere; }
.overview-tile .overview-i { position: absolute; top: 14px; right: 14px; font-size: 12px; }
.overview-tile.overview-info-on { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* Skeleton tiles: same count and near-identical box height as the real ones, so the
   grid does not resize when the numbers arrive. */
.overview-skel { cursor: default; }
.overview-skel .overview-bar { background: var(--surface-2); border-radius: 6px; animation: overview-pulse 1.2s ease-in-out infinite; }
.overview-skel .overview-b1 { height: calc(clamp(19px, 4.4vw, 26px) * 1.1); width: 62%; }
.overview-skel .overview-b2 { height: 20px; width: 46%; margin-top: 3px; }
.overview-skel .overview-b3 { height: 17px; width: 72%; margin-top: 2px; }
@keyframes overview-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* Reserved strip under the title: spinner while loading, "updated" note after.
   Occupied in both states === no vertical jump for the tiles below it. */
.overview-loading { display: flex; align-items: center; gap: 10px; min-height: 30px; margin-bottom: 10px; }
.overview-loading .spinner { width: 15px; height: 15px; border-width: 2px; margin: 0; }
.overview-stats-msg:not(:empty) { margin-bottom: 14px; }

.overview-info-panel .overview-close { padding: 3px 10px; font-size: 13px; line-height: 1.4; }

.overview-langs { scroll-margin-top: 74px; }
.overview-lang-grid { grid-template-columns: repeat(auto-fill, minmax(172px, 1fr)); }
/* Grid items and flex children default to min-width:auto, which lets a long name
   ("Standard Moroccan Tamazight") widen the column instead of ellipsing. */
.overview-pill, .overview-pill-text { min-width: 0; }
.overview-lang-loading { padding: 24px 0; text-align: center; }
.overview-lang-loading .spinner { margin: 0 auto 10px; }
.overview-lang-status:not(:empty) { margin-top: 14px; }
.overview-lang-status .hint { margin-right: 4px; }
.overview-more, .overview-all { padding: 6px 14px; font-size: 13px; }

/* These add to the base :focus-visible ring: the tile also takes an accent border and
   reveals its → affordance, which the shared rule cannot express. */
.overview-tile:focus-visible,
.overview-pill:focus-visible,
.overview-refresh:focus-visible,
.overview-retry:focus-visible,
.overview-more:focus-visible,
.overview-all:focus-visible,
.overview-close:focus-visible,
.overview-filter:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent);
}
.overview-tile:focus-visible .go { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .overview-skel .overview-bar { animation: none; }
}

/* ============================================================
   Dictionary tab (views/dictionary.js) — tab-scoped additions.
   Every selector below starts with a .dictionary-* class owned by that view,
   so nothing here can reach into another tab.
   ============================================================ */
.dictionary-form { margin-bottom: 14px; align-items: flex-start; }
.dictionary-inputwrap { position: relative; }
.dictionary-lang { max-width: 132px; font-family: var(--mono); }

/* Autocomplete popover */
.dictionary-sug {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 50;
  max-height: 320px; overflow-y: auto; overflow-x: hidden; padding: 4px;
}
.dictionary-sug-row {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 6px 8px; border-radius: 6px; cursor: pointer;
}
.dictionary-sug-row[aria-selected="true"] { background: var(--surface-2); }
.dictionary-sug-lemma { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
/* the gloss is the elastic column: it takes the slack and ellipsises */
.dictionary-sug-gloss { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dictionary-sug-note { padding: 8px 9px; font-size: 13px; color: var(--text-faint); overflow-wrap: anywhere; }

/* Result states. The min-height keeps loading / empty / error / loaded in one box,
   so the page does not jump as a search resolves. `overflow-wrap` is inherited:
   at 360px a German compound, an IPA string or a raw URL in an error message would
   otherwise push the whole layout sideways. */
.dictionary-results { min-height: 260px; overflow-wrap: anywhere; }
.dictionary-results .src-item { flex-wrap: wrap; }
.dictionary-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 190px; padding: 40px 20px; text-align: center; color: var(--text-dim);
}
.dictionary-state .spinner { margin: 0 0 14px; }
.dictionary-summary { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.dictionary-note {
  display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap;
  background: var(--warn-soft); border-radius: var(--radius-sm);
  padding: 9px 11px; margin-bottom: 12px; font-size: 13px; color: var(--text-dim);
}
.dictionary-note span { flex: 1; min-width: 0; }
.dictionary-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }

/* focus rings: see the single :focus-visible rule in the base section */

@media (max-width: 420px) {
  .dictionary-form .btn { flex: 1; }
  .dictionary-lang { max-width: 108px; }
  .dictionary-state { min-height: 170px; padding: 32px 12px; }
}

/* ============================================================
   Text reader (views/text.js) — tab-scoped. Every selector below starts with a
   .text-* class this view introduces, so nothing here can reach another tab.
   ============================================================ */
.text-editor { margin-bottom: 8px; }
.text-count { font-size: 12px; color: var(--text-faint); text-align: right; margin-top: 4px;
  font-variant-numeric: tabular-nums; }
.text-count-over { color: var(--danger); font-weight: 600; }

/* Wraps at 360px without ever pushing the page sideways: every control may shrink
   to nothing (min-width:0) and the row wraps instead of overflowing. */
.text-controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.text-controls .text-lang,
.text-controls .text-to { flex: 1 1 130px; min-width: 0; max-width: 200px; }
.text-controls .text-arrow { flex: 0 0 auto; font-size: 13px; }
/* Fixed widths so starting or stopping a run never reflows the control row. */
.text-controls .text-read { min-width: 88px; }
.text-controls .text-stop { min-width: 76px; }
/* focus rings: see the single :focus-visible rule in the base section */

.text-quick { align-items: center; margin-bottom: 12px; }
.text-quick-label { font-size: 12px; }

.text-status { font-size: 13px; color: var(--text-dim); margin: 0 0 14px; min-height: 19px;
  display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap; overflow-wrap: anywhere; }

/* Reserved height so the spinner -> strip transition does not jump the page, and
   overflow-wrap so a long IPA string or URL cannot widen the layout at 360px. */
.text-results { min-height: 300px; overflow-wrap: anywhere; }
.text-loading { display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 240px; padding: 32px 16px; text-align: center; }
.text-loading .spinner { margin: 0 0 14px; }
.text-loading .text-loading-sub { margin-top: 6px; opacity: .75; }

.text-strip { padding: 18px 20px; font-size: 17px; line-height: 1.35; }
.text-col { display: inline-block; vertical-align: top; text-align: center;
  margin: 0 6px 12px; max-width: min(170px, 100%); }
.text-punct { display: inline-block; vertical-align: top; color: var(--text-faint); margin: 0 1px; }

.text-word { font: inherit; background: none; border: 0; padding: 0 3px; margin: 0;
  border-radius: 5px; color: var(--text-faint); border-bottom: 1px dotted transparent;
  cursor: default; overflow-wrap: anywhere; transition: color .15s, background .12s; }
.text-word.text-hit { color: var(--accent); border-bottom-color: var(--accent); cursor: pointer; }
.text-word.text-hit:hover { background: var(--accent-soft); }
.text-word.text-miss { color: var(--text-faint); border-bottom: 1px dotted var(--border-strong); cursor: pointer; }
.text-word.text-pending { color: var(--text-faint); opacity: .55; cursor: progress; }
.text-word.text-word-active { background: var(--accent-soft); color: var(--accent); }
.text-word:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Clamped so one long definition cannot swamp the strip; the full text always
   stays reachable in the title attribute. */
.text-gloss { font-size: 11px; line-height: 1.2; color: var(--text-faint); margin-top: 3px;
  overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3;
  -webkit-box-orient: vertical; overflow: hidden; }
.text-gloss-fallback { font-style: italic; opacity: .72; }
.text-gloss-none { opacity: .5; }

.text-note { font-size: 12.5px; color: var(--text-faint); margin-top: 12px; }
.text-note-warn { color: var(--warn); }

.text-detail { margin-top: 16px; }
.text-detail-head { margin-bottom: 8px; text-transform: none; font-size: 14px; letter-spacing: 0; }
.text-cands { margin-bottom: 10px; }
.text-cands-label { margin-bottom: 6px; }
/* Long IPA / source rows inside the rendered entry must wrap, not widen the page. */
.text-detail .pron { overflow-wrap: anywhere; }
.text-detail .src-item { flex-wrap: wrap; }

@media (max-width: 420px) {
  .text-controls .btn { flex: 1 1 auto; }
  .text-strip { padding: 14px 12px; font-size: 16px; }
  .text-col { margin: 0 4px 10px; }
  .text-loading { min-height: 200px; }
}

/* ============================================================
   Browse tab (views/browse.js) — tab-scoped. Every selector
   below starts with a .browse-* class owned by that view, so
   nothing here can reach another tab.
   ============================================================ */
.browse-bar { margin-bottom: 14px; }
.browse-lang-select { max-width: 220px; }
.browse-prefix { max-width: 160px; }
.browse-mini { padding: 4px 10px; font-size: 12.5px; }

.browse-notice { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 12px 14px; margin-bottom: 14px; }
.browse-notice-text { flex: 1; min-width: 200px; font-size: 13.5px; color: var(--text-dim); overflow-wrap: anywhere; }
.browse-notice code { font-family: var(--mono); font-size: 12.5px; color: var(--text); }
.browse-langerr-line { margin-bottom: 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

/* Occupied in every state (loading / loaded / failed) so the list never shifts under it. */
.browse-status { min-height: 20px; margin-bottom: 10px; overflow-wrap: anywhere; }
.browse-list { padding: 0; overflow: hidden; }
.browse-list .empty { overflow-wrap: anywhere; }
.browse-foot { margin-top: 16px; display: flex; justify-content: center; }
.browse-foot .spinner { margin: 10px auto; }
.browse-end { display: flex; flex-direction: column; align-items: center; }
.browse-end .browse-actions { margin-top: 12px; }
.browse-note { max-width: 52ch; text-align: center; overflow-wrap: anywhere; }
.browse-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }

/* Rows are real <button>s so they are tabbable and answer Enter/Space; strip UA chrome. */
.browse-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--border); color: inherit;
  font: inherit; padding: 11px 14px; cursor: pointer; }
.browse-row:hover { background: var(--surface-2); }
.browse-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.browse-item:last-child .browse-row { border-bottom: none; }
.browse-item.browse-open .browse-row { background: var(--surface-2); }
.browse-lemma { font-weight: 700; overflow-wrap: anywhere; }
.browse-lemma.browse-rtl { direction: rtl; unicode-bidi: isolate; }
.browse-lang { flex: none; white-space: nowrap; }
/* One-line preview at desktop width; the ellipsis needs min-width:0 inside a flex row. */
.browse-gloss { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.browse-caret { flex: none; color: var(--text-faint); font-size: 11px; }
.browse-expand { padding: 4px 14px 16px; }
/* renderEntry() hands back a .card; nested in the list card its border+shadow reads as a
   box inside a box, and its long chip rows must wrap rather than widen the page. */
.browse-expand .card.entry { box-shadow: none; background: var(--surface-2); }
.browse-expand .tr-chips, .browse-expand .rel-words, .browse-expand .pron { overflow-wrap: anywhere; }
.browse-expand .src-item { flex-wrap: wrap; }

/* Skeleton rows hold the card's height while the first page loads. */
.browse-skel { display: flex; align-items: center; gap: 10px; padding: 14px; border-bottom: 1px solid var(--border); }
.browse-skel:last-child { border-bottom: none; }
.browse-skel-bar { height: 14px; border-radius: 6px; background: var(--surface-2);
  animation: browse-pulse 1.2s ease-in-out infinite; }
.browse-skel-a { width: 22%; }
.browse-skel-b { width: 12%; }
.browse-skel-c { flex: 1; }
@keyframes browse-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* focus rings: see the single :focus-visible rule in the base section */

@media (max-width: 560px) {
  .browse-lang-select, .browse-prefix { max-width: none; width: 100%; }
  /* At 360px lemma + badge + language + gloss cannot share one line; the gloss drops to
     its own row and clamps, so long definitions wrap instead of forcing a page scrollbar. */
  .browse-row { flex-wrap: wrap; gap: 6px 8px; padding: 11px 12px; }
  .browse-lang { white-space: normal; }
  .browse-gloss { flex: 1 0 100%; white-space: normal; text-overflow: clip;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
  .browse-caret { margin-left: auto; }
  .browse-expand { padding: 4px 10px 14px; }
  .browse-actions .browse-btn { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) { .browse-skel-bar { animation: none; } }

/* ============================================================
   World tab (views/world.js) — additions that belong to that view only.
   Every selector below starts with a class introduced by world.js and
   prefixed "world-". Compound selectors are used where the rule has to
   out-specify world.css, which is loaded after this file.
   ============================================================ */

/* three buttons do not fit the 332px HUD of a 360px phone */
.world-actions-wrap { flex-wrap: wrap; }
/* !important because world.css loads after this file: a display rule added there for one of
   these boxes must not resurrect an element the view deliberately hid */
.world-hidden { display: none !important; }

/* .world-kb marks the click targets world.js makes keyboard-reachable (layer toggles,
   result rows, language chips); the base :focus-visible rule draws their ring. */
.world-on.btn { border-color: var(--accent); color: var(--accent); }

/* HUD status line: empty results, background failures, bad deep links */
.world-note { margin-top: 10px; padding: 8px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface-2); font-size: 12px; color: var(--text-dim); }
.world-note-title { color: var(--text); font-weight: 650; font-size: 12.5px; line-height: 1.35; }
.world-note-body { margin-top: 3px; line-height: 1.45; overflow-wrap: anywhere; }
.world-note-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px; }
.world-note-chip { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface); color: var(--text-dim); }
.world-note-chip:hover { border-color: var(--accent); color: var(--accent); }
.world-note-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.world-note-btn.btn { flex: 0 0 auto; padding: 5px 10px; font-size: 12px; }

/* "no match" line inside the search dropdown — a message, not a target */
.world-note-row.world-result { color: var(--text-faint); cursor: default; }
.world-note-row.world-result:hover { background: transparent; }
/* row highlighted by the ↑/↓ keys from the search box */
.world-result-on.world-result { background: var(--accent-soft); color: var(--accent); }

/* a chip with nothing behind it must not look clickable */
.world-inert.wp-lang { cursor: default; opacity: .6; }
.world-inert.wp-lang:hover { border-color: var(--border); color: inherit; }

/* failure cards: stage-wide, panel-wide and overlay-wide */
.world-fail { max-width: min(560px, 100%); padding: 16px 18px; text-align: left; }
.world-fail pre { margin: 6px 0 0; padding: 8px 10px; overflow-x: auto; font-family: var(--mono);
  font-size: 12px; background: var(--surface-2); border-radius: var(--radius-sm); }

/* wide content scrolls inside its own box, never the page (report table, JSON dump) */
.world-scroll-x { overflow-x: auto; max-width: 100%; }

/* ---------- Language passport (views/language.js) ---------------------------------------
   Свои классы с префиксом li-, чтобы не столкнуться с существующими: сетка карточек здесь
   двухколоночная и схлопывается на узком экране, а таблица сравнения должна прокручиваться
   сама, не растягивая страницу вбок. */
.li-wrap { display:flex; flex-direction:column; gap:16px; }
.li-bar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.li-lab { font-size:12px; text-transform:uppercase; letter-spacing:.08em; opacity:.7; }
.li-sel { padding:6px 10px; border-radius:8px; border:1px solid var(--line,#ddd);
          background:var(--card,#fff); color:inherit; font:inherit; font-size:14px; }
.li-cards { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
@media (max-width:760px){ .li-cards { grid-template-columns:1fr; } }
.li-card { background:var(--card,#fff); border:1px solid var(--line,#e3e3e3);
           border-radius:12px; padding:16px 18px; display:flex; flex-direction:column; gap:10px; }
.li-head { display:flex; flex-direction:column; gap:6px; }
.li-name { font-size:19px; font-weight:650; }
.li-meta { display:flex; gap:6px; flex-wrap:wrap; }
.li-sub { display:flex; gap:12px; font-size:13px; opacity:.75; flex-wrap:wrap; }
.li-dim { opacity:.6; font-family:ui-monospace,Menlo,monospace; font-size:12px; }
.li-hint { font-size:13px; opacity:.72; }
.li-grid { display:flex; flex-direction:column; gap:2px; }
.li-row { display:grid; grid-template-columns:minmax(120px,42%) 1fr; gap:10px;
          padding:6px 0; border-bottom:1px dotted var(--line,#e6e6e6); font-size:13.5px; }
.li-row:last-child { border-bottom:none; }
.li-k { opacity:.72; }
.li-v { font-weight:550; }
.li-none { font-size:13.5px; opacity:.7; padding:8px 0; }
.li-cmp { width:100%; border-collapse:collapse; font-size:13.5px; display:block;
          overflow-x:auto; white-space:nowrap; }
.li-cmp th { text-align:left; font-size:10.5px; text-transform:uppercase; letter-spacing:.07em;
             opacity:.65; padding:7px 12px 7px 0; border-bottom:1px solid var(--line,#e3e3e3); }
.li-cmp td { padding:7px 12px 7px 0; border-bottom:1px solid var(--line,#eee); }
.li-cmp tr.diff td { background:color-mix(in srgb, var(--warn,#c98a00) 9%, transparent); }
.li-cmp tr.same td { opacity:.62; }
.li-src { font-size:12.5px; opacity:.72; }
.li-src a { color:inherit; }
