/* ORA web app mirror — dark romantasy palette (mirrors mobile app I008). */
/* Palette SSOT: app/src/screens/*.tsx — #0f0f14 bg / #bfa6f5 accent / #7a3eea brand / #f4f4f6 text / #9b9bad muted. */

:root {
  --bg: #0f0f14;
  --scrim: rgba(0, 0, 0, 0.55);
  --surface: #15151c;
  --surface-2: #1a1a22;
  --border: #2a2a35;
  --text: #f4f4f6;
  --muted: #9b9bad;
  --accent: #bfa6f5;
  --brand: #7a3eea;
  --danger: #ef4444;
  --positive: #2fb463;
  --max-width: 720px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.65;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

button { font: inherit; cursor: pointer; }

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* ── Top bar ── */
.topbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 1rem 1.25rem 0.5rem;
  max-width: var(--max-width);
  margin: 0 auto;
}
.brand {
  font-size: 1.75rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 3px;
}
.subtitle { font-size: 0.85rem; color: var(--muted); margin-top: 3px; font-weight: 600; }
/* ORA wordmark as a home link (shared header, every page). Keep it looking like the
   plain wordmark — no underline in any state; the .brand class still owns the color. */
.brand-home, .brand-home:hover { text-decoration: none; display: inline-block; }
.hero-sub { font-size: 0.78rem; color: var(--muted); margin-top: 4px; max-width: 32rem; line-height: 1.45; }
.signout {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.75rem;
  text-decoration: underline;
}

/* ── Continue reading card (FEATURE 1) ── */
.continue-card {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  max-width: var(--max-width);
  margin: 0.75rem auto 0;
  padding: 0.85rem 1rem;
  /* Branded dark card — FIXED dark-purple gradient (both stops dark) + fixed light text,
     so it stays legible in BOTH themes. Previously the 2nd stop was var(--surface-2)
     (light in light-theme) and text used var(--text)/--muted/--accent (which flip dark
     in light-theme) → dark-on-dark, unreadable. Fixed colors keep AA contrast either way. */
  background: linear-gradient(135deg, #2a1a4a, #3a2566);
  border: 1px solid var(--brand);
  border-radius: 14px;
  color: #f4f4f6;
}
.continue-card:hover { text-decoration: none; border-color: var(--accent); }
.continue-card .continue-cover {
  width: 48px;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: 6px;
  flex: 0 0 auto;
  display: block;
}
.continue-card .continue-body { flex: 1 1 auto; min-width: 0; }
.continue-card .continue-kicker {
  font-size: 0.65rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: #cdb7ff; /* fixed bright lavender — AA on the dark card in both themes */
}
.continue-card .continue-title {
  font-size: 1rem;
  font-weight: 700;
  margin: 0.15rem 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.continue-card .continue-ep {
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.82); /* fixed light — AA on the dark card in both themes (was var(--muted), dark in light-theme) */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.continue-card .continue-resume {
  flex: 0 0 auto;
  background: var(--brand);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
}

/* ── Search (FEATURE 2) ── */
.search-row {
  max-width: var(--max-width);
  margin: 0.75rem auto 0;
  padding: 0 1.25rem;
}
.search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.7rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.95rem;
}
.search-input::placeholder { color: var(--muted); }
.search-input:focus { outline: none; border-color: var(--accent); }

/* ── Trope chips ── */
.trope-row {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding: 0.5rem 1.25rem;
  max-width: var(--max-width);
  margin: 0 auto;
  scrollbar-width: none;
}
.trope-row::-webkit-scrollbar { display: none; }
.trope-chip {
  flex: 0 0 auto;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
}
.trope-chip[aria-pressed="true"] {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}

/* ── G8 — first-visit trope-pick personalization bar ── */
/* Dismissible card ABOVE the rails (never a page-trapping modal). All theme-var
   colored so it flips cleanly across light / sepia / dark. */
.trope-pick {
  max-width: var(--max-width);
  margin: 0.25rem auto 0.5rem;
  padding: 0 1.25rem;
}
.trope-pick.hidden { display: none; }
.trope-pick__inner {
  position: relative;
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--border));
  border-radius: 14px;
  padding: 0.9rem 1rem 1rem;
}
.trope-pick__close {
  position: absolute;
  top: 0.35rem;
  right: 0.55rem;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
}
.trope-pick__close:hover { color: var(--text); }
.trope-pick__head {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  padding-right: 1.5rem;
}
.trope-pick__sub {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0.15rem 0 0.7rem;
}
.trope-pick__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.8rem;
}
.trope-pick__chip {
  flex: 0 0 auto;
  padding: 0.35rem 0.8rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
}
.trope-pick__chip[aria-pressed="true"] {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.trope-pick__actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.trope-pick__apply {
  padding: 0.4rem 0.95rem;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--bg);
  font-size: 0.8rem;
  font-weight: 600;
}
.trope-pick__apply:disabled {
  opacity: 0.5;
  cursor: default;
}
.trope-pick__skip {
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: none;
  color: var(--muted);
  font-size: 0.8rem;
}
.trope-pick__skip:hover { color: var(--text); }

/* ── IP card ── */
.catalog { padding: 1rem 1.25rem 2rem; max-width: var(--max-width); margin: 0 auto; }
.ip-card {
  display: block;
  position: relative; /* anchor for .ip-badge overlay */
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1rem;
  margin-bottom: 0.85rem;
  color: inherit;
  text-decoration: none;
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.ip-card:hover { transform: translateY(-2px); border-color: var(--accent); text-decoration: none; }
/* Featured card: distinguished by its gradient background + the TRENDING badge, NOT by a
   permanent coloured border. --brand == --accent (#7a3eea), so a resting brand border was
   pixel-identical to the :hover accent border — the first card looked permanently hovered
   ("sticky hover") and hovering it produced no visible change. Keep its border neutral
   (inherits --border) so :hover is a real, self-releasing state like every other card. */
.ip-card.featured { background: linear-gradient(180deg, var(--surface-2), var(--surface)); }
.ip-card .cover { width: 100%; aspect-ratio: 3/4; object-fit: cover; border-radius: 8px; margin-bottom: 0.4rem; display: block; }
.ip-card .pen { font-size: 0.62rem; color: var(--accent); letter-spacing: 1.5px; text-transform: uppercase; }
.ip-card .title {
  font-size: 0.92rem; font-weight: 700; margin: 0.3rem 0;
  /* Uniform card height: reserve exactly two title lines (1-line titles keep the same
     vertical footprint as 2-line titles) and clamp longer titles to two lines. */
  line-height: 1.3;
  min-height: 2.6em;               /* 2 lines * 1.3 line-height */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ip-card .synopsis { font-size: 0.9rem; color: var(--muted); }
/* Catalog-card genre + tag row (benchmark R-2026-06-30). Genre = distinct accent
   pill (was hidden); tropes = muted chips + "+N" overflow. Chips WRAP and the row is
   height-clamped to ~2 lines (max-height) so it degrades gracefully on desktop AND
   mobile (2-up) with NO mid-word truncation — replaces the old single-line ellipsis. */
.ip-card .card-meta {
  margin-top: 0.5rem;
  display: flex; flex-wrap: wrap; gap: 0.3rem;
  /* Uniform card height: reserve TWO chip rows (measured 2-row height ~63px) so a card whose
     tags fit one row is the same height as one whose tags wrap to a second row. Chip COUNT is
     already bounded by the "+N" overflow chip (<=4 short chips), so 2 rows is the practical
     max on desktop — min-height (not max-height) keeps every tag visible while equalising the
     footprint. (2.9rem only covered ~1 row, leaving the featured card ~16px shorter.) */
  min-height: 3.95rem;
  align-content: flex-start;
  /* NO max-height / overflow clip. The chip COUNT is already bounded (genre + 2 trope
     chips + a "+N" overflow chip ≤ 4 short chips), so they wrap freely and ALL stay
     visible at EVERY width — desktop AND every mobile viewport. A height clamp here
     previously cut mobile tags to a single row (the recurring "tags clipped" bug). */
}
.ip-card .card-genre,
.ip-card .card-tag {
  font-size: 0.66rem; line-height: 1.25; padding: 0.12rem 0.5rem; border-radius: 999px;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.ip-card .card-genre {
  font-weight: 600; text-transform: capitalize;
  color: var(--brand, #7c3aed);
  background: color-mix(in srgb, var(--brand, #7c3aed) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand, #7c3aed) 30%, transparent);
}
.ip-card .card-tag {
  color: var(--muted); background: var(--surface-2, rgba(255, 255, 255, 0.05));
}
.ip-card .card-tag--more { color: var(--accent); font-weight: 600; }

/* ── "New" corner badge — overlays the cover image, top-left. ──
   Uses --positive (green) to distinguish from the brand-purple featured badge.
   Theme-aware: inherits CSS vars so light/sepia themes render correctly.
   Positioned absolute within .ip-card (position:relative added above). */
.ip-badge {
  position: absolute;
  top: calc(1rem + 8px); /* 1rem card padding + 8px from cover top */
  left: calc(1rem + 8px);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.8px;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 4px;
  pointer-events: none;
}
.ip-badge--new {
  /* darker green than --positive so white text clears the 3.0 contrast gate in
     BOTH themes (white on #2fb463 was only 2.68). */
  background: #15803d;
  color: #fff;
}

/* ── G5 (SCOPED) — "continue" indicator on the single last-read IP card. ──
   Only the ONE most-recent IP from fetchLastRead can show progress today; full
   per-card grid progress needs a per-IP read-state store (ora-infra follow-up).
   Subtle + single: a thin accent bar at the cover bottom + a small "Ch N" chip.
   All theme-var colored (color-mix over --accent/--bg) so it renders in
   light / sepia / dark; no hardcoded palette. */
.ip-card__cover-wrap { position: relative; margin-bottom: 0.4rem; }
.ip-card__cover-wrap .cover { margin-bottom: 0; }
.ip-card__progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px; border-radius: 0 0 8px 8px; overflow: hidden;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  pointer-events: none;
}
.ip-card__progress-fill { display: block; height: 100%; background: var(--accent); }
.ip-card__resume-chip {
  position: absolute; left: 6px; bottom: 8px;
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.3px;
  padding: 0.1rem 0.45rem; border-radius: 999px;
  color: var(--accent);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  pointer-events: none;
}

/* ── Mobile (2-up grid): cover-forward layout ──────────────────────────────
   At narrow widths the 2-column grid makes covers small (~150px wide) while
   text stays the same size, making the info block look disproportionately
   long. Fix: hide tropes entirely and tighten title/pen sizes so the card
   stays cover-forward. Desktop (≥760px) is unchanged — tropes look fine there.
   max-width:559px captures 320/360/390px test widths without touching tablet. */
@media (max-width: 559px) {
  /* Mobile 2-up: smaller chips so genre + tags stay cover-forward, but NO height
     clamp — chips wrap to as many rows as the (count-capped) set needs so tags are
     never clipped. The "+N" chip bounds the count; the card grows to fit. */
  .ip-card .card-meta { margin-top: 0.4rem; gap: 0.25rem; }
  .ip-card .card-genre, .ip-card .card-tag { font-size: 0.6rem; padding: 0.1rem 0.4rem; }
  .ip-card .title { font-size: 0.8rem; margin: 0.2rem 0; }
  .ip-card .pen { font-size: 0.58rem; }
}

/* Author works are a fluid cover-grid at ALL widths (auto-fill): the column count
   changes continuously as the window resizes — so the layout never gets "stuck"
   in a single full-width column with a giant clipped cover (the 720–760px bug).
   Placed BEFORE the @media blocks below so the desktop min-track bumps (240/260px)
   still cascade over this base. The discover catalog gets the same treatment — once
   it is anon-visible, its featured card otherwise sprawled to 680px in the 720–760
   single-column band (caught by the responsive sweep gate). */
[data-ora-author-list],
[data-ora-catalog] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));   /* 2-up on phones (3-up was too cramped); more columns scale up on tablet/desktop */
  gap: 1rem;
  align-items: start;
}

/* ── Desktop / wide viewport ──────────────────────────────────────────────
   read-app is a mobile mirror (single 720px column). On a monitor the catalog
   wasted horizontal space (cards stacked one-per-row), so on wider screens the
   home/discovery surfaces widen and the catalog becomes a responsive multi-column
   grid (auto-fill). The reader body stays narrow (optimal reading line length) —
   only browse/discovery widens. */
@media (min-width: 760px) {
  .topbar,
  .auth-wrap,
  .continue-card,
  .search-row,
  .trope-row,
  .catalog { max-width: 1160px; }
  /* author.html renders its works into [data-ora-author-list]; it must share the
     same responsive multi-column grid as the discover catalog, or the author's
     books sprawl one-per-row full-width on desktop (the width bug). */
  [data-ora-catalog],
  [data-ora-author-list] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
    gap: 1.25rem;
    align-items: start;
  }
  .ip-card { margin-bottom: 0; height: 100%; }
  .ip-card .synopsis {
    display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
  }
}
@media (min-width: 1240px) {
  [data-ora-catalog],
  [data-ora-author-list] { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
}

/* ── Auth ── */
.auth-wrap {
  max-width: 380px;
  margin: 4rem auto 2rem;
  padding: 0 1.25rem;
  text-align: center;
}
.auth-wrap h1 { color: var(--accent); font-size: 2.5rem; letter-spacing: 4px; margin-bottom: 0.5rem; }
.auth-wrap .tagline { color: var(--muted); margin-bottom: 2rem; font-size: 0.95rem; }
.email-input {
  width: 100%;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-size: 0.95rem;
  margin-bottom: 0.75rem;
}
.email-input:focus { outline: none; border-color: var(--accent); }
.btn-primary {
  width: 100%;
  padding: 0.9rem 1rem;
  background: var(--brand);
  border: none;
  border-radius: 10px;
  color: white;
  font-weight: 600;
  font-size: 0.95rem;
}
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.auth-divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin: 0.9rem 0;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  border-bottom: 1px solid var(--border);
  margin: 0 0.6rem;
}
.auth-input {
  width: 100%;
  padding: 0.85rem 1rem;
  background: #ffffff;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: #1f1f1f;
  font-size: 0.95rem;
  margin-bottom: 0.6rem;
}
.auth-input:focus { outline: none; border-color: var(--accent); }
.auth-email-row { display: flex; gap: 0.6rem; margin-bottom: 0.6rem; }
.auth-email-row .btn-google { margin: 0; }
.btn-email-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
.btn-email-primary:hover { filter: brightness(1.08); background: var(--accent); }
.btn-email-secondary {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
.btn-email-secondary:hover { background: rgba(191, 166, 245, 0.1); }
.btn-google {
  width: 100%;
  padding: 0.9rem 1rem;
  background: #ffffff;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: #1f1f1f;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
}
.btn-google:hover { background: #f2f2f2; }
.btn-google:disabled { opacity: 0.5; cursor: not-allowed; }
.auth-hint { font-size: 0.8rem; color: var(--muted); margin-top: 0.75rem; text-align: center; }

/* ── Sign-up consent block (auth.js mountAuthForm + Google onboarding) ───────
   Region-aware marketing consent (Option A "Agree to all" master). Theme-var
   colours only, so it reads correctly in light / sepia / dark. */
.ora-consent {
  text-align: left;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.65rem 0.75rem;
  margin-bottom: 0.6rem;
}
.ora-consent-line {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.22rem 0;
  font-size: 0.82rem;
  color: var(--text);
  line-height: 1.35;
}
.ora-consent-line input[type="checkbox"] {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  margin-top: 0.15rem;
  accent-color: var(--brand);
  cursor: pointer;
}
.ora-consent-line label { cursor: pointer; }
.ora-consent-line a { color: var(--brand); text-decoration: underline; }
.ora-consent-line--master label { font-weight: 700; }
.ora-consent-divider { border-top: 1px dashed var(--border); margin: 0.35rem 0; }
.ora-consent-group {
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0.45rem 0 0.15rem;
}
.ora-consent-note {
  font-size: 0.7rem;
  color: var(--muted);
  line-height: 1.4;
  padding: 0.1rem 0 0.15rem;
}
.ora-consent-note--muted { opacity: 0.9; }

/* ── Auth prompt modal (engagement gating) ──────────────────────────────────
   Dismissible overlay reusing the email/password + Google form (auth-prompt.js).
   Replaces the old full-page auth wall: the catalog is browsable anon, this
   modal only opens on a protected action or the header "Sign in" button. */
.ora-auth-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}
.ora-auth-modal.hidden { display: none; }
.ora-auth-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.66);
  backdrop-filter: blur(2px);
}
.ora-auth-modal__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.75rem 1.5rem 1.5rem;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.ora-auth-modal__close {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.6rem;
  line-height: 1;
  padding: 0.25rem 0.5rem;
}
.ora-auth-modal__close:hover { color: var(--text); }
.ora-auth-modal__brand {
  font-size: 1.75rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 3px;
  margin-bottom: 0.5rem;
}
.ora-auth-modal__reason {
  color: var(--text);
  font-size: 0.95rem;
  margin-bottom: 1.25rem;
}
.ora-auth-modal .fineprint { margin-top: 0.75rem; }
body.ora-modal-open { overflow: hidden; }

/* Unified chapter-lock modal (js/chapter-access.js) — the page-agnostic "This chapter is
   locked" sheet the Chapters dropdown opens for a LOCKED chapter (anon = Sign in to unlock;
   signed-in = coins/subscribe), so a locked item never silently navigates into the reader. */
.ora-lockmodal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}
.ora-lockmodal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.66);
  backdrop-filter: blur(2px);
}
.ora-lockmodal__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.75rem 1.5rem 1.5rem;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.ora-lockmodal__panel h3 { font-size: 1.25rem; font-weight: 800; margin-bottom: 0.5rem; color: var(--text); }
.ora-lockmodal__panel p { color: var(--muted); font-size: 0.95rem; margin-bottom: 0.75rem; }
.ora-lockmodal__panel .price { font-size: 1.3rem; font-weight: 700; color: var(--text); margin-bottom: 1rem; }
.ora-lockmodal__panel .btn-primary,
.ora-lockmodal__panel .btn-secondary { width: 100%; margin-bottom: 0.6rem; }

/* Signed-out gate CTA — shown on Library/Profile when reached anon (no redirect
   loop). The Sign-in button opens the auth-prompt modal. */
.gate-cta {
  max-width: 380px;
  margin: 4rem auto 2rem;
  padding: 0 1.25rem;
  text-align: center;
}
.gate-cta-title { color: var(--text); font-size: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; }
.gate-cta-sub { color: var(--muted); font-size: 0.9rem; margin-bottom: 1.5rem; }
.gate-cta .btn-primary { max-width: 240px; margin: 0 auto; }
.btn-secondary {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  padding: 0.75rem 1.25rem;
  border-radius: 999px;
  font-size: 0.85rem;
}
.fineprint { font-size: 0.7rem; color: var(--muted); margin-top: 2rem; line-height: 1.5; }

/* ── Reader ── */
.reader-topbar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  /* safe-area-inset-top so the sticky bar clears the notch/status bar on iOS;
     env(..., 0px) → no change on non-notched viewports. (reader clip fix) */
  padding: calc(0.6rem + env(safe-area-inset-top, 0px)) 0.85rem 0.6rem;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 5;
}
/* ⑤ Home-style header cluster (Search + profile avatar) in the reader topbar. It sits at
   the far right after the coin/unlimited/share affordances (the .reader-info flex:1 pushes
   it over). Kept compact + shrink-safe so the reader topbar never overflows on mobile. */
.reader-topbar .topbar-right { flex-shrink: 0; margin-left: auto; gap: 0.4rem; }
.reader-topbar .topbar-right .ora-search-field { max-width: 46vw; }
/* Mobile: keep the whole topbar on one non-overflowing row — clamp the episode title so the
   right-side cluster (Unlimited/Share/Search/avatar) always stays fully visible + tappable. */
@media (max-width: 767px) {
  .reader-topbar { gap: 0.3rem; padding-left: 0.6rem; padding-right: 0.6rem; }
  .reader-topbar .reader-info { min-width: 0; }
  /* Title WRAPS to two lines instead of a hard 1-line ellipsis clip (was "Truce of Frost and
     F…"). Two lines fit realistic titles fully while the sticky bar's measured height
     (--reader-topbar-h) grows to match, so the right cluster stays intact and nothing scrolls. */
  .reader-topbar .reader-info .eptitle {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.2;
  }
  .reader-topbar .reader-info .reader-readtime { display: none; }
  .reader-topbar .reader-unlimited .unlimited-label { display: none; }
  .reader-topbar .topbar-right { gap: 0.25rem; }
}
/* Very narrow phones (≤ 430px): the reader topbar carries too many affordances (logo,
   cover thumb, title, Unlimited-upsell, Share, Search, account) — the flex:1 title column
   collapsed to 0 and the right cluster overflowed a few px. Drop the two SECONDARY items
   (the Unlimited upsell — still reachable via the coin pill / coins page — and Share) so
   the title keeps real, readable width and the bar never overflows. */
@media (max-width: 430px) {
  .reader-topbar .reader-unlimited,
  .reader-topbar .reader-share,
  .reader-topbar .reader-cover-thumb { display: none; }
  .reader-topbar .reader-info { flex: 1 1 auto; min-width: 4rem; }
  /* The reader header is the MOST crowded (logo · chapter title · search · coin · wait-timer ·
     streak · avatar). Signed-in, it can't fit at phone widths. Keep the reading-context essentials
     (logo · title · account) + the monetization coin + wait-timer; drop the two most secondary
     (streak — gamification, on Profile/Reading; search — reachable from home) so nothing clips. */
  .reader-topbar .streak-mount,
  .reader-topbar .topbar-right .ora-search { display: none; }
}
/* ROOT top-bar-fits fix (user-reported 2026-07-13): entering a locked chapter arms the
   per-episode wait-timer, which paints "Free in 23h 59m 59s" (~140px) into the reader
   top-bar — the ONE status slot that had no narrow adaptation, so the bar clipped. Mirror
   the header wait-timer chip: on phones drop the "Free in" label + the seconds segment so
   the slot's WORST case shrinks to "23h 59m" (~55px). The coin-wrap also flex-shrinks so it
   can never push the bar past the viewport. Enforced by scripts/check_topbar_fits.py (every
   status slot seeded with worst-case content → 0 clip at 360/390/430). */
/* The reader carries its OWN per-episode wait timer (.reader-freetimer). header-extras also
   mounts the GLOBAL coin + "Free unlocks waiting" timer + streak into .reader-topbar .topbar-right
   — so the reader showed TWO "Free in" timers, and the per-episode one overflowed its shrunk wrap
   INTO the coin pill (user-reported z-overlap 2026-07-13). Fix: on the reader, suppress the
   REDUNDANT global timer + streak (the per-episode timer + the paywall cover the same ground);
   keep the coin. The per-episode wrap takes its natural width so its text never spills into a
   neighbour. */
/* Suppress the redundant global timer/streak ONLY when the per-episode timer wrap is ACTIVE
   (reader.html on a locked chapter → .reader-coin-wrap is unhidden). episode.html (IP-intro+EP1)
   ALSO uses .reader-topbar but has NO per-episode timer, so the GLOBAL chip is the only "Free in"
   indicator there and must STAY (regression fix 2026-07-13 [B]). orderRightCluster (tab-bar.js)
   now MOVES .reader-coin-wrap INTO .topbar-right, ranked right after the coin — so coin/timer
   order matches home/IP. The wrap precedes the global timer + streak inside the cluster, so the
   general-sibling combinator still scopes the suppression correctly. */
.reader-topbar .topbar-right .reader-coin-wrap:not([hidden]) ~ .header-timer-wrap,
.reader-topbar .topbar-right .reader-coin-wrap:not([hidden]) ~ .streak-badge,
.reader-topbar .topbar-right .reader-coin-wrap:not([hidden]) ~ .streak-mount { display: none; }
.reader-topbar .reader-coin-wrap { flex: 0 0 auto; min-width: 0; overflow: hidden; }
@media (max-width: 480px) {
  /* Narrow top bar: drop the "Free in" caption AND the seconds so the countdown ("23h 59m")
     fits beside a worst-case coin balance (check_topbar_fits seeds "Free in 23h 59m 59s" +
     "🪙 99999" — with seconds shown that co-occurrence overflows 360 by ~7px). Desktop (≥480px)
     shows the full "Free in HH MM SS". [2b] 2026-07-13: mobile shows HH MM, not HH MM SS. */
  .reader-freetimer .live-countdown__label,
  .reader-freetimer .live-countdown__seg--sec { display: none; }
  .reader-freetimer { font-variant-numeric: tabular-nums; white-space: nowrap; }
}
.reader-home {
  /* Match the home catalog wordmark (.brand): inherit the body sans font, weight 800,
     wide tracking — so the ORA logo is identical across home / reader / episode. */
  font-weight: 800;
  font-size: 1.15rem;
  letter-spacing: 2px;
  color: var(--accent);
  text-decoration: none;
  padding: 0 0.5rem 0 0.1rem;
  flex-shrink: 0;
}
/* IP cover thumbnail in the reader header — small brand-continuity confirmation next to
   the episode meta. Poster aspect (2:3), never dominates the topbar. */
.reader-cover-thumb {
  width: 30px;
  height: 44px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
  margin-right: 0.5rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.reader-share {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 1.3rem;
  padding: 0 0.6rem;
  cursor: pointer;
}
/* M003/I027/T009 — persistent Unlimited (subscription) badge in the reader topbar.
   Unobtrusive pill next to the share button; always visible. .is-subscribed switches
   from the accent "subscribe" affordance to a filled "Unlimited active" state. */
.reader-unlimited {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: none;
  border: 1px solid var(--accent);
  color: var(--accent);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  line-height: 1;
  padding: 0.28rem 0.55rem;
  margin-right: 0.15rem;
  cursor: pointer;
  flex-shrink: 0;
}
.reader-unlimited svg { width: 15px; height: 15px; }
.reader-unlimited .unlimited-label { white-space: nowrap; }
.reader-unlimited.is-subscribed {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.reader-info { flex: 1; margin-left: 0.25rem; }
.reader-info .epnum { font-size: 0.65rem; color: var(--muted); letter-spacing: 1px; text-transform: uppercase; }
.reader-info .eptitle { font-size: 0.9rem; font-weight: 600; }
/* IP (story) title above the episode line — tells the reader which story this is.
   ③ Injected by reader-meta.js ONLY on EP1 (the IP intro belongs to the IP-detail). */
.reader-info .reader-ip-title { font-size: 0.8rem; font-weight: 700; color: var(--accent); line-height: 1.2; margin-bottom: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* (The old top-left .ep-dropdown chapter picker was replaced by the right-side
   ⑥ "Chapters ▾" box — see .reader-chapters / .cbx-* below.) */

.reader-cover-thumb[data-ora-cover] { cursor: pointer; }
/* Reading progress bar — a thin brand-accent line that fills 0→100% as the chapter is
   scrolled (driven by reader.js onScroll off the SAME scrollPct as the read_progress event).
   STICKY just under the sticky .reader-topbar so it stays pinned + visible the whole read
   (it used to sit in normal flow → scrolled away on the first swipe = invisible). --reader-topbar-h
   is the measured topbar height (tab-bar.js), so it tucks right below at any topbar height. */
.progress-track {
  position: sticky;
  top: calc(var(--reader-topbar-h, 56px));
  z-index: 4;
  height: 3px;
  background: var(--surface-2);
}
.progress-fill {
  height: 100%;
  background: var(--brand);
  width: 0%;
  transition: width 0.1s linear;
}

.reader-body {
  max-width: 620px;
  margin: 0 auto;
  /* bottom padding clears the sticky nav (~63px) + the home-indicator safe area
     so the last lines never sit behind the nav at rest. (reader clip fix) */
  padding: 1.5rem 1.25rem calc(4rem + env(safe-area-inset-bottom, 0px));
  /* I022/T206 — typography driven by reader-prefs CSS vars (size/line/font). */
  font-size: calc(1.05rem * var(--reader-font-scale, 1));
  line-height: var(--reader-line, 1.75);
  font-family: var(--reader-font, Georgia, 'Times New Roman', serif);
}
.reader-body h2 { font-size: 1.35rem; margin: 1.5rem 0 0.75rem; color: var(--accent); }
.reader-body p { margin-bottom: 1rem; }
.reader-body em { color: var(--accent); }
/* Piracy protection — block text selection on the STORY BODY only (reader.html render
   + episode.html EP1 inline both use .reader-body). Scoped to the body container, NOT
   the page, so UI (topbar, nav, comment inputs, reactions) stays fully selectable/usable.
   JS additionally blocks copy/cut/contextmenu/dragstart on this same container. */
.reader-body {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none; /* iOS Safari: suppress the long-press copy/share callout */
}
/* bigcover A/B variant — hero cover at the top of the body. Capped at ~40vh so the first
   paragraph still peeks below the fold (avoids pushing all text off-screen on iPhone). */
.reader-hero-cover { margin: 0 0 1.5rem; padding: 0; display: flex; flex-direction: column; align-items: center; }
.reader-hero-cover img {
  width: auto;
  max-width: min(320px, 78%);
  max-height: 40vh;
  border-radius: 12px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.32);
  object-fit: cover;
  display: block;
}
.reader-hero-cover figcaption {
  margin-top: 0.6rem;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--accent);
  text-align: center;
}

.locked-overlay {
  position: relative;
  margin-top: 1.5rem;
  padding: 2rem 1.25rem;
  text-align: center;
  background: linear-gradient(180deg, transparent, var(--surface-2));
  border-top: 1px solid var(--border);
  border-radius: 0 0 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.locked-overlay h3 { color: var(--accent); margin-bottom: 0.6rem; font-size: 1.1rem; }
.locked-overlay p { color: var(--muted); margin-bottom: 1.25rem; font-size: 0.9rem; }
.locked-overlay .price { font-size: 1.4rem; font-weight: 700; color: var(--text); margin-bottom: 0.75rem; }
/* Primary CTA inside paywall: trim vertical padding + add a clear break before
   the secondary stack (Subscribe / More packs / Not now) so the purple block
   doesn't abut the outline buttons below it. */
.locked-overlay .btn-primary { padding: 0.75rem 1rem; margin-bottom: 12px; }
/* Free wait-to-unlock countdown surfaced INSIDE the paywall (reader.js showPaywall) — a tinted
   brand-accent box so the coin-free option is obvious next to the paid buttons. Only shown when
   the timer is armed. */
.paywall-freetimer {
  margin: 0.15rem 0 0.9rem;
  padding: 0.6rem 0.85rem;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  font-size: 1.05rem;
  font-weight: 700;
}
.paywall-freetimer .live-countdown__label { color: var(--accent); font-weight: 700; }
.paywall-freetimer .live-countdown { color: var(--text); font-variant-numeric: tabular-nums; }

/* M003/I027/T002 — TOP-BAR coin balance + FREE auto-unlock timer (ReelShort/DramaBox style).
   The coin pill sits at the right of the reader top bar; the free wait-to-unlock countdown
   ("Free in Hh Mm Ss") mounts to its LEFT and, when it elapses, the episode auto-unlocks and
   the timer disappears — leaving just the coin balance. No button; the flow is automatic. */
.reader-coin-wrap {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
  margin-right: 0.3rem;
}
.reader-coin-wrap[hidden] { display: none; }
.reader-coin {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.24rem 0.5rem;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}
.reader-coin__icon { font-size: 0.85rem; }
.reader-coin__balance { font-variant-numeric: tabular-nums; }
.reader-freetimer { display: inline-flex; align-items: center; }
.reader-freetimer:empty { display: none; }
.reader-freetimer-slot { display: inline-flex; align-items: center; }
/* READY-state tap CTA — shown in the top-bar slot when the free unlock is available NOW (fresh
   first-free, or an elapsed timer) IN PLACE of a countdown, so the slot is NEVER blank
   (user-reported [2a] 2026-07-13). Compact pill sized to sit beside the coin balance without
   overflowing the bar; single-line so it never wraps into a neighbour. */
.reader-freeready {
  display: inline-flex; align-items: center; gap: 0.2rem;
  border: 1px solid var(--brand); background: transparent; color: var(--brand);
  border-radius: 999px; padding: 0.2rem 0.5rem;
  font-size: 0.72rem; font-weight: 700; line-height: 1.15; white-space: nowrap;
  cursor: pointer; max-width: 100%;
}
.reader-freeready:disabled { opacity: 0.6; cursor: default; }

/* M003/I027 — compact live countdown for the top-bar free-timer slot. tabular numerals so
   the ticking seconds don't jitter the layout; a subtle per-second beat marks the tick-tock. */
.live-countdown {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.24rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  line-height: 1;
  transition: box-shadow 0.5s ease;
}
.live-countdown.is-tick {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}
.live-countdown__label {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent);
  white-space: nowrap;
}
.live-countdown__clock {
  display: inline-flex;
  align-items: baseline;
  gap: 0.28rem;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.live-countdown__seg { display: inline-flex; align-items: baseline; gap: 1px; }
.live-countdown__seg b {
  font-size: 0.82rem;
  line-height: 1;
  font-weight: 800;
  color: var(--text);
  min-width: 2ch;
  text-align: center;
}
.live-countdown__seg i {
  font-style: normal;
  font-size: 0.6rem;
  font-weight: 600;
  color: var(--muted);
}
/* The seconds place is the live "tick-tock" beat — accent-tint it so the eye tracks the motion. */
.live-countdown__seg--sec b { color: var(--accent); }
/* Zero-state: countdown elapsed → "Unlocking" + unlock glyph, calm (no beat). */
.live-countdown.is-ready {
  background: color-mix(in srgb, var(--accent) 20%, var(--surface));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
}
.live-countdown.is-ready .live-countdown__label { color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .live-countdown { transition: none; }
  .live-countdown.is-tick { box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent); }
}

/* M003/I027/T010 — chapter-end peak-intent paywall. Reuses the .locked-overlay sheet; adds a
   reward nudge row ("free in X h + check in for N coins") above the CTAs. */
.locked-overlay.peak-paywall { border-top-color: var(--accent); }
.peak-reward {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 4px 0 12px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1.4;
}
.peak-reward__icon { font-size: 1rem; }
.peak-reward__text strong { color: var(--accent); }

/* ── Chapter-END block (js/chapter-end.js) — replaces the old sticky .reader-nav.
   Rendered in-flow at the bottom of the reader body: Next-chapter CTA + reaction
   row → (existing) EP comment section → recommendations + secondary Prev/Episodes.
   Inherits the reader theme tokens (sepia/light/dark) like .ep-comments. ── */
.chapter-end {
  max-width: var(--max-width);
  margin: 0 auto;
  border-top: 1px solid var(--border);
}
/* The EP comment section keeps its own border-top when it follows the CTA block;
   suppress the double rule since .chapter-end already draws the top border. */
.chapter-end .ce-top + .ep-comments { border-top: 1px solid var(--border); }
.chapter-end > .ep-comments:first-child { border-top: none; }

.ce-top {
  padding: 1.5rem 1.1rem 1.25rem;
}
.ce-next {
  /* Reuses .btn-primary sizing; slightly taller for the prominent chapter-end CTA. */
  padding: 1rem 1rem;
  font-size: 1rem;
}

/* ep1_ending_hook A/B — 'treatment' (EP1 only): teaser line + a more prominent,
   gently pulsing Next button. Presentation-only; the copy is generic non-spoiler
   hook framing set in chapter-end.js. Control renders the plain .ce-next above. */
.ce-hook { text-align: center; }
.ce-hook__teaser {
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 0.75rem;
  line-height: 1.45;
}
.ce-next--hook {
  font-size: 1.08rem;
  padding: 1.1rem 1rem;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
  animation: ce-hook-pulse 1.8s ease-out infinite;
}
@keyframes ce-hook-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  70%  { box-shadow: 0 0 0 12px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .ce-next--hook { animation: none; }
}
.ce-caughtup {
  text-align: center;
  padding: 1.25rem 1rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}
.ce-caughtup__title { font-size: 1.05rem; font-weight: 700; color: var(--accent); margin-bottom: 0.35rem; }
.ce-caughtup__sub { font-size: 0.85rem; color: var(--muted); line-height: 1.5; }

/* Reaction GRID — 12 tiles, 4×3 (Inkitt parity). Each tile = emoji + count + label.
   Emit-only (no server total): the count reflects the reader's own tapped state. */
.ce-reactions { margin-top: 1.5rem; }
.ce-reactions__prompt {
  font-size: 0.9rem; font-weight: 600; color: var(--text);
  text-align: center; margin-bottom: 0.9rem; line-height: 1.4;
}
.ce-reactions__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
}
.ce-reaction {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 0.15rem; padding: 0.6rem 0.3rem;
  min-height: 78px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; line-height: 1.15; cursor: pointer;
  transition: transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
}
.ce-reaction:hover, .ce-reaction:focus-visible { border-color: var(--accent); outline: none; transform: translateY(-1px); }
.ce-reaction.is-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
}
.ce-reaction__emoji { display: block; font-size: 1.5rem; line-height: 1; }
.ce-reaction__count { font-size: 0.78rem; font-weight: 700; color: var(--muted); line-height: 1; }
.ce-reaction.is-active .ce-reaction__count { color: var(--accent); }
.ce-reaction__label { font-size: 0.68rem; color: var(--muted); text-align: center; line-height: 1.15; }
.ce-reaction.is-active .ce-reaction__label { color: var(--text); }

/* 390px phones: drop to 3 columns (12 → 4 rows) so labels/emoji stay legible. */
@media (max-width: 439px) {
  .ce-reactions__grid { grid-template-columns: repeat(3, 1fr); gap: 0.45rem; }
  .ce-reaction { min-height: 74px; padding: 0.55rem 0.25rem; }
  .ce-reaction__label { font-size: 0.64rem; }
}

/* Recommendations block — "Your next obsession" (Inkitt FURTHER RECOMMENDATIONS style).
   Cards: landscape COVER on top → title + by-author → star rating → top-reviewer line
   (**{reviewer}:** {snippet}) → "Read now". Responsive grid; 1-col on phones. */
.ce-bottom { padding: 0.5rem 1.1rem calc(2.5rem + env(safe-area-inset-bottom, 0px)); }
.ce-similar { margin-bottom: 1.5rem; }
.ce-similar__head { font-size: 1.05rem; font-weight: 700; color: var(--text); margin-bottom: 0.9rem; }
.ce-rec-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.9rem;
}
.ce-rec {
  display: flex; flex-direction: column;
  color: inherit; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; overflow: hidden;
  transition: border-color 0.15s ease, transform 0.12s ease;
}
.ce-rec:hover, .ce-rec:focus-visible { text-decoration: none; border-color: var(--accent); transform: translateY(-1px); outline: none; }
.ce-rec__cover {
  width: 100%; aspect-ratio: 16/9; object-fit: cover;
  display: block; border-bottom: 1px solid var(--border);
}
.ce-rec__cover--empty { background: var(--surface-2); }
.ce-rec__body { min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; padding: 0.7rem; }
.ce-rec__title {
  font-size: 0.9rem; font-weight: 700; line-height: 1.25; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ce-rec__by { font-size: 0.72rem; color: var(--muted); }
.ce-rec__stars { display: flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; }
.ce-rec__stars-num { color: var(--muted); font-weight: 600; }

/* ── Shared fractional star meter (starMeterHtml, review-format.js) ──────────────
   The SSOT web star renderer for AVG + per-review ratings. An empty ★ track with a
   gold ★ overlay clipped to an exact width → a clean half/fractional star, no broken
   U+2BE8 glyph. Gold (#f5a623) unifies the rating color everywhere (was a mix of gold
   and brand-purple). Track + fill share identical font metrics so the overlay aligns. */
.ora-stars {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  line-height: 1;
  font-size: 0.95rem;
  letter-spacing: 1px;
  vertical-align: middle;
}
.ora-stars__track { color: var(--border); }
.ora-stars__fill {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  color: #f5a623;
}
.ora-stars-sm { font-size: 0.82rem; }
.ce-rec__reviewer {
  font-size: 0.75rem; color: var(--muted); line-height: 1.4; margin-top: 0.15rem;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.ce-rec__reviewer-name { font-weight: 700; color: var(--text); }
.ce-rec__cta { font-size: 0.75rem; font-weight: 600; color: var(--accent); margin-top: 0.35rem; }
@media (max-width: 720px) {
  .ce-rec-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .ce-rec-grid { grid-template-columns: 1fr; }
}

/* Secondary Prev / Episodes controls — light links, not the primary CTA. */
.ce-secondary {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);
}
.ce-secondary__link { font-size: 0.85rem; color: var(--accent); }
.ce-secondary__link:hover { text-decoration: underline; }
.ce-secondary__link.is-disabled { color: var(--muted); opacity: 0.5; cursor: default; }
.ce-secondary__link.is-disabled:hover { text-decoration: none; }

/* ── IP detail header (episode.html, above the episode list) ── */
.ip-detail {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.25rem 1.25rem 0.5rem;
}
.ip-detail .cover {
  width: 100%;
  max-width: 240px;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: 12px;
  display: block;
  margin: 0 auto 1.1rem;
  border: 1px solid var(--border);
}
.ip-detail .pen {
  font-size: 0.72rem;
  color: var(--accent);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.ip-detail .pen a { color: var(--accent); }
.ip-detail .ip-title {
  font-size: 1.55rem;
  font-weight: 800;
  margin: 0.25rem 0 0.5rem;
  line-height: 1.25;
}
.ip-detail .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.6rem 0 0.9rem;
}
.ip-detail .chip {
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.72rem;
  white-space: nowrap;
}
.ip-detail .chip.genre {
  color: var(--accent);
  border-color: var(--accent);
}
.ip-detail .synopsis {
  font-size: 0.92rem;
  color: var(--text);
  margin-bottom: 0.4rem;
}
.ip-detail .synopsis.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.synopsis-toggle {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0 0 0.25rem;
  text-decoration: underline;
}
.btn-follow {
  width: 100%;
  padding: 0.85rem 1rem;
  margin: 0.6rem 0 0.4rem;
  background: var(--brand);
  border: 1px solid var(--brand);
  border-radius: 10px;
  color: #ffffff;
  font-weight: 700;
  font-size: 0.95rem;
}
.btn-follow:hover { filter: brightness(1.08); }
.btn-follow.is-following {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}
.btn-follow:disabled { opacity: 0.6; cursor: default; }

/* ── Author page (author.html) ── */
.author-head {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.5rem 1.25rem 0.5rem;
}
.author-head .label {
  font-size: 0.72rem;
  color: var(--accent);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.author-head .name {
  font-size: 1.8rem;
  font-weight: 800;
  margin-top: 0.2rem;
}
.author-head .count { font-size: 0.85rem; color: var(--muted); margin-top: 0.25rem; }
/* Header must share the catalog's max-width so its left edge aligns with the books
   grid — otherwise it's centered to 720px while the grid spans 1160px and looks
   off-center (≈120px skew at ~1000px wide). */
@media (min-width: 760px) { .author-head { max-width: 1160px; } }

/* ── Episode list ── */
.ep-list { padding: 1rem 1.25rem 2rem; max-width: var(--max-width); margin: 0 auto; }
.ep-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 0.6rem;
  color: inherit;
  text-decoration: none;
}
.ep-card:hover { border-color: var(--accent); text-decoration: none; }
.ep-card .ep-meta .num { font-size: 0.7rem; color: var(--muted); letter-spacing: 1px; }
.ep-card .ep-meta .title { font-size: 0.95rem; font-weight: 600; }
.ep-card .ep-lock { font-size: 0.8rem; color: var(--accent); white-space: nowrap; text-align: right; }
.ep-card .ep-lock.free { color: var(--positive); }
.ep-card .ep-lock.locked { color: var(--muted); }
/* I027/T014 — an already-unlocked paid chapter still shows its coin price up front,
   but muted (informational, not a call-to-unlock). */
.ep-card .ep-lock.unlocked { color: var(--muted); }

/* ── I027/T014 — IP-detail heat badge ── */
.ip-detail .chip.heat {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  color: #e2564a;
  border-color: #e2564a;
  background: color-mix(in srgb, #e2564a 12%, transparent);
}
.ip-detail .chip.heat .heat-flame { display: inline-flex; line-height: 1; }
.ip-detail .chip.heat .heat-flame svg { width: 13px; height: 13px; }
.ip-detail .chip.heat .heat-label { margin-left: 0.25rem; font-weight: 600; }
.ip-detail .chip.heat-3 { color: #d63a2c; border-color: #d63a2c; }

/* ── I027/T014 — summary + similar-titles A/B modules ── */
.detail-modules {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.25rem;
}
.detail-module { margin: 0 0 1.75rem; }
.detail-module-title {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0 0 0.6rem;
}
.detail-summary-body {
  font-size: 0.92rem;
  color: var(--text);
  line-height: 1.55;
  margin: 0;
}
.similar-row {
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 0.25rem;
  -webkit-overflow-scrolling: touch;
}
.similar-card {
  flex: 0 0 116px;
  scroll-snap-align: start;
  color: inherit;
  text-decoration: none;
}
.similar-card:hover { text-decoration: none; }
.similar-cover {
  width: 116px;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
  margin-bottom: 0.4rem;
}
.similar-cover-empty { background: var(--surface); }
.similar-card:hover .similar-cover { border-color: var(--accent); }
.similar-title {
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.similar-pen {
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 0.1rem;
}

/* ── Misc ── */
.empty, .error-state { text-align: center; padding: 4rem 1.25rem; color: var(--muted); }
.error-state { color: var(--danger); }
.loading { text-align: center; padding: 4rem 1.25rem; color: var(--accent); }
.hidden { display: none !important; }

/* ════════════════════════════════════════════════════════════════════════════
   I022/T206 — Reader customization: themes · settings panel · page mode.
   ════════════════════════════════════════════════════════════════════════════ */

/* Theme overrides (default = dark, the :root palette above). Light/Sepia swap
   the reader chrome palette so topbar/nav/body are all consistent. */
:root[data-reader-theme="light"] {
  --positive: #15803d;
  --bg: #ffffff;
  --scrim: rgba(20, 20, 30, 0.30);
  --surface: #f4f4f7;
  --surface-2: #ececef;
  --border: #e3e3ea;
  --text: #1f1f27;
  --muted: #6c6c79;
  --accent: #7a3eea;
  --brand: #7a3eea;
}
:root[data-reader-theme="sepia"] {
  --bg: #f4ecd8;
  --scrim: rgba(60, 45, 20, 0.28);
  --surface: #efe5cd;
  --surface-2: #eaddc0;
  --border: #ddcfae;
  --text: #4b3b2a;
  /* #6b5738 on --surface-2 #eaddc0 ≈ 3.3:1 — clears the 3.0 contrast gate
     for comment/like/edit/delete controls that use var(--muted). The original
     #8a785a was only ~2.1:1 on the sepia surface, making icons near-invisible. */
  --muted: #6b5738;
  --accent: #8a5a2b;
  --brand: #8a5a2b;
  --positive: #3a6b2a;
}

/* In sepia the comment bubble rests at opacity:0.32 which compounds the muted
   colour contrast problem. Raise the floor so inactive bubbles are readable. */
:root[data-reader-theme="sepia"] .cm-bubble { opacity: 0.7; }
:root[data-reader-theme="light"] .cm-bubble  { opacity: 0.6; }

/* Aa settings button in the topbar (right-aligned, mirrors Kindle/Apple Books). */
.reader-aa {
  margin-left: auto;
  background: none;
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 0.95rem;
  font-weight: 600;
  border-radius: 8px;
  padding: 4px 10px;
  cursor: pointer;
}
.reader-aa:hover { border-color: var(--accent); color: var(--accent); }

/* Settings bottom-sheet panel. */
.rs-panel { position: fixed; inset: 0; z-index: 40; }
.rs-panel[hidden] { display: none; }
.rs-backdrop {
  position: absolute; inset: 0; z-index: 1;
  background: rgba(0, 0, 0, 0.4);
  opacity: 0; transition: opacity 0.2s ease;
}
.rs-panel.is-open .rs-backdrop { opacity: 1; }
.rs-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  max-width: var(--max-width); margin: 0 auto;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  padding: 0.5rem 1.1rem 1.4rem;
  transform: translateY(100%);
  transition: transform 0.22s ease;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.35);
}
.rs-panel.is-open .rs-sheet { transform: translateY(0); }
.rs-handle {
  width: 36px; height: 4px; border-radius: 2px;
  background: var(--border); margin: 8px auto 14px;
}
.rs-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 0;
}
.rs-label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.rs-segs { display: flex; gap: 6px; align-items: center; }
.rs-seg {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 6px 12px;
  font-size: 0.85rem;
  cursor: pointer;
  min-width: 44px;
}
.rs-seg.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
  font-weight: 600;
}
.rs-stepper { gap: 4px; }
.rs-size-ind { font-size: 0.8rem; color: var(--muted); min-width: 34px; text-align: center; }

/* ── Page (paginated) reading mode ──────────────────────────────────────────
   Vertical translateY pagination — avoids CSS-multicol horizontal bleed and
   half-line clipping. The viewport clips to exactly one page of height; the
   body is a normal vertical document translated upward by N × pageHeight.
   pageHeight is snapped to whole line-heights in JS so text never splits
   mid-line. Body scroll is locked (overflow:hidden on viewport). */
.reader-viewport.is-paged {
  overflow: hidden;
  height: var(--reader-page-h, 70vh);
  position: relative;
  cursor: default;
  /* prevent body scroll bleed-through on iOS */
  -webkit-overflow-scrolling: auto;
}
.reader-body.is-paged {
  /* keep normal vertical flow — no multicol, no fixed height */
  max-width: 620px;
  height: auto;
  /* Zero top/bottom padding so scrollHeight = pure text height with no
     padding distortion. The PAGE_INSET breathing room is applied via the
     translateY offset in JS so every page (not just page 0) gets identical
     top+bottom margins. Horizontal padding preserved for line measure. */
  padding: 0 1.25rem;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}

/* Chevron prev/next buttons — absolutely positioned in the viewport.
   Visible affordance (Kindle / Apple Books style). */
.reader-pgbtn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--accent);
  border-radius: 50%;
  width: 40px;
  height: 40px;
  font-size: 1.4rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.75;
  transition: opacity 0.15s ease;
  cursor: pointer;
  padding: 0;
}
.reader-pgbtn:hover { opacity: 1; }
.reader-pgbtn:disabled { opacity: 0.2; cursor: default; }
.reader-pgbtn--prev { left: 8px; }
.reader-pgbtn--next { right: 8px; }
.reader-pgbtn[hidden] { display: none; }

/* Invisible tap zones (left third / right third) layered under the chevrons
   so a tap anywhere in those zones turns the page — chevrons give the visual. */
.reader-tapzone {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 33%;
  z-index: 2;
  cursor: pointer;
}
.reader-tapzone--prev { left: 0; }
.reader-tapzone--next { right: 0; }
.reader-tapzone[hidden] { display: none; }

/* Page indicator N / total — bottom centre of viewport */
.reader-pageind {
  position: absolute; bottom: 8px; left: 0; right: 0;
  text-align: center; font-size: 0.72rem; color: var(--muted);
  pointer-events: none;
  z-index: 4;
}
.reader-pageind[hidden] { display: none; }

/* ════════════════════════════════════════════════════════════════════════════
   I022/T207 — Per-paragraph inline comments (Wattpad/Webtoon top-comment culture).
   ════════════════════════════════════════════════════════════════════════════ */
.reader-body > p { position: relative; }
.cm-bubble {
  position: absolute;
  right: -2px;
  bottom: -7px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 12px;
  padding: 1px 7px;
  font-size: 0.7rem;
  line-height: 1.6;
  cursor: pointer;
  opacity: 0.32;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  z-index: 2;
}
.cm-bubble .cm-ico { font-size: 0.72rem; }
.cm-bubble.has-comments { opacity: 1; color: var(--accent); border-color: var(--accent); }
.cm-bubble:hover, .cm-bubble:focus-visible { opacity: 1; }
.cm-bubble .cm-count:empty { display: none; }
/* Page (immersive) mode hides inline-comment bubbles — absolute bubbles inside
   CSS-multicol can clip at column breaks, and paged reading is distraction-free
   (Kindle/Apple Books paged mode likewise omits inline social). Comments remain
   fully available in scroll mode (the default). (critic page-mode follow-up) */
.reader-body.is-paged .cm-bubble { display: none; }

/* Comment thread bottom-sheet (mirrors the reader-settings sheet). */
.cm-panel { position: fixed; inset: 0; z-index: 45; }
.cm-panel[hidden] { display: none; }
.cm-backdrop {
  position: absolute; inset: 0; z-index: 1;
  background: var(--scrim, rgba(0, 0, 0, 0.5));
  opacity: 0; transition: opacity 0.2s ease;
}
.cm-panel.is-open .cm-backdrop { opacity: 1; }
.cm-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  max-width: var(--max-width); margin: 0 auto;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  padding: 0.5rem 1.1rem 1.1rem;
  max-height: 70vh; display: flex; flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.22s ease;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.35);
}
.cm-panel.is-open .cm-sheet { transform: translateY(0); }
.cm-head { font-size: 0.8rem; color: var(--muted); padding: 4px 2px 10px; }
.cm-list { list-style: none; overflow-y: auto; flex: 1; margin: 0; padding: 0; }
.cm-item { padding: 9px 2px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.cm-item .cm-author { display: block; font-size: 0.72rem; color: var(--accent); margin-bottom: 2px; font-weight: 600; }
.cm-item.cm-mine .cm-author { color: var(--brand); }
.cm-item .cm-text { color: var(--text); }
/* (The "review" chip on reviews-as-comments was removed 2026-07-13 — reviews render as plain
   comments, distinguished only by their varied avatar + content, per user polish.) */
.cm-seeded-note { margin: 8px 2px 0; font-size: 0.7rem; color: var(--muted); line-height: 1.4; }
.cm-empty { padding: 18px 2px; color: var(--muted); font-size: 0.85rem; text-align: center; }
.cm-form { display: flex; gap: 8px; padding-top: 10px; }
.cm-input {
  flex: 1; background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text); border-radius: 10px; padding: 9px 12px; font-size: 0.9rem;
}
.cm-input:focus { outline: none; border-color: var(--accent); }
.cm-post {
  background: var(--accent); color: var(--bg); border: none; border-radius: 10px;
  padding: 0 16px; font-weight: 600; font-size: 0.85rem; cursor: pointer;
}
/* Comment footer: like button + owner actions on the same row. */
.cm-footer { display: flex; align-items: center; gap: 6px; margin-top: 5px; }
.cm-like-btn {
  display: inline-flex; align-items: center; gap: 3px;
  background: none; border: none; padding: 2px 4px; border-radius: 6px;
  color: var(--muted); font-size: 0.75rem; cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.cm-like-btn:hover, .cm-like-btn:focus-visible { color: var(--accent); background: var(--surface-2); outline: none; }
.cm-like-btn.is-liked { color: var(--accent); }
.cm-like-ico { display: flex; align-items: center; }
.cm-like-count:empty { display: none; }
/* Push owner actions to the right. */
.cm-footer .cm-actions { margin-left: auto; display: inline-flex; gap: 2px; }
/* Owner Edit/Delete — ghost buttons matching .cm-like-btn (were unstyled browser
   defaults: gray boxes that clashed with the styled like button). */
.cm-edit-btn, .cm-del-btn, .cm-report-btn, .cm-block-btn {
  background: none; border: none; cursor: pointer;
  font-size: 0.72rem; font-weight: 500; line-height: 1;
  color: var(--muted); padding: 3px 7px; border-radius: 6px;
  transition: color 0.15s ease, background 0.15s ease;
}
.cm-edit-btn:hover, .cm-edit-btn:focus-visible { color: var(--text); background: var(--surface-2); outline: none; }
.cm-del-btn:hover, .cm-del-btn:focus-visible { color: var(--danger, #e5484d); background: var(--surface-2); outline: none; }
.cm-report-btn:hover, .cm-report-btn:focus-visible { color: var(--danger, #e5484d); background: var(--surface-2); outline: none; }
.cm-block-btn:hover, .cm-block-btn:focus-visible { color: var(--text); background: var(--surface-2); outline: none; }
.cm-report-btn[disabled] { color: var(--muted); opacity: 0.6; cursor: default; }
/* Inline edit mode: Save (primary) + Cancel (ghost). */
.cm-edit-save, .cm-edit-cancel {
  font-size: 0.72rem; font-weight: 600; line-height: 1; cursor: pointer;
  padding: 5px 11px; border-radius: 6px; border: none;
  transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.cm-edit-save { background: var(--brand, #7a3eea); color: #fff; }
.cm-edit-save:hover, .cm-edit-save:focus-visible { opacity: 0.9; outline: none; }
.cm-edit-cancel { background: var(--surface-2); color: var(--muted); }
.cm-edit-cancel:hover, .cm-edit-cancel:focus-visible { color: var(--text); outline: none; }

/* Episode-level discussion section (paragraph_index = -1 channel, ep-comments.js).
   Reuses .cm-* item/form/like styling; this wrapper just gives it end-of-chapter
   block layout under the reader nav. Separate from the per-paragraph bubble sheet. */
.ep-comments {
  max-width: var(--max-width); margin: 0 auto;
  padding: 1.4rem 1.1rem 3rem;
  border-top: 1px solid var(--border);
}
.ep-comments-head {
  font-size: 0.9rem; font-weight: 600; color: var(--text);
  padding: 0 2px 12px;
}
.ep-comments .cm-list { max-height: none; overflow: visible; }

/* "View N previous comments…" expander (Inkitt parity) — collapses older comments. */
.ep-comments .cm-more {
  background: none; border: none; cursor: pointer;
  color: var(--accent); font-size: 0.82rem; font-weight: 600;
  padding: 4px 2px 10px; text-align: left;
}
.ep-comments .cm-more:hover, .ep-comments .cm-more:focus-visible { text-decoration: underline; outline: none; }

/* Avatar-initialled comment rows: chip + stacked author/text/meta (Inkitt layout). */
.ep-comments .cm-item { display: flex; gap: 10px; align-items: flex-start; }
.ep-comments .cm-avatar {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  /* Background is set inline per-commenter (js/ep-comments.js#avatarColor) for VARIED colours;
     this is the fallback. White initial reads on every palette colour. */
  background: color-mix(in srgb, var(--accent) 18%, var(--surface));
  color: #fff; font-size: 0.82rem; font-weight: 700; line-height: 1;
  text-transform: uppercase;
}
.ep-comments .cm-main { flex: 1; min-width: 0; }
/* Meta row: "Xd · Like · Reply" — age + like + reply share one line. */
.ep-comments .cm-age { font-size: 0.72rem; color: var(--muted); }
.ep-comments .cm-age::after { content: "·"; margin: 0 2px 0 6px; color: var(--muted); }
.cm-reply-btn {
  background: none; border: none; cursor: pointer;
  font-size: 0.72rem; font-weight: 600; line-height: 1;
  color: var(--muted); padding: 2px 6px; border-radius: 6px;
  transition: color 0.15s ease, background 0.15s ease;
}
.cm-reply-btn:hover, .cm-reply-btn:focus-visible { color: var(--accent); background: var(--surface-2); outline: none; }

/* I022/T213 — reader read-time + episode-list parts meta */
.reader-readtime { font-size: 0.66rem; color: var(--muted); margin-top: 1px; }
.ep-list-meta { font-size: 0.72rem; color: var(--muted); padding: 0 1.25rem 0.5rem; max-width: var(--max-width); margin: -0.5rem auto 0.5rem; }

/* ════════════════════════════════════════════════════════════════════════════
   PRIMARY NAV = TOP HEADER ONLY (all viewports). ORA wordmark (left, → Discover) ·
   optional center links (.ora-topnav, empty today) · Search + auth cluster (right).
   The mobile bottom tab bar (.ora-bottomtab) was REMOVED 2026-07-13 — web nav is
   header-only; the Discover/Library/Profile bottom tab is app-only. The base .topbar
   header carries navigation at every width; Library/Profile live in the avatar dropdown.
   Active = var(--accent), inactive = var(--muted). Faint separators use --muted.
   ════════════════════════════════════════════════════════════════════════════ */

/* Shared tab-item look (desktop center-nav scaffold; empty by default). */
.ora-tab-item {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.2px;
  padding: 0.35rem 0.6rem;
  border-radius: 999px;
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}
.ora-tab-item:hover { text-decoration: none; color: var(--accent); background: var(--surface); }
.ora-tab-item.is-active { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.ora-tab-icon { display: inline-flex; line-height: 0; }
.ora-tab-icon svg { width: 20px; height: 20px; }
.ora-tab-label { font-size: 0.9rem; line-height: 1; }

/* ── Desktop center nav (injected into the header) — hidden on mobile ── */
.ora-topnav {
  display: none;               /* mobile default → hidden (bottom tab takes over) */
  align-items: center;
  gap: 0.35rem;
  margin-left: auto;
  margin-right: 0.75rem;
}
.reader-topbar .ora-topnav { margin-right: 0; }

/* ── Header SEARCH (Inkitt pattern) — a magnifier BUTTON that expands an in-header
   search FIELD inline. Shown in BOTH viewports: on desktop the field springs open in
   the header; on mobile the button deep-links to index.html#search (the catalog page
   opens the field there). Themed via CSS vars so light/sepia/dark all read cleanly. ── */
.ora-search {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
  margin-right: 0.5rem;
  min-width: 0;         /* allow the open field to shrink instead of pushing right-cluster siblings off-screen (search__open_anon_topbar_fit) */
  position: relative;   /* anchor for the fallback in-header results dropdown */
}
.ora-search-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2, var(--surface));
  white-space: nowrap;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.ora-search-btn:hover { color: var(--accent); border-color: var(--accent); }
.ora-search.is-open .ora-search-btn { color: var(--accent); border-color: var(--accent); }
.ora-search-btn-icon { display: inline-flex; line-height: 0; }
.ora-search-btn-icon svg { width: 18px; height: 18px; }
/* Once the field is open, collapse the button to just the magnifier. */
.ora-search.is-open .ora-search-btn-label { display: none; }
/* On phones the label is dropped to save header width (button = icon only). */
@media (max-width: 767px) { .ora-search-btn-label { display: none; } }

.ora-search-field {
  width: 0;
  min-width: 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.9rem;
  opacity: 0;
  pointer-events: none;
  transition: width 0.22s ease, opacity 0.18s ease, padding 0.22s ease;
}
.ora-search.is-open .ora-search-field {
  /* Shrink-to-fit: take the space the right-cluster siblings (coin/avatar/sign-in)
     leave, never a fixed width that forces the header past the viewport. */
  flex: 1 1 auto;
  width: auto;
  min-width: 2.5rem;
  max-width: min(300px, 46vw);
  padding: 0.45rem 0.85rem;
  border-color: var(--accent);
  opacity: 1;
  pointer-events: auto;
}
.ora-search-field::placeholder { color: var(--muted); }
.ora-search-field:focus { outline: none; border-color: var(--accent); }

/* Fallback in-header search results dropdown — shown on non-catalog shells (reader,
   library, profile, coins, …) where there is no page-local ORA_SEARCH rail filter.
   Anchored under the expanded field; scrolls if the match list is long. */
.ora-search-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: min(360px, 92vw);
  max-height: min(60vh, 420px);
  overflow-y: auto;
  z-index: 1200;
  padding: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
}
.ora-search-panel.hidden { display: none; }
.ora-search-empty {
  padding: 0.75rem 0.6rem;
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}

@media (min-width: 768px) {
  .ora-topnav { display: flex; }
}

/* (The mobile bottom tab bar .ora-bottomtab was removed 2026-07-13 — web nav is
   header-only; the Discover/Library/Profile bottom tab is app-only.) */

/* ════════════════════════════════════════════════════════════════════════════
   Site footer — appended to <body>. 3-column layout (brand · Explore · Company)
   over a bottom copyright bar. Single-column stack on mobile → 3-col ≥768px.
   Faint text uses var(--muted) so it stays readable in light/sepia/dark.
   ════════════════════════════════════════════════════════════════════════════ */

.ora-footer {
  margin-top: 3rem;
  border-top: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.ora-footer__inner {
  max-width: 1160px;
  margin: 0 auto;
  padding: 2.25rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom, 0px));
}
.ora-footer__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.75rem 2.5rem;
}
.ora-footer__logo {
  font-weight: 800;
  font-size: 1.25rem;
  letter-spacing: 0.12em;
  color: var(--accent);
  margin-bottom: 0.6rem;
}
.ora-footer__about {
  font-size: 0.85rem;
  color: var(--muted);
  line-height: 1.6;
  max-width: 24rem;
}
.ora-footer__col-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text);
  margin: 0 0 0.75rem;
}
.ora-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.ora-footer__links a {
  font-size: 0.85rem;
  color: var(--muted);
  text-decoration: none;
}
.ora-footer__links a:hover,
.ora-footer__links a:focus-visible { color: var(--accent); }
.ora-footer__bar {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.ora-footer__copy { font-size: 0.78rem; color: var(--muted); }

/* Desktop: brand column wider, then the two link columns. */
@media (min-width: 768px) {
  .ora-footer__cols {
    grid-template-columns: 2fr 1fr 1fr;
    gap: 2.5rem;
  }
}

/* ════════════════════════════════════════════════════════════════════════════
   Profile page (profile.html) — mirrors SettingsScreen.tsx sections.
   Card style: bg var(--surface), radius 14, border var(--border) (hairline).
   ════════════════════════════════════════════════════════════════════════════ */

.profile-page {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.25rem 1rem 2rem;
}

.profile-page-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text);
  text-align: center;
  padding: 0.75rem 1rem 1rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.25rem;
}

.profile-section-head {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0.25rem 0.4rem;
}

.profile-card {
  background: var(--surface);
  border-radius: 14px;
  border: 1px solid var(--border);
  overflow: hidden;
  margin-bottom: 1.5rem;
}

.profile-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--border);
  min-height: 48px;
}
.profile-row:last-child { border-bottom: none; }

.profile-row-label {
  font-size: 0.875rem;
  color: var(--text);
}
.profile-row-label.destructive { color: var(--danger); }

.profile-row-value {
  font-size: 0.875rem;
  color: var(--muted);
  text-align: right;
  flex-shrink: 1;
  max-width: 55%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-row-chevron {
  color: var(--muted);
  font-size: 1.1rem;
  margin-left: 0.5rem;
  flex-shrink: 0;
}

/* Tappable rows */
button.profile-row {
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  cursor: pointer;
  text-align: left;
}
button.profile-row:last-child { border-bottom: none; }
button.profile-row:hover { background: rgba(255,255,255,0.03); }

a.profile-row {
  color: inherit;
  text-decoration: none;
}
a.profile-row:hover { background: rgba(255,255,255,0.03); text-decoration: none; }

/* Reading pref sub-controls inside the card */
.profile-pref-block {
  padding: 0.65rem 1rem 0.75rem;
  border-bottom: 1px solid var(--border);
}
.profile-pref-block:last-child { border-bottom: none; }
.profile-pref-label {
  font-size: 0.75rem;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.profile-seg-row {
  display: flex;
  gap: 8px;
}
.profile-seg-btn {
  flex: 1;
  padding: 0.6rem 0;
  border-radius: 8px;
  border: 1px solid var(--surface-2);
  background: transparent;
  color: var(--muted);
  font-size: 0.8rem;
  cursor: pointer;
  text-align: center;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.profile-seg-btn.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  font-weight: 600;
}

/* Toggle switch */
.profile-toggle {
  position: relative;
  width: 44px;
  height: 26px;
  flex-shrink: 0;
}
.profile-toggle input { opacity: 0; width: 0; height: 0; position: absolute; }
.profile-toggle-track {
  position: absolute;
  inset: 0;
  border-radius: 13px;
  background: var(--surface-2);
  transition: background 0.2s;
  cursor: pointer;
}
.profile-toggle input:checked + .profile-toggle-track { background: var(--brand); }
.profile-toggle-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.2s;
}
.profile-toggle input:checked + .profile-toggle-track::after { transform: translateX(18px); }

/* ════════════════════════════════════════════════════════════════════════════
   Library page (library.html) — mirrors LibraryScreen.tsx sections.
   ════════════════════════════════════════════════════════════════════════════ */

.library-page {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.25rem 1rem 2rem;
}

.library-page-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text);
  padding: 0.75rem 0.25rem 1rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.25rem;
}

.library-section { margin-bottom: 1.75rem; }

.library-section-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.6rem;
}

/* Bookmark cards — mirrors LibraryScreen bookmarkCard */
.bookmark-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border-radius: 12px;
  padding: 12px;
  margin-bottom: 10px;
  text-decoration: none;
  color: inherit;
  border: 1px solid var(--border);
  transition: border-color 0.15s;
}
.bookmark-card:hover { border-color: var(--accent); text-decoration: none; }

.bookmark-cover {
  width: 56px;
  height: 75px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}
.bookmark-cover-initial {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--accent);
}
.bookmark-info { flex: 1; min-width: 0; }
.bookmark-ip-title {
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bookmark-ep {
  font-size: 0.75rem;
  color: var(--text);
  margin-bottom: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bookmark-progress-track {
  height: 3px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}
.bookmark-progress-fill {
  height: 100%;
  background: var(--brand);
  border-radius: 2px;
}
.bookmark-progress-label {
  font-size: 0.625rem;
  color: var(--muted);
  margin-top: 4px;
}
.bookmark-state-label {
  font-size: 0.625rem;
  color: var(--accent);
  margin-top: 4px;
}
/* M003/I027 — compact FREE wait-unlock countdown chip on Library continue-reading cards.
   Reuses the shared .live-countdown vocabulary (same render helper as the reader top bar),
   sized down to sit under the episode line. Hidden until an armed timer mounts (only shows
   when the IP's free unlock is not ready + next_free_at is in the future). */
.lib-card-freetimer { margin-top: 6px; }
.lib-card-freetimer[hidden] { display: none; }
.lib-card-freetimer:empty { display: none; }
.lib-card-freetimer .live-countdown { padding: 0.16rem 0.44rem; gap: 0.3rem; }
.lib-card-freetimer .live-countdown__label { font-size: 0.6rem; }
.lib-card-freetimer .live-countdown__seg b { font-size: 0.72rem; }
.lib-card-freetimer .live-countdown__seg i { font-size: 0.55rem; }

/* Episode rows (subscription / unlocked) */
.library-ep-row {
  display: flex;
  align-items: center;
  background: var(--surface);
  border-radius: 12px;
  padding: 14px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s;
}
.library-ep-row:hover { border-color: var(--accent); text-decoration: none; }
.library-ep-row-text { flex: 1; }
.library-ep-num { font-size: 0.625rem; color: var(--muted); letter-spacing: 1px; text-transform: uppercase; }
.library-ep-title { font-size: 0.875rem; font-weight: 600; color: var(--text); margin-top: 2px; }
.library-ep-chevron { color: var(--muted); font-size: 1.375rem; margin-left: 8px; }

/* Empty card inside library sections */
.library-empty-card {
  background: var(--surface);
  border-radius: 12px;
  padding: 1.25rem 1rem;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}
.library-empty-card a { color: var(--accent); }

/* Full-page empty state */
.library-empty-state {
  text-align: center;
  padding: 4rem 1.5rem;
}
.library-empty-icon { font-size: 3rem; margin-bottom: 1rem; }
.library-empty-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 0.4rem;
}
.library-empty-subtitle {
  font-size: 0.8125rem;
  color: var(--muted);
  line-height: 1.55;
}
.library-empty-cta {
  display: inline-block;
  margin-top: 1.5rem;
  background: var(--brand);
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  padding: 0.75rem 1.5rem;
  border-radius: 22px;
  text-decoration: none;
}
.library-empty-cta:hover { text-decoration: none; filter: brightness(1.08); }

/* For You rail */
.library-foryou-rail {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 4px;
}
.library-foryou-rail::-webkit-scrollbar { display: none; }
.library-foryou-card {
  flex: 0 0 120px;
  text-decoration: none;
  color: inherit;
}
.library-foryou-cover {
  width: 120px;
  height: 160px;
  border-radius: 8px;
  object-fit: cover;
  background: var(--border);
  display: block;
  margin-bottom: 8px;
}
.library-foryou-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}
.library-foryou-initial { font-size: 2.25rem; font-weight: 700; color: var(--accent); }
.library-foryou-title {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.library-foryou-author { font-size: 0.625rem; color: var(--muted); }
.library-foryou-reason { font-size: 0.625rem; color: var(--accent); margin-top: 4px; }

/* ===========================================================================
   I025 ENGAGEMENT FEATURES — streak badge (F1), social-proof counts (F2),
   rankings sort (F3). Dark-theme tokens reused; matches card/chip styles above.
   =========================================================================== */

/* ── Topbar right cluster (streak badge + sign-out) ── */
.topbar-right { display: flex; align-items: center; gap: 0.75rem; }
.topbar-right { min-width: 0; }  /* allow the cluster to shrink instead of pushing the bar wide */

/* ── Narrow phones: the SIGNED-IN header right cluster (search · coin · wait-timer · streak ·
   avatar) overflowed the bar — the wait-timer chip alone is ~160px ("Free in 16h 19m 09s").
   The header adapts by USER STATE (anon=logo·search·avatar; signed-in adds coin·timer·streak)
   but had no NARROW-width adaptation → User-State × Viewport gap. Compact per width so BOTH
   states fit on one row with ZERO overflow: drop the timer's "Free in" label + seconds (full
   detail stays in the tap dropdown), tighten gaps, then at the very narrowest shrink chips +
   show hours only. Applies to every page's shared header (home/library/profile/coins/reader). */
/* Home header's decorative tagline ("Romantasy made for you.") crowds the right cluster on
   phones — it's the single biggest space hog (~120px) and pure decoration. Drop it on narrow
   so the signed-in cluster (search·coin·wait-timer·streak·avatar) all fits with room. */
@media (max-width: 600px) {
  .topbar .subtitle { display: none; }
}
@media (max-width: 480px) {
  .topbar { padding-left: 0.7rem; padding-right: 0.7rem; }  /* was ~20px each — reclaim ~18px for the cluster */
  .topbar-right { gap: 0.3rem; }
  .header-timer { padding: 0.24rem 0.4rem; }
  .header-timer .live-countdown { gap: 0.14rem; }
  .header-timer .live-countdown__label,
  .header-timer .live-countdown__seg--sec { display: none; }  /* "16h 19m" instead of "Free in 16h 19m 09s" */
  /* Subscriber badge: drop the "· until <date>" (~110px) → just "👑 Unlimited" on phones; the
     full expiry stays on the Profile → Subscription row. Without this the ~200px badge alone
     blew the header past the viewport for Unlimited users. */
  .header-coin--sub { max-width: none; }
  .header-coin__until { display: none; }
}
@media (max-width: 400px) {
  .header-timer .live-countdown__seg:not(:first-child) { display: none; }  /* hours only: "16h" */
  .header-coin, .streak-badge { padding: 0.28rem 0.45rem; }
  .topbar-right { gap: 0.25rem; }
}

/* ── FEATURE 1 — streak badge (compact, in the signed-in header) ── */
.streak-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  line-height: 1;
}
.streak-badge:hover { border-color: var(--accent); }
.streak-badge__flame { font-size: 0.9rem; }
.streak-badge__count { color: var(--accent); }

/* ── (b) GLOBAL coin balance pill — common header, signed-in only (js/header-extras.js). The
   ONE canonical coin element; sits left of the streak badge in every page's right cluster. ── */
.header-coin {
  display: inline-flex;
  align-items: center;
  gap: 0.24rem;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
}
.header-coin__icon { font-size: 0.9rem; }
.header-coin__balance { font-variant-numeric: tabular-nums; color: var(--accent); }

/* The pill is a BUTTON now (js/header-extras.js): 🪙 N → coins surface, or
   "Unlimited · until <date>" → billing portal. */
.header-coin { cursor: pointer; }
.header-coin:hover { border-color: var(--accent); }
.header-coin.hidden { cursor: default; }
/* Subscriber state — accent-tinted "Unlimited · until <date>" badge (crown + date). */
.header-coin--sub {
  cursor: pointer;
  max-width: 12.5rem;
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
}
.header-coin--sub .header-coin__balance {
  color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Skeleton placeholders (coin + streak) — fixed-size shimmer chips that RESERVE the
   cluster's width/height so the real values arrive with NO layout shift (signed-in only,
   js/header-extras.js primePaint / js/streak.js prime). Matches the light-default theme. */
.header-coin--skel, .streak-badge--skel {
  cursor: default;
  border-color: var(--border);
  background: linear-gradient(100deg,
    var(--surface) 30%,
    color-mix(in srgb, var(--accent) 12%, var(--surface)) 50%,
    var(--surface) 70%);
  background-size: 200% 100%;
  animation: ora-hdr-shimmer 1.15s ease-in-out infinite;
}
.header-coin--skel { width: 3.4rem; height: 1.85rem; }
.streak-badge--skel { display: inline-flex; width: 2.9rem; height: 1.85rem; }
@keyframes ora-hdr-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .header-coin--skel, .streak-badge--skel { animation: none; }
}

/* ── (c)/(d) GLOBAL armed wait-timer indicator + dropdown — common header, signed-in only
   (js/header-extras.js). Compact "Free in Hh Mm Ss" chip (soonest timer); click → a small
   themed dropdown listing each armed wait (IP + next locked chapter + link). ── */
.header-timer-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.header-timer {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  padding: 0.24rem 0.5rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  color: var(--text);
  cursor: pointer;
  line-height: 1;
}
.header-timer:hover { border-color: var(--accent); }
.header-timer .live-countdown {
  padding: 0; border: 0; background: transparent; gap: 0.28rem;
}
.header-timer__caret { font-size: 0.7rem; color: var(--muted); }
.header-timer-dropdown {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 70;
  min-width: 15rem;
  max-width: 84vw;
  padding: 0.35rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}
/* On phones the trigger sits near the right edge, so a right:0-anchored 15rem panel ran OFF the
   LEFT of the viewport (user-reported skew 2026-07-13 [A]: dropdown left edge at ~-43px). Anchor
   it to the VIEWPORT instead (fixed, left+right margins) so it spans the width and can never
   overflow either edge, regardless of header width / scroll. Desktop keeps the trigger-anchored
   absolute panel (plenty of room there). */
@media (max-width: 560px) {
  .header-timer-dropdown {
    position: fixed;
    top: calc(3.1rem + env(safe-area-inset-top, 0px));
    left: 0.6rem;
    right: 0.6rem;
    min-width: 0;
    max-width: none;
  }
}
.header-timer-dropdown__head {
  padding: 0.35rem 0.5rem 0.4rem;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.header-timer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem;
  border-radius: 9px;
  text-decoration: none;
  color: var(--text);
}
.header-timer-row:hover { background: var(--surface-2); }
.header-timer-row__main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.header-timer-row__title { font-size: 0.85rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.header-timer-row__chap { font-size: 0.72rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.header-timer-row__count { flex-shrink: 0; }
.header-timer-row__count .live-countdown { padding: 0.16rem 0.44rem; gap: 0.3rem; }
.header-timer-row__count .live-countdown__label { font-size: 0.58rem; }
.header-timer-row__count .live-countdown__seg b { font-size: 0.72rem; }
.header-timer-row__count .live-countdown__seg i { font-size: 0.55rem; }

/* Streak popover (tap-to-expand from the badge) */
/* Daily check-in reward toast — proactively surfaces the "+N coins" grant once per session
   (user-directed 2026-07-13) so the credit is seen without tapping the streak badge. */
.streak-toast {
  position: fixed;
  left: 50%;
  bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
  transform: translate(-50%, 1rem);
  z-index: 1200;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  max-width: calc(100vw - 2rem);
  background: var(--surface-2, #1a1a22);
  color: var(--text, #f4f4f6);
  border: 1px solid var(--brand, #7a3eea);
  border-radius: 999px;
  padding: 0.6rem 1rem;
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.28s ease, transform 0.28s ease;
  pointer-events: none;
}
.streak-toast.is-open { opacity: 1; transform: translate(-50%, 0); }
.streak-toast__flame { color: var(--brand, #7a3eea); display: inline-flex; }
.streak-toast__flame svg { width: 16px; height: 16px; }
@media (prefers-reduced-motion: reduce) {
  .streak-toast { transition: opacity 0.28s ease; transform: translate(-50%, 0); }
}

.streak-pop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 4.5rem;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}
.streak-pop.is-open { opacity: 1; pointer-events: auto; }
.streak-pop__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.55); }
.streak-pop__sheet {
  position: relative;
  z-index: 1;
  width: min(340px, calc(100vw - 2.5rem));
  background: var(--surface-2);
  border: 1px solid var(--brand);
  border-radius: 16px;
  padding: 1.25rem;
  text-align: center;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.streak-pop__big { font-size: 2.2rem; font-weight: 800; color: var(--text); }
.streak-pop__unit { font-size: 1rem; font-weight: 600; color: var(--muted); margin-left: 0.35rem; }
.streak-pop__sub { font-size: 0.85rem; color: var(--muted); margin-top: 0.35rem; }
.streak-pop__dots { display: flex; gap: 0.4rem; justify-content: center; margin: 0.9rem 0; }
.streak-pop__hint { font-size: 0.8rem; color: var(--muted); }

/* 7-day dots (shared by popover + profile row) */
.streak-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--border);
  display: inline-block;
}
.streak-dot.on { background: var(--accent); }
.streak-dots { display: inline-flex; gap: 0.35rem; }
.profile-row.streak-row-dots { align-items: center; }

/* FEATURE 2 metric-chip CSS removed 2026-06-25 — card metrics dropped (all-remove);
   see docs/company-os/read_app_ui_decisions.md. Re-add if metrics return on Story Detail. */

/* ── FEATURE 3 — rankings sort chips ── */
.sort-row {
  display: flex;
  gap: 0.5rem;
  padding: 0.25rem 1.25rem 0.5rem;
  max-width: var(--max-width);
  margin: 0 auto;
}
.sort-chip {
  flex: 0 0 auto;
  padding: 0.4rem 0.95rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.sort-chip[aria-selected="true"] {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}
@media (min-width: 760px) {
  .sort-row { max-width: 1160px; }
}

/* ════════════════════════════════════════════════════════════════════════════
   Reader reviews (episode.html) — IP-level avg stars + count, review form
   (5-star input + textarea, login-gated), like (heart) toggle, recent list.
   Stars #f5a623; reuses dark-theme card tokens. Matches .ip-detail width.
   ════════════════════════════════════════════════════════════════════════════ */
.ip-reviews {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0.5rem 1.25rem 2rem;
}

.rev-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.75rem;
}

.rev-summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.7rem 0.9rem;
  margin-bottom: 0.9rem;
}
.rev-avg { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.rev-count { font-size: 0.8rem; color: var(--muted); }
.rev-like {
  margin-left: auto;
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.35rem 0.55rem;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.rev-like:hover { border-color: var(--accent); color: var(--accent); }
.rev-like.is-liked, .rev-like[aria-pressed="true"] { color: var(--danger); border-color: var(--danger); }
.rev-like:disabled { opacity: 0.6; cursor: default; }

.rev-form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.85rem 0.9rem;
  margin-bottom: 1.1rem;
}
.rev-star-input { display: flex; gap: 0.2rem; margin-bottom: 0.6rem; }
.rev-star-btn {
  background: none;
  border: none;
  color: #f5a623;
  font-size: 1.5rem;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.rev-star-btn.on { color: #f5a623; }
.rev-textarea {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  padding: 0.6rem 0.75rem;
  resize: vertical;
  margin-bottom: 0.6rem;
}
.rev-textarea:focus { outline: none; border-color: var(--accent); }
.rev-textarea::placeholder { color: var(--muted); }
.rev-submit {
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 10px;
  color: var(--bg);
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0.6rem 1.1rem;
}
.rev-submit:hover { filter: brightness(1.05); }
.rev-submit:disabled { opacity: 0.6; cursor: default; }
.rev-gate-hint { font-size: 0.78rem; color: var(--muted); margin-top: 0.5rem; }
.rev-gate-hint a { color: var(--accent); }

.rev-list { list-style: none; margin: 0; padding: 0; }
.rev-item {
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--border);
}
.rev-item:last-child { border-bottom: none; }
.rev-item-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}
.rev-author { font-size: 0.78rem; font-weight: 600; color: var(--accent); }
.rev-date { font-size: 0.7rem; color: var(--muted); margin-left: auto; }
.rev-body { font-size: 0.9rem; color: var(--text); line-height: 1.55; }
.rev-empty { padding: 1rem 0; color: var(--muted); font-size: 0.85rem; text-align: center; }

/* FTC seeded-review disclosure (shown when any displayed reaction is seeded). */
.rev-disclosure {
  font-size: 0.72rem;
  color: var(--muted);
  font-style: italic;
  margin: -0.4rem 0 0.9rem;
}

/* "Early reader" tag on individual seeded snippets/items (subtle). */
.rev-seed-tag {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.4rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Top-snippet strip — 1–3 highest-signal review excerpts above the full list. */
.rev-snips { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 0.6rem; }
.rev-snip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.7rem 0.85rem;
}
.rev-snip-head { display: flex; align-items: center; gap: 0.45rem; margin-bottom: 0.35rem; }
.rev-snip-author { font-size: 0.74rem; font-weight: 600; color: var(--accent); }
.rev-snip-body { margin: 0; font-size: 0.88rem; color: var(--text); line-height: 1.5; }

/* "Read all N reactions" collapsible full list (secondary to snippets + form). */
.rev-all { margin-top: 0.4rem; }
.rev-all-toggle {
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent);
  padding: 0.4rem 0;
  list-style: none;
}
.rev-all-toggle::-webkit-details-marker { display: none; }
.rev-all[open] .rev-all-toggle { margin-bottom: 0.4rem; }

.library-see-all { display:block; padding:0.6rem 0; font-size:0.85rem; color:var(--accent); text-align:center; }

/* ── Account avatar + dropdown (js/avatar-dropdown.js) — theme-aware ── */
.ora-legacy-auth-hidden { display: none !important; }
.ora-signin-btn { display: inline-flex; align-items: center; gap: 0.4rem;
  background: var(--surface-2, var(--surface)); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px; padding: 0.4rem 0.85rem;
  font-size: 0.85rem; font-weight: 600; cursor: pointer; }
.ora-signin-btn:hover { border-color: var(--accent); color: var(--accent); }
.ora-avatar-wrap { position: relative; display: inline-block; }
.ora-avatar-btn { width: 36px; height: 36px; padding: 0; border: none; border-radius: 50%;
  background: transparent; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
.ora-avatar-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ora-avatar-disc { width: 36px; height: 36px; border-radius: 50%; display: flex;
  align-items: center; justify-content: center; color: #fff; font-weight: 700;
  font-size: 0.9rem; text-transform: uppercase; }
.ora-avatar-img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.ora-avatar-dropdown { position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 170px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 6px; box-shadow: 0 10px 30px rgba(0,0,0,0.35);
  display: flex; flex-direction: column; }
.ora-avatar-dropdown.hidden { display: none; }
.ora-dropdown-item { display: flex; align-items: center; gap: 0.6rem; width: 100%;
  padding: 9px 10px; border: none; background: transparent; cursor: pointer; color: var(--text);
  font-size: 0.9rem; text-decoration: none; border-radius: 8px; text-align: left; }
.ora-dropdown-item:hover, .ora-dropdown-item:focus-visible {
  background: var(--surface-2, rgba(127,127,127,0.12)); color: var(--accent); outline: none; }
.ora-dropdown-item-icon { display: inline-flex; opacity: 0.85; }
.ora-dropdown-signout { border-top: 1px solid var(--border); margin-top: 4px; }

.ip-badge--trending { background: #c2410c; color: #fff; }   /* hot orange — real Trending; white passes 3.0 contrast */
.ip-badge--pick     { background: var(--brand); color: #fff; } /* brand purple — Editor's Pick (curation) */

/* ══════════════════════════════════════════════════════════════════════════════
   Series page (episode.html — Inkitt inline model). EP1 reads INLINE on the series
   page: blurred-cover HERO → left rail + center (title/author + intro + 2×3 meta
   grid) → EP1 full body (.reader-body) → chapter-end block. All colours are theme
   tokens (var(--*)) so light/sepia/dark stay readable (check_theme_contrast).
   ════════════════════════════════════════════════════════════════════════════ */

/* 1. HERO — full-width blurred+darkened cover backdrop with the sharp portrait on top. */
.series-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 260px;
  padding: 2.25rem 1.25rem 1.5rem;
  overflow: hidden;
  /* Themed fallback when the IP has no cover (backdrop image absent). */
  background: linear-gradient(160deg, var(--surface-2), var(--surface));
}
.series-hero .hero-backdrop {
  position: absolute;
  inset: -8%;                 /* bleed so the blur has no hard edges */
  background-size: cover;
  background-position: center;
  filter: blur(26px) saturate(1.1);
  transform: scale(1.12);
  z-index: 0;
}
.series-hero .hero-scrim {
  position: absolute;
  inset: 0;
  /* Darken so the sharp cover + any overlaid text stay legible in every theme. */
  background: linear-gradient(180deg, var(--scrim), rgba(0, 0, 0, 0.55));
  z-index: 1;
}
.series-hero .hero-cover-wrap {
  position: relative;
  z-index: 2;
  width: 180px;
  max-width: 46vw;
  aspect-ratio: 3 / 4;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  border: 1px solid var(--border);
  background: var(--surface);
}
.series-hero .hero-cover-wrap.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
}
.series-hero .hero-cover-wrap.is-empty::after {
  content: 'ORA';
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--muted);
  font-size: 1.1rem;
}
.series-hero .hero-cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* 2+3. Layout — LEFT RAIL + CENTER. Desktop: rail is a sticky sidebar. */
/* Content column sits at --max-width (same as the chapter-end + reviews below, which are separate
   full-width sections), so the EP body lines up with the Next-chapter button + reviews. The LEFT
   RAIL floats in the left gutter on desktop instead of taking a grid track that would shrink the body. */
/* FLUID 3-column grid (rail · center · chapters). The rail + chapters columns SHRINK with
   viewport width (clamp) so the rail stays pinned to the LEFT of the content down to tablet
   widths instead of collapsing to a strip on top of the title. grid-template-areas drive the
   reflow at each breakpoint. */
.series-layout {
  padding: 1.25rem clamp(0.75rem, 2vw, 1.25rem) 0;
  display: grid;
  grid-template-columns: clamp(104px, 13vw, 168px) minmax(0, var(--max-width)) clamp(140px, 16vw, 200px);
  grid-template-areas: "rail center chapters";
  gap: clamp(0.6rem, 1.5vw, 1.25rem);
  align-items: start;
  justify-content: center;
}
.series-center { grid-area: center; min-width: 0; }

/* LEFT RAIL — stays in the left column, fluid width, next to the content. */
.series-rail {
  grid-area: rail;
  justify-self: stretch;
  min-width: 0;
  width: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  position: sticky;
  /* offset below the sticky topbar by its ACTUAL measured height (--reader-topbar-h, set by
     tab-bar.js) + a small gap, so the rail clears the topbar at ANY height (read-time line /
     wrapped title). Falls back to 72px before the measurement lands. */
  top: calc(var(--reader-topbar-h, 72px) + 0.5rem);
}
/* Tablet / mid (≤900px): drop the SECONDARY Chapters box to a full-width strip on top, but
   keep the rail ON THE LEFT beside the content (shrinking proportionally). Rail no longer
   jumps above the title — only the ≤480 tier below fully stacks it. */
@media (max-width: 900px) {
  .series-layout {
    grid-template-columns: clamp(96px, 15vw, 150px) minmax(0, 1fr);
    grid-template-areas: "chapters chapters" "rail center";
  }
  .series-chapters { position: static; width: auto; justify-self: stretch; }
}
.rail-btn {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.6rem 0.7rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  line-height: 1.2;
}
.rail-btn:hover { border-color: var(--accent); }
.rail-btn .rail-btn-ico { display: inline-flex; color: var(--accent); flex-shrink: 0; }
/* ① Like tap feedback — a brief pressed pulse on the heart, GUARANTEED on every click
   (logged-in OR anon), independent of the async like write / auth modal. */
.rail-btn .rail-btn-ico.is-pulsing { animation: ce-heart-pulse 0.4s ease; }
@keyframes ce-heart-pulse {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.35); }
  60%  { transform: scale(0.9); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .rail-btn .rail-btn-ico.is-pulsing { animation: none; }
}
.rail-btn .rail-btn-label { min-width: 0; }
.rail-btn.is-on { background: var(--accent); border-color: var(--accent); color: #ffffff; font-weight: 700; }
.rail-btn.is-on .rail-btn-ico { color: #ffffff; }
.rail-btn--primary {
  background: var(--brand);
  border-color: var(--brand);
  color: #ffffff;
}
.rail-btn--primary .rail-btn-ico { color: #ffffff; }
.rail-btn--primary:hover { filter: brightness(1.08); border-color: var(--brand); }
.rail-btn:disabled { opacity: 0.6; cursor: default; }

/* CENTER — title / author / intro / meta grid. */
.series-meta { padding: 0 0 0.5rem; }
.series-meta .ip-title {
  font-size: 1.6rem;
  font-weight: 800;
  line-height: 1.2;
  margin: 0 0 0.6rem;
  color: var(--text);
}
.series-meta .ip-author {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--text);
  margin-bottom: 0.9rem;
}
.series-meta .ip-author-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--brand);
  color: #ffffff;
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
}
.series-meta .ip-author-name { font-weight: 600; font-size: 0.9rem; color: var(--accent); }

/* Intro — the synopsis rendered as a short series intro (the 'Summary' slot). */
.series-meta .ip-intro { margin: 0 0 1rem; }
.series-meta .ip-intro-text {
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--text);
  margin: 0 0 0.35rem;
}
.series-meta .ip-intro-text.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.series-meta .ip-intro-toggle {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
}

/* META GRID — 2×3: Genre / Status / Rating · Author / Chapters / Age rating. */
.series-meta .meta-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.6rem;
  margin: 0.5rem 0 0.5rem;
}
.series-meta .meta-cell {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 0.55rem 0.65rem;
  min-width: 0;
}
.series-meta .meta-label {
  font-size: 0.62rem;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.2rem;
}
.series-meta .meta-value {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Rating cell: the .meta-value wrapper is overflow:hidden (ellipsis for long title/author),
   which would CLIP the rating NUMBER when stars + number exceed a narrow grid cell (user-reported
   2026-07-13). Guarantee the number is never the thing cut: it flex:0 0 auto (never shrinks); the
   decorative stars flex-shrink instead. max-width:100% keeps the row within the cell. */
.series-meta .meta-rating { display: inline-flex; align-items: center; gap: 0.25rem; max-width: 100%; min-width: 0; }
.series-meta .meta-rating .ora-stars { flex: 0 1 auto; min-width: 0; overflow: hidden; }
.series-meta .meta-rating-num { font-weight: 700; flex: 0 0 auto; }
.series-meta .meta-sub { display: block; font-size: 0.68rem; font-weight: 500; color: var(--muted); }
/* star glyphs in the rating cell inherit the review star colouring. */

/* Trope chips — clickable discovery links under the title/author. Themed like the
   catalog/genre chips; each links to index.html?q=<trope> (a real navigation). */
.series-meta .ip-tropes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1rem;
}
.series-meta .trope-chip-link {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.series-meta .trope-chip-link:hover,
.series-meta .trope-chip-link:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  text-decoration: none;
}

/* Reader reviews (ip_comments incl. the seeded friend reviews) — compact list in the
   center column, with the FTC seeded disclosure under the header. */
.ip-reviews { margin: 1.25rem 0 0.5rem; }
.ip-reviews .reviews-head {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 0.35rem;
}
.ip-reviews .reviews-disclosure {
  font-size: 0.74rem;
  color: var(--muted);
  margin: 0 0 0.75rem;
  font-style: italic;
}
.ip-reviews .reviews-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.ip-reviews .review-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 0.7rem 0.8rem;
}
.ip-reviews .review-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}
.ip-reviews .review-author {
  font-weight: 700;
  font-size: 0.82rem;
  color: var(--accent);
}
.ip-reviews .review-title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 0.25rem;
  line-height: 1.35;
}
.ip-reviews .review-body {
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 0.3rem;
}
.ip-reviews .review-foot {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.ip-reviews .review-date {
  font-size: 0.68rem;
  color: var(--muted);
}
.ip-reviews .review-chapters {
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 600;
}

/* EP1 inline body wrapper. .reader-body already carries the reader typography +
   theme; the series page just gives it a top divider and its own reveal. */
.series-reader { margin-top: 0.5rem; border-top: 1px solid var(--border); }
.series-reader .reader-body { max-width: none; }
.inline-body-fallback { color: var(--muted); text-align: center; padding: 2rem 1rem; }
.inline-body-fallback a { color: var(--accent); }

/* The series page is a scrolling landing page — never a paginated reader. If a reader
   previously chose "Page" mode (a saved pref), reader-prefs.js would try to paginate
   this viewport (fixed height + horizontal transform). Neutralise it here so EP1 always
   flows normally inline (defensive; reader-prefs.js is owned by the reader task). */
.series-reader.reader-viewport.is-paged {
  overflow: visible !important;
  height: auto !important;
}
.series-reader .reader-body.is-paged { transform: none !important; }
.series-reader .reader-pgbtn,
.series-reader .reader-tapzone,
.series-reader .reader-pageind { display: none !important; }

/* ④ EP2+ READER layout — reuses .series-layout (LEFT RAIL + CENTER + right Chapters box)
   so the standalone reader shows the SAME hero + rail + chapters chrome as the EP1 page,
   MINUS the synopsis intro + meta grid (those stay EP1-only). Tighter top padding than the
   EP1 page since the reader has no title/author/meta block above the body — the reading
   column starts right under the hero. The reader body keeps its own centered 620px column. */
.reader-layout { padding-top: 0.5rem; }
.reader-layout .reader-viewport { margin-top: 0; }
/* The reader viewport inside the center column: no extra divider (unlike the EP1 inline
   body which sits under the meta grid). Keeps the reader's own body typography + padding. */
.reader-layout .series-center { min-width: 0; }

/* chapter-end block on the series page shares the same max-width as the layout. */
.series-chapter-end { max-width: var(--max-width); }

/* ② Reader-reviews host — rendered BELOW the EP1 body + chapter-end. Constrained to the
   layout width and centered so the review list lines up with the reading column. */
.series-reviews-host { max-width: var(--max-width); margin: 0 auto; padding: 0 1.25rem; }
.series-reviews-host:empty { display: none; }

/* ── ⑥ "Chapters ▾" box (Inkitt pattern) — shared by the IP-detail right column
   (.series-chapters) and the reader topbar (.reader-chapters). A bordered box showing
   "Chapters ▾" + the current chapter; clicking reveals the full episode list. Themed via
   CSS vars so light/sepia/dark all read correctly (theme-contrast gate). ── */
.series-chapters { grid-area: chapters; justify-self: stretch; align-self: start;
  position: sticky; top: calc(var(--reader-topbar-h, 72px) + 0.5rem); width: 100%; }
.series-chapters.hidden, .reader-chapters.hidden { display: none; }
.cbx-trigger {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.55rem 0.7rem;
  cursor: pointer;
  color: var(--text);
  font: inherit;
}
.cbx-trigger:hover { border-color: var(--accent); }
.cbx-trigger-head {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.cbx-trigger-head::after {
  content: '';
  width: 0; height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.8;
}
.cbx-trigger[aria-expanded="true"] .cbx-trigger-head::after {
  border-top: none;
  border-bottom: 4px solid currentColor;
}
.cbx-trigger-current {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.cbx-panel {
  margin-top: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
  max-height: 60vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 4px 0;
}
.cbx-panel[hidden] { display: none; }
.cbx-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.55rem 0.75rem;
  text-decoration: none;
  color: var(--text);
  line-height: 1.35;
}
.cbx-item:hover, .cbx-item:focus-visible { background: var(--surface-2); outline: none; }
.cbx-item.is-current { background: var(--surface-2); }
.cbx-item.is-current .cbx-item-num { color: var(--accent); font-weight: 700; }
.cbx-item-num { font-size: 0.72rem; color: var(--muted); flex-shrink: 0; white-space: nowrap; }
.cbx-item-title { font-size: 0.85rem; font-weight: 500; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cbx-lock {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  padding: 1px 5px;
  border-radius: 4px;
  flex-shrink: 0;
  white-space: nowrap;
}
.cbx-lock.locked { background: var(--surface-2); color: var(--muted); }
.cbx-lock.free   { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }

/* Reader topbar variant — compact box on the right of the sticky topbar. The panel is
   positioned absolutely so it overlays the body (the topbar is a flex row). */
.reader-chapters { position: relative; flex-shrink: 0; margin-left: auto; max-width: 160px; }
.reader-chapters .cbx-trigger { padding: 0.35rem 0.55rem; border-radius: 8px; }
.reader-chapters .cbx-trigger-current { font-size: 0.75rem; max-width: 120px; }
.reader-chapters .cbx-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 200;
  min-width: 220px;
  max-width: min(320px, calc(100vw - 1.7rem));
}
/* When the reader-chapters box sits before the unlimited/share buttons, keep those to its
   right; margin-left:auto pushes the box + the trailing buttons as a group to the right. */
.reader-chapters + .reader-unlimited, .reader-chapters + .reader-share { margin-left: 0.5rem; }

/* ── Narrow mobile ONLY (≤ 480px): the rail finally stacks to a horizontal scroll strip
   above the center. Tablet / mid widths (481–900px) keep the rail on the LEFT (see the
   ≤900 tier above) — only here, where a left column can't fit, do we collapse it. ── */
@media (max-width: 480px) {
  .series-hero { min-height: 220px; padding: 1.75rem 1rem 1.25rem; }
  .series-hero .hero-cover-wrap { width: 150px; }
  .series-layout {
    /* flex-column so the ⑥ Chapters box can order:-1 above the reading column on the
       narrowest phones — full-width, below the rail, never overlapping the body. */
    display: flex;
    flex-direction: column;
    padding: 1rem 1rem 0;
  }
  .series-chapters { order: -1; width: auto; }
  /* The action rail WRAPS onto multiple rows here (was a nowrap overflow-x:auto strip that
     wasn't width-constrained → its 6 buttons laid out ~780px wide and pushed the whole page
     into horizontal overflow, clipping the last buttons off-screen). Wrapping + a hard
     max-width keeps every button on-screen with ZERO horizontal overflow at any phone width. */
  .series-rail {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    gap: 0.45rem;
    margin-bottom: 0.75rem;
  }
  .rail-btn {
    width: auto;
    flex: 0 1 auto;          /* natural width, may shrink; wraps to the next row when out of room */
    max-width: 100%;
    white-space: nowrap;
    padding: 0.5rem 0.7rem;
  }
  .series-meta .meta-grid { gap: 0.45rem; }
  .series-meta .ip-title { font-size: 1.35rem; }
}

/* Narrow phones (≤ 400px, e.g. 390px target): keep the meta grid 2 columns so the
   labels/values never truncate to unreadable widths. */
@media (max-width: 400px) {
  .series-meta .meta-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ════════════════════════════════════════════════════════════════════════════
   G6 — editorial HERO banner (single spotlighted title above the Trending rail).
   Compact on mobile (side-by-side: small cover + text so it never pushes the rails
   far down); on desktop the cover grows on the left with the text to its right. All
   theme-var colored (light / sepia / dark) — the CTA reuses the proven --brand + #fff
   pill from the continue card (AA in every theme).
   ════════════════════════════════════════════════════════════════════════════ */
[data-ora-hero] { display: block; }
[data-ora-hero].hidden { display: none; }
/* The generic [data-ora-hero]{display:block} reveal above has the SAME specificity as
   .series-hero{display:flex} but comes later, so it was clobbering the reader/episode hero to
   block — killing justify-content:center and left-aligning the cover. Restore flex for the
   series-hero specifically (higher specificity; :not(.hidden) so it never overrides the hide). */
[data-ora-hero].series-hero:not(.hidden) { display: flex; }
.hero {
  display: flex;
  gap: 1rem;
  align-items: center;
  margin: 0 0 1.5rem;
  padding: 1rem;
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
  border: 1px solid var(--border);
  border-radius: 16px;
}
.hero__cover-wrap { flex: 0 0 auto; }
.hero__cover {
  width: 96px;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: 10px;
  display: block;
  border: 1px solid var(--border);
}
.hero__body { flex: 1 1 auto; min-width: 0; }
.hero__kicker {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent);
}
.hero__title {
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1.25;
  color: var(--text);
  margin: 0.25rem 0 0.1rem;
}
.hero__pen {
  font-size: 0.66rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.45rem;
}
.hero__synopsis {
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 0.7rem;
  /* Clamp so the hero stays compact and doesn't push the rails far down. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hero__cta {
  display: inline-block;
  background: var(--brand);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  padding: 0.5rem 1.1rem;
  border-radius: 999px;
}
.hero__cta:hover { filter: brightness(1.08); text-decoration: none; }
@media (min-width: 768px) {
  .hero { gap: 1.5rem; padding: 1.5rem; }
  .hero__cover { width: 150px; }
  .hero__title { font-size: 1.6rem; }
  .hero__pen { font-size: 0.72rem; margin-bottom: 0.6rem; }
  .hero__synopsis { font-size: 0.95rem; -webkit-line-clamp: 4; }
  .hero__cta { font-size: 0.9rem; padding: 0.6rem 1.4rem; }
}

/* ════════════════════════════════════════════════════════════════════════════
   HOME CURATION RAILS (app-like) — replaces the flat catalog grid on index.html.
   Each rail = a heading + a horizontally-scrollable row of cover cards. Grouped by
   sub_genre / trope in index.html. Themed via CSS vars (light/sepia/dark clean).
   ════════════════════════════════════════════════════════════════════════════ */
[data-ora-rails] { display: block; }
.rail { margin: 0 0 1.75rem; }
.rail-head {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 0.75rem;
  letter-spacing: 0.2px;
}
/* Horizontal-scroll row. Cards are fixed-width so the row overflows-x and scrolls
   (touch on mobile, trackpad/scroll on desktop). Scrollbar hidden for a clean rail. */
.rail-scroll {
  display: flex;
  gap: 0.9rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* overflow-x:auto forces overflow-y to 'auto' (CSS spec), so the box clips vertically.
     A card's :hover translateY(-2px) + accent border then gets its TOP clipped. Reserve
     GENEROUS top room with padding (clip headroom = padding-top - 2px translate ≈ 9px, so the
     border is never clipped even with subpixel rounding), and pull the box up with a negative
     margin kept SMALLER than the head→rail gap (~12px) so it never overlaps the rail heading.
     Net: cards shift down ~4px (harmless); the accent hover border is always fully visible. */
  padding-top: 0.7rem;
  margin-top: -0.45rem;
  padding-bottom: 0.35rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.rail-scroll::-webkit-scrollbar { display: none; }
/* Within a rail, cards are fixed-width (override the grid card's block layout). */
.rail-scroll .ip-card {
  flex: 0 0 46vw;
  max-width: 172px;
  scroll-snap-align: start;
  margin-bottom: 0;
}
@media (min-width: 560px) {
  .rail-scroll .ip-card { flex-basis: 176px; max-width: 176px; }
}
@media (min-width: 768px) {
  .rail { margin-bottom: 2.25rem; }
  .rail-head { font-size: 1.2rem; }
  .rail-scroll { gap: 1.1rem; }
  .rail-scroll .ip-card { flex-basis: 210px; max-width: 210px; }
}

/* ── Header SEARCH results (flat list: cover thumb + title + author + genre) ── */
.search-results { display: flex; flex-direction: column; gap: 0.5rem; }
.sr-item {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.6rem 0.7rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.sr-item:hover { border-color: var(--accent); text-decoration: none; }
.sr-thumb {
  flex: 0 0 auto;
  width: 40px;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  display: block;
}
.sr-thumb--empty { background: var(--surface-2); }
.sr-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.sr-title { font-size: 0.92rem; font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-pen { font-size: 0.72rem; color: var(--accent); letter-spacing: 0.5px; text-transform: uppercase; }
.sr-genre { font-size: 0.72rem; color: var(--muted); text-transform: capitalize; }

/* ── Zero-result SEARCH recovery (turn a dead end into a trope pivot) ── */
/* All theme-var colored so it flips cleanly across light / sepia / dark. Chips reuse
   the .trope-chip pill look so a recovery pivot reads as the same control as the row. */
.sr-recover {
  padding: 1.5rem 0.5rem 0.5rem;
  text-align: center;
}
.sr-recover__msg { color: var(--text); font-size: 0.95rem; font-weight: 700; margin: 0; }
.sr-recover__prompt { color: var(--muted); font-size: 0.82rem; margin: 0.6rem 0 0.75rem; }
.sr-recover__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}
.sr-recover__chip {
  flex: 0 0 auto;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.sr-recover__chip:hover,
.sr-recover__chip:focus-visible {
  border-color: var(--accent);
  color: var(--text);
}
