/* Tworks — design system. See docs/UI-DESIGN-PLAN.md for the rationale. */

/* Web fonts are loaded from index.html via a non-blocking <link> (preconnect +
   media=print swap), not a CSS @import — @import here would make styles.css
   itself wait on fonts.googleapis.com before the browser could apply ANY of
   these rules, delaying first paint of the whole app. */

:root {
  --paper: #ede7da;
  --paper-dim: #e1d9c8;
  --paper-raised: #f5f1e6;
  --ink: #22283b;
  --ink-soft: #55597a;
  --jade: #3f6659;
  --jade-bg: #dde8e3;
  --seal: #a8311f;
  --seal-dark: #7d2216;
  --gold: #a9803f;
  --line: rgba(34, 40, 59, 0.16);
  --line-strong: rgba(34, 40, 59, 0.3);
  --shadow: 0 2px 10px rgba(34, 40, 59, 0.08);
  --radius: 3px;

  --font-display: 'Fraunces', 'Noto Serif SC', serif;
  --font-cjk: 'Noto Serif SC', 'Songti SC', serif;
  --font-body: 'Source Serif 4', 'Noto Serif SC', serif;
  --font-ui: 'Inter', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;
}

* { box-sizing: border-box; }

/* html/body base styling, .shell/.topbar/.wordmark/nav.primary-nav, and
   main.view now live in css/base.css + css/components.css (UI overhaul v2)
   — index.html's shell markup was fully replaced in Phase 1, so those
   selectors no longer match anything and were removed rather than left as
   dead CSS. */

.view-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.view-header h1 { font-family: var(--font-ui); font-size: clamp(26px, 3vw, 34px); font-weight: 600; margin: 0; }

/* .eyebrow now lives in css/components.css (UI overhaul v2). */

.view-header-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* FIX 1.9, Epic 2 item 3 — Library header warning banner, shown once
   automated-conversion usage nears (amber) or reaches (red) the daily
   system-wide cap. Left accent bar + tinted background reuses the same
   "not just color" visual language as .chip (an icon too), so the two
   severities stay distinguishable without relying on color alone. */
.usage-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-radius: var(--radius);
  border: 1px solid;
  border-left-width: 4px;
  font-size: 13.5px;
  margin-bottom: 20px;
}
.usage-banner-icon { font-size: 16px; line-height: 1; flex-shrink: 0; }
.usage-banner--nearing { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }
.usage-banner--reached { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

/* FIX 1.9, Epic 2 item 2 — bulk action bar while Library select mode is on. */
.library-select-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 20px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}
.library-select-count { font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-soft); margin-right: auto; }

/* FIX 1.9 addendum, Epic 2 item 4 — non-intrusive real-time progress bar
   for the background auto-conversion pipeline. Empty (zero height, no
   border) when there's nothing to show — #auto-convert-progress is always
   in the DOM so JS can update it in place without a full Library re-render. */
#auto-convert-progress:empty { margin: 0; }
#auto-convert-progress:not(:empty) {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
  font-size: 12.5px;
  color: var(--ink-soft);
}
.auto-convert-progress-bar {
  flex: 1;
  max-width: 280px;
  height: 6px;
  border-radius: 3px;
  background: var(--paper-dim);
  overflow: hidden;
}
.auto-convert-progress-fill {
  height: 100%;
  background: var(--jade);
  transition: width 0.4s ease;
}
.auto-convert-progress-label { font-family: var(--font-mono); }

/* ---------- Buttons ----------
   Base .btn/.btn-primary now live in css/components.css (UI overhaul v2).
   Keep only the modifiers that new system doesn't define. */

.btn-quiet { border-color: transparent; background: none; padding: 6px 8px; }

/* ---------- Library ---------- */

/* Library screen refactor (2026-07-27) — filter bar restacked into three
   rows (Novel Status filter / Sort by / Actions), replacing the old
   single-row layout. The old rules here referenced design tokens
   (--ink-soft/--line-strong/--ink/--seal/--paper) that no longer exist
   post-redesign — this block was effectively unstyled dead CSS before this
   pass; rebuilt on the real tokens components.css/tokens.css use elsewhere. */
/* Filter bar layout fix (2026-07-27, same-day follow-up) — replaces the
   earlier 3-row stacked layout (library-controls--stacked/.filter-row, now
   deleted — unused anywhere else) with a single inline row: Search / +Add
   Content / Card View / List View / Sort By. */
.library-controls--inline { flex-direction: row; align-items: center; flex-wrap: wrap; }
/* Card View/List View + Sort By all push right as one group, Search/+Add
   stay left (2026-07-27 follow-up: view-switch specifically requested
   right-aligned; margin-left:auto goes on the first element of the group,
   not on #sortBy alone, so the view-switch buttons right before it come
   along with it instead of sitting left-aligned on their own). */
.library-controls--inline .library-view-switch { margin-left: auto; }

/* Search popup (2026-07-27, revised to more closely match output/Tìm
   truyện trên Wiki Dịch.html's own search page shape: a plain text box up
   top with no visible label — matching that reference's quick-search box —
   then a divider before a labeled filter section, then a full-width
   primary submit button with Cancel demoted below it). Novel Status is now
   a button that opens the shared small-board popover (.options-popover),
   same pattern as Sort By — no dedicated button-group class needed here
   anymore. */
.modal-divider { border: none; border-top: 1px solid var(--border); margin: var(--space-3) 0; }
/* Search popup layout (2026-07-27 follow-up): Row 1 — Novel Status + Genre
   side by side; Row 2 (.modal-actions, unmodified from the shared class) —
   Search/Clear/Cancel inline instead of stacked. */
.search-filter-row { display: flex; gap: var(--space-4); margin-bottom: var(--space-3); }
.search-filter-row .search-filter-section { flex: 1; margin-bottom: 0; }
.search-filter-row .search-filter-section button { width: 100%; }
.search-filter-section { margin-bottom: var(--space-3); }
.modal-actions--stacked { flex-direction: column; align-items: stretch; gap: 8px; }
.modal-actions--stacked .btn-quiet { text-align: center; }
/* Search input given the reference's own proportions (tall, rounded,
   roomy padding) rather than the default compact .field input — keeps
   Tworks' own color tokens (border/surface/brand) instead of the
   reference's cyan palette, which would clash with every other modal in
   the app (name-edit, edit-metadata, upload-novel all share this system). */
.library-search-modal #lib-search-query {
  height: 44px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-strong);
  padding: 0 16px;
  font-size: 15px;
}
/* Direct request (superseding an earlier true-centered-Clear attempt):
   Search + Clear cluster together on the left (Search flush left, aligned
   with the "All" Novel Status button above it; Clear immediately next to
   it, not centered in the row) — Cancel alone ends flush right, aligned
   with the "Any" Genre button above it. Plain flex-start flow keeps
   Search/Clear adjacent; margin-left:auto on Cancel alone pushes just that
   one button to the end. Not right-aligned like every other modal's
   .modal-actions (shared class, scoped override here only). */
.library-search-modal .modal-actions { justify-content: flex-start; }
.library-search-modal .modal-actions #modal-cancel { margin-left: auto; }

/* Genre picker (2026-07-27 follow-up) — real multi-group checkbox picker
   (GENRE_FACET_GROUPS/GENRE_TAG_GROUPS), reused by the Search popup's
   Genre filter and Edit Metadata's Genre field. Wide/scrollable like
   Upload a Novel's modal — this one has even more content. */
.genre-picker-modal { max-width: 640px; max-height: 88vh; overflow-y: auto; }
.genre-group { margin-bottom: var(--space-3); }
.genre-group-name { font-size: 12px; font-weight: 700; color: var(--text-soft); margin-bottom: 6px; }
.genre-tag-row { display: flex; flex-wrap: wrap; gap: 6px; }
.genre-tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: 12px;
  cursor: pointer;
  background: var(--surface-alt);
}
.genre-tag-chip:has(input:checked) { border-color: var(--brand); color: var(--brand); background: var(--surface); }
.genre-tag-chip input { margin: 0; }
.genre-facets { margin-bottom: var(--space-3); }
.genre-tag-section { border-top: 1px solid var(--border); padding-top: var(--space-3); }
.genre-tag-header { display: flex; gap: 8px; margin-bottom: var(--space-3); }
.genre-tag-header input[type="text"] { flex: 1; }
.genre-tag-groups { max-height: 320px; overflow-y: auto; }

/* Genre chips preview (2026-07-27 follow-up: "split the comma-separated
   string into individual chips") — read-only, shown below whichever
   button opens showGenrePickerModal (Edit Metadata, Upload a Novel). */
.genre-chips-preview { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.genre-chips-preview:empty { margin-top: 0; }
.genre-chip { background: var(--surface-alt); color: var(--text-soft); font-size: 11px; padding: 3px 9px; }

/* Search-by-tag (2026-08-02) — Novel Detail's tag row renders these as real
   <button> elements (see genreChipsHtml's `clickable` option), so they're
   keyboard-reachable and get a native click/Enter activation for free;
   .chip:hover already covers the hover background (components.css). */
.genre-chip.is-clickable { cursor: pointer; font: inherit; }
.genre-chip.is-clickable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Card View / List View toggle — icon-only, no border, right-aligned
   (2026-07-27 follow-up). No background (the old dead rule this replaced
   set one to a now-nonexistent --paper token — kept off deliberately, not
   just by omission); active state now shown by icon color alone since
   there's no border to color. */
.library-view-switch { display: flex; gap: 4px; }
.library-view-switch button {
  font-size: 16px;
  line-height: 1;
  border: none;
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  color: var(--text-soft);
  background: none;
}
.library-view-switch button.active { color: var(--brand); }

/* Individual novel item's new info block (2026-07-27) — Author Name / Novel
   Status / Conversion Status, stacked directly below the title. */
/* Novel Status bottom-alignment (2026-07-27 follow-up) — .card-body/
   .lib-item-info both stretch to fill their parent's height (the grid
   already stretches every .book-card in a row to the tallest sibling's
   height by default), then margin-top:auto on the Novel Status chip pushes
   it (and Conversion Status right after it) down to the card's bottom edge
   regardless of how much room the title/author take above it — so every
   card in the same grid row lines its Novel Status up on the same
   horizontal line. min-height:0 on both flex parents avoids the usual
   nested-flexbox sizing bug where a flex:1 child won't shrink/grow properly
   without it. */
.card-body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.lib-item-info { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; margin-top: 2px; flex: 1; min-height: 0; }
.lib-item-info .author:empty { display: none; }
/* Truncate a long author name the same way the title (.book-card h3)
   already clips to one line — a long name shouldn't wrap and push Novel
   Status/the card height around. `.book-card .lib-item-info .author`
   (3 classes), not `.lib-item-info .author` (2) — components.css's
   `.book-card .author` is also 2 classes and loads after styles.css, so an
   equal-specificity rule here silently lost that tie (same cascade trap
   FIX 1.57 already hit once: styles.css loads first, ties go to whichever
   file loads later). */
.book-card .lib-item-info .author {
  width: 100%;
  /* Direct request: author name black, novel status #6b6e82 (var(--text-faint)
     below). var(--text) (#14161f) is this design system's near-black
     body-text token. */
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* One rule controls every box-model property here on purpose (not split
   across two rules of equal specificity) — an earlier version set
   padding-left:0 in one rule and a later shorthand `padding` in another,
   and the shorthand silently won the cascade and re-added left padding. */
.lib-item-info [class*="novel-status"] {
  margin-top: auto;
  margin-left: 0;
  padding: 5px 9px 5px 0;
  border-left: none;
  white-space: nowrap;
  /* Direct request: #6b6e82 (var(--text-faint)) — overrides pages.css's
     .chip--novel-status-* orange (#fdae31) for this context; this selector
     is higher-specificity so it wins regardless of file load order. */
  color: var(--text-faint);
}

/* Pagination (FIX 1.5, Story 1.2 item 1) — same visual pattern as the novel
   detail page's .toc-pagination. */
.library-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-size: 13px;
  color: var(--ink-soft);
  margin: 20px 0 4px;
}

/* .catalog-meta still used by Learning Hub's file rows (UI overhaul v2 only
   reskinned Library/Trash/Bookshelf's cards, not the whole .catalog-* family
   — Learning Hub gets its own pass below in this same phase). Every other
   .catalog-*/.trash-card-* rule this used to sit next to (card/grid/list/
   table/cover/remove/select) is gone: Trash (the last holdout) now shares
   Library's BookCard/gradient-cover system instead. */
.catalog-meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-soft); margin-bottom: 10px; }

/* ---------- Status chips (always icon + text, never color alone) ----------
   Base .chip now lives in css/components.css (UI overhaul v2); keep only
   the status-color modifiers here. */

.chip--not-sent::before { content: '○'; }
.chip--pending-conversion::before { content: '◐'; }
.chip--partially-converted::before { content: '◑'; }
.chip--fully-converted { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.chip--fully-converted::before { content: '●'; }
/* [Feature B] 'failed' isn't a chapter.conversionStatus value — it's the
   Auto-Convert picker's derived status for a chapter whose most recent
   auto-convert job errored out (chipHtml()'s own comment). Background/color
   already come from components.css's .chip--failed; just the border and
   icon are added here, matching every other status chip's own pattern. */
.chip--failed { border-color: var(--danger); }
.chip--failed::before { content: '✕'; }

/* ---------- Reading view: two facing pages + a book-spine gutter ---------- */

/* var(--space-3) (12px), not the old 20px magic number — matches the gap
   .reading-view .pagination already uses below the chapter-table, so the
   gap above the table (chapter-list-head to the top pagination copy) and
   the gap below it (table to the bottom pagination copy) read as the same
   rhythm instead of two unrelated values. */
/* Same 760px cap as .nd-hero/.synopsis-block: without it the TOC stretches
   to the page's full .container width on desktop instead of matching the
   reading column's proportions. Used to come from the now-deleted
   .novel-tab-content wrapper (removed in f78fb49 along with the old
   Chapters/Vocab/Notes/Reviews tabs); now provided by the shared
   .nd-col-cap class (pages.css), added alongside .reading-view in app.js. */
.reading-view { display: flex; flex-direction: column; gap: var(--space-3); }

/* ---------- Novel title block: Translated Title + original author name,
   shown exactly as entered (FIX 1.9 addendum, Epic 1 items 3/6) ---------- */

/* The translated title's font-size is held in a custom property so the
   author name below it can be set to exactly 1/3 of it via calc() — doing
   this instead of hand-picking a smaller font-size keeps the ratio exact
   even as --novel-title-size changes at wider viewports (same clamp() the
   plain .view-header h1 uses elsewhere). */
.novel-title-block {
  --novel-title-size: clamp(26px, 3vw, 34px);
}
.novel-title-translated {
  font-family: var(--font-display);
  font-size: var(--novel-title-size);
  font-weight: 600;
  margin: 0;
}
.novel-title-author {
  font-family: var(--font-ui);
  font-size: calc(var(--novel-title-size) / 3);
  color: var(--ink-soft);
  /* FIX 1.14, NG3 — more visual separation from the title above; the 1/3
     size ratio itself (FIX 1.9 addendum) is unchanged. */
  margin: 10px 0 0;
}

/* ---------- Novel header: cover + synopsis (Story 1.6) ---------- */

.novel-header { display: flex; gap: 18px; align-items: flex-start; margin-bottom: 4px; flex-wrap: wrap; }

/* FIX 1.7, Story 1.6 Do2: 200x280, matching the Library card view's
   .catalog-cover-lg — previously 78x104, a different size than every other
   "full size" cover shown elsewhere in the app. */
.novel-cover {
  width: 200px;
  height: 280px;
  max-width: 100%;
  flex-shrink: 0;
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.novel-cover img { width: 100%; height: 100%; object-fit: cover; }

.novel-info { flex: 1; min-width: 220px; }
.novel-original-title { font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft); margin-bottom: 6px; }

.novel-edit-panel {
  margin: 10px 0 18px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  max-width: 480px;
}
.novel-edit-panel textarea, .novel-edit-panel input[type="text"] {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--paper);
  font-family: var(--font-ui);
  font-size: 13px;
  margin-top: 4px;
}
.novel-edit-actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; }

/* ---------- FIX 1.11, Novel Details renewal ---------- */

.novel-genre-line { font-family: var(--font-ui); font-size: 13px; color: var(--ink-soft); margin: 4px 0 0; }

/* Header progress bar — same visual language as the Library's
   .auto-convert-progress-bar (thin track, jade fill, mono label). */
.novel-progress-block { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; font-size: 12.5px; color: var(--ink-soft); }
.novel-progress-bar { flex: 1; max-width: 320px; height: 6px; border-radius: 3px; background: var(--paper-dim); overflow: hidden; }
.novel-progress-fill { height: 100%; background: var(--jade); transition: width 0.4s ease; }
.novel-progress-label { font-family: var(--font-mono); white-space: nowrap; }

.novel-hero-row { display: flex; align-items: flex-start; gap: 18px; flex-wrap: wrap; margin-bottom: 18px; }
.btn-hero { font-size: 15px; padding: 12px 20px; }
.hero-subtext { font-size: 12px; color: var(--ink-soft); margin-top: 4px; }
.engine-select-label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--ink-soft);
}
.engine-select-label select {
  font-family: var(--font-ui);
  font-size: 13px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 6px 8px;
  color: var(--ink);
  max-width: 340px;
}

.glossary-panel {
  margin: -4px 0 18px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 13px;
}
.glossary-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.glossary-list li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.glossary-raw { font-family: var(--font-cjk); }
.glossary-name { font-weight: 600; }
.glossary-hint { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); }
.empty-hint { color: var(--ink-soft); font-size: 13px; font-style: italic; }

.toc-search {
  font-family: var(--font-ui);
  font-size: 13px;
  border-radius: var(--radius);
  padding: 4px 8px;
  color: var(--ink);
  flex: 1;
  min-width: 140px;
}
.toc-empty { color: var(--ink-soft); font-size: 13px; font-style: italic; padding: 8px 12px; }

/* Read-status chips (a separate axis from conversion-status .chip's colors —
   whether the reader has actually seen this chapter, not whether it's been
   translated yet). */
.chip--read { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.chip--read::before { content: '✓ '; }
.chip--in-progress { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }
.chip--in-progress::before { content: '► '; }
.chip--unread { background: var(--surface-alt); border-color: var(--border-strong); color: var(--text-soft); }

/* Fix NG, 2026-08-02 — replaces the removed .vocab-readonly-* read-only
   popup (bottom nav's old "Vocab" button, now "Vote"). */
.novel-vote-modal { max-width: 360px; text-align: center; }
.novel-vote-title { margin: 0 0 14px; color: var(--text-faint); font-size: 12px; }
.novel-vote-summary { margin-bottom: 16px; font-size: 15px; font-weight: 700; min-height: 1.4em; }
.novel-vote-summary.loading { color: var(--text-faint); font-weight: 400; }
.novel-vote-count { font-weight: 400; color: var(--text-faint); font-size: 12.5px; }
.novel-vote-picker { display: flex; justify-content: center; gap: 6px; margin-bottom: 18px; }
.novel-vote-star { border: none; background: none; padding: 4px; color: var(--gold); cursor: pointer; }
.novel-vote-star svg { width: 30px; height: 30px; }
.novel-vote-star:disabled { cursor: default; opacity: 0.6; }
/* "Edit name" header icon's list modal — reworked to match a reference UI
   the user provided: a plain "raw=name" list with a total-count header, a
   row of maintenance actions, and a row of Copy/Paste/Đóng. */
.name-override-modal { max-width: 420px; }
.name-override-count { font-size: 13px; color: var(--ink-soft); text-align: center; margin-bottom: 10px; }
.name-override-plain-list { max-height: 260px; overflow-y: auto; margin: 0 0 14px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 13px; line-height: 1.7; }
.name-override-row { font-family: var(--font-cjk); white-space: pre-wrap; overflow-wrap: break-word; }
.name-override-actions-row { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 10px; }

/* "Paste" bulk-add modal ("Danh sách name muốn gộp") — one "raw=name" pair
   per line. */
.name-override-bulk-textarea { width: 100%; min-height: 120px; resize: none; }

/* "Lịch sử" (History) modal — paginated add/edit log. */
.name-history-entry { font-size: 13px; padding: 6px 0; }
.name-history-date { color: var(--ink-soft); font-size: 11px; font-family: var(--font-mono); margin-bottom: 2px; }
.name-history-divider { border: none; border-top: 1px dashed var(--line); margin: 6px 0; }
.name-history-pagination { display: flex; justify-content: center; gap: 6px; margin: 10px 0; }
.name-history-page { border: 1px solid var(--line); border-radius: var(--radius-sm); background: none; padding: 2px 8px; font-size: 12px; cursor: pointer; }
.name-history-page.active { background: var(--brand); color: #fff; border-color: var(--brand); }

/* New Auto-Convert feature (2026-07-26) — bottom-nav batch chapter picker +
   progress modal. */
.auto-convert-batch-modal { max-width: 480px; }
/* [Feature B, revised again] Status filter dropdown + All/None sit in
   their own row; the selection counter now sits on its own row directly
   below, left-aligned, rather than sharing the controls' row on the
   right. */
.auto-convert-select-actions { display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 10px; }
.auto-convert-controls-row { display: flex; align-items: center; gap: 6px; }
.auto-convert-counter { margin-top: 8px; align-self: flex-start; text-align: left; font-size: 12.5px; color: var(--ink-soft); }
/* [Dropdown-layout unification, 2026-07-27] Status filter is now the same
   anchored options-popover "small board" as Sort By, not a native <select> —
   just needs to not shrink in the shared controls row. */
#ac-status-filter { flex-shrink: 0; }
/* Same specificity reasoning: .btn's own font-weight:600 (components.css)
   loads after this file. */
button.auto-convert-select-btn { flex-shrink: 0; font-weight: 700; }
.auto-convert-chapter-list { max-height: 320px; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: 14px; }
.auto-convert-chapter-row, .auto-convert-progress-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 13px; cursor: pointer; }
.auto-convert-progress-row { cursor: default; justify-content: space-between; }
.auto-convert-chapter-row:last-child, .auto-convert-progress-row:last-child { border-bottom: none; }
.auto-convert-chapter-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.auto-convert-job-status { font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft); flex-shrink: 0; }
.auto-convert-job-status.is-completed { color: var(--success); }
.auto-convert-job-status.is-failed { color: var(--danger); cursor: help; }
/* [Feature B] Icon-only convert-status, matching the Novel Detail TOC's own
   icon-only chip treatment exactly (pages.css's .reading-view .chapter-row
   .toc-conversion-chip .chip) — same font-size:0 text-hiding trick, same
   ::before icon at 16px, so this picker's chip and the TOC's chip render
   identically rather than just sharing a color palette. */
.auto-convert-chapter-row .chip { padding: 0; font-size: 0; gap: 0; background: none; border: none; flex-shrink: 0; }
.auto-convert-chapter-row .chip::before { font-size: 16px; }

/* ---------- Chapter comments ---------- */
.comments-list { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.comments-list li { padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--radius); font-size: 13px; }
.comment-body { margin: 4px 0 0; color: var(--ink); line-height: 1.5; overflow-wrap: break-word; }
.comment-body p { margin: 0 0 10.5px; }
.comment-body p:last-child { margin-bottom: 0; }
.comment-date { display: block; margin-top: 4px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); }
.comment-form { max-width: 480px; display: flex; flex-direction: column; gap: 8px; padding: 0 0 0 28px; }
.comment-form button { align-self: flex-start; }
.chapter-comments { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--line); }
.chapter-comments-heading { font-size: 15px; margin: 0 0 10px; padding: 0px 0px 0px 28px; }

/* ---------- Chapter pager (Story 1.3, FIX 1.8) ---------- */

.chapter-pager { display: flex; justify-content: space-between; gap: 10px; margin-bottom: 4px; }

/* ---------- Table of contents (Story 1.6) ---------- */

.toc-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.toc-controls label { display: flex; align-items: center; gap: 6px; }
.toc-controls select {
  font-family: var(--font-ui);
  font-size: 13px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 4px 8px;
  color: var(--ink);
}
.toc-summary { font-family: var(--font-mono); font-size: 12px; }

.toc-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-size: 13px;
  color: var(--ink-soft);
  margin: 4px 0 8px;
}
.toc-page-jump { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.toc-page-input {
  width: 42px;
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: center;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 3px 4px;
  color: var(--ink);
}

.toc-list {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* FIX 1.6 — the contents table shows every entry on its own page
     (TOC_PAGE_SIZE already caps a page at 100 chapters); no inner scroll. */
}
.toc-list li { display: flex; align-items: center; gap: 6px; }
.toc-list li .toc-open {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  font-family: var(--font-ui);
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 12px;
  color: var(--ink);
}
.toc-list li .toc-open.active { border-color: var(--seal); box-shadow: inset 3px 0 0 var(--seal); }
.toc-list li .toc-remove { flex-shrink: 0; }
.toc-number { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); flex-shrink: 0; width: 20px; }
.toc-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* FIX 1.3.1 ("full Remove") — the novel detail page's "Removed chapters"
   restore panel, a smaller/quieter echo of .toc-list for chapters currently
   soft-deleted (softDeleteChapter). */
.trashed-chapters {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.trashed-chapters li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 13px;
  color: var(--ink-soft);
  padding: 6px 12px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* "Update Chapter Reading Screen" — the converted-content pane now renders
   directly on the reading channel's own background (no boxed book-spread
   card around it), so .page only needs the text-wrapping/padding half of
   what it used to share with the retired raw pane. */
.page { padding: 28px 30px; min-width: 0; overflow-wrap: break-word; word-break: break-word; }
/* Vietnamese reader text defaults to the same UI font as Synopsis while
   still honoring a font selected from the reader toolbar. */
#vi-pane { padding: 0px 0px 0px 28px; font-family: var(--reader-font, var(--font-ui)); }

/* ---------- Edit raw (Story 3.7) ---------- */

.raw-edit-textarea {
  display: block;
  width: 100%;
  margin: 0 0 12px;
  padding: 10px 12px;
  font-family: var(--font-cjk);
  font-size: 18px;
  line-height: 1.8;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  resize: none;
  overflow-y: hidden;
}

/* In-place raw-edit mode (Edit raw icon swaps #vi-pane's own content,
   no modal) — same visual language as .draft-label for the mode banner. */
.raw-edit-banner {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: 10px;
  display: block;
}
.raw-edit-actions { display: flex; gap: 8px; }

/* Mandatory: preserve exact formatting of pasted content — without this,
   the browser's default `white-space: normal` collapses every line break
   the user pasted (both the paragraph-fallback blob and any single-\n
   in-paragraph line breaks within an aligned block), rendering pasted
   text as one run-on block regardless of how it was actually formatted
   when pasted. Same fix already applied elsewhere for user free-text
   (.comment-body).
   Fixed 10.5px margin-bottom (Clean Chapter Content Formatting) replaces
   the browser's default <p> margin — paragraph-to-paragraph spacing now
   comes from this alone, not from blank lines surviving in the pasted
   text (sanitizeChapterText, app.js, strips those before render). */
.page-vi-draft p { margin: 0 0 10.5px; white-space: pre-wrap; overflow-wrap: break-word; }
.page-vi-draft p:last-child { margin-bottom: 0; }

/* A paragraph with no converted text yet (never converted, or invalidated
   by an "Edit raw" save) shows its raw text in place of a translation —
   this subtle treatment keeps it readable while still marking it as
   distinct from real converted content. */
.page-vi-draft p.is-raw-fallback {
  font-style: italic;
  color: var(--ink-soft);
}

.draft-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 10px;
  display: block;
}

/* ---------- Auto-convert card (Phase 4 — replaces the retired dispatch
   counter/stamp motif; base .auto-convert-card lives in components.css) --- */

.paste-back-block { margin-top: var(--space-4); }
.paste-back-block textarea {
  width: 100%;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  background: var(--surface-alt);
  overflow-y: hidden;
}
/* [Feature A] Converted-title field — same box treatment as the textarea
   above, just single-line, sitting between its own label and the content
   textarea's label. */
.paste-back-title-input {
  width: 100%;
  font-family: var(--font-ui);
  font-size: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  background: var(--surface-alt);
  margin-bottom: var(--space-4);
}

/* ---------- Learning Hub (UI overhaul v2, Phase 6 — tokens only, no
   markup/logic change: .hub-section/.learner-file-row have no mock
   equivalent worth inventing, so this is a straight recolor) ---------- */

.hub-section { margin-bottom: 36px; }
.hub-section h2 { font-family: var(--font-ui); font-size: 20px; font-weight: 600; margin: 0 0 6px; }
.hub-section .section-note { color: var(--text-soft); font-size: 13px; margin: 0 0 14px; }

/* OCR-review textarea (was inline-styled) — general text box per [UI/UX]
   standardized text box behavior. */
.learner-review-textarea {
  width: 100%;
  font-family: var(--font-cjk);
  line-height: 1.5;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  overflow-y: hidden;
}

.learner-file-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  margin-bottom: 10px;
}

/* Base .tag/.empty-state now live in css/components.css (UI overhaul v2). */
.tag--mined { border: 1px solid var(--gold); color: var(--gold); }
.tag--ai { border: 1px dashed var(--text-soft); color: var(--text-soft); }

/* ---------- Add Content ---------- */

.add-content-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }

.add-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 22px; }
.add-card h3 { font-family: var(--font-ui); font-size: 17px; margin: 0 0 6px; }
.add-card p { color: var(--text-soft); font-size: 13.5px; margin: 0 0 16px; }

.field { margin-bottom: 12px; }
.field label { display: block; font-family: var(--font-ui); font-size: 12px; color: var(--text-soft); margin-bottom: 4px; }
.field input[type="text"], .field select {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-family: var(--font-ui);
  font-size: 14px;
}
/* [UI] "Choose file" button — styles the native file input's own button
   (::file-selector-button) to match the app's .btn-secondary look instead
   of the unstyled browser default, without needing to restructure every
   call site into a hidden-input+styled-label widget. */
.field input[type="file"] {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--text-soft);
}
.field input[type="file"]::file-selector-button {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: 8px 14px;
  margin-right: 10px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.field input[type="file"]::file-selector-button:hover { background: var(--surface-alt); }
/* Checkbox toggles in the Split Chapter tool (title-cleanup options) —
   overrides the generic .field label's block layout so the checkbox and
   its text sit inline instead of stacked. */
.split-toggle-field label {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--text); margin-bottom: 0;
}

/* FIX 1.10 — live ✔/✖ feedback under the Cover image URL field. Was
   var(--accent, #2a7a3b)/var(--danger, #b23b3b) with fallbacks for a system
   that had neither token; now that --accent/--danger are real (and --accent
   is blue, not green), .is-ok points at --success directly instead of
   picking up the wrong hue through a now-satisfied fallback. */
.url-preview-status { display: block; margin-top: 4px; font-family: var(--font-ui); font-size: 12px; }
.url-preview-status.is-checking { color: var(--text-soft); }
.url-preview-status.is-ok { color: var(--success); }
.url-preview-status.is-error { color: var(--danger); }

.field textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.5;
  resize: none;
  overflow-y: hidden;
}

.form-message { font-size: 13px; margin-top: 10px; }
.form-message.success { color: var(--success); }
.form-message.error { color: var(--brand); }

/* Upload progress bar (FIX 1.4, Story 1.5 item 2). */
.upload-progress { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.upload-progress-track { flex: 1; height: 8px; border-radius: 4px; background: var(--surface-alt); overflow: hidden; border: 1px solid var(--border); }
.upload-progress-fill { height: 100%; width: 0%; background: var(--brand); transition: width 0.15s ease; }
.upload-progress-pct { font-family: var(--font-mono); font-size: 11px; color: var(--text-soft); min-width: 34px; text-align: right; }

/* ---------- Split Chapter tool (Story 1.5) ---------- */

.split-tool { margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--border-strong); }

/* Toolbar row (FIX 1.6, Story 1.5 Do1) — check-all/uncheck-all, the range
   picker, and the live selected-count sit in one wrapping row instead of two
   separately-spaced blocks. */
.split-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  flex-wrap: wrap;
}
.split-check-controls { display: flex; gap: 8px; }

/* Chapter range picker (FIX 1.5, Story 1.5) — "from chapter X to chapter Y". */
.split-range-controls { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-soft); flex-wrap: wrap; }
.split-range-controls label { display: flex; align-items: center; gap: 6px; }
.split-range-controls input[type="number"] {
  width: 64px;
  padding: 5px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--text);
}

/* Live selected/total count (FIX 1.6, Story 1.5 Do3). */
.split-selected-count {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
  margin-left: auto;
  white-space: nowrap;
}

.split-preview-list {
  list-style: none;
  margin: 10px 0;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.split-preview-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.split-preview-list li:last-child { border-bottom: none; }
/* 0-indexed chapter number (FIX 1.6, Story 1.5 Do2) — matches the range
   picker's "from/to" fields exactly, no off-by-one translation between them. */
.split-index { font-family: var(--font-mono); font-size: 11px; color: var(--text-soft); flex-shrink: 0; }
.split-preview-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-char-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-soft); flex-shrink: 0; }
.split-warn { color: var(--brand); font-size: 11px; font-family: var(--font-mono); flex-shrink: 0; }
.split-summary { font-size: 12px; color: var(--text-soft); margin: 8px 0 4px; }

/* ---------- Account ---------- */

.account-grid { max-width: 480px; display: flex; flex-direction: column; gap: 18px; }
.account-row { display: flex; justify-content: space-between; align-items: center; padding: 14px 0; border-bottom: 1px solid var(--border); }
.account-row .label { font-size: 14px; }
.account-row .hint { font-size: 12px; color: var(--text-soft); }

/* ---------- Confirm popup (FIX 1.2) ---------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 22, 31, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 100;
}
.modal-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 22px 24px;
  max-width: 380px;
  width: 100%;
}
/* Every popup gets a sticky header bar (direct request, extended to also
   cover the title — not just the ✕ button — in a follow-up):
   addModalCloseButton wraps the modal's own <h3> + a new ✕ button together
   in .modal-header and prepends that to .modal-card. position:sticky (not
   absolute) so it stays pinned to the top while scrolling a tall modal's
   own content — genre-picker-modal/upload-novel-modal/edit-metadata-card
   are all overflow-y:auto scroll containers, and a plain
   absolutely-positioned/normal-flow element would scroll away with the
   rest of their content instead of staying put. Negative margins extend it
   to the card's own edges (undoing .modal-card's padding) and give it an
   opaque background so scrolled-past content doesn't show through. */
.modal-header {
  position: sticky;
  top: -22px;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin: -22px -24px 12px;
  padding: 22px 24px 10px;
  background: var(--surface);
  border-top-left-radius: 16px;
  border-top-right-radius: 16px;
  border-top-left-radius: 16px;
  border-top-right-radius: 16px;
}
.modal-header h3 { margin: 0; }
.modal-close-x {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  border-radius: 50%;
  color: var(--text-soft);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.modal-close-x:hover { background: var(--surface-alt); color: var(--text); }
.modal-close-x:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
.modal-card h3 { font-family: var(--font-ui); font-size: 18px; margin: 0 0 8px; }
.modal-card p { color: var(--text-soft); font-size: 13.5px; margin: 0 0 18px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; }

/* Convert-choice modal (chapter reading screen's Convert icon) — the two
   real choices (Manual-convert / Auto-convert) get equal-width buttons on
   their own row, visually distinct from the plain right-aligned Cancel row
   below them. */
.convert-choice-actions { justify-content: stretch; margin-bottom: 12px; }
.convert-choice-actions button { flex: 1; }

/* ---------- Selection action sheet (Story 3.4) ---------- */

.selection-sheet {
  position: absolute;
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  min-width: 180px;
  max-width: min(260px, calc(100vw - 16px));
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.selection-sheet button {
  font-family: var(--font-ui);
  font-size: 12px;
  white-space: nowrap;
  border: none;
  background: none;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-align: left;
  width: 100%;
  touch-action: manipulation;
}
.selection-sheet button:hover { background: var(--surface-alt); }
.selection-sheet button:disabled { color: var(--text-soft); }

/* "Sort by" popover (2026-07-27 follow-up) — same anchored-popover pattern
   as .selection-sheet above, options stacked vertically instead of
   side-by-side. */
.options-popover {
  position: absolute;
  /* Higher than .modal-overlay (z-index:100) — this popover now also opens
     from inside the Search modal (Novel Status), not just the plain filter
     bar, so it must stack above the modal, not just above ordinary page
     content. */
  z-index: 110;
  display: flex;
  flex-direction: column;
  min-width: 140px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 4px;
}
.options-popover button {
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: left;
  white-space: nowrap;
  border: none;
  background: none;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text);
}
.options-popover button:hover { background: var(--surface-alt); }
.options-popover button.active { color: var(--brand); font-weight: 600; }
/* ---------- Name-edit popup (Story 3.6, FIX 1.3) ---------- */

.name-edit-card { max-width: 420px; }

/* ---------- FIX 1.14 ---------- */

/* B1 — Edit Metadata modal (replaces the old always-inline panel). */
.edit-metadata-card { max-width: 460px; max-height: 85vh; overflow-y: auto; }
/* Library screen refactor (2026-07-27) — "+ Add Content" modal, reusing the
   full "Upload a novel" form/split-tool, so it needs the same wide/
   scrollable treatment as Edit Metadata rather than the default 380px
   .modal-card. */
.upload-novel-modal { max-width: 560px; max-height: 88vh; overflow-y: auto; }
/* "Upload chapter" popup, Split Raw path (2026-07-31 follow-up: "Upload
   Chapter(s)" vs "Split Raw") — reuses the same split-tool as
   .upload-novel-modal, so it needs the same wide/scrollable treatment; the
   default one-chapter-per-file path keeps the default 380px .modal-card
   untouched. */
.upload-chapter-modal--multi { max-width: 560px; max-height: 88vh; overflow-y: auto; }
/* General-purpose small hint paragraph inside a modal (upload-chapter's own
   note) — distinct from .name-edit-card's inline-label .field-note. */
p.field-note { font-size: 12px; color: var(--text-soft); margin: 4px 0 0; }

/* Inline "Upload Chapter(s)" / "Split Raw" and "Choose file" / "Split Raw"
   button pairs (2026-07-31 follow-up), used in both the "Upload a novel"
   form and the "Upload chapter" modal. */
.upload-mode-row { display: flex; gap: 10px; margin: 14px 0 6px; }
.upload-mode-row .btn { flex: 1; }
.upload-file-summary { font-size: 12.5px; color: var(--text-soft); margin: 8px 0; word-break: break-word; }

/* Novel-scoped "auto-converting in the background" status — same
   empty/not-empty toggle as the Library page's #auto-convert-progress, so
   it takes zero space when there's nothing to show. */
.novel-autoconvert-status:empty { margin: 0; }
.novel-autoconvert-status:not(:empty) { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; font-size: 12.5px; color: var(--text-soft); }

/* B2 — search snippet under a matching chapter's title. */
.toc-title-block { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.toc-title-block .toc-title { flex: none; }

.name-edit-card .field label { display: flex; align-items: center; justify-content: space-between; }
/* (2)/(3) Hán Việt + Pinyin — read-only reference fields, real hanzii.net
   lookups (2026-07-27). Visually muted so they read as "reference", not an
   editable field left empty by mistake. */
.name-edit-card .field input[readonly] { background: var(--surface); color: var(--text-soft); cursor: default; }
.name-hint-chip {
  display: inline-block;
  margin-top: 6px;
  margin-right: 6px;
  border: 1px dashed var(--border-strong);
  background: none;
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--brand);
  cursor: pointer;
}
.name-hint-chip:hover { background: var(--surface-alt); }

/* ---------- Misc ---------- */

.loading, .error-banner { font-family: var(--font-mono); font-size: 13px; color: var(--text-soft); padding: 20px 0; }
.error-banner { color: var(--brand); }

.back-link {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--text-soft);
  background: none;
  border: none;
  padding: 0;
  margin-bottom: 16px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.back-link:hover { color: var(--ink); }

/* TWC-20260729-SUPPLEMENTAL-LINKS */
.supplemental-links-field { gap: 10px; }
.supplemental-link-group { display: grid; gap: 8px; padding: 12px; margin-bottom: 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-alt); }
.supplemental-link-title { font-size: 12px; font-weight: 700; color: var(--text); }
.supplemental-link-line { display: grid; grid-template-columns: 82px minmax(0, 1fr) 34px; align-items: center; gap: 8px; }
.supplemental-link-line label { margin: 0; }
.supplemental-link-line input { width: 100%; min-width: 0; }
.supplemental-link-remove { width: 34px; height: 34px; border: 1px solid var(--border-strong); border-radius: 50%; background: var(--surface); color: #b42318; cursor: pointer; font-size: 20px; line-height: 1; }
.supplemental-link-add-row { display: flex; justify-content: flex-start; }
/* Fix NG 2026-08-02: was missing the same 760px cap every other Novel
   Detail block gets (see pages.css's .nd-col-cap), so it stretched to the
   page's full .container width and hugged the left edge instead of
   centering in the reading column. Only the vertical margin stays here;
   .nd-col-cap (added alongside this class in app.js) owns the horizontal
   centering. */
.nd-supplemental-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 10px; margin-bottom: 18px; }
.nd-supplemental-links a { color: var(--danger, #d93025); font-size: 12.5px; text-decoration: underline; text-underline-offset: 2px; }
.nd-supplemental-links a:hover { color: #a61b12; }
