/* florilegium — household reading tracker in the wrkflw house style.
   Flat, compact, 2px radii, gold accents, deliberately light-only.
   The bookshelf (from #shelf onward) is the primary view; the
   transit-map rules earlier in this file back a parked, unlinked
   browsing mode (#hub / #tree). */

:root {
  --paper: #fafafa;
  --card: #ffffff;
  --ink: #2b2b2b;
  --ink-soft: #6c757d;
  --hairline: #e0e1e4;
  --ghost: #a9abb0;
  --gold: #d5a63a;
  --gold-dk: #b88c2f;
  --gold-soft: #f3f0e6;
  --link: #8a6a1c;
  --danger: #b0433a;
  --display: 'Hammersmith One', 'Gill Sans', 'Trebuchet MS', sans-serif;
  --body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
  --panel-w: 400px;
  --r: 2px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--body);
  color: var(--ink);
  background: var(--paper);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

button { font: inherit; cursor: pointer; }
input, select { font: inherit; }

/* ── Brand: straight-set wordmark, the two i-dots in gold ───
   (em-sized so the dots scale with wherever .brand-word is used —
   topbar 16px, auth 20px, hero 34px). */
.brand-word {
  font-family: var(--display); font-weight: 400; letter-spacing: .04em;
  color: var(--ink);
}
.brand-word .wm-i { position: relative; display: inline-block; }
.brand-word .wm-dot {
  position: absolute; left: 50%; top: -0.02em; transform: translateX(-50%);
  width: 0.16em; height: 0.16em; border-radius: 50%;
  background: var(--gold-dk); font-style: normal;
}
.brand-word-lg { font-size: 34px; }
.brand-word-md { font-size: 20px; }
.brand-btn { background: none; border: 0; padding: 2px 4px; display: flex; align-items: center; }
.brand-btn .brand-word { font-size: 16px; }

/* ── Top bar ─────────────────────────────────────────── */
/* Three real grid tracks — outer two size to their CONTENT (auto), the
   middle takes the leftover and centres the nav inside it. The earlier
   1fr|min-content|1fr version centred the nav to the topbar exactly,
   but paid for it by splitting free space equally between two very
   unequal sides, starving the search box down to a sliver at laptop
   widths. auto|1fr|auto lets the search keep a real width (clamp
   below) and the nav floats centred in whatever gap remains between
   search and stats — which is also literally what was asked for:
   "centered to the search bar and the stats". */
#topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px;
  height: 52px; padding: 0 12px;
  background: var(--card);
  border-bottom: 2px solid var(--gold);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .08);
}
.topbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
.dock-search { position: relative; width: clamp(220px, 36vw, 430px); }
.dock-search input {
  width: 100%; height: 34px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  background: var(--paper); outline: none;
}
.dock-search input:focus { border-color: var(--gold); background: #fff; }
.topbar-nav { display: flex; align-items: center; gap: 8px; justify-self: center; white-space: nowrap; }
.topbar-right { display: flex; align-items: center; gap: 8px; justify-self: end; }
/* Bookshelf nav icon — a row of spines with one leaning, standing in
   for a text label since one household has SHELVES, plural. */
.nav-shelf { display: inline-flex; align-items: center; padding: 4px 6px; }
.nav-shelf svg { width: 21px; height: 21px; fill: currentColor; }
.topbar-stats { display: none; gap: 20px; }
.topbar-stats .stat b { font-size: 16px; }
.topbar-stats .stat span { font-size: 8px; }

/* ── Buttons / chips ─────────────────────────────────── */
.btn {
  border: 1px solid var(--ink); border-radius: var(--r);
  padding: 6px 13px; background: var(--card); color: var(--ink);
  font-family: var(--display); font-size: 12.5px; letter-spacing: .05em;
}
.btn:hover { background: var(--gold-soft); }
.btn.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.btn.on:hover { background: var(--ink); }
.btn-solid { background: var(--gold); border-color: var(--gold-dk); color: var(--ink); }
.btn-solid:hover { background: var(--gold-dk); color: #fff; }
.btn-quiet { border-color: transparent; background: none; color: var(--ink-soft); font-size: 16px; padding: 4px 8px; }
.btn-quiet:hover { background: none; color: var(--ink); }
.btn-wide { width: 100%; }
.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-danger { border-color: var(--danger); color: var(--danger); }
.btn-danger:hover { background: #f8ecea; }
.link-btn { background: none; border: 0; color: var(--link); text-decoration: underline; padding: 4px; }

.chip {
  display: inline-flex; align-items: center;
  border-radius: 999px; padding: 3px 10px;
  font-size: 12px; font-weight: 600; color: #fff;
}

/* ── Hero ────────────────────────────────────────────── */
#hero {
  height: 100dvh; display: flex; align-items: center; justify-content: center;
  background: var(--paper);
  transition: opacity .45s ease, transform .45s ease;
}
#hero.leaving { opacity: 0; transform: translateY(-40px) scale(.98); pointer-events: none; }
.hero-inner { display: flex; flex-direction: column; align-items: center; gap: 20px; padding: 24px; width: min(560px, 94vw); }
.hero-search { position: relative; width: 100%; }
.hero-search input {
  width: 100%; height: 52px; padding: 0 18px;
  font-size: 17px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  outline: none; background: #fff;
  box-shadow: 0 1px 3px rgba(43, 43, 43, .07);
}
.hero-search input:focus { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(213, 166, 58, .18); }
.hero-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.hero-chips button {
  border: 1px solid var(--hairline); border-radius: var(--r); background: #fff;
  padding: 6px 13px; font-size: 13px; color: var(--ink-soft);
  display: inline-flex; align-items: center; gap: 8px;
}
.hero-chips button::before { content: ''; width: 14px; height: 4px; border-radius: 2px; background: var(--c, var(--gold)); }
.hero-chips button:hover { border-color: var(--c, var(--gold)); color: var(--ink); }
.hero-empty { color: var(--ink-soft); }

/* ── Autocomplete ────────────────────────────────────── */
.ac-drop {
  /* min-width, not left:0;right:0 tied exactly to the search box —
     dock-search itself can get squeezed on a narrower window (it now
     shares the topbar with a centred nav), and a dropdown row with a
     cover + text + two action buttons needs real room regardless of
     how narrow its trigger got. */
  position: absolute; top: calc(100% + 5px); left: 0; width: max(100%, 300px); z-index: 60;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  max-height: 320px; overflow: auto; padding: 4px;
  box-shadow: 0 8px 26px rgba(43, 43, 43, .14);
}
.ac-group { font-family: var(--display); font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ghost); padding: 8px 10px 2px; }
.ac-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: 0; background: none; border-radius: var(--r); text-align: left; font-size: 14px;
}
.ac-item .sub { color: var(--ghost); font-size: 12px; margin-left: auto; padding-left: 12px; white-space: nowrap; }
.ac-item.on, .ac-item:hover { background: var(--gold-soft); }
.ac-new { color: var(--link); font-weight: 600; }
.bm-lookup-wrap { position: relative; }
.ac-item .ac-cover { width: 26px; height: 38px; object-fit: cover; background: #eee; flex: none; border-radius: 1px; }
.ac-item .ac-cover.ac-cover-blank { display: flex; align-items: center; justify-content: center; font-family: var(--display); font-size: 12px; color: #bbb; }
.ac-ol-item { cursor: pointer; }
/* flex:1 so the actions column always lands flush against the row's
   right edge — without it, the star/plus pair trails immediately after
   the title text instead, drifting left/right with title length rather
   than lining up in a scannable column row to row. */
.ac-ol-text { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.ac-ol-text .sub { margin-left: 0; padding-left: 0; }
.ac-ol-series { font-size: 11px; font-style: italic; color: var(--ghost); overflow: hidden; text-overflow: ellipsis; }
.ac-ol-actions { flex: none; display: flex; align-items: center; gap: 4px; }
.ac-ol-star, .ac-ol-plus, .ac-ol-read {
  flex: none; width: 26px; height: 26px; border: 1px solid var(--hairline); border-radius: 50%;
  background: #fff; color: var(--ink-soft); font-size: 14px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.ac-ol-star:hover { border-color: var(--gold); color: var(--gold); }
.ac-ol-plus:hover { border-color: var(--link); color: var(--link); }
.ac-ol-read { font-size: 12px; }
.ac-ol-read:hover { border-color: #1f5c52; color: #1f5c52; }
/* The whole-series row sits ABOVE that series' individual volumes, styled
   like .ac-new (bold, tinted) so it reads as the primary way to grab a
   series in one shot rather than a stray icon glued to the group label. */
.ac-ol-addall { color: var(--gold-dk); font-weight: 600; }
.ac-ol-addall:disabled { opacity: .4; cursor: default; }
.ac-ol-more { color: var(--link); font-weight: 600; justify-content: center; }

/* ── Author catalog page (<name> search → #author=…) ── */
.ac-author-photo {
  width: 30px; height: 30px; border-radius: 50%; object-fit: cover;
  background: #eee; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display); font-size: 12px; color: #bbb;
}
.cat-series { border: 1px solid var(--hairline); border-radius: var(--r); overflow: hidden; }
.cat-summary {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; border: 0; background: none; text-align: left; font-size: 14px;
}
.cat-series.open .cat-summary, .cat-summary:hover { background: var(--gold-soft); }
.cat-caret { font-family: var(--display); color: var(--gold-dk); width: 12px; flex: none; }
.cat-series-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-count { color: var(--ghost); font-size: 12px; margin-left: auto; flex: none; }
.cat-addall {
  flex: none; font-size: 12px; font-weight: 600; color: var(--gold-dk);
  border: 1px solid var(--gold); border-radius: var(--r); padding: 3px 9px; background: #fff;
}
.cat-addall:hover { background: var(--gold); color: #fff; }
.cat-have { flex: none; font-size: 12px; color: var(--gold-dk); }
.cat-rows { border-top: 1px solid var(--hairline); padding: 4px 8px; }
.cat-row { display: flex; align-items: center; gap: 10px; padding: 6px 4px; }
/* Catalogue rows reuse .ac-cover but sit OUTSIDE .ac-item, where the only
   sizing rule lives — so they need their own or a real cover renders at
   full natural size and blows the row apart. The blank variant is a quiet
   empty box that just holds the titles in a column. */
.cat-row .ac-cover {
  width: 26px; height: 38px; object-fit: cover; flex: none;
  background: #f0f0f0; border-radius: 1px;
}
.cat-row .ac-cover.ac-cover-blank { background: #f4f4f6; }
.cat-row + .cat-row { border-top: 1px solid #f1f1f3; }
.cat-check {
  flex: none; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  color: var(--gold-dk); font-size: 15px; font-weight: 700;
}

/* ── Map ─────────────────────────────────────────────── */
#map-wrap {
  position: fixed; inset: 52px 0 0 0;
  touch-action: none;
  background: var(--paper);
}
#map { width: 100%; height: 100%; display: block; cursor: grab; }
#map.panning { cursor: grabbing; }
.map-hint {
  position: absolute; left: 12px; bottom: calc(10px + env(safe-area-inset-bottom));
  font-size: 11px; color: var(--ghost); pointer-events: none;
  font-family: var(--display); letter-spacing: .08em;
}

#map-legend {
  position: absolute; right: 14px; bottom: calc(14px + env(safe-area-inset-bottom));
  background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 12px 16px; max-width: 240px;
  box-shadow: 0 2px 12px rgba(43, 43, 43, .08);
}
.lg-title { font-family: var(--display); font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 5px; }
.lg-row { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--ink);
  padding: 2.5px 0; border: 0; background: none; width: 100%; text-align: left;
  text-transform: capitalize; }
.lg-row i { width: 20px; height: 5px; border-radius: 2.5px; flex: none; }
button.lg-link { cursor: pointer; }
.lg-link:hover { color: var(--link); text-decoration: underline; }
@media (max-width: 560px) { #map-legend { display: none; } }

#map .line-path { fill: none; stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; }
#map .line-label { font-family: var(--display); font-size: 13px; letter-spacing: .03em; }
#map .line-sub { font-family: var(--body); font-size: 10.5px; fill: var(--ghost); }
#map .stn { cursor: pointer; }
#map .stn .core { transition: transform .12s ease; transform-box: fill-box; transform-origin: center; }
#map .stn:hover .core { transform: scale(1.25); }
#map .stn:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
#map .stn-label {
  font-family: var(--body); font-size: 12.5px; font-weight: 600; fill: var(--ink);
  paint-order: stroke; stroke: var(--paper); stroke-width: 4px; stroke-linejoin: round;
  pointer-events: none;
}
#map .stn-label.dim { fill: var(--ghost); font-weight: 500; }
#map .inter-label { font-family: var(--display); font-size: 13px; fill: var(--ink);
  paint-order: stroke; stroke: var(--paper); stroke-width: 4px; stroke-linejoin: round; pointer-events: none; }
#map .origin-label {
  font-family: var(--display); font-size: 21px; fill: var(--ink);
  paint-order: stroke; stroke: var(--paper); stroke-width: 6px; stroke-linejoin: round;
}
#map .origin-sub { font-family: var(--body); font-size: 12px; fill: var(--ink-soft);
  paint-order: stroke; stroke: var(--paper); stroke-width: 4px; }
#map .connect-name { font-family: var(--display); font-size: 14px; fill: var(--ink); cursor: pointer;
  paint-order: stroke; stroke: var(--paper); stroke-width: 5px; stroke-linejoin: round; }
#map .connect-name:hover { fill: var(--link); }
#map .series-label { font-family: var(--body); font-size: 13.5px; font-weight: 600; fill: var(--ink); }
#map .series-progress { font-family: var(--body); font-size: 12px; fill: var(--ink-soft); }
#map .zone-label { font-family: var(--display); font-size: 13px; letter-spacing: .2em; opacity: .9; pointer-events: none; }
#map .cover-title { font-family: var(--body); font-size: 10.5px; font-weight: 600; fill: var(--ink); pointer-events: none; }
#map .cover-num { font-family: var(--body); font-size: 9.5px; fill: var(--ink-soft); pointer-events: none; }
#map .cover-init { font-family: var(--display); font-size: 26px; fill: rgba(255,255,255,.85); pointer-events: none; }
#map .dim2 { fill: var(--ghost); }
#map .cover { cursor: pointer; }

@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes pop { from { opacity: 0; transform: scale(.2); } to { opacity: 1; transform: scale(1); } }
#map .anim-line { animation: draw .7s ease forwards; }
#map .anim-stn { animation: pop .3s ease backwards; transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
  #map .anim-line, #map .anim-stn { animation: none !important; stroke-dashoffset: 0 !important; }
  #hero { transition: none; }
}

/* ── Panel (bottom sheet -> side panel) ──────────────── */
#panel {
  position: fixed; z-index: 50;
  left: 0; right: 0; bottom: 0; top: auto;
  max-height: 72dvh;
  background: var(--card);
  border-top: 2px solid var(--gold);
  border-radius: 6px 6px 0 0;
  box-shadow: 0 -10px 34px rgba(43, 43, 43, .18);
  display: flex; flex-direction: column;
  transform: translateY(105%);
  transition: transform .28s cubic-bezier(.2, .8, .25, 1);
}
#panel.open { transform: translateY(0); }
.panel-grab { display: flex; justify-content: flex-end; padding: 6px 10px 0; }
.panel-grab::before {
  content: ''; position: absolute; left: 50%; top: 10px; transform: translateX(-50%);
  width: 44px; height: 5px; border-radius: 3px; background: var(--hairline);
}
#panel-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 20px calc(24px + env(safe-area-inset-bottom)); }

.pb-line { display: flex; align-items: center; gap: 8px; font-family: var(--display); font-size: 12px;
  letter-spacing: .06em; border-radius: var(--r); padding: 4px 12px; width: fit-content; }
.pb-title { font-family: var(--display); font-size: 24px; margin: 10px 0 2px; line-height: 1.15; }
.pb-author { margin: 0 0 4px; }
.pb-author button { background: none; border: 0; padding: 0; color: var(--link); text-decoration: underline; font-size: 14px; }
.pb-progress { display: flex; align-items: center; gap: 5px; margin: 10px 0 2px; flex-wrap: wrap; }
.pb-progress .p-dot { width: 12px; height: 12px; border-radius: 50%; border: 2.5px solid var(--c); background: #fff; }
.pb-progress .p-dot.full { background: var(--c); }
.pb-progress .p-dot.ghost { border-style: dashed; border-color: var(--ghost); }
.pb-progress .p-txt { font-size: 12px; color: var(--ink-soft); margin-left: 4px; }

.pb-section { margin-top: 18px; }
.pb-h {
  font-family: var(--display); font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink); border-bottom: 2px solid var(--gold); padding-bottom: 4px; margin: 0 0 10px;
  display: flex; align-items: baseline; gap: 8px;
}
.pb-h .note { font-family: var(--body); font-size: 11px; letter-spacing: 0; text-transform: none; color: var(--ghost); margin-left: auto; }

.reader-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; flex-wrap: wrap; }
.reader-row .chip { flex: none; }
.seg { display: inline-flex; border: 1px solid var(--hairline); border-radius: var(--r); overflow: hidden; }
.seg button { border: 0; background: #fff; padding: 4px 11px; font-size: 12px; color: var(--ink-soft); }
.seg button.on { background: var(--ink); color: #fff; }
.own-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-soft); }
.own-toggle input { width: 16px; height: 16px; accent-color: var(--gold-dk); }
.track-btn { border: 1px dashed var(--hairline); background: none; border-radius: var(--r); padding: 4px 12px; font-size: 12px; color: var(--ink-soft); }
.track-btn:hover { border-color: var(--gold); color: var(--ink); }

.tag-cloud { display: flex; flex-wrap: wrap; gap: 6px; }
.tag { border: 1px solid var(--hairline); border-radius: var(--r); padding: 3px 9px; font-size: 12px; color: var(--ink-soft); background: #fff; }
.tag.on { border-color: var(--gold-dk); color: var(--ink); background: var(--gold-soft); }

.rec-card {
  display: flex; align-items: center; gap: 10px; width: 100%;
  border: 1px solid var(--hairline); border-radius: var(--r); background: #fff;
  padding: 10px 12px; margin-bottom: 8px; text-align: left;
}
.rec-card:hover { border-color: var(--gold); }
.rec-main { min-width: 0; flex: 1; }
.rec-title { font-weight: 700; font-size: 14px; }
.rec-sub { font-size: 12px; color: var(--ink-soft); }
.rec-why { font-size: 12px; color: var(--ink-soft); margin-top: 3px; font-style: italic; }
.rec-score { font-family: var(--display); font-size: 11px; color: var(--gold-dk); flex: none; }
.rec-claude { border-style: dashed; }
.claude-src { display: flex; align-items: center; gap: 8px; }
.pb-actions { display: flex; gap: 8px; margin-top: 22px; }

/* ── Overlays / modals ───────────────────────────────── */
.overlay {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(43, 43, 43, .42);
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.auth-card, .modal-card {
  background: var(--card); border-radius: 4px; border-top: 3px solid var(--gold);
  padding: 28px; width: min(430px, 100%);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  max-height: 92dvh; overflow-y: auto;
}
.modal-card { align-items: stretch; width: min(520px, 100%); position: relative; }
/* Same quiet corner × as .bc-close-btn on the book-detail card — every
   .modal-card overlay gets one now, not just that one, since click-
   outside-to-dismiss isn't discoverable and "Cancel"/"Done" at the
   bottom of a long scrolling form is easy to miss. */
.modal-close-btn {
  position: absolute; top: 10px; right: 10px;
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  width: 26px; height: 26px; padding: 0; display: flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1; color: var(--ink-soft);
}
.modal-close-btn:hover { color: var(--ink); border-color: var(--ink-soft); }

/* Settings → Moderation (admin only): one row per account. */
.rm-mod-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--hairline); font-size: 13px;
}
.rm-mod-row:last-child { border-bottom: 0; }
.rm-mod-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rm-mod-who .rec-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rm-mod-badge {
  font-family: var(--display); font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gold-dk); flex: none;
}
.auth-card h1 { font-family: var(--display); font-size: 19px; margin: 4px 0 0; text-align: center; color: var(--ink); }
#auth-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
#auth-form input, .modal-card input[type=text], .modal-card input[type=number],
.modal-card input[type=search], .modal-card select {
  height: 42px; padding: 0 12px; border: 1px solid var(--hairline); border-radius: var(--r); outline: none; width: 100%;
  background: #fff;
}
#auth-form input:focus, .modal-card input:focus, .modal-card select:focus { border-color: var(--gold); }
.auth-err { color: var(--danger); font-size: 13px; min-height: 17px; margin: 0; text-align: center; }

.modal-title { font-family: var(--display); font-size: 19px; color: var(--ink); margin: 0 0 6px; }
.f-row { display: flex; gap: 10px; }
.f-row > * { flex: 1; }
.f-label { font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 12px 0 5px; }
.modal-actions { display: flex; gap: 10px; margin-top: 18px; justify-content: flex-end; }
.swatches { display: flex; gap: 8px; flex-wrap: wrap; }
.swatches button { width: 24px; height: 24px; border-radius: 50%; border: 3px solid transparent; }
.swatches button.on { border-color: var(--ink); }
/* Each reader in its own bordered container (was just a bottom-border
   divider — a real box reads more clearly as "these fields belong to
   one person" once there are several readers). name+private are
   grouped into their own nested flex item (.rm-name-row, nowrap) so
   they always stay on the same line, same trick .rm-colors already
   uses for swatches+hex — colours/delete are free to wrap onto their
   own line below if the modal's too narrow, name+private never split.
   Goodreads link gets its own full-width line, since a URL needs room. */
.reader-manage {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px; border: 1px solid var(--hairline); border-radius: var(--r);
}
.reader-manage + .reader-manage { margin-top: 10px; }
.rm-row1 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rm-name-row { display: flex; align-items: center; gap: 10px; flex: 1 1 180px; min-width: 150px; flex-wrap: nowrap; }
.rm-name-row .rm-name { flex: 1 1 auto; min-width: 80px; }
.rm-username-row { display: flex; align-items: center; gap: 4px; flex: none; }
.rm-at { color: var(--ink-soft); font-size: 13px; }
.rm-colors { display: flex; align-items: center; gap: 8px; flex: none; flex-wrap: nowrap; }
.rm-colors .swatches { flex: none; flex-wrap: nowrap; }
/* Rescoped through .modal-card input.rm-hex / .rm-username to beat the
   global .modal-card input[type=text] rule (0,2,1) which would
   otherwise stretch these to width:100%/height:42px — same specificity
   gotcha documented for .bc-field-plain / .bc-num-plain. */
.modal-card input.rm-hex, .modal-card input.rm-username {
  height: 26px; padding: 0 6px; font-size: 11px; color: var(--ink-soft);
  border: 1px solid var(--hairline); border-radius: var(--r); background: #fff;
}
.modal-card input.rm-hex { width: 74px; font-family: monospace; }
.modal-card input.rm-username { width: 110px; font-family: var(--body); }
.modal-card input.rm-hex:focus, .modal-card input.rm-username:focus { border-color: var(--gold); color: var(--ink); }
.rm-public { flex: none; white-space: nowrap; }
.rm-row1 .rm-del { margin-left: auto; }
.rm-goodreads { width: 100%; }

/* ── Bulk add from barcode photos ────────────────────── */
.ba-modal { width: min(640px, 100%); }
#ba-readers { display: flex; gap: 14px; flex-wrap: wrap; }
.ba-drop-zone { display: flex; align-items: center; gap: 12px; margin-top: 14px; padding: 10px 0; border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
.ba-hint { font-size: 12.5px; color: var(--ink-soft); }
.ba-list { display: flex; flex-direction: column; gap: 8px; max-height: 46vh; overflow-y: auto; margin-top: 12px; }
.ba-row { display: flex; align-items: center; gap: 12px; padding: 8px; border: 1px solid var(--hairline); border-radius: var(--r); }
.ba-thumb { width: 44px; height: 44px; object-fit: cover; border-radius: 2px; flex: none; background: #f2f1ec; }
.ba-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ba-status { font-size: 12.5px; color: var(--ink-soft); margin: 0; }
.ba-status-err { color: var(--danger); }
.ba-field { width: 100%; height: 30px; padding: 0 8px; border: 1px solid transparent; background: transparent; border-radius: var(--r); font-size: 13.5px; }
.ba-field:hover { border-color: var(--hairline); background: #fafafa; }
.ba-field:focus { border-color: var(--gold); background: #fff; outline: none; }
.ba-field-sub { font-size: 12px; color: var(--ink-soft); height: 24px; }
.ba-isbn { font-size: 11px; color: var(--ghost); padding: 0 8px; }
.ba-row-select input { width: 18px; height: 18px; accent-color: var(--gold-dk); }
.ba-remove { flex: none; color: var(--ink-soft); font-size: 15px; }

.gc-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 4px 10px; max-height: 50vh; overflow-y: auto; }
.gc-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; color: var(--ink-soft); }
.gc-row.set { color: var(--ink); font-weight: 600; }
.gc-row input[type=color] {
  width: 26px; height: 26px; padding: 0; border: 1px solid var(--hairline); border-radius: 50%;
  overflow: hidden; flex: none; background: none; cursor: pointer;
}
.gc-row input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.gc-row input[type=color]::-webkit-color-swatch { border: 0; border-radius: 50%; }

#toast {
  position: fixed; left: 50%; bottom: calc(22px + env(safe-area-inset-bottom)); transform: translateX(-50%);
  background: var(--ink); color: #fff; border-radius: var(--r); padding: 9px 18px;
  font-size: 13.5px; z-index: 100; box-shadow: 0 6px 20px rgba(43, 43, 43, .3);
}

/* Bulk-select action bar — same fixed/bottom-center idiom as #toast
   (so it reads as part of the same "floating feedback" family) but
   z-index 90, one below toast's 100: every bulk action clears the
   selection (hiding this bar) right before its own summary toast
   fires, so they shouldn't overlap in practice, but if a stray toast()
   ever does land while this is still up, the toast should still win. */
#shelf-select-bar {
  position: fixed; left: 50%; bottom: calc(22px + env(safe-area-inset-bottom)); transform: translateX(-50%);
  z-index: 90; display: flex; align-items: center; gap: 14px;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  box-shadow: 0 8px 26px rgba(43, 43, 43, .18);
  padding: 9px 14px;
}
.ssb-count { font-family: var(--display); font-size: 13px; color: var(--ink); white-space: nowrap; }
.ssb-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2.5px solid rgba(184, 140, 47, .25); border-top-color: var(--gold-dk);
  animation: spin .7s linear infinite; display: inline-block; vertical-align: -3px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Desktop ─────────────────────────────────────────── */
@media (min-width: 900px) {
  .topbar-stats { display: flex; }
  #panel {
    left: auto; top: 52px; bottom: 0; right: 0;
    width: var(--panel-w); max-height: none;
    border-radius: 0; border-top: 0; border-left: 2px solid var(--gold);
    transform: translateX(105%);
  }
  #panel.open { transform: translateX(0); }
  .panel-grab::before { display: none; }
}

/* ── The bookshelf ───────────────────────────────────── */
#shelf {
  position: fixed; inset: 52px 0 0 0;
  overflow-y: auto; overflow-x: hidden;
  background: #fff;
  padding: 18px 26px calc(60px + env(safe-area-inset-bottom));
}
#shelf-head {
  display: flex; align-items: flex-end;
  gap: 16px; flex-wrap: wrap; margin-bottom: 26px;
}
.stat { display: flex; flex-direction: column; gap: 1px; }
.stat b { font-family: var(--display); font-size: 21px; font-weight: 400; color: var(--ink); line-height: 1.1; }
.stat span { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }

#shelf-suggest:not(:empty) { margin-bottom: 26px; }
#shelf-suggest-list { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
#shelf-suggest-list .rec-card { flex: 0 0 240px; margin-bottom: 0; }

#shelf-popular { margin-bottom: 18px; }
.rec-cover { width: 34px; height: 50px; object-fit: cover; background: #eee; flex: none; border-radius: 1px; display: block; }
.rec-cover-blank { display: flex; align-items: center; justify-content: center; font-family: var(--display); font-size: 15px; color: #bbb; }

/* Collapsed by default — slides in from the left (max-width/opacity
   transition) when #popular-toggle is clicked, rather than dropping
   down from the top. Rows are already full-line (cover+title+actions
   left-to-right), so a horizontal reveal reads more naturally than a
   vertical one; capped to a fixed max-width instead of stretching to
   the shelf's full content width, so it reads as its own narrow list
   rather than a full-width banner. */
/* ── Home page ───────────────────────────────────────── */
#home {
  position: fixed; inset: 52px 0 0 0;
  overflow-y: auto; overflow-x: hidden;
  background: var(--paper);
  padding: 26px 26px calc(60px + env(safe-area-inset-bottom));
}
.home-head { max-width: 1100px; margin: 0 auto 6px; }
.home-head h2 { margin: 0; font-family: var(--display); font-weight: 400; font-size: 24px; }
.home-head .rec-sub { margin: 3px 0 0; }
.home-sec { max-width: 1100px; margin: 26px auto 0; }
.home-sec .seg { margin-bottom: 10px; display: inline-flex; }
#home #popular-list { max-width: 520px; }
.home-row {
  display: flex; gap: 12px; overflow-x: auto; padding-bottom: 6px;
  scrollbar-width: thin;
}
.hb-card {
  flex: 0 0 250px; display: flex; gap: 11px; align-items: flex-start;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 11px; cursor: pointer; text-decoration: none; color: inherit;
}
.hb-card:hover { border-color: var(--gold); }
.hb-cover { width: 46px; height: 68px; object-fit: cover; background: #eee; flex: none; border-radius: 1px; }
.hb-cover-blank { display: flex; align-items: center; justify-content: center; font-family: var(--display); font-size: 18px; color: #bbb; }
.hb-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.hb-title { font-weight: 700; font-size: 13.5px; line-height: 1.25; }
.hb-author { font-size: 12px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hb-sub { font-size: 11px; color: var(--ghost); }
.hb-chip { align-self: flex-start; padding: 1px 8px; font-size: 10.5px; margin-top: 2px; }
.hb-prog { height: 4px; border-radius: 2px; background: #eceae2; overflow: hidden; margin-top: 2px; }
.hb-prog i { display: block; height: 100%; border-radius: 2px; }
.popular-card { flex: 0 0 auto; }
/* Star (interested) + plus (own it) sit beside the title now, not
   overlaid on the cover — plain persistent icon buttons, not a hover
   reveal, since there's no image underneath them to keep clear. */
.pp-actions { display: flex; gap: 6px; flex: none; margin-left: auto; }
.pp-star, .pp-plus {
  width: 28px; height: 28px; border: 1px solid var(--hairline); border-radius: var(--r);
  background: #fff; color: var(--ink-soft); font-size: 15px;
  display: flex; align-items: center; justify-content: center;
}
.pp-star:hover, .pp-plus:hover { border-color: var(--gold-dk); color: var(--ink); }
.pp-star.on { border-color: var(--gold-dk); color: var(--gold-dk); background: var(--gold-soft); }
.pp-plus.on { border-color: var(--gold-dk); color: #fff; background: var(--gold-dk); cursor: default; }

#reader-switch { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 18px; }
#reader-switch .rs-arrow {
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
  font-size: 15px; color: var(--ink-soft);
}
#reader-switch .rs-arrow:hover { border-color: var(--gold); color: var(--ink); }
#reader-switch .rs-current {
  display: flex; align-items: center; justify-content: center; min-width: 140px;
  font-family: var(--display); font-weight: 400; font-size: 16px; color: var(--ink);
}
.rs-goodreads {
  font-size: 11.5px; color: var(--link); text-decoration: none;
  border: 1px solid var(--hairline); border-radius: var(--r); padding: 3px 10px;
  margin-left: 8px; white-space: nowrap;
}
.rs-goodreads:hover { border-color: var(--gold-dk); background: var(--gold-soft); }
#shelf-cases { transition: transform .18s ease, opacity .18s ease; }
#shelf-cases.rs-out { transform: translateX(var(--rs-dx, -18px)); opacity: 0; }

/* ── Browse: other households' public bookcases ─────── */
#browse, #pubshelf, #authorcat {
  position: fixed; inset: 52px 0 0 0;
  overflow-y: auto; overflow-x: hidden;
  background: #fff;
  padding: 22px 26px calc(60px + env(safe-area-inset-bottom));
}
#authorcat-head {
  margin-bottom: 18px; max-width: 640px;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
}
.authorcat-head-tools { display: flex; align-items: center; gap: 8px; flex: none; }
#cat-lang { height: 32px; padding: 0 8px; border: 1px solid var(--hairline); border-radius: var(--r); background: #fff; color: var(--ink); flex: none; }
#cat-lang:focus { border-color: var(--gold); outline: none; }
#cat-clean-btn:disabled { opacity: .6; cursor: default; }
#authorcat-body { display: flex; flex-direction: column; gap: 2px; max-width: 640px; }
/* Catalog rows show the FULL title — wrap instead of ellipsing. */
.cat-title-text { white-space: normal; overflow: visible; text-overflow: clip; }
#browse-head { justify-content: space-between; margin-bottom: 22px; }
#browse-head input { height: 34px; padding: 0 12px; border: 1px solid var(--hairline); border-radius: var(--r); min-width: 240px; outline: none; }
#browse-head input:focus { border-color: var(--gold); }
#browse-list { display: flex; flex-direction: column; gap: 8px; max-width: 560px; }
#browse-list .rec-card { gap: 12px; }
#pubshelf-head { display: flex; flex-direction: column; gap: 8px; margin-bottom: 22px; align-items: flex-start; }

/* ── Book clubs: directory of clubs + one club's pick & discussion ── */
#clubs, #club, #talk, #topic {
  position: fixed; inset: 52px 0 0 0;
  overflow-y: auto; overflow-x: hidden;
  background: #fff;
  padding: 22px 26px calc(60px + env(safe-area-inset-bottom));
}
#clubs-head, #club-head, #talk-head, #topic-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
#clubs-body { max-width: 560px; }
#clubs-list { display: flex; flex-direction: column; gap: 8px; }
/* Directory cards are divs (they hold a real join/leave button, and a
   button can't nest inside a button), so the pointer is restored. */
.club-dir-card { gap: 12px; cursor: pointer; align-items: center; }
.club-dir-card .btn { flex: none; }

.club-create { display: flex; flex-direction: column; gap: 10px; margin-bottom: 26px; }
.club-create input {
  width: 100%; height: 36px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  font: inherit; font-size: 13.5px; outline: none; background: #fff;
}
.club-create input:focus { border-color: var(--gold); }

#club-body { max-width: 680px; }

/* My readers' member/non-member state, one quiet row each. */
.club-membership { display: flex; flex-direction: column; gap: 6px; margin-bottom: 22px; }
.club-member-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-soft); }
.club-member-row .chip { flex: none; font-size: 11.5px; }
.club-current { display: flex; gap: 26px; align-items: flex-start; flex-wrap: wrap; }
/* The pick stands on its own miniature shelf — the same bottom board
   the real bookcases use (.case), sized to a single book. */
.club-shelf { display: flex; align-items: flex-end; flex: none; border-bottom: 3px solid #d9d9d9; padding: 6px 14px 0; }
.club-meta { min-width: 200px; flex: 1; }
.club-title { font-family: var(--display); font-size: 22px; font-weight: 400; margin: 10px 0 3px; line-height: 1.15; }
.club-pickline { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12.5px; color: var(--ink-soft); margin: 12px 0 0; }
.club-pickline .chip { font-size: 11.5px; }
.club-due { color: var(--gold-dk); font-style: italic; }
.club-status { margin-top: 10px; }
.club-who { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-soft); margin-right: 14px; }
.club-who i { width: 10px; height: 10px; border-radius: 50%; flex: none; }

/* Chip + @username stacked — in a cross-household thread the username
   is the durable public identity, the display name just the friendly
   face on it (same pairing browse-public uses). */
.club-id { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; flex: none; }
.club-username { font-size: 10px; color: var(--ghost); }

.club-comments { display: flex; flex-direction: column; gap: 12px; }
.club-comment { display: flex; align-items: flex-start; gap: 10px; }
.club-comment .chip { flex: none; font-size: 11.5px; }
.cc-main { min-width: 0; flex: 1; }
.cc-body { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: break-word; }
.cc-time { font-size: 11px; color: var(--ghost); }
/* Deletion stays available (a household tool has no per-person
   permissions to enforce) but visually silent until the row is hovered. */
.cc-del { border: 0; background: none; color: var(--ghost); font-size: 14px; padding: 0 4px; opacity: 0; flex: none; }
.club-comment:hover .cc-del, .cc-del:focus-visible { opacity: 1; }
.cc-del:hover { color: var(--danger); }

.club-form { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.club-form textarea, .club-why {
  width: 100%; border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 9px 11px; font: inherit; font-size: 13.5px; line-height: 1.5;
  resize: vertical; background: #fff;
}
.club-form textarea:focus, .club-why:focus { border-color: var(--gold); outline: none; }
.club-form-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.club-say-as { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.club-rchip { border: 0; cursor: pointer; opacity: .4; }
.club-rchip.on { opacity: 1; box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px var(--ink); }

.club-picker { margin-bottom: 30px; }
.club-picker .club-search {
  width: 100%; height: 36px; padding: 0 12px; margin-bottom: 10px;
  border: 1px solid var(--hairline); border-radius: var(--r); outline: none; background: #fff;
}
.club-picker .club-search:focus { border-color: var(--gold); }
.club-confirm { display: flex; flex-direction: column; gap: 10px; }
.club-past { margin-top: 32px; }
.club-past-item .club-comments { margin: 4px 0 16px 12px; padding-left: 12px; border-left: 2px solid var(--hairline); }

.case-head {
  font-family: var(--display); font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ink-soft); margin: 30px 2px 10px;
}
.case {
  display: flex; align-items: flex-end; gap: 3px;
  border-bottom: 3px solid #d9d9d9;
  padding: 6px 8px 0 8px;
  margin-bottom: 44px;
  min-height: 250px;
  flex-wrap: nowrap;
}
/* flex-shrink:0 (spines already have this via flex:none) — a series
   group is itself a flex item of .case, and without this it was the
   one thing standing between "row runs out of room" and "row wraps
   correctly": the browser would shrink the whole bracketed group to
   fit instead. Nothing in a case should ever compress; it should wrap. */
.series-group { display: flex; align-items: flex-end; gap: 3px; position: relative; flex-shrink: 0; }
.series-group.bracketed { margin-bottom: 0; }
.series-bracket {
  position: absolute; left: 1px; right: 1px; top: 100%; margin-top: 8px;
  height: 7px;
  border-left: 1.5px solid #9a9a9a; border-right: 1.5px solid #9a9a9a; border-bottom: 1.5px solid #9a9a9a;
  pointer-events: none;
}
.series-caption {
  position: absolute; top: 100%; margin-top: 17px; left: 0; right: 0;
  text-align: center; font-size: 11px; font-style: italic; color: #6d6d6d;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Interactive on the household's own shelf (clicking the title itself
   toggles vertical <-> flat — no separate hover button); plain text on
   read-only public bookcases, where .clickable is never added. */
.series-caption.clickable { pointer-events: auto; cursor: pointer; }
.series-caption.clickable:hover { color: var(--ink); text-decoration: underline; }
.series-own-all {
  position: absolute; top: -18px; right: 0;
  border: 1px solid var(--gold-dk); background: #fff; color: var(--ink);
  border-radius: var(--r); padding: 2px 8px; font-size: 10px;
  font-family: var(--body); letter-spacing: 0; white-space: nowrap;
  opacity: 0; transition: opacity .15s ease;
}
.series-group:hover .series-own-all,
.series-group:focus-within .series-own-all { opacity: 1; }
.series-own-all:hover { background: var(--gold-soft); }
.series-own-all:disabled { opacity: .6; cursor: default; }

/* A series laid flat: each book is a horizontal bar (width = the
   shared series height, height = the same page-thickness value a
   standing spine uses as its width) stacked into a pile, position 1 at
   the bottom like you'd actually set books down. */
.stack-group { display: flex; flex-direction: column-reverse; gap: 2px; flex-shrink: 0; }
.stack-bar {
  position: relative; overflow: hidden; box-sizing: border-box;
  background: #fff; border: 1px solid #cfcfcf; border-radius: 2px;
  display: flex; align-items: center; gap: 7px; padding: 0 8px;
  cursor: pointer; transition: box-shadow .12s ease;
}
.stack-bar:hover { box-shadow: 0 2px 6px rgba(0,0,0,.12); z-index: 2; }
.stack-bar.unowned { border-color: #e2e0d8; }
.stack-bar.unowned .stack-fill,
.stack-bar.unowned .stack-author { opacity: .45; }
/* Interested-but-not-owned — same whole-element dashed/faded ghost
   treatment standing spines use, borders included. */
.stack-bar.ghost { border-style: dashed; border-color: #bdbdbd; opacity: .34; }
.stack-fill { position: absolute; top: 0; bottom: 0; left: 0; background: #efe8d3; pointer-events: none; }
.stack-num { position: relative; font-size: 9px; font-weight: 800; color: #555; letter-spacing: .04em; flex: none; }
.stack-title {
  position: relative; font-size: 11px; font-weight: 700; color: var(--ink);
  text-transform: uppercase; letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0;
}
.stack-author { position: relative; font-size: 9.5px; color: #6d6d6d; flex: none; white-space: nowrap; }
.stack-bar.thin .stack-author { display: none; }
.stack-bar.thin .stack-num { display: none; }
/* Reader-colour applied (owned) — same contrast-safe text override
   pattern as .spine.rainbow. */
.stack-bar.rainbow .stack-title,
.stack-bar.rainbow .stack-num { color: var(--spine-text, var(--ink)); }
.stack-bar.rainbow .stack-author { color: var(--spine-text, #6d6d6d); opacity: .8; }
/* Flat colour modes' (Rainbow/True colour) progress tint — mirrors
   .spine.rainbow .sp-fill exactly; split-fill (Shelf) mode
   overrides this per-bar with the reader's own vivid colour instead. */
.stack-bar.rainbow .stack-fill { background: rgba(0,0,0,.16); }

.spine {
  position: relative;
  background: #fff;
  border: 1px solid #cfcfcf;
  border-radius: 2px 2px 0 0;
  cursor: pointer;
  flex: none;
  transition: transform .12s ease, box-shadow .12s ease;
}
.spine:hover { transform: translateY(-5px); box-shadow: 0 4px 10px rgba(0,0,0,.10); z-index: 2; }
/* Dim an unowned spine's content, title included — border/shape stay
   crisp (per-element opacity, not a blanket one on the whole spine,
   which would also wash out the border and shape). */
.spine.unowned { border-color: #e2e0d8; }
.spine.unowned .sp-fill,
.spine.unowned .sp-band,
.spine.unowned .sp-author,
.spine.unowned .sp-title { opacity: .45; }
.spine.ghost { border-style: dashed; border-color: #bdbdbd; opacity: .34; }
.spine.spine-pulse { animation: spine-pulse 1.5s ease-out; z-index: 2; }
@keyframes spine-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(213, 166, 58, 0); }
  15% { box-shadow: 0 0 0 4px rgba(213, 166, 58, .55); }
}

/* Bulk-select pick, shared by standing spines and laid-flat bars — an
   outline (not a border-colour swap, which on .stack-bar would be easy
   to miss against its already-visible border) plus a small badge, since
   a shelf full of near-identical spines needs something readable at a
   glance across a whole case, not a subtle tint you'd have to compare
   book-to-book to notice. Both elements already have position:relative
   from their own base rules, so the badge just needs its own absolute
   position, no extra positioning context to add here. Gold-dk matches
   the .sel-btn tab that turns this mode on, so "selected" reads as the
   same colour language as the control that caused it. */
.spine.selected, .stack-bar.selected { outline: 2px solid var(--gold-dk); outline-offset: 1px; }
.spine.selected::after, .stack-bar.selected::after {
  content: '✓'; position: absolute; top: 4px; right: 4px; z-index: 1;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--gold-dk); color: #fff; font-size: 10px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 3px rgba(0,0,0,.3); pointer-events: none;
}

/* Everything visual is clipped to the spine's own box; the favourite
   heart lives outside .sp-body so it can sit above the spine's top edge. */
.sp-body { position: absolute; inset: 0; overflow: hidden; border-radius: inherit; }
.sp-fill {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: #efe8d3;
  pointer-events: none;
}

/* The title/band/author stack is real flexbox now, not precomputed
   `bottom` offsets — a fixed 8px gap (top-to-title, band-to-author,
   author-to-bottom) is the browser's own measured pixel, identical on
   every spine, not a per-book estimate. .sp-title-zone is the only
   flexible piece (flex:1): it eats whatever's left after .sp-lower's
   real content height, so title-length is the one distance allowed to
   vary spine-to-spine — everything below it is fixed. */
/* gap:8px is a FLOOR on the one variable distance (title-end to band) —
   a long title that fills its flex:1 box would otherwise run flush
   against .sp-lower with zero clearance; this guarantees at least the
   same 8px everything else gets, even though the title can still use
   less of it (or all of it, minus this) when the title itself is short. */
.sp-stack { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 8px; }
.sp-title-zone {
  flex: 1 1 auto; min-height: 0;
  padding-top: 7px;
  writing-mode: vertical-rl;
  display: flex; align-items: center; justify-content: flex-start;
  gap: 4px;
  overflow: hidden;
}
.sp-title, .sp-sub {
  font-size: 11.5px; font-weight: 700;
  line-height: 1.15; letter-spacing: .01em; text-transform: uppercase;
}
.sp-title { color: var(--ink); }
/* A separate flex item, not nested text, so the subtitle is its own
   line under the title rather than an inline run that wraps into a
   new *column* (vertical-rl wraps sideways, not downward). */
.sp-sub { color: var(--gold-dk); }
.sp-sub.sp-sub-small { font-size: 9px; }

/* Fixed-size bottom cluster: band + author, each sized to its own
   content, 8px gap between them and 8px below the last one — never
   absolutely positioned, so there's nothing here for a per-book number
   to drift. */
.sp-lower { flex: none; display: flex; flex-direction: column; gap: 8px; padding-bottom: 7px; }
.sp-band {
  flex: none; margin: 0 1px; height: 19px;
  background: #faf7ef;
  border-top: 1px solid #e6e0cb; border-bottom: 1px solid #e6e0cb;
  display: flex; align-items: center; justify-content: center;
}
/* Plain upright text, not rotated with the spine — a call-number label
   reads across, not sideways. */
.sp-num { font-size: 9px; font-weight: 800; color: #555; letter-spacing: .08em; }

/* Height is intrinsic to the text (no fixed budget, no JS estimate) —
   overflow:hidden here is a pure safety net. */
.sp-author {
  flex: none;
  writing-mode: vertical-rl;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  font-size: 8.5px; color: #6d6d6d;
  text-transform: uppercase; letter-spacing: .06em;
}
.sp-author b { font-weight: 800; color: #3a3a3a; }

/* Rainbow shelf: the spine's own background carries the reader's
   genre colour (set inline per-spine, see spineEl), so title/author/
   number need a contrast-checked override instead of their normal
   ink-on-cream colours — --spine-text is computed once in JS
   (textOn()) and reused at full strength for the number/title and
   softened for the author line. The band and progress-fill go
   translucent white/black instead of their fixed colours so they read
   as an overlay on any hue rather than clashing with one. */
.spine.rainbow .sp-title,
.spine.rainbow .sp-num { color: var(--spine-text, var(--ink)); }
.spine.rainbow .sp-sub { color: var(--spine-text, var(--gold-dk)); opacity: .8; }
.spine.rainbow .sp-author { color: var(--spine-text, #6d6d6d); opacity: .8; }
.spine.rainbow .sp-author b { color: var(--spine-text, #3a3a3a); opacity: 1; }
.spine.rainbow .sp-band { background: rgba(255,255,255,.28); border-color: rgba(255,255,255,.4); }
.spine.rainbow .sp-fill { background: rgba(0,0,0,.16); }

.sp-fav {
  position: absolute; top: -19px; left: 50%; transform: translateX(-50%);
  font-size: 13px; color: var(--gold-dk); pointer-events: none;
  text-shadow: 0 1px 1px rgba(0,0,0,.15);
}

/* Thin spines shrink the title and show the surname only (see
   authorSpineHtml's surnameOnly arm) — they used to drop the author
   line entirely, which hid it on every book under ~268pp, picture books
   included. The 8.5px rotated line always fit; SPINE_MIN_W in app.js is
   the floor that guarantees room for it. */
.sp-thin .sp-author { font-size: 8px; letter-spacing: .04em; }
.sp-thin .sp-title { font-size: 9.5px; font-weight: 600; }
.spine.ghost .sp-title { color: #9a9a9a; }

/* A collapsed run of unread books. Gold and solid, NOT the grey dashed
   ghost look: a ghost is a book you don't own, this is a stack of books
   you do — conflating them would make the shelf lie about what's on it. */
.spine.sp-collapsed {
  border: 1px dashed var(--gold-dk); background: var(--gold-soft);
  cursor: pointer; opacity: 1;
}
.spine.sp-collapsed:hover, .spine.sp-collapsed:focus-visible {
  border-style: solid; background: #fff;
}
.spine.sp-collapsed .sp-title { color: var(--gold-dk); font-weight: 700; }

#shelf-controls,
#browse-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
#shelf-controls .seg,
#shelf-controls select,
#shelf-controls .btn {
  height: 30px; box-sizing: border-box;
  font-family: var(--body); font-size: 12px; letter-spacing: 0;
}
#shelf-controls .seg button {
  height: 100%; padding: 0 12px;
  display: inline-flex; align-items: center;
  font-family: var(--body); font-size: 12px; letter-spacing: 0;
}
#shelf-controls select { padding: 0 8px; border: 1px solid var(--hairline); border-radius: var(--r); background: #fff; color: var(--ink); }
#shelf-controls .btn { padding: 0 12px; display: inline-flex; align-items: center; }

/* Bookmark-ribbon dropdown: every shelf display option lives here so
   the toolbar stops growing each time a new one shows up — "+ Book" is
   the only thing that stays a standalone visible button (it's an
   action, not a display preference). The tab itself IS the bookmark —
   no button chrome/border box around an icon glyph, just a coloured,
   notched ribbon shape hanging from the top of the page. Positioned
   right:12px to match #topbar's own edge padding, so it hangs directly
   under the stats+cog cluster (also right-aligned at 12px) rather than
   an arbitrary offset.
   The panel is the FIRST flex child (attached flush to the gold line
   at the top of the fixed wrapper — the seam it visibly "slides out"
   from, no top border/radius of its own so the gold line reads as ITS
   top edge, not a separate thing sitting above it) and the ribbon is
   the SECOND, sitting right at the panel's bottom edge — a real
   pull-tab, not a lid. The ribbon's flat edge (not the notched point)
   touches the panel — same orientation as a real bookmark ribbon,
   which is flat where it's bound into the book and notched at the
   loose hanging end, never the other way round. Closed (max-height:0),
   the panel contributes no height so the ribbon still sits right at
   the gold line, same resting spot as always; opening it grows the
   panel downward from that fixed top, and the ribbon — attached to
   its bottom, not fixed in place — visibly moves down with it,
   reading as "pulled down" rather than "a lid flips open below a
   static tab". Plain flex-column siblings with zero gap are already
   touching by construction, so no negative-margin tuck is needed the
   way the old ribbon-on-top layout required. */
/* pointer-events:none on the wrapper, re-enabled per child — the menu's
   own min-width (200px) forces this flex container that wide even while
   visually collapsed (max-height:0 only clips height, not width), and
   without this the empty flex space to its left silently ate clicks
   meant for the reading-list ribbon sitting right next to it. */
/* z-index 47 vs the reading-list wrapper's 46 — the maroon container
   (panel included) always paints ABOVE the green ribbon, per explicit
   request, so opening the wide options panel covers the green tab
   rather than the tab poking through it. The green tab is briefly
   unclickable while the maroon panel is open — closing it (its own
   tab, or any outside click) brings the green one back. The reverse
   never collides: the green panel is anchored at right:50px, so the
   maroon tab (right:12-42px) stays clear of it even fully open. */
.sc-options { position: fixed; top: 51px; right: 12px; z-index: 47; display: flex; flex-direction: column; align-items: flex-end; pointer-events: none; }
.sc-options-btn {
  pointer-events: auto;
  width: 30px; height: 42px; padding: 0; border: 0; cursor: pointer; flex: none;
  background: #7a2333;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 74%, 0 100%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .2);
  display: flex; align-items: flex-start; justify-content: center; padding-top: 8px;
}
.sc-options-btn:hover, .sc-options-btn.on { background: #8f2a3d; }
.sc-options-btn svg { width: 12px; height: 12px; fill: #fff; pointer-events: none; }
.sc-options-menu {
  width: max-content; min-width: 200px; max-width: 380px;
  background: #fff; border: 1px solid var(--hairline); border-top: 0; border-radius: 0 0 var(--r) var(--r);
  box-shadow: 0 8px 26px rgba(43, 43, 43, .14);
  padding: 0 12px; display: flex; flex-direction: column;
  max-height: 0; opacity: 0; overflow: hidden; pointer-events: none;
  transition: max-height .28s ease, opacity .2s ease, padding .28s ease;
}
.sc-options-menu.open { max-height: 600px; opacity: 1; padding: 22px 12px 12px; pointer-events: auto; }
.sc-options-menu .seg { width: 100%; }
.sc-options-menu .seg button { flex: 1; justify-content: center; }
.sc-options-menu select { width: 100%; }
.sc-filter-input {
  width: 100%; height: 32px; padding: 0 10px; margin-bottom: 14px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  background: var(--paper); outline: none; font-size: 13px;
}
.sc-filter-input:focus { border-color: var(--gold); background: #fff; }

/* Bulk-select toggle: same hanging-tab shape/shadow as .rl-btn and
   .sc-options-btn (so it reads as a sibling of the two, not a bolted-on
   extra), one more column left of the reading-list tab (right:88 vs its
   right:50, an 8px gap past its 30px width). No panel/wrapper of its
   own — unlike its neighbours it doesn't disclose anything, it's a
   straight on/off flip, so it's just the tab, fixed-positioned
   directly. Gold rather than the maroon/teal the other two use, since
   selection isn't part of either of those two existing systems. */
.sel-btn {
  position: fixed; top: 51px; right: 88px; z-index: 45;
  width: 30px; height: 42px; padding: 0; border: 0; cursor: pointer; flex: none;
  background: var(--gold-dk);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 74%, 0 100%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .2);
  display: flex; align-items: flex-start; justify-content: center; padding-top: 8px;
}
.sel-btn:hover, .sel-btn.on { background: var(--gold); }
.sel-btn svg { width: 14px; height: 14px; pointer-events: none; }

/* Reading-list bookmark: same hanging-ribbon-and-attached-panel
   construction as .sc-options above — a single fixed flex-column
   wrapper (.rl-options), panel FIRST (attached to the gold line, the
   fixed top it slides out from) and the ribbon SECOND, sitting at the
   panel's bottom edge so it moves down with the panel as it opens
   rather than staying pinned at the top. pointer-events:none on the
   wrapper, re-enabled per child, for the same reason .sc-options needs
   it — the panel's own min-width still forces the wrapper wide even
   while visually collapsed. Positioned one column left of the options
   ribbon (right:50 vs its right:12) so the two hang as separate tabs
   off the same top edge. Bounded to roughly 10
   spines wide (a
   dropdown, not another shelf case) — scrolls horizontally past that
   rather than growing to the shelf's own width. */
.rl-options { position: fixed; top: 51px; right: 50px; z-index: 46; display: flex; flex-direction: column; align-items: flex-end; pointer-events: none; }
.rl-btn {
  pointer-events: auto;
  width: 30px; height: 42px; padding: 0; border: 0; cursor: pointer; flex: none;
  background: #1f5c52;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 74%, 0 100%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .2);
  display: flex; align-items: flex-start; justify-content: center; padding-top: 8px;
}
.rl-btn:hover, .rl-btn.on { background: #276f63; }
.rl-btn svg { width: 13px; height: 13px; fill: #fff; pointer-events: none; }
.rl-panel {
  width: min(90vw, 560px);
  background: #fff; border: 1px solid var(--hairline); border-top: 0; border-radius: 0 0 var(--r) var(--r);
  box-shadow: 0 8px 26px rgba(43, 43, 43, .14);
  padding: 0 16px; display: flex; flex-direction: column; gap: 4px;
  max-height: 0; opacity: 0; overflow: hidden; pointer-events: none;
  transition: max-height .28s ease, opacity .2s ease, padding .28s ease;
}
.rl-panel.open { max-height: 70vh; opacity: 1; padding: 20px 16px 18px; pointer-events: auto; overflow-y: auto; }
.rl-head h3 { margin: 0; font-family: var(--display); font-weight: 400; font-size: 16px; }
.rl-head .rec-sub { margin: 2px 0 12px; }
.rl-row-label { font-family: var(--display); font-size: 13px; color: var(--ink-soft); margin: 10px 0 4px; }
.rl-row-label:first-of-type { margin-top: 0; }
.rl-row-empty { margin: 0 0 10px; }
.rl-row {
  display: flex; align-items: flex-end; gap: 10px; overflow-x: auto; overflow-y: hidden;
  padding-bottom: 6px; margin-bottom: 6px;
}
.rl-spine-wrap { position: relative; flex: none; cursor: grab; }
.rl-spine-wrap.dragging { opacity: .35; }
.rl-remove {
  position: absolute; top: -8px; right: -8px; z-index: 1;
  width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--hairline);
  background: #fff; color: var(--ink-soft); font-size: 12px; line-height: 1;
  display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity .15s ease;
}
.rl-spine-wrap:hover .rl-remove { opacity: 1; }
.rl-remove:hover { border-color: #c33; color: #c33; }
.rl-top10-badge {
  flex: none; align-self: stretch; display: flex; align-items: center; justify-content: center;
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--display); font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ghost); border-right: 1px dashed var(--hairline); padding-right: 10px; margin-right: 5px;
}
.rl-top10-divider { flex: none; align-self: stretch; width: 0; border-left: 1px dashed var(--hairline); margin: 0 5px; }

/* ── Book card overlay: two EQUAL-width panels — left is reference
   data (cover/title/series/author/description/details), right is
   editable tracking state. No separate cover pane anymore; the cover
   is the first thing in the left column. ──────────────────────────── */
.book-card {
  --bc-col-w: 430px; --bc-h: 660px;
  display: flex; flex-direction: column; gap: 14px;
  background: #fff; padding: 22px; border-radius: 4px;
  width: auto; max-width: 96vw;
  border-top: 0; /* no gold plate on the card — only the accent inputs use gold now */
}
.bc-columns { display: flex; gap: 22px; align-items: flex-start; flex: 1; min-height: 0; height: var(--bc-h); }
.bc-details { width: var(--bc-col-w); flex: none; display: flex; flex-direction: column; min-width: 0; height: 100%; }
/* The scrollable part (cover/title/series/byline/summary) is the ONE
   flex:1 child — it fills exactly the space left after .bc-meta below
   it, scrolling internally if it doesn't fit. That's what actually
   pins the meta line to the bottom regardless of content length above;
   a plain margin-top:auto on .bc-meta only pins it when there happens
   to be leftover space in the column, which isn't reliable once a tall
   cover + a long description are both in play. */
.bc-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
.bc-yours { width: var(--bc-col-w); flex: none; min-width: 0; height: 100%; display: flex; flex-direction: column; gap: 12px; padding-left: 22px; border-left: 1px solid var(--hairline); overflow-y: auto; }

/* Cover sits inside the (white) left column now, not its own grey
   pane — a plain hairline border gives it an edge without a colour
   block behind it. */
.bc-cover {
  position: relative; flex: none;
  width: 100%; height: 300px; background: #fff;
  border: 1px solid var(--hairline); border-radius: 4px; overflow: hidden;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
/* Scale to fit, pad, centre — never crop a cover. */
.bc-cover img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; }
.bc-cover .no-cover { font-family: var(--display); font-size: 44px; color: #bbb; }
/* Shared circular corner-button chrome for the cover — favourite sits
   top-left, the close (×) sits top-right, so the two never collide. */
.bc-cover-btn {
  position: absolute; top: 10px; z-index: 1;
  border: 0; background: rgba(255,255,255,.85); border-radius: 50%;
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  font-size: 17px; color: #ccc; box-shadow: 0 1px 4px rgba(0,0,0,.15);
}
.bc-fav { left: 10px; }
.bc-fav.on { color: var(--gold-dk); }
.bc-fav:hover { color: var(--gold-dk); }
.bc-cover-close { right: 10px; font-size: 16px; color: var(--ink-soft); }
.bc-cover-close:hover { color: var(--ink); }

/* Everything here is directly editable — no separate "Edit details"
   step. Title/author/series save on blur (book-save); readers/tags
   save their own field the moment it changes. */
.bc-field {
  width: 100%; height: 36px; padding: 0 10px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  background: #fff; font-size: 14.5px;
}
.bc-field:focus { border-color: var(--gold); outline: none; }

/* Book-details fields (title/author/series) read as content — like
   the record you'd pull from a book database — not a form, until you
   actually touch one. Border/background only appear on hover/focus,
   and padding never changes, so nothing shifts when they do. */
/* Specificity note: .modal-card input[type=text/number] (auth/add-book
   forms) outranks a plain .bc-field-plain class selector, so the
   border never actually went transparent without this extra scoping. */
.book-card input.bc-field-plain {
  height: auto; border-color: transparent; background: transparent;
  padding: 3px 5px; margin: 0 -5px;
}
.book-card input.bc-field-plain:hover { border-color: var(--hairline); background: #fafafa; }
.book-card input.bc-field-plain:focus { border-color: var(--gold); background: #fff; }
.bc-field-title { font-family: var(--display); font-size: 20px; font-weight: 400; margin-top: 12px; }

.bc-byline { display: flex; align-items: center; gap: 5px; font-size: 14px; color: var(--ink-soft); }
.bc-byline .bc-field-plain { flex: 1; font-size: 14px; color: var(--ink-soft); }
/* Lone (non-series) books only — a series' own stand/flat toggle lives
   on the shelf now (click the series title), not duplicated here. A
   quiet inline link rather than a standalone centred segmented
   control — a minor display preference, not worth its own visual row. */
.bc-stack-toggle { font-size: 12px; flex: none; }
.bc-series-row { display: flex; align-items: center; gap: 7px; min-height: 24px; }
/* Standalone is the common case, so its resting state is a quiet opt-in
   rather than an empty text field that reads as an unfinished form. Sized
   and coloured like the surrounding metadata, not like a primary button —
   it's a disclosure, and most books never need it. */
.bc-series-add {
  flex: none; border: 1px dashed var(--hairline); border-radius: var(--r);
  background: none; padding: 3px 10px;
  font-size: 12px; color: var(--ghost);
}
.bc-series-add:hover, .bc-series-add:focus-visible {
  border-style: solid; border-color: var(--gold); color: var(--ink-soft); background: #fff;
}
.bc-series-row .bc-field-plain { font-size: 12.5px; color: var(--ink-soft); }
/* NOT flex:1 — stretching the name field pinned the number to the far
   right edge, reading as an unrelated column rather than as this series'
   volume number. It's sized to its own text (the `size` attribute set in
   app.js) so the two sit together and the slack falls after them.
   The .book-card input specificity is needed to beat the global
   `.modal-card input { width: 100% }` form rule. */
.bc-series-row .bc-field:first-child { flex: 0 1 auto; min-width: 0; }
.book-card input#bc-series-name { width: auto; max-width: 100%; }
/* Position/count are never more than a few digits — three digits'
   worth of width, not a flex-stretched field, and it's still a fixed
   width even though the VALUE (e.g. "of 12") can grow if the series
   gains books later; three digits covers any series in practice. */
/* Specificity: .modal-card input[type=number] (same global form rule
   noted above) outranks a plain .bc-num-plain class, so width:100%
   was winning and stretching a 1-2 digit field across the whole row —
   same fix pattern as .book-card input.bc-field-plain above. */
.book-card input.bc-num-plain { flex: none; width: 34px; text-align: center; }
.bc-num-plain::-webkit-inner-spin-button, .bc-num-plain::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.bc-num-plain { -moz-appearance: textfield; }
.bc-series-slash { font-size: 12.5px; color: var(--ghost); }

/* The volume number in a gold-trimmed square, echoing the spine's own
   number band — it's an identifier, so it gets a container rather than
   sitting as loose text. min-width matched to height keeps a single
   digit exactly square while letting "13" widen instead of clipping.
   Clicking still swaps in the real position/total inputs above. */
.bc-series-num {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  height: 22px; min-width: 22px; padding: 0 4px; margin: 0;
  border: 1px solid var(--gold); border-radius: var(--r);
  background: #fff;
  font-size: 12px; line-height: 1; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.bc-series-num:hover { background: var(--gold-soft); border-color: var(--gold-dk); }

.bc-stars { display: flex; gap: 5px; font-size: 23px; cursor: pointer; user-select: none; }
.bc-stars span { color: #d8d8d8; }
.bc-stars span.on { color: var(--gold); }
/* Half-star: gold left half, grey right half, via a background-clip
   gradient on the glyph itself — no extra markup per star needed. */
.bc-stars span.half {
  color: transparent;
  background: linear-gradient(90deg, var(--gold) 50%, #d8d8d8 50%);
  -webkit-background-clip: text;
  background-clip: text;
}

.bc-readers-table { display: flex; flex-direction: column; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--hairline); }

/* One reader with a relationship to this book = one labeled container.
   A reader with none isn't shown at all: a book is a single person's copy,
   so anyone else "adding" it creates their own row rather than joining
   this one (the old .rr-add chip-button did that and is gone). There's no
   "owns" checkbox: the container existing IS the ownership, so a tick here
   would be permanently checked and say nothing. Removal is the × instead —
   quiet until the row is hovered, like .cc-del. */
/* A real fieldset, the reader's name as its legend in plain uppercase —
   no coloured chip. The name is the LABEL of this group of controls, and
   a filled pill reads as a state/value rather than a label. */
.rr-group {
  position: relative; margin: 0; min-width: 0;
  border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 2px 10px 8px;
}
.rr-group > legend {
  padding: 0 5px; font-size: 10px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft);
}
/* Sits in the fieldset's own top-right corner, clear of the legend. */
.rr-remove {
  position: absolute; top: -2px; right: 4px;
  border: 0; background: none; padding: 0 3px;
  font-size: 16px; line-height: 1; color: var(--ghost);
  opacity: 0; transition: opacity .15s ease, color .15s ease;
}
.rr-group:hover .rr-remove, .rr-remove:focus-visible { opacity: 1; }
.rr-remove:hover { color: var(--danger); }
.rr-group-body { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 7px; font-size: 13px; }
/* Specificity: .modal-card input[type=number] outranks a plain
   .rr-page class (same issue as .bc-num-plain above) — scope through
   .book-card to win. */
.book-card input.rr-page {
  width: 40px; height: 28px; padding: 0 6px; text-align: center;
  border: 1px solid var(--hairline); border-radius: var(--r); font-size: 13px;
}
.rr-page::-webkit-inner-spin-button, .rr-page::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.rr-page { -moz-appearance: textfield; }
.rr-page:disabled, .rr-date:disabled {
  background: #f2f1ec; color: #b7b4a8; border-color: #e6e3d8; cursor: not-allowed;
}
.rr-of { color: var(--ink-soft); font-size: 11.5px; flex: none; }
/* Order is DNF, FIN, date — .rr-dnf before .rr-read in the markup now
   drives this; no margin-left:auto push needed any more. */
.rr-dnf { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-soft); flex: none; }
.rr-dnf input { width: 15px; height: 15px; accent-color: var(--ghost); }
.rr-read { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-soft); flex: none; }
.rr-read input { width: 15px; height: 15px; accent-color: var(--gold-dk); }
.rr-date { width: 118px; height: 28px; padding: 0 6px; border: 1px solid var(--hairline); border-radius: var(--r); font-size: 12px; color: var(--ink-soft); flex: none; }
/* A native date input always renders "dd/mm/yyyy" when empty — there's no
   placeholder attribute that suppresses it — and on a shelf of finished
   books with no recorded date that's just noise repeated down the card.
   Transparent until focused. The text is only HIDDEN, never removed, so
   the field keeps its width and the row doesn't shift when it appears.
   The picker icon deliberately stays: with the hint gone it's the only
   thing marking this as a date field rather than an empty box. */
.rr-date.rr-date-empty:not(:focus)::-webkit-datetime-edit { color: transparent; }
.rr-date.rr-date-empty:not(:focus)::-moz-date-placeholder { color: transparent; }

.bc-tags-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.bc-tags-all { display: flex; flex-wrap: wrap; gap: 6px; }
.bc-tag-chip, .bc-tags-toggle {
  border: 1px solid var(--hairline); border-radius: var(--r); background: #fff;
  padding: 4px 10px; font-size: 12px; color: var(--ink-soft);
}
.bc-tag-chip.on { border-color: var(--gold-dk); color: var(--ink); background: var(--gold-soft); }
.bc-tags-toggle { color: var(--link); }

.bc-summary { max-height: 220px; overflow-y: auto; margin-top: 6px; }
.bc-summary-text { font-size: 13px; line-height: 1.6; color: var(--ink-soft); margin: 0; text-align: justify; text-align-last: left; }

/* "Details" — whatever factual metadata is actually on file (page
   count, Dewey class today; ISBN/trim size whenever that data exists)
   — omitted line by line, not as a whole section, when a given fact
   isn't available. Sits below .bc-scroll (a fixed flex:none footer,
   not part of the scrolling area) and centred, so it reads as a
   caption line rather than trailing content. */
/* margin-top, not padding: the gap belongs ABOVE the rule, holding the
   summary text off it. Padding would sit below the line and just push the
   meta text down instead. */
.bc-meta { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; text-align: center; gap: 3px 14px; margin-top: 20px; padding-top: 10px; border-top: 1px solid var(--hairline); flex: none; }
.bc-meta span { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }

/* Right-aligned so it sits under the "Your reading" column, not
   centred/left under the whole card. */
.bc-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; flex-wrap: wrap; flex: none; }
/* Small, quiet — matches the cover's own × close button rather than
   reading as a full-weight action button. */
.bc-close-btn {
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  width: 26px; height: 26px; padding: 0; display: flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1; color: var(--ink-soft);
}
.bc-close-btn:hover { color: var(--ink); border-color: var(--ink-soft); }

@media (max-width: 860px) {
  .book-card { width: min(92vw, 460px); padding: 16px; }
  .bc-columns { flex-direction: column; height: auto; max-height: 76dvh; }
  .bc-details { width: 100%; height: auto; }
  .bc-scroll { overflow-y: visible; }
  .bc-yours { width: 100%; height: auto; overflow-y: visible; border-left: 0; padding-left: 0; padding-top: 14px; border-top: 1px solid var(--hairline); }
  .bc-cover { height: 260px; }
}

/* ── Talk: site-wide forum ───────────────────────────── */
#talk-body, #topic-body { max-width: 680px; }
.talk-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.talk-controls input {
  height: 34px; padding: 0 12px; min-width: 220px;
  border: 1px solid var(--hairline); border-radius: var(--r); outline: none;
}
.talk-controls input:focus { border-color: var(--gold); }
.talk-list { display: flex; flex-direction: column; gap: 8px; }
.talk-card { width: 100%; }
.talk-club-tag {
  font-family: var(--display); font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--gold-dk);
}
.talk-topic-book { max-width: 320px; flex: none; }
.talk-compose textarea, .club-form textarea, #topic-composer textarea {
  width: 100%; padding: 10px 12px; border: 1px solid var(--hairline); border-radius: var(--r);
  font: inherit; font-size: 13.5px; outline: none; resize: vertical; background: #fff;
}
.talk-compose textarea:focus, #topic-composer textarea:focus { border-color: var(--gold); }
.talk-messages { margin-bottom: 20px; }
.tm-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.tm-quote {
  display: block; max-width: 100%; text-align: left; margin-bottom: 4px;
  border: 0; border-left: 3px solid var(--gold); border-radius: 0 var(--r) var(--r) 0;
  background: var(--gold-soft); color: var(--ink-soft);
  font-size: 12px; padding: 5px 9px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tm-flagged .cc-body { opacity: .45; }
.tm-flag-tag { color: var(--danger); font-size: 11px; }
.tm-hilite { background: var(--gold-soft); transition: background .3s; border-radius: var(--r); }
#topic-composer { margin-top: 6px; display: flex; flex-direction: column; gap: 10px; }

/* ── Club roles / schedules (2026-07 build-out) ──────── */
.role-badge {
  font-family: var(--display); font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gold-dk); border: 1px solid var(--gold); border-radius: var(--r); padding: 1px 6px; flex: none;
}
.club-memberlist { margin-top: 8px; }
.club-memberlist .club-member-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid #f1f1f3; font-size: 13px; }
.club-memberlist .club-member-row:last-child { border-bottom: 0; }
.cm-controls { margin-left: auto; font-size: 12px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.club-schedule { margin: 4px 0 0; font-size: 13px; color: var(--gold-dk); font-weight: 600; }
.club-sched-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.club-sched-row input[type=date] {
  height: 32px; padding: 0 8px; border: 1px solid var(--hairline); border-radius: var(--r);
  font: inherit; font-size: 13px; background: #fff; outline: none;
}
.club-sched-row input:focus { border-color: var(--gold); }
.cp-react { font-size: 12px; flex: none; }

/* ── Faceted browsing ────────────────────────────────── */
.facet-panel {
  max-width: 640px; margin-bottom: 18px; padding: 12px 14px;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  display: flex; flex-direction: column; gap: 8px;
}
.facet-panel .facet-q { margin-bottom: 4px; }
.facet-ribbon { display: flex; flex-direction: column; gap: 8px; }
.facet-ribbon .facet-q { display: none; }  /* the ribbon already has #sc-filter */
.facet-group { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.facet-label {
  font-family: var(--display); font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ghost); flex: none; width: 100%;
}
.facet-chip {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--hairline); border-radius: var(--r); background: #fff;
  padding: 3px 8px; font-size: 12px; color: var(--ink-soft); cursor: pointer;
}
.facet-chip.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.facet-chip.zero { opacity: .4; }
.facet-chip .facet-count { font-size: 10px; color: var(--ghost); }
.facet-chip.on .facet-count { color: #ddd; }
.facet-more { border-style: dashed; }
.facet-clear { align-self: flex-start; color: var(--danger); font-size: 12px; }

/* ── CSV import ──────────────────────────────────────── */
.ci-row { display: flex; align-items: flex-start; gap: 8px; padding: 7px 0; border-bottom: 1px solid #f1f1f3; font-size: 13px; }
.ci-row:last-child { border-bottom: 0; }
.ci-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ci-dupe { color: var(--gold-dk); }

/* ── The bookcase designer ───────────────────────────────
   The physical room. Shares the shelf's visual language on purpose —
   .spine / .stack-bar are rendered by the same functions, so a book
   looks identical whether you're seeing the derived shelf or the real
   furniture; only the frame around it changes. The tray is pinned to
   the bottom and the scroll area is inset above it, so dragging never
   has to chase a moving target. */
#designer {
  position: fixed; inset: 52px 0 0 0;
  overflow: hidden;
  background: #fff;
  display: flex; flex-direction: column;
}
#designer-head { padding: 18px 26px 0; flex: none; }
#designer-body { flex: 1; min-height: 0; overflow: auto; padding: 14px 26px 30px; }
.bcd-head-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.bcd-locate input {
  width: clamp(200px, 26vw, 320px); height: 34px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  background: var(--paper); outline: none;
}
.bcd-locate input:focus { border-color: var(--gold); background: #fff; }

/* Bookcase switcher — same arrows-and-name widget #reader-switch uses,
   one axis over (furniture, not people). */
#bcd-switch { display: flex; align-items: center; gap: 10px; margin: 14px 0 0; flex-wrap: wrap; }
#bcd-switch .rs-arrow {
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
  font-size: 15px; color: var(--ink-soft);
}
#bcd-switch .rs-arrow:hover { border-color: var(--gold); color: var(--ink); }
#bcd-switch .rs-current { font-family: var(--display); font-size: 17px; min-width: 120px; text-align: center; }
.bcd-case-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.bcd-case-room { font-size: 11.5px; color: var(--ink-soft); }
.bcd-case-mixed {
  font-family: var(--display); font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--gold-dk); border: 1px solid var(--gold-dk); border-radius: var(--r); padding: 1px 6px;
}
.bcd-rchip { padding: 1px 8px; font-size: 10.5px; }
.bcd-case-tools { display: flex; gap: 6px; margin-left: auto; opacity: 0; transition: opacity .15s ease; }
#bcd-switch:hover .bcd-case-tools, #bcd-switch:focus-within .bcd-case-tools { opacity: 1; }
.bcd-tool {
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  padding: 2px 8px; font-size: 11px; color: var(--ink-soft);
}
.bcd-tool:hover { border-color: var(--gold-dk); color: var(--ink); }
.bcd-tool-danger:hover { border-color: var(--danger); color: var(--danger); background: #f8ecea; }
/* Name row alongside the arrows — the arrows are the requirement, this
   is just a shortcut once there are more than two or three cases. */
.bcd-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 0; }
.bcd-tab {
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  padding: 3px 10px; font-size: 12px; color: var(--ink-soft);
}
.bcd-tab.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.bcd-who { display: flex; align-items: center; gap: 10px; margin: 10px 0 0; }
.bcd-empty { margin: 24px 2px; }

/* Locate results: a list, not a filter — see applyLocate(). */
.bcd-found { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; max-width: 640px; }
.bcd-found-row {
  display: flex; align-items: baseline; gap: 8px; text-align: left;
  border: 0; border-bottom: 1px solid #f1f1f3; background: none;
  padding: 5px 2px; font-size: 13px; color: var(--ink);
}
.bcd-found-row:hover { background: var(--gold-soft); }
.bcd-found-row span { font-size: 11.5px; color: var(--ink-soft); }

/* ── The carcass ──────────────────────────────────────────
   A real CSS grid, because a bookcase is one: columns across, shelves
   down. Thick sides and a base so it reads as a single piece of
   furniture, and internal hairlines between compartments so a 5x5 cube
   unit looks like cubes rather than like one wide shelf per row. */
.bcd-frame {
  display: grid;
  border: 2px solid #cfcbc0; border-top-width: 6px; border-bottom-width: 8px;
  border-radius: 3px; background: #fdfcfa;
  max-width: 100%;
}
.bcd-shelf {
  display: flex; align-items: stretch; gap: 4px;
  border-bottom: 4px solid #d9d9d9;
  border-right: 1px solid #e6e2d8;
  padding: 4px 2px 0;
  min-height: 196px; min-width: 0;
}
/* The rightmost column has the carcass side for its right edge already,
   so it drops the internal divider. Which cell that is depends on
   col_count, which no nth-child expression can be told at runtime —
   drawDesignerBody tags it instead. */
.bcd-shelf.bcd-lastcol { border-right: 0; }
.bcd-shelf-gap { background: repeating-linear-gradient(45deg, #fafafa, #fafafa 6px, #f2f0ea 6px, #f2f0ea 12px); }
.bcd-shelf-stranded { border-color: var(--danger); }
.bcd-shelf-tag {
  flex: none; width: 20px; display: flex; align-items: flex-end; justify-content: center;
  padding-bottom: 6px; cursor: pointer;
  font-family: var(--display); font-size: 10px; letter-spacing: .08em;
  color: var(--ghost); writing-mode: vertical-rl; text-orientation: mixed;
  white-space: nowrap; overflow: hidden;
}
.bcd-shelf-tag-short { writing-mode: horizontal-tb; font-size: 11px; }
.bcd-shelf-tag:hover { color: var(--gold-dk); }
/* The plank itself. align-items:flex-end so spines stand ON it and a
   flat book lies at its surface; nowrap because a real shelf doesn't
   wrap — running out of room is information, and the scrollbar says so. */
.bcd-run {
  flex: 1; min-width: 0;
  display: flex; align-items: flex-end; gap: 3px;
  flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
  padding: 6px 2px 0;
}
.bcd-shelf-tools { flex: none; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 4px; padding-bottom: 6px; }
.bcd-fill { display: block; width: 26px; height: 4px; border-radius: 2px; background: #eceae2; overflow: hidden; }
.bcd-fill i { display: block; height: 100%; background: var(--gold-dk); }
.bcd-shelf-tools .bcd-tool { opacity: 0; transition: opacity .15s ease; padding: 0 5px; }
.bcd-shelf:hover .bcd-tool, .bcd-shelf:focus-within .bcd-tool { opacity: 1; }

/* Compartments left outside the case by a shrink. Deliberately loud —
   they hold real books and want dealing with, but nothing was deleted. */
.bcd-stranded { margin-top: 22px; max-width: 100%; }
.bcd-stranded .rec-sub { max-width: 640px; margin: 0 0 8px; }
.bcd-frame-loose { border-color: var(--danger); border-style: dashed; }

/* ── Items on a plank ─────────────────────────────────── */
.bcd-item { position: relative; flex: none; display: flex; align-items: flex-end; }
.bcd-item.bcd-dragging { opacity: .35; }
/* The whose-books filter fades; it never removes — see designerItemEl. */
.bcd-item.bcd-dim { opacity: .22; }
.bcd-item-tools {
  position: absolute; top: -19px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 2px; opacity: 0; transition: opacity .12s ease; z-index: 3;
}
.bcd-item:hover .bcd-item-tools, .bcd-item:focus-within .bcd-item-tools { opacity: 1; }
.bcd-item-btn {
  width: 17px; height: 17px; line-height: 1; padding: 0;
  border: 1px solid var(--hairline); background: #fff; border-radius: var(--r);
  font-size: 11px; color: var(--ink-soft);
  display: flex; align-items: center; justify-content: center;
}
.bcd-item-btn:hover { border-color: var(--gold-dk); color: var(--ink); }
/* A divider is a physical partition, so it's drawn as one: a thin full-
   height plate, not a book-shaped thing with a label on it. */
.bcd-divider-plate { width: 9px; height: 176px; background: #b9b3a4; border-radius: 1px; position: relative; }
.bcd-divider-plate span {
  position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
  writing-mode: vertical-rl; text-orientation: mixed;
  font-size: 9.5px; letter-spacing: .08em; color: #fff; white-space: nowrap;
  max-height: 156px; overflow: hidden;
}
/* Where the dragged thing will land. */
.bcd-marker { flex: none; width: 3px; align-self: stretch; margin: 6px 0 0; background: var(--gold); border-radius: 2px; }
/* Locate hit — a gold ring, and never a change in size or position:
   the point of this view is that a book stays exactly where it is. */
.bcd-hit .spine, .bcd-hit .stack-bar { box-shadow: 0 0 0 2px var(--gold), 0 0 0 4px rgba(213,166,58,.28); z-index: 2; }

/* ── Tray ─────────────────────────────────────────────── */
#designer-tray {
  flex: none; border-top: 2px solid var(--gold);
  background: var(--paper);
  padding: 0 26px calc(6px + env(safe-area-inset-bottom));
}
#designer-tray.bcd-tray-target { background: var(--gold-soft); }
.bcd-tray-bar { display: flex; align-items: center; gap: 12px; height: 40px; }
.bcd-tray-label {
  border: 0; background: none; padding: 0;
  font-size: 12px; color: var(--ink-soft); display: flex; align-items: center; gap: 6px;
}
.bcd-tray-label:hover { color: var(--ink); }
.bcd-caret { font-size: 9px; }
.bcd-tray-strip {
  display: none; gap: 3px; align-items: flex-end;
  overflow-x: auto; overflow-y: hidden;
  padding: 0 2px 8px; height: 142px;
}
#designer-tray.open .bcd-tray-strip { display: flex; }
/* Tray spines are shown small — this is the pile waiting to go up, not
   another shelf, and full-height spines down here compete with the real
   ones above for what the eye reads as "the collection". */
.bcd-tray-strip .spine { transform-origin: bottom center; transform: scale(.5); margin: 0 -10px; }
.bcd-tray-strip .spine:hover { transform: scale(.5) translateY(-9px); }
.bcd-tray-item { flex: none; display: flex; align-items: flex-end; }
.bcd-tray-item.bcd-dragging { opacity: .35; }

/* + menu: opens UPWARD out of the tray bar, since there is nothing but
   the window edge below it. */
.bcd-add { position: relative; flex: none; }
.bcd-add-btn {
  width: 28px; height: 28px; border: 1px solid var(--ink); background: #fff;
  border-radius: var(--r); font-size: 17px; line-height: 1; color: var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.bcd-add-btn.on, .bcd-add-btn:hover { background: var(--ink); color: #fff; }
.bcd-add-menu {
  position: absolute; bottom: 34px; left: 0; z-index: 30;
  display: none; flex-direction: column; min-width: 156px;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  box-shadow: 0 -4px 14px rgba(0,0,0,.10);
}
.bcd-add-menu.open { display: flex; }
.bcd-add-menu button {
  border: 0; background: none; text-align: left; padding: 9px 12px;
  font-size: 13px; color: var(--ink); border-bottom: 1px solid #f1f1f3;
}
.bcd-add-menu button:last-child { border-bottom: 0; }
.bcd-add-menu button:hover { background: var(--gold-soft); }

/* .f-label uppercases everything inside it, which turns an inline
   "optional" hint into a second shouted heading. Only in the designer's
   dialogs, where the hints are used. */
.bcd-grid-fields ~ .f-label .rec-sub,
#modal-root .f-label .rec-sub { text-transform: none; letter-spacing: 0; font-weight: 400; }
.bcd-grid-fields { display: flex; align-items: center; gap: 8px; }
.bcd-grid-fields input { width: 78px; }
.bcd-share { display: flex; flex-direction: column; gap: 6px; }
.bcd-share-row { display: flex; align-items: center; gap: 8px; font-size: 13px; }

/* ── Author catalog: multi-select ────────────────────── */
.cat-selbox { flex: none; display: flex; align-items: center; }
.cat-selbox input { width: 16px; height: 16px; accent-color: var(--gold-dk); cursor: pointer; }
/* Owned rows have no checkbox but still need the column to line up. */
.cat-selbox-spacer { flex: none; width: 16px; }
.cat-row.on { background: var(--gold-soft); }
.cat-selgroup {
  flex: none; font-size: 12px; font-weight: 600; color: var(--ink-soft);
  border: 1px solid var(--hairline); border-radius: var(--r); padding: 3px 9px; background: #fff;
}
.cat-selgroup:hover { border-color: var(--gold); color: var(--ink); }
#cat-sel-bar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 90;
  display: flex; align-items: center; gap: 14px; flex-wrap: nowrap;
  background: #fff; border: 1px solid var(--hairline); border-radius: var(--r);
  box-shadow: 0 8px 26px rgba(43, 43, 43, .18); padding: 9px 14px;
}
/* Nothing in this bar wraps. It's shrink-to-fit (position:fixed), and a
   wrapping flex container reports a max-content width narrower than its
   contents really need — which broke the four buttons across two rows at
   1280px with 640px of bar and viewport to spare. */
#cat-sel-bar .ssb-actions { flex-wrap: nowrap; }
#cat-sel-bar .btn { white-space: nowrap; }
/* Who a batch add credits. A real <fieldset> — these checkboxes are one
   grouped question, and the legend is the question. Names are plain
   uppercase text, not the coloured reader chips: a chip reads as a
   filled-in state, and here they're an unanswered choice. */
.reader-pick {
  border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 4px 12px 7px; margin: 0;
  display: flex; align-items: center; gap: 14px;
  /* flex:none + nowrap: inside the floating batch bar this is a flex item,
     and letting it shrink stacked the names vertically and tripled the
     bar's height. The names sit in a row, always. */
  flex: none; flex-wrap: nowrap;
}
.reader-pick > legend {
  padding: 0 5px; font-size: 10px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ghost);
}
.reader-pick .own-toggle {
  margin: 0; gap: 6px;
  font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-soft);
}
.reader-pick input:checked ~ span { color: var(--ink); }
.reader-pick input { width: 14px; height: 14px; }
/* In the floating batch bar the fieldset sits inline with the buttons, so
   it loses the block margin a modal's copy needs. */
#ba-readers.reader-pick, #ci-readers.reader-pick { margin: 0 0 14px; }
