/* bookworm/recui/static/shell.css
   Reading-room design system: warm paper ground, ink controls, oxblood accent,
   banker's-lamp green for positive signal. Titles set in Iowan Old Style (book face);
   UI and data in the system sans; numbers tabular. Covers are always shown WHOLE
   (2:3 contain) standing on a shelf — never cropped. */

:root {
  --paper: #f4f1e9;         /* room */
  --card: #fdfbf5;          /* raised surface */
  --ink: #241d12;           /* warm ink */
  --muted: #7a7060;
  --line: #e0d8c6;
  --line-soft: #eae4d5;
  --oxblood: #7f2d26;       /* leather: links, active, like */
  --oxblood-ink: #5f1f1a;
  --lamp: #2e6b52;          /* positive bars, agreement */
  --rust: #b0532f;          /* negative bars, dislike */
  --brass: #9c7a3c;         /* small warm highlights */
  --shadow-soft: 0 1px 2px rgba(60,45,20,.08), 0 8px 24px rgba(60,45,20,.08);
  --shadow-lift: 0 2px 4px rgba(60,45,20,.10), 0 14px 34px rgba(60,45,20,.16);
  --serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --ease-out: cubic-bezier(.22, .8, .3, 1);
}

* { box-sizing: border-box; }
html { height: 100%; -webkit-text-size-adjust: 100%; }
body { margin: 0; font: 15px/1.5 var(--sans); background: var(--paper); color: var(--ink); }
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px 28px 48px; }
h1 { font-family: var(--serif); font-weight: 600; font-size: 27px; letter-spacing: .01em; margin: .3em 0 .35em; }
a { color: var(--oxblood); }
.muted { color: var(--muted); font-size: 13px; }
.bar { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
/* controls sitting next to an h1/baseline row centre themselves instead of dangling off the
   text baseline — this is most of the "buttons don't line up" jank */
.bar > button, .bar > select, .bar > label,
.page-head > button, .page-head > select, .page-head > label { align-self: center; }
.hidden { display: none !important; }

:focus-visible { outline: 2px solid var(--oxblood); outline-offset: 2px; border-radius: 4px; }

/* ---------- icons ---------- */
/* Stroke icons inherit currentColor, so a button's state is a COLOR change (ink/lamp/rust),
   never a grayscale filter over an emoji. Sized in em so they track the button's font. */
.ic { width: 1.15em; height: 1.15em; flex: none; }

/* ---------- buttons ---------- */
button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font: 13px/1.4 var(--sans); color: var(--ink); cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 6px 12px; transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
button:hover { border-color: #c9bfa8; background: #fffef9; }
.btn-primary {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
  font-weight: 600; padding: 9px 18px; border-radius: 10px;
}
.btn-primary:hover { background: #3a3020; border-color: #3a3020; }
.btn-quiet { background: none; border-color: transparent; color: var(--muted); }
.btn-quiet:hover { background: rgba(60,45,20,.06); border-color: transparent; }
.link-btn { border: none; background: none; color: var(--oxblood); cursor: pointer; font-size: 13px;
  padding: 6px 0 0; gap: 5px; border-radius: 0; }
.link-btn:hover { text-decoration: underline; }
.link-btn .ic { width: 1em; height: 1em; }

/* ---------- top nav ---------- */
.protonav {
  position: sticky; top: 0; z-index: 30; flex-shrink: 0;
  padding: 10px 20px 8px; background: rgba(253,251,245,.92); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.protonav .nav-row { display: flex; gap: 6px; align-items: center; width: 100%; }
.protonav .nav-debug { margin-left: 14px; }
.protonav .brand {
  font-family: var(--serif); font-style: italic; font-weight: 600; font-size: 19px;
  color: var(--ink); text-decoration: none; margin-right: 14px; letter-spacing: .01em;
}
.protonav .brand:hover { color: var(--oxblood); }
.protonav a.proto-link {
  color: var(--muted); text-decoration: none; font-size: 13.5px; padding: 5px 11px;
  border-radius: 999px; transition: color .15s, background .15s;
}
.protonav a.proto-link:hover { color: var(--ink); background: rgba(60,45,20,.06); }
.protonav a.proto-link.active { background: var(--ink); color: var(--paper); }
.protonav .chk { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); cursor: pointer; font-size: 12.5px; }

/* shaping strip: quiet segmented controls */
.shapebar { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center; width: 100%; padding-top: 7px; font-size: 12.5px; }
.shapebar .seg { flex: none; }
.shapebar .seg { display: inline-flex; align-items: center; gap: 0; }
.shapebar .seg b { color: var(--muted); font-weight: 500; margin-right: 7px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
.shapebar .seg button {
  border: 1px solid var(--line); border-left-width: 0; border-radius: 0; background: var(--card);
  padding: 3px 11px; font-size: 12px; color: var(--muted); white-space: nowrap;
}
.shapebar .seg button:first-of-type { border-left-width: 1px; border-radius: 7px 0 0 7px; }
.shapebar .seg button:last-of-type { border-radius: 0 7px 7px 0; }
.shapebar .seg button.on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.shapebar .chk { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); cursor: pointer; }

/* hard filters — same segmented-button idiom as .shapebar, laid out as a 2-column form grid:
   right-aligned label column, control column. Every segmented control shares one left edge. */
.filterpicker { display: grid; grid-template-columns: max-content 1fr; gap: 8px 12px;
  align-items: center; padding: 12px 0 4px; border-top: 1px dashed var(--line); margin-top: 10px; }
.filterpicker .flabel { color: var(--muted); font-weight: 500; font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .05em; text-align: right; white-space: nowrap; }
.filterpicker .fcell { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; min-width: 0; }
.filterpicker .seg { display: inline-flex; align-items: center; gap: 0; }
.filterpicker .seg button {
  border: 1px solid var(--line); border-left-width: 0; border-radius: 0; background: var(--card);
  padding: 3px 11px; font-size: 12px; color: var(--muted); white-space: nowrap;
}
.filterpicker .seg button:first-of-type { border-left-width: 1px; border-radius: 7px 0 0 7px; }
.filterpicker .seg button:last-of-type { border-radius: 0 7px 7px 0; }
.filterpicker .seg button.on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.filterpicker .seg button:disabled { opacity: .35; cursor: not-allowed; }
.filterpicker .coverage-hint { font-size: 11.5px; }

/* ---------- covers: always whole, standing on a shelf ---------- */
/* .cover is a WRAPPER (div) with a fixed 2:3 slot; the content inside is absolutely
   positioned so a tall image can never stretch the slot. Images are never cropped. */
.cover {
  position: relative; width: 100%; aspect-ratio: 2 / 3; flex: none;
}
.cover .cover-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; object-position: 50% 100%;      /* whole cover, feet on the shelf */
  border-radius: 3px; filter: drop-shadow(0 2px 3px rgba(60,45,20,.28));
}
.cover .cover-letters {
  position: absolute; left: 6%; right: 6%; top: 3%; bottom: 0;
  border-radius: 3px 6px 6px 3px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4%; padding: 10% 8%; text-align: center; color: rgba(255,252,240,.94);
  box-shadow: inset 3px 0 6px rgba(0,0,0,.25), inset 0 0 0 1px rgba(255,255,255,.12);
  filter: drop-shadow(0 2px 3px rgba(60,45,20,.28));
}
.cover .cover-letters::before {           /* blind-embossed frame, like a cloth binding */
  content: ""; position: absolute; inset: 7%; border: 1px solid rgba(255,252,240,.35); border-radius: 2px;
}
.cover .cover-letters .init { font-family: var(--serif); font-weight: 600; font-size: 1.9em; letter-spacing: .06em; }
.cover .cover-letters .t {
  font-family: var(--serif); font-size: .72em; line-height: 1.3; opacity: .9;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* the shelf the books stand on (cards + feed only) */
.cover.on-shelf::after {
  content: ""; position: absolute; left: -5%; right: -5%; bottom: -7px; height: 6px;
  border-radius: 2px; background: linear-gradient(#ddd3bc, #cbc0a4);
  box-shadow: 0 5px 9px rgba(60,45,20,.18);
}

/* ---------- rec cards: a bookshop table ---------- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 30px 22px; padding-top: 8px; }
.card { position: relative; display: flex; flex-direction: column; background: none; border: none;
  animation: rise .45s var(--ease-out) backwards; animation-delay: calc(var(--i, 0) * 40ms); }
.card > .cover { transition: transform .22s var(--ease-out); }
.card:hover > .cover { transform: translateY(-4px); }
.card .body { padding: 13px 2px 2px; cursor: pointer; }
.card .title {
  font-family: var(--serif); font-weight: 600; font-size: 14.5px; line-height: 1.32;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  min-height: 2.64em;
}
.card .body:hover .title { color: var(--oxblood); }
.card .author { color: var(--muted); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.card.liked > .cover::before {
  content: "✓ in your taste"; position: absolute; z-index: 2; top: 6px; left: 2%;
  background: var(--lamp); color: #fff; font-size: 10px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; box-shadow: 0 1px 3px rgba(0,0,0,.2);
}
.actions { display: flex; gap: 6px; padding: 8px 0 0; }
.actions button {
  flex: none; width: 34px; height: 30px; padding: 0; font-size: 14px; line-height: 1;
  border-radius: 8px; background: none; border: 1px solid transparent; color: var(--muted);
  opacity: .65; transition: opacity .15s, color .15s, background .15s, border-color .15s, transform .1s;
}
.actions button:hover { opacity: 1; background: var(--card); border-color: var(--line); transform: scale(1.08); }
.actions button.on { opacity: 1; background: var(--card); border-color: var(--line); }
/* each verb keeps its signal colour on hover/active — like: lamp, dislike: rust, save: brass */
.actions .act-like:hover, .actions .act-like.on { color: var(--lamp); }
.actions .act-dislike:hover, .actions .act-dislike.on { color: var(--rust); }
.actions .act-save:hover, .actions .act-save.on { color: var(--brass); }
.actions .act-remove:hover { color: var(--rust); }

/* .card is bookCard()'s own class, so this rule reaches every bookCard() surface — Grid, the
   chat-results list, Duels, Swipe and the Shelves ("reason rows") page — not just Grid: the three
   verbs are distributed under the cover with clear air at both ends, so two adjacent cards'
   controls don't read as one continuous run of six buttons. It does NOT reach the taste tray or
   the detail drawer, which keep their own separate .actions rules below (tray: centered, drawer:
   left-aligned default, both scoped to their own class). The My-books page wants this same
   space-evenly look but isn't a .card at all — it builds its own markup (books.js) and gets it
   from the separate .shelf-item .actions rule further down, not from this one. */
.card .actions { justify-content: space-evenly; padding: 8px 6px 2px; }

/* ---------- search ---------- */
.searchwrap { position: relative; }
.searchwrap::before {              /* quiet magnifier, drawn in the muted ink of the room */
  content: ""; position: absolute; left: 15px; top: 50%; width: 16px; height: 16px;
  transform: translateY(-50%); pointer-events: none; opacity: .55;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a7060' stroke-width='1.8' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.35-4.35'/></svg>") center/contain no-repeat;
}
.searchwrap input {
  width: 100%; padding: 11px 16px 11px 40px; font: 15px var(--sans); color: var(--ink);
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
  box-shadow: 0 1px 2px rgba(60,45,20,.05); transition: border-color .15s, box-shadow .15s;
}
.searchwrap input::placeholder { color: #a89c88; }
.searchwrap input:focus { outline: none; border-color: var(--oxblood); box-shadow: 0 0 0 3px rgba(127,45,38,.12); }
.dropdown {
  position: absolute; z-index: 10; left: 0; right: 0; margin-top: 6px; background: var(--card);
  border: 1px solid var(--line); border-radius: 12px; max-height: 360px; overflow: auto;
  box-shadow: var(--shadow-lift); animation: rise .18s var(--ease-out);
}
.dropdown .row { display: flex; gap: 10px; align-items: center; padding: 7px 12px; cursor: pointer; }
.dropdown .row:hover { background: rgba(127,45,38,.06); }
.dropdown .cover { width: 30px; font-size: 8px; }
.dropdown .title { font-family: var(--serif); font-weight: 600; font-size: 14px; }
.dropdown .author { color: var(--muted); font-size: 12px; }

/* ---------- taste tray: single-line carousel ---------- */
/* Fixed height + nowrap + overflow-x is what keeps this a modest one-liner whether the
   profile holds 3 books or 195 — never a wrapping wall (see grid.js's renderTray). */
.tray { display: flex; flex-wrap: nowrap; gap: 12px; padding: 10px 12px; background: var(--card);
  border: 1px solid var(--line); border-radius: 12px; margin: 10px 0 4px;
  /* 204px, not 196: box-sizing is border-box, so usable inner height is 204 − 2px border − 20px
     padding = 182px, against an item that measures 177.125px (cover 114 + 4 gap + 30.125 title/author
     + 4 gap + 25 action row). The old 196px left 174px of room for a 174.125px item — over by a
     hair, hidden only by overflow-y:hidden — so the 21px→24px button bump would have clipped the
     button row's bottom 3px. Still a modest single-line carousel, which is the whole point of the
     fixed height (see the class comment above); it just accounts for its own border now. */
  height: 204px; align-items: flex-start; overflow-x: auto; overflow-y: hidden; scroll-behavior: smooth; }
.tray .taste-item { flex: none; width: 96px; display: flex; flex-direction: column; gap: 4px; }
/* Cover stays 76px while the item grows to 96px, and the item's width is set by the BUTTON ROW,
   not by the cover: 3 x 26px + 2 x 6px gap = 90px, and @media (pointer:coarse) enlarges the
   buttons to 30px for a 102px row. An 84px item spilled buttons 3px each side with a mouse and
   9px on touch (measured). 96px fits the mouse row with slack and cuts the touch spill to 3px,
   down from the 10px each side it spilled before this task.
     The cover must NOT follow the item's width. A 2:3 cover at 96px is 144px tall, and
   144 + title/author + a 25px action row = ~207px against the tray's 182px of usable inner height
   — it would clip, the one thing .tray's own comment says the fixed height exists to prevent.
   Growing the tray to fit a bigger COVER would defeat the "modest one-liner" purpose; growing it
   8px to fit bigger BUTTONS does not. */
.tray .taste-item .cover { width: 76px; margin: 0 auto; font-size: 7px; }
.tray .ti-title { font-family: var(--serif); font-weight: 600; font-size: 11.5px; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tray .ti-author { color: var(--muted); font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tray .actions { gap: 6px; padding-top: 1px; justify-content: center; }
.tray .actions button { width: 26px; height: 24px; font-size: 10.5px; border-radius: 6px; }
.tray .actions .ic { width: 13px; height: 13px; }

/* ---------- tag picker ---------- */
.tagpanel { background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px 12px; margin: 8px 0; animation: rise .2s var(--ease-out); }
.tagpicker .hint { color: var(--muted); font-size: 12px; margin-bottom: 10px; }
.tagsec { margin-bottom: 12px; }
.tagsec:last-child { margin-bottom: 2px; }
.seclbl { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 6px; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tagchip { border: 1px solid var(--line); background: var(--paper); border-radius: 999px;
  padding: 3px 12px; font-size: 12.5px; cursor: pointer; color: var(--ink); transition: all .12s; }
.tagchip:hover { border-color: var(--oxblood); color: var(--oxblood); }
.tagchip.like { background: var(--lamp); color: #fff; border-color: var(--lamp); }
.tagchip.like::before { content: "+ "; }
.tagchip.dislike { background: var(--rust); color: #fff; border-color: var(--rust); }
.tagchip.dislike::before { content: "\2212  "; }

/* ---------- detail drawer ---------- */
.detail-backdrop { position: fixed; inset: 0; background: rgba(36,29,18,.35); opacity: 0;
  pointer-events: none; transition: opacity .25s; z-index: 40; }
.detail-backdrop.open { opacity: 1; pointer-events: auto; }
.detail-panel {
  position: fixed; top: 0; right: 0; width: 560px; max-width: 94vw; height: 100%;
  background: var(--card); border-left: 1px solid var(--line);
  box-shadow: -12px 0 40px rgba(36,29,18,.18); padding: 22px 26px 32px; overflow-y: auto;
  transform: translateX(100%); transition: transform .32s var(--ease-out); z-index: 41;
}
.detail-panel.open { transform: translateX(0); }
.detail-head { display: flex; gap: 18px; margin-top: 4px; }
.detail-head .cover { width: 132px; font-size: 11px; }
.detail-headinfo { flex: 1; min-width: 0; padding-top: 6px; }
.detail-panel h2 { font-family: var(--serif); font-weight: 600; font-size: 21px; line-height: 1.25; margin: 0 0 4px; }
.detail-panel .author { color: var(--muted); font-size: 14px; }
.detail-panel .why { color: var(--oxblood); font-size: 13px; margin-top: 10px; line-height: 1.45; }
.detail-panel .detail-meta { color: var(--muted); font-size: 12.5px; margin-top: 6px; font-variant-numeric: tabular-nums; }
.detail-panel .actions { padding: 14px 0 0; flex-wrap: wrap; }
.detail-panel .actions button {
  width: auto; height: auto; opacity: 1; display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; font-size: 13px; background: var(--paper); border: 1px solid var(--line); color: var(--ink);
}
.detail-panel .actions button:hover { transform: none; border-color: #c9bfa8; }
.detail-syn { font-size: 14px; line-height: 1.6; color: #453b2c; margin: 12px 0 0;
  display: -webkit-box; -webkit-line-clamp: 10; -webkit-box-orient: vertical; overflow: hidden; }
.detail-syn.expanded { display: block; -webkit-line-clamp: none; }
.detail-close {
  position: sticky; top: 0; float: right; z-index: 2; border: 1px solid var(--line);
  background: var(--card); width: 30px; height: 30px; padding: 0; border-radius: 999px;
  font-size: 14px; color: var(--muted);
}
.detail-close:hover { color: var(--ink); }
.detail-close .ic { width: 15px; height: 15px; }

/* section headers shared by drawer / debug / compare */
.dsec-h { font-weight: 600; color: var(--muted); margin: 16px 0 6px; font-size: 11px;
  text-transform: uppercase; letter-spacing: .08em; }

/* "why it matches" chips */
.matchbox { margin: 12px 0 0; display: flex; flex-direction: column; gap: 7px; }
.matchsec { font-size: 13px; }
.matchsec .matchlbl { color: var(--muted); margin-right: 6px; font-size: 12px; }
.matchbox .chips { display: inline-flex; flex-wrap: wrap; gap: 5px; margin-top: 3px; }
.matchchip { border-radius: 999px; padding: 2px 10px; font-size: 12px; border: 1px solid transparent; }
.matchchip.axis  { background: #e9f1ea; color: #275a41; border-color: #cfe0d2; }
.matchchip.trope { background: #f1ecf7; color: #59407d; border-color: #ded2ec; }
.matchchip.sub   { background: #eaeef6; color: #35507f; border-color: #d4dcec; }
.matchchip.theme { background: #f7efe4; color: #7d5423; border-color: #ecdcc2; }
.matchchip.shared { box-shadow: 0 0 0 1.5px var(--brass); }
.matchsec.reads-like { color: #574c3a; font-style: italic; font-family: var(--serif); }

/* "More like this book, tuned to my taste" — anchor action results, inside the detail drawer */
.morelikebox { margin: 14px 0 0; display: flex; flex-direction: column; gap: 8px; }
.morelike-head { font-weight: 600; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }
.morelikebox .seg { display: inline-flex; align-items: center; gap: 0; }
.morelikebox .seg b { color: var(--muted); font-weight: 500; margin-right: 7px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
.morelikebox .seg button {
  border: 1px solid var(--line); border-left-width: 0; background: var(--paper); color: var(--ink);
  padding: 3px 10px; font-size: 12px; cursor: pointer;
}
.morelikebox .seg button:first-of-type { border-left-width: 1px; border-radius: 7px 0 0 7px; }
.morelikebox .seg button:last-of-type { border-radius: 0 7px 7px 0; }
.morelikebox .seg button.on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.morelike-results { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 12px; }
.morelike-item { cursor: pointer; }
.morelike-item .cover { width: 84px; font-size: 8px; }
.morelike-item .title { font-size: 12px; font-weight: 500; margin-top: 4px; line-height: 1.25; }
.morelike-item .author { font-size: 11px; color: var(--muted); }

/* ---------- bars (debug, compare) ---------- */
.drow { display: grid; grid-template-columns: 128px 1fr 40px; align-items: center; gap: 8px; margin: 2px 0; }
.dlbl { color: #5c5343; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dnum { text-align: right; color: var(--muted); font-size: 11.5px; font-family: var(--mono); }
.dbar { position: relative; height: 8px; background: #e9e2d1; border-radius: 4px; overflow: hidden; }
.dbar-fill { position: absolute; top: 0; height: 100%; border-radius: 4px; animation: barfill .5s var(--ease-out); }
.dbar-fill.pos { background: var(--lamp); }
.dbar-fill.neg { background: var(--rust); }
@keyframes barfill { from { width: 0; } }

/* debug drawer */
.debugbox { margin-top: 18px; padding-top: 10px; border-top: 2px dashed var(--line); font-size: 12.5px; }
.dmeta { color: #6b6154; margin: 2px 0; line-height: 1.4; }
.dwarn { color: var(--rust); margin: 3px 0; }
.dtraj { color: #574c3a; font-style: italic; margin: 4px 0; line-height: 1.45; font-family: var(--serif); }

/* ---------- update pill (grid batched refresh) ---------- */
.update-pill {
  position: sticky; top: 74px; z-index: 20; display: inline-flex; align-items: center; gap: 8px;
  background: var(--ink); color: var(--paper); border: none; border-radius: 999px;
  padding: 9px 20px; font-size: 13.5px; font-weight: 600; box-shadow: var(--shadow-lift);
  animation: rise .25s var(--ease-out);
}
.update-pill .ic { width: 14px; height: 14px; }
.update-pill:hover { background: #3a3020; }

/* ---------- compare page ---------- */
.cmp-pick { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin: 14px 0 6px; }
.cmp-picklbl { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 6px; }
.cmp-picked { display: flex; align-items: center; gap: 10px; margin-top: 10px; min-height: 46px; }
.cmp-picked .cover { width: 32px; font-size: 6px; }
.cmp-picktitle b { font-family: var(--serif); font-size: 14.5px; }
.cmp-picktitle .author { display: block; color: var(--muted); font-size: 12px; }

/* head-to-head: one shared grid per line, so the two sides can never drift */
.duel { margin-top: 18px; }
.duel-line { display: grid; grid-template-columns: 1fr 150px 1fr; gap: 8px 18px; align-items: center; }
.duel-line.covers { align-items: end; margin-bottom: 10px; }
.duel-line.covers .cover { width: 150px; margin: 0 auto; font-size: 12px; }
.duel-verdict { text-align: center; align-self: center; }
.duel-verdict .pct { font-family: var(--serif); font-size: 34px; font-weight: 600; display: block; line-height: 1.1; }
.duel-verdict .word { color: var(--muted); font-size: 12.5px; }
.duel-line.names { align-items: start; margin-bottom: 16px; }
.duel-name { text-align: center; }
.duel-name b { font-family: var(--serif); font-size: 15.5px; line-height: 1.3; display: block; }
.duel-name .author { color: var(--muted); font-size: 12.5px; }
.duel-mid { text-align: center; color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.duel-line.axis-row { margin: 3px 0; }
.duel-cell { display: flex; align-items: center; gap: 8px; }
.duel-cell .dbar { flex: 1; }
.duel-cell .dnum { flex: none; width: 38px; }
.duel-cell.a { flex-direction: row-reverse; }     /* mirrored: bars grow toward the center */
.duel-cell.a .dbar-fill { right: 0; left: auto !important; }
.duel-line.tag-row { margin: 10px 0; align-items: start; }
.duel-line.tag-row .chips { justify-content: flex-end; }
.duel-line.tag-row .chips.b-side { justify-content: flex-start; }
.duel-line.reads { font-size: 12.5px; color: #574c3a; font-style: italic; font-family: var(--serif); margin: 8px 0; align-items: start; }
.duel-line.reads .a-side { text-align: right; }
.cmp-match { border-top: 1px solid var(--line-soft); margin-top: 22px; padding-top: 6px; }

/* per-channel similarity rows */
.simlegend { color: var(--muted); font-size: 12px; margin: 2px 0 8px; }
.simrow { display: grid; grid-template-columns: 132px 220px 44px 1fr; align-items: center; gap: 10px; margin: 3px 0; cursor: help; }
.simrow .dbar.simbar { height: 9px; }
.simrow .dbar.simbar::before { content: ""; position: absolute; left: 50%; top: -1px; bottom: -1px; width: 1px; background: #b9ae97; z-index: 1; }
.simrow .simword { color: var(--muted); font-size: 12px; }

/* bridge shelf */
.cmp-bridge { display: flex; align-items: center; gap: 8px; overflow-x: auto; padding: 14px 2px 18px; }
.cmp-node { flex: none; width: 96px; text-align: center; cursor: pointer; border-radius: 8px; padding: 5px 4px;
  transition: background .15s, transform .18s var(--ease-out); }
.cmp-node:hover { background: rgba(60,45,20,.06); transform: translateY(-2px); }
.cmp-node.A, .cmp-node.B { background: var(--card); box-shadow: inset 0 0 0 1.5px var(--oxblood); }
.cmp-node .cover { font-size: 8px; }
.cmp-nodetitle { font-family: var(--serif); font-size: 11.5px; font-weight: 600; line-height: 1.25;
  margin-top: 6px; max-height: 2.6em; overflow: hidden; }
.cmp-node .author { font-size: 10.5px; color: var(--muted); }
.cmp-link { flex: none; color: var(--muted); font-size: 11px; font-family: var(--mono); white-space: nowrap; }

/* ---------- animations ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }
.card.leaving { animation: card-out .28s ease-in forwards; pointer-events: none; }
@keyframes card-out { to { opacity: 0; transform: perspective(600px) rotateY(65deg) scale(.92); } }
.card.entering { animation: card-in .32s var(--ease-out); }
@keyframes card-in { from { opacity: 0; transform: translateY(10px) scale(.96); } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

.protonav .nav-who { font-size: 13px; }
.protonav .nav-logout { background: none; border: 1px solid var(--line); color: var(--muted);
  border-radius: 7px; padding: 4px 10px; font-size: 12px; cursor: pointer; margin-left: 10px; }
.protonav .nav-logout:hover { color: var(--oxblood); border-color: #cfc4a9; }

.recui-toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--ink); color: var(--paper); padding: 9px 16px; border-radius: 9px; font-size: 13px;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 200; }
.recui-toast.show { opacity: .96; transform: translateX(-50%) translateY(0); }

/* ---------- my books shelves ---------- */
/* The jump bar sticks BELOW the nav, not at top:0 — .protonav is itself sticky at top:0 with
   z-index:30, so a bar at top:0 with a lower z-index would slide underneath it. 81px is the
   nav's measured rendered height on desktop (77px on phones, see the ≤640 block below); it is a
   measurement, not a round number, so re-measure it if the nav's rows or padding ever change.
   Sits at z-index 20 — under the nav, over the shelves. */
.shelf-jump { position: sticky; top: 81px; z-index: 20; display: flex; flex-wrap: wrap; gap: 8px;
  padding: 10px 0; margin: 4px 0 2px;
  background: rgba(253,251,245,.94); backdrop-filter: blur(8px); }
.shelf-jump a { display: inline-flex; align-items: baseline; gap: 6px; text-decoration: none;
  color: var(--muted); font-size: 13px; border: 1px solid var(--line); border-radius: 999px;
  padding: 5px 13px; background: var(--paper); transition: color .15s, border-color .15s; }
.shelf-jump a:hover { color: var(--oxblood); border-color: var(--oxblood); }
.shelf-jump a .n { font-variant-numeric: tabular-nums; font-size: 12px; opacity: .8; }

/* scroll-margin-top clears BOTH sticky layers (nav 81 + jump bar ~53) plus a little air, so an
   anchor jump doesn't park the section's heading underneath them. */
.shelf { margin: 22px 0; scroll-margin-top: 146px; }
.shelf h2 { font-family: var(--serif); font-size: 19px; }
.shelf-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 14px; }
.shelf-item .title { font-size: 13px; font-weight: 600; margin-top: 6px; }
.shelf-item .author { font-size: 12px; color: var(--muted); }
/* Shelf items use the shared .actions vocabulary (see the "rec cards" section above) rather than
   a bespoke .shelf-actions style: same icon-only buttons, same per-verb hover colours, same
   sizes as the grid card and the taste tray. Icon+text buttons wrapped onto two rows here (the
   row measured 96px tall, making each item 427px), which is what made the block look ragged. */
.shelf-item .actions { justify-content: space-evenly; padding: 6px 4px 0; }

/* ---------- chat mode: new-chat chooser ---------- */
.chooser-btns { display: flex; gap: 10px; margin: 8px 0; }
.chooser-btns button { font: 13px var(--sans); color: var(--ink); background: var(--paper);
  border: 1px solid var(--line); border-radius: 999px; padding: 7px 16px; cursor: pointer; transition: border-color .15s, background .15s; }
.chooser-btns button:hover { border-color: var(--brass); background: var(--card); }
.chooser-btns .ic { width: 14px; height: 14px; color: var(--brass); }

/* ---------- phones (≤640px): same reading room, smaller table ---------- */
/* Pages must carry <meta name="viewport" content="width=device-width, initial-scale=1,
   viewport-fit=cover"> for any of this to apply — without it iOS lays out at ~980px. */
@media (max-width: 640px) {
  .wrap { padding: 14px 16px calc(36px + env(safe-area-inset-bottom)); }
  h1 { font-size: 23px; }
  /* nav rows scroll sideways instead of wrapping into a 3-row wall; still unpinned —
     even two thin rows are too dear to keep on a phone screen */
  .protonav { position: static; padding: 8px 12px 6px; }
  .protonav .nav-row, .shapebar {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .protonav .nav-row::-webkit-scrollbar, .shapebar::-webkit-scrollbar { display: none; }
  .protonav .nav-row > * { flex: none; }
  .protonav .brand { margin-right: 8px; }
  .protonav .nav-who { display: none; }          /* the session is yours; the name earns no 60px here */
  .protonav .nav-debug { display: none; }        /* a dev toggle doesn't earn phone-nav space */

  /* page-head hints are desk-plaque copy — on a phone the control keeps its room instead.
     Live counters (#counter, #progress) carry ids; static hints don't, so only hints go. */
  .page-head > .muted:not([id]), .bar > .muted:not([id]) { display: none; }

  /* two comfortable columns of whole covers on a 390–430pt screen */
  .grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 24px 14px; }

  /* iOS zooms into any focused field under 16px — match it instead of fighting it */
  .searchwrap input { font-size: 16px; }

  /* filters: the label column would squeeze the controls off the edge — stack instead */
  .filterpicker { grid-template-columns: 1fr; gap: 3px 0; }
  .filterpicker .flabel { text-align: left; margin-top: 8px; }
  .filterpicker .flabel:first-child { margin-top: 0; }

  /* the drawer becomes the whole page (it already scrolls; the ✕ stays sticky) */
  .detail-panel { width: 100%; max-width: 100%; border-left: none;
    padding: 18px 16px calc(28px + env(safe-area-inset-bottom)); }
  .detail-head { gap: 14px; }
  .detail-head .cover { width: 108px; }
  .detail-panel .actions { flex-wrap: wrap; }

  /* debug / compare bars: narrower label gutter */
  .drow { grid-template-columns: 92px 1fr 36px; }

  /* compare: pickers stack; the head-to-head keeps its shared grid with a slimmer spine */
  .cmp-pick { grid-template-columns: 1fr; gap: 16px; }
  .duel-line { grid-template-columns: 1fr 92px 1fr; gap: 6px 10px; }
  .duel-line.covers .cover { width: 92px; font-size: 9px; }
  .duel-verdict .pct { font-size: 24px; }
  /* per-channel rows: the verdict word drops under the bar instead of overflowing right */
  .simrow { grid-template-columns: 92px 1fr 36px; gap: 1px 8px; }
  .simrow .simword { grid-column: 2 / -1; }

  .update-pill { top: 10px; }                    /* the wrapped nav is taller; pin near the top edge */
  .recui-toast { bottom: calc(16px + env(safe-area-inset-bottom)); max-width: calc(100% - 32px); }

  /* On phones .protonav is `position: static` (see the rule above in this same block) — the nav
     deliberately scrolls away to give a small screen its height back. So there is no sticky layer
     for the jump bar to sit below, and pinning it at the nav's 77px height would leave 77px of
     blank page above a floating bar once the nav scrolls off (measured: gapAboveJump 77px). It
     sticks to the viewport top instead, and scroll-margin-top drops to just the bar's own height
     (52px measured) plus air. */
  .shelf-jump { top: 0; }
  .shelf { scroll-margin-top: 64px; }
}

/* ---------- touch: no hover, bigger targets ---------- */
@media (pointer: coarse) {
  /* hover-revealed color is invisible to a thumb — rest state must already read as live */
  .actions button { width: 42px; height: 38px; font-size: 16px; opacity: .85; border-color: var(--line-soft); }
  .tray .actions button { width: 30px; height: 27px; font-size: 12px; }
  .tray .actions .ic { width: 14px; height: 14px; }
  .protonav a.proto-link { padding: 7px 12px; }
  .shapebar .seg button, .filterpicker .seg button, .morelikebox .seg button { padding: 6px 11px; }
  .tagchip { padding: 6px 13px; }
  .dropdown .row { padding: 10px 12px; }
}

/* ---------- chat mode: "thinking" status line (pulsing stage label while a turn is in flight) ---------- */
/* opacity-only pulse, never layout — the global prefers-reduced-motion rule above (animation: none)
   already turns this off, leaving the plain (fully opaque) label text with no pulse. */
.msg.status .pulse { display: inline-block; color: var(--muted); font-style: italic;
  animation: status-pulse 1.7s ease-in-out infinite; }
@keyframes status-pulse { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }
