/* Shared component contracts. Legacy selectors in base.css remain only while pages migrate.
   DESIGN-SYSTEM.md §5 is the binding contract. This file implements it. */

/* ==========================================================================
   §1  Touch targets and control contexts (R-503)
   ========================================================================== */
/* `.add-item` is the shared control ROW (base.css): usually a <form>, but the issue
   target picker renders one as a <div> so Enter cannot submit a bare search field.
   Naming the row keeps the tap floor with the component, not with the tag it uses. */
:where(.tabbar, .bottom-tabs, .app-header, .book-row, .lib-card, .shelf-item, .sheet,
       .more-sheet, form, .add-item, .pager, .control)
  :is(a[href], button, [role="button"], input, select, summary),
:where(a, button, [role="button"], input, select, summary).control {
  min-block-size: var(--tap-min);
  min-inline-size: var(--tap-min);
}
/* Prose links retain normal flow but gain a forgiving vertical hit area.
   Scoped to actual PROSE contexts (paragraphs, table cells, prose blocks and explicit
   inline markers). Card/row title links (`li.book-row > a.book-title`,
   `li.shelf-item > a.card-link`, `li.bookmark > a`, etc.) are NOT prose — they are
   controls and the tap-target floor above still applies to them. UI-BRIEF §6.5
   read with SPEC-ADDENDUM correction 2. */
p a[href], td a[href], .inline-link,
.empty-state a[href],
.home-surface-count a[href], .more-sheet p a[href],
.filter-strip > a[href]:not(.filter-chip):not(.chip) {
  min-block-size: auto;
  min-inline-size: auto;
  padding-block: .2em;
}
/* …but a kit ROW is a list item too, and its title / author / series links are real targets,
   not prose (R-503). Every surface that renders line() rows gets ≥44 px links from the kit
   itself — no per-surface special-casing (library.css used to carry this rule alone).
   The box is the HIT area, not the layout: block padding grows the box to the tap floor and
   equal negative margins hand that padding back, so a card's title, byline and date sit one
   line apart instead of 44px apart (owner report 2026-10-03). Padding (not min-block-size)
   keeps a two-line title's box at content + padding, never shorter than its text. The hit
   boxes of stacked links overlap; the later link wins the overlap, as in a native list. */
.book-row > a[href], .book-row .book-row-author a[href], .book-row .book-row-series a[href],
.book-row .book-title, .ledger-row__title, .catalogued-buckets a[href],
/* `.hit-link` is the same contract as a class: any text link that must be a ≥44px touch
   target without taking 44px of layout (DESIGN-SYSTEM.md §3.6). */
.hit-link {
  --hit-pad: max(0px, calc((var(--tap-min) - 1lh) / 2));
  min-inline-size: var(--tap-min);
  display: inline-flex; align-items: center;
  padding-block: var(--hit-pad);
  margin-block: calc(var(--hit-pad) * -1);
}
input, select, textarea { font-size: var(--fs-input); }
.button, .btn { min-block-size: var(--tap-min); align-items: center; justify-content: center; }
/* A checkbox or radio is drawn at its own size and its LABEL carries the tap floor (R-287,
   R-376, #830). Stretching the native box to 44×44 drew the empty grey squares the owner
   reported. Segmented-control radios are invisible overlays sized by `.segmented` below. */
input:is([type="checkbox"], [type="radio"]) {
  min-block-size: 0; min-inline-size: 0;
  inline-size: 1.25rem; block-size: 1.25rem;
  margin: 0; flex: none;
  accent-color: var(--accent);
}
label:has(> input:is([type="checkbox"], [type="radio"])) {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-block-size: var(--tap-min); min-inline-size: var(--tap-min);
  cursor: pointer;
}
/* A label that holds only the box (Discover's dismiss pick) centres it in the tap square. */
label.book-row-pick { justify-content: center; }

/* ==========================================================================
   §2  Page container
   ========================================================================== */
.page-container { inline-size: min(100%, var(--page-max)); margin-inline: auto; padding-inline: var(--space-4); }

/* ==========================================================================
   §2b  Section header row + quiet actions (#784)
   ========================================================================== */
/* One shape for every "heading + trailing action" pair: baseline-aligned flex row,
   the heading at --fs-h3, the action a QUIET text link — never a full-size pill. */
.section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
}
.section-head > :is(h1, h2, h3) { margin: 0; font-size: var(--fs-h3); line-height: var(--lh-h3); }
/* …except the page title. Shelves, Discover, Notifications and the browse lists put their
   <h1> in a section-head and so rendered it at 17px, while Library, Profile and every admin
   page rendered 24px — one app, two title sizes (owner report 2026-10-03). */
.section-head > h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); }
.section-head__aside { display: flex; align-items: center; gap: var(--space-1); flex: 0 0 auto; }
/* Quiet action: link-weight affordance with a full tap-floor hit box. The visual is small
   text that lines up with the content edge; the hit box comes from the hit-area pattern
   (padding returned by negative margin), so it neither takes 44px of the row nor sits 8px
   inside the gutter as the old inline padding made it. A <button> link-action sheds the
   base button padding the same way. */
.link-action {
  --hit-pad: max(0px, calc((var(--tap-min) - 1lh) / 2));
  display: inline-flex; align-items: center; gap: 4px;
  min-block-size: 0; min-inline-size: var(--tap-min);
  padding: var(--hit-pad) 0; margin-block: calc(var(--hit-pad) * -1);
  font-size: var(--fs-small); font-weight: 600;
  color: var(--accent); text-decoration: none;
  background: none; border: 0; border-radius: var(--radius-sm); cursor: pointer;
}
.link-action:hover { text-decoration: underline; color: var(--accent); }
.link-action--muted { color: var(--fg-muted); }
.link-action--muted:hover { color: var(--fg); }

/* ==========================================================================
   §3  Badges, pills, flags, page-type pill (R-235, R-228)
   ========================================================================== */
.page-type-pill, .badge, .row-badge, .book-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-caption); line-height: var(--lh-caption);
}
.badge, .row-badge, .book-flag {
  padding: .17em .5em; border-radius: var(--radius-pill);
  background: var(--bg-elev-2); color: var(--fg-muted);
}
.row-badge--status { background: var(--success-weak); color: var(--success); }
.row-badge--star { padding: 0; background: transparent; color: var(--warning); }
.row-badge--comment { background: var(--bg-elev-2); color: var(--fg-muted); cursor: default; }
.row-badge--multi-author {
  background: var(--warning-weak); color: var(--warning);
  font-weight: 700;
}

/* Page-type pill colour coding (R-235) */
.page-type-pill {
  display: inline-flex; align-items: center;
  font-size: .68rem; font-weight: 800; letter-spacing: .06em;
  padding: .2em .65em; border-radius: var(--radius-pill);
}
.page-type-pill[data-page-type="book"]   { background: var(--accent-weak); color: var(--accent); }
.page-type-pill[data-page-type="series"] { background: var(--success-weak); color: var(--success); }
.page-type-pill[data-page-type="author"] { background: var(--warning-weak); color: var(--warning); }
.page-type-pill[data-page-type="search"] { background: var(--bg-elev-2); color: var(--fg-muted); }
.page-type-pill[data-page-type="admin"]  { background: var(--danger-weak); color: var(--danger); }

/* ==========================================================================
   §4  Chips, tags, filter chips
   ========================================================================== */
.chip, .tag, .trope-chip a {
  display: inline-flex; align-items: center;
  border-radius: var(--radius-pill);
}
/* Only a chip or tag you can tap carries the tap floor; a <span> label chip is text and
   stays its own height instead of a 44px pill. */
:is(a, button).chip, :is(a, button).tag, .trope-chip a { min-block-size: var(--tap-min); }
.tag {
  padding: 2px 8px; font-size: var(--fs-caption); font-weight: 600;
  background: var(--bg-elev-2); color: var(--fg-muted);
  text-decoration: none;
}
.tag:hover { background: var(--accent-weak); color: var(--accent); text-decoration: none; }
.tag--own { background: var(--accent-weak); color: var(--accent); }
.tag--ghost { background: var(--bg-elev-2); color: var(--fg-faint); }
.tag-add {
  display: flex;
  inline-size: 100%;
  max-inline-size: 100%;
  gap: var(--space-2);
}
/* flex-basis 0, not auto: the input's size="20" intrinsic (~230px) otherwise enters the
   row's min-content, widens the reading-card grid track past a 320px viewport (#784). */
.tag-add input { min-inline-size: 0; flex: 1 1 0%; }
.tag-add button { flex: 0 0 auto; }
.filter-chip {
  min-block-size: var(--tap-min);
  min-inline-size: var(--tap-min);
  display: inline-flex; align-items: center; gap: 4px;
  padding: 0 var(--space-3); border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 600;
  background: var(--bg-elev-2); color: var(--fg-muted);
  border: 1px solid var(--border); cursor: pointer;
}
.filter-chip[aria-pressed="true"], .chip.is-active {
  background: var(--accent); color: var(--accent-fg); border-color: var(--accent);
}
.filter-chip__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; margin-left: 2px;
  background: transparent; border: none; cursor: pointer;
  color: inherit; font-size: 14px; line-height: 1;
}
.filter-chip__remove:hover { background: rgb(255 255 255 / 20%); }

/* ==========================================================================
   §5  Book row — line (phone default, R-505)
   ========================================================================== */
.book-row-list, .series-books, .standalone-books { padding: 0; list-style: none; overflow-x: hidden; }

.book-row {
  container-type: inline-size;
  min-width: 0;
  overflow: hidden;
}
/* The LINE shape only (#784): grid + padding apply to list rows, never to the card and
   carousel variants that also carry .book-row for the shared answer-set contract —
   the padding used to leak into .shelf-item/.lib-card and inset their covers 16px. */
.book-row:not(.shelf-item):not(.lib-card) {
  min-block-size: var(--row-min-h);
  display: grid;
  grid-template-columns: var(--row-thumb-w) minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: start;
  padding: var(--space-2) var(--space-4);
}
/* #784: the cover used to span a HARDCODED 4 rows, so a row with 5+ text children
   (seq/title/author/series/date/badges/acquire) auto-placed its overflow back into the
   56px cover column — badges stacked vertically in a sliver. Every non-cover child is
   pinned to the text column instead, and the cover over-spans (empty implicit rows
   collapse to zero height with no row-gap set), so the shape survives any child count. */
.book-row > * { grid-column: 2; min-width: 0; }
.book-row > .book-row-cover { grid-column: 1; grid-row: 1 / span 12; }
.book-row > .book-row-pick { grid-column: 1; place-self: center; }
.book-row-cover {
  inline-size: var(--row-thumb-w);
  aspect-ratio: var(--cover-ratio);
  object-fit: cover;
  border-radius: var(--cover-radius);
}
.book-row .book-title {
  min-inline-size: 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
  font-weight: 600; color: var(--fg);
  text-decoration: none;
}
.book-row a.book-title:hover { color: var(--accent); }
.book-row-author, .book-row-series, .book-row .book-date {
  min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--fg-muted); font-size: var(--fs-small);
}
.book-row-series { font-size: inherit; color: var(--fg-faint); display: inline; }
.book-row .book-date { font-size: var(--fs-caption); color: var(--fg-faint); }
/* #784: the answer cluster is ONE inline meta line (date + badges + tags), never a stack. */
.book-row .row-badges {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-1) var(--space-2); min-width: 0;
}
.book-row .row-badges:empty { display: none; }
.book-row .row-tags { display: contents; }
/* The in-title sequence prefix (#784): plain muted text, not base.css's banner chip. */
.book-title .book-seq {
  background: none; padding: 0; min-inline-size: 0;
  font-size: var(--fs-small); font-weight: 600; color: var(--fg-faint);
  margin-inline-end: .35em;
}
.book-row .acquire { grid-column: 1 / -1; margin-top: var(--space-2); min-inline-size: 0; }
/* #784: ownership + action read as ONE control line — the flag used to stack as its
   own row above the button. */
.book-row:not(.shelf-item):not(.lib-card) .acquire {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2);
}
.book-row:not(.shelf-item):not(.lib-card) .acquire > :is(form, a.btn, button.btn) { flex: 1 1 auto; }
.book-row .acquire .btn { inline-size: 100%; }
.book-row .book-title { min-width: 0; overflow-wrap: anywhere; }

/* Alternating shading (R-223) — LIST rows only. The bare `.book-row:nth-child(even)`
   selector used to hit carousel `.shelf-item.book-row` items too, painting every other
   carousel card with a stray white panel (#784). */
:is(.book-row-list, .series-books, .standalone-books, .discover-list) > .book-row:nth-child(even) {
  background: color-mix(in srgb, var(--bg-elev) 72%, transparent);
}

/* Other-author de-emphasis (R-224, R-502) */
.book-row--other-author { opacity: .55; }
.book-row--other-author:focus-within { opacity: 1; }

/* Annotated rows (pick_form / note) */
.book-row--annotated { padding-bottom: var(--space-4); }
.book-row-pick { grid-column: 1 / -1; margin-bottom: var(--space-1); }
.book-row-note {
  grid-column: 1 / -1;
  font-size: var(--fs-small); color: var(--fg-muted);
  margin: var(--space-1) 0 0;
}

/* Container query: at ≥420px keep the acquire action beside the text without
   stretching one short label across the whole row. */
@container (min-width: 420px) {
  .book-row .acquire {
    grid-column: 2;
    inline-size: min(100%, 15rem);
    justify-self: start;
  }
}

/* Sequence number badge */
.book-seq {
  display: inline-block;
  font-size: var(--fs-caption); font-weight: 700; color: var(--fg-faint);
  min-inline-size: 2em; text-align: center;
}

/* ==========================================================================
   §6  Book row — card (Library grid, R-207)
   ========================================================================== */
.library-grid {
  gap: var(--space-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.lib-card {
  container-type: inline-size;
  min-inline-size: 0;
  gap: var(--space-1);
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--radius);
  /* #784: the cover is full-bleed (Seerr card anatomy) — padding moved onto the text
     children below so the image owns the card's top edge. */
  padding: 0;
  background: var(--bg-elev);
  overflow: hidden;
  position: relative;
}
.lib-card > :not(.lib-cover, .cover-placeholder) { margin-inline: var(--space-3); }
.lib-card > .acquire { margin-block-end: var(--space-3); }
.lib-card > .lib-title { margin-block-start: var(--space-1); }
/* Every cover <img> now carries width/height attributes (intrinsic size for CLS, R-514). The
   height attribute is a presentational hint that would beat `aspect-ratio` and stretch the
   image when the CSS width shrinks — so block-size is explicitly auto here. */
.lib-cover, .shelf-item__cover, .cover-placeholder {
  display: block;
  aspect-ratio: var(--cover-ratio);
  inline-size: 100%;
  block-size: auto;
  object-fit: cover;
  border-radius: var(--cover-radius);
}
.book-row-cover, .lib-row-cover { block-size: auto; }
.lib-cover { border-radius: var(--radius) var(--radius) 0 0; }
.lib-title {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; font-weight: 600; color: var(--fg);
  text-decoration: none; min-inline-size: 0;
}
.lib-title:hover { color: var(--accent); }
/* Clamped 2-line titles that are also ≥44 px tap targets: make two lines EXACTLY the tap
   height, otherwise the min-block-size leaves a 7 px sliver of a hidden third line visible. */
.lib-title, .shelf-item__link { line-height: calc(var(--tap-min) / 2); }
.lib-author {
  font-size: var(--fs-small); color: var(--fg-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lib-series {
  font-size: var(--fs-caption); color: var(--fg-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lib-series a { color: var(--fg-faint); text-decoration: none; }
.lib-series a:hover { color: var(--accent); text-decoration: none; }
/* U2/R-050: base.css's stretched-link escape (`.lib-card form, .lib-card button { position:
   relative; z-index: 2; }`) never covered a plain nested <a> — so the series/author links a
   Library card already renders (_series_link()/_author_links() in _book_row.html) sat BELOW
   .card-link::after and never received a real pointer click; only the whole-card navigation
   worked. Found while measuring R-050's "Library -> series -> button" tap path in a real
   browser (LUI's own diagnosis method) — the same two-line escape base.css already uses. */
.lib-card .lib-series a, .lib-card .lib-author a { position: relative; z-index: 2; }
.lib-added { font-size: var(--fs-caption); color: var(--fg-faint); }
.lib-card .row-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.lib-card .acquire { margin-top: auto; }
.lib-card .acquire .btn { inline-size: 100%; }
/* #864: a status pill and Checkout are separate controls. The card's compact
   base gap is --space-1; reserve another token inside the action only when that
   pill exists, while retaining its auto margin for equal-height grid rows. */
.lib-card[data-reading-status]:not([data-reading-status="none"]) .acquire {
  padding-block-start: var(--space-1);
}

/* ==========================================================================
   §7  Book row — carousel_item (Shelf)
   ========================================================================== */
.shelf-row {
  /* flex-start + FIXED text slots (#784): every card renders the same slot heights
     (2-line title box, one author line, one series line, one capped badge line), so
     actions align across the strip by construction — stretch was tried and rejected:
     one decorated card inflated every sibling with dead vertical space. */
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding-inline: var(--space-4);
  scroll-padding-inline: var(--space-4);
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.shelf-row::-webkit-scrollbar { display: none; }

.shelf-item {
  inline-size: var(--shelf-item-w);
  min-inline-size: var(--shelf-item-w);
  flex-shrink: 0;
  position: relative;
  scroll-snap-align: start;
  display: flex; flex-direction: column;
  gap: var(--space-1);
}
.shelf-item__cover { border-radius: var(--radius); }
/* The compact acquire control's real button (`.acquire-compact-btn`) is painted over
   the cover's bottom-right corner via `position: absolute`, so it needs no flow space
   of its own — an absolutely-positioned-only child collapses its flex parent to 0
   height regardless, so the fixed Shelf card slots never reserve the old text-pill
   row either way. `display: contents` used to unwrap the `.acquire--compact` wrapper
   entirely for this instead, which reads as equivalent until something measures the
   wrapper's OWN box: `display: contents` gives it a permanent zero rect pinned to the
   viewport origin, not "wherever its child renders" — the phone-gate containment
   check (tests/e2e/test_phone_gate.py::test_shelf_owned_card_acquire_control_stays_
   inside_its_card) measures exactly that box, so it always read as "starts at (0,0),
   spills outside the card" no matter where the real button actually sat. Keeping the
   wrapper a real, empty flex box fixes the measurement without changing anything
   about where the compact button paints. */
.shelf-item .acquire {
  position: static; margin-block-start: 0; z-index: auto;
  padding-block-start: var(--space-1);
  display: flex; flex-direction: column; gap: var(--space-1);
  inline-size: 100%; max-inline-size: 100%; min-inline-size: 0;
}
.shelf-item .acquire--compact {
  display: flex;
}
/* Card text slots (#784): Shelf strips share fixed title, metadata, and badge slots
   across book and series cards. The acquire control rides the cover for book cards, so
   empty text slots remain real geometry instead of collapsing a card shorter than its
   neighbours. */
.shelf-item__link { block-size: 44px; max-block-size: 44px; }
.shelf-item__author, .shelf-item__series { block-size: 18px; max-block-size: 18px; }
.shelf-item .row-badges {
  block-size: 20px; max-block-size: 20px;
  overflow: hidden; flex-wrap: nowrap; align-items: center; gap: var(--space-1);
}
/* A badge never wraps internally — the fixed 20px slot would clip the second line. */
.shelf-item .row-badge, .shelf-item .tag { white-space: nowrap; flex: 0 0 auto; }
.shelf-item__reason { max-block-size: calc(2 * var(--lh-caption)); overflow: hidden; }
.shelf-item .acquire .btn {
  inline-size: 100%; max-inline-size: 100%; min-inline-size: 0;
  min-block-size: var(--tap-min); padding-inline: var(--space-2);
  font-size: 0.7rem; letter-spacing: -0.01em; border-radius: var(--radius-pill);
  overflow: hidden; white-space: normal; line-height: 1.15; text-align: center;
  display: inline-flex; align-items: center; justify-content: center;
}
.shelf-item .acquire .btn::before { content: none; }
/* The In-Library / Pre-order flag rides the cover's bottom-left corner (cover = 2:3 of the
   card width), never the text below it. */
.shelf-item .acquire .book-flag,
.lib-card .acquire .book-flag {
  /* #784: the flag rides the cover's TOP-left corner on a solid chip — the bottom band
     is where generated covers and photographic covers put their own text. */
  position: absolute; inset-inline-start: var(--space-1);
  inset-block-start: var(--space-1);
  margin: 0; font-size: 0.62rem; line-height: 1.3; padding: .2em .6em; z-index: 2;
  background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
  color: var(--success);
  max-inline-size: calc(100% - var(--space-4)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shelf-item .acquire form { display: contents; }
/* (badge slot rules live above — fixed 20px slot, #784) */
.shelf-item__link {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; font-weight: 600; color: var(--fg);
  text-decoration: none; font-size: var(--fs-small); overflow-wrap: normal; word-break: normal;
}
.shelf-item__link:hover { color: var(--accent); }
.shelf-item__author, .shelf-item__series {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-caption); color: var(--fg-muted);
}
.shelf-item__reason {
  font-size: var(--fs-caption); color: var(--fg-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
/* U5/R-236: the optional `seq_badge` overlay. `.shelf-item` has no padding of its own
   (unlike `.lib-card`), so the cover `<img>` sits flush at the card's own top-left
   corner — a plain small inset against the `.shelf-item` positioning context lands
   exactly on the cover, no card-padding math needed. `aria-hidden`: the number is
   already read from the plain-text "Series Name #3" sub-line below; this is a visual
   duplicate for a glance, not a second announcement. */
.shelf-item__seq-badge {
  /* Top-RIGHT (#784): the ownership flag owns the top-left corner now. */
  position: absolute; inset-block-start: var(--space-1); inset-inline-end: var(--space-1);
  z-index: 2;
  background: color-mix(in srgb, var(--bg-elev-2) 85%, transparent);
  font-weight: 700;
}

/* ==========================================================================
   §8  Covers and typographic placeholder (R-239, R-514, R-241)
   ========================================================================== */
.cover-placeholder {
  aspect-ratio: var(--cover-ratio);
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--space-3); overflow: hidden; position: relative;
  background: hsl(var(--ph-hue, 220) 25% 91%);
  color: var(--fg);
  border-radius: var(--cover-radius);
}
.cover-placeholder__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3); line-height: var(--lh-h3);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; font-weight: 700;
  color: hsl(var(--ph-hue, 220) 20% 25%);
}
.cover-placeholder__author {
  font-size: var(--fs-small); line-height: var(--lh-small);
  color: hsl(var(--ph-hue, 220) 15% 40%);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin-top: 2px;
}
.cover-placeholder__glyph {
  position: absolute; top: var(--space-2); right: var(--space-2);
  width: 20px; height: 20px;
  color: hsl(var(--ph-hue, 220) 20% 65%);
}

/* ==========================================================================
   §9  Acquire control (R-002, R-004, R-020)
   ========================================================================== */
.acquire { min-inline-size: 0; }
.acquire [aria-live] { min-block-size: 1lh; }
/* Every acquire state is one button height: the same 1px border as every other button and
   the shared 1.2 line-height, so swapping Request → Searching → Checkout never changes the
   control's size (a 1.5px border and the body line-height made them 36/38px side by side). */
.acquire .btn, .btn-request, .btn-progress, .btn-deferred, .btn-checkout, .acquire-failed .btn-retry {
  border-radius: var(--radius); font-weight: 600; line-height: 1.2;
}

/* Idle request */
.btn-request {
  background: transparent; color: var(--accent);
  border: 1px solid var(--accent);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
}
.btn-request:hover { background: var(--accent-weak); }
.btn-request:disabled { opacity: .5; cursor: not-allowed; }

/* Live progress */
.btn-progress, .btn-deferred {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  font-size: var(--fs-small); font-weight: 600;
  border-radius: var(--radius);
  min-block-size: var(--tap-min);
}
.btn-progress { background: var(--bg-elev-2); color: var(--fg-muted); }
.btn-deferred { background: var(--warning-weak); color: var(--warning); }

/* Ready / download */
.btn-checkout {
  background: var(--success); color: #fff;
  border: none; padding: var(--space-2) var(--space-4);
  font-weight: 700; cursor: pointer;
  min-block-size: var(--tap-min); border-radius: var(--radius);
  display: inline-flex; align-items: center; gap: var(--space-2);
}
.btn-checkout:hover { filter: brightness(1.1); }

/* Failed */
.acquire-failed {
  display: flex; flex-direction: column; gap: var(--space-1);
}
/* R-009: the plain-words failure-type headline, distinct from the specific reason
   beside it (.acquire-reason). */
.acquire-failure-type { font-weight: 700; color: var(--danger); }
.acquire-failed .btn-retry {
  background: transparent; color: var(--danger);
  border: 1px solid var(--danger);
  padding: var(--space-2) var(--space-4);
  min-block-size: var(--tap-min); border-radius: var(--radius);
  cursor: pointer; font-weight: 600;
}
.acquire-failed .btn-retry:hover { background: var(--danger-weak); }
/* R-009: a real tap target (not running prose), so it carries its own ≥44px floor
   rather than relying on the `.prose`/`p a[href]` exemption. */
.acquire-issue-link {
  display: inline-flex; align-items: center;
  min-block-size: var(--tap-min);
  color: var(--accent); font-size: var(--fs-small); font-weight: 600;
}

/* Cancelled */
.acquire-cancelled {
  display: flex; flex-direction: column; gap: var(--space-1);
}

/* Reason / queue position */
.acquire-reason {
  font-size: var(--fs-caption); color: var(--fg-muted);
  display: block; margin-top: 2px;
}
.acquire-queue-pos { font-weight: 700; }
.acquire-state { font-size: var(--fs-caption); color: var(--fg-faint); display: block; }

/* Ownership pill */
.book-flag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: .17em .5em; border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700;
  background: var(--success-weak); color: var(--success);
}


/* Compact acquire mode: one 44 × 44 circular icon control for Shelf carousel cards. */
.acquire-compact-btn,
.shelf-item .acquire--compact .acquire-compact-btn {
  inline-size: var(--tap-min); block-size: var(--tap-min);
  min-inline-size: var(--tap-min); min-block-size: var(--tap-min);
  max-inline-size: var(--tap-min); max-block-size: var(--tap-min);
  padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1; font-size: var(--fs-small); font-weight: 800;
  background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
  color: var(--fg); border: 1.5px solid color-mix(in srgb, var(--border-strong) 80%, transparent);
  box-shadow: var(--elevation-1);
}
.acquire-compact-btn .icon { inline-size: 24px; block-size: 24px; }
.shelf-item .acquire--compact .acquire-compact-btn {
  position: absolute;
  inset-block-start: calc(var(--shelf-item-w) * 1.5 - 50px);
  inset-inline-start: calc(var(--shelf-item-w) - 50px);
  z-index: 3;
}
.acquire-compact-btn--success,
.shelf-item .acquire--compact .acquire-compact-btn--success {
  background: var(--success); border-color: var(--success); color: #fff;
}
.acquire-compact-btn--danger,
.shelf-item .acquire--compact .acquire-compact-btn--danger {
  background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
  border-color: var(--danger); color: var(--danger);
}
.acquire-compact-btn--queued,
.acquire-compact-btn--neutral,
.shelf-item .acquire--compact .acquire-compact-btn--queued,
.shelf-item .acquire--compact .acquire-compact-btn--neutral {
  background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
  color: var(--fg-muted);
}
.acquire-compact-btn--working,
.shelf-item .acquire--compact .acquire-compact-btn--working {
  background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
  color: var(--accent);
}
.acquire-compact-spinner {
  inline-size: 20px; block-size: 20px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-block-start-color: currentColor;
  animation: acquire-compact-spin 0.8s linear infinite;
}
@keyframes acquire-compact-spin { to { transform: rotate(1turn); } }
/* ==========================================================================
   §10  Series load-bar (R-214–R-218)
   ========================================================================== */
.load-bar {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 2px;
}
.load-bar__seg {
  flex: 1 1 0;
  inline-size: 0;
  min-inline-size: 0;
  max-inline-size: var(--tap-min);
  aspect-ratio: 1;
  block-size: auto;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: .6rem; font-weight: 700;
  text-decoration: none; color: inherit;
}
.load-bar__seg:hover { filter: brightness(0.9); }
.load-bar__seg[data-state="read"]     { background: var(--success); border-color: var(--success); color: #fff; }
.load-bar__seg[data-state="reading"]  {
  background: repeating-linear-gradient(-45deg, var(--success) 0 5px, transparent 5px 10px);
  background-size: 18px 18px;
  border-color: var(--success);
  animation: load-bar-reading 1.1s linear infinite;
}
.load-bar__seg[data-state="owned"]    { background: transparent; border-color: var(--success); color: var(--success); }
.load-bar__seg[data-state="pending"] {
  border-color: var(--warning); color: var(--warning);
  background: repeating-linear-gradient(
    -45deg, var(--warning-weak) 0 5px, transparent 5px 10px
  );
  background-size: 18px 18px;
}
.load-bar__seg[data-state="failed"] {
  border-color: var(--danger); color: var(--danger);
  background: repeating-linear-gradient(
    -45deg, var(--danger-weak) 0 5px, transparent 5px 10px
  );
  background-size: 18px 18px;
}
.load-bar__seg[data-state="missing"] {
  background: transparent; border-color: var(--border-strong); color: var(--fg-muted);
}
.load-bar__seg[data-state="catalog-missing"] {
  background: transparent; border-color: var(--border-strong); color: var(--fg-muted);
  border-style: dashed;
}
.load-bar__summary {
  font-size: var(--fs-caption); color: var(--fg-muted);
  margin-top: var(--space-1);
}
.load-bar__legend {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  list-style: none; margin: var(--space-1) 0 0; padding: 0;
  font-size: var(--fs-caption); color: var(--fg-muted);
}
.load-bar__legend-item {
  min-block-size: 0;
  display: flex; align-items: center; gap: var(--space-1);
}
.load-bar__legend-swatch {
  inline-size: 12px; block-size: 12px;
  border-radius: 3px;
  border: 1px solid var(--border-strong);
  background: transparent;
  flex-shrink: 0;
}
.load-bar__legend-item[data-state="read"] .load-bar__legend-swatch {
  background: var(--success); border-color: var(--success);
}
.load-bar__legend-item[data-state="reading"] .load-bar__legend-swatch {
  background: repeating-linear-gradient(
    -45deg, var(--success) 0 3px, transparent 3px 5px
  );
  background-size: 8px 8px;
  border-color: var(--success);
  animation: load-bar-reading 1.1s linear infinite;
}
.load-bar__legend-item[data-state="owned"] .load-bar__legend-swatch {
  background: transparent; border-color: var(--success);
}
.load-bar__legend-item[data-state="pending"] .load-bar__legend-swatch {
  background: repeating-linear-gradient(
    -45deg, var(--warning-weak) 0 3px, transparent 3px 5px
  );
  background-size: 8px 8px;
  border-color: var(--warning);
}
.load-bar__legend-item[data-state="failed"] .load-bar__legend-swatch {
  background: repeating-linear-gradient(
    -45deg, var(--danger-weak) 0 3px, transparent 3px 5px
  );
  background-size: 8px 8px;
  border-color: var(--danger);
}
.load-bar__legend-item[data-state="missing"] .load-bar__legend-swatch {
  background: transparent; border-color: var(--border-strong);
}
.load-bar__legend-item[data-state="catalog-missing"] .load-bar__legend-swatch {
  background: transparent; border-color: var(--border-strong);
  border-style: dashed;
}

@keyframes load-bar-reading {
  from { background-position: 0 0; }
  to { background-position: 18px 18px; }
}

/* ==========================================================================
   §11  Forms and inputs
   ========================================================================== */
.field { display: grid; gap: var(--space-1); }
.field > label { color: var(--fg); font-size: var(--fs-small); font-weight: 600; }
.field__hint { color: var(--fg-muted); font-size: var(--fs-small); }
.field__error { color: var(--danger); font-size: var(--fs-small); }
/* ONE fused capsule (#784): the container owns the border and radius; segments are
   flat and divided by hairlines. N options as N separate pills was the "control farm"
   #784 calls out — and it is what the pill-density lint counts. Segments may be
   <label><input hidden>…</label> (forms) or <a> (scope navs). */
.segmented {
  display: inline-flex; flex-wrap: wrap;
  /* A form's segmented control is a <fieldset>: drop the UA padding, margin and min-width
     that inset the filled segment inside the capsule (the Shelves "Manual" chip). */
  margin: 0; padding: 0; min-inline-size: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.segmented > label, .segmented > a, .segmented > button {
  position: relative;
  min-block-size: var(--tap-min); min-inline-size: var(--tap-min);
  justify-content: center;
  display: inline-flex; align-items: center;
  padding-inline: var(--space-3);
  font-size: var(--fs-small); cursor: pointer;
  color: var(--fg); text-decoration: none;
}
/* A <button> segment (the Library's date quick-pick: JS actions, not form state) must
   shed the generic button chrome — the capsule owns the border, radius and fill. */
.segmented > button { background: none; border: 0; border-radius: 0; }
.segmented > :is(label, a, button) + :is(label, a, button) {
  border-inline-start: 1px solid var(--border-strong);
}
.segmented > label:has(input:checked), .segmented > a[aria-current],
.segmented > button[aria-pressed="true"] {
  background: var(--accent); color: var(--accent-fg);
}
.segmented > label > input {
  position: absolute; inset: -1px; inline-size: calc(100% + 2px); block-size: calc(100% + 2px);
  margin: 0; opacity: 0; cursor: pointer;
}
/* The capsule clips to its own radius, so the shared OUTSET `--ring` on a segment is
   cut to an invisible sliver — and that ring is the app's only focus indicator
   (`:focus-visible { outline: none }`). Same ring, drawn inward, so it survives the
   clip; `--ring` still carries the `prefers-contrast: more` swap to a solid 2px accent.
   A <label> segment's focus lands on its transparent <input>, whose own shadow can
   never paint, so the label wears the ring on its behalf. */
.segmented > :is(a, button):focus-visible,
.segmented > label:has(> input:focus-visible) {
  box-shadow: inset var(--ring);
}
/* …except on the ACTIVE segment, which is filled with `--accent` while `--ring-c` IS the
   accent (or a tint of it): ring against fill measured 1.00:1 in every theme/mode and
   under `prefers-contrast: more`, i.e. no indicator at all — and a radio group's single
   tab stop lands exactly there, as does a scope nav's `aria-current` link. Same ring
   width, drawn as a gap punched in the fill in the capsule's own surface colour.
   `--bg-elev`, not `--accent-fg`: the fill's foreground is white on every theme, which
   only clears 3:1 (SC 1.4.11) on the light modes — measured 2.56 on neutral dark, 2.94
   on midnight dark. The surface token holds 4.17–7.84 across all eight combinations. */
.segmented > a[aria-current]:focus-visible,
.segmented > label:has(input:checked):has(> input:focus-visible) {
  box-shadow: inset 0 0 0 var(--ring-w) var(--bg-elev);
}

/* Generic button colour variants (UI-S9 — promoted from surfaces/utilities.css so every
   surface shares one vocabulary). Colour only — geometry stays with the base `.btn`, so
   nothing here resets an acquire variant's fill. `.btn--sm` is the one smaller size. */
.btn--sm { font-size: var(--fs-small); padding: var(--space-2) var(--space-3); }
.btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.btn-primary:hover { color: var(--accent-fg); filter: brightness(1.05); }
.btn-secondary { background: var(--accent-weak); color: var(--accent); border-color: var(--accent); }
.btn-secondary:hover { background: var(--accent); color: var(--accent-fg); }
.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.btn-danger:hover { color: #fff; filter: brightness(1.05); }
.btn-ghost { background: transparent; color: var(--fg-muted); border-color: var(--border); }
.btn-ghost:hover { background: var(--bg-elev-2); color: var(--fg); }

/* Form-level error (one red sentence above the fields, R-008) and the inline error
   fragment an HTMX swap lands where the action was (error_fragment.html, #353). Both are
   shared because either can appear on any page, so neither may live in a surface file. */
.form-error {
  margin: 0; padding: var(--space-3) var(--space-4);
  background: var(--danger-weak); color: var(--danger);
  border-radius: var(--radius); font-size: var(--fs-small); font-weight: 600;
}
.error-inline {
  display: grid; gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  background: var(--danger-weak); color: var(--danger);
  border-radius: var(--radius); font-size: var(--fs-small);
}
.error-inline__message { font-weight: 600; }

/* ==========================================================================
   §12  Table cards (R-348, below 1024px)
   ========================================================================== */
.table-scroll { position: relative; max-inline-size: 100%; overflow-x: auto; }
.table-card {
  display: grid; gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
  margin-block-end: var(--space-3);
}
.table-card__title { font-weight: 700; color: var(--fg); }
.table-card__field { display: flex; justify-content: space-between; gap: var(--space-2); font-size: var(--fs-small); }
.table-card__label { color: var(--fg-muted); }
.table-card__value { color: var(--fg); font-weight: 600; text-align: end; }
.table-card__actions { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }

/* ==========================================================================
   §13  Toasts
   ========================================================================== */
.toast-region {
  position: fixed; z-index: var(--z-toast);
  inset-inline: var(--space-4);
  inset-block-end: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-3));
  pointer-events: none;
}
.toast {
  padding: var(--space-3); border-radius: var(--radius);
  background: var(--fg); color: var(--bg-elev);
  box-shadow: var(--elevation-2);
  font-size: var(--fs-small); font-weight: 600;
  animation: toast-in var(--motion-base) var(--motion-ease);
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }

/* ==========================================================================
   §14  Skeletons (R-526)
   ========================================================================== */
.skeleton {
  background: linear-gradient(90deg, var(--bg-elev-2), var(--bg-elev), var(--bg-elev-2));
  background-size: 200% 100%;
  animation: skeleton-shimmer 1s linear infinite;
  border-radius: var(--radius-sm);
}
@keyframes skeleton-shimmer { to { background-position: -200% 0; } }
.skeleton--cover { aspect-ratio: var(--cover-ratio); }
.skeleton--line  { min-block-size: var(--row-min-h); }

/* ==========================================================================
   §15  Empty states (R-319)
   ========================================================================== */
/* Sized by its content: the old 12rem floor plus --space-6 padding made a one-line "No
   Shelves yet" a 190px+ dashed box that dwarfed the page (owner report 2026-10-03). */
.empty-state {
  display: grid; place-content: center; gap: var(--space-3);
  text-align: center;
  color: var(--fg-muted);
  padding: var(--space-5) var(--space-4);
}
.empty-state a { color: var(--accent); font-weight: 600; }
/* The empty state's "next action" (R-319) is a real target, not prose: a ≥44 px pill. */
.empty-state a[href] {
  display: inline-flex; align-items: center; justify-content: center; gap: .4em;
  min-block-size: var(--tap-min); min-inline-size: var(--tap-min);
  padding: 0 var(--space-4); border-radius: var(--radius-pill);
  background: var(--accent-weak); color: var(--accent);
  font-weight: 600; text-decoration: none;
}
.empty-state a[href]:hover { filter: brightness(0.97); text-decoration: none; }

/* ==========================================================================
   §16  Home surface pages
   ========================================================================== */
.home-surface-page { }
.home-surface-count { color: var(--fg-muted); font-size: var(--fs-small); margin-bottom: var(--space-4); }

/* ==========================================================================
   §17  Book detail page
   ========================================================================== */
.book-header { grid-template-columns: 1fr; gap: var(--space-4); }
.book-cover { max-width: 200px; }
.book-user-state { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* ==========================================================================
   §18  Admin landing
   ========================================================================== */
.admin-landing__cards { grid-template-columns: 1fr; gap: var(--space-4); }
.admin-card {
  padding: 0; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-elev);
}
.admin-card a {
  display: block; padding: var(--space-3) var(--space-4) 0;
  font-weight: 700; color: var(--fg); text-decoration: none;
}
.admin-card a:hover { color: var(--accent); text-decoration: none; }
.admin-card p {
  padding: 0 var(--space-4) var(--space-3);
  font-size: var(--fs-small); color: var(--fg-muted);
}

/* ==========================================================================
   §19  Icon sizing
   ========================================================================== */
.icon { vertical-align: middle; flex-shrink: 0; }
.bottom-tabs__item .icon { inline-size: 20px; block-size: 20px; }
.shell-back .icon { inline-size: 20px; block-size: 20px; }
.settings-toggle .icon { inline-size: 18px; block-size: 18px; }

/* ==========================================================================
   §20  Review case (R-336)
   ========================================================================== */
.review-case {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4); margin: 0 0 var(--space-4);
}
.review-case-count {
  font-size: .72rem; font-weight: 600; color: var(--fg-muted);
  margin-left: var(--space-2);
}

/* ==========================================================================
   §21  Scope notes
   ========================================================================== */
.scope-note {
  padding: var(--space-3) var(--space-4);
  background: var(--bg-elev-2); border-radius: var(--radius);
  font-size: var(--fs-small); color: var(--fg-muted);
  margin-bottom: var(--space-4);
}
.scope-note a { color: var(--accent); }

/* ==========================================================================
   §21b  Ledger rows (R-505, /catalogued)
   ========================================================================== */
.ledger-row-list {
  padding: 0; list-style: none;
  display: flex; flex-direction: column;
}
.ledger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
  align-items: start;
}
.ledger-row__title {
  font-weight: 600; color: var(--fg);
  text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ledger-row__title:hover { color: var(--accent); }
.ledger-row__author {
  font-size: var(--fs-small); color: var(--fg-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-inline-size: 0;
}
.ledger-row__date {
  font-size: var(--fs-caption); color: var(--fg-faint);
  white-space: nowrap;
}

/* Task 8: shelf_detail.html's `cards` row shape falls back to this bare row for a
   non-'book' card kind (a Series/Author member — e.g. Series in Progress, Full Series
   Available, a bookmarked Series/Author). It had no styling at all: an unstyled `<a>`
   rendered at its own text line-height, well under the 44px tap-target floor the same
   `.ledger-row__title` technique above already establishes for its own title link. */
.shelf-detail-row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.shelf-detail-row__title {
  display: inline-flex; align-items: center;
  min-block-size: var(--tap-min);
  color: var(--fg); font-weight: 600; text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-inline-size: 0;
}
.shelf-detail-row__title:hover { color: var(--accent); }
.catalogued-buckets {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.catalogued-buckets a {
  display: inline-flex; align-items: center;
  min-block-size: var(--tap-min);
  padding: 0 var(--space-3);
  font-size: var(--fs-small); font-weight: 600;
  color: var(--fg-muted);
  text-decoration: none;
  border-radius: var(--radius-pill);
  background: var(--bg-elev-2);
}
.catalogued-buckets a:hover { background: var(--accent-weak); color: var(--accent); text-decoration: none; }
.catalogued-buckets a[aria-current="page"] {
  background: var(--accent); color: var(--accent-fg);
}

/* ==========================================================================
   §21c  Cached trope picker
   ========================================================================== */
.trope-picker {
  display: grid;
  gap: var(--space-2);
  min-inline-size: 0;
}
.trope-picker__label {
  color: var(--fg);
  font-size: var(--fs-small);
  font-weight: 600;
}
.trope-picker__pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}
.trope-picker__pill {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  padding-inline-start: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--accent-weak);
  color: var(--fg);
}
.trope-picker__remove {
  min-block-size: var(--tap-min);
  min-inline-size: var(--tap-min);
  padding: 0;
  border: 0;
  background: transparent;
}
.trope-picker__editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--space-2);
}
.trope-picker__editor input { min-inline-size: 0; }
.trope-picker__results {
  display: grid;
  max-block-size: 40dvh;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
}
.trope-picker__results:empty { display: none; }
.trope-picker__choice,
.trope-picker__more {
  min-block-size: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--fg);
  text-align: start;
}
.trope-picker__choice:hover,
.trope-picker__choice:focus-visible,
.trope-picker__more:hover,
.trope-picker__more:focus-visible {
  background: var(--accent-weak);
}
.trope-picker__clear {
  justify-self: start;
  min-block-size: var(--tap-min);
}
.trope-picker__empty,
.trope-picker__no-match {
  margin: 0;
  padding: var(--space-2);
}

/* ==========================================================================
   §22  Responsive breakpoints
   ========================================================================== */

/* sm: 600px — three-up grids, wider cards */
@media (min-width: 600px) {
  .library-grid { grid-template-columns: repeat(3, minmax(158px, 1fr)); }
  .admin-landing__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* md: 900px — shell and detail layout move together. */
@media (min-width: 900px) {
  .book-header {
    grid-template-columns: minmax(160px, 200px) minmax(0, 1fr);
    gap: clamp(var(--space-4), 3vw, var(--space-6));
  }
}

/* desktop: 1024px — table density */
@media (min-width: 1024px) {
  .table-card { display: none; }
  .book-row--cells { display: table-row; }
}

/* ==========================================================================
   §23  Accessibility
   ========================================================================== */
@media (pointer: coarse) {
  :is(a[href], button, [role="button"], input, select, summary).control {
    min-block-size: var(--tap-min); min-inline-size: var(--tap-min);
  }
}
@media (prefers-contrast: more) {
  .book-row--other-author { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .load-bar__seg[data-state="reading"],
  .load-bar__legend-item[data-state="reading"] .load-bar__legend-swatch {
    animation: none;
  }
  .skeleton {
    animation: none; background-image: none;
  }
  .acquire-compact-spinner { animation: none; }
}

/* Skip link for keyboard navigation */
.skip-link {
  position: absolute; left: -999px; top: auto;
  z-index: 9999; padding: var(--space-2) var(--space-4);
  background: var(--accent); color: var(--accent-fg);
  font-weight: 700; text-decoration: none;
  border-radius: 0 0 var(--radius) var(--radius);
}

/* ==========================================================================
   §24  Image upload control (R-295, R-242/R-544)
   ========================================================================== */
/* A file input hidden behind a real, visible, ≥44 px .btn — reused by the profile
   avatar upload (profile.html) and the admin author-photo replace (author.html), so it
   lives here rather than in either surface's own CSS. Same technique admin-ops.css's
   segmented-control inputs already use: the <input> is absolutely positioned to fill
   its label at opacity 0 (not display:none/visibility:hidden, so it stays focusable and
   in the accessibility tree), so its OWN hit box is the label's full real size — the
   phone gate measures the input's actual rendered box, not a 1x1 clip-hidden one. */
.image-upload-btn { position: relative; overflow: hidden; }
.image-upload-btn input[type="file"] {
  position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%;
  opacity: 0; margin: 0; cursor: pointer;
}

/* ==========================================================================
   §25  Comment editor draft notice (R-294)
   ========================================================================== */
.comment-draft-notice {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-caption);
  color: var(--warning);
  font-weight: 600;
}
.skip-link:focus { left: var(--space-4); top: 0; }

/* ==========================================================================
   §26  Avatar / initials (R-295, #957)
   ========================================================================== */
/* Promoted from surfaces/utilities.css: the header's Profile control (base.html)
   renders this same circle on every signed-in page, not only the personal
   surfaces that link utilities.css — so the rule must live on a sheet every
   page loads, not a surface-scoped one. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  color: #fff;
  background: hsl(var(--avatar-hue, 220) 60% 52%);
  font-weight: 700;
  flex-shrink: 0;
}
.avatar--lg { inline-size: 64px; block-size: 64px; font-size: 22px; }
.avatar--md { inline-size: 44px; block-size: 44px; font-size: 15px; }
.avatar--sm { inline-size: 32px; block-size: 32px; font-size: 12px; }
/* An uploaded profile image (R-295) fills the same circle the initials fill —
   object-fit: cover crops to the circle rather than squashing a non-square photo. */
.avatar__image { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.avatar__initials {
  font-family: var(--font-display);
  letter-spacing: -0.02em;
  line-height: 1;
}

/* Try harder (ADR-0053): a text link that is still a 44 px target. */
.th-link { display: inline-flex; align-items: center; min-block-size: var(--tap-min); }
/* The Try harder control (ADR-0053, `_try_harder_control.html`): the button and its "more" link
   wrap on a phone and never overflow; the quiet link sits under real matches. A refusal's
   reason takes the whole row under them, and the empty slot takes no room at all. */
.th-control { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.th-control__refused { flex: 1 1 100%; min-inline-size: 0; }
.th-control__refused:empty { display: none; }
.th-link--quiet { color: var(--fg-muted); font-size: var(--fs-small); }
