/* WordUp-style word feed — NoLookupWords 3.0 */

@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/Poppins-Regular.otf") format("opentype");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/Poppins-Medium.otf") format("opentype");
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/Poppins-SemiBold.otf") format("opentype");
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/Poppins-Bold.otf") format("opentype");
  font-weight: 700;
  font-display: swap;
}

:root {
  --bg: #0b0f1a;
  --bg-soft: #131a2b;
  --card: #171f33;
  --card-2: #1d2740;
  --text: #eef1f8;
  --muted: #93a0b8;
  --line: rgba(255, 255, 255, 0.08);
  --accent: #22c6a5;
  --accent-ink: #06251e;
  --gold: #f5b73c;
  --shadow: rgba(0, 0, 0, 0.45);
  --mark: rgba(245, 183, 60, 0.28);
}
body.light {
  --bg: #eef0f5;
  --bg-soft: #e4e8f0;
  --card: #ffffff;
  --card-2: #f4f6fa;
  --text: #1c2233;
  --muted: #667084;
  --line: rgba(20, 30, 60, 0.1);
  --accent: #0ea88b;
  --accent-ink: #ffffff;
  --shadow: rgba(30, 40, 70, 0.16);
  --mark: rgba(245, 183, 60, 0.45);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Poppins", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---------- top bar ---------- */
.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: flex-end;    /* the book layer is absolute; controls sit right */
  gap: 12px;
  padding: 10px clamp(12px, 4vw, 28px);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  font-size: 17px;
}
/* ---- 正在读的书：封面钉在顶栏正中，书名紧靠其左 ----
   The cover is absolutely centred so it stays put no matter how wide the
   search box or the title get; the title occupies the space to its left. */
.now-reading {
  position: absolute;
  inset: 0;
  pointer-events: none;              /* clicks fall through to the controls */
}
.nr-cover-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: pointer;
  width: 30px;
  height: 40px;
  border-radius: 4px 6px 6px 4px;
  overflow: hidden;
  background: var(--card-2);
  box-shadow: 0 4px 12px var(--shadow), 0 0 0 1px var(--line);
}
.nr-cover-wrap::after {              /* thin spine highlight — reads as a book */
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: linear-gradient(rgba(255,255,255,.28), rgba(255,255,255,.04));
}
.nr-cover { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Title sits in the gap between the left edge and the centred cover. Short
   names hug the cover; long ones start at the left and drift. */
.nr-title-wrap {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 14px;
  right: calc(50% + 26px);
  display: flex;
  justify-content: flex-end;
  overflow: hidden;
}
.nr-title-wrap.over {
  justify-content: flex-start;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
}
.nr-title {
  display: inline-block;
  white-space: nowrap;
  font-weight: 700;
  font-size: 15.5px;
  letter-spacing: .2px;
  color: var(--text);
  will-change: transform;
}
.nr-title.marquee { animation: nrScroll var(--dur, 9s) ease-in-out infinite alternate; }
@keyframes nrScroll {
  0%, 14%   { transform: translateX(0); }
  86%, 100% { transform: translateX(var(--shift, 0)); }
}

.top-actions { display: flex; align-items: center; gap: 8px; flex: none; min-width: 0; position: relative; }
/* Collapsed, search is just an icon; expanded it takes the whole bar, so it
   can never sit on top of the centred cover. */
/* `display:flex` below would beat the UA's [hidden] rule — spell it out. */
.search-bar[hidden] { display: none; }
.search-bar {
  position: absolute;
  inset: 6px clamp(12px, 4vw, 28px);
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 14px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--card);
  box-shadow: 0 8px 24px var(--shadow);
  animation: searchIn .18s ease;
}
@keyframes searchIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.icon-btn.ghost { border: 0; background: transparent; width: 32px; height: 32px; }
/* Reverse order: the arrows flip and the button lights up. */
.icon-btn.flipped { color: var(--accent-ink); background: var(--accent); border-color: transparent; }
.icon-btn.flipped svg { transform: scaleY(-1); }
.icon-btn svg { transition: transform .2s; }
.search-ico { flex: none; color: var(--muted); }
.search-bar input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 16px;              /* 16px keeps iOS from zooming the page */
}
.search-count:empty { display: none; }
.search-count { flex: none; font-size: 11px; color: var(--accent); white-space: nowrap; font-weight: 600; }
.icon-btn {
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
}
.view-toggle {
  flex: none;
  display: flex;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card);
}
.view-btn {
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.view-btn.active {
  color: var(--accent-ink);
  background: var(--accent);
}

/* ---------- glance (quick-scan) page ---------- */
/* `display:flex` below outranks the UA's [hidden] rule, so setting .hidden on
   this list did nothing and 速览 stayed on screen underneath the card feed —
   which is why switching to 卡片 sometimes still showed the list. Same trap the
   search bar hit; spell it out here too. */
.glance-list[hidden] { display: none; }
.feed[hidden] { display: none; }
.glance-list {
  max-width: 720px;
  margin: 0 auto;
  padding: 76px 12px 40px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.glance-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--card);
  box-shadow: 0 8px 24px var(--shadow);
}
/* A row that has not been filled in yet still has to hold its place in the
   list, and one scrolled far off screen should cost the browser nothing. */
.glance-row {
  min-height: 92px;
  content-visibility: auto;
  contain-intrinsic-size: auto 92px;
}
.glance-row.search-hidden { display: none; }
/* Search locates rather than filters: hits are marked, nothing is removed. */
.glance-row.search-hit { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.glance-row.search-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent),
              0 8px 24px var(--shadow);
}
.word-screen.search-current .word-card {
  box-shadow: 0 0 0 2px var(--accent), 0 24px 60px var(--shadow);
}
.glance-audio {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}
.glance-audio.loading { opacity: 0.5; }
.glance-main { min-width: 0; flex: 1; }
.glance-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.glance-word { font-size: 20px; font-weight: 700; color: var(--text); }
.glance-head .ipa { font-size: 14px; color: var(--muted); }
.glance-def { margin: 4px 0 0; font-size: 15px; line-height: 1.5; color: var(--text); }
.g-sense { display: block; margin-top: 2px; }

/* ---------- feed ---------- */
.feed {
  height: 100dvh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}
.feed::-webkit-scrollbar { display: none; }
/* Exactly one screen, never more. A card that ran taller made you scroll to
   reach its example — and that scroll ran straight on into the next card,
   because the feed itself was what was moving. Now the picture yields space to
   the text, and if the text still does not fit it scrolls inside the card. */
/* Back to min-height. A fixed height forced the picture to shrink to fit, and
   shrinking a frame that is exactly the photo's shape means cropping it — which
   is the one thing this layout exists to avoid. The card takes the height it
   needs; the scroll problem is solved by scroll-snap-stop below, not by
   squeezing the image. */
.word-screen {
  min-height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;   /* one flick = one card, never two */
  display: grid;
  justify-items: center;
  align-items: stretch;       /* the card fills the screen instead of floating */
  padding: 62px 12px 80px;    /* clear of the top bar and the floating nav bar */
}
.word-screen.search-hidden { display: none; }

/* Laid out top-down so the picture can take every pixel the text does not
   need: one short definition leaves a big photo, a long one shrinks it to its
   floor rather than leaving the card stranded in the middle of the screen. */
.word-card {
  position: relative;
  width: min(560px, 100%);
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 26px;
  overflow: hidden;
  box-shadow: 0 24px 60px var(--shadow);
}

/* ---------- hero ---------- */
/* The frame is given the photo's own proportions (set per card in JS), so the
   picture spans it edge to edge at full size: nothing cropped, nothing padded.
   Whatever height is left over goes to the card body below, as plain card. */
/* The frame still takes the photo's proportions, but it may now be squeezed:
   shrink 4 against the text's 1 means the picture is what gives way when the
   two of them will not both fit, and it gives way before the reader has to
   scroll for anything. */
/* 34% is the floor because the picture is the point of the card — it is what
   tells you the meaning before you have read a word. Past that floor the text
   scrolls (safely, inside the card) rather than the photo shrinking to a
   letterbox. */
/* The frame takes the photo's own proportions (set per card in JS), so the
   picture spans it edge to edge at full size: nothing cropped, nothing padded,
   no filler behind it. flex: 0 0 auto is load-bearing — let this shrink and the
   frame stops matching the photo, and `cover` starts eating the edges. */
.hero {
  position: relative;
  flex: 0 0 auto;
  width: 100%;
  /* Set per card as --hero-ratio. A custom property rather than an inline
     `aspect-ratio`, because inline styles outrank media queries and the
     landscape layout below needs to be able to say `auto`. */
  aspect-ratio: var(--hero-ratio, 4 / 3);
  background: var(--card-2);
  overflow: hidden;
}
/* Without a picture there is nothing to shape the frame to, so it goes back to
   soaking up the spare height rather than leaving a gap under the letter. */
.hero.hero-empty {
  flex: 1 1 auto;
  aspect-ratio: auto;
  min-height: 28%;
}
/* min-height:0 is what actually lets a flex child scroll instead of stretching
   its parent; overscroll-behavior is what stops that scroll, once it reaches
   the end, from carrying on into the next card. */
/* Not a scroller. The card is as tall as it needs to be and the feed scrolls;
   making this scroll instead is what forced the fixed-height card. */
.card-body { flex: 1 1 auto; }
.hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* a no-op guard: the frame is already its shape */
}
.hero-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 90% at 15% 10%, rgba(34, 198, 165, 0.35), transparent 55%),
    radial-gradient(120% 100% at 90% 20%, rgba(245, 183, 60, 0.28), transparent 50%),
    linear-gradient(160deg, #22304e, #131a2b);
}
.hero-fallback span {
  font-size: clamp(96px, 26vh, 200px);
  font-weight: 700;
  color: rgba(255, 255, 255, 0.16);
}
.hero-shade {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8, 10, 18, 0.18) 0%, transparent 38%, rgba(8, 10, 18, 0.86) 100%);
  z-index: 2;
}
.hero-chips {
  position: absolute;
  top: 12px;
  left: 12px;
  right: 12px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
  z-index: 3;
}
.chip {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(10, 14, 24, 0.62);
  color: #e8ecf5;
  backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.rank-chip { color: var(--gold); }
.hero-title { position: absolute; left: 18px; right: 18px; bottom: 14px; z-index: 3; }
.word {
  margin: 0;
  font-size: clamp(34px, 9vw, 52px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: 0.2px;
  color: #fff;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
  overflow-wrap: anywhere;
}
.word-meta { display: flex; align-items: center; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.ipa { font-size: 15px; color: rgba(235, 240, 250, 0.85); text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5); }
.lemma-chip {
  font-size: 12px;
  color: #cfe9e2;
  background: rgba(34, 198, 165, 0.22);
  border: 1px solid rgba(34, 198, 165, 0.4);
  border-radius: 999px;
  padding: 2px 9px;
}
.audio-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(34, 198, 165, 0.4);
  transition: transform 0.15s ease;
}
.audio-btn:active { transform: scale(0.92); }
.audio-btn.loading { opacity: 0.6; cursor: wait; }

/* ---------- body ---------- */
.card-body { padding: 18px 20px 8px; }
.section-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--accent);
  margin: 14px 0 8px;
}
.section-label:first-child { margin-top: 0; }
.sense {
  display: flex;
  gap: 10px;
  margin: 0 0 8px;
  font-size: 16.5px;
  line-height: 1.55;
}
.pos-chip {
  display: inline-block;
  margin-right: 7px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  vertical-align: 2px;
}
.sense-num {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 2px;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  background: var(--card-2);
  border-radius: 50%;
}

/* ---------- example quote card ---------- */
/* The example continues the definition rather than starting a new block: it
   shares the senses' text column and is linked by a rail down the gutter. */
.example-block {
  position: relative;
  margin: 10px 0 4px;
  padding-left: 32px;              /* = .sense-num width + .sense gap */
}
.example-block::before {
  content: "";
  position: absolute;
  left: 10px;
  top: -6px;
  bottom: 14px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--accent) 55%, transparent),
              transparent);
}
.example-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.4px;
  color: var(--muted);
  margin: 0 0 6px;
}
.quote-card {
  position: relative;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  min-height: 96px;
  display: grid;
  align-items: end;
  background: linear-gradient(150deg, #24334f, #182236);
}
.quote-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: blur(0.5px); }
.quote-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 13, 22, 0.25), rgba(10, 13, 22, 0.82));
}
.quote-card blockquote {
  position: relative;
  margin: 0;
  padding: 26px 16px 12px;
  color: #f2f5fb;
  font-size: 15.5px;
  line-height: 1.5;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.5);
}
/* The look-up sheet's example is ordinary flowing text, so the sentence wraps
   normally and the highlight stays on the word instead of becoming a column. */
.lookup-example {
  margin: 0;
  font-size: 16.5px;
  line-height: 1.6;
  color: var(--text);
}

/* Target word inside an example — the eye should land on it instantly. */
.quote-card mark,
.lookup-content mark {
  background: color-mix(in srgb, var(--gold) 26%, transparent);
  box-shadow: inset 0 -3px 0 color-mix(in srgb, var(--gold) 75%, transparent);
  color: inherit;
  font-weight: 700;
  padding: 0 2px;
  border-radius: 3px;
}
.quote-card blockquote::before {
  content: "\201C";
  position: absolute;
  top: -2px;
  left: 10px;
  font-size: 34px;
  font-weight: 700;
  color: var(--gold);
  opacity: 0.85;
}

/* ---------- book context ---------- */
.book-line {
  margin: 14px 0 6px;
  padding: 12px 14px;
  border-left: 3px solid var(--gold);
  background: var(--card-2);
  border-radius: 0 14px 14px 0;
}
.book-line .section-label { color: var(--gold); margin: 0 0 6px; }
.page-chip {
  font-size: 11px;
  letter-spacing: 0;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 8px;
  text-transform: none;
}
.book-sentence { margin: 0; font-size: 15.5px; line-height: 1.6; color: var(--text); }
mark { background: var(--mark); color: inherit; border-radius: 4px; padding: 0 3px; font-weight: 600; }

.data-credit {
  position: fixed;
  left: 12px;
  bottom: 8px;
  z-index: 5;
  font-size: 10px;
  color: var(--muted);
  opacity: 0.55;
  pointer-events: none;
}
.card-foot {
  flex: none;
  padding: 10px 20px 14px;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
  letter-spacing: 1.5px;
}

/* clickable words */
.tap-word { cursor: pointer; border-bottom: 1px dashed color-mix(in srgb, var(--muted) 65%, transparent); }
.tap-word:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* Tapping a button twice in quick succession must page twice, not zoom the
   page — which is what the browser does by default when you double-tap. */
button, .glance-row, .nav-arrow, .pager-btn, .icon-btn {
  touch-action: manipulation;
}

/* ---------- pager ---------- */
.pager {
  position: fixed;
  right: clamp(10px, 3vw, 26px);
  bottom: 26px;
  z-index: 25;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Up/down card stepper — thumb-reachable on the right edge, so a long deck
   can be walked without swiping every card. */
.pager-btn {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(10px);
  color: var(--text);
  font-size: 15px;
  cursor: pointer;
  box-shadow: 0 10px 26px var(--shadow);
  transition: background .15s, color .15s, transform .1s, opacity .15s;
}
.pager-btn:hover:not(:disabled) { background: var(--accent); color: var(--accent-ink); }
.pager-btn:active:not(:disabled) { transform: scale(0.92); }
.pager-btn:disabled { opacity: .25; cursor: default; }
.pager-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- lookup sheet ---------- */
.lookup-backdrop[hidden] { display: none; }
.lookup-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(5, 8, 15, 0.6);
  backdrop-filter: blur(4px);
  display: grid;
  align-items: end;
  justify-items: center;
}
.lookup-sheet {
  position: relative;
  width: min(560px, 100%);
  max-height: 76dvh;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 22px 22px 0 0;
  /* The floating nav bar sits on top of this sheet, so the last line — usually
     the example sentence — has to end well above it. */
  padding: 20px 20px calc(96px + env(safe-area-inset-bottom));
  animation: sheet-up 0.22s ease;
}
@keyframes sheet-up {
  from { transform: translateY(40px); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.lookup-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 32px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card-2);
  color: var(--text);
  cursor: pointer;
}
.lookup-hero {
  margin: 0 0 14px;
  border-radius: 16px;
  overflow: hidden;
  background: var(--card-2);
  box-shadow: 0 10px 30px var(--shadow);
  animation: heroFade .35s ease;
}
/* The sheet is a flowing column, so the picture simply keeps its own height —
   nothing to crop it against. */
.lookup-hero img { width: 100%; height: auto; display: block; }
@keyframes heroFade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.lookup-word { margin: 0 0 2px; font-size: 30px; font-weight: 700; }
.lookup-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; color: var(--muted); }
.lookup-images { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0; }
.lookup-images img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 12px; }
.lookup-note { color: var(--muted); font-size: 14px; }

.no-data {
  margin: 40vh auto 0;
  width: fit-content;
  padding: 18px 26px;
  border: 1px dashed var(--line);
  border-radius: 14px;
  color: var(--muted);
}

/* ==================== 横屏 ====================
   Turned sideways there is no height left to stack a picture on top of text —
   a 390px-tall window would leave the photo a sliver. So the card becomes two
   columns: the picture takes the left half at its own proportions, the reading
   takes the right and scrolls on its own if a word has many senses.

   The rule keys on aspect-ratio rather than orientation so it also catches a
   laptop window dragged wide and short, and it needs `pointer: coarse` off the
   equation entirely — a desktop browser at 1200x600 wants this layout too. */
@media (min-aspect-ratio: 13/10) and (max-height: 560px) {
  .word-screen { padding: 54px 10px 12px; }

  .word-card {
    flex-direction: row;
    width: min(980px, 100%);
    height: 100%;
    align-items: stretch;
  }

  /* Half the width, full height. The photo still fills its half edge to edge
     at its own shape — object-fit keeps it honest — so the no-crop promise
     survives the rotation; what changes is which dimension is the constraint. */
  .hero {
    flex: 0 0 46%;
    aspect-ratio: auto;
    height: 100%;
    min-height: 0;
    border-radius: 26px 0 0 26px;
  }
  .hero.hero-empty { flex: 0 0 46%; min-height: 0; }
  .hero-shade {
    background: linear-gradient(180deg, rgba(8,10,18,.15) 0%, transparent 34%,
                                        rgba(8,10,18,.88) 100%);
  }
  .word { font-size: clamp(26px, 4.4vw, 40px); }

  /* The text column is the one that scrolls now — contained, so reaching its
     end never carries on into the next card. */
  .card-body {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 16px 18px 6px;
    scrollbar-width: thin;
  }
  .card-body::-webkit-scrollbar { width: 4px; }
  .card-body::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--muted) 40%, transparent);
    border-radius: 4px;
  }
  .card-foot {
    position: absolute;
    right: 14px;
    bottom: 8px;
    padding: 0;
    font-size: 11px;
    opacity: .6;
  }
  /* The pager would sit on top of the text column; tuck it against the seam. */
  .pager { bottom: 14px; right: 12px; flex-direction: row; }
  .pager-btn { width: 40px; height: 40px; }
  .navbar { bottom: 8px; padding: 4px; }
  .nav-arrow { width: 38px; height: 38px; }
  .data-credit { display: none; }
  .glance-list { padding: 66px 12px 74px; }
  /* Two columns of rows: the same list, half the scrolling. */
  .glance-list { max-width: 1100px; display: grid; grid-template-columns: 1fr 1fr; }
  .lookup-sheet { max-height: 92dvh; padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
}

@media (max-width: 620px) {
  .word-screen { padding: 58px 8px 76px; }
  .word-card { border-radius: 20px; }
  .card-body { padding: 14px 16px 6px; }
  /* The up/down buttons float over the bottom-right of the card on a phone,
     so only the block that reaches down there has to keep clear of them. */
  .card-body > :last-child { padding-right: 46px; }
  .topbar { gap: 6px; padding: 10px 12px; }
  .top-actions { gap: 6px; }
  .icon-btn { width: 36px; height: 36px; }
  .view-btn { padding: 5px 11px; }
}
.brand span { white-space: nowrap; }

/* ==================== web-only: upload & progress screens ==================== */
/* Specificity must beat .hero-wrap's own `display`, hence :not(). */
.screen:not(.active) { display: none !important; }

.hero-wrap {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 28px 16px 60px;
}
.intro { width: min(560px, 100%); text-align: center; }
.intro-logo { width: 84px; height: 84px; border-radius: 22px; object-fit: cover; margin: 0 auto 16px; display: block; }
.intro h1 { margin: 0 0 8px; font-size: 30px; letter-spacing: 1px; }
.intro .tagline { margin: 0 0 26px; color: var(--muted); font-size: 15px; line-height: 1.6; }

/* ---- 直接查词：不导入 PDF 也能查任意单词 ---- */
.quick-look {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  box-shadow: 0 10px 30px var(--shadow);
  transition: border-color .15s;
}
.quick-look:focus-within { border-color: var(--accent); }
.ql-ico { flex: none; color: var(--muted); }
.quick-look input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 16px;          /* 16px stops iOS zooming the page on focus */
}
.ql-go {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  transition: transform .1s, opacity .15s;
}
.ql-go:active { transform: scale(0.93); }
.ql-go.busy { opacity: .55; }
.ql-err { margin: 8px 0 0; font-size: 13px; color: #e98282; }
.ql-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0 14px;
  color: var(--muted);
  font-size: 12px;
}
.ql-divider::before, .ql-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.drop {
  border: 2px dashed var(--line);
  border-radius: 20px;
  background: var(--card);
  padding: 34px 20px;
  cursor: pointer;
  transition: border-color .18s, background .18s;
}
.drop:hover, .drop.over { border-color: var(--accent); background: var(--card-2); }
.drop-icon { font-size: 38px; line-height: 1; }
.drop-main { margin: 10px 0 4px; font-size: 17px; font-weight: 600; }
.drop-sub { color: var(--muted); font-size: 13px; }

.opts { margin: 22px 0 0; text-align: left; border: 0; padding: 0; }
.opts legend { font-size: 13px; color: var(--muted); padding: 0 0 8px; }
.toggle-row { display: flex; align-items: center; gap: 8px; margin-top: 14px; font-size: 14px; color: var(--muted); }

/* ==================== 词频刻度（密码锁式滚轮） ====================
   Five wheels, but not five equal wheels: the two on the left decide the
   answer and the three on the right are noise, so the left ones are typed
   bigger and each of their digits is a taller throw of the thumb. The size
   ladder is the instruction — you can see which end matters. */
.dialer {
  display: flex;
  align-items: stretch;
  gap: 14px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--card);
  box-shadow: 0 10px 30px var(--shadow);
}
.dial-window {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 0 8px;
  height: 168px;
  border-radius: 14px;
  background: var(--card-2);
  /* fade the digits above and below the selected one */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 26%, #000 74%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 26%, #000 74%, transparent 100%);
}
.dial-band {
  position: absolute;
  left: 4px;
  right: 4px;
  top: 50%;
  height: 56px;
  transform: translateY(-50%);
  border-top: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  pointer-events: none;
}
.dial {
  height: 100%;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  outline: 0;
  cursor: grab;
  touch-action: pan-y;
  /* no bounce past the ends on iOS, which would show blank spacer */
  overscroll-behavior: contain;
}
.dial::-webkit-scrollbar { display: none; }
.dial:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); border-radius: 8px; }
.dial i {                                   /* the centring spacers */
  display: block;
  font-style: normal;
}
.dial b {
  display: grid;
  place-items: center;
  scroll-snap-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  opacity: .45;
  transition: color .15s, opacity .15s, transform .15s;
}
.dial b.on { color: var(--text); opacity: 1; }

/* Every wheel the same, and bigger than the biggest one used to be — five
   equal digits read as one number, which is what it is. */
.dial b { height: 56px; width: 44px; font-size: 44px; }

.range-side { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.seg {
  display: flex;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-2);
}
.seg-btn {
  flex: 1;
  padding: 7px 4px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.seg-btn.on { background: var(--accent); color: var(--accent-ink); }
.range-note {
  margin: 0;
  flex: 1;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
}
.range-note b { color: var(--text); font-weight: 600; }
.vt-open {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 10px;
  border: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 12px;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s;
}
.vt-open:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }

@media (max-width: 560px) {
  .dialer { flex-direction: column; gap: 12px; }
  .dial-window { align-self: center; }
}

/* ==================== 词汇量测试 ==================== */
.vt-backdrop[hidden] { display: none; }
.vt-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(5, 8, 15, .68);
  backdrop-filter: blur(5px);
  display: grid;
  place-items: center;
  padding: 16px;
}
.vt-sheet {
  position: relative;
  width: min(600px, 100%);
  max-height: 90dvh;
  overflow-y: auto;
  padding: 22px 20px 20px;
  border: 1px solid var(--line);
  border-radius: 22px;
  background: var(--card);
  box-shadow: 0 30px 80px var(--shadow);
  animation: sheet-up .22s ease;
}
.vt-title { margin: 0 0 4px; font-size: 21px; font-weight: 700; }
.vt-sub { margin: 0 0 14px; font-size: 13px; color: var(--muted); line-height: 1.6; }
.vt-progress { height: 4px; border-radius: 99px; background: var(--card-2); overflow: hidden; }
.vt-progress > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .3s; }

.vt-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin: 16px 0 18px;
  min-height: 180px;
  align-content: flex-start;
}
.vt-word {
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  cursor: pointer;
  transition: transform .16s ease, opacity .16s ease, border-color .12s, background .12s;
}
.vt-word:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.vt-word.gone { transform: scale(.55); opacity: 0; pointer-events: none; }
.vt-empty { color: var(--muted); font-size: 14px; padding: 8px 2px; }

.vt-actions { display: flex; gap: 10px; }
.vt-next, .vt-again, .vt-apply {
  flex: 1;
  padding: 12px;
  border: 0;
  border-radius: 13px;
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.vt-none, .vt-again {
  flex: none;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
.vt-result { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); text-align: center; }
.vt-num { margin: 0; display: flex; align-items: baseline; justify-content: center; gap: 8px; }
.vt-num b { font-size: 44px; font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.vt-num span { font-size: 15px; color: var(--muted); }
.vt-detail { margin: 8px 0 16px; font-size: 12.5px; line-height: 1.7; color: var(--muted); }
.vt-result-actions { display: flex; gap: 10px; }

.vt-gift {
  margin: 0 0 14px;
  padding: 11px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text);
  text-align: left;
}
.vt-gift b { color: var(--accent); }
.vt-share {
  width: 100%;
  margin-top: 10px;
  padding: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s;
}
.vt-share:hover { border-color: var(--accent); color: var(--accent); }

/* ==================== 分享海报 ==================== */
.share-backdrop[hidden], .pay-backdrop[hidden] { display: none; }
.share-backdrop, .pay-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(5, 8, 15, .72);
  backdrop-filter: blur(6px);
  display: grid;
  place-items: center;
  padding: 14px;
  overflow-y: auto;
}
.share-sheet, .pay-sheet {
  position: relative;
  width: min(400px, 100%);
  max-height: 94dvh;
  overflow-y: auto;
  padding: 18px 18px 16px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--card);
  box-shadow: 0 30px 80px var(--shadow);
  animation: sheet-up .22s ease;
}
.share-canvas-wrap {
  border-radius: 14px;
  overflow: hidden;
  background: var(--card-2);
  line-height: 0;
}
.share-canvas-wrap canvas { width: 100%; height: auto; display: block; }
.share-row { display: flex; gap: 8px; margin-top: 12px; }
.share-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s, transform .1s;
}
.share-btn:active { transform: scale(.96); }
.share-btn.wechat:hover  { border-color: #2aae67; color: #2aae67; }
.share-btn.rednote:hover { border-color: #ff2e4d; color: #ff2e4d; }
.share-btn.douyin:hover  { border-color: #7ce7e0; color: #7ce7e0; }
.share-ico { font-size: 18px; line-height: 1; }
.share-secondary {
  flex: 1;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.share-secondary:hover { color: var(--accent); border-color: var(--accent); }
.share-tip { margin: 10px 0 0; font-size: 12px; line-height: 1.6; color: var(--muted); text-align: center; }

/* ==================== 试用 / 购买 ==================== */
.trial-line {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
  text-align: center;
}
.trial-line b { color: var(--accent); }
.trial-line a { color: var(--accent); cursor: pointer; text-decoration: underline; }
.pay-title { margin: 0 0 4px; font-size: 21px; font-weight: 700; }
.pay-sub { margin: 0 0 16px; font-size: 13px; color: var(--muted); line-height: 1.6; }
.pay-price {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.pay-price b { font-size: 38px; font-weight: 700; color: var(--accent); }
.pay-price span { font-size: 13px; color: var(--muted); }
.pay-list { margin: 12px 0 16px; padding-left: 18px; font-size: 13.5px; line-height: 1.9; color: var(--text); }
.pay-list li::marker { color: var(--accent); }
/* three ways to pay, one visible at a time */
.pay-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 14px;
  border-radius: 999px;
  background: var(--card-2);
  border: 1px solid var(--line);
}
.pay-tab {
  flex: 1;
  padding: 8px 4px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s, color .15s;
}
.pay-tab.on { background-color: var(--accent); color: var(--accent-ink); }
.pay-pane { min-height: 150px; margin-bottom: 14px; }
/* the one-tap button, when the vendor allows one */
.pay-go {
  display: block;
  padding: 15px;
  border-radius: 14px;
  text-align: center;
  text-decoration: none;
  font-size: 16.5px;
  font-weight: 700;
  color: #fff;
  box-shadow: 0 8px 22px var(--shadow);
}
.pay-go.wechat { background: #07c160; }
.pay-go.alipay { background: #1677ff; }
.pay-go.card   { background: #635bff; }
.pay-go:active { transform: scale(.985); }
.pay-hint {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--muted);
  text-align: center;
}
.pay-hint b { color: var(--text); }
.pay-hint code {
  font-size: 11.5px;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--card-2);
  color: var(--text);
}
.pay-todo { text-align: left; }
/* the code box is for people who already paid — folded away by default so the
   first thing on screen is how to pay, not a field they cannot fill in yet */
.pay-code { border-top: 1px solid var(--line); padding-top: 12px; }
.pay-code summary {
  cursor: pointer;
  font-size: 13.5px;
  color: var(--muted);
  list-style: none;
  padding: 4px 0;
}
.pay-code summary::-webkit-details-marker { display: none; }
.pay-code summary::before { content: "＋ "; color: var(--accent); }
.pay-code[open] summary::before { content: "－ "; }
.pay-code .pay-form { margin-top: 10px; }
.pay-qr { margin: 0 0 16px; text-align: center; }
.pay-qr img {
  width: 200px;
  max-width: 100%;
  border-radius: 12px;
  background: #fff;
  display: block;
  margin: 0 auto 8px;
}
.pay-qr img.missing { display: none; }
.pay-qr figcaption { font-size: 12px; line-height: 1.6; color: var(--muted); }
.pay-form { display: flex; gap: 8px; }
.pay-form input {
  flex: 1;
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 15px;
  letter-spacing: .5px;
  outline: 0;
}
.pay-form input:focus { border-color: var(--accent); }
.pay-form button {
  flex: none;
  padding: 11px 20px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.pay-msg { margin: 10px 0 0; font-size: 13px; line-height: 1.6; }
.pay-msg.bad { color: #e98282; }
.pay-msg.good { color: var(--accent); }

/* ==================== 顶栏自动朗读开关 ====================
   Three lines of text with sound coming off them: reads as "say what is
   written", and stays distinct from the plain speaker on the card. It lives in
   the same row as the other three controls, so all four share one gap. */
/* A round grey icon among three other round grey icons says nothing about what
   it does. This one is a labelled pill instead — it reads "连读", it is the
   only coloured control in the bar when it is on, and off it still says its
   own name rather than making you guess from a glyph. */
.read-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 38px;
  padding: 0 12px 0 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .5px;
  cursor: pointer;
  white-space: nowrap;
  /* background-color, not the `background` shorthand: transitioning the
     shorthand between a color-mix() and a plain colour leaves the computed
     value parked at the starting alpha, so the "on" state never actually
     filled in. */
  transition: background-color .16s, color .16s, border-color .16s, box-shadow .16s;
}
.read-toggle .wave { opacity: .4; transition: opacity .16s; }
.read-toggle:hover { border-color: var(--accent); }
.read-toggle[aria-pressed="true"] {
  color: var(--accent-ink);
  background-color: var(--accent);
  border-color: transparent;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent),
              0 4px 14px color-mix(in srgb, var(--accent) 40%, transparent);
}
.read-toggle[aria-pressed="true"] .wave { opacity: 1; }
/* Reading a whole card takes a while — show it is still going. */
.read-toggle.speaking .wave { animation: readWave 1.1s ease-in-out infinite; }
.read-toggle.speaking .wave2 { animation-delay: .18s; }
@keyframes readWave { 45% { opacity: .12; } }

/* On a phone the label would crowd the three icons out; the pill keeps its
   colour, which is what actually identifies it. */
@media (max-width: 620px) {
  .read-toggle { height: 36px; padding: 0 9px; gap: 0; }
  .read-label { display: none; }
}

/* ==================== 词组卡片 ==================== */
.phrase-chip {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, rgba(10, 14, 24, .55));
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.word.is-phrase { font-size: clamp(27px, 6.4vw, 40px); line-height: 1.12; }
.glance-word.is-phrase { font-size: 18px; }
/* ---- when a definition still leans on the word's own family ----
   A quiet ? on the line rather than a second line of explanation under every
   sense: the card stays clean, and the answer is one tap away. Once a
   plain-English gloss exists for the word (data/gloss.json) this never
   appears at all. */
.sense-plain > span > .linkify { font-weight: 500; }
.lean-btn {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  vertical-align: 2px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.lean-btn:hover { color: var(--accent); border-color: var(--accent); }
.lean-btn:disabled { display: none; }
.gloss-inline {
  display: block;
  margin-top: 4px;
  padding-left: 9px;
  border-left: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}
.gloss-inline b { color: var(--text); font-weight: 600; }
.go-btn {
  width: 100%;
  margin-top: 18px;
  padding: 14px;
  border: 0;
  border-radius: 14px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
.go-btn:disabled { opacity: .45; cursor: default; }

.prog-wrap { width: min(460px, 100%); text-align: center; }
.bar { height: 8px; border-radius: 99px; background: var(--card-2); overflow: hidden; margin: 20px 0 12px; }
.bar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .25s; }
.prog-msg { color: var(--muted); font-size: 14px; min-height: 22px; }
.prog-pct { font-size: 34px; font-weight: 700; }

.err { color: #e98282; font-size: 14px; margin-top: 10px; }
.footnote { margin-top: 26px; font-size: 12px; color: var(--muted); opacity: .8; line-height: 1.7; }
.restart-btn {
  flex: none; padding: 6px 12px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--muted); font-size: 13px; cursor: pointer;
}

/* one-time swipe discoverability nudge */
.swipe-hint {
  position: fixed;
  left: 50%;
  bottom: 90px;
  transform: translateX(-50%);
  z-index: 60;
  padding: 10px 18px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 13px;
  /* Was nowrap, so a longer message ran off both edges of a phone screen. */
  max-width: min(92vw, 460px);
  line-height: 1.5;
  text-align: center;
  box-shadow: 0 10px 30px var(--shadow);
  opacity: 1;
  transition: opacity .7s;
  pointer-events: none;
}
.swipe-hint.gone { opacity: 0; }


/* ---------- glance rows are tappable ---------- */
.glance-row { cursor: pointer; transition: border-color .15s, transform .12s; }
.glance-row:active { transform: scale(0.995); }
.glance-row:hover { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.glance-go {
  flex: none;
  align-self: center;
  font-size: 22px;
  line-height: 1;
  color: var(--muted);
  opacity: .55;
}


/* keep the vertical pager clear of the history bar */
.pager { bottom: 70px; }

/* ==================== bottom ladder nav ====================
   导入 › 速览 › 卡片 › 查中查 — one control for the whole hierarchy:
   arrows step through it, the cover in the middle says which book. */
.navbar {
  position: fixed;
  left: 50%;
  bottom: max(14px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(16px) saturate(1.2);
  box-shadow: 0 12px 40px var(--shadow), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
}
.nav-arrow {
  flex: none;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  transition: background .15s, color .15s, opacity .15s, transform .1s;
}
.nav-arrow:hover:not(:disabled) { background: var(--accent); color: var(--accent-ink); }
.nav-arrow:active:not(:disabled) { transform: scale(0.93); }
.nav-arrow:disabled { opacity: .22; cursor: default; }

.nav-center {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 12px 0 8px;
  min-width: 108px;
}
.nav-cover {
  flex: none;
  width: 30px;
  height: 40px;
  object-fit: cover;
  border-radius: 4px;
  background: var(--card-2);
  box-shadow: 0 3px 10px var(--shadow);
}
/* Four bars = four levels, with the current level named to their right. */
.nav-meta { display: flex; align-items: center; gap: 10px; }
.nav-level {
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  color: var(--text);
  white-space: nowrap;
}
.nav-steps { display: flex; align-items: center; gap: 3px; }
.nav-steps i {
  width: 9px;
  height: 3px;
  border-radius: 2px;
  background: var(--muted);
  opacity: .3;
  transition: width .2s, background .2s, opacity .2s;
}
.nav-steps i.on { background: var(--accent); opacity: 1; width: 17px; }
.nav-steps i.off { opacity: .13; }

/* keep content clear of the bar */
.glance-list { padding-bottom: 96px; }
.pager { bottom: 96px; }
.data-credit { bottom: 4px; }
.swipe-hint { bottom: 104px; }

/* sentence read-aloud button, sits in the example label row */
.example-label {
  display: flex;
  align-items: center;
  gap: 8px;
}
.say-btn {
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  cursor: pointer;
  transition: background .15s, transform .1s;
}
.say-btn:hover { background: var(--accent); color: var(--accent-ink); }
.say-btn:active { transform: scale(0.9); }
.say-btn.playing { background: var(--accent); color: var(--accent-ink); }
