/* ============================================================
   Release Radar — design system
   ============================================================ */

/* Self-hosted fonts (was Google Fonts). Variable woff2 from Fontsource — one
   file per family covers every weight via the `wght` axis; Newsreader also carries
   the optical-size (`opsz`) axis, so title-size text optically sizes itself when
   `font-optical-sizing: auto` (the browser default) is in effect. latin +
   latin-ext subsets keep accented developer names from falling back to tofu.
   `swap` = paint immediately in the fallback stack, swap in the branded face when
   it arrives. Served same-origin, cached a year (see server.py). Licenses: OFL
   1.1, web/fonts/LICENSES/. */
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Newsreader"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/newsreader-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Newsreader"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/newsreader-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Space Grotesk"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/space-grotesk-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Space Grotesk"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/space-grotesk-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Newsreader — the cheat-sheet poster's display serif. The public pages get
   these two faces from site_chrome.CHROME_CSS; the app needs them here too.
   Same self-hosted files, same unicode-range split. */
@font-face {
  font-family: "Newsreader"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/newsreader-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Newsreader"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/newsreader-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Caveat — the share canvas's handwritten face (layout.js FF_ROLES.annotation,
   the "annotation" text role a designed card can set a margin note in). Only
   the graphic renderer asks for it, so the file is fetched the first time a
   card paints and never on an ordinary page load. Variable wght like the rest. */
@font-face {
  font-family: "Caveat"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/caveat-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Caveat"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/caveat-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #0c0d12;
  --bg-elev: #14161f;
  --bg-elev-2: #1b1e2a;
  --bg-hover: #222637;
  --border: #272b3a;
  --border-strong: #353a4f;
  --text: #eef0f7;
  --text-dim: #9aa0b5;
  --text-faint: #646a82;
  --accent: #c08bff;
  --accent-2: #6ad7ff;
  --accent-2-text: #6ad7ff;
  --accent-grad: linear-gradient(120deg, #c08bff 0%, #6ad7ff 100%);
  --good: #5be4a0;
  --warn: #ffcb6b;
  --danger: #ff7a93;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
  --radius: 16px;
  --radius-sm: 10px;
  /* Topbar height: 8px padding ×2 + 40px ghost-btn + 1px border. Sticky
     offsets (sidebar, week-head) derive from this — retune here, not there. */
  --header-h: 57px;
  /* Brand assets (inline SVG cat + wordmark). Mirror --accent on each theme. */
  --brand-c0: #c08bff;
  --brand-c1: #6ad7ff;
  --brand-bow: #ff7a93;
  --brand-bow-knot: #e86580;
}

/* While flipping the theme, kill every transition so the whole page repaints
   in one instant frame instead of animating background/color on every node at
   once — the latter is what makes the toggle feel sluggish on mobile. */
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after {
  transition: none !important;
}

[data-theme="light"] {
  --bg: #f4f5fa;
  --bg-elev: #ffffff;
  --bg-elev-2: #ffffff;
  --bg-hover: #eef0f7;
  --border: #e3e6ef;
  --border-strong: #d3d7e4;
  --text: #1a1c26;
  --text-dim: #5a6075;
  --text-faint: #8b90a4;
  --accent: #8b3dff;
  --accent-2: #1499d6;
  /* Small cyan labels need a darker foreground than the decorative accent. */
  --accent-2-text: #0b6f9f;
  --accent-grad: linear-gradient(120deg, #8b3dff 0%, #1499d6 100%);
  --good: #16a06b;
  --warn: #c98300;
  --danger: #e0395b;
  --shadow: 0 8px 30px rgba(60, 70, 120, 0.12);
  /* Deepen the brand gradient so the cool end holds contrast on near-white. */
  --brand-c0: #8b3dff;
  --brand-c1: #1499d6;
  --brand-bow: #e0395b;
  --brand-bow-knot: #c42f4d;
}

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

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/* radial glow backdrop */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background:
    radial-gradient(60% 50% at 12% 0%, rgba(192, 139, 255, 0.10), transparent 70%),
    radial-gradient(50% 50% at 100% 0%, rgba(106, 215, 255, 0.08), transparent 70%);
  pointer-events: none;
  z-index: 0;
}

/* ===================== TOPBAR ===================== */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 8px 28px;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}

/* Transparent grouping wrappers: the nav + utility buttons flow directly in
   the topbar on every breakpoint (≤640px hides the tabs copy — the bottom
   bar owns them there). */
.nav-menu, .nav-util { display: contents; }

.brand { display: flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; }
.brand-logo {
  /* 40, not 36: the mark is eight shapes and the seams between them are the
     first thing to go. A quarter more size is a quarter more of every gap. */
  width: 40px; height: 40px; flex-shrink: 0;
  object-fit: contain;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
}
[data-theme="light"] .brand-logo { filter: drop-shadow(0 2px 6px rgba(60, 70, 120, 0.18)); }

/* TWO MARKS, ONE VISIBLE. The mark has a dark-art version for light grounds and
   a light-art version for dark ones — the same shapes with the body gradient and
   the stroke inverted, because a near-white outline disappears on white and dark
   navy shapes disappear on navy.
   Both are INLINED in the shell (index.html) rather than swapped as an `src`:
   the topbar paints before any image request resolves, and a mark that arrives
   late is worse than one that is briefly the wrong colour. Light is the default
   because the app's default theme is light. */
/* Scoped through .brand-wordmark on purpose: `.brand-wordmark svg` above sets
   `display: block` and is a class PLUS a type, which outranks a bare
   `.brand-word--dark`. Without the extra class both wordmarks painted, stacked
   one above the other. */
.brand-logo--dark,
.brand-wordmark .brand-word--dark { display: none; }
[data-theme="dark"] .brand-logo--light,
[data-theme="dark"] .brand-wordmark .brand-word--light { display: none; }
[data-theme="dark"] .brand-logo--dark { display: block; }
[data-theme="dark"] .brand-wordmark .brand-word--dark { display: block; }

/* The <img> surfaces cannot swap their own src on a theme change, so CSS
   replaces the image itself. A browser that ignores `content` here keeps the
   light-ground mark, which is legible on dark — just without the contrast the
   variant buys. */
[data-theme="dark"] .login-cat,
[data-theme="dark"] .brand-cat,
[data-theme="dark"] .dr-web-actions .dr-action-btn .launcheta-icon {
  content: url("/images/launcheta_mark_on_dark.svg");
}
[data-theme="dark"] .login-wordmark,
[data-theme="dark"] .brand-word {
  content: url("/images/launcheta_wordmark_on_dark.svg");
}
.brand-mark {
  font-size: 26px;
  background: var(--accent-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 12px rgba(192, 139, 255, 0.4));
}
.brand h1 {
  font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.brand-wordmark { margin: 0; line-height: 0; }
.brand-wordmark img, .brand-wordmark svg { height: 22px; width: auto; display: block; }
/* Inline-SVG brand assets are themed via these tokens (see :root). */
.brand-stop-0 { stop-color: var(--brand-c0); }
.brand-stop-1 { stop-color: var(--brand-c1); }
.brand-bow { fill: var(--brand-bow); }
.brand-bow-knot { fill: var(--brand-bow-knot); }
/* Playful flourish: the bowtie spins once when the logo is clicked. */
.brand-bowtie { transform-box: fill-box; transform-origin: center; }
.brand-bowtie.spin { animation: bow-spin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes bow-spin { from { transform: rotate(0); } to { transform: rotate(360deg); }}
@media (prefers-reduced-motion: reduce) {
  .brand-bowtie.spin { animation: none; }
  /* A reader who asks for reduced motion was still getting every transition in
     the app — only two named animations were being silenced. Colour and
     position tweens are motion too, and the nav's 150ms colour tween is also
     what made an accessibility audit sample a tab label mid-fade and report a
     contrast failure that no resting state has. */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }}
/* Tagline retired from the header — the slim one-row bar has no room for it. */
.tagline { display: none; font-size: 12px; color: var(--text-faint); margin-top: 1px; }

/* Icon buttons sit on the right edge — pushed there by the nav pill's
   margin-inline:auto (a second auto margin here would pull the pill off-center).
   Desktop right side reads Studio (DOM order) · Help · avatar menu: #helpBtn is
   last in the DOM (it lives outside #navMenu for the phone header), so `order`
   slots it before the avatar chip. */
#helpBtn { order: 1; }
.user-menu-wrap { order: 2; position: relative; display: inline-flex; }

.ghost-btn {
  width: 40px; height: 40px;
  border-radius: 12px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  font-size: 18px;
  transition: background 0.15s, transform 0.15s;
}
.ghost-btn:hover { background: var(--bg-hover); transform: translateY(-1px); }

/* Avatar chip (desktop): the user's initial on the accent gradient. Rides the
   .ghost-btn 40px box; its .user-menu-wrap anchors the dropdown below. */
.avatar-btn {
  border-radius: 50%;
  background: var(--accent-grad);
  border: none;
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  line-height: 1;
}
/* .ghost-btn:hover (0,2,0) outranks .avatar-btn (0,1,0), so without this the
   chip drops its gradient for the flat hover fill. Re-assert it and give the
   hover its own cue; .ghost-btn:hover's lift still applies. */
.avatar-btn:hover { background: var(--accent-grad); filter: brightness(1.06); }

/* The avatar dropdown — a .shelf-menu popover pinned to the chip's right edge.
   z-index above the sticky week/genre headers (the shelf-menu base z:6 is only
   meant for in-panel pills). */
.user-menu {
  right: 0; top: calc(100% + 6px);
  min-width: 230px; z-index: 80;
}
/* .shelf-menu's display:flex would otherwise beat the UA [hidden] rule. */
.user-menu[hidden] { display: none; }
.user-menu-id { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px 6px; min-width: 0; }
.user-menu-id .name {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-menu-id .email {
  font-size: 12px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-menu .shelf-menu-item { font-size: 13px; padding: 8px 10px; }
.user-menu .shelf-menu-item .icon { width: 15px; height: 15px; }
/* The JS-rendered wrapper inside the menu: transparent on desktop (the popover
   styles the menu element itself); the <=640px block turns the menu element
   into a full-screen scrim and this wrapper into the bottom sheet. */
.user-menu-panel { display: contents; }

/* Account modal (.modal--acct frame): stacked bordered sections, everything
   width-capped so nothing stretches to the modal edge. .acct-avatar repeats
   the .nav-user-avatar recipe (that rule lives inside the <=640px media block,
   so it can't be reused at base). */
.modal--acct {
  max-width: 620px; display: flex; flex-direction: column;
  max-height: calc(100vh - 80px); max-height: calc(100dvh - 80px);
}
.modal--acct .modal-body { overflow-y: auto; }
.acct-section { padding: 18px 0; border-top: 1px solid var(--border); }
.acct-section:first-child { padding-top: 0; border-top: none; }
.acct-section:last-child { padding-bottom: 0; }
/* Group labels chunk the section stack (Features / Security & data). The label
   carries the separator, so the section right after it drops its own. */
.acct-group-label {
  padding-top: 20px; border-top: 1px solid var(--border);
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-faint);
}
.acct-group-label + .acct-section { border-top: none; padding-top: 12px; }
.acct-section-title {
  font-family: "Newsreader", serif; font-size: 15px; font-weight: 600;
  color: var(--text); margin-bottom: 4px;
}
.acct .hint {
  display: block; font-size: 12.5px; color: var(--text-dim);
  line-height: 1.5; margin: 8px 0;
}
.acct .hint:last-child { margin-bottom: 0; }
.acct-profile { display: flex; gap: 14px; align-items: flex-start; }
.acct-avatar {
  flex: none; width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-grad); color: #fff;
  font-size: 19px; font-weight: 600; line-height: 1;
}
.acct-profile-fields { flex: 1; min-width: 0; max-width: 420px; }
.acct-label { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 5px; }
.acct-row { display: flex; gap: 8px; align-items: center; }
.acct-input {
  flex: 1; min-width: 0;
  padding: 8px 11px; border-radius: var(--radius-sm);
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font-size: 13px; font-family: inherit;
}
.acct-input::placeholder { color: var(--text-faint); }
.acct-input:focus { outline: none; border-color: var(--accent); }
/* Compact quiet action button (Save / Update / Show link) — the gradient
   .dl-btn reads far too loud repeated down a settings page. */
.acct-btn {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 8px 14px; border-radius: var(--radius-sm);
  background: var(--bg-elev-2); border: 1px solid var(--border-strong);
  color: var(--text); font-size: 12.5px; font-weight: 600;
  cursor: pointer; font-family: inherit;
}
.acct-btn:hover { background: var(--bg-hover); }
/* Password inputs stack in a narrow column; Update sits under them. */
.acct-pw-row {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 8px; max-width: 340px; margin-top: 10px;
}
.acct-pw-row .acct-input { width: 100%; flex: none; }
.acct-meta { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 9px; font-size: 12.5px; color: var(--text-dim); }
.acct-verified { color: var(--ok, #5fd07a); font-weight: 600; }
.acct-handle { color: var(--text-faint); }
.acct-error { color: var(--danger); font-size: 12.5px; margin-top: 8px; }
/* Danger zone: permanent account deletion. A tinted rule sets it apart from the
   ordinary settings above; the button reads red on hover to match its weight. */
.acct-danger { border-top: 1px solid color-mix(in srgb, var(--danger) 40%, var(--border)); padding-top: 16px; }
.acct-danger .acct-section-title { color: var(--danger); }
.acct-btn-danger { border-color: color-mix(in srgb, var(--danger) 55%, var(--border-strong)); color: var(--danger); }
.acct-btn-danger:hover:not(:disabled) { background: var(--danger); border-color: var(--danger); color: #fff; }
.acct-btn-danger:disabled { opacity: .5; cursor: not-allowed; }
.acct-linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; text-decoration: underline; }
.acct-legal { text-align: center; color: var(--text-faint); font-size: 12px; margin: 8px 0 0; }
.acct-legal a { color: var(--text-dim); text-decoration: none; }
.acct-legal a:hover { color: var(--accent); text-decoration: underline; }
/* The shared digest/calendar callouts drop their boxed-card chrome inside the
   modal — sections already separate themselves, so a card-in-card looks bulky. */
.acct .shelf-callout {
  background: none; border: none; padding: 0; margin: 0;
  border-radius: 0; overflow: visible;
}
.acct .shelf-callout::before { content: none; }
.acct .shelf-callout-icon { width: 34px; height: 34px; font-size: 17px; }
.acct .shelf-callout-title { font-size: 15px; }
.acct .shelf-callout .hint { margin: 0; } /* "Creating your link…" placeholder */
/* The revealed calendar box sits under the section's own "Calendar feed"
   heading — its callout icon + title would say the same thing twice. */
#acctCalWrap .shelf-callout-icon, #acctCalWrap .shelf-callout-title { display: none; }
.acct .dl-btn { padding: 7px 12px; font-size: 12px; box-shadow: none; }
.acct .invite-link { max-width: 420px; }
.acct .invite-count { font-size: 12.5px; color: var(--text-dim); margin-top: 9px; }

/* ===================== SHELL ===================== */
.shell {
  position: relative;
  z-index: 1;
  /* Single content column: the filters are an overlay drawer now (fixed, out of
     flow), not an in-flow grid column. */
  display: block;
  /* Generous cap so big monitors fit more gallery tiles (the Releases grid is
     auto-fill of 190px columns, so width = more tiles); below this the shell
     just fills the viewport. Keep the footer's max-width in sync. */
  max-width: 2200px;
  margin: 0 auto;
  /* Tight top padding: the shell's first row is a small control strip (the
     active-filter chips, or a week head) that read as floating in a band of
     dead air under the header at 26px. */
  padding: 12px 28px 60px;
}

/* My Shelf + Admin ignore the sidebar filters, so drop the drawer trigger/bar
   entirely there (the drawer itself is force-closed in syncSidebar). */
.shell.no-sidebar .filter-bar { display: none; }

/* Scrim behind the filter drawer — shown on both desktop and mobile whenever the
   drawer is open. Sits just under the drawer (z 121); the book drawer + modals
   (z 200+) still stack above both. */
.sidebar-scrim { display: none; }
/* Anchored below the sticky topbar: the scrim + drawer live inside .shell (z-index
   1), which sits under the topbar (z-index 20) in the page's stacking order, so they
   can't cover it anyway — starting at --header-h keeps the header clean and clickable
   above the dimmed feed, the standard docked-panel look. */
.sidebar-scrim:not([hidden]) {
  display: block; position: fixed; top: var(--header-h); left: 0; right: 0; bottom: 0;
  z-index: 120;
  background: rgba(0, 0, 0, 0.5);
  animation: scrim-in 0.2s ease;
}

/* ===================== SIDEBAR (filter drawer) ===================== */
/* An overlay drawer: slides in from the left on every breakpoint and widens on
   phones (see the <=640px block). Driven by transform + visibility off the inverse
   open-class (.shell.sidebar-collapsed = closed) so it animates in both directions
   and a closed drawer drops out of the tab order (null offsetParent). */
.sidebar {
  position: fixed;
  top: var(--header-h); left: 0; bottom: 0;
  width: min(400px, 92vw);
  z-index: 121;
  /* The sidebar itself doesn't scroll — it's a flex column hosting one visible view
     (master or facet detail), each of which owns its own scrolling body so a header
     + footer can pin. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg-elev);
  border-right: 1px solid var(--border);
  box-shadow: var(--shadow);
  transform: translateX(-100%);
  visibility: hidden;
  /* visibility with a *duration*: becomes visible instantly when opening, and
     stays visible through the slide-out before hiding when closing — so a
     closed drawer is untabbable but the exit still animates. */
  transition: transform 0.28s ease, visibility 0.28s ease;
}
/* Open state (drawer not collapsed, on a filter-driven tab). */
.shell:not(.sidebar-collapsed):not(.no-sidebar) .sidebar {
  transform: translateX(0);
  visibility: visible;
}

/* A view fills the drawer as its own header / scrolling body / footer column. Only
   one shows at a time (master vs facet detail); [hidden] must beat display:flex. */
.sidebar-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.sidebar-view[hidden] { display: none; }
/* The scrolling middle of a view; head/foot stay pinned outside it. */
.sidebar-body {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px;
  padding: 16px 18px 22px;
}
.filter-block { display: flex; flex-direction: column; gap: 8px; }
/* display:flex above defeats the low-specificity UA `[hidden]{display:none}`, so
   a `hidden` .filter-block (e.g. the admin-only #qualityBlock, revealed by
   applyRole for admins only) would otherwise leak to every user — pair it. */
.filter-block[hidden] { display: none; }
/* Facet rows are grouped into discoverability and publishing concepts rather
   than presented as one undifferentiated checklist. */
.refine-block { gap: 7px; }
.filter-section-card {
  padding: 13px; border: 1px solid var(--border); border-radius: 13px;
  background: color-mix(in srgb, var(--bg) 72%, var(--bg-elev));
}

/* ----- saved filter presets: one row — inline label + dropdown ----- */
/* The menu applies, updates or deletes a saved set. Save current lives beside
   the dropdown so creating the first set is always discoverable. */
.preset-block[hidden] { display: none; }
.preset-drop-wrap { position: relative; display: flex; flex: 1; min-width: 0; }
.preset-drop-btn {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  min-height: 42px; padding: 9px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-elev); color: var(--text-faint); cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 600;
  transition: border-color 0.15s, color 0.15s;
}
.preset-drop-btn:hover { border-color: var(--border-strong); color: var(--text); }
/* CSS chevron (v), matching .filter-row-chev's stroke look */
.preset-drop-btn::after {
  content: ""; width: 7px; height: 7px; flex: none; margin-top: -4px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.preset-drop-val {
  flex: 1; min-width: 0; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* An applied preset reads as a set value (same accent as .filter-row.is-set);
   a drifted one ("<name> (edited)") reads dimmer — set-but-not-saved. */
.preset-drop-val.is-set { color: var(--accent); }
.preset-drop-val.is-edited { color: var(--text-dim); font-style: italic; }
/* The menu rides .shelf-menu (bg/border/shadow/item styles); full trigger width. */
.preset-menu { top: calc(100% + 4px); left: 0; right: 0; max-height: 260px; overflow-y: auto; }
.preset-menu[hidden] { display: none; }
.preset-menu-row { display: flex; align-items: center; gap: 2px; }
.preset-menu-row .shelf-menu-item { flex: 1; min-width: 0; }
.preset-menu-row .shelf-menu-item span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.preset-menu-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none; margin-right: 4px;
  border: none; border-radius: 999px; cursor: pointer;
  background: none; color: var(--text-faint);
  transition: background 0.15s, color 0.15s;
}
.preset-menu-x .icon { width: 12px; height: 12px; }
.preset-menu-x:hover { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }

/* drawer header: title + close (master) or back + title (facet detail). */
.sidebar-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  flex: none; padding: 17px 14px 17px 20px;
  background: var(--bg-elev); border-bottom: 1px solid var(--border);
}
.sidebar-heading { display: flex; align-items: center; gap: 10px; min-width: 0; }
.sidebar-title {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: "Newsreader", Georgia, serif;
  font-size: 22px; line-height: 1.1; font-weight: 600; color: var(--text);
}
.sidebar-active-count {
  flex: none; padding: 4px 8px; border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--border));
  border-radius: 999px; background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent); font-size: 11px; font-weight: 700;
}
.sidebar-collapse {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex-shrink: 0;
  border: 0; border-radius: 10px;
  background: transparent; color: var(--text-dim);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.15s;
}
.sidebar-collapse .icon { width: 17px; height: 17px; }
.sidebar-collapse:hover { background: var(--bg-hover); color: var(--text); }

/* Sticky footer: quiet clear action + the live "Show N releases" button. */
.sidebar-foot {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border); background: var(--bg-elev);
  box-shadow: 0 -8px 24px color-mix(in srgb, var(--bg) 72%, transparent);
}
.apply-btn {
  flex: 1; min-height: 44px; padding: 10px 12px; border: none; border-radius: 11px;
  background: var(--accent-grad); color: #fff;
  font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  box-shadow: 0 3px 12px color-mix(in srgb, var(--accent) 30%, transparent);
  transition: transform 0.12s, box-shadow 0.15s;
}
.apply-btn:hover { transform: translateY(-1px); }
[data-theme="light"] .apply-btn { color: #fff; }

/* ----- master: grouped facet rows (drill-in to the detail picker) ----- */
.filter-row-list {
  overflow: hidden; border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev);
}
.filter-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 45px; padding: 10px 11px; border: 0; border-radius: 0;
  background: transparent; color: var(--text); cursor: pointer;
  font-family: inherit; font-size: 13px; text-align: left;
  transition: border-color 0.15s, background 0.15s;
}
.filter-row + .filter-row { border-top: 1px solid var(--border); }
.filter-row:hover { background: var(--bg-hover); }
.filter-row:disabled { opacity: 0.5; cursor: default; }
.filter-row-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 25px; height: 25px; flex: none; color: var(--text-dim);
}
.filter-row-icon .icon { width: 17px; height: 17px; }
.filter-row-label { font-weight: 600; }
.filter-row-val {
  margin-left: auto; color: var(--text-faint); font-size: 12px;
  max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filter-row.is-set { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.filter-row.is-set .filter-row-icon,
.filter-row.is-set .filter-row-val { color: var(--accent); font-weight: 600; }
/* CSS chevron (›) — no icon dependency. */
.filter-row-chev {
  flex: none; width: 8px; height: 8px; margin-left: 2px;
  border-right: 2px solid var(--text-faint); border-top: 2px solid var(--text-faint);
  transform: rotate(45deg);
}

/* ----- personal-scope toggles ----- */
.option-pills { display: flex; flex-wrap: wrap; gap: 9px; }
.option-pill {
  display: inline-flex; flex: 1 1 130px; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; cursor: pointer; user-select: none;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: 11px;
  background: var(--bg-elev); color: var(--text-dim); font-size: 12.5px; font-weight: 600;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.option-pill input { position: absolute; opacity: 0; width: 0; height: 0; }
.option-pill:hover { border-color: var(--border-strong); color: var(--text); }
.option-pill:has(input:checked) {
  border-color: color-mix(in srgb, var(--accent) 56%, var(--border));
  color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev));
}
.option-pill-icon { display: inline-flex; align-items: center; justify-content: center; }
.option-pill-icon .icon { width: 17px; height: 17px; }
.option-pill:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.filter-help {
  margin: 0 2px; color: var(--text-faint); font-size: 10.5px; line-height: 1.45;
}

/* "Updated X ago" freshness line, pinned to the bottom of the master body. */
.filter-fresh { margin: auto 2px 0; font-size: 10.5px; color: var(--text-faint); }
.filter-fresh:has(:empty) { display: none; }

/* ----- facet detail: back header, search, checklist ----- */
.facet-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--text-dim); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.facet-back:hover { background: var(--bg-hover); color: var(--text); }
/* CSS chevron (‹) pointing left */
.facet-back::before {
  content: ""; width: 8px; height: 8px; margin-left: 3px;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.sidebar-head-spacer { width: 30px; flex: none; } /* balances the back button so the title centers */
.facet-view .sidebar-title {
  flex: 1; text-align: center; font-family: "Space Grotesk", "Inter", sans-serif;
  font-size: 15px;
}
.facet-search-wrap { position: relative; flex: none; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.facet-search-wrap .search-icon { left: 28px; }
/* .facet-search-wrap prefix out-specifies the base `input[type="text"]` (0,1,1),
   which would otherwise reset padding-left and slide text under the search icon. */
.facet-search-wrap .facet-search {
  width: 100%; padding: 10px 12px 10px 36px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text); font-size: 13px; font-family: inherit;
}
.facet-search-wrap .facet-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.facet-list { flex: 1; min-height: 0; overflow-y: auto; padding: 8px; }
.facet-check {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border: none; border-radius: 8px; cursor: pointer;
  background: transparent; color: var(--text); font-family: inherit; font-size: 13px; text-align: left;
}
.facet-check:hover { background: var(--bg-hover); }
.facet-check-box {
  flex: none; width: 18px; height: 18px; border-radius: 5px;
  border: 1.5px solid var(--border-strong); position: relative;
  transition: background 0.12s, border-color 0.12s;
}
.facet-check.checked .facet-check-box { background: var(--accent-grad); border-color: transparent; }
.facet-check.checked .facet-check-box::after {
  content: ""; position: absolute; left: 5px; top: 1.5px;
  width: 5px; height: 9px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.facet-check-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.facet-check.checked .facet-check-name { font-weight: 600; }
.facet-check-n { flex: none; color: var(--text-faint); font-size: 11px; font-variant-numeric: tabular-nums; }
.facet-empty { padding: 20px; text-align: center; color: var(--text-faint); font-size: 12.5px; }
/* Inert group headers in a grouped facet checklist (tropes) — browse only;
   a search query flattens the list and hides these. */
.facet-group-h {
  padding: 12px 10px 4px; font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint);
}
.facet-group-h:first-child { padding-top: 4px; }

/* Divider closing a facet's pinned group (the Big Five lead the Publisher list).
   Inert like .facet-group-h — a search query flattens the list and hides it. */
.facet-divider {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 10px 6px; font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint);
}
.facet-divider::before, .facet-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}

/* ===================== FILTERS TRIGGER (Releases header) ===================== */
/* A pill with an icon + "Filters" label + active-count badge; icon-only on phones
   (see the <=640px block). Starts in the topbar markup but is relocated into the
   Releases header tools on first render (tabs/weekly.js) — it only ever shows on
   that filter-driven tab, so a `hidden` state fully collapses it wherever it sits. */
.filter-open-btn {
  display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0;
  height: 40px; padding: 0 15px;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--bg-elev); color: var(--text);
  font-family: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.filter-open-btn:hover { border-color: var(--accent); transform: translateY(-1px); }
.filter-open-btn[hidden] { display: none; }
.filter-open-ico { width: 16px; height: 16px; display: inline-flex; }
.filter-open-ico .icon { width: 16px; height: 16px; }
.filter-open-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 999px;
  background: var(--accent-grad); color: #fff;
  font-size: 11px; font-weight: 700;
}
.filter-open-count[hidden] { display: none; }

/* ===================== FILTER BAR (active-filter chips) ===================== */
/* A thin row of removable chips + "Clear all", rendered only when at least one
   filter is set — so the default no-filter view adds nothing above the feed. */
.filter-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px;
  margin-bottom: 20px;
}
.filter-bar[hidden] { display: none; }

/* Active-filter chips: reuse the tag-chip look but tinted to read as "applied". */
.filter-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-width: 0; }
.filter-chips:empty { display: none; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 6px 5px 11px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-elev); color: var(--text-dim);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.filter-chip .fc-kind { color: var(--text-faint); font-weight: 600; }
.filter-chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex-shrink: 0;
  border: none; border-radius: 999px; cursor: pointer;
  background: var(--bg-hover); color: var(--text-faint);
  transition: background 0.15s, color 0.15s;
}
.filter-chip-x .icon { width: 12px; height: 12px; }
.filter-chip-x:hover { background: var(--accent); color: #fff; }
/* "12 new in <view>": the nightly saved-view count. Tinted with the accent so it
   reads as news rather than as another applied-filter chip. `hidden` alone would
   lose to display:flex — pair the two (see the .login-marketing precedent). */
.new-in-view {
  display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 8px 6px 12px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev));
  font-size: 12px; color: var(--text-dim); white-space: nowrap;
}
.new-in-view[hidden] { display: none; }
.new-in-view b { color: var(--text); font-weight: 700; }
.niv-dot {
  width: 7px; height: 7px; flex-shrink: 0;
  border-radius: 50%; background: var(--accent);
}
.niv-act {
  flex-shrink: 0; padding: 4px 9px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--bg-elev);
  color: var(--text-dim); font-family: inherit; font-size: 11px; font-weight: 600;
  transition: color 0.15s, border-color 0.15s;
}
.niv-act:hover { color: var(--accent); border-color: var(--accent); }

/* Unacknowledged count on a saved view's row in the drawer dropdown. */
.preset-new {
  margin-left: auto; padding: 1px 7px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
}

.filter-clear-all {
  flex-shrink: 0; padding: 6px 10px; border: none; background: none;
  color: var(--text-faint); font-family: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer; transition: color 0.15s;
}
.filter-clear-all:hover { color: var(--accent); }
.filter-clear-all[hidden] { display: none; }

/* segmented Fiction / Nonfiction toggle */
.seg {
  display: flex; gap: 4px; padding: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.seg button {
  flex: 1; padding: 8px 4px; border: none; border-radius: 7px;
  background: transparent; color: var(--text-dim);
  /* Let long labels (e.g. "Non-fiction") wrap; flex centers them and the row's
     default stretch keeps every button the same height. */
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 34px; line-height: 1.15;
  font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.seg button:hover { color: var(--text); }
.seg button.active {
  color: #fff; background: var(--accent-grad);
  box-shadow: 0 3px 10px color-mix(in srgb, var(--accent) 30%, transparent);
}
[data-theme="light"] .seg button.active { color: #fff; }
.field-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.105em;
  color: var(--accent);
  font-weight: 750;
}
/* Small badge on a field label marking an admin-only section (Data quality). */
.field-admin-tag {
  margin-left: 6px; padding: 1px 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  font-size: 9px; letter-spacing: 0.06em; font-weight: 700;
  vertical-align: middle;
}

.search-wrap { position: relative; }
.search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-faint); font-size: 16px;
  z-index: 2; pointer-events: none;
}
/* higher specificity than the base input rule so the left padding isn't reset */
.search-wrap input[type="text"] { padding-left: 36px; padding-right: 34px; }
.search-clear {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 6px; cursor: pointer;
  background: transparent; color: var(--text-faint); font-size: 11px;
  transition: background 0.15s, color 0.15s;
}
.search-clear:hover { background: var(--bg-hover); color: var(--text); }
.search-clear[hidden] { display: none; }
/* Touch: a mouse-sized 22px "x" is an awkward thumb target — grow the tap area
   and the glyph, and push the query text clear of the wider absolute button.
   .wk-search's clear is in-flow (below), so only the .search-wrap variant needs
   the extra right padding. Round it and suppress the default tap highlight so
   the bigger button reads as a circular clear affordance, not a grey square. */
@media (hover: none) and (pointer: coarse) {
  .search-clear {
    width: 34px; height: 34px; font-size: 15px;
    border-radius: 50%; -webkit-tap-highlight-color: transparent;
  }
  .search-clear:active { background: var(--bg-hover); color: var(--text); }
  .search-wrap input[type="text"] { padding-right: 44px; }
}

input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  transition: border 0.15s, box-shadow 0.15s;
}
input[type="text"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Release date: a segmented quick range + collapsible inline mini-calendar. */
.block-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.date-presets, .release-history-seg {
  display: grid; gap: 0;
  overflow: hidden; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-elev);
}
.date-presets { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.release-history-seg { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.date-presets button, .release-history-seg button {
  min-width: 0; min-height: 40px; padding: 7px 4px;
  border: 0; border-right: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--text-dim);
  font-family: inherit; font-size: 11.5px; font-weight: 600; white-space: nowrap;
  cursor: pointer; transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.date-presets button:last-child, .release-history-seg button:last-child { border-right: 0; }
.date-presets button:hover, .release-history-seg button:hover { color: var(--text); background: var(--bg-hover); }
.date-presets button.active, .release-history-seg button[aria-pressed="true"] {
  color: #fff; background: var(--accent-grad);
  box-shadow: 0 3px 10px color-mix(in srgb, var(--accent) 30%, transparent);
}
[data-theme="light"] .date-presets button.active,
[data-theme="light"] .release-history-seg button[aria-pressed="true"] { color: #fff; }
.release-history-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.date-custom-toggle {
  align-self: flex-start; padding: 2px 0 0; border: none; background: none;
  color: var(--accent); font-family: inherit; font-size: 11.5px; font-weight: 650;
  cursor: pointer;
}
.date-custom-toggle::before { content: "+ "; }
.date-custom-toggle.open::before { content: "\2212 "; } /* minus */
.date-custom-toggle:hover { color: var(--accent); }

/* Custom range: an inline mini-calendar. Click a start day, then an end day; the
   sweep between highlights (with a live hover preview). Replaces the old pair of
   native date inputs — one calendar, both endpoints. */
.range-cal { display: flex; flex-direction: column; gap: 8px; padding-top: 6px; }
.range-cal[hidden] { display: none; }
.rc-readout {
  min-height: 15px; text-align: center;
  font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.rc-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.rc-month { font-size: 12.5px; font-weight: 700; color: var(--text); }
.rc-nav {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--text-dim);
  font-size: 15px; line-height: 1; cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.rc-nav:hover:not(:disabled) { color: var(--text); border-color: var(--border-strong); }
.rc-nav:disabled { opacity: 0.35; cursor: default; }
.rc-dows, .rc-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.rc-dows { margin-bottom: 2px; }
.rc-dow {
  text-align: center; font-size: 10px; font-weight: 700; color: var(--text-faint);
}
.rc-day {
  aspect-ratio: 1 / 1; min-width: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent;
  color: var(--text-dim); font-family: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer; transition: background 0.12s, color 0.12s;
}
.rc-day:hover:not(:disabled):not(.rc-blank) { background: var(--bg-hover); color: var(--text); border-radius: 6px; }
.rc-blank { cursor: default; }
.rc-disabled { color: var(--text-faint); opacity: 0.3; cursor: default; }
.rc-today { box-shadow: inset 0 0 0 1px var(--accent); color: var(--text); border-radius: 6px; }
/* Contiguous sweep between the endpoints (grid has no column gap). */
.rc-in { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); }
.rc-start { border-top-left-radius: 6px; border-bottom-left-radius: 6px; }
.rc-end { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }
.rc-start, .rc-end {
  background: var(--accent-grad); color: #fff;
  box-shadow: 0 3px 10px color-mix(in srgb, var(--accent) 30%, transparent);
}
.rc-start.rc-end { border-radius: 6px; }

.reset-btn {
  flex: none; min-height: 44px; padding: 10px 8px; border-radius: 10px;
  background: transparent; border: 0;
  color: var(--accent); cursor: pointer; font-size: 12.5px; font-family: inherit;
  font-weight: 650;
  transition: all 0.15s;
}
.reset-btn:hover { color: var(--text); background: var(--bg-hover); }

/* ===================== TABS ===================== */
/* Nav lives in the sticky topbar so it stays reachable deep in a long list.
   Desktop: one segmented pill centered between the brand and the icon buttons
   (margin-inline:auto owns the free space on both sides). The <=640px drawer
   strips this chrome and restores per-tab borders. */
.tabs {
  display: flex; gap: 2px; flex-wrap: nowrap; margin-inline: auto;
  padding: 3px; background: var(--bg-elev);
  border: 1px solid var(--border); border-radius: 999px;
}
.tab {
  padding: 7px 15px; border-radius: 999px;
  background: transparent; border: none;
  color: var(--text-dim); cursor: pointer; font-size: 13.5px; font-weight: 500;
  font-family: inherit; transition: all 0.15s;
  white-space: nowrap; /* a label never wraps to two lines — it collapses to its data-short form instead */
}
.tab:hover { color: var(--text); background: var(--bg-hover); }
.tab.active {
  color: var(--text);
  background: var(--accent-grad);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 35%, transparent);
}
[data-theme="light"] .tab.active { color: #fff; }

/* The mobile bottom tab bar (#tabsBottom, a duplicate of the drawer nav) only
   exists ≤640px — the media block below flips it to a fixed bottom bar. */
.tabs-bottom { display: none; }
.mobile-more-menu { display: none; }

.panel { display: none; }
.panel.active { display: block; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; }}

/* ===================== PERSONALIZED HOME ===================== */
#panel-home { max-width: 1180px; margin: 0 auto; }
.home-page { display: flex; flex-direction: column; gap: 20px; padding: 8px 0 30px; animation: fade 0.2s ease; }
.home-preview-bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px 18px; padding: 12px 14px; background: color-mix(in srgb, var(--warn) 9%, var(--bg-elev)); border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--border)); border-radius: 13px; }
.home-preview-bar > div:first-child { display: grid; grid-template-columns: auto 1fr; align-items: center; justify-content: start; gap: 2px 9px; }
.home-preview-badge { grid-row: 1 / span 2; padding: 4px 7px; border-radius: 6px; background: var(--warn); color: #1d1720; font-size: 9px; font-weight: 800; letter-spacing: 0.08em; }
.home-preview-bar b { font-size: 12px; }
.home-preview-bar small { color: var(--text-dim); font-size: 10px; }
.home-preview-bar > label { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 10.5px; font-weight: 600; }
.home-preview-bar select { min-width: 180px; padding: 7px 30px 7px 9px; border: 1px solid var(--border-strong); border-radius: 8px; background: var(--bg-elev); color: var(--text); font: inherit; }
.home-diagnostics { grid-column: 1 / -1; border-top: 1px solid color-mix(in srgb, var(--warn) 28%, var(--border)); padding-top: 8px; }
.home-diagnostics summary { width: max-content; color: var(--text); font-size: 10.5px; font-weight: 600; cursor: pointer; }
.home-diagnostic-account { display: flex; flex-wrap: wrap; gap: 6px; margin: 9px 0 7px; }
.home-diagnostic-account span { padding: 3px 7px; border-radius: 999px; background: var(--bg-elev); color: var(--text-dim); font-size: 9.5px; }
.home-diagnostic-scroll { overflow-x: auto; }
.home-diagnostics table { width: 100%; border-collapse: collapse; background: var(--bg-elev); font-size: 9.5px; }
.home-diagnostics th, .home-diagnostics td { padding: 6px 8px; border: 1px solid var(--border); text-align: left; vertical-align: top; }
.home-diagnostics thead th { color: var(--text-faint); font-weight: 600; }
.home-diagnostics tbody th { min-width: 110px; color: var(--text); }
.home-diagnostics td > span { display: block; color: var(--text-dim); line-height: 1.35; }
.home-preview-simulation { min-height: 440px; position: relative; }
.home-preview-simulation > p { margin: 12px 0 0; text-align: center; color: var(--text-faint); font-size: 11px; }
/* Title + sub come from the shared .page-head-* scale (below) so Home reads at
   the same size as Releases / My Shelf / Lists. No eyebrow here — the greeting
   is the top line. Zero side/top padding for the same reason as .releases-header. */
.home-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; padding: 0 0 2px; }
.home-header-side { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.home-fresh { color: var(--text-faint); font-size: 11.5px; white-space: nowrap; }
.home-release-cta { display: inline-flex; align-items: center; gap: 7px; margin-top: 11px; padding: 7px 11px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 9px; background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev)); color: var(--accent); font: inherit; font-size: 11.5px; font-weight: 650; cursor: pointer; }
.home-release-cta:hover { background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev)); }
.home-release-cta .icon { width: 14px; height: 14px; }

/* ---------- Shared page head (Home / Releases / My Shelf / Lists) ----------
   One type scale for every workspace title: accent eyebrow, serif title,
   dim sub-line. A tab uses the parts it has — Home has no eyebrow (the
   greeting is its top line), Lists no sub. Only the typography is shared:
   what sits *beside* the text (view toggles, triage button, New list) stays
   each tab's own layout.
   The 3px is the head's only top breathing room: keep it here, not on the
   per-tab wrapper, or the tabs drift apart again. */
.page-head-text { min-width: 0; padding-top: 3px; }
/* Block, not inline: as an inline the eyebrow inherits the parent's taller
   strut and its half-leading shoves the whole head down a few px — which is
   exactly how the three tabs used to disagree. */
.page-eyebrow {
  display: block; color: var(--accent); font: 700 10.5px/1.2 "Space Grotesk", sans-serif;
  letter-spacing: 0.11em; text-transform: uppercase;
}
.page-title {
  margin: 3px 0 0; font: 600 clamp(24px, 2.4vw, 31px)/1.08 "Newsreader", serif;
  letter-spacing: -0.015em;
}
.page-sub { margin: 5px 0 0; color: var(--text-dim); font-size: 13.5px; }

/* Variant C stacks the title over one responsive control bar, avoiding separate
   half-empty search and sorting rows while the viewport is resized. */
/* No side padding: it inset the whole header 2px from the week heads and cards
   below it (and from the other tabs' heads). Bottom gap only. */
.releases-header { display: flex; flex-direction: column; align-items: stretch; gap: 12px; padding: 0 0 10px; }
.releases-header-top { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; min-width: 0; }
.releases-view-mode-tools { display: flex; align-items: center; flex: none; }
.releases-view-mode-tools .view-toggle { height: 40px; }
.releases-view-mode-tools .vt-btn {
  min-width: 40px; height: 100%; padding-top: 0; padding-bottom: 0;
  justify-content: center;
}
.releases-bulk-tools { display: flex; align-items: center; flex: none; }
.releases-bulk-tools .wk-all-btn { height: 40px; background: var(--bg-elev); }

/* The catalog lens is structural, not a hidden filter: it is always visible in
   the Releases header. Desktop uses a compact popover; phones turn the same DOM
   into a bottom sheet below. */
.catalog-scope-row { display: flex; align-items: center; flex-wrap: wrap; gap: 9px 12px; min-width: 0; }
#catalogSelectorHost { flex: none; }
.catalog-selector-wrap { position: relative; }
.catalog-selector-trigger {
  display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 13px;
  border: 1px solid var(--border-strong); border-radius: 12px;
  background: var(--bg-elev); color: var(--text-dim); font: inherit;
  font-size: 12.5px; cursor: pointer;
  box-shadow: 0 3px 12px rgba(20, 24, 45, 0.10);
  transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.catalog-selector-trigger:hover { border-color: var(--accent); transform: translateY(-1px); }
.catalog-selector-trigger:focus-visible,
.catalog-option:focus-visible,
.catalog-hint-dismiss:focus-visible,
.catalog-picker-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.catalog-selector-trigger > .icon:first-child { width: 17px; height: 17px; color: var(--accent); }
.catalog-selector-trigger > .icon:last-child { width: 14px; height: 14px; margin-left: 2px; }
.catalog-selector-trigger b { color: var(--text); font-weight: 700; }
.catalog-selector-wrap:has(.catalog-picker:not([hidden])) .catalog-selector-trigger {
  border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.catalog-picker[hidden] { display: none; }
.catalog-picker { position: absolute; left: 0; top: calc(100% + 8px); z-index: 90; width: 340px; }
.catalog-picker-backdrop { display: none; }
.catalog-picker-sheet {
  width: 100%; padding: 8px; border: 1px solid var(--border); border-radius: 14px;
  background: var(--bg-elev); color: var(--text);
  box-shadow: 0 18px 46px rgba(12, 14, 30, 0.28);
  animation: catalog-pop 0.14s ease-out;
}
@keyframes catalog-pop { from { opacity: 0; transform: translateY(-4px) scale(0.985); }}
.catalog-picker-handle { display: none; }
.catalog-picker-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 5px 6px 7px 8px; }
.catalog-picker-head h2 { margin: 0; font: 700 13px/1.25 "Space Grotesk", sans-serif; }
.catalog-picker-close {
  display: none; align-items: center; justify-content: center; width: 34px; height: 34px;
  padding: 0; border: 0; border-radius: 9px; background: transparent; color: var(--text-dim); cursor: pointer;
}
.catalog-picker-close:hover { background: var(--bg-hover); color: var(--text); }
.catalog-picker-close .icon { width: 18px; height: 18px; }
.catalog-options { display: flex; flex-direction: column; gap: 2px; }
.catalog-option {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto 20px;
  align-items: center; gap: 8px; width: 100%; min-height: 42px; padding: 7px 8px;
  border: 0; border-radius: 9px; background: transparent; color: var(--text-dim);
  font: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.catalog-option:hover { background: var(--bg-hover); color: var(--text); }
.catalog-option.selected { background: color-mix(in srgb, var(--accent) 11%, var(--bg-elev)); color: var(--accent); }
.catalog-option-icon { display: inline-flex; align-items: center; justify-content: center; }
.catalog-option-icon .icon { width: 17px; height: 17px; }
.catalog-option-label { color: inherit; font-weight: 600; }
.catalog-option-count { color: var(--text-faint); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.catalog-option-check { display: inline-flex; color: var(--accent); }
.catalog-option-check .icon { width: 16px; height: 16px; }
.catalog-picker-foot { margin: 6px 7px 1px; padding-top: 8px; border-top: 1px solid var(--border); color: var(--text-faint); font-size: 10.5px; }
#catalogHintHost[hidden] { display: none; }
.catalog-hint {
  display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 8px 11px;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: 11px;
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev)); color: var(--text-dim);
  font-size: 11.5px;
}
.catalog-hint > .icon { width: 15px; height: 15px; flex: none; color: var(--accent); }
.catalog-hint-dismiss {
  padding: 2px 3px; border: 0; background: none; color: var(--accent);
  font: inherit; font-size: 11.5px; font-weight: 700; text-decoration: underline; cursor: pointer;
}

/* Search/Filters stay left and sort/density/view stay right on desktop. */
.releases-control-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px; min-width: 0; }
.releases-header-tools { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.releases-view-tools { display: flex; align-items: center; justify-content: flex-end; gap: 10px; flex-shrink: 0; }
/* The relocated Filters button is always shown on this (filter-driven) tab, so a
   `hidden` state should fully collapse it, not reserve topbar-centering space.
   `order:0` undoes the ≤640px topbar rule that floated it ahead of siblings. */
.releases-header-tools .filter-open-btn { order: 0; }
.releases-control-bar .wk-search { height: 40px; }
.releases-control-bar .wk-search-toggle { height: 100%; }

/* Rounded custom sort menu used by the main Releases toolbar. */
.wk-sort-select-wrap {
  position: relative; display: inline-flex; min-width: 112px; height: 40px; flex: none;
  font-size: 13px; font-weight: 500;
}
.wk-sort-trigger {
  position: relative; display: inline-flex; align-items: center; gap: 4px;
  width: 100%; height: 100%; padding: 0 31px 0 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text-dim);
  font: inherit; cursor: pointer;
}
.wk-sort-prefix { color: var(--text-dim); }
.wk-sort-value { color: var(--text); font-weight: 600; }
.wk-sort-trigger > .icon {
  position: absolute; right: 10px; width: 14px; height: 14px;
  pointer-events: none; color: var(--text-faint); transition: transform .15s ease;
}
.wk-sort-select-wrap.open .wk-sort-trigger > .icon { transform: rotate(180deg); }
.wk-sort-trigger > span { pointer-events: none; }
.wk-sort-trigger:focus-visible,
.wk-sort-select-wrap.open .wk-sort-trigger {
  outline: 0; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.wk-sort-menu {
  position: absolute; top: calc(100% + 7px); right: 0; z-index: 40;
  min-width: max(100%, 148px); padding: 5px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev); box-shadow: 0 12px 30px rgba(20, 24, 45, .18);
}
.wk-sort-menu[hidden] { display: none; }
.wk-sort-option {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 8px 9px; border: 0; border-radius: 8px;
  background: transparent; color: var(--text); font: inherit; text-align: left;
  cursor: pointer;
}
.wk-sort-option:hover,
.wk-sort-option:focus-visible { outline: 0; background: var(--bg-hover); }
.wk-sort-option.selected {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev));
  color: var(--accent); font-weight: 650;
}
.wk-sort-check { opacity: 0; color: var(--accent); font-weight: 700; }
.wk-sort-option.selected .wk-sort-check { opacity: 1; }
/* The shared toolbar controls should read as one system. The base segmented
   buttons are shorter because they also serve dense admin toolbars; normalize
   only the Releases copies to the same 40px height as Search/Filters/Sort. */
.releases-view-tools .view-toggle { height: 40px; }
/* Admin-only ISBN export (weekly.js exportBtn) — sized to sit level with the
   sort/density controls it shares the bar with. */
.releases-view-tools .vt-btn {
  min-width: 40px; height: 100%; padding-top: 0; padding-bottom: 0;
  justify-content: center;
}
.home-section, .home-card-section { min-width: 0; }
.home-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin: 0 2px 8px; }
.home-section-head h2, .home-card-section h2 { margin: 0; font-family: "Newsreader", serif; font-size: 21px; line-height: 1.15; }
.home-section-head > span { color: var(--text-faint); font-size: 11.5px; }
.home-link { display: inline-flex; align-items: center; gap: 5px; padding: 2px; background: none; border: 0; color: var(--accent); font: inherit; font-size: 12px; cursor: pointer; }
.home-link:hover { text-decoration: underline; }
.home-link .icon { width: 13px; height: 13px; }
.home-eyebrow { color: var(--accent); font: 700 10.5px/1.2 "Space Grotesk", sans-serif; letter-spacing: 0.11em; }

.home-focus-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; align-items: stretch; }
.home-focus-grid:has(.home-focus-card:only-child) { grid-template-columns: minmax(0, 560px); }
.home-focus-grid:has(.home-focus-card:nth-child(2):last-child) { grid-template-columns: repeat(2, minmax(0, 520px)); }
.home-focus-card { min-width: 0; min-height: 205px; display: flex; gap: 14px; align-items: center; padding: 13px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 15px; box-shadow: 0 6px 20px rgba(0,0,0,0.08); cursor: pointer; overflow: hidden; position: relative; }
.home-focus-card:first-child { grid-row: auto; }
.home-focus-card:first-child::after { content: ""; position: absolute; inset: auto 0 0; height: 3px; background: var(--accent-grad); }
.home-focus-card.secondary { min-height: 205px; gap: 14px; padding: 13px; }
.home-focus-card.secondary .home-focus-cover { position: static; width: 92px; height: 138px; opacity: 1; transform: none; }
.home-focus-card.secondary .home-focus-main { position: relative; z-index: 1; }
.home-focus-cover { width: 100px; height: 150px; flex: none; object-fit: cover; border-radius: 9px; background: var(--bg-hover); box-shadow: 0 7px 18px rgba(0,0,0,0.2); }
@media (min-width: 641px) {
  .home-focus-card:first-child .home-focus-cover { width: 112px; height: 168px; }
  .home-focus-card:first-child .home-actions { gap: 8px; }
  .home-focus-card:first-child .home-primary { min-width: 132px; }}
.home-focus-main { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; }
.home-focus-main h3 { margin: 7px 0 3px; font-family: "Newsreader", serif; font-size: 22px; line-height: 1.12; }
.home-focus-card.secondary h3 { font-size: 18px; max-width: none; }
.home-focus-card.secondary .home-actions { gap: 8px; margin-top: 8px; }
.home-focus-card.secondary .home-primary { min-width: 112px; padding: 6px 11px; }
.home-focus-main p { margin: 0; color: var(--text-dim); font-size: 13px; }
.home-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 13px; }
.home-primary, .home-outline, .home-watch { border-radius: 8px; font-family: inherit; font-weight: 600; cursor: pointer; transition: transform 0.15s, border-color 0.15s, opacity 0.15s; }
.home-primary { min-width: 150px; padding: 8px 14px; color: #fff; border: 0; background: var(--accent-grad); box-shadow: 0 4px 13px color-mix(in srgb, var(--accent) 28%, transparent); }
.home-primary:hover, .home-outline:hover, .home-watch:hover { transform: translateY(-1px); }
.home-primary:disabled, .home-watch:disabled { opacity: 0.55; cursor: wait; transform: none; }

.home-runway { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 9px; border-top: 2px solid transparent; border-image: var(--accent-grad) 1; padding-top: 11px; }
.home-release { min-width: 0; display: flex; align-items: center; gap: 11px; padding: 9px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 11px; cursor: pointer; }
.home-release:hover, .home-orbit-card:hover, .home-recs article:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.home-release-cover { width: 42px; height: 63px; object-fit: cover; flex: none; border-radius: 5px; background: var(--bg-hover); }
.home-release h3 { margin: 0; font: 600 14px/1.2 "Newsreader", serif; }
.home-release p { margin: 2px 0 5px; color: var(--text-dim); font-size: 11px; }
.home-release-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 10.5px; }
.home-release-meta time { color: var(--text-dim); }
.home-release-meta span { padding: 2px 6px; border-radius: 999px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }

.home-adaptive-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(300px, 0.75fr); gap: 14px; align-items: start; }
.home-adaptive-grid.single { grid-template-columns: 1fr; }
.home-card-section { padding: 15px 16px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 14px; }
.home-orbit-list { display: flex; flex-direction: column; gap: 7px; margin-top: 9px; }
.home-orbit-card { min-width: 0; position: relative; display: flex; align-items: center; gap: 11px; padding: 7px; border: 1px solid var(--border); border-radius: 10px; cursor: pointer; }
.home-orbit-cover { width: 48px; height: 72px; object-fit: cover; flex: none; border-radius: 5px; background: var(--bg-hover); }
.home-orbit-main { flex: 1; min-width: 0; }
.home-reason { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 7px; border-radius: 999px; color: var(--accent); background: color-mix(in srgb, var(--accent) 11%, transparent); font-size: 9.5px; }
.home-orbit-main h3, .home-rec-main h3 { margin: 4px 0 0; font: 600 15px/1.15 "Newsreader", serif; }
.home-orbit-main p, .home-rec-main p { margin: 2px 0; color: var(--text-dim); font-size: 11px; }
.home-orbit-main time, .home-rec-main time { color: var(--text-faint); font-size: 10.5px; }
.home-watch { flex: none; min-width: 76px; padding: 6px 12px; background: transparent; border: 1px solid var(--accent); color: var(--accent); font-size: 11px; }
/* Orbit cards are a horizontal flex row; stack their actions in a column. */
.home-orbit-actions { flex: none; flex-direction: column; align-items: stretch; gap: 3px; margin-top: 0; }

/* The negative-signal pair (Snooze / Not for me) lives behind a ⋯ on rec and
   orbit cards, so the action row carries only Watch (+ Why this? on recs).
   Placement differs because the cards do: a rec card has a free top-right
   corner, an orbit card is a dense one-line row whose actions already stack,
   so its ⋯ joins that column under Watch. Same control, same menu. */
.home-more {
  width: 26px; height: 26px; flex: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--text-faint); cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.home-more:hover, .home-more[aria-expanded="true"] {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-elev));
}
.home-more:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 58%, transparent); outline-offset: 2px;
}
.home-more .icon { width: 15px; height: 15px; }
.home-recs .home-more { position: absolute; top: 6px; right: 6px; }
.home-orbit-actions .home-more { align-self: flex-end; }

.home-cardmenu {
  position: absolute; z-index: 8; width: 194px; padding: 5px;
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: 11px; box-shadow: var(--shadow);
}
.home-cardmenu[hidden] { display: none; }
/* A wrapper only the phone sheet uses; on desktop the items are the menu. */
.home-cardmenu-panel { display: contents; }
.home-cardmenu .shelf-menu-item { color: var(--text); }
.home-cardmenu .shelf-menu-item:disabled { opacity: 0.55; cursor: wait; }
.home-recs .home-cardmenu { top: 38px; right: 6px; }
.home-orbit-card .home-cardmenu { top: calc(100% + 4px); right: 7px; }
/* An open menu must paint over the next card in the grid/list, not under it. */
.home-recs article:has(.home-cardmenu:not([hidden])),
.home-orbit-card:has(.home-cardmenu:not([hidden])) { z-index: 9; }

.home-arc-layout { min-width: 0; display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.4fr); align-items: center; gap: clamp(12px, 2vw, 26px); }
.home-adaptive-grid:not(.single) .home-arc-layout { grid-template-columns: 1fr; gap: 12px; }
.home-arc-layout.no-capacity { grid-template-columns: minmax(0, 480px); }
.home-arc-summary, .home-cap-panel { min-width: 0; }
.home-card-lede { margin: 7px 0 10px; color: var(--text-dim); font-size: 12px; line-height: 1.45; }
.home-cap-panel > span { display: block; margin-bottom: 2px; color: var(--text-faint); font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; }
.home-capacity { width: 100%; min-width: 0; height: 72px; display: flex; justify-content: space-between; align-items: flex-end; gap: 8px; padding: 3px 4px 0; }
.home-cap-week { min-width: 0; flex: 1; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 5px; }
.home-cap-bar { width: min(22px, 70%); height: calc(10px + min(var(--n), 4) * 10px); min-height: 10px; border-radius: 6px 6px 3px 3px; background: var(--accent-grad); opacity: 0.78; }
.home-cap-bar.home-cap-empty { height: 3px; min-height: 3px; border-radius: 2px; background: var(--border-strong); opacity: 1; }
.home-cap-bar.over { background: var(--danger); opacity: 0.9; }
.home-cap-week small { color: var(--text-faint); font-size: 9px; white-space: nowrap; }
.home-arc-tasks { display: flex; flex-direction: column; gap: 3px; margin: 7px 0; }
.home-arc-tasks button { display: flex; justify-content: space-between; gap: 8px; padding: 5px 0; background: none; border: 0; color: var(--text); font: inherit; font-size: 11px; cursor: pointer; text-align: left; }
.home-arc-tasks button span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-arc-tasks button b { color: var(--accent); font-weight: 600; white-space: nowrap; }
.home-outline { width: 100%; margin-top: 8px; padding: 7px 12px; background: transparent; border: 1px solid var(--border-strong); color: var(--text); font-size: 11.5px; }

.home-signals { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); background: var(--bg-elev); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.home-signals article { min-width: 0; display: flex; align-items: center; gap: 10px; padding: 9px 12px; cursor: pointer; border-right: 1px solid var(--border); }
.home-signals article:last-child { border-right: 0; }
.home-signal-cover { width: 34px; height: 51px; flex: none; object-fit: cover; border-radius: 4px; background: var(--bg-hover); }
.home-signals article div { min-width: 0; flex: 1; }
.home-signals b, .home-signals span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-signals b { font-size: 11.5px; }
.home-signals span { margin-top: 2px; color: var(--text-dim); font-size: 10.5px; }
.home-signals .icon { color: var(--text-faint); width: 13px; }

.home-recs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.home-recs article { min-width: 0; position: relative; display: flex; gap: 12px; padding: 9px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 12px; cursor: pointer; }
.home-rec-cover { width: 78px; height: 117px; object-fit: cover; flex: none; border-radius: 6px; background: var(--bg-hover); }
.home-rec-main { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; }
.home-rec-main .home-reason { max-width: calc(100% - 28px); }
.home-rec-main .home-actions { margin-top: auto; gap: 9px; }
.home-why { width: 100%; margin-top: 9px; padding: 8px 9px; border-left: 2px solid var(--accent); border-radius: 0 8px 8px 0; background: color-mix(in srgb, var(--accent) 7%, var(--bg)); color: var(--text-dim); cursor: default; }
.home-why b { display: block; color: var(--text); font-size: 10.5px; }
.home-why p { margin: 3px 0 5px; font-size: 10px; line-height: 1.35; }
.home-why small { color: var(--text-faint); font-size: 8.5px; line-height: 1.3; }

.home-starter { display: grid; grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.6fr); gap: 24px; padding: 20px; background: linear-gradient(125deg, color-mix(in srgb, var(--accent) 11%, var(--bg-elev)), var(--bg-elev) 60%); border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: 16px; }
.home-starter h2 { margin: 6px 0 4px; font: 600 23px/1.1 "Newsreader", serif; }
.home-starter p { margin: 0; max-width: 40ch; color: var(--text-dim); font-size: 12.5px; line-height: 1.5; }
.home-starter-label { display: flex; align-items: center; gap: 9px; }
.home-starter-progress { padding: 3px 7px; border-radius: 999px; background: color-mix(in srgb, var(--accent) 10%, var(--bg-elev)); color: var(--accent); font-size: 9px; font-weight: 650; }
.home-starter-steps { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; align-content: start; }
.home-starter-steps button { min-width: 0; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 8px; padding: 11px; background: var(--bg-elev); color: var(--text); border: 1px solid var(--border); border-radius: 11px; font: inherit; cursor: pointer; text-align: left; }
.home-starter-steps button:hover { border-color: var(--accent); }
.home-starter-steps button.done { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); background: color-mix(in srgb, var(--accent) 5%, var(--bg-elev)); }
.home-starter-steps button > .icon:first-child { color: var(--accent); width: 18px; height: 18px; }
.home-starter-steps button > .icon:last-child { color: var(--text-faint); width: 12px; }
.home-starter-steps b, .home-starter-steps small { display: block; }
.home-starter-steps b { font-size: 11px; }
/* --text-dim, not --text-faint: at this size the faint tone lands near 3.4:1 on
   the card and axe flags it. Dim clears 4.5:1 in both themes. */
.home-starter-steps small { margin-top: 2px; color: var(--text-dim); font-size: 9.5px; line-height: 1.25; }

.home-utilities { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding: 10px 12px; border: 1px dashed var(--border-strong); border-radius: 11px; }
.home-utilities span { margin-right: auto; color: var(--text-dim); font-size: 11.5px; }
.home-utilities button { display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-elev); color: var(--text); font: inherit; font-size: 10.5px; cursor: pointer; }
.home-caught { margin: -4px 0 0; text-align: center; color: var(--text-faint); font-size: 11.5px; }
.home-caught .icon { margin-right: 5px; color: var(--accent); }
/* Trope DNA — top-trope bars + the blind-spot picks. Bars share the lists-tab
   genre-mix idiom (track + accent fill via --w). */
.home-dna-layout { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 18px; margin-top: 9px; }
.home-dna-sub { margin: 0 0 10px; color: var(--text-dim); font-size: 11.5px; }
.home-dna-row { display: grid; grid-template-columns: minmax(0, 150px) minmax(0, 1fr) 40px; align-items: center; gap: 10px; padding: 5px 0; }
.home-dna-label { min-width: 0; display: flex; align-items: center; gap: 6px; font-size: 12px; }
.home-dna-label em { font-style: normal; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-dna-track { height: 7px; border-radius: 999px; background: var(--bg-hover); box-shadow: inset 0 0 0 1px rgba(31, 35, 74, 0.05); }
.home-dna-track > i { display: block; height: 100%; width: calc(12% + 88% * var(--w)); border-radius: 999px; background: color-mix(in srgb, var(--accent) calc(34% + 56% * var(--w)), transparent); }
.home-dna-pct { color: var(--text); font-size: 12px; font-weight: 650; text-align: right; }
.home-dna-rarity { margin: 12px 0 0; padding: 8px 10px; border-left: 2px solid var(--accent); border-radius: 0 8px 8px 0; background: color-mix(in srgb, var(--accent) 7%, var(--bg)); color: var(--text-dim); font-size: 11px; line-height: 1.4; }
.home-dna-rarity .icon { margin-right: 4px; color: var(--accent); }
.home-dna-spots { display: flex; flex-direction: column; gap: 7px; }
.home-dna-spots-label { color: var(--text-faint); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; }
@media (max-width: 720px) { .home-dna-layout { grid-template-columns: minmax(0, 1fr); }}
/* Combo Explorer launcher — the Trends-hero look, at home on Home. */
.home-combo {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  flex-wrap: wrap; padding: 14px 16px; border-radius: 13px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.home-combo-text b { font-family: "Newsreader", serif; font-size: 15px; }
.home-combo-text span { display: block; margin-top: 2px; color: var(--text-dim); font-size: 11.5px; max-width: 52ch; }
.home-combo .home-primary { display: inline-flex; align-items: center; gap: 7px; }
.home-loading { display: grid; grid-template-columns: 1.4fr 0.8fr; gap: 14px; padding-top: 66px; }
.home-loading span { display: block; height: 190px; border-radius: 15px; background: linear-gradient(100deg, var(--bg-elev) 30%, var(--bg-hover) 50%, var(--bg-elev) 70%); background-size: 220% 100%; animation: home-shimmer 1.4s infinite; }
.home-loading span:last-child { grid-column: 1 / -1; height: 120px; }
@keyframes home-shimmer { to { background-position-x: -220%; }}
.home-error { max-width: 430px; margin: 90px auto 0; padding: 28px; text-align: center; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 15px; }
.home-error > .icon { width: 27px; height: 27px; color: var(--danger); }
.home-error h2 { margin: 9px 0 4px; font-family: "Newsreader", serif; }
.home-error p { margin: 0 0 15px; color: var(--text-dim); font-size: 12px; }

@media (max-width: 900px) {
  .home-focus-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .home-focus-card:first-child { grid-column: 1 / -1; grid-row: auto; }
  .home-focus-card.secondary { min-height: 140px; }
  .home-runway { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .home-adaptive-grid { grid-template-columns: 1fr; }
  .home-arc-layout { grid-template-columns: 1fr; gap: 12px; }
  .home-recs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .home-recs article:last-child { grid-column: 1 / -1; }
  .home-starter { grid-template-columns: 1fr; gap: 14px; }}

@media (max-width: 640px) {
  .home-page { gap: 17px; padding-top: 2px; }
  .home-preview-bar { grid-template-columns: 1fr; padding: 11px; }
  .home-preview-bar > label { justify-content: space-between; }
  .home-preview-bar select { min-width: 0; width: 190px; }
  .home-header { align-items: flex-start; }
  .home-fresh { display: none; }
  .home-release-cta { margin-top: 9px; }
  .home-section-head h2, .home-card-section h2 { font-size: 19px; }
  .home-focus-grid { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: 9px; padding-bottom: 3px; }
  .home-focus-card, .home-focus-card:first-child { min-width: 88%; min-height: 174px; grid-column: auto; scroll-snap-align: start; }
  .home-focus-card.secondary { min-height: 174px; flex-direction: row; }
  .home-focus-card.secondary .home-focus-cover { position: static; width: 94px; height: 141px; opacity: 1; transform: none; }
  .home-focus-card.secondary h3 { max-width: none; }
  .home-runway { display: flex; overflow-x: auto; scroll-snap-type: x proximity; }
  .home-release { min-width: 235px; scroll-snap-align: start; }
  .home-recs { display: flex; overflow-x: auto; scroll-snap-type: x proximity; }
  /* The card ⋯ menu becomes a bottom sheet, the .user-menu recipe: the
     .home-cardmenu element stretches over the viewport as the scrim (a tap on
     it falls through to the document dismiss listener) and .home-cardmenu-panel
     is the sheet pinned to its bottom edge. This is required, not cosmetic —
     .home-recs above is an overflow-x carousel, so it clips a dropdown.
     The sheet is fixed inside .shell (a stacking context at z 1), so the
     body-level bars have to drop beneath it while it's open. */
  body:has(.home-cardmenu:not([hidden])) .topbar,
  body:has(.home-cardmenu:not([hidden])) .tabs-bottom { z-index: 0; }
  .home-cardmenu {
    position: fixed; top: 0; left: 0; right: auto;
    width: 100vw; height: 100vh; height: 100dvh;
    z-index: 150; padding: 0; border: 0; border-radius: 0; box-shadow: none;
    display: flex; flex-direction: column; justify-content: flex-end;
    background: rgba(0, 0, 0, 0.5);
    animation: scrim-in 0.2s ease;
    cursor: pointer; /* marks the scrim tappable for legacy Safari */
  }
  .home-recs .home-cardmenu, .home-orbit-card .home-cardmenu { top: 0; right: auto; }
  .home-cardmenu-panel {
    display: flex; flex-direction: column; gap: 2px; cursor: auto;
    padding: 10px 10px calc(12px + env(safe-area-inset-bottom));
    background: var(--bg-elev);
    border-top: 1px solid var(--border);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.35);
    animation: sheet-up 0.22s ease;
  }
  @media (prefers-reduced-motion: reduce) {
    .home-cardmenu, .home-cardmenu-panel { animation: none; }}
  .home-cardmenu .shelf-menu-item { font-size: 14px; padding: 12px; border-radius: 10px; }
  .home-cardmenu .shelf-menu-item .icon { width: 17px; height: 17px; }
  .home-recs article, .home-recs article:last-child { min-width: 82%; grid-column: auto; scroll-snap-align: start; }
  .home-signals { grid-template-columns: 1fr; }
  .home-signals article { border-right: 0; border-bottom: 1px solid var(--border); }
  .home-signals article:last-child { border-bottom: 0; }
  .home-starter { padding: 16px; }
  .home-starter-steps { grid-template-columns: 1fr; }
  .home-orbit-card { align-items: flex-start; }
  .home-orbit-card .home-watch { align-self: center; }
  .home-utilities { justify-content: stretch; }
  .home-utilities span { flex-basis: 100%; }
  .home-utilities button { flex: 1; justify-content: center; }}

@media (prefers-reduced-motion: reduce) {
  .home-page { animation: none; }
  .home-loading span { animation: none; }}

/* ===================== WEEKLY DROPS ===================== */
.week-section { margin-bottom: 12px; }
/* Reclaim some of the shell's top padding so the first week doesn't float far
   below the toolbar. */
/* first-of-type, not first-child: the (desktop-hidden) .wk-list-toolbar div
   precedes the sections, and :first-child ignores display:none */
#weeklyContent > .week-section:first-of-type { margin-top: 0; }
/* divider + breathing room between consecutive weeks while scrolling */
.week-section + .week-section {
  border-top: 1px solid var(--border);
  padding-top: 8px;
}
.week-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 6px;
  /* Pin the div's own top edge just under the topbar (not 15px up, which tucked
     the header's top padding + text baseline behind the chrome). */
  position: sticky; top: var(--header-h); z-index: 5;
  padding: 8px 0;
  /* No background at rest — any tint reads as a rectangle over the body's
     radial glow. weekly.js adds .stuck while a header is actually pinned
     (the only time covers scroll behind it); only then paint the same
     frosted backdrop as the topbar so the pinned header joins the chrome. */
  cursor: pointer; user-select: none;
  transition: background 0.2s;
}
.week-head.stuck {
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
}
.week-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* chevron rotates to point right when its week is collapsed */
.week-head .wk-toggle {
  flex: none; font-size: 13px; color: var(--text-faint);
  transition: transform .15s ease;
}
.week-section.collapsed > .week-head .wk-toggle { transform: rotate(-90deg); }
.week-section.collapsed .week-body { display: none; }
/* A collapsed week is nothing but its header, so the spacing meant to separate
   the head from its covers (and the section from the next one) is pure air
   between two adjacent titles. Tighten it to just the section divider's gap. */
.week-section.collapsed { margin-bottom: 0; }
.week-section.collapsed > .week-head { margin-bottom: 0; padding: 5px 0; }
/* flex:none + nowrap: the date is the one thing in this row that must stay
   readable at a glance, and as a shrinkable flex item it wrapped to two lines
   as soon as the rule + toolbar squeezed the row. The .wk-rule (flex:1, no
   content) absorbs the pressure instead. */
.week-head h2 {
  font-family: "Newsreader", serif; font-size: 19px; font-weight: 600;
  display: flex; align-items: center; gap: 10px;
  flex: none; white-space: nowrap;
}
/* The whole title ("Week of March 15, 2026") is one flex child so the gap above
   only spaces the accent bar from the text — the preface-to-date spacing is a
   plain space inside .wk-of. */
.week-head h2 .wk-title { min-width: 0; }
/* A coarse section states why it is not a month, beside its name — "Rest of
   2026" sitting under four months of 2026 otherwise reads as a contradiction.
   Sans-serif and dim so it is an aside, not part of the heading; --text-dim
   rather than --text-faint because at 12px the faint tone misses 4.5:1. */
.week-head h2 .wk-of-note {
  margin-left: 9px;
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 12px; font-weight: 400; color: var(--text-dim);
}
/* The heading over the half of a section whose games carry no day. It was a
   centred full-width rule, which reads as a boundary between two equal things —
   in a section holding one dated game and nineteen undated, that one card above
   the line looked stranded. Left-aligned with the rule trailing off is the
   shape .week-head already uses, so it reads as the start of a group rather
   than the end of one. Deliberately much quieter than .week-head: sans-serif,
   13px, dim, no accent bar — a subordinate heading, not a second period. */
.wk-soft-head {
  display: flex; align-items: center; gap: 12px;
  margin: 30px 0 16px;
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 13px; font-weight: 500; color: var(--text-dim);
}
.wk-soft-head span { flex: none; white-space: nowrap; }
.wk-soft-head::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
/* gradient accent bar gives each week a clear, modern identity marker */
.week-head h2::before {
  content: ""; flex: none;
  width: 4px; height: 1.1em; border-radius: 4px;
  background: var(--accent-grad);
}
.week-head .wk-count {
  font-size: 12px; color: var(--text-faint);
  background: var(--bg-elev); border: 1px solid var(--border);
  padding: 2px 10px; border-radius: 20px;
  flex: none; white-space: nowrap;   /* "102 games" must never break in the pill */
}
.week-head .wk-rule {
  flex: 1; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 45%, transparent),
    transparent 75%);
}
/* The full toolbar (bulk-collapse + sort + density + view switches + back-to-
   top) rides EVERY week header so the controls travel with whichever head is
   pinned. Resting heads below the first hide their copy — only the first
   header (the tab's toolbar at rest) and a pinned (.stuck) header show the
   controls; a resting mid-list head is just title + count. The rule only ever
   HIDES, so the mobile breakpoint's blanket hide of the bulkier controls
   (they live in .wk-list-toolbar there) still wins everywhere — .wk-top-btn
   is the one control mobile keeps, on the pinned head only. */
#weeklyContent .week-head:not(.stuck) .wk-all-btn,
#weeklyContent .week-head:not(.stuck) .wk-sort-toggle,
#weeklyContent .week-head:not(.stuck) .wk-density-toggle,
#weeklyContent .week-head:not(.stuck) .wk-view-toggle,
#weeklyContent .week-head:not(.stuck) .wk-refine-tools,
#weeklyContent .week-head:not(.stuck) .wk-top-btn { display: none; }

/* Search and Filters travel with the pinned week without duplicating filter
   state. Their buttons are lightweight proxies; Search moves the already-wired
   live input into the fixed dock below, and Filters opens the existing drawer. */
.wk-refine-tools { display: flex; align-items: center; gap: 6px; flex: none; }
.wk-sticky-search-btn, .wk-sticky-filter-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 30px; padding: 0 9px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text-dim); font: inherit; font-size: 12px; font-weight: 600;
  white-space: nowrap; cursor: pointer;
}
.wk-sticky-search-btn:hover, .wk-sticky-filter-btn:hover { border-color: var(--accent); color: var(--text); }
.wk-sticky-search-btn.active, .wk-sticky-filter-btn.active { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.wk-sticky-search-btn .icon, .wk-sticky-filter-btn .icon { width: 14px; height: 14px; }
.wk-sticky-filter-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 4px; border-radius: 999px;
  background: var(--accent-grad); color: #fff; font-size: 9px; line-height: 1;
}
.wk-sticky-search-dock {
  position: fixed; top: calc(var(--header-h) + 8px); right: 30px; z-index: 45;
  display: flex; align-items: center; gap: 6px; width: min(390px, calc(100vw - 60px));
  padding: 6px; border: 1px solid var(--border); border-radius: 15px;
  background: color-mix(in srgb, var(--bg-elev) 94%, transparent);
  box-shadow: 0 12px 32px rgba(20, 24, 45, .2); backdrop-filter: blur(14px);
}
.wk-sticky-search-dock[hidden] { display: none; }
.wk-sticky-search-host { min-width: 0; flex: 1; }
.wk-sticky-search-dock .wk-search { width: 100%; height: 40px; }
.wk-sticky-search-dock .wk-search-toggle { width: 40px; height: 40px; }
.wk-sticky-search-dock .wk-search input[type="text"],
.wk-sticky-search-dock .wk-search.open input[type="text"] {
  flex: 1 1 auto; width: auto; min-width: 0; opacity: 1;
}
.wk-sticky-search-close {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 34px; height: 34px; padding: 0; border: 0; border-radius: 9px;
  background: transparent; color: var(--text-dim); cursor: pointer;
}
.wk-sticky-search-close:hover { background: var(--bg-hover); color: var(--text); }
.wk-sticky-search-close .icon { width: 15px; height: 15px; }

/* Bulk expand/collapse button riding every week header. */
.wk-all-btn {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-dim);
  font-size: 12.5px; font-weight: 500; font-family: inherit; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.wk-all-btn:hover { background: var(--bg-hover); color: var(--text); }
.wk-all-btn .icon { width: 14px; height: 14px; }
/* chevrons point up while every week is open (the action is "collapse") */
.wk-all-btn.open .icon { transform: rotate(180deg); }

/* Back-to-top: a compact icon button on the week headers (shown on the first
   and the pinned one, per the toolbar rule above). Because the heads pin while
   scrolling, the jump stays one tap away at any depth — including mobile,
   where the bulkier toolbar controls above are hidden. */
.wk-top-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-dim); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.wk-top-btn:hover { background: var(--bg-hover); color: var(--text); }
.wk-top-btn .icon { width: 14px; height: 14px; }

/* --- Header search (Releases tab) --- */
/* The catalog's single Search surface, living inline in the Releases header
   tools. Collapsed it's one round magnifier button; .open grows the inline
   input + clear. */
.wk-search {
  display: flex; align-items: center;
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: 999px;
}
.wk-search[hidden] { display: none; } /* display:flex would defeat `hidden` */
.wk-search-toggle {
  flex: none; width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--text);
  transition: color 0.15s;
}
.wk-search-toggle .icon { width: 17px; height: 17px; }
.wk-search-toggle:hover { color: var(--accent); }
/* Out-specify the base input rule (0,1,1) so its border/padding don't apply.
   16px font minimum — anything smaller makes iOS Safari zoom the page on focus. */
.wk-search input[type="text"] {
  width: 0; padding: 9px 0; border: 0; background: transparent;
  font-size: 16px; opacity: 0;
  transition: width 0.18s ease, opacity 0.18s ease;
}
.wk-search input[type="text"]:focus { box-shadow: none; }
.wk-search.open input[type="text"] { width: min(240px, 42vw); opacity: 1; }
/* Reuse the shared clear-button look, but keep this instance in flow. */
.wk-search .search-clear { position: static; transform: none; flex: none; margin: 0 8px 0 2px; }

/* Description search: an offer row hanging under the pill while the query
   reads like prose, an accent "matching" chip while a result is applied.
   Absolutely positioned so it never reflows the header toolbar. */
.wk-search { position: relative; }
.wk-search-semantic {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30;
  padding: 7px 13px; border: 1px solid var(--border-strong);
  border-radius: 999px; background: var(--bg-elev);
  color: var(--accent); font-size: 12.5px; font-weight: 650;
  cursor: pointer; white-space: nowrap;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.wk-search-semantic[hidden] { display: none; }
.wk-search-semantic:hover { border-color: var(--accent); }
/* Active chip rides IN the pill (in flow, before the clear button) — the
   hanging offer position would occlude the week heading below the toolbar. */
.wk-search-semantic.is-active {
  position: static; flex: none; margin: 0 6px 0 2px; box-shadow: none;
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.wk-search-semantic:disabled { opacity: 0.7; cursor: default; }
/* Narrow screens: the floating offer row would collide with the toolbar row
   below the pill, so it stays hidden there — the zero-result empty state's
   full-size offer button (and the keyboard's Enter) carry the affordance. The
   ACTIVE chip is in-flow inside the pill and stays on every width. */
@media (max-width: 940px) {
  .wk-search-semantic:not(.is-active) { display: none; }
}
.wk-semantic-offer-wrap { margin: 14px 0 4px; }

/* Desktop keeps search expanded as a conventional input. Its `.open` class is
   still meaningful on mobile, where the control starts as one icon. */
@media (min-width: 941px) {
  .releases-control-bar .wk-search input[type="text"] { width: min(300px, 28vw); opacity: 1; }
}

/* --- Genre sections (the Releases list's A–Z/Genre switch, genre mode) --- */
/* Approximate rendered .week-head height (padding + line box), used to pin the
   genre pills one tier below the week header. */
.week-section { --wk-head-h: 44px; }
/* Sticky pill, second tier under the week head. Unlike the full-width week
   head (which only paints its frosted backdrop while pinned, via .stuck), a
   fit-content pill has an intrinsic background that reads fine at rest AND
   while stuck — so no observer is needed here. */
.genre-head {
  display: inline-flex; align-items: center; gap: 8px; width: fit-content;
  position: sticky; top: calc(var(--header-h) + var(--wk-head-h)); z-index: 4; /* under .week-head's 5 */
  margin: 2px 0 10px;
  padding: 3px 12px 3px 9px;
  background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border); border-radius: 20px;
  cursor: pointer; user-select: none;
}
.genre-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.genre-head .wk-toggle {
  flex: none; font-size: 12px; color: var(--text-faint);
  transition: transform .15s ease;
}
.genre-head h3 { font-family: "Newsreader", serif; font-size: 14.5px; font-weight: 600; }
.genre-head .gn-count { font-size: 11.5px; color: var(--text-faint); }
.genre-section + .genre-section { margin-top: 16px; }
.genre-section.collapsed .wk-toggle { transform: rotate(-90deg); }
.genre-section.collapsed .gallery { display: none; }
/* A collapsed genre is nothing but its pill (same story as .week-section.collapsed
   above): the pill's gallery-gap margin + the full inter-section gap is pure air
   between two adjacent pills, and the inline-flex pill's line box adds descent
   space under it besides — block it and tighten both margins. */
.genre-section.collapsed > .genre-head { display: flex; margin-bottom: 0; }
.genre-section.collapsed + .genre-section { margin-top: 8px; }

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 20px;
}
.card {
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column;
  transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
  cursor: pointer;
  content-visibility: auto;
  contain-intrinsic-size: 360px 420px;
}
.card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow);
  border-color: var(--border-strong);
}
.cover-wrap {
  position: relative; aspect-ratio: 460 / 215; overflow: hidden;
  background: var(--bg-elev-2);
}
.cover-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A few seconds of the game's trailer, over its still art (web/card-clips.js).
   The still stays underneath rather than being replaced: the clip fades in when
   it is actually playing, and anything that goes wrong — a stale manifest, a
   dropped connection, a machine that cannot keep up — removes the video and
   leaves the art exactly as it was, with nothing to repaint. */
.card-clip {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; pointer-events: none;
  opacity: 0; transition: opacity .3s ease;
}
.cover-wrap.has-clip .card-clip { opacity: 1; }
.cover-wrap > button.clip-preview, .release-card > button.clip-preview {
  position:absolute; z-index:3; right:6px; bottom:6px; display:flex;
  align-items:center; justify-content:center; width:44px; height:44px;
  padding:10px; border:1px solid #ffffff70; border-radius:50%;
  background:#121520db; color:#fff; cursor:pointer;
}
.release-card > button.clip-preview { top:6px; bottom:auto; }
/* Keep the separate watchlist button's hit area clear on compact covers. */
.card .cover-wrap > button.clip-preview { right:44px; }
.clip-preview svg { width:20px; height:20px; fill:currentColor; pointer-events:none; }
.clip-preview .clip-pause, .clip-preview[aria-pressed="true"] .clip-play { display:none; }
.clip-preview[aria-pressed="true"] .clip-pause { display:block; }
.clip-preview:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.cover-wrap > button.clip-preview[hidden], .release-card > button.clip-preview[hidden] { display:none; }
@media (hover:hover) and (pointer:fine) {
  .clip-preview { opacity:0; }
  .card:hover .clip-preview, .release-card:hover .clip-preview,
  .card:focus-within .clip-preview, .release-card:focus-within .clip-preview,
  .clip-preview[aria-pressed="true"] { opacity:1; }
}
/* The player defaults off for reduced motion; a deliberate Autoplay choice
   may enable it. Keep that choice visible without adding a fade animation. */
@media (prefers-reduced-motion: reduce) { .card-clip { transition: none; } }
.cover-fallback {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 5px;
  text-align: center; padding: 14px; background: var(--bg-elev-2);
}
.cover-fallback-title {
  font-family: "Newsreader", serif; font-size: 14px; font-weight: 600; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 4; line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.cover-fallback-developer {
  font-size: 12px; color: var(--text-faint);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.genre-tag {
  position: absolute; top: 10px; left: 10px;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border-strong);
  color: var(--text); font-size: 10.5px; font-weight: 600;
  padding: 3px 9px; border-radius: 20px;
}
/* One-click "watch" paw, top-right of each cover. Filled pink once shelved. */
.cover-paw {
  position: absolute; top: 8px; right: 8px;
  width: 30px; height: 30px; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border-strong);
  color: var(--text); cursor: pointer;
  transition: transform .12s ease, color .12s ease, border-color .12s ease;
}
.cover-paw .icon { width: 16px; height: 16px; }
.cover-paw:hover { transform: scale(1.12); }
/* First run only: until the reader has watched anything, the paw wears a visible
   "Watch" caption. On touch there is no hover, so the glyph's title never shows
   and the control is unlabelled exactly when it matters most. Gated by a root
   attribute (watchlist.js syncs it off the server-backed shelf, so it is one-time
   per account rather than per device) — flipping one attribute collapses every
   caption on the page the moment the first book is watched, with no re-render. */
.cover-paw-label { display: none; font-size: 11px; font-weight: 600; line-height: 1; }
:root[data-shelf-empty="1"] .card .cover-paw {
  width: auto; border-radius: 999px; gap: 5px; padding: 0 10px 0 8px;
}
:root[data-shelf-empty="1"] .card .cover-paw-label { display: inline; }
.cover-paw.active { color: #ff7a93; border-color: color-mix(in srgb, #ff7a93 55%, transparent); }
.cover-paw.active .icon { fill: #ff7a93; }
.card-body { padding: 13px 14px 16px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.release-badge {
  align-self: flex-start; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; padding: 3px 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent-2) 35%, var(--border));
  background: color-mix(in srgb, var(--accent-2) 10%, var(--bg-elev));
  color: var(--accent-2-text); font-size: 9.5px; font-weight: 700; line-height: 1.2;
}
.release-badge-short { display: none; }
/* Clamped, because ONE title sets the height of every card in its grid row.
   Measured over the live catalogue: 97.8% of titles are 40 characters or less
   and p99.9 is 72, but the longest is 252 — a Steam joke title that runs to
   eleven lines and drags a whole row of cards down with it. Three lines fits
   everything up to about p99.9 at both densities; past that the card shows a
   readable opening and the drawer has the rest. The full string stays in the
   DOM, so search, sorting and screen readers are untouched. */
.card-title {
  font-family: "Newsreader", serif; font-size: 15.5px; font-weight: 600; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-developer { font-size: 12.5px; color: var(--text-dim); }
.card-meta { margin-top: auto; display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--text-faint); }
.card-meta .row { display: flex; gap: 6px; }
.card-meta .row b { color: var(--text-dim); font-weight: 500; min-width: 42px; flex-shrink: 0; white-space: nowrap; }
.subgenre-line {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--accent); align-self: flex-start;
}
.series-line { font-size: 11px; color: var(--accent-2); font-style: italic; }
.format-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 10px; border-radius: 6px; overflow: hidden;
  background: var(--bg-hover); color: var(--text-dim); border: 1px solid var(--border);
}
.chip-fmt { padding: 3px 7px; }
.chip-price {
  padding: 3px 7px; font-weight: 700; color: var(--text);
  background: color-mix(in srgb, var(--good) 18%, transparent);
  border-left: 1px solid var(--border);
  text-decoration: none; cursor: pointer; transition: background 0.15s;
}
/* No format label (e.g. NetGalley's suppressed "Other Format"): the price is the
   only child, so drop the divider border that would otherwise hang on its left. */
.chip-price:first-child { border-left: none; }
a.chip-price:hover {
  background: color-mix(in srgb, var(--good) 38%, transparent);
  text-decoration: underline;
}

/* List tiles always use the compact layout: artwork, title, and developer.
   Full metadata stays available in the shared game-details drawer. */
@media (min-width: 641px) {
  .wk-content.compact .gallery {
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 14px;
  }
  .wk-content.compact .card { contain-intrinsic-size: 190px 200px; }
  .wk-content.compact .card-meta,
  .wk-content.compact .series-line,
  .wk-content.compact .subgenre-line { display: none; }
  .wk-content.compact .card-body { padding: 9px 11px 13px; gap: 4px; }
  .wk-content.compact .card-title { font-size: 13.5px; line-height: 1.22; }
  .wk-content.compact .card-developer { font-size: 11.5px; }
}

/* ===================== ARC PLANNER ===================== */
.arc-controls {
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px 22px; margin-bottom: 24px;
}
.arc-controls .lead { color: var(--text-dim); font-size: 13.5px; margin-bottom: 16px; max-width: 70ch; }
.arc-sliders { display: flex; flex-direction: column; gap: 12px; margin-bottom: 12px; }
.arc-slider-row { display: flex; align-items: center; gap: 14px; }
.arc-slider-label { font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-faint); width: 56px; flex-shrink: 0; }
.arc-slider-row input[type="range"] { flex: 1; accent-color: var(--accent); height: 5px; }
.arc-slider-readout { font-size: 13px; color: var(--text-dim); white-space: nowrap; min-width: 0; }
.arc-mode-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.arc-mode-row .arc-slider-label { width: auto; } /* one word — don't wrap it in the 56px slider column */
.arc-mode-opt { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-dim); cursor: pointer; }
.arc-mode-opt input { accent-color: var(--accent); cursor: pointer; }
.arc-lead { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-faint); }
.arc-lead[hidden] { display: none; } /* inline-flex above would otherwise beat the hidden attribute */
.arc-lead input {
  width: 56px; padding: 4px 6px; font: inherit; font-size: 12.5px;
  background: var(--bg-elev-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.slider-readout {
  font-family: "Newsreader", serif; font-size: 15px; white-space: nowrap;
  background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent;
  font-weight: 700; margin-top: 4px;
}
.arc-summary { font-size: 12.5px; color: var(--text-faint); margin-top: 6px; }
/* Insight cards (approval odds + review load) — chart-card + bar-row internals
   are the Trends styles; only the arc-specific bits live here. */
.arc-insights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 16px; margin-bottom: 24px; }
.arc-insights:empty { display: none; } /* no funnel data yet — don't leave a stray margin */
.arc-odds-n { font-size: 11px; color: var(--text-faint); white-space: nowrap; flex-shrink: 0; min-width: 70px; }
.arc-pace { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-faint); margin-bottom: 14px; }
.arc-pace input {
  width: 52px; padding: 4px 6px; font: inherit; font-size: 12.5px;
  background: var(--bg-elev-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.arc-insights .bar-fill.over { background: var(--danger); }
/* legacy single-slider helper kept for other potential callers */
.slider-row { display: flex; align-items: center; gap: 18px; }
.slider-row input[type="range"] { flex: 1; accent-color: var(--accent); height: 5px; }
.arc-toolbar {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 12px; min-height: 36px;
}
.arc-select-all { display: flex; align-items: center; gap: 7px; font-size: 13px; cursor: pointer; }
.arc-select-all[hidden] { display: none; } /* display:flex above would otherwise beat the hidden attribute */
.arc-select-all input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.arc-sel-count { font-size: 12.5px; color: var(--text-faint); }
.arc-export-btn {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px; border-radius: var(--radius-sm);
  background: var(--accent-grad); color: #fff; border: none;
  font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 25%, transparent);
  transition: opacity 0.15s, transform 0.15s;
}
.arc-export-btn:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; transform: none; }
.arc-export-btn:not(:disabled):hover { transform: translateY(-2px); }
.arc-shelf-btn {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px; border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text); border: 1px solid var(--border-strong);
  font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
  transition: background 0.15s, transform 0.15s, opacity 0.15s;
}
.arc-shelf-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.arc-shelf-btn:not(:disabled):hover { background: var(--bg-hover); transform: translateY(-2px); }
/* group the buttons on the right: the first shelf button carries the auto margin */
.arc-shelf-btn + .arc-shelf-btn, .arc-shelf-btn + .arc-export-btn { margin-left: 10px; }
.arc-list { display: flex; flex-direction: column; gap: 12px; }
.arc-row {
  display: grid; grid-template-columns: 20px 52px minmax(0, 1fr) auto; gap: 16px; align-items: center;
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 12px 16px;
  transition: border-color 0.15s, transform 0.15s;
  content-visibility: auto;
  contain-intrinsic-size: auto 102px; /* row ≈ 78px img + 12px×2 padding */
}
.arc-row:hover { border-color: var(--border-strong); transform: translateX(3px); }
.arc-row.selected { background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev)); border-color: var(--accent); }
.arc-check { display: flex; align-items: center; justify-content: center; cursor: pointer; }
.arc-check input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.arc-row img { width: 52px; height: 78px; object-fit: cover; border-radius: 6px; background: var(--bg-elev-2); }
.arc-info .t { font-family: "Newsreader", serif; font-size: 15px; font-weight: 600; }
.arc-info .a { font-size: 12.5px; color: var(--text-dim); }
.arc-info .m { font-size: 11.5px; color: var(--text-faint); margin-top: 3px; }
.arc-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.arc-chip {
  font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  background: var(--bg-elev-2); border: 1px solid var(--border-strong); color: var(--text-dim);
}
.arc-chip-follow {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.countdown { text-align: center; min-width: 96px; }
.countdown .n { font-family: "Newsreader", serif; font-size: 26px; font-weight: 700; line-height: 1; }
.countdown .u { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-faint); }
.countdown.urgent .n { color: var(--danger); }
.countdown.soon .n { color: var(--warn); }
.countdown.far .n { color: var(--good); }

/* ===================== WEEKLY VIEW TOGGLE & CALENDAR ===================== */
/* The List/Calendar toggle has no row of its own — it rides the right end of
   each view's first header row (first week head / calendar header). */
/* None of the segmented toggles may shrink — .view-toggle clips (overflow:hidden),
   so a squeezed sort/density switch would swallow a label. The
   title (min-width:0) and the .wk-rule spacer (flex:1) absorb the squeeze instead. */
.wk-view-toggle, .wk-sort-toggle, .wk-density-toggle { flex: none; }
/* fallback row for the list view's empty state (no week header to ride) */
.wk-toggle-row { display: flex; justify-content: flex-end; gap: 10px; margin-bottom: 16px; }
/* Mobile copy of the list toolbar (all-btn + A–Z/Genre + List/Calendar): the
   first week head carries these on desktop, but can't fit them on a phone
   without wrapping to two sticky rows — the <=640px block swaps which copy
   shows. Hidden here so desktop is unchanged. */
.wk-list-toolbar { display: none; }
.view-toggle { display: flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.vt-btn {
  padding: 7px 16px; border: none; background: transparent;
  color: var(--text-dim); font-size: 13px; font-weight: 500; cursor: pointer;
  font-family: inherit; transition: background 0.15s, color 0.15s;
}
.vt-btn:hover { background: var(--bg-hover); color: var(--text); }
.vt-btn.active { background: var(--accent-grad); color: #fff; }

/* Between the phone layout and roomy desktop, the first week head carries the
   full control cluster (collapse-all + sort + density + view). Full text labels
   only fit past ~1300px (a long month name — "September" — needs the room), so
   below that drop the density and view toggles to icons (their titles/aria carry
   the names) and tighten the sort switch, keeping the sticky head a single row
   instead of wrapping or clipping its labels. */
@media (min-width: 641px) and (max-width: 1300px) {
  .wk-view-toggle .vt-btn span,
  .wk-density-toggle .vt-btn span { display: none; }
  .wk-view-toggle .vt-btn,
  .wk-density-toggle .vt-btn { padding: 7px 12px; }
  .wk-sort-toggle .vt-btn { padding: 7px 10px; font-size: 12px; }
  /* Drop the "Week of " preface (as the phone layout does) — the date alone reads
     fine and the shorter title keeps the head one row under the toolbar. */
  .week-head h2 .wk-of { display: none; }
  .week-head h2 .wk-of-note { display: none; }
}

/* The page-level mode switch now has its own roomy title-row slot. It does not
   need the early compression used by copies inside crowded sticky week heads;
   retain both labels throughout the desktop layout. */
@media (min-width: 941px) and (max-width: 1300px) {
  .releases-view-mode-tools .wk-view-toggle .vt-btn span { display: inline; }
  .releases-view-mode-tools .wk-view-toggle .vt-btn { padding: 0 16px; }
  .releases-view-tools .wk-density-toggle .vt-btn span { display: inline; }
  .releases-view-tools .wk-density-toggle .vt-btn { padding: 0 16px; }
}

/* Top pull (-16px) + padding mirror the List view's first .week-head (its section's
   :first-of-type margin + the head's own padding), and the -tools gap matches the
   head's flex gap, so the shared List/Calendar toggle holds the exact same position
   when switching between the two views. */
.cal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 0; padding: 8px 0; margin-bottom: 14px; }
.cal-header-tools { display: flex; align-items: center; gap: 12px; }
.cal-header-tools .wk-sort-toggle, .cal-header-tools .wk-view-toggle { display: none; }
.cal-nav-group { display: flex; align-items: center; gap: 10px; }
/* stable width so Prev/Next don't shift with the month name ("September 2026") */
.cal-nav-group .cal-month-label { min-width: 132px; text-align: center; }
.cal-month-label { font-family: "Newsreader", serif; font-size: 18px; font-weight: 600; }
.cal-nav {
  padding: 6px 14px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text-dim); font-size: 13px; cursor: pointer;
  font-family: inherit; transition: border-color 0.15s, color 0.15s;
}
.cal-nav:hover { border-color: var(--border-strong); color: var(--text); }
.cal-nav:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
/* Week-focused calendar: one row per release week, each a cover carousel. */
.wcal { display: flex; flex-direction: column; gap: 24px; }

/* Week header — same visual language as the List view's week heads (accent
   bar + serif title) so the click-through between the views feels like the
   same object in two arrangements. */
.wcal-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 4px 2px; margin-bottom: 6px;
  cursor: pointer; user-select: none; border-radius: var(--radius-sm);
}
.wcal-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wcal-title {
  font-family: "Newsreader", serif; font-size: 19px; font-weight: 600;
  display: flex; align-items: center; gap: 10px; min-width: 0;
  transition: color 0.15s;
}
.wcal-title::before {
  content: ""; flex: none;
  width: 4px; height: 1.1em; border-radius: 4px;
  background: var(--accent-grad);
}
.wcal-range { font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.wcal-count {
  font-size: 12px; color: var(--text-faint); white-space: nowrap;
  background: var(--bg-elev); border: 1px solid var(--border);
  padding: 2px 10px; border-radius: 20px;
}
.wcal-now {
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em; color: #fff;
  background: var(--accent-grad); padding: 3px 10px; border-radius: 20px;
}
/* Quiet "View in list ›" affordance at the row's right edge; warms up with the
   rest of the header on hover so the whole head reads as one link. */
.wcal-go {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--text-faint); white-space: nowrap;
  transition: color 0.15s, transform 0.15s;
}
.wcal-head:hover .wcal-title, .wcal-head:hover .wcal-go { color: var(--accent); }
.wcal-head:hover .wcal-go { transform: translateX(2px); }

/* The strip: a scroll-snap carousel with frosted arrows and edge fades, both
   shown only toward directions that still hide content (.can-prev/.can-next,
   maintained from JS on scroll/resize). The fades are a mask on the carousel
   itself — truly transparent, so no gradient band fights the body glow. */
.wcal-strip { position: relative; }
.wcal-carousel {
  --fade-l: 0px; --fade-r: 0px;
  display: flex; gap: 14px; align-items: stretch;
  overflow-x: auto; padding: 6px 2px 12px;
  scroll-snap-type: x proximity; overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%);
  mask-image: linear-gradient(90deg,
    transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%);
}
.wcal-carousel::-webkit-scrollbar { display: none; }
.wcal-carousel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.wcal-strip.can-prev .wcal-carousel { --fade-l: 40px; }
.wcal-strip.can-next .wcal-carousel { --fade-r: 40px; }
.wcal-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 36px; height: 36px; border-radius: 50%; padding: 0 0 2px;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; line-height: 1; font-family: inherit;
  color: var(--text); cursor: pointer;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-strong);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s, border-color 0.15s, color 0.15s;
}
.wcal-arrow.prev { left: -8px; }
.wcal-arrow.next { right: -8px; }
.wcal-strip.can-prev .wcal-arrow.prev,
.wcal-strip.can-next .wcal-arrow.next { opacity: 1; pointer-events: auto; }
.wcal-arrow:hover { border-color: var(--accent); color: var(--accent); }

/* Tiles: uniform landscape art so each week reads as one clean shelf. The item
   clips its children (rounded corners + the slide-up caption). Wider than the
   old portrait tile because the art is — 118px of a 460x215 header is 55px
   tall, too short to recognise a game by. */
.wcal-item {
  flex: none; width: 196px; aspect-ratio: 460 / 215; margin: 0;
  position: relative; scroll-snap-align: start;
  border-radius: 8px; overflow: hidden; cursor: pointer;
  background: var(--bg-elev-2);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  transition: transform 0.16s ease, box-shadow 0.16s ease;
  content-visibility: auto; /* tiles far along the strip skip layout + paint */
  contain-intrinsic-size: 118px 177px;
}
.wcal-item:hover { transform: translateY(-4px); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35); }

/* Genre divider in a genre-sorted strip: a slim shelf marker (accent rule +
   the genre name reading bottom-to-top) that opens each section, sized to the
   cover height so it sits between tiles rather than reading as one. */
.wcal-genre-div {
  flex: none; align-self: stretch; scroll-snap-align: start;
  display: flex; align-items: center; gap: 8px;
  padding: 2px 2px 2px 0;
}
.wcal-genre-div::before {
  content: ""; flex: none; width: 3px; align-self: stretch; border-radius: 3px;
  background: var(--accent-grad);
}
.wcal-genre-name {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: "Newsreader", serif; font-size: 13px; font-weight: 600;
  letter-spacing: 0.02em; color: var(--text-dim); white-space: nowrap;
  max-height: 100%; overflow: hidden; text-overflow: ellipsis;
}
/* Watch paw scaled down for the compact carousel tiles. */
.wcal-item .cover-paw { top: 5px; right: 5px; width: 26px; height: 26px; }
.wcal-item .cover-paw .icon { width: 14px; height: 14px; }
.wcal-release {
  position: absolute; left: 5px; bottom: 5px; z-index: 1;
  max-width: calc(100% - 10px); overflow: hidden; text-overflow: ellipsis;
  padding: 3px 6px; border-radius: 999px; white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--accent-2) 45%, transparent);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  color: var(--accent-2-text); backdrop-filter: blur(6px);
  font-size: 8.5px; font-weight: 700; line-height: 1;
}
.wcal-cover { display: block; width: 100%; height: 100%; object-fit: cover; }
.wcal-fb {
  width: 100%; height: 100%; box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; text-align: center; padding: 8px; line-height: 1.3; overflow: hidden;
  background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px;
}
.wcal-fb-title {
  font-size: 11px; font-weight: 700; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.wcal-fb-developer {
  font-size: 10px; color: var(--text-faint);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Title/developer caption slides up over the cover's foot on hover (hover-capable
   pointers only — on touch a tap goes straight to the drawer). */
.wcal-cap {
  position: absolute; inset: auto 0 0 0; padding: 20px 8px 8px;
  display: flex; flex-direction: column; gap: 2px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.85));
  color: #fff; pointer-events: none;
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
.wcal-item:hover .wcal-cap { opacity: 1; transform: none; }
.wcal-cap-title {
  font-size: 11px; font-weight: 700; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.wcal-cap-developer {
  font-size: 10px; opacity: 0.85;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Touch devices scroll the strip with a thumb — the arrows are just clutter. */
@media (hover: none) {
  .wcal-arrow { display: none; }
  /* iOS Safari auto-zooms when focusing any input < 16px. Target the base
     input[type="..."] selectors directly, plus the more-specific facet search
     selector, so every free-text filter/facet/login field stays at 16px and
     tapping in doesn't blow up the viewport. */
  input[type="text"], input[type="email"], input[type="password"], input[type="search"] { font-size: 16px; }
  .facet-search-wrap .facet-search { font-size: 16px; }
}

/* ===================== BOOK DETAIL DRAWER ===================== */
.drawer-overlay {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5);
  z-index: 200; backdrop-filter: blur(2px);
}
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 100vw);
  background: var(--bg-elev); border-left: 1px solid var(--border);
  z-index: 201; overflow-y: auto; padding: 24px 28px 40px; box-sizing: border-box;
  box-shadow: -24px 0 60px rgba(15, 12, 32, 0.14);
  transform: translateX(100%); transition: transform 0.28s cubic-bezier(.2,.75,.2,1);
}
.drawer[hidden], .drawer-overlay[hidden] { display: none; }
.drawer.open { transform: translateX(0); }
.drawer-swipe-handle { display: none; }
.drawer-close {
  position: sticky; top: 0; float: right; z-index: 3; margin: 0 0 12px 12px;
  width: 40px; height: 40px; border-radius: 12px; border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-elev) 90%, transparent); color: var(--text-dim); font-size: 14px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  backdrop-filter: blur(8px); transition: background 0.12s, color 0.12s, transform 0.12s;
}
.drawer-close:hover { background: var(--bg-hover); color: var(--text); transform: translateY(-1px); }
/* align-items: flex-start so a tall title-block (long clamped/expanded summary)
   can't stretch the cover wrap — that would drop the absolutely-positioned
   download button below the image and drag the cover down on expand. */
.dr-header { display: flex; align-items: flex-start; gap: 22px; margin: 8px 0 18px; }
.dr-cover-wrap { position: relative; flex-shrink: 0; line-height: 0; }
.dr-cover {
  width: 260px; height: 122px; object-fit: cover; border-radius: 12px;
  background: var(--bg-elev-2); flex-shrink: 0;
  box-shadow: 0 14px 30px rgba(18, 15, 34, 0.16), 0 2px 7px rgba(18, 15, 34, 0.12);
}
.dr-cover-wrap .dr-cover[role="button"] { cursor: zoom-in; }
.drawer.cover-expanded { overflow: hidden; }
.dr-cover-wrap.expanded {
  position: absolute; top: 14px; right: 14px; left: 14px; z-index: 8;
  display: flex; align-items: center; justify-content: center;
  padding: 10px; box-sizing: border-box; border: 1px solid var(--border);
  border-radius: 16px; background: color-mix(in srgb, var(--bg-elev) 96%, transparent);
  box-shadow: 0 24px 70px rgba(15, 12, 32, .28);
  animation: dr-cover-expand .18s ease-out;
}
.dr-cover-wrap.expanded .dr-cover {
  width: auto; height: auto; max-width: 100%; max-height: calc(100dvh - 48px);
  object-fit: contain; cursor: zoom-out;
  box-shadow: 0 16px 40px rgba(18, 15, 34, .2), 0 2px 8px rgba(18, 15, 34, .14);
}
.dr-cover-wrap.expanded .dr-cover-dl,
@keyframes dr-cover-expand {
  from { opacity: .65; transform: scale(.97); }
  to { opacity: 1; transform: scale(1); }}
.dr-cover-fb { border: 1px solid var(--border); }
/* Hi-res download — small icon button tucked into the cover's bottom-right. */
.dr-cover-dl {
  position: absolute; right: 6px; bottom: 6px;
  width: 30px; height: 30px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg-elev) 70%, transparent);
  border: 1px solid var(--border-strong); color: var(--text);
  cursor: pointer; backdrop-filter: blur(4px);
  opacity: 0.85; transition: opacity 0.15s, background 0.15s, transform 0.15s;
}
.dr-cover-wrap:hover .dr-cover-dl { opacity: 1; }
.dr-cover-dl:hover { background: var(--bg-elev); transform: translateY(-1px); }
.dr-cover-dl .icon { width: 16px; height: 16px; }
.dr-title-block { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; padding-top: 4px; }
.dr-title { font-family: "Newsreader", serif; font-size: 27px; font-weight: 700; line-height: 1.06; letter-spacing: -0.015em; }
/* A long title is SCALED, not clamped. The card clamps to three lines because a
   grid row is only as short as its tallest card; the drawer is where the whole
   name has to stay readable, so truncating here would leave nowhere to read it.
   At 27px a 139-character Steam joke title runs to eighteen lines and pushes
   the buy button off the panel. Thresholds come from the catalogue: p99.9 of
   titles is 72 characters, so >70 is already the outlier band and >120 is the
   three longest in 20,715. Two-class specificity so these still win inside the
   narrow-screen media queries below, which restate `.dr-title`. */
.dr-title.dr-title--long { font-size: 20px; line-height: 1.12; letter-spacing: -0.01em; }
.dr-title.dr-title--xlong { font-size: 16px; line-height: 1.2; letter-spacing: 0; }
.dr-developer { font-size: 14px; color: var(--text-dim); }
.dr-badges { display: flex; flex-wrap: wrap; gap: 6px; }
.dr-badge {
  font-size: 10px; font-weight: 700; padding: 4px 9px; border-radius: 20px;
  text-transform: uppercase; letter-spacing: 0.045em;
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-2));
  color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
}
.dr-release-fact {
  margin-top: 2px; color: var(--accent-2-text); font-size: 10px;
  font-weight: 650; line-height: 1.3;
}
/* WHERE TO BUY — one row per store (drawer.js whereToBuyHTML): a letter mark,
   the store and its platforms, the price, and an outbound arrow. Rows are at
   least 56px, a comfortable tap on a phone. The first row is the store the
   card's price comes from, tinted so the eye lands there. */
.dr-where { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 12px; }
.dr-where-title {
  margin: 0; color: var(--text-dim); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.dr-where-list {
  display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--border-strong); border-radius: 14px; background: var(--bg-elev-2);
}
.dr-buy-row {
  display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 8px 14px;
  box-sizing: border-box; color: var(--text); text-decoration: none;
  border-top: 1px solid var(--border); transition: background .15s;
}
.dr-buy-row:first-child { border-top: 0; }
.dr-buy-row.is-primary { background: color-mix(in srgb, var(--accent) 10%, var(--bg-elev-2)); }
.dr-buy-row:hover { background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-2)); }
.dr-buy-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dr-buy-mark {
  flex: none; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center; font-size: 12.5px; font-weight: 700;
  background: #2b2d38; color: #eef0f7;
}
/* Dark tints that also differ in lightness, so the marks read apart without
   relying on hue; the letter carries the identity either way. */
.dr-buy-mark[data-store="steam"] { background: #16324a; color: #cfe3f2; }
.dr-buy-mark[data-store="epic"] { background: #2b2b2e; color: #f2f2f2; }
.dr-buy-mark[data-store="playstation"] { background: #17306b; color: #dbe5ff; }
.dr-buy-mark[data-store="xbox"] { background: #1d4a26; color: #dcf5df; }
.dr-buy-mark[data-store="nintendo"] { background: #6b1a1f; color: #ffe1e3; }
.dr-buy-mark[data-store="gog"] { background: #3d1f5c; color: #eadbff; }
.dr-buy-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dr-buy-name > span { font-size: 14.5px; font-weight: 600; line-height: 1.25; }
.dr-buy-name small { color: var(--text-dim); font-size: 12px; line-height: 1.35; }
.dr-buy-price { flex: none; font-size: 15px; font-weight: 700; }
.dr-buy-price.is-missing { color: var(--text-dim); font-size: 13px; font-weight: 500; }
.dr-buy-out { flex: none; width: 16px; height: 16px; color: var(--text-dim); }
.dr-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.dr-tag {
  font-size: 10.5px; font-weight: 500; padding: 2px 8px; border-radius: 20px;
  background: var(--bg-hover); color: var(--text-dim); border: 1px solid var(--border);
}
/* Trope badges — accent-tinted, clickable (tap = filter the feed by that trope).
   Sit above the muted tag chips so the trope row reads as the interactive one. */
.dr-tropes { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.dr-trope {
  font-size: 10.5px; font-weight: 600; padding: 2px 9px; border-radius: 20px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: color-mix(in srgb, var(--accent) 78%, var(--text));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  cursor: pointer; font-family: inherit;
}
.dr-trope:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
/* Split trope pill: the label half filters the feed, the ＋ half follows the
   trope (drawer.js wireTropeBadges). Dashed seam marks the second action. */
.dr-trope-wrap { display: inline-flex; align-items: stretch; }
.dr-trope-wrap .dr-trope { border-radius: 20px 0 0 20px; border-right: none; }
.dr-trope-fw {
  display: inline-flex; align-items: center; padding: 2px 8px;
  border-radius: 0 20px 20px 0;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: color-mix(in srgb, var(--accent) 78%, var(--text));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-left-style: dashed;
  cursor: pointer; font-family: inherit;
}
.dr-trope-fw:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.dr-trope-fw.active { background: color-mix(in srgb, var(--accent) 32%, transparent); }
.dr-trope-fw .icon { width: 11px; height: 11px; }
/* Micro-label naming each pill row (the purple-vs-grey distinction isn't
   self-evident). Full flex-basis puts it on its own line above the pills. */
.dr-pill-lbl {
  flex-basis: 100%; font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-faint);
}
/* "+N more" reveal pill on the tag row — tag-shaped but visibly a control. */
.dr-tag-more {
  font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 20px;
  background: transparent; color: var(--text-dim); border: 1px dashed var(--border-strong);
  cursor: pointer; font-family: inherit;
}
.dr-tag-more:hover { color: var(--text); }
/* ONE COLUMN PER FACT, and there are at most two: a date and a platform list.
   This was wired to three — a leftover from a third fact that no longer
   exists — so a third of the strip sat empty while the platform list was
   clipped by nine pixels in the third it was given. `minmax(0, 1fr)` rather
   than `1fr`, or a long value would push the track wider than the panel. */
/* The count beside a date-certainty band. A number here is the whole reason
   the band is useful — "no date yet" reads very differently at 4,533 than it
   would at 40 — so it sits inside the pill rather than in a tooltip. */
/* The way out of an empty state that has one. Not a link: it changes tabs
   inside the app rather than going anywhere. */
.empty-cta { margin-top: 14px; }
.empty-go {
  padding: 10px 16px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-elev); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.empty-go:hover { border-color: var(--accent); color: var(--accent); }

.option-pill-count {
  margin-left: 2px; color: var(--text-dim); font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}
.precision-block .option-pills { gap: 7px; }
.precision-block .option-pill { flex: 0 1 auto; }

.dr-quick-facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0 0 20px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev-2); overflow: hidden;
}
.dr-quick-facts[data-facts="1"] { grid-template-columns: minmax(0, 1fr); }
.dr-quick-fact {
  min-width: 0; display: flex; align-items: flex-start; gap: 9px; padding: 13px 11px;
  border-right: 1px solid var(--border);
}
.dr-quick-fact:last-child { border-right: 0; }
.dr-quick-fact .icon { flex: none; width: 17px; height: 17px; margin-top: 2px; color: var(--accent); }
.dr-quick-fact span { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dr-quick-fact small {
  color: var(--text-dim); font-size: 9.5px; font-weight: 750;
  letter-spacing: 0.055em; text-transform: uppercase;
}
/* WRAPS RATHER THAN TRUNCATES. A clipped platform list is worse than a tall
   one: it cut mid-list and left a dangling comma ("PC, Xbox Series,…"), which
   reads as though the app does not know the rest. Eleven records in the
   catalogue carry more platforms than fit on one line even at half width —
   Graveyard Keeper 2 lists seven — and a second line costs a few pixels the
   panel has. The row sizes to the taller fact, which is what
   `align-items: flex-start` above is already for. */
.dr-quick-fact strong {
  color: var(--text); font-size: 12.5px; font-weight: 700; line-height: 1.25;
}
.dr-quick-fact em { color: var(--text-dim); font-size: 12px; font-style: normal; }
.dr-quick-detail {
  align-self: flex-start; min-height: 20px; margin: -1px 0 0; padding: 1px 0;
  border: 0; background: transparent; color: var(--accent-2-text);
  font: inherit; font-size: 12px; line-height: 1.3; text-align: left;
  text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
  text-underline-offset: 2px; cursor: pointer;
}
.dr-quick-detail:hover { color: var(--accent); text-decoration-color: currentColor; }
.dr-quick-detail:focus-visible { border-radius: 3px; outline: 2px solid var(--accent); outline-offset: 2px; }
.dr-signals {
  margin: 0 0 20px; padding: 12px 14px 13px; border: 0; border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 3%, var(--bg-elev-2));
}
.dr-signals .dr-section-title { margin-bottom: 0; }
.dr-signal-pills {
  display: flex; align-content: flex-start; flex-wrap: wrap; gap: 6px;
}
.dr-signal-pills .dr-trope, .dr-signal-pills .dr-tag, .dr-signal-pills .dr-tag-more,
.dr-signal-pills .dr-trope-fw {
  min-height: 25px; display: inline-flex; align-items: center; box-sizing: border-box;
}
.dr-signal-pills .dr-tag[hidden] { display: none; }
/* Jacket/marketing summary — full width below the header, flowing across the
   whole drawer (under the cover), not squeezed into the column beside it.
   Clamped to a few lines; the toggle (shown only when it overflows) expands it. */
.dr-summary-wrap { margin-bottom: 20px; }
.dr-summary {
  margin: 0; font-size: 13.5px; line-height: 1.58; color: var(--text-dim);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5; line-clamp: 5; overflow: hidden;
  /* The scraper keeps the jacket copy's paragraph breaks as newlines (one \n =
     a line break, \n\n = a paragraph); pre-line renders them while still
     collapsing runs of spaces. Clamped state is unaffected — the breaks just
     count as lines. */
  white-space: pre-line;
}
.dr-summary-expanded { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
.dr-summary-toggle {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; padding: 6px 10px;
  background: var(--bg-elev); border: 1px solid var(--border-strong); border-radius: 999px;
  cursor: pointer; font-family: inherit; font-size: 11.5px; font-weight: 700; color: var(--accent);
  transition: border-color .15s, background .15s, transform .15s;
}
.dr-summary-toggle[hidden] { display: none; }
.dr-summary-toggle:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 5%, var(--bg-elev)); transform: translateY(-1px); }
.dr-summary-toggle .icon { width: 13px; height: 13px; transition: transform .18s; }
.dr-summary-toggle.expanded .icon { transform: rotate(180deg); }

/* ── Screenshots: the contact sheet ─────────────────────────────────────
   A grid rather than a rail, on purpose. The drawer already scrolls
   vertically, and a horizontal scroller inside it fights the swipe-to-close
   gesture on touch; a grid needs no gesture at all. It also degrades on its
   own — a game with one screenshot is one tile, where a rail of one reads as
   something that failed to load. Four tiles show by default (drawer.js
   SHEET_CAP) and the heading carries the true count, so the cap is stated
   rather than discovered. */
.dr-shots { margin: 0 0 20px; }
.dr-shots[hidden] { display: none; }
.dr-shot-count {
  margin-left: 7px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-hover); color: var(--text-faint);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.dr-sheet { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
/* The trailer's poster frame leads the sheet and spans BOTH columns. It is the
   one tile that leaves the app — every other one opens the in-app viewer — so
   the difference is structural, not a badge: full width, a play glyph, and a
   caption naming where it goes. */
.dr-trailer {
  grid-column: 1 / -1; position: relative; overflow: hidden;
  aspect-ratio: 16 / 9; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: #0b0d14; color: #fff;
  transition: border-color .15s;
}
.dr-trailer:hover { border-color: var(--accent); }
/* The poster face: a <button> when we can play it, an <a> when the store page
   is the only destination. One rule either way — they look identical because
   what differs is where the press goes, not what it is. */
.dr-trailer-go {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.dr-trailer-go:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.dr-trailer-go:disabled { cursor: progress; }
.dr-trailer img, .dr-trailer-video {
  width: 100%; height: 100%; object-fit: cover; display: block; background: #0b0d14;
}
/* Playing, the caption would sit over the controls. */
.dr-trailer.playing .dr-trailer-cap { display: none; }
.dr-trailer.loading .dr-trailer-play { opacity: .35; }
.dr-trailer-play {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(12, 14, 22, .62); border: 1px solid rgba(255, 255, 255, .34);
  backdrop-filter: blur(4px);
}
/* Nudged right: an optical centre, since the glyph's mass sits left of its box. */
.dr-trailer-play .icon { width: 20px; height: 20px; margin-left: 3px; }
.dr-trailer-cap {
  position: absolute; inset: auto 0 0 0; z-index: 2; pointer-events: none;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 24px 12px 9px;
  background: linear-gradient(transparent, rgba(8, 10, 20, .86));
}
.dr-trailer-name {
  font-size: 12px; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dr-trailer-where {
  pointer-events: auto;   /* the caption itself is inert */
  position: relative; z-index: 3;   /* over the poster button beneath it */
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  font-size: 11px; font-weight: 600; color: rgba(255, 255, 255, .82);
  text-decoration: none;
}
.dr-trailer-where:hover { color: #fff; text-decoration: underline; }
.dr-trailer-where:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.dr-trailer-out { width: 12px; height: 12px; }
/* The cap is a CSS concern, not a render one: every tile is in the DOM, so
   expanding is a class toggle and the images the reader already downloaded are
   never refetched. */
.dr-sheet.clipped > .dr-shot:nth-of-type(n + 5) { display: none; }
/* `nth-of-type` counts .dr-shot buttons only, so the trailer <a> leading
   the grid does not push the fifth screenshot into view. */
.dr-shot {
  display: block; padding: 0; width: 100%; aspect-ratio: 16 / 9;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev-2); overflow: hidden; cursor: zoom-in;
  transition: border-color .15s, transform .15s;
}
.dr-shot:hover { border-color: var(--accent); transform: translateY(-1px); }
.dr-shot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dr-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dr-sheet-more {
  width: 100%; margin-top: 9px; min-height: 34px; padding: 6px 12px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text-dim);
  font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
  transition: color .15s, border-color .15s;
}
.dr-sheet-more:hover { color: var(--text); border-color: var(--text-faint); }

/* ── The full-screen viewer ─────────────────────────────────────────────
   A body-level sibling of .drawer, above it (201) and above the bottom tab
   bar (60) — nothing inside .drawer could cover the screen, since the drawer
   is a fixed scrolling box that clips its own children. */
.shot-viewer {
  position: fixed; inset: 0; z-index: 260;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 24px;
  background: rgba(8, 9, 16, .94); backdrop-filter: blur(3px);
}
.shot-viewer[hidden] { display: none; }
.shot-full {
  max-width: min(1400px, 94vw); max-height: 78vh; max-height: 78dvh;
  border-radius: var(--radius-sm); display: block;
  /* A 1920x1080 fetch is cold on the first press; the tile behind it is the
     only thing the reader saw, so land on a matching dark plate rather than a
     flash of the backdrop. */
  background: #14161f;
}
.shot-bar { display: flex; align-items: center; gap: 16px; color: #e7e9f4; }
.shot-viewer.shot-single .shot-bar { display: none; }
.shot-step, .shot-x {
  width: 42px; height: 42px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, .26); background: rgba(255, 255, 255, .08);
  color: #fff; display: flex; align-items: center; justify-content: center;
}
.shot-step:hover, .shot-x:hover { background: rgba(255, 255, 255, .18); }
.shot-step:focus-visible, .shot-x:focus-visible {
  outline: 2px solid #fff; outline-offset: 2px;
}
.shot-step .icon, .shot-x .icon { width: 19px; height: 19px; }
.shot-n {
  min-width: 68px; text-align: center; font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.shot-x { position: absolute; top: 18px; right: 20px; }

@media (max-width: 380px) {
  .dr-sheet { grid-template-columns: 1fr; }
}

.dr-section { border-top: 1px solid var(--border); padding-top: 16px; margin-bottom: 16px; }
.dr-section-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-dim); margin-bottom: 10px; }
.dr-signals-heading {
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
  margin: 0 0 9px; padding: 0;
}
.dr-signals-title {
  display: flex; align-items: center; gap: 6px; flex: none; margin: 0;
  color: var(--text); font-size: 12px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .055em; line-height: 1.2;
}
.dr-signals-title .dr-signal-key { display: inline-flex; align-items: center; gap: 5px; }
.dr-signals-title .dr-signal-key::before {
  content: ""; width: 7px; height: 7px; flex: none; border-radius: 50%;
}
.dr-signal-key-trope { color: color-mix(in srgb, var(--accent) 78%, var(--text)); }
.dr-signal-key-trope::before {
  background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent);
}
.dr-signal-key-tag { color: var(--text-dim); }
.dr-signal-key-tag::before {
  background: var(--bg-hover); border: 1px solid var(--border-strong);
}
.dr-signals-amp { color: var(--text-faint); font-size: 10px; font-weight: 700; }
.dr-signals-hint {
  min-width: 0; margin: 0; color: var(--text-dim);
  font-size: 10.5px; font-weight: 500; line-height: 1.3; text-align: left;
}
/* Save + follow: one wrapping row of pills, no card and no headings. The pills
   are the .dr-action-btn family the outbound-link row below uses, so the two
   read as siblings; everything the old card spent height on (frame, section
   heading, per-field labels, a divider) now lives inside the save menu, where
   it costs nothing until opened. */
.dr-save-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
/* Watch is the drawer's one action about the game itself: full width, taller,
   and the control system's primary role (controls.css .ui-primary, set in
   drawer.js), so it reads apart from the store rows and the Studios panel. */
.dr-save-row .dr-action-btn.dr-save-btn { flex: 1; --control-height: 46px; }
.dr-save-row .dr-action-btn {
  min-height: 34px; padding: 5px 11px; background: var(--bg-elev); font-size: 11.5px;
}
.dr-save-row .dr-action-btn .icon { width: 14px; height: 14px; }
/* The Watch button. It was a split pill with a caret half opening a menu; the
   menu's only surviving item was a Remove this button already does. */
.dr-save-btn:disabled { cursor: wait; }
.dr-save-btn.active {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border-strong));
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent);
}
/* STUDIOS (drawer.js studiosPanelHTML): one row per studio with its role and
   a Follow toggle, then a line on what following does. */
.dr-studios { display: flex; flex-direction: column; gap: 8px; margin: 0 0 18px; }
.dr-studio-list {
  display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--border-strong); border-radius: 14px; background: var(--bg-elev-2);
}
.dr-studio-row {
  display: flex; align-items: center; gap: 12px; min-height: 60px; padding: 8px 14px;
  border-top: 1px solid var(--border);
}
.dr-studio-row:first-child { border-top: 0; }
.dr-studio-mark {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; background: var(--bg-hover);
  font-size: 13.5px; font-weight: 700; color: var(--text);
}
.dr-studio-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dr-studio-name > span { font-size: 14.5px; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; }
.dr-studio-name small { color: var(--text-dim); font-size: 12px; }
.dr-studio-row .dr-follow-btn {
  flex: none; min-height: 40px; padding: 0 14px; border-radius: 999px; font-size: 13px;
}
.dr-studios-note {
  margin: 0; display: flex; align-items: center; gap: 7px;
  color: var(--text-dim); font-size: 12px;
}
.dr-studios-note .icon { flex: none; width: 14px; height: 14px; }
.dr-disclosure {
  margin: 0 0 18px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev-2); overflow: clip;
}
.dr-disclosure summary {
  list-style: none; display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; gap: 10px; min-height: 50px; padding: 0 14px;
  color: var(--text); cursor: pointer; font-size: 12px; font-weight: 700;
}
.dr-disclosure summary::-webkit-details-marker { display: none; }
.dr-disclosure summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dr-disclosure-chevron { width: 15px; height: 15px; color: var(--text-dim); transition: transform .18s ease; }
.dr-disclosure[open] .dr-disclosure-chevron { transform: rotate(180deg); }
.dr-action-row.dr-web-actions {
  grid-template-columns: minmax(0, 1fr); gap: 0; margin: 0 0 18px;
  overflow: hidden; border: 1px solid var(--border); border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 2%, var(--bg-elev-2));
}
.dr-web-actions .dr-action-btn {
  min-height: 48px; justify-content: flex-start; padding: 10px 12px;
  border: 0; border-radius: 0; background: transparent;
}
.dr-web-actions .dr-action-btn:nth-child(odd) { border-right: 1px solid var(--border); }
.dr-web-actions .dr-action-btn:nth-child(n + 3) { border-top: 1px solid var(--border); }
.dr-web-actions .dr-action-btn:nth-child(3):last-child {
  grid-column: 1 / -1; border-right: 0;
}
.dr-web-actions .dr-share-link:nth-child(5):last-child {
  grid-column: 1 / -1; border-right: 0;
}
.dr-web-actions .dr-action-btn:hover {
  background: color-mix(in srgb, var(--accent) 5%, var(--bg-elev));
}
.dr-web-actions .dr-action-btn:focus-visible {
  position: relative; z-index: 1; outline: 2px solid var(--accent); outline-offset: -3px;
}
.dr-web-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dr-web-actions .dr-action-btn .icon,
.dr-web-actions .dr-action-btn .launcheta-icon { width: 24px; height: 24px; object-fit: contain; }
.dr-web-actions .dr-action-btn .dr-external-arrow {
  flex: none; width: 14px; height: 14px; margin-left: auto; color: var(--text-faint);
}
.dr-web-actions .dr-action-btn .dr-copy-icon {
  flex: none; width: 14px; height: 14px; margin-left: auto; color: var(--text-faint);
}
.dr-details summary { grid-template-columns: minmax(0, 1fr) auto; }
.dr-details-heading { min-width: 0; display: flex; align-items: center; gap: 9px; }
.dr-details-icon {
  flex: none; width: 28px; height: 28px; display: grid; place-items: center;
  border-radius: 8px; background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent);
}
.dr-details-icon .icon { width: 17px; height: 17px; }
.dr-details-body { padding: 0 14px 15px; border-top: 1px solid var(--border); }
.dr-meta-grid { padding: 9px 0 4px; }
.dr-detail-subsection { padding-top: 14px; margin-top: 10px; border-top: 1px solid var(--border); }
.dr-detail-subsection .dr-section-title { margin-bottom: 6px; }
.dr-row { display: flex; gap: 10px; font-size: 13px; padding: 5px 0; border-bottom: 1px solid var(--border); }
.dr-row:last-child { border-bottom: none; }
.dr-lbl { color: var(--text-faint); font-size: 11.5px; font-weight: 600; min-width: 76px; flex-shrink: 0; padding-top: 1px; }
.dr-editions { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.dr-editions th { text-align: left; padding: 6px 8px; color: var(--text-faint); font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); }
.dr-editions td { padding: 7px 8px; border-bottom: 1px solid var(--border); }
.dr-editions tr:last-child td { border-bottom: none; }
.dr-muted { color: var(--text-faint); font-size: 11px; }
/* Admin-only diagnostics block: read as plumbing, not reader content. */
.dr-debug .dr-row span:last-child { font-family: monospace; font-size: 12px; word-break: break-word; }
.dr-debug-src { margin-bottom: 12px; }
.dr-debug-src:last-child { margin-bottom: 0; }
.dr-debug-src-name { font-size: 11.5px; font-weight: 700; color: var(--text-dim); margin-bottom: 4px; }
.dr-debug-src-name a { color: var(--accent); }
.dr-action-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
.dr-action-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: var(--radius-sm);
  background: var(--bg-elev-2); border: 1px solid var(--border-strong);
  color: var(--text); font-size: 12px; font-weight: 600; font-family: inherit;
  text-decoration: none; cursor: pointer; line-height: 1;
  transition: background 0.15s, transform 0.15s;
}
.dr-action-btn:hover { background: var(--bg-hover); transform: translateY(-1px); }
.dr-action-btn .icon { width: 16px; height: 16px; }
/* Follow toggles share the action-button look; active = following. */
.dr-follow-btn.active { border-color: var(--accent); color: var(--accent); }
.dr-price-link { color: var(--accent); text-decoration: none; }
.dr-price-link:hover { text-decoration: underline; }

/* drawer admin "Hide this book" control */
.dr-admin-control { margin-bottom: 14px; }
.dr-admin-control-label {
  margin-bottom: 7px; color: var(--text-dim); font-size: 11.5px; font-weight: 650;
}
.dr-release-override {
  display: grid; grid-template-columns: repeat(3, 1fr); overflow: hidden;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
}
.dr-release-override button {
  min-width: 0; padding: 8px 5px; border: 0; border-right: 1px solid var(--border);
  background: transparent; color: var(--text-dim); cursor: pointer;
  font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1.2;
}
.dr-release-override button:last-child { border-right: 0; }
.dr-release-override button:hover:not(:disabled) { color: var(--accent); }
.dr-release-override button[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent) 13%, var(--bg-elev));
  color: var(--accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}
.dr-release-override button:disabled { cursor: default; opacity: 0.6; }
.dr-hide-btn {
  display: inline-flex; align-items: center; gap: 7px; width: 100%;
  justify-content: center; padding: 9px 12px; border-radius: var(--radius-sm);
  cursor: pointer; background: transparent; border: 1px solid var(--border-strong);
  color: var(--text-dim); font-size: 12.5px; font-weight: 600; font-family: inherit;
}
.dr-hide-btn:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); }
.dr-hide-btn:disabled { opacity: 0.6; cursor: default; }
.dr-hide-btn .icon { width: 15px; height: 15px; }
/* "Fetch cover now" — same shape as Hide, but a non-destructive accent hover */
.dr-fetch-cover-btn { margin-top: 12px; }
.dr-fetch-cover-btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.dr-hint { margin-top: 7px; font-size: 10.5px; color: var(--text-faint); line-height: 1.4; }
/* "Mark as duplicate of…" — same shape as Hide, accent (non-destructive) hover */
.dr-merge-btn { margin-top: 12px; }
.dr-merge-btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }

/* manual duplicate-merge picker (in the generic modal body) */
.dr-merge-intro { margin: 0 0 12px; font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
.dr-merge-search {
  width: 100%; box-sizing: border-box; padding: 9px 12px; font-size: 13px; font-family: inherit;
  border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: var(--bg-elev-2); color: var(--text);
}
.dr-merge-search:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.dr-merge-results { margin-top: 10px; max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
.dr-merge-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer;
  background: transparent; border: 1px solid var(--border); font-family: inherit;
}
.dr-merge-row:hover { border-color: var(--border-strong); background: var(--bg-elev-2); }
.dr-merge-row.is-picked { border-color: var(--accent); background: var(--accent-soft, var(--bg-elev-2)); }
.dr-merge-row-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.dr-merge-row-meta { font-size: 11px; color: var(--text-faint); }
.dr-merge-empty { padding: 10px; font-size: 12px; color: var(--text-faint); }

/* ===================== MY SHELF ===================== */
.shelf-callout {
  display: flex; gap: 16px; align-items: flex-start;
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 20px; margin-bottom: 22px;
  position: relative; overflow: hidden;
}
/* accent spine down the left edge */
.shelf-callout::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent-grad);
}
.shelf-callout-icon {
  flex-shrink: 0; width: 40px; height: 40px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center; font-size: 20px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.shelf-callout-body { min-width: 0; }
.shelf-callout-title {
  font-family: "Newsreader", serif; font-size: 16px; font-weight: 600; margin-bottom: 4px;
}
.shelf-callout-text { font-size: 13px; color: var(--text-dim); line-height: 1.5; margin: 0; }
/* Calendar-subscribe box in Account (reuses the shelf callout shell) */
.shelf-calbox { margin-bottom: 14px; }
.shelf-cal-row { display: flex; gap: 8px; margin-top: 10px; }
.shelf-cal-url {
  flex: 1; min-width: 0; padding: 8px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim);
  font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.shelf-cal-actions { display: flex; gap: 16px; margin-top: 8px; }
.shelf-cal-linkbtn {
  background: none; border: none; padding: 0; cursor: pointer; font-family: inherit;
  font-size: 12px; color: var(--text-dim); text-decoration: underline;
}
.shelf-cal-linkbtn:hover { color: var(--text); }
.shelf-cal-linkbtn.danger:hover { color: var(--danger); }
/* Weekly-digest box (same callout shell): the single opt-in checkbox row */
.shelf-digest-optin {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
  font-size: 13px; color: var(--text); cursor: pointer; user-select: none;
}
/* The hover transform makes a row its own stacking context, which would trap an
   open pill/source menu underneath later siblings — lift the row that has one. */
.shelf-row:has(.shelf-menu) { position: relative; z-index: 7; }
.shelf-row.selected { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev)); }
.shelf-info .t { font-family: "Newsreader", serif; font-size: 15px; font-weight: 600; }
.shelf-info .a { font-size: 12.5px; color: var(--text-dim); margin-bottom: 4px; display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; }
/* saved note, collapsed: quiet text, click to edit (the dotted underline hints
   at editability; it follows the text, not the box, so it clamps cleanly) */
.shelf-note-text {
  font-size: 12px; color: var(--text-dim); line-height: 1.4; cursor: pointer;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  text-decoration: underline dotted var(--text-faint); text-underline-offset: 3px;
}
.shelf-saved.show { opacity: 1; }
.shelf-star:hover, .shelf-star.on { color: var(--warn); }
.shelf-star.on .icon { fill: var(--warn); }

/* status tabs (the .seg segmented control with per-status counts) */

/* acquisition source chip (in the byline) + its picker; the "+ Note" and
   "+ Review link" reveal chips share the same look */
.shelf-source-wrap { position: relative; display: inline-flex; }
.shelf-source-chip.is-empty, .shelf-note-chip.is-empty, .shelf-review-chip.is-empty {
  background: transparent; border: 1px dashed var(--border-strong); color: var(--text-faint);
}
.shelf-source-chip.is-empty:hover, .shelf-note-chip.is-empty:hover, .shelf-review-chip.is-empty:hover { color: var(--text); border-color: var(--text-faint); }

/* dropdown menus for the status pill + source chip */
.shelf-menu {
  position: absolute; top: calc(100% + 4px); z-index: 6; min-width: 170px;
  display: flex; flex-direction: column; padding: 5px;
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.shelf-pill-menu { right: 0; }
.shelf-menu-item {
  display: flex; align-items: center; gap: 7px; cursor: pointer; text-align: left;
  padding: 7px 10px; border-radius: 7px; border: none; background: none;
  color: var(--text-dim); font-size: 12.5px; font-family: inherit;
}
.shelf-menu-item:hover { background: var(--bg-elev-2); color: var(--text); }
.shelf-menu-item.on { color: var(--accent); font-weight: 700; }
.shelf-menu-item.danger { color: var(--danger); }
.shelf-menu-item.danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.shelf-menu-item .icon { width: 13px; height: 13px; }
.shelf-menu-sep { height: 1px; margin: 4px 2px; background: var(--border); }
.shelf-bulk-list .icon, .shelf-bulk-share .icon, .shelf-bulk-remove .icon { width: 14px; height: 14px; }
/* "Add a book" — the lists-builder search dropdown mounted on the toolbar
   (same .list-results / .bl-* rows; only the pick action differs). */
.shelf-add { margin-bottom: 0; flex: 0 1 240px; }
.shelf-add .list-results { right: auto; width: max(100%, 420px); max-width: min(92vw, 480px); }

/* transient toast */
#toastHost { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); z-index: 1000; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast {
  display: flex; align-items: center; gap: 14px; padding: 11px 16px;
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); box-shadow: 0 6px 20px rgba(0,0,0,0.3);
  color: var(--text); font-size: 13px; animation: toast-in 0.2s ease;
}
.toast-action {
  background: none; border: none; cursor: pointer; font-family: inherit; font-size: 13px;
  font-weight: 700; color: var(--accent); padding: 0;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; }}
.shelf-pill {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 7px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 600; font-family: inherit;
  background: var(--bg-elev-2); border: 1px solid var(--border-strong); color: var(--text);
}
.shelf-pill:hover { border-color: var(--accent); }

/* My Shelf's sort <select> swaps the browser arrow for a custom chevron so it
   sits in a comfortable gutter, not jammed to the edge. */
.shelf-sort select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23889096' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center;
  padding-right: 28px;
}

/* ===================== MASTER GRID ===================== */
.grid-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; gap: 12px; }
.grid-toolbar .hint { font-size: 12.5px; color: var(--text-faint); }
.dl-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 16px; border-radius: var(--radius-sm);
  background: var(--accent-grad); color: #fff; border: none;
  font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--accent) 30%, transparent);
  transition: transform 0.15s;
}
.dl-btn:hover { transform: translateY(-2px); }
.table-wrap {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: auto; max-height: 72vh; background: var(--bg-elev);
}
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-elev-2); text-align: left;
  padding: 12px 14px; font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-dim); white-space: nowrap;
  cursor: pointer; user-select: none; border-bottom: 1px solid var(--border-strong);
}
thead th:hover { color: var(--text); }
thead th .sort-ind { color: var(--accent); margin-left: 4px; }
tbody td { padding: 10px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--bg-hover); }
tbody td img { width: 34px; height: 51px; object-fit: cover; border-radius: 4px; }
.td-title { font-weight: 600; }

/* ===================== TRENDS ===================== */
.trends-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 22px; }
.chart-card {
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px 22px;
}
.chart-card h3 { font-family: "Newsreader", serif; font-size: 16px; font-weight: 600; margin-bottom: 4px;
  display: flex; align-items: center; gap: 8px; }
.chart-card h3 .icon { color: var(--accent); }
.chart-card .sub { font-size: 12px; color: var(--text-faint); margin-bottom: 18px; }
.chart-card:hover .card-share, .card-share:focus-visible { opacity: 1; }
.card-share .icon { width: 15px; height: 15px; color: inherit; }
.bar-row { display: flex; align-items: center; gap: 12px; margin-bottom: 9px; }
/* Bars that deep-link back into a filtered view — the clickable cues the inert
   bars don't get. */
.bar-row.link { cursor: pointer; }
.bar-row.link:hover .bar-label { color: var(--accent); }
.bar-label { font-size: 12px; color: var(--text-dim); width: 96px; text-align: right; flex-shrink: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Stacked variant (barChart {stacked:true}): compound labels like the Trope ×
   Genre pairings can't fit any side column, so the label rides above its bar at
   full card width instead. Ellipsis stays as a safety net; the two-class selector
   outranks the mobile 72px .bar-label override. */
.bar-row.stacked { display: block; margin-bottom: 11px; }
.bar-row.stacked .bar-label { display: block; width: 100%; text-align: left; margin-bottom: 3px; }
.bar-track { flex: 1; background: var(--bg); border-radius: 6px; overflow: hidden; height: 22px; }
.bar-fill {
  height: 100%; background: var(--accent-grad); border-radius: 6px;
  display: flex; align-items: center; justify-content: flex-end;
  padding-right: 8px; color: #fff; font-size: 11px; font-weight: 600;
  min-width: 22px; transition: width 0.5s ease;
}
[data-theme="light"] .bar-fill { color: #fff; }

.summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 22px; }
.sum-card { background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 16px 18px; }
.sum-card .big { font-family: "Newsreader", serif; font-size: 26px; font-weight: 700; }
.sum-card .cap { font-size: 11.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.06em; }

/* Section headings between the dashboard's groups of cards. scroll-margin-top
   keeps a heading clear of the topbar + sticky jump-nav on a nav-chip hop. */
.trend-section { font-family: "Newsreader", serif; font-size: 20px; font-weight: 600;
  margin: 32px 0 16px; display: flex; align-items: center; gap: 10px;
  scroll-margin-top: calc(var(--header-h) + 52px); }
.trend-section:first-child { margin-top: 0; }
.trend-section .icon { color: var(--accent); }

/* "Today on the radar" — the auto-briefing strip above the dashboard. */
.trend-digest { background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 20px 12px; margin-bottom: 14px; }
.trend-digest-head { font-size: 11.5px; color: var(--text-faint); text-transform: uppercase;
  letter-spacing: 0.06em; margin-bottom: 10px; }
.trend-digest-items { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 8px 20px; }
.trend-digest-item { display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: 13px; color: var(--text-dim); }
.trend-digest-item .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trend-digest-item .t b { color: var(--text); font-weight: 600; }
.trend-digest-glyph { color: var(--accent); flex-shrink: 0; display: inline-flex; }
.trend-digest-item.link { cursor: pointer; }
.trend-digest-item.link:hover .t { color: var(--accent); }
.trend-digest-item.link:hover .t b { color: var(--accent); }

/* The sticky section jump-nav — pins just under the topbar (same offset +
   frosted backdrop as the weekly week-heads), scrolls sideways on a phone. */
.trend-nav { position: sticky; top: var(--header-h); z-index: 5;
  display: flex; gap: 8px; overflow-x: auto; padding: 8px 2px; margin-bottom: 12px;
  background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(14px);
  scrollbar-width: none; }
.trend-nav::-webkit-scrollbar { display: none; }
.trend-nav-chip { flex-shrink: 0; padding: 5px 13px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-elev); color: var(--text-dim);
  font-family: inherit; font-size: 12px; }
.trend-nav-chip:hover { border-color: var(--accent); color: var(--text); }

/* Fresh Jackets — the just-landed cover-art strip (the tab's one place that
   shows covers rather than analyzing them). Uniform landscape tiles like the
   weekly calendar covers. */
.fresh-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 14px; }
.fresh-item { cursor: pointer; }
.fresh-cover { aspect-ratio: 460 / 215; border-radius: 5px; overflow: hidden;
  background: var(--bg-elev-2); border: 1px solid var(--border); }
.fresh-cover img { width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 0.2s; }
.fresh-item:hover .fresh-cover img { transform: scale(1.05); }
.fresh-fallback { width: 100%; height: 100%; display: flex; align-items: center;
  justify-content: center; text-align: center; padding: 8px;
  font-size: 11px; color: var(--text-faint); }
.fresh-date { margin-top: 5px; font-size: 10.5px; color: var(--text-faint); text-align: center; }
.fresh-pub { font-size: 11.5px; font-weight: 600; color: var(--text-dim); text-align: center; }

/* Ranked, clickable rows inside a chart-card. */
.lead-list { display: flex; flex-direction: column; }
.lead-row { display: flex; align-items: baseline; gap: 10px; padding: 7px 0;
  border-bottom: 1px solid var(--border); cursor: pointer; counter-increment: lead; }
.lead-row:last-child { border-bottom: none; }
.lead-row::before { content: counter(lead); width: 18px; flex-shrink: 0;
  font-size: 12px; font-weight: 700; color: var(--text-faint); text-align: right; }
.lead-list { counter-reset: lead; }
.lead-title { flex: 1; font-size: 13px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Muted secondary text inside a lead-title (developer · publisher on a cancelled
   row) — dimmed and lighter so the title still leads. */
.lead-sub { color: var(--text-faint); font-weight: 400; }
.lead-row:hover .lead-title { color: var(--accent); }
/* Inert leaderboard rows (color families, title constructions): they name a
   pattern, not a book, so they don't route anywhere — drop the clickable cues. */
.lead-row.static { cursor: default; }
.lead-row.static:hover .lead-title { color: var(--text); }
.lead-metric { flex-shrink: 0; font-size: 12px; font-weight: 600; color: var(--text-dim); }
/* ▲ marker on Fastest Rising rows — small, accent-tinted, before the title. */
.lead-glyph { color: var(--accent); font-size: 10px; margin-right: 5px; }

/* Title Constructions — a pattern + a few real example titles, the fixed
   skeleton words accented so the shape reads against the variable slots. */
.ctor-list { display: flex; flex-direction: column; }
.ctor-entry { padding: 8px 0; border-bottom: 1px solid var(--border); }
.ctor-entry:last-child { border-bottom: none; }
.ctor-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.ctor-name { font-size: 13px; font-weight: 600; color: var(--text); }
.ctor-count { flex-shrink: 0; font-size: 12px; font-weight: 600; color: var(--text-dim); }
.ctor-egs { margin-top: 4px; display: flex; flex-direction: column; gap: 2px; }
.ctor-eg { font-size: 12px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Example titles that resolve to a book open its drawer on click. */
.ctor-eg[data-key] { cursor: pointer; }
.ctor-eg[data-key]:hover { color: var(--text); text-decoration: underline; }
.ctor-fixed { color: var(--accent); font-weight: 700; }


/* "Did you know" fact list. */
.facts-card { background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 22px; }
.fact-row { display: flex; align-items: center; gap: 12px; padding: 13px 0;
  border-bottom: 1px solid var(--border); }
.fact-row:last-child { border-bottom: none; }
.fact-row.clickable { cursor: pointer; }
.fact-glyph { font-size: 18px; flex-shrink: 0; width: 22px; text-align: center; color: var(--accent);
  line-height: 0; }
.fact-label { font-size: 13px; color: var(--text-faint); flex-shrink: 0; width: 150px; }
.fact-value { font-size: 14px; color: var(--text); font-weight: 500; }
.fact-row.clickable:hover .fact-value { color: var(--accent); }

/* ===================== MISC ===================== */
.empty {
  text-align: center; padding: 70px 20px; color: var(--text-faint);
  background: var(--bg-elev); border: 1px dashed var(--border-strong); border-radius: var(--radius);
}
.empty .big { font-size: 40px; margin-bottom: 12px; }
/* "Hidden by your filters" hint row inside an empty state: names the drawer
   filter responsible + a one-click relaxation (see filters.js emptyFilterHints). */
.empty-hints {
  margin-top: 16px; display: flex; flex-wrap: wrap; gap: 8px;
  justify-content: center; align-items: center; font-size: 12.5px;
}
.empty-hints-lead { color: var(--text-dim); }
/* Backlist nudge inside an empty state: shown only when a search matches nothing
   in the whole corpus (see filters.js emptySearchNote) — never alongside the
   hints above, which mean the book exists and a filter is hiding it. */
.empty-note {
  margin: 0 auto; max-width: 42ch; line-height: 1.5; color: var(--text-dim);
}
/* The "tell us about it" half of that nudge — its own line, a shade quieter, so
   the explanation reads first and the ask doesn't look like a support prompt. */
.empty-note-ask { display: block; margin-top: 10px; font-size: 12.5px; }
.empty-note-ask a { color: var(--accent); text-decoration: none; }
.empty-note-ask a:hover { text-decoration: underline; }
.loader {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--text-dim); font-size: 15px;
  transition: opacity 0.3s; gap: 12px;
}
.loader.hidden { opacity: 0; pointer-events: none; }
.loader::before {
  content: ""; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); }}

@media (max-width: 1000px) {
  /* The shell is a single content column at every width now (the filters are an
     overlay drawer, not a grid track), so nothing to re-flow here except Trends. */
  /* minmax(0, ...) not 1fr: a bare 1fr is minmax(auto, 1fr), and `auto` = the
     column's min-content — the nowrap lead-titles / bar-labels blow that past the
     viewport and drag the whole shell wide (header stops short, page shrinks to
     fit). minmax(0,1fr) lets the track shrink so that content ellipsizes instead. */
  .trends-grid { grid-template-columns: minmax(0, 1fr); }}

/* 941–1120px: compress the header instead of wrapping it — smaller brand,
   tighter tabs, and the data-short labels — so it stays one slim row until the
   <=940px bottom tab bar takes over. min-width-gated so none of this reaches
   the mobile header layout. Upper bound sized to the tab count (a new tab pushes
   the point where full labels stop fitting). */
@media (min-width: 941px) and (max-width: 1120px) {
  .topbar { gap: 12px; padding-inline: 16px; }
  .brand-logo { width: 36px; height: 36px; }
  .brand-wordmark img, .brand-wordmark svg { height: 20px; }
  .tab { padding: 6px 11px; font-size: 13px; }
  /* Short labels: hide the JS-painted <span>, paint the attr text instead. */
  .tab[data-short] > span { display: none; }
  .tab[data-short]::after { content: attr(data-short); }
}
/* <=940px: the header goes mobile well before the rest of the layout does. Six
   tabs plus the Filters pill and the Studio/help/avatar cluster stop being a
   comfortable single row long before phone width — compressed labels just make
   a cramped row rather than a working one — so the bottom tab bar takes the nav
   over here, while the feed keeps its roomy tablet/desktop grid down to 640px.
   Everything in this block is header-shaped (topbar, bottom bar, avatar sheet)
   or a consequence of the bottom bar existing (shell padding, z-index lifts). */
@media (max-width: 940px) {
  /* Bottom padding clears the fixed bottom tab bar (+ the iOS home-indicator
     inset) so the last row of content can scroll above it. */
  .shell { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }

  /* --- Header: brand · Filters left; Studio · Help · avatar right --- */
  /* One short row (overriding the <=1000px wrap + orders). Primary nav lives
     in the fixed bottom bar (below); Theme/Sign-out/etc. live in the avatar
     menu, which opens as a bottom sheet here (web/account.js). */
  .topbar { flex-wrap: nowrap; gap: 8px; padding: 8px 14px; }
  /* Base `order` reads brand (-2) · Filters (-1) · Updates (0) · help (1) ·
     avatar (2), and ONE auto margin splits that into two clusters: brand and
     Filters left, the utilities right.
     It used to sit on `#studioBtn`, which the phase-0 strip deleted — so
     nothing carried the push and Help and the avatar sat against the wordmark
     on every phone. The margin has to live on the FIRST utility that is
     actually present, and Updates is hidden until the reader has any, so which
     one that is changes at runtime: `:has()` moves it. (Two elements both
     holding `margin-left: auto` would split the free space between them and
     pull the cluster apart, which is why this is either/or rather than both.) */
  .brand { order: -2; }
  #helpBtn { margin-left: auto; }
  #changesBtn:not([hidden]) { margin-left: auto; }
  .topbar:has(#changesBtn:not([hidden])) #helpBtn { margin-left: 0; }
  /* Shrink the brand so the header stays short. Dropping the tagline also
     lets the wordmark center vertically against the cat (flex align-center). */
  .brand-logo { width: 38px; height: 38px; }
  .brand-wordmark img, .brand-wordmark svg { height: 24px; }
  .tagline { display: none; }
  /* The bottom bar owns the tabs. */
  .nav-menu .tabs { display: none; }

  /* Filters trigger goes icon-only here (label hidden) to stay compact next to
     the search in the Releases header tools. */
  .filter-open-btn { width: 40px; height: 40px; padding: 0; justify-content: center; gap: 0; }
  .filter-open-label { display: none; }
  /* The count badge overlaps the icon corner instead of sitting inline. */
  .filter-open-btn { position: relative; }
  .filter-open-count {
    position: absolute; top: -5px; right: -5px;
    min-width: 17px; height: 17px; padding: 0 4px; font-size: 10px;
    border: 2px solid var(--bg); /* punch it out from the header backdrop */
  }

  /* Releases Variant C: the wrappers dissolve so Search, Filters, Sort, and
     View participate in one wrapping row. Opening search gives it a full row;
     the remaining controls naturally form the compact second row beneath. */
  .releases-header { gap: 10px; }
  .releases-control-bar {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    justify-content: stretch; gap: 8px; width: 100%;
  }
  .releases-control-bar:has(.wk-search.open) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .releases-control-bar:not(:has(.releases-bulk-tools)) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .releases-control-bar:not(:has(.releases-bulk-tools)):has(.wk-search.open) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .releases-header-tools, .releases-view-tools { display: contents; }
  .releases-control-bar .wk-search { width: 100%; min-width: 0; }
  .releases-control-bar .wk-search-toggle {
    width: 100%; min-width: 0; padding: 0 10px; gap: 7px;
  }
  .releases-control-bar .wk-search-toggle::after {
    content: "Search"; font-family: inherit; font-size: 13px;
    font-weight: 600; line-height: 1; white-space: nowrap;
  }
  .releases-control-bar .filter-open-btn {
    position: relative; width: 100%; min-width: 0; height: 40px; padding: 0 10px;
    justify-content: center; gap: 7px;
  }
  .releases-control-bar .filter-open-label { display: inline; }
  .releases-control-bar .filter-open-count {
    position: static; min-width: 18px; height: 18px; padding: 0 5px;
    border: 0; font-size: 10px;
  }
  .releases-view-tools .wk-density-toggle { display: none; }
  /* A CSV download is a desktop errand, and every child here is a control-bar
     grid item (the parent is display:contents) — an extra one breaks the
     column math the :has() rules above set up. */
  .releases-control-bar .releases-bulk-tools { width: 100%; min-width: 0; }
  .releases-control-bar .releases-bulk-tools .wk-all-btn {
    width: 100%; min-width: 0; padding: 0 8px; justify-content: center;
  }
  .releases-control-bar .wk-sort-select-wrap {
    width: 100%; min-width: 0;
  }
  .releases-control-bar .wk-sort-trigger { justify-content: center; }
  .releases-control-bar .wk-view-toggle { width: 100%; }
  .releases-control-bar .wk-view-toggle .vt-btn { flex: 1 1 50%; min-width: 0; }
  .releases-control-bar .wk-search.open { grid-column: 1 / -1; width: 100%; }
  .releases-control-bar .wk-search.open .wk-search-toggle {
    width: 40px; flex: none; padding: 0;
  }
  .releases-control-bar .wk-search.open .wk-search-toggle::after { content: none; }
  .releases-control-bar .wk-search.open input[type="text"] {
    flex: 1 1 auto; width: auto; min-width: 0; opacity: 1;
  }
  /* Header is a single short row now, so the sticky week dividers ride higher
     (and the genre pills pin one tier below them). */
  .week-head { top: 52px; }
  .genre-head { top: calc(52px + var(--wk-head-h)); }

  /* The control cluster (all-btn + A–Z/Genre + density + List/Calendar) moves
     off the first week head into its own scroll-away toolbar row — the cluster
     plus a date can't share one sticky row below this width, and the date is
     nowrap, so it would push the row past the viewport rather than wrap. Every
     head is a single slim row again. */
  .wk-list-toolbar { display: flex; }
  .week-head .wk-sort-toggle, .week-head .wk-density-toggle, .week-head .wk-view-toggle { display: none; }
  /* The title-row button is the primary bulk action. A compact icon copy stays
     on the pinned week header so Expand/Collapse All remains reachable while
     the user is deep in a long list. Resting copies remain hidden by the base
     `.week-head:not(.stuck)` rule above. */
  .week-head.stuck .wk-all-btn {
    width: 32px; height: 32px; padding: 0; justify-content: center;
    background: var(--bg-elev);
  }
  .week-head.stuck .wk-all-btn span { display: none; }
  .week-head.stuck .wk-refine-tools { gap: 5px; }
  .week-head.stuck .wk-sticky-search-btn,
  .week-head.stuck .wk-sticky-filter-btn { width: 32px; height: 32px; padding: 0; }
  .week-head.stuck .wk-sticky-search-btn > span,
  .week-head.stuck .wk-sticky-filter-btn > span:not(.wk-sticky-filter-count) { display: none; }
  .week-head.stuck .wk-sticky-filter-count {
    position: absolute; top: -5px; right: -5px; border: 2px solid var(--bg);
  }
  .wk-sticky-search-dock {
    top: calc(var(--header-h) + 6px); left: 12px; right: 12px;
    width: auto; padding: 5px;
  }
  /* The calendar's back-to-top only exists to align the toggle with the list
     head's on desktop; here the list toggle isn't in the head at all, so drop
     it — it's just clutter next to the month nav. */
  .cal-header-tools .wk-top-btn { display: none; }

  /* The bottom tab bar: fixed to the viewport bottom (it lives outside the
     topbar, so the backdrop-filter containing-block gotcha below doesn't
     apply). Icon above a data-short label per tab; the JS-painted full-label
     <span> is hidden. z-index 60 sits under the root-level overlays —
     avatar-menu sheet (150), modals (200), book drawer (201), login overlay
     (200, opaque), tour (600) — so each covers it. The filter drawer's 121 is
     trapped inside .shell's stacking context (z 1), so the shell itself is
     lifted while the drawer is open (see below) — otherwise the tab bar paints
     over the drawer's Reset/apply footer. */
  .tabs-bottom {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    margin: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0;
    background: var(--bg-elev);
    padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
  }
  .tabs-bottom .tab {
    display: flex; flex: 1; min-width: 0; align-items: center;
    flex-direction: column; gap: 2px;
    padding: 4px 2px; font-size: 10px; border: 0; border-radius: 10px;
  }
  .tabs-bottom .tab .icon { width: 20px; height: 20px; }
  .tabs-bottom .tab span { display: none; }
  .tabs-bottom .tab::after { content: attr(data-short); }
  .mobile-more-menu {
    display: grid; position: fixed; right: 10px;
    bottom: calc(66px + env(safe-area-inset-bottom)); z-index: 61;
    width: min(220px, calc(100vw - 20px)); padding: 7px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 13px;
    box-shadow: 0 12px 35px rgba(0,0,0,0.2);
  }
  .mobile-more-menu[hidden] { display: none; }
  .mobile-more-menu button { display: flex; align-items: center; gap: 10px; padding: 10px 11px; background: transparent; border: 0; border-radius: 8px; color: var(--text); font: 500 13px/1.2 inherit; text-align: left; cursor: pointer; }
  .mobile-more-menu button:hover { background: var(--bg-hover); }
  .mobile-more-menu .icon { width: 18px; height: 18px; color: var(--accent); }

  /* .shell (z 1) is a stacking context, so the filter drawer's z 121 can't beat
     the root-level bottom tab bar (60) on its own — the bar would cover the
     drawer's Reset/apply footer. Lift the whole shell above the bar while the
     drawer is open (same trick as the avatar-sheet topbar lift below). */
  .shell:not(.sidebar-collapsed):not(.no-sidebar) { z-index: 121; }
  /* ...and lift the topbar above the lifted shell in turn: the shell now
     out-stacks the topbar's z 20, and when the drawer opens on a scrolled
     page the feed fills the whole viewport — its cards would paint over the
     header in the strip the scrim leaves clear (scrim + drawer start at
     --header-h, deliberately below the header). */
  body:has(.shell:not(.sidebar-collapsed):not(.no-sidebar)) .topbar { z-index: 122; }

  /* The avatar menu becomes a bottom sheet: the .user-menu element stretches
     over the whole viewport as the scrim (a tap on it closes — direct
     listener in account.js) and the JS-rendered .user-menu-panel is the sheet
     pinned to its bottom edge.
     Positioning gotcha: .topbar's backdrop-filter makes it the containing
     block for fixed descendants (Chrome), so top/left resolve against the
     topbar, not the viewport. The sticky topbar is always pinned at the
     viewport's top-left, so top:0 + left:0 + an explicit viewport size land
     the scrim identically under either interpretation (bottom:0 would not). */
  /* The sheet paints inside the topbar's stacking context (z 20), which the
     bottom tab bar (60) would beat: lift the whole topbar while it's open. */
  .topbar:has(.user-menu:not([hidden])) { z-index: 150; }
  .user-menu {
    position: fixed; top: 0; left: 0; right: auto;
    width: 100vw; height: 100vh; height: 100dvh;
    min-width: 0; z-index: 150;
    display: flex; flex-direction: column; justify-content: flex-end;
    padding: 0; border: 0; border-radius: 0; box-shadow: none;
    background: rgba(0, 0, 0, 0.5);
    animation: scrim-in 0.2s ease;
    cursor: pointer; /* marks the scrim tappable for legacy Safari */
  }
  .user-menu-panel {
    display: flex; flex-direction: column; gap: 2px;
    cursor: auto;
    padding: 10px 10px calc(12px + env(safe-area-inset-bottom));
    background: var(--bg-elev);
    border-top: 1px solid var(--border);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.35);
    animation: sheet-up 0.22s ease;
  }
  @keyframes sheet-up { from { transform: translateY(24px); } to { transform: none; } }
  @media (prefers-reduced-motion: reduce) {
    .user-menu, .user-menu-panel { animation: none; }
  }
  /* Roomier tap targets. */
  .user-menu .shelf-menu-item { font-size: 14px; padding: 12px; border-radius: 10px; }
  .user-menu .shelf-menu-item .icon { width: 17px; height: 17px; }
  .user-menu-id { padding: 10px 12px 8px; }
  .user-menu-id .name { font-size: 14.5px; }
  .user-menu-id .email { font-size: 12.5px; }
}

/* Phones: the reader grid's 190px tiles only fit one-up on a ~375px screen, so
   switch the Releases gallery to a fixed 3-up cover-first layout and trim the
   per-card meta — Publisher / Imprint / Pub date / chips are noise at this size
   and stay one tap away in the drawer. Also tighten the page gutters so tiles
   get the width back. */
@media (max-width: 640px) {
  .release-badge-long { display: none; }
  .release-badge-short { display: inline; }
  /* Leave a sliver of scrim visible and round the leading edge so this reads
     as a right-side sheet rather than a replacement page. The fixed handle is
     a truthful affordance: wireSwipeToClose tracks a rightward drag. */
  .drawer {
    width: calc(100vw - 10px); max-width: none;
    padding: 16px 18px calc(28px + env(safe-area-inset-bottom));
    border-radius: 16px 0 0 16px;
    box-shadow: -18px 0 42px rgba(15, 12, 32, 0.22);
  }
  .drawer-swipe-handle {
    position: fixed; left: 3px; top: 50%; z-index: 4;
    width: 9px; height: 58px; display: grid; place-items: center;
    transform: translateY(-50%); pointer-events: none;
    border-radius: 0 999px 999px 0;
    background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
    box-shadow: 1px 0 0 var(--border);
  }
  .drawer-swipe-handle::before {
    content: ""; width: 3px; height: 34px; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 45%, var(--border-strong));
  }
  .drawer-close { top: 4px; width: 38px; height: 38px; }
  /* THE DRAWER HEADER STACKS ON A PHONE.
     Side by side, the cover had to stay narrow enough to leave the title room
     — and the box was still the book app's 138x207 JACKET while the store art
     is landscape 460x215, so `object-fit: cover` served the middle strip of a
     wide image ("...TASY RESON..."). Stacked, the art gets the full width at
     its own ratio and nothing is cropped; the title block below inherits that
     width too, which is what stops the store buttons ellipsing their own
     names. The ratio is written as `aspect-ratio`, the same way the card
     states it, so the two cannot drift apart. */
  .dr-header { display: block; margin-top: 6px; }
  /* BLOCK, not a flex column, so only the part that needs to dodge the close
     button does. `.drawer-close` is a sticky RIGHT FLOAT and a flex container
     establishes a formatting context, so the whole header stepped 50px aside
     from it — cover, title, byline and buttons alike. That is what the buy
     row's `width: calc(100% + 34px)` was clawing back, less precisely. Stacked,
     only the COVER is level with that button: it gets its own formatting
     context and steps aside, and everything below it has cleared the float and
     takes the full width. */
  .dr-cover-wrap { display: flow-root; width: auto; margin-bottom: 12px; }
  .dr-cover {
    width: 100%; height: auto; aspect-ratio: 460 / 215; border-radius: 10px;
  }
  .dr-cover-wrap.expanded { top: 8px; right: 8px; left: 8px; padding: 8px; }
  .dr-cover-wrap.expanded .dr-cover {
    width: auto; aspect-ratio: auto; max-height: calc(100dvh - 32px);
  }
  /* The min-height existed to hold the row as tall as a portrait cover beside
     it. Stacked, there is nothing to match and it only adds dead space. */
  .dr-title-block { min-height: 0; box-sizing: border-box; gap: 7px; padding-top: 1px; }
  .dr-title { font-size: 22px; }
  .dr-developer { font-size: 12.5px; }
  .dr-badge { padding: 3px 7px; font-size: 8.5px; }
  .dr-quick-facts { margin-bottom: 17px; }
  .dr-quick-fact { flex-direction: column; gap: 5px; padding: 10px 9px; }
  .dr-quick-fact .icon { width: 16px; height: 16px; }
  .dr-quick-fact small { font-size: 9px; }
  .dr-quick-fact strong { font-size: 11.5px; }
  .dr-quick-fact em { font-size: 12px; }
  .dr-summary { font-size: 13px; }
  /* .dr-save-row is a wrapping flex row, not the outbound-link grid the
     .dr-action-btn phone rule below sizes — so its pills get their own
     touch height here rather than inheriting that one. */
  .dr-save-row { margin-bottom: 16px; }
  .dr-save-row .dr-action-btn { min-height: 38px; padding: 7px 12px; }
  /* Two 16:9 tiles across a 350px content width is ~168px each, which still
     reads; below that the grid stacks. The viewer's controls grow to a touch
     target and the close button clears the notch. */
  .shot-viewer { padding: 16px; }
  .shot-full { max-height: 66vh; max-height: 66dvh; }
  .shot-step, .shot-x { width: 46px; height: 46px; }
  .shot-x { top: calc(14px + env(safe-area-inset-top)); right: 14px; }
  .dr-action-row { grid-template-columns: 1fr 1fr; }
  .dr-action-btn { min-height: 39px; padding: 8px 10px; line-height: 1.15; }
  .dr-web-actions .dr-action-btn { min-height: 46px; padding: 9px 10px; }
  .dr-disclosure summary { padding-inline: 12px; }
  /* Bottom padding clears the fixed bottom tab bar (+ the iOS home-indicator
     inset) so the last row of content can scroll above it. */
  .shell { padding: 16px 12px calc(64px + env(safe-area-inset-bottom)); gap: 16px; }
  .releases-header { padding-bottom: 8px; }
  .page-sub { font-size: 12.5px; }
  .catalog-scope-row { align-items: stretch; flex-direction: column; gap: 8px; }
  #catalogSelectorHost, #catalogHintHost { width: 100%; }
  .catalog-selector-trigger { width: 100%; height: 44px; justify-content: flex-start; border-radius: 12px; }
  .catalog-selector-trigger > .icon:last-child { margin-left: auto; }
  .catalog-hint { width: 100%; min-height: 42px; }
  .catalog-hint > span { flex: 1; }
  .catalog-hint-dismiss { flex: none; }
  .catalog-picker { position: fixed; inset: 0; z-index: 210; width: auto; }
  .catalog-picker-backdrop {
    display: block; position: absolute; inset: 0; background: rgba(6, 8, 20, 0.55);
    animation: scrim-in 0.16s ease;
  }
  .catalog-picker-sheet {
    position: absolute; left: 0; right: 0; bottom: 0; width: 100%;
    max-height: min(680px, 82dvh); overflow-y: auto;
    padding: 9px 14px calc(16px + env(safe-area-inset-bottom));
    border: 0; border-radius: 20px 20px 0 0;
    box-shadow: 0 -14px 42px rgba(0, 0, 0, 0.34);
    animation: catalog-sheet-up 0.2s ease-out;
  }
  @keyframes catalog-sheet-up { from { transform: translateY(28px); opacity: 0.8; } }
  .catalog-picker-handle {
    display: block; width: 42px; height: 4px; margin: 0 auto 7px;
    border-radius: 999px; background: var(--border-strong);
  }
  .catalog-picker-head { padding: 2px 2px 8px 4px; }
  .catalog-picker-head h2 { font: 600 23px/1.15 "Newsreader", serif; }
  .catalog-picker-close { display: inline-flex; }
  .catalog-options { gap: 3px; }
  .catalog-option {
    grid-template-columns: 36px minmax(0, 1fr) auto 24px;
    gap: 9px; min-height: 53px; padding: 8px 9px; border-radius: 11px; font-size: 14px;
  }
  .catalog-option-icon {
    width: 34px; height: 34px; border: 1px solid var(--border); border-radius: 9px;
    background: var(--bg);
  }
  .catalog-option.selected .catalog-option-icon { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); color: var(--accent); }
  .catalog-option-icon .icon { width: 18px; height: 18px; }
  .catalog-option-count { font-size: 13px; }
  .catalog-option-check .icon { width: 18px; height: 18px; }
  .catalog-picker-foot { margin: 7px 5px 0; padding-top: 10px; font-size: 12px; }
  .wk-sort-prefix { display: none; }
  .wk-sort-trigger { padding-left: 12px; padding-right: 29px; }
  /* Two up, not three. The art is LANDSCAPE now (Steam's 460x215 header — see
     .cover-wrap), so a third of a phone's width renders it about 55px tall,
     which is not enough to recognise a game by. Two columns give the art room
     and still fit four rows above the fold. */
  .gallery {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }
  .card { contain-intrinsic-size: 190px 200px; }
  /* Cover + title + developer only — full detail is one tap away in the drawer. */
  .card-meta, .series-line, .subgenre-line { display: none; }
  .card-body { padding: 7px 8px 10px; gap: 3px; }
  .card-title { font-size: 12px; line-height: 1.2; }
  .card-developer { font-size: 10.5px; }
  /* Shrink the cover overlays so they don't swamp a ~90px-wide tile. */
  .genre-tag { top: 6px; left: 6px; font-size: 8.5px; padding: 2px 6px; }
  .cover-paw { top: 5px; right: 5px; width: 26px; height: 26px; }
  .cover-paw .icon { width: 14px; height: 14px; }
  :root[data-shelf-empty="1"] .card .cover-paw { width: auto; padding: 0 9px 0 7px; }
  .cover-paw-label { font-size: 10px; }
  /* At 390px the cover is ~113px: the genre chip (~52px) and the labelled pill
     (~67px) don't fit on one row, and the pill — later in the DOM — paints over
     the chip's tail. Both are absolutely positioned, so nothing reflows to warn
     us. For the first-run state only, the control that teaches the core action
     wins over a decorative chip; the genre is in the drawer, and the chip comes
     back for good the moment the first book is watched. */
  :root[data-shelf-empty="1"] .card .genre-tag { display: none; }
  /* Slim the sticky week headers to match the denser grid. */
  .week-head h2 { font-size: 16px; }
  .week-head h2 .wk-of { display: none; }
  .week-head h2 .wk-of-note { display: none; }
  .week-head .wk-count { font-size: 11px; padding: 2px 8px; }
  .week-head.stuck .wk-count { display: none; }
  /* Date + count + back-to-top are all nowrap/unshrinkable, so the row's width
     is fixed content plus gaps — and at 320px the 12px gaps put it ~11px past
     the viewport (the .wk-rule has already collapsed to 0 and can give no
     more). 8px is the slack that keeps the narrowest phone on one line. */
  .week-head { gap: 8px; }
  /* Only the travelling copy inside a week header is icon-only. */
  .week-head .wk-all-btn span { display: none; }
  /* Four equal controls fit the primary row if its bulk label uses the concise
     verb; the title/aria-label retain the full "Collapse all" wording. */
  .releases-bulk-tools .wk-all-suffix { display: none; }
  /* The back-to-top button stays on the (sticky) head — it's the one control
     that's useless in a scroll-away toolbar — just shrunk to keep the row slim. */
  .wk-top-btn { width: 26px; height: 26px; }
  .wk-top-btn .icon { width: 13px; height: 13px; }
  /* Slimmed single-row head height (now set by the 26px top button + the head's
     8px padding) for the genre pills' sticky offset. */
  .week-section { --wk-head-h: 42px; }

  /* Compact, touch-friendly drawer heading; long catalog names ellipsize before
     displacing the active-count pill or close control. */
  .sidebar-head { padding: 14px 10px 14px 16px; }
  .sidebar-heading { gap: 8px; min-width: 0; }
  .sidebar-title { font-size: 21px; }
  .sidebar-active-count { padding: 4px 7px; }
  .sidebar-collapse, .facet-back { width: 38px; height: 38px; }
  .sidebar-collapse .icon { width: 19px; height: 19px; }

  /* --- Filters drawer: same left slide-in as desktop, just wider --- */
  /* The drawer is a full-height left slide-in on every breakpoint now (it used to
     become a bottom sheet here, which clipped its header / bled the feed through on
     iOS as the viewport shifted). A full-height panel with an internally-scrolling
     body sidesteps that whole class of bug. Only the width widens on phones. */
  .sidebar { width: min(400px, 92vw); }
  /* Denser vertical rhythm inside the drawer — every control stays, the air
     between them shrinks. Text inputs are untouched height-wise beyond padding:
     they must keep the 16px font (the hover:none rule) or iOS zooms on focus. */
  .sidebar-body { gap: 13px; padding: 14px 14px 18px; }
  .filter-block { gap: 7px; }
  .filter-section-card { padding: 12px; }
  .refine-block { gap: 6px; }
  .seg button { min-height: 30px; padding: 6px 4px; }
  .search-wrap input[type="text"] { padding-top: 8px; padding-bottom: 8px; }
  .preset-drop-btn { padding: 8px 11px; }
  .date-presets button, .release-history-seg button { padding: 6px 3px; font-size: 11px; }
  .option-pill { padding: 8px 10px; }
  .filter-row { padding: 9px 10px; }
  .sidebar-foot { padding: 10px 14px calc(10px + env(safe-area-inset-bottom)); }
  .apply-btn { padding: 9px 10px; font-size: 12.5px; }
  /* Keep the active-filter chip row to a single line (it was wrapping to 2–3 rows):
     let the chips scroll horizontally, with "Clear all" pinned at the end. */
  .filter-bar { flex-wrap: nowrap; gap: 8px; margin-bottom: 14px; }
  .filter-chips {
    flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .filter-chips::-webkit-scrollbar { display: none; }
  .filter-chip { flex-shrink: 0; }
  .filter-clear-all { flex-shrink: 0; padding: 6px 4px; }
  /* --- My Shelf: reflow the dense desktop rows for a narrow screen --- */
  /* The desktop row is a 4-column grid with a fixed-width status pill in the
     trailing column; on a phone it can't shrink and pushes the row (and the
     whole page) wider than the viewport. Restack: checkbox + cover + info on
     top, the action controls on their own full-width row underneath. */
  .shelf-row {
    grid-template-columns: 20px 52px minmax(0, 1fr);
    grid-template-areas:
      "sel cover info"
      ". cover act";
    gap: 7px 12px; padding: 12px; align-items: start;
  }
  .shelf-info .a { display: grid; gap: 6px; margin-top: 2px; }
  .shelf-pill { width: auto; min-width: 108px; justify-content: space-between; padding: 6px 10px; }
  /* Long unbroken titles/developers shouldn't force horizontal overflow. */
  .shelf-info .t, .shelf-info .a { overflow-wrap: anywhere; }

  /* Five status tabs don't fit: let the segmented control scroll sideways. */
  /* Toolbar wraps: search on its own full-width line, filters + selection below. */
  .shelf-toolbar { flex-wrap: wrap; gap: 10px; }

  /* Trim the intro callout so it doesn't dominate the first screen. */
  .shelf-callout { padding: 14px 16px; gap: 12px; margin-bottom: 18px; }
  .shelf-callout-icon { width: 34px; height: 34px; font-size: 17px; }

  /* --- ARC Planner: same reflow story as the shelf --- */
  .arc-controls { padding: 16px; margin-bottom: 18px; }
  /* Slider readouts are long ("18 months out · January 1, 2028") and set to
     nowrap, so let each row wrap and drop its readout onto a full-width line. */
  .arc-slider-row { flex-wrap: wrap; gap: 6px 12px; }
  .arc-slider-label { width: auto; }
  .arc-slider-readout { flex-basis: 100%; white-space: normal; font-size: 12px; }
  .slider-readout { white-space: normal; }

  /* Toolbar wraps: the checkboxes + count on top, the long-labeled buttons share
     the next lines instead of overflowing off the right edge. */
  .arc-toolbar { flex-wrap: wrap; gap: 10px 12px; }
  .arc-shelf-btn, .arc-shelf-btn + .arc-shelf-btn, .arc-shelf-btn + .arc-export-btn { margin-left: 0; }
  .arc-shelf-btn, .arc-export-btn { flex: 1 1 45%; justify-content: center; }

  /* Tighten the row grid + countdown so the fixed columns leave room for info,
     and let long titles break rather than push the page wider than the screen. */
  .arc-row { gap: 10px; padding: 12px; }
  .arc-row:hover { transform: none; }   /* the 3px nudge fights touch scrolling */
  .countdown { min-width: 58px; }
  .countdown .n { font-size: 22px; }
  .arc-info .t, .arc-info .a, .arc-info .m { overflow-wrap: anywhere; }

  /* --- Trends: the grids already collapse to one column at <=1000px; here just
     tighten the cards and drop each fact's value below its label so the fixed
     150px label doesn't crush it on a narrow screen. --- */
  .chart-card { padding: 16px; }
  .facts-card { padding: 6px 16px; }
  .fact-row { flex-wrap: wrap; gap: 4px 10px; }
  .fact-label { width: auto; }
  .fact-value { flex: 1 1 100%; }   /* value drops onto its own line */
  .bar-label { width: 72px; }
  .trend-section { font-size: 17px; margin: 24px 0 12px; }
  .trend-digest { padding: 12px 16px 10px; }
  .trend-digest-items { grid-template-columns: 1fr; }

  /* Admin's phone layout lives with the shell rules in the ADMIN section
     below — the overrides must FOLLOW the base rules in file order to win at
     equal specificity. Only the shared toolbars wrap here. (Tables already
     scroll inside their own .table-wrap, so they don't push the page.) */
  .grid-toolbar, .dir-toolbar-right { flex-wrap: wrap; }
  /* Releases toggle: icon-only (title/aria-label carry the names), and the
     calendar's Prev/Next collapse to bare arrows, so the header rows it
     shares (first week head / month nav) fit a phone. */
  .wk-view-toggle .vt-btn span { display: none; }
  .wk-view-toggle .vt-btn { padding: 7px 12px; }
  /* The A–Z/Genre switch keeps its (short) labels — there's no icon to fall
     back on — but tightens up. */
  .wk-sort-toggle .vt-btn { padding: 7px 9px; font-size: 12px; }
  .cal-nav-txt { display: none; }
  /* The sort switch now rides the calendar header too; the month nav plus both
     toggles can't share one phone row, so let the header wrap and drop the tools
     to their own right-aligned row beneath the nav. */
  .cal-header { flex-wrap: wrap; }
  .cal-header-tools { margin-left: auto; }

  /* Week-carousel calendar on a phone: smaller tiles, slimmer heads (drop the
     "Week of" preface and the range — the date + count carry it), and the
     "View in list" label goes icon-only. */
  .wcal { gap: 16px; }
  .wcal-item { width: 94px; contain-intrinsic-size: 94px 141px; }
  .wcal-carousel { gap: 10px; }
  .wcal-title { font-size: 16px; }
  .wcal-title .wk-of { display: none; }
  .wcal-range { display: none; }
  .wcal-go-txt { display: none; }
}

@media (max-width: 360px) {
  .dr-header { gap: 8px; }
  /* Width and ratio are inherited from the stacked rule above; only the
     breathing room changes this far down. */
  .dr-title-block { min-height: 0; }
  /* Preserve a single closed control row on the narrowest supported phones. */
  .releases-control-bar .filter-open-label { display: none; }
  .releases-control-bar .wk-search-toggle::after { content: none; }
  .releases-control-bar .releases-bulk-tools .wk-all-label { display: none; }
  .week-head.stuck .wk-top-btn { display: none; }}
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; }}
@media (prefers-reduced-motion: reduce) {
  /* No slide/fade for the filter drawer + scrim (both breakpoints). */
  .sidebar-scrim:not([hidden]) { animation: none; }
  .sidebar,
  .shell:not(.sidebar-collapsed):not(.no-sidebar) .sidebar { transition: none; }
  .catalog-picker-sheet, .catalog-picker-backdrop { animation: none; }}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px; }
::-webkit-scrollbar-track { background: transparent; }

/* ===================== ADMIN ===================== */
/* Admin tabs (Catalogs / Directory / Data Quality) are operator-only tools, so
   they run at a tighter, denser scale than the reader-facing tabs. Everything is
   scoped to #panel-admin so the drawer's editions table and the reader grid keep
   their normal size. */
#panel-admin { font-size: 12px; }
#panel-admin table { font-size: 12px; }
#panel-admin thead th { padding: 6px 10px; font-size: 10.5px; letter-spacing: 0.05em; }
#panel-admin tbody td { padding: 5px 10px; }
#panel-admin tbody td img { width: 28px; height: 42px; }
#panel-admin .table-wrap { max-height: 78vh; }
#panel-admin .grid-toolbar { margin-bottom: 10px; }
#panel-admin .grid-toolbar .hint, #panel-admin .hint { font-size: 11px; }
/* --- Admin shell: grouped sidebar + content pane (web/tabs/admin.js). The
   sidebar is the navigation *and* the status surface — badge slots are filled
   from /api/admin-overview after render, so switching views never waits. --- */
.admin-shell { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 18px; align-items: start; }
.admin-side {
  position: sticky; top: calc(var(--header-h) + 12px);
  display: flex; flex-direction: column; padding: 8px 0;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-elev);
}
.side-group { display: flex; flex-direction: column; }
.side-group + .side-group { margin-top: 5px; }
.side-group-label {
  padding: 7px 14px 2px; font-size: 9.5px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-faint);
}
.side-item {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 14px;
  border: none; background: none; color: var(--text-dim);
  font: inherit; font-size: 12.5px; cursor: pointer; text-align: left;
}
.side-item svg { width: 14px; height: 14px; flex: none; }
.side-item:hover { background: var(--bg-hover); color: var(--text); }
.side-item.active { background: var(--bg-hover); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
.side-label { flex: 1 1 auto; min-width: 0; }
.badge-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 16px; padding: 0 5px; border-radius: 99px;
  font-size: 10px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.badge-count.warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.badge-count.danger { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }
.badge-pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--good); animation: badge-pulse 1.6s ease-in-out infinite; }
@keyframes badge-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; }}
.side-foot {
  margin-top: 8px; padding: 8px 14px 2px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 4px; font-size: 11px; color: var(--text-faint);
}
.side-foot a { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); text-decoration: none; }
.side-foot a:hover { color: var(--text); }
.side-foot svg { width: 13px; height: 13px; }
.admin-main { min-width: 0; }
.admin-crumb { font-size: 11px; color: var(--text-faint); margin-bottom: 10px; }
.admin-crumb b { color: var(--text-dim); font-weight: 600; }
/* The mobile picker bar + sheet scrim + grip exist in the markup at every
   width; desktop simply never shows them (the phone rules live in the
   max-width block below the base rules, where file order lets them win). */
.admin-picker, .admin-sheet-scrim, .side-grip { display: none; }

/* --- Admin ▸ Overview: the landing dashboard. Every card is a <button> that
   navigates to the view owning its numbers (web/tabs/admin/overview.js). --- */
.ov-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 12px; }
.ov-card {
  display: flex; flex-direction: column; gap: 7px; padding: 13px 15px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ov-card:hover { background: var(--bg-hover); border-color: var(--border-strong); }
.ov-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ov-card-title { font-family: "Newsreader", serif; font-size: 15px; font-weight: 600; color: var(--text); }
.ov-card-go { color: var(--text-faint); display: inline-flex; }
.ov-card-go svg { width: 14px; height: 14px; transform: rotate(-90deg); } /* chevrons point right: "go" */
.ov-stat { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; color: var(--text-dim); }
.ov-val { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ov-val.ok { color: var(--good); }
.ov-val.warn { color: var(--warn); }
.ov-val.danger { color: var(--danger); }
span.ov-val[data-goto]:hover { text-decoration: underline; }
.ov-pipe { display: flex; gap: 3px; margin: 2px 0; }
.ov-seg { flex: 1 1 auto; height: 6px; border-radius: 3px; background: var(--border-strong); }
.ov-seg.ok { background: var(--good); opacity: 0.8; }
.ov-seg.failed, .ov-seg.stalled { background: var(--danger); }
.ov-seg.running { background: var(--accent-2); animation: badge-pulse 1.6s ease-in-out infinite; }
.ov-seg.skipped { background: var(--border); }
.ov-foot { font-size: 10.5px; color: var(--text-faint); }
@media (prefers-reduced-motion: reduce) {
  .badge-pulse, .ov-seg.running { animation: none; }}
/* Admin on a phone: the sidebar has no column to live in, so it becomes a
   bottom sheet behind one picker bar (the catalog-picker idiom — same scrim,
   radius, and slide-up). The bar names where you are and docks the attention
   badges; the sheet is the full grouped nav, thumb-reachable. Placed after
   the base rules on purpose: same specificity, so file order wins. */
@media (max-width: 640px) {
  .admin-shell { grid-template-columns: 1fr; gap: 10px; }
  .admin-picker {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 10px 14px; border: 1px solid var(--border); border-radius: 12px;
    background: var(--bg-elev); color: var(--text);
    font: inherit; font-size: 13.5px; cursor: pointer; text-align: left;
  }
  .admin-picker .picker-where { font-weight: 600; }
  .admin-picker .picker-where small { color: var(--text-faint); font-weight: 400; font-size: 12px; }
  .admin-picker .picker-badges { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; }
  .admin-picker .picker-caret { display: inline-flex; color: var(--text-faint); }
  .admin-picker .picker-caret svg { width: 14px; height: 14px; }
  .admin-sheet-scrim:not([hidden]) {
    display: block; position: fixed; inset: 0; z-index: 210;
    background: rgba(6, 8, 20, 0.55); animation: scrim-in 0.16s ease;
  }
  /* The shell is a stacking context at z-index 1, and the topbar (20) + the
     bottom tab bar (60) are body-level siblings above it — nothing inside the
     shell can paint over them, whatever its z-index. While the sheet is open,
     drop both beneath the shell so the scrim really covers the screen (the
     filter drawer plays the same body:has() trick on the topbar). */
  body:has(.admin-shell.sheet-open) .topbar,
  body:has(.admin-shell.sheet-open) .tabs-bottom { z-index: 0; }
  .admin-side { display: none; }
  .admin-shell.sheet-open .admin-side {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 211;
    max-height: min(680px, 82dvh); overflow-y: auto;
    padding: 9px 0 calc(14px + env(safe-area-inset-bottom));
    border: 0; border-radius: 20px 20px 0 0;
    box-shadow: 0 -14px 42px rgba(0, 0, 0, 0.34);
    animation: admin-sheet-up 0.2s ease-out;
  }
  .admin-shell.sheet-open .side-grip {
    display: block; flex: none; width: 42px; height: 4px; margin: 0 auto 4px;
    border-radius: 999px; background: var(--border-strong);
  }
  /* Fatter touch targets inside the sheet. */
  .side-item { padding: 10px 18px; font-size: 13.5px; }
  .side-group-label { padding: 10px 18px 3px; }
  .side-foot { padding: 10px 18px calc(2px + env(safe-area-inset-bottom)); }
}
@keyframes admin-sheet-up { from { transform: translateY(28px); opacity: 0.8; }}
@media (prefers-reduced-motion: reduce) {
  .admin-shell.sheet-open .admin-side, .admin-sheet-scrim:not([hidden]) { animation: none; }}
#panel-admin .vt-btn { padding: 5px 12px; font-size: 12px; }
#panel-admin .dl-btn { padding: 6px 12px; font-size: 12px; }
#panel-admin .dir-desc { font-size: 11.5px; }
#panel-admin .dq-summary { margin-bottom: 16px; }
#panel-admin .dq-pill { padding: 7px 12px; }
#panel-admin .dq-pill .num { font-size: 17px; }
#panel-admin .dq-section { margin-bottom: 22px; }
#panel-admin .dq-section-head h3 { font-size: 15px; }
#panel-admin .dq-subhead, #panel-admin .dq-cluster-reason { font-size: 11.5px; }
#panel-admin .mini-btn { padding: 3px 9px; margin-left: 5px; font-size: 11px; border-radius: 6px; }
#panel-admin .mono { font-size: 11px; }

.admin-loading { padding: 40px; color: var(--text-faint); text-align: center; }

/* Admin -> Hand-added: one-off Goodreads URL import. The preview is read-only;
   confirming sends a signed server-built row into goodreads_books. */
.goodreads-import {
  margin-bottom: 20px; padding: 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--border));
  border-radius: var(--radius); background:
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 8%, var(--bg-elev)), var(--bg-elev) 55%);
}
.goodreads-import-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
}
.goodreads-import-head h2 {
  margin: 0 0 3px; font-family: "Newsreader", serif; font-size: 17px; font-weight: 650;
}
.goodreads-import-head p {
  max-width: 720px; margin: 0; color: var(--text-dim); font-size: 12px; line-height: 1.5;
}
.goodreads-import-form { margin-top: 14px; }
.goodreads-import-form > label {
  display: block; margin-bottom: 5px; color: var(--text-dim); font-size: 11px; font-weight: 700;
}
.goodreads-import-controls { display: flex; gap: 8px; max-width: 820px; }
.goodreads-import-controls input {
  flex: 1 1 auto; min-width: 0; padding: 8px 10px;
  border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--bg-input, var(--bg)); color: var(--text); font: inherit;
}
.goodreads-import-controls input:focus {
  outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-color: var(--accent);
}
.goodreads-import .dl-btn:disabled { opacity: .55; cursor: wait; transform: none; }
.goodreads-import-error { margin-top: 7px; min-height: 0; }
.goodreads-import-error:empty { display: none; }
.goodreads-import-loading { padding-top: 14px; color: var(--text-faint); }
.goodreads-preview-card {
  display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 16px;
  max-width: 820px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
}
.goodreads-preview-cover {
  width: 104px; aspect-ratio: 2 / 3; overflow: hidden; border-radius: 8px;
  background: var(--bg-hover); box-shadow: 0 7px 18px color-mix(in srgb, #000 16%, transparent);
}
.goodreads-preview-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.goodreads-preview-no-cover {
  display: grid; place-items: center; width: 100%; height: 100%;
  color: var(--text-faint); font-size: 11px; text-align: center;
}
.goodreads-preview-body { min-width: 0; }
.goodreads-preview-status { margin-bottom: 6px; }
.goodreads-preview-body h3 {
  margin: 0; font-family: "Newsreader", serif; font-size: 19px; line-height: 1.18;
}
.goodreads-preview-developer { margin: 2px 0 7px; color: var(--text-dim); font-size: 13px; }
.goodreads-preview-facts,
.goodreads-preview-publisher,
.goodreads-preview-rating { margin: 3px 0; color: var(--text-dim); line-height: 1.4; }
.goodreads-preview-shelves { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.goodreads-preview-shelves span {
  padding: 3px 7px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-hover); color: var(--text-dim); font-size: 10.5px;
}
.goodreads-preview-warning { margin: 8px 0 0; color: var(--warn); line-height: 1.4; }
.goodreads-preview-actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.goodreads-preview-actions .mini-btn {
  display: inline-flex; align-items: center; gap: 5px; margin-left: 0; text-decoration: none;
}
.goodreads-preview-actions .mini-btn svg { width: 12px; height: 12px; }

@media (max-width: 600px) {
  .goodreads-import { padding: 14px; }
  .goodreads-import-controls { flex-direction: column; }
  .goodreads-import-controls .dl-btn { justify-content: center; }
  .goodreads-preview-card { grid-template-columns: 80px minmax(0, 1fr); gap: 12px; }
  .goodreads-preview-cover { width: 80px; }
  .goodreads-preview-actions { align-items: stretch; flex-direction: column; }
  .goodreads-preview-actions .dl-btn,
  .goodreads-preview-actions .mini-btn { justify-content: center; margin: 0; }}

/* Admin ▸ System: the "Site availability" (maintenance mode) toggle panel. */
.maint-admin {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 16px; margin-bottom: 16px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-elev);
}
.maint-admin.is-off {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-elev));
}
/* Same panel, the manual feed freeze: held, not broken — warn, not danger. */
.maint-admin.is-frozen {
  border-color: color-mix(in srgb, var(--warn) 55%, var(--border));
  background: color-mix(in srgb, var(--warn) 9%, var(--bg-elev));
}
/* Admin-wide freeze reminder (admin.js/#adminFreezeBanner): shown on every
   sub-view except System while the manual freeze is on — edits queue behind
   the freeze, and this is what keeps a queued Save from reading as a lost one.
   Same warn palette as .maint-admin.is-frozen. */
.admin-freeze-banner {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 14px;
  border: 1px solid color-mix(in srgb, var(--warn) 55%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 9%, var(--bg-elev));
  font-size: 12.5px; color: var(--text-dim); line-height: 1.5;
}
.admin-freeze-banner[hidden] { display: none; } /* display:flex would defeat `hidden` */
.admin-freeze-banner .freeze-banner-text { flex: 1 1 260px; min-width: 0; }
.maint-admin-main { flex: 1 1 260px; min-width: 0; }
.maint-admin .label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); }
.maint-admin .value { font-size: 15px; font-weight: 700; margin: 2px 0 3px; }
.maint-admin .sub { font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.maint-admin .dl-btn.danger {
  background: none; color: var(--danger); border: 1px solid var(--danger); box-shadow: none;
}
.maint-admin .dl-btn.danger:hover { background: var(--danger); color: #fff; }
/* Admin ▸ Tropes: full-width group header rows inside the vocabulary table. */
.trope-group-row td {
  padding-top: 16px; font-family: "Newsreader", serif; font-weight: 700;
  font-size: 13px; color: var(--text-dim); border-bottom: none;
}
.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12px; color: var(--text-dim); }
.muted { color: var(--text-faint); }
#panel-admin .grid-toolbar { align-items: flex-end; }
.admin-actions { white-space: nowrap; text-align: right; }
.mini-btn {
  padding: 5px 11px; margin-left: 6px; border-radius: 8px; cursor: pointer;
  background: var(--bg-hover); border: 1px solid var(--border); color: var(--text-dim);
  font-family: inherit; font-size: 12px; font-weight: 600; transition: all 0.15s;
}
.mini-btn:hover { color: var(--text); border-color: var(--border-strong); }
.mini-btn.danger:hover { color: #fff; background: var(--danger); border-color: var(--danger); }
.mini-btn.copied { color: var(--good); border-color: var(--good); }

/* ----- Admin ▸ Users: per-account activity columns + expandable detail ----- */
.user-row { cursor: pointer; }
.user-row:hover { background: var(--bg-hover); }
.user-caret { width: 22px; color: var(--text-faint); }
.user-caret svg { width: 14px; height: 14px; transition: transform 0.15s; }
.user-row.expanded .user-caret svg { transform: rotate(180deg); }
.td-num { text-align: center; }
th.td-num { text-align: center; }
.user-detail-row > td { border-bottom: none; padding-top: 0; }
.user-detail {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px 18px; padding: 4px 0 14px;
}
.user-detail-sec { display: flex; flex-direction: column; gap: 5px; }
.user-detail-lbl {
  font-size: 11px; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; color: var(--text-faint);
}
.user-detail-body { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.user-chip {
  display: inline-flex; align-items: baseline; gap: 5px; padding: 2px 8px;
  border-radius: 999px; background: var(--bg-hover); border: 1px solid var(--border);
  font-size: 11px; color: var(--text-dim);
}
.user-chip b { color: var(--text); font-variant-numeric: tabular-nums; }
.user-list-link {
  display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 12px;
  color: var(--accent, var(--text)); text-decoration: none; border: 1px solid var(--border);
}
.user-list-link:hover { border-color: var(--border-strong); text-decoration: underline; }

/* ----- Catalog Directory: every column header is a sort toggle + a filter ----- */
.dir-table thead th { cursor: default; vertical-align: top; padding-bottom: 7px; }
.dir-sort {
  display: inline-flex; align-items: baseline; gap: 3px; cursor: pointer;
  padding: 0; background: none; border: none; color: inherit;
  font: inherit; text-transform: inherit; letter-spacing: inherit;
}
.dir-sort:hover { color: var(--text); }
.dir-sort.r { width: 100%; justify-content: flex-end; }
.dir-sort .sort-ind { color: var(--accent); font-size: 8px; }
.dir-col-filter {
  display: block; box-sizing: border-box; width: 100%; min-width: 0; margin-top: 4px;
  padding: 2px 5px; border-radius: 4px;
  background: var(--bg-elev); border: 1px solid var(--border); color: var(--text);
  font-family: inherit; font-size: 10.5px; font-weight: 400;
  text-transform: none; letter-spacing: normal;
}
.dir-col-filter::placeholder { color: var(--text-faint); }
/* Selects need a floor + arrow gutter so the native chevron never laps the text. */
select.dir-col-filter { min-width: 84px; padding-right: 18px; }
.dir-link { color: var(--text); text-decoration: none; border-bottom: 1px solid transparent; }
.dir-link:hover { color: var(--accent); border-bottom-color: currentColor; }
.dir-toolbar-right { display: flex; align-items: center; gap: 14px; }
.dir-show-hidden { display: inline-flex; align-items: center; gap: 5px; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
.dir-hidden-row { opacity: 0.5; }
.dir-hidden-row:hover { opacity: 0.85; }
/* Admin runs sidebar-less (.shell.no-sidebar), so its tables already fill the
   viewport minus the 28px page gutters up to the shell's 2200px cap — no width
   override needed there. Once the shell caps and centers (viewport ≳2256px),
   let every admin table-wrap (Catalogs / Directory / Data Quality) break out
   rightward past the cap to 32px from the display edge:
   content-left = (100vw − 2200)/2 + 28px pad = 50vw − 1072px, so
   width = 100vw − content-left − 32px = 50vw + 1040px. */
@media (min-width: 2256px) {
  #panel-admin .table-wrap { width: calc(50vw + 1040px); }
}
#dirStatus { margin: 0 0 12px; min-height: 1em; }
.dir-desc { max-width: 380px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--text-dim); font-size: 12.5px; }
/* Bulk actions operate on the filtered row set; label + tight button group. */
.dir-bulk { display: inline-flex; align-items: center; gap: 2px; color: var(--text-faint); white-space: nowrap; }
.dir-bulk .mini-btn { margin-left: 4px; }
/* Full-width escape hatch under the capped tbody render. */
.dir-show-all { text-align: center; padding: 10px; }
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600;
  color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent);
}
/* Accent variant: catalogs first seen in the last few days */
.pill-new { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
/* Triage suggestion pills (Directory): review = warn, empty catalog = muted
   (hide reuses .pill-bad, keep the base .pill green). */
.pill-warn { color: var(--warn, #e0a458); background: color-mix(in srgb, var(--warn, #e0a458) 16%, transparent); }
.pill-muted { color: var(--text-faint); background: color-mix(in srgb, var(--text-faint) 14%, transparent); }
/* "Detected by" badge in the Admin → Hidden excluded-language table */
.lang-src { padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
.lang-src-heur { color: var(--warn, #e0a458); background: color-mix(in srgb, var(--warn, #e0a458) 16%, transparent); }
.lang-src-decl { color: var(--text-faint); background: color-mix(in srgb, var(--text-faint) 14%, transparent); }

/* ----- Admin ▸ Runs + System ----- */
/* Divider between the toggle's view groups (content · people · operations). */
.vt-sep { width: 1px; align-self: stretch; background: var(--border); }
.pill-bad { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.run-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin-bottom: 20px; }
.run-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  padding: 11px 13px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border);
}
.run-card-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; width: 100%; }
.run-card-title { font-weight: 600; font-size: 13px; min-width: 0; overflow-wrap: anywhere; }
.run-card-name, .run-name-sub { font-size: 10.5px; color: var(--text-faint); overflow-wrap: anywhere; }
.run-card-body { font-size: 12px; color: var(--text-dim); }
.run-card .mini-btn { margin-left: 0; }
.run-errs { color: var(--danger); }
/* Live scrape-pipeline diagram (Admin ▸ Runs) — the dashed lane mirrors the
   admin guide §5 figure's scrape_active lane. */
.pipe-lane {
  border: 1.5px dashed var(--border); border-radius: var(--radius-sm);
  padding: 14px 16px; margin-bottom: 24px;
}
.pipe-lane[data-active="1"] { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.pipe-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.pipe-explain {
  margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-dim); cursor: pointer; user-select: none;
  white-space: nowrap;
}
/* Step descriptions ride every box but only show when the toggle asks. */
.pipe-step-desc { display: none; margin-top: 4px; font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }
.pipe-lane[data-explain="1"] .pipe-step-desc { display: block; }
.pipe-live { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: var(--accent); }
.pipe-idle { font-size: 13px; font-weight: 600; color: var(--text-dim); }
.pipe-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; animation: pipePulse 1.6s ease-in-out infinite; }
.pill .pipe-dot { margin-right: 4px; }
@keyframes pipePulse { 50% { opacity: 0.25; }}
@media (prefers-reduced-motion: reduce) { .pipe-dot { animation: none; }}
/* Vertical connector between stages, like the figure's arrows. */
.pipe-stage + .pipe-stage::before {
  content: ""; display: block; width: 1.5px; height: 12px;
  background: var(--border); margin: 3px auto;
}
.pipe-stage-label {
  margin-bottom: 4px; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-faint);
}
.pipe-stage-note { text-transform: none; letter-spacing: 0; font-weight: 500; }
.pipe-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px; }
.pipe-step {
  padding: 8px 11px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border);
}
.pipe-step[data-log], .pipe-step[data-tail] { cursor: pointer; }
.pipe-step[data-log]:hover, .pipe-step[data-log]:focus-visible,
.pipe-step[data-tail]:hover, .pipe-step[data-tail]:focus-visible { border-color: var(--accent); }
/* The head wraps the pill under a long title rather than bleed into the
   neighbouring box; the internal script name rides beneath in small mono. */
.pipe-step-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 4px 8px; }
.pipe-step-title { min-width: 0; overflow-wrap: anywhere; font-size: 12.5px; font-weight: 600; }
.pipe-step-name { font-size: 10.5px; color: var(--text-faint); overflow-wrap: anywhere; }
.pipe-step-meta { margin-top: 3px; font-size: 12px; color: var(--text-dim); }
.pipe-step[data-status="pending"], .pipe-step[data-status="skipped"] { border-style: dashed; opacity: 0.65; }
.pipe-step[data-status="running"] { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.pipe-step[data-status="failed"], .pipe-step[data-status="stalled"] {
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
}
/* Run timeline (inside the pipeline lane): one bar per recorded step on the
   run's wall-clock span. Quarter-mark gridlines ride the track background. */
.pipe-tl { margin: 0 0 14px; }
.pipe-tl summary {
  cursor: pointer; user-select: none;
  font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.pipe-tl-head, .pipe-tl-row {
  display: grid; grid-template-columns: 230px minmax(0, 1fr) 72px;
  align-items: center; gap: 10px;
}
.pipe-tl-head { margin-top: 8px; }
.pipe-tl-scale { position: relative; height: 14px; font-size: 10px; color: var(--text-faint); }
.pipe-tl-scale span { position: absolute; transform: translateX(-50%); }
.pipe-tl-row { padding: 1px 0; border-radius: 4px; }
.pipe-tl-row[data-log], .pipe-tl-row[data-tail] { cursor: pointer; }
.pipe-tl-row[data-log]:hover .pipe-tl-bar, .pipe-tl-row[data-log]:focus-visible .pipe-tl-bar,
.pipe-tl-row[data-tail]:hover .pipe-tl-bar, .pipe-tl-row[data-tail]:focus-visible .pipe-tl-bar {
  background: var(--accent);
}
.pipe-tl-name {
  font-size: 11px; color: var(--text-dim); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pipe-tl-track {
  position: relative; height: 12px; border-radius: 3px; background: var(--bg-elev);
  background-image: linear-gradient(to right, var(--border) 1px, transparent 1px);
  background-size: 25% 100%;
}
.pipe-tl-bar {
  position: absolute; top: 1px; bottom: 1px; min-width: 3px; border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 55%, var(--bg-elev));
}
.pipe-tl-bar[data-status="failed"], .pipe-tl-bar[data-status="stalled"] {
  background: color-mix(in srgb, var(--danger) 70%, var(--bg-elev));
}
.pipe-tl-bar[data-status="running"] { animation: pipePulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .pipe-tl-bar { animation: none; }}
.pipe-tl-dur { font-size: 11px; color: var(--text-faint); white-space: nowrap; }
@media (max-width: 640px) {
  .pipe-tl-head, .pipe-tl-row { grid-template-columns: 110px minmax(0, 1fr) 56px; gap: 6px; }}
.log-controls { display: flex; align-items: center; gap: 10px; }
.log-controls select {
  padding: 5px 8px; border-radius: 8px; max-width: 320px;
  background: var(--bg-elev); border: 1px solid var(--border); color: var(--text);
  font-family: inherit; font-size: 12px;
}
.log-pane pre {
  margin: 6px 0 0; padding: 12px 14px; max-height: 480px; overflow: auto;
  border-radius: var(--radius-sm); background: var(--bg-elev); border: 1px solid var(--border);
  font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word;
}
.log-line-err { color: var(--danger); }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-bottom: 18px; }
.stat-card { padding: 12px 14px; border-radius: var(--radius-sm); background: var(--bg-elev); border: 1px solid var(--border); }
.stat-card .label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-faint); }
.stat-card .value { font-size: 21px; font-weight: 650; margin: 3px 0 1px; }
.stat-card .sub { font-size: 12px; color: var(--text-dim); }
/* warning accent (e.g. System's taxonomy-gaps card while gaps remain) */
.stat-card.warn { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.stat-card.warn .value { color: var(--danger); }

/* Admin ▸ Metrics (web/tabs/admin/metrics.js). Percentage cells are drawn as
   proportional bars rather than bare numbers so a funnel/retention column can
   be read down at a glance; no chart library is involved. */
.metric-bar { position: relative; display: flex; align-items: center; gap: 8px;
  height: 18px; }
.metric-bar::before { content: ""; position: absolute; inset: 3px 0; border-radius: 3px;
  background: var(--bg-elev); border: 1px solid var(--border); }
.metric-bar-fill { position: relative; height: 12px; border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 60%, transparent); min-width: 2px; }
.metric-bar-fill.danger { background: color-mix(in srgb, var(--danger) 55%, transparent); }
.metric-bar span { position: relative; font-size: 11px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; margin-left: auto; }
.metric-row-strong td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.metric-flag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-faint); white-space: nowrap; }
.muted-cell { color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* minmax(0,…) so a long search term scrolls its own table instead of forcing
   the page wide (the 1fr overflow trap). */
.metric-gap-grid { display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
/* Boot-performance histograms: one card per metric, each a bucket-ordered bar
   list. Same minmax(0,…) guard as above — the bucket column is nowrap. */
.metric-perf-grid { display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.metric-perf-card { border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 14px; background: var(--bg-elev); min-width: 0; }
.metric-perf-card table { width: 100%; }
.metric-perf-card td { padding: 2px 8px 2px 0; border: 0;
  font-variant-numeric: tabular-nums; }
.metric-perf-head { display: flex; flex-wrap: wrap; gap: 4px 10px;
  align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
.metric-perf-head .muted-cell { font-size: 11px; }
/* Active-users chart: the day/week/month toggle, the live "online now" tile,
   and the per-line legend/current-value readout. */
.au-top { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center;
  justify-content: space-between; margin-bottom: 10px; }
.au-toggle { display: inline-flex; gap: 2px;
  border: 1px solid var(--border); border-radius: 7px; padding: 2px;
  background: var(--bg); }
.au-gran { border: 0; background: transparent; color: var(--text-dim);
  font: inherit; font-size: 12px; padding: 4px 12px; border-radius: 5px;
  cursor: pointer; }
.au-gran:hover { color: var(--text); }
.au-gran.active { background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--text); }
.au-online { display: inline-flex; align-items: center; gap: 8px; font-size: 12px;
  color: var(--text-dim); }
.au-online .muted-cell { font-size: 11px; }
.au-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok, #35c48a);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok, #35c48a) 60%, transparent);
  animation: au-pulse 2.2s ease-out infinite; }
@keyframes au-pulse { to { box-shadow: 0 0 0 6px transparent; }}
@media (prefers-reduced-motion: reduce) { .au-dot { animation: none; }}
.au-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
  margin-bottom: 6px; font-size: 11px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; }
.au-key { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.au-key i { width: 10px; height: 3px; border-radius: 2px; display: inline-block; }
.run-bits { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; }

/* Toasts (admin action feedback; created on demand by tabs/admin/common.js). */
#toastHost {
  position: fixed; right: 18px; bottom: 18px; z-index: 400;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
}
/* Phones: lift toasts clear of the fixed bottom tab bar. Placed after (not
   inside) the main ≤640px block so it also beats this later #toastHost
   definition on source order — it covers both toast hosts. */
@media (max-width: 640px) {
  #toastHost { bottom: calc(64px + env(safe-area-inset-bottom)); }
}
.toast {
  padding: 9px 14px; border-radius: 10px; font-size: 13px; font-weight: 600;
  background: var(--bg-elev-2); border: 1px solid var(--border-strong);
  color: var(--text); box-shadow: var(--shadow);
  opacity: 1; transition: opacity 0.3s, transform 0.3s;
}
.toast-danger { border-color: var(--danger); color: var(--danger); }
.toast-out { opacity: 0; transform: translateY(6px); }

.dq-summary { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; }
.dq-pill {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 16px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border);
}
.dq-pill .num { font-size: 20px; font-weight: 700; color: var(--text); }
.dq-pill .lbl { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
/* The pills double as the check selector: clickable buttons that pick which
   check renders below. The trailing "Records scanned" stat is not selectable. */
button.dq-pill {
  font-family: inherit; text-align: left; cursor: pointer;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
button.dq-pill:hover { border-color: var(--accent); }
button.dq-pill.has-issues .num { color: var(--accent); }
button.dq-pill.active {
  border-color: var(--accent);
  box-shadow: 0 3px 12px color-mix(in srgb, var(--accent) 22%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev));
}
.dq-pill-stat { opacity: 0.75; }
.dq-section { margin-bottom: 30px; }
.dq-section-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.dq-section-head h3 { font-family: "Newsreader", serif; font-size: 17px; font-weight: 600; }
.dq-subhead { font-size: 12.5px; font-weight: 600; color: var(--text-dim); margin: 14px 0 6px; }
.dq-cluster { margin-bottom: 14px; }
.dq-cluster-reason { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-dim); margin-bottom: 6px; }
.dq-cluster-actions { display: flex; gap: 8px; margin-top: 6px; }
.dq-row-actions { white-space: nowrap; text-align: right; }
.dq-identity-toolbar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.dq-identity .dq-cluster-reason { flex-wrap: wrap; }
.dq-lifecycle {
  display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
  border: 1px solid var(--border);
}
.dq-lifecycle-active { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.dq-lifecycle-dormant { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.dq-lifecycle-historical { color: var(--text-dim); background: var(--bg-hover); }
@media (max-width: 640px) {
  .dq-identity-section .dq-section-head { display: block; }
  .dq-identity-section .dq-section-head .hint { display: block; margin-top: 5px; line-height: 1.35; }}
/* Duplicate-confidence badge (radar.title_match band). Scoped with the double class
   so it colours only the per-cluster badge, not the filter chips that reuse
   .dq-band-<band> for their data hook. */
.dq-band {
  display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
  border: 1px solid transparent;
}
.dq-band.dq-band-very_likely { background: rgba(34, 160, 90, 0.16); color: #2a9d5b; border-color: rgba(34, 160, 90, 0.4); }
.dq-band.dq-band-likely      { background: rgba(70, 130, 220, 0.16); color: #5b8ee0; border-color: rgba(70, 130, 220, 0.4); }
.dq-band.dq-band-review      { background: rgba(210, 160, 40, 0.16); color: #c99a2e; border-color: rgba(210, 160, 40, 0.4); }
.dq-band.dq-band-unlikely    { background: rgba(150, 150, 160, 0.15); color: var(--text-dim); border-color: rgba(150, 150, 160, 0.35); }
@media (max-width: 640px) {
  .dq-release-table, .dq-release-table tbody { display: block; width: 100%; }
  .dq-release-table thead { display: none; }
  .dq-release-table tr {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px;
    padding: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg-elev);
  }
  .dq-release-table td {
    display: block; min-width: 0; padding: 0; border: 0;
    white-space: normal; overflow-wrap: anywhere;
  }
  .dq-release-table td::before {
    content: attr(data-label); display: block; margin-bottom: 3px;
    color: var(--text-faint); font-size: 9.5px; font-weight: 700;
    letter-spacing: 0.05em; text-transform: uppercase;
  }
  .dq-release-table td:nth-child(2),
  .dq-release-table .dq-row-actions { grid-column: 1 / -1; }
  .dq-release-table .dq-row-actions {
    display: flex; gap: 7px; text-align: left;
  }
  .dq-release-table .dq-row-actions .mini-btn { margin-left: 0; }}
/* Duplicates toolbar: search + reason chips + bulk-confirm. */
.dq-dupbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }
.dq-dupsearch {
  flex: 1 1 240px; min-width: 0; padding: 7px 11px; font: inherit; font-size: 13px;
  color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: 9px;
}
.dq-dupsearch:focus { outline: none; border-color: var(--accent); }
.dq-dupchips { display: flex; gap: 6px; }
.dq-chip {
  font: inherit; font-size: 12px; padding: 5px 10px; border-radius: 20px; cursor: pointer;
  background: var(--bg-hover); color: var(--text-dim); border: 1px solid var(--border);
}
.dq-chip .c { opacity: 0.65; margin-left: 3px; }
.dq-chip.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); font-weight: 600;
}
.dq-chip.active .c { opacity: 0.85; }
/* Buy-link verdict strength: "Confirmed" = Amazon's own catalog agrees the
   identifier is dead, so the search fallback is permanent. Anything unmarked is
   the image probe's inference alone, which over-flags — those are the rows a
   human glance is actually worth. */
.dq-confirmed {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--text-dim);
}
.dq-bulk { margin-left: auto; }
.dq-bulk[hidden] { display: none; }
.dq-nomatch { font-size: 12.5px; color: var(--text-dim); padding: 8px 2px; }
/* "Why the auto-merge missed this" hint under a cluster's reason badge. */
.dq-why { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--text-dim); margin: -2px 0 7px; }
.dq-why svg { width: 13px; height: 13px; flex: none; opacity: 0.7; }
/* Fixed layout with shared column widths so every cluster's table lines up down the
   whole list (each cluster is its own <table>, so auto-layout would drift). Title
   (col 2) is left unset to absorb the remaining width — never overflows. */
/* min-width so the 7-column table keeps readable columns on a phone: the .table-wrap
   (overflow:auto) scrolls horizontally instead of table-layout:fixed squeezing every
   column to a few px and overflow-wrap shattering text one char per line. No effect on
   the wide admin desktop layout, where the table is already far wider than this. */
.dq-dup-table { table-layout: fixed; min-width: 680px; }
.dq-dup-table :is(th, td) { overflow-wrap: anywhere; }
.dq-dup-table :is(th, td):nth-child(1) { width: 48px; }   /* cover */
.dq-dup-table :is(th, td):nth-child(3) { width: 16%; }    /* developer */
.dq-dup-table :is(th, td):nth-child(4) { width: 14%; }    /* publisher */
.dq-dup-table :is(th, td):nth-child(5) { width: 104px; white-space: nowrap; }  /* pub date */
.dq-dup-table :is(th, td):nth-child(6) { width: 18%; }    /* ISBNs */
.dq-dup-table :is(th, td):nth-child(7) { width: 140px; }  /* actions */
/* Cover thumbnail + source chips + difference highlighting inside a cluster table. */
.dq-cover-cell { width: 48px; padding-right: 0; }
.dq-thumb { width: 34px; height: 51px; object-fit: cover; border-radius: 3px; display: block; background: var(--bg-hover); }
.dq-thumb-none {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-dim); opacity: 0.4; background: var(--bg-hover);
}
.dq-thumb-none svg { width: 18px; height: 18px; }
.dq-srcs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.dq-src {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.02em; padding: 1px 6px; border-radius: 10px;
  color: var(--text-dim); background: var(--bg-hover); border: 1px solid var(--border);
}
.dq-diff { background: color-mix(in srgb, var(--warn, #d08700) 12%, transparent); }

/* Flat covers: the dual-cover pair, render beside flat. Both are shown at the
   same size so a mismatched edition is obvious at a glance; the live one carries
   the accent ring, the other is dimmed back so the default reads instantly. */
.dq-flat-pair { display: flex; gap: 12px; align-items: flex-start; width: 260px; }
.dq-flat-fig { margin: 0; text-align: center; opacity: 0.55; transition: opacity 0.12s; }
.dq-flat-fig.live { opacity: 1; }
/* The id selector is deliberate: #panel-admin tbody td img pins every admin table
   thumbnail to 28x42, and these need to be big enough to compare two jackets. */
#panel-admin .dq-flat-fig img {
  height: 150px; width: auto; max-width: 112px; object-fit: contain;
  border-radius: 4px; background: var(--bg-hover);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}
#panel-admin .dq-flat-fig.live img { outline: 2px solid var(--accent); outline-offset: 2px; }
.dq-flat-score { display: block; font-variant-numeric: tabular-nums; }
.dq-flat-fig figcaption {
  font-size: 10.5px; font-weight: 600; color: var(--text-dim); margin-top: 4px;
}
.dq-flat-fig.live figcaption { color: var(--accent); }
.dq-isbn-shared {
  font-weight: 700; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent); border-radius: 3px; padding: 0 2px;
}
/* Missing-covers group header: subhead on the left, "Fetch all" on the right. */
.dq-covers-head { display: flex; align-items: center; justify-content: space-between; }
.dq-covers-head .mini-btn { margin-left: 0; }

/* Week report: one live-week audit, then four editorial catalog slices. The
   detailed rows are cards rather than a wide table because cause + resolution
   copy needs to remain readable on phones. */
[data-week-report] { transition: opacity 0.15s; }
[data-week-report].is-loading { opacity: 0.55; pointer-events: none; }
.dq-week-toolbar {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 18px;
  padding: 18px 20px; border: 1px solid var(--border); border-radius: var(--radius);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--accent) 9%, var(--bg-elev)), var(--bg-elev));
}
.dq-week-toolbar h3 {
  margin: 2px 0 3px; font-family: "Newsreader", serif; font-size: 25px; line-height: 1.1;
}
.dq-week-kicker {
  color: var(--accent); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
}
.dq-week-picker {
  display: grid; gap: 5px; min-width: 240px; color: var(--text-dim);
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
}
.dq-week-picker select {
  width: 100%; padding: 8px 32px 8px 10px; color: var(--text);
  font: inherit; font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none;
  background: var(--bg); border: 1px solid var(--border-strong); border-radius: 8px;
}
.dq-week-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 12px 0; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); overflow: hidden;
}
.dq-week-stats > button {
  display: grid; gap: 1px; padding: 11px 14px; border: 0; border-right: 1px solid var(--border);
  border-radius: 0; color: inherit; background: transparent; font: inherit; text-align: left; cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s;
}
.dq-week-stats > button:last-child { border-right: 0; }
.dq-week-stats > button:hover { background: var(--bg-hover); }
.dq-week-stats > button.active { background: color-mix(in srgb, var(--accent) 9%, var(--bg-elev)); box-shadow: inset 0 -3px var(--accent); }
.dq-week-stats b { font-size: 20px; line-height: 1; }
.dq-week-stats span { color: var(--text-dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; }
.dq-week-stats .bad b { color: var(--accent); }
.dq-week-stats .good b { color: var(--success, #36865d); }
.dq-week-catalogs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; }
.dq-week-catalog-card {
  min-width: 0; padding: 12px 13px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: inherit; background: var(--bg-elev);
  font: inherit; text-align: left; cursor: pointer;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.dq-week-catalog-card:hover { background: var(--bg-hover); }
.dq-week-catalog-card.active {
  border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev));
  box-shadow: 0 3px 12px color-mix(in srgb, var(--accent) 18%, transparent);
}
.dq-week-catalog-card.has-issues { border-top: 3px solid var(--accent); padding-top: 10px; }
.dq-week-catalog-card.is-clean { border-top: 3px solid var(--success, #36865d); padding-top: 10px; }
.dq-week-catalog-name { color: var(--text-dim); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.045em; }
.dq-week-catalog-count { margin-top: 2px; font-size: 22px; font-weight: 700; line-height: 1; }
.dq-week-catalog-meta { margin-top: 5px; color: var(--text-dim); font-size: 11px; line-height: 1.3; }
.dq-week-mix { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 18px; }
.dq-week-mix-item {
  padding: 4px 8px; color: var(--text-dim); background: var(--bg-hover);
  border: 1px solid var(--border); border-radius: 20px; font: inherit; font-size: 11px;
  cursor: pointer; transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.dq-week-mix-item:hover { border-color: var(--accent); }
.dq-week-mix-item.active {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
}
.dq-week-mix-item b { color: var(--text); margin-left: 3px; }
.dq-week-filter-state {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 18px 0 4px; padding: 9px 11px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text-dim); background: var(--bg-elev); font-size: 12px;
}
.dq-week-filter-state b { color: var(--text); }
.dq-week-filter-state .mini-btn { margin: 0; white-space: nowrap; }
.dq-week-group { margin-top: 22px; }
.dq-week-group-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 7px; border-bottom: 1px solid var(--border);
}
.dq-week-group-head h4 { margin: 0; font-family: "Newsreader", serif; font-size: 18px; }
.dq-week-group-head span { color: var(--text-dim); font-size: 11.5px; }
.dq-week-group.is-clean .dq-week-group-head { border-left: 3px solid var(--success, #36865d); padding-left: 9px; }
.dq-week-group.is-empty { opacity: 0.62; }
.dq-week-games { display: grid; gap: 9px; margin-top: 9px; }
.dq-week-book {
  display: grid; grid-template-columns: 50px minmax(0, 1fr) auto; gap: 12px;
  align-items: start; padding: 12px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--bg-elev);
}
#panel-admin .dq-week-cover {
  display: block; width: 50px; height: 74px; object-fit: cover;
  border-radius: 4px; background: var(--bg-hover); box-shadow: 0 1px 4px rgba(0,0,0,.14);
}
.dq-week-book-title { font-size: 14px; font-weight: 700; line-height: 1.25; }
.dq-week-book-meta { margin-top: 2px; color: var(--text-dim); font-size: 11.5px; }
.dq-week-sources { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.dq-week-sources span {
  padding: 1px 6px; border: 1px solid var(--border); border-radius: 10px;
  color: var(--text-faint); background: var(--bg-hover); font-size: 9.5px; font-weight: 600;
}
.dq-week-issues { display: grid; gap: 6px; margin-top: 9px; }
.dq-week-issue {
  padding: 7px 9px; border-left: 3px solid var(--border-strong);
  border-radius: 0 6px 6px 0; background: var(--bg-hover);
  color: var(--text-dim); font-size: 11.5px; line-height: 1.4;
}
.dq-week-issue.error { border-left-color: var(--danger, #b54a4a); }
.dq-week-issue.warning { border-left-color: var(--warn, #d08700); }
.dq-week-issue.note { border-left-color: var(--text-faint); }
.dq-week-issue-label { margin-bottom: 2px; color: var(--text); font-weight: 700; }
.dq-week-issue b { color: var(--text); font-size: 10.5px; }
.dq-week-clean {
  display: flex; align-items: center; gap: 6px; margin-top: 9px; padding: 7px 9px;
  border-radius: 6px; color: var(--success, #36865d);
  background: color-mix(in srgb, var(--success, #36865d) 9%, transparent);
  font-size: 11.5px; font-weight: 700;
}
.dq-week-clean svg { width: 14px; height: 14px; }
.dq-week-actions { display: grid; gap: 6px; min-width: 88px; }
.dq-week-actions .mini-btn { margin: 0; white-space: nowrap; }

@media (max-width: 760px) {
  .dq-week-toolbar { align-items: stretch; flex-direction: column; padding: 15px; }
  .dq-week-picker { min-width: 0; }
  .dq-week-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dq-week-stats > button:nth-child(2) { border-right: 0; }
  .dq-week-stats > button:nth-child(-n+2) { border-bottom: 1px solid var(--border); }
  .dq-week-catalogs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dq-week-book { grid-template-columns: 42px minmax(0, 1fr); gap: 10px; padding: 10px; }
  #panel-admin .dq-week-cover { width: 42px; height: 63px; }
  .dq-week-actions { grid-column: 2; display: flex; flex-wrap: wrap; min-width: 0; }}

@media (max-width: 420px) {
  .dq-week-catalogs { grid-template-columns: 1fr; }
  .dq-week-group-head { align-items: flex-start; flex-direction: column; gap: 2px; }}

/* ===================== THIS OR THAT (web/this-or-that.js) ===================== */
/* Launch hero on the Trends tab's Trope Radar section. (The .mm-hero-card name
   predates the quiz — it survived the Matchmaker it was built for.) */
.mm-hero-card {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  flex-wrap: wrap; padding: 16px 20px; margin-bottom: 16px; border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.mm-hero-card-text .t { font-family: "Newsreader", serif; font-weight: 700; font-size: 18px; }
.mm-hero-card-text .s { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }

/* The overlay itself. */
.tot {
  background: var(--bg-panel, var(--bg)); border: 1px solid var(--border);
  border-radius: 16px; width: min(560px, calc(100vw - 24px));
  max-height: calc(100dvh - 40px); display: flex; flex-direction: column;
}
.tot-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.tot-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 16px; }
.tot-body { padding: 18px; overflow-y: auto; }
.tot-lede { margin: 0 0 14px; font-size: 14px; color: var(--text-dim); }

/* Quiz stage. */
.tot-progress { display: flex; align-items: center; gap: 5px; margin-bottom: 16px; }
.tot-progress i { flex: 1 1 auto; height: 4px; border-radius: 2px; background: var(--bg-hover); }
.tot-progress i.done { background: var(--accent); }
.tot-progress-n { margin-left: 6px; font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.tot-q { font-family: "Newsreader", serif; font-weight: 700; font-size: 21px; margin-bottom: 14px; text-wrap: balance; }
.tot-opts { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; }
.tot-opt {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 18px 12px; border-radius: 12px; border: 1px solid var(--border-strong);
  background: var(--bg-hover); color: var(--text); font-family: inherit;
  cursor: pointer; text-align: center;
}
.tot-opt:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.tot-opt-e { font-size: 30px; }
.tot-opt-t { font-weight: 700; font-size: 15px; }
.tot-opt-d { font-size: 11.5px; color: var(--text-dim); }
.tot-skips { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 14px; color: var(--text-faint); font-size: 12.5px; }
.tot-skip { background: none; border: none; color: var(--text-dim); font: inherit; font-size: 12.5px; cursor: pointer; }
.tot-skip:hover { color: var(--text); }

/* ---- Updates (header bell + change-entry overlay; web/changes.js) ---- */
.changes-btn { position: relative; }
.changes-btn[hidden] { display: none; }
.changes-count {
  position: absolute; top: -5px; right: -5px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px;
  background: var(--accent-grad); color: #fff;
  font-size: 10px; font-weight: 700;
  border: 2px solid var(--bg); /* punch it out from the header backdrop */
}
.changes-count[hidden] { display: none; } /* inline-flex would defeat `hidden` */
/* The Updates panel's modal shell. The .mm-* names are inherited from the
   retired Matchmaker (changes.js reused its burst-overlay pattern); the
   Matchmaker's removal took the shared rules with it, so they live here now
   with changes.js as their only consumer. */
.mm {
  background: var(--bg-panel, var(--bg)); border: 1px solid var(--border);
  border-radius: 16px; width: min(680px, calc(100vw - 24px));
  max-height: calc(100dvh - 40px); display: flex; flex-direction: column;
}
.mm-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.mm-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 16px; }
.mm-body { padding: 18px; overflow-y: auto; }
.mm-lede { margin: 0 0 14px; font-size: 14px; color: var(--text-dim); }
.chg .mm-body { padding-top: 14px; }
.chg-sum {
  margin: 0 0 12px; padding: 8px 12px; border-radius: 10px;
  background: var(--bg-hover); border: 1px solid var(--border);
  font-size: 12.5px; color: var(--text-dim);
}
.chg-sum b { color: var(--accent); }
.chg-cycle { margin-bottom: 6px; } /* reads as a pair with the since-you-checked box below */
.chg-entry {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 8px; border-radius: 10px; cursor: pointer;
}
.chg-entry:hover { background: var(--bg-hover); }
.chg-entry.gone { cursor: default; opacity: 0.7; }
.chg-entry.gone:hover { background: none; }
.chg-entry.unread, .chg-group-head.unread, .chg-roll.unread { position: relative; }
.chg-entry.unread::before, .chg-group-head.unread::before, .chg-roll.unread::before {
  content: ""; position: absolute; left: -2px; top: 50%; margin-top: -3px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}
.chg-cover {
  width: 34px; height: 50px; flex: none; border-radius: 4px; object-fit: cover;
  background: var(--bg-hover); border: 1px solid var(--border);
}
.chg-main { min-width: 0; }
.chg-title {
  font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chg-developer { color: var(--text-dim); font-weight: 400; }
.chg-line {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; margin-top: 2px;
}
.chg-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: none; font-size: 11px;
  border-radius: 5px; background: var(--bg-hover);
  border: 1px solid var(--border); color: var(--accent);
}
.chg-when { font-size: 11.5px; color: var(--text-dim); margin-top: 1px; }
/* The two tracks (shelf changes / follow arrivals), shown only when both have
   entries — web/changes.js segHtml. */
.chg-seg {
  display: flex; gap: 2px; padding: 3px; margin: 0 0 12px;
  background: var(--bg-hover); border: 1px solid var(--border); border-radius: 10px;
}
.chg-seg-tab {
  flex: 1; min-width: 0; padding: 8px 6px; border: 0; border-radius: 7px;
  background: none; color: var(--text-dim); cursor: pointer;
  font: inherit; font-size: 13px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.chg-seg-tab:hover { color: var(--text); }
.chg-seg-tab[aria-selected="true"] {
  background: var(--bg-elev); color: var(--text); font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.chg-seg-n { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.chg-seg-n.unread { color: var(--accent); font-weight: 600; }
.chg-pane[hidden] { display: none; }
.chg-pane .mm-lede { margin-bottom: 8px; }
/* Rolled-up rows (web/changes.js shelfGroupHtml / followGroupHtml): one row per
   book, one per follow. The ROW is the expander — it's where the history lives,
   so it gets the big target — and the jacket alone opens the book. */
.chg-roll {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 8px; border-radius: 10px; cursor: pointer;
}
.chg-roll:hover { background: var(--bg-hover); }
.chg-roll.gone { cursor: default; opacity: 0.7; }
.chg-cover-btn {
  flex: none; display: flex; padding: 0; border: 0; background: none;
  border-radius: 5px; cursor: pointer;
}
.chg-cover-btn:hover { outline: 2px solid var(--accent); outline-offset: 2px; }
.chg-expand {
  flex: none; margin-left: auto; display: flex; align-items: center;
  padding: 0; border: 0; background: none; cursor: pointer; color: inherit;
}
.chg-group-head {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 8px; border: 0; border-radius: 10px;
  background: none; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
}
.chg-group-head:hover { background: var(--bg-hover); }
/* The expander has to look like a control and the row has to say it holds more
   than it shows — a bare chevron at the right edge reads as decoration. */
.chg-chev-box {
  flex: none; margin-left: auto; width: 26px; height: 26px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-hover); border: 1px solid var(--border-strong);
  color: var(--text-dim); transition: transform 0.15s, color 0.15s, border-color 0.15s;
}
.chg-chev { width: 14px; height: 14px; }
.chg-roll:hover .chg-chev-box, .chg-group-head:hover .chg-chev-box,
.chg-expand:focus-visible .chg-chev-box {
  border-color: var(--accent); color: var(--accent);
}
.chg-more { color: var(--accent); font-weight: 600; }
.chg-roll:hover .chg-more, .chg-group-head:hover .chg-more { text-decoration: underline; }
.chg-group-covers { display: flex; flex: none; }
/* A 34px cover under -24px leaves a 10px sliver — a smudge, not a stack. The
   edge shadow is the panel's own ground, so the jackets read as separate. */
.chg-group-covers .chg-cover + .chg-cover {
  margin-left: -14px; box-shadow: -2px 0 0 var(--bg-panel, var(--bg));
}
.chg-group-head[aria-expanded="true"] > .chg-chev-box,
.chg-expand[aria-expanded="true"] > .chg-chev-box { transform: rotate(180deg); }
.chg-group-body { margin-left: 14px; padding-left: 8px; border-left: 2px solid var(--border); }
.chg-group-body[hidden] { display: none; }
/* One event inside a rolled-up book — no cover, no repeated title. */
.chg-ev { gap: 8px; padding: 7px 8px; }
.chg-ev .chg-main { flex: 1; }
.chg-ev .chg-line { margin-top: 0; }
.chg-ev-when {
  flex: none; font-size: 11.5px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
/* Divides the events the head speaks for from the ones already read. */
.chg-earlier {
  padding: 8px 8px 3px; font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-faint);
}

/* ---- Studio Planner (power-user content calendar) ---- */
.sp {
  background: var(--bg-panel, var(--bg)); border: 1px solid var(--border);
  border-radius: 16px; width: min(1080px, calc(100vw - 24px));
  max-height: calc(100dvh - 40px); display: flex; flex-direction: column;
}
.sp-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.sp-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 16px; }
.sp-body { padding: 16px 18px; overflow-y: auto; }
.sp-lede { margin: 0; font-size: 14px; color: var(--text-dim); }
.sp-muted { color: var(--text-dim); font-size: 12.5px; }

.sp-genrebar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--border);
}
.sp-genrebar-label { font-weight: 600; font-size: 13px; }
.sp-genres { display: flex; flex-wrap: wrap; gap: 6px; }
.sp-genre {
  font-size: 12px; padding: 4px 10px; border-radius: 16px; cursor: pointer;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  font-family: inherit;
}
.sp-genre-add, .sp-input {
  font-family: inherit; font-size: 12.5px; padding: 5px 8px; border-radius: 8px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
}

.sp-board {
  display: grid; grid-template-columns: repeat(7, minmax(220px, 1fr)); gap: 12px;
  overflow-x: auto;
}
@media (max-width: 900px) { .sp-board { grid-template-columns: 1fr; }}
.sp-day {
  border: 1px solid var(--border); border-radius: 12px; padding: 10px;
  display: flex; flex-direction: column; gap: 8px; background: var(--bg);
}
.sp-day-head { display: flex; align-items: center; justify-content: space-between; }
.sp-day-head h4 { margin: 0; font-size: 13.5px; font-family: "Newsreader", serif; }
.sp-link {
  background: none; border: none; color: var(--accent); cursor: pointer;
  font-family: inherit; font-size: 12px; padding: 0;
}
.sp-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
.sp-tab {
  font-size: 11px; padding: 3px 8px; border-radius: 12px; cursor: pointer;
  background: var(--bg-hover); color: var(--text-dim); border: 1px solid var(--border);
  font-family: inherit;
}
.sp-tab.on { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); font-weight: 600; }
.sp-preview-head { display: flex; flex-direction: column; }
.sp-heading { font-weight: 600; font-size: 13px; }
.sp-sub { font-size: 11.5px; color: var(--text-dim); }
.sp-covers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.sp-covers.solo { grid-template-columns: 1fr; }
.sp-cover {
  display: flex; flex-direction: column; gap: 2px; text-align: left; cursor: pointer;
  background: none; border: none; padding: 0; font-family: inherit; color: var(--text);
}
.sp-cover img { width: 100%; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 6px; border: 1px solid var(--border); }
.sp-covers.solo .sp-cover img { max-width: 150px; }
.sp-cover-t { font-size: 11px; font-weight: 600; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-cover-a { font-size: 10px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.sp-btn {
  font-size: 11.5px; padding: 5px 9px; border-radius: 8px; cursor: pointer;
  background: var(--bg-hover); color: var(--text); border: 1px solid var(--border);
  font-family: inherit; display: inline-flex; align-items: center; gap: 4px;
}
.sp-btn .icon { width: 13px; height: 13px; }
.sp-btn-go { background: var(--accent); color: #fff; border-color: var(--accent); }
.sp-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.sp-override { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.sp-search { font-family: inherit; font-size: 12px; padding: 6px 8px; border-radius: 8px; background: var(--bg); color: var(--text); border: 1px solid var(--border); }
.sp-results { display: flex; flex-direction: column; gap: 2px; max-height: 180px; overflow-y: auto; }
.sp-result { display: flex; align-items: center; gap: 6px; padding: 3px; border: none; background: none; cursor: pointer; font-family: inherit; font-size: 11.5px; color: var(--text); text-align: left; }
.sp-result img { width: 22px; height: 33px; object-fit: cover; border-radius: 3px; }
.sp-result:hover { background: var(--bg-hover); border-radius: 6px; }

.sp-editing .sp-elist { display: flex; flex-direction: column; gap: 4px; }
.sp-erow { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.sp-erow-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-star, .sp-x { background: none; border: none; cursor: pointer; color: var(--text-dim); font-size: 13px; padding: 0 2px; }
.sp-star.on { color: var(--accent); }
.sp-esep { font-size: 11px; font-weight: 600; color: var(--text-dim); margin-top: 6px; }
.sp-cform { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.sp-cform-actions { display: flex; align-items: center; gap: 8px; }
.sp-input-n { width: 80px; }
.tot-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }

/* Result stage. */
.tot-reveal-k { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
.tot-reveal-t { font-family: "Newsreader", serif; font-weight: 700; font-size: 23px; margin-top: 4px; text-wrap: balance; }
.tot-reveal-d { font-size: 13.5px; color: var(--text-dim); margin-top: 4px; }
.tot-matches-label { font-size: 11.5px; color: var(--text-dim); margin: 16px 0 8px; }
.tot-matches { display: flex; flex-direction: column; gap: 8px; }
.tot-match {
  display: flex; align-items: center; gap: 12px; padding: 8px;
  background: var(--bg-hover); border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; font-family: inherit; color: var(--text); text-align: left;
}
.tot-match img { width: 44px; height: 66px; border-radius: 5px; object-fit: cover; background: var(--bg); flex: none; }
.tot-match-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.tot-match-t { font-family: "Newsreader", serif; font-weight: 700; font-size: 15px; }
.tot-match-a { font-size: 12px; color: var(--text-dim); }
.tot-match-tropes { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.tot-match-n { font-size: 11.5px; font-weight: 700; color: var(--accent); white-space: nowrap; }
.tot-more { margin-top: 10px; }
.tot-card-wrap { margin-top: 18px; }
.tot-canvas { width: min(320px, 100%); display: block; margin: 0 auto; border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); }
.tot-card-actions { display: flex; justify-content: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.tot-card-actions .btn-ghost, .tot-card-actions .btn-primary { display: inline-flex; align-items: center; gap: 7px; }

/* ===================== COMBO EXPLORER (web/combo-explorer.js) ===================== */
.combo {
  background: var(--bg-panel, var(--bg)); border: 1px solid var(--border);
  border-radius: 16px; width: min(640px, calc(100vw - 24px));
  max-height: calc(100dvh - 40px); display: flex; flex-direction: column;
}
.combo-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.combo-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 16px; }
.combo-body { padding: 18px; overflow-y: auto; }
.combo-lede { margin: 0 0 12px; font-size: 14px; color: var(--text-dim); }
.combo-picked { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.combo-search {
  width: 100%; font-family: inherit; font-size: 13px; padding: 9px 12px; margin-bottom: 10px;
  border-radius: 11px; background: var(--bg); color: var(--text); border: 1px solid var(--border);
}
.combo-search:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.combo-chip {
  font-size: 12.5px; font-weight: 600; padding: 6px 12px; border-radius: 20px;
  background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  cursor: pointer; font-family: inherit;
}
.combo-chip-e { margin-right: 5px; }
/* The finder — the search box's listbox: the whole vocabulary grouped by
   genre (count-ranked within each), narrowed by typing, pairs-with-yours
   pinned on top mid-combo. Scrolls like a dropdown so the results below stay
   in reach. */
.combo-finder {
  display: flex; flex-direction: column; gap: 6px;
  max-height: 320px; overflow-y: auto; padding-right: 2px;
  border-bottom: 1px solid var(--border); padding-bottom: 10px;
}
.combo-sect {
  font-family: "Space Grotesk", "Inter", sans-serif; font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-faint);
  margin: 8px 2px 2px; display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
}
.combo-sect em { font-style: normal; letter-spacing: 0; text-transform: none; font-weight: 400; }
.combo-group-sect { display: flex; align-items: center; gap: 8px; margin: 8px 2px 2px; }
.combo-group-sect b { font-family: "Space Grotesk", "Inter", sans-serif; font-size: 11.5px; letter-spacing: 0.02em; }
.combo-group-sect i { flex: 1; height: 1px; background: var(--border); }
.combo-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; flex: none;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elev-2);
  cursor: pointer; font-family: inherit; color: var(--text); text-align: left;
}
.combo-row.focus {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-elev-2));
}
.combo-row-e { font-size: 17px; width: 22px; text-align: center; flex: none; }
.combo-row-main { flex: 1; min-width: 0; }
.combo-row-l { display: block; font-size: 13px; font-weight: 600; }
.combo-row-l mark { background: none; color: var(--accent); }
.combo-row-d { display: block; font-size: 10.5px; color: var(--text-faint); }
.combo-row-d .up { color: var(--good); font-weight: 700; }
.combo-row-n { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.combo-row-add {
  flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
  font-weight: 700; font-size: 14px;
}
.combo-hint { font-size: 10.5px; color: var(--text-faint); text-align: center; margin-top: 4px; flex: none; }
.combo-clear { background: none; border: none; color: var(--text-dim); font: inherit; font-size: 12px; cursor: pointer; text-decoration: underline; }
.combo-none { color: var(--text-faint); font-size: 12.5px; }
/* Follow-a-trope toggles (web/trope-follow.js) — shared by This or That's
   result row and the Combo Explorer's picked chips. Dashed = not following. */
.tfollow-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0 2px; }
.tfollow-lead { flex-basis: 100%; font-size: 11.5px; color: var(--text-dim); }
.tfollow-btn {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; padding: 5px 11px; border-radius: 20px;
  background: transparent; color: var(--text-dim);
  border: 1px dashed var(--border-strong);
  cursor: pointer; font-family: inherit;
}
.tfollow-btn b { font-weight: 700; color: var(--accent); }
.tfollow-btn:hover { border-color: var(--accent); color: var(--text); }
.tfollow-btn.active {
  border-style: solid; border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
}
.tfollow-btn.compact { width: 26px; height: 26px; justify-content: center; padding: 0; border-radius: 50%; gap: 0; }
.combo-chip-wrap { display: inline-flex; align-items: center; gap: 5px; }
.combo-callout {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 14px 0 12px; padding: 11px 14px; border-radius: 11px; font-size: 13.5px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 9%, var(--bg-elev));
}
.combo-callout.none { border-color: var(--border-strong); background: var(--bg-hover); color: var(--text-dim); }
.combo-callout.none .icon { color: var(--accent); }
.combo-badge {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 4px 11px; border-radius: 999px; white-space: nowrap;
  background: var(--accent-grad); color: #0c0d12;
}
.combo-matches { display: flex; flex-direction: column; gap: 8px; }
.combo-match {
  display: flex; align-items: center; gap: 12px; padding: 8px;
  background: var(--bg-hover); border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; font-family: inherit; color: var(--text); text-align: left;
}
.combo-match img { width: 44px; height: 66px; border-radius: 5px; object-fit: cover; background: var(--bg); flex: none; }
.combo-match-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.combo-match-t { font-family: "Newsreader", serif; font-weight: 700; font-size: 15px; }
.combo-match-a { font-size: 12px; color: var(--text-dim); }
.combo-match-extra { font-size: 11px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo-more { margin-top: 10px; }
.combo-sub { font-size: 11.5px; color: var(--text-dim); margin: 16px 0 8px; }
.combo-heat-scroll { overflow-x: auto; }
.combo-heat { border-collapse: separate; border-spacing: 3px; font-size: 11.5px; min-width: 480px; }
/* Headers wrap rather than truncate — a trope name is the whole point of the
   grid, so it never ellipsizes. The global `thead th` rule (sticky, uppercase,
   nowrap — built for the data tables) must be neutralized wholesale here, or
   its nowrap wins and the names clip at the column edge. */
.combo-heat th { font-weight: 600; color: var(--text-dim); font-size: 10.5px; padding: 3px 5px; text-align: left; line-height: 1.25; }
.combo-heat tbody th { max-width: 96px; }
.combo-heat thead th {
  position: static; background: none; border-bottom: none; cursor: default;
  text-transform: none; letter-spacing: 0; white-space: normal;
  text-align: center; max-width: 80px; vertical-align: bottom;
  padding: 3px 5px; font-size: 10.5px; color: var(--text-dim);
}
.combo-heat thead th:hover { color: var(--text-dim); }
.combo-heat td {
  min-width: 56px; height: 30px; border-radius: 6px; text-align: center;
  font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text);
  background: var(--bg-elev-2); cursor: pointer;
}
.combo-heat td.diag { cursor: default; color: var(--text-dim); }
.combo-heat td.zero { color: var(--text-faint); }
.combo-heat td.h1 { background: color-mix(in srgb, var(--accent) 9%, var(--bg-elev-2)); }
.combo-heat td.h2 { background: color-mix(in srgb, var(--accent) 18%, var(--bg-elev-2)); }
.combo-heat td.h3 { background: color-mix(in srgb, var(--accent) 30%, var(--bg-elev-2)); }
.combo-heat td.h4 { background: color-mix(in srgb, var(--accent) 44%, var(--bg-elev-2)); }
.combo-heat td.sel { outline: 2px solid var(--accent); outline-offset: -2px; }
.combo-cap { font-size: 11px; color: var(--text-faint); margin: 8px 0 0; }

/* Trope DNA card modal (launched from Home) — the shared burst-overlay card
   block as its own overlay, no picker/result stages. */
.dna-modal {
  background: var(--bg-panel, var(--bg)); border: 1px solid var(--border);
  border-radius: 16px; width: min(460px, calc(100vw - 24px));
  max-height: calc(100dvh - 40px); display: flex; flex-direction: column;
}
.dna-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.dna-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 16px; }
.dna-body { padding: 18px; overflow-y: auto; }
.dna-canvas { width: min(320px, 100%); display: block; margin: 0 auto; border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); }
.dna-card-actions { display: flex; justify-content: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.dna-card-actions .btn-ghost, .dna-card-actions .btn-primary { display: inline-flex; align-items: center; gap: 7px; }
.dna-fineprint { margin: 12px 0 0; text-align: center; color: var(--text-faint); font-size: 10.5px; }
/* Phones: the runner-up tiles (max-width 240px, nowrap titles) overflow the
   label row and wrap unevenly — stack them full-width under the label instead.
   The card buttons ride the same edge, so let them share the row width. */
@media (max-width: 640px) {
  .tot-opts { grid-template-columns: minmax(0, 1fr); }
  .tot-card-actions { gap: 8px; }
  .tot-card-actions .btn-ghost, .tot-card-actions .btn-primary {
    flex: 1 1 auto; min-width: 0; padding-left: 12px; padding-right: 12px;
  }
  .tot-actions .btn-ghost { width: 100%; }
}

/* Trope-signals "Silence a phrase" inline form (quality.js phraseKillForm). */
.dq-inline-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.dq-inline-form input[type="text"] {
  flex: 1 1 180px; min-width: 0; padding: 5px 9px; font-size: 12px;
  background: var(--bg-input, var(--bg-hover)); color: var(--text);
  border: 1px solid var(--border); border-radius: 7px;
}

/* ===================== MODAL ===================== */
/* Freeze the page behind any open overlay (modal / drawer / studio). The
   padding-right that overlay.js sets alongside this class offsets the vanished
   scrollbar so the page doesn't jump sideways. */
/* position:fixed (not just overflow:hidden — iOS ignores that on <body>) so the
   background isn't a live scroll layer that bleeds through fixed overlays on iOS
   when the keyboard opens. overlay.js stashes/restores scrollY around the class. */
body.scroll-locked { overflow: hidden; position: fixed; left: 0; right: 0; width: 100%; }

.modal-overlay {
  /* Size to the *visible* viewport (dvh), not the large iOS one — with `inset:0`
     / `vh` the overlay runs behind Safari's address bar + bottom toolbar, so the
     centered modal loses its top/bottom margin and bleeds into the chrome. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 200;
  height: 100vh; height: 100dvh;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(3px);
  animation: fade 0.18s ease;
}
.modal {
  width: 100%; max-width: 520px;
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
  /* Cap to the visible viewport and let the body scroll, so a tall form (e.g.
     Admin ▸ Hand-added's ~16 fields) never pushes the footer buttons off-screen.
     Mirrors what .modal--wide / .modal--acct already do; short modals size to
     their content and never hit the cap. */
  display: flex; flex-direction: column;
  max-height: calc(100vh - 40px); max-height: calc(100dvh - 40px);
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.modal-head h3 { font-family: "Newsreader", serif; font-size: 17px; font-weight: 600; }
.modal-x {
  width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
  background: transparent; border: none; color: var(--text-faint); font-size: 15px;
}
.modal-x:hover { background: var(--bg-hover); color: var(--text); }
.modal-body { padding: 20px; overflow-y: auto; min-height: 0; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 14px 20px; border-top: 1px solid var(--border); background: var(--bg);
  flex-shrink: 0;
}
.form-grid { display: flex; flex-direction: column; gap: 13px; }
.form-row { display: flex; flex-direction: column; gap: 5px; }
.form-row span { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.form-row .req { color: var(--danger); font-style: normal; }
.form-row input[readonly] { opacity: 0.6; cursor: not-allowed; }
.form-error { color: var(--danger); font-size: 12.5px; margin-top: 12px; min-height: 1em; }
/* Fine print under a modal form (openModal renders into the global #modalRoot,
   so the scoped .hint rules — .acct, #panel-admin — never reach it). */
.modal-body .hint { margin-top: 10px; font-size: 12px; line-height: 1.5; color: var(--text-faint); }
.confirm-text { font-size: 14px; line-height: 1.5; color: var(--text-dim); }
.confirm-text b { color: var(--text); }
/* ---- Admin ▸ Cheat Sheets: the create/edit form pickers ---- */
.form-row-split { flex-direction: row; gap: 14px; }
.form-row-split label { flex: 1; display: flex; flex-direction: column; gap: 5px; }
.form-row-split span { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.pill-live { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 700;
  color: var(--good); background: color-mix(in srgb, var(--good) 15%, transparent); }
/* Published-but-not-featured: live on the public web, absent from the app. A
   distinct hue from Featured's green so the two read apart at a glance in the
   status column, without a draft's greyed-out "nothing is happening" look. */
.pill-live.pill-web { color: var(--accent-2-text);
  background: color-mix(in srgb, var(--accent-2) 15%, transparent); }
.cs-vis .mini-btn { border-radius: 0; margin: 0; }
.cs-vis .mini-btn:first-child { border-radius: 7px 0 0 7px; }
.cs-vis .mini-btn:last-child { border-radius: 0 7px 7px 0; }
.cs-vis .mini-btn + .mini-btn { border-left: 0; }
.cs-vis .mini-btn.is-on { background: var(--bg-hover); color: var(--text); font-weight: 700;
  box-shadow: inset 0 0 0 1px var(--border-strong); }
.cs-book-meta .t { font-size: 13px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-book-meta .a { font-size: 11.5px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-book-row.is-excluded { opacity: 0.7; }
.cs-book-row.is-excluded .cs-book-meta .t { text-decoration: line-through; }
.cs-book-row.is-excluded .cs-book-mode { color: var(--danger, #d64545); border-color: var(--danger, #d64545); }

/* The games count is a button that opens the catalog's book list. */
.cat-games-link {
  background: none; border: none; padding: 0; cursor: pointer; font: inherit;
  color: var(--accent); font-weight: 600; border-bottom: 1px solid transparent;
}
.cat-games-link:hover { border-bottom-color: currentColor; }
.cat-games-link .muted { color: var(--text-faint); font-weight: 400; }

/* ---------- Wide, scrollable info modal (e.g. a catalog's book list) ---------- */
.modal--wide {
  max-width: 1100px; display: flex; flex-direction: column;
  max-height: calc(100vh - 80px); max-height: calc(100dvh - 80px);
}
.modal--wide .modal-body { overflow: auto; padding: 0; }

/* ---------- Help Center (web/help/help-center.js) ---------- */
/* Roomy two-pane overlay: a TOC rail + a scrollable content pane. On phones
   the rail becomes a sticky horizontal chip strip (see the media query). */
.helpc {
  max-width: 980px; width: 100%;
  height: min(88vh, 760px); height: min(88dvh, 760px);
  display: flex; flex-direction: column;
}
.helpc-head { gap: 14px; }
.helpc-head h3 { flex-shrink: 0; }
.helpc-search {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 7px;
  padding: 0 11px; height: 34px; border-radius: var(--radius-sm);
  background: var(--bg); border: 1px solid var(--border);
}
.helpc-search:focus-within { border-color: var(--border-strong); }
.helpc-search .icon { color: var(--text-faint); flex-shrink: 0; }
.helpc-search input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 13.5px;
}
.helpc-body { flex: 1; min-height: 0; display: flex; }

/* TOC rail */
.helpc-nav {
  flex-shrink: 0; width: 214px; overflow-y: auto;
  padding: 14px 10px; border-right: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
}
.helpc-navitem {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 10px; border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-dim); cursor: pointer;
  font: inherit; font-size: 13px; text-align: left;
}
.helpc-navitem .icon { flex-shrink: 0; color: var(--text-faint); }
.helpc-navitem:hover { background: var(--bg-hover); color: var(--text); }
.helpc-navitem.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); }
.helpc-navitem.active .icon { color: var(--accent); }
.helpc-navitem[hidden] { display: none; }

/* Content pane. min-height:0 lets it shrink below its content and scroll — in
   the mobile column layout a flex item defaults to min-height:auto, which would
   grow the pane past the 100dvh sheet and clip it (unscrollable) instead. */
.helpc-main {
  flex: 1; min-width: 0; min-height: 0; overflow-y: auto;
  -webkit-overflow-scrolling: touch; padding: 24px 28px;
  font-size: 13.5px; line-height: 1.6; color: var(--text-dim);
}
.helpc-main b { color: var(--text); font-weight: 600; }
.helpc-section { margin-bottom: 34px; scroll-margin-top: 8px; }
.helpc-section:last-of-type { margin-bottom: 8px; }
.helpc-section[hidden] { display: none; }
.helpc-kicker {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
.helpc-title {
  font-family: "Newsreader", serif; font-size: 20px; font-weight: 600;
  color: var(--text); line-height: 1.25; margin-bottom: 12px;
}
.helpc-fig { margin: 0 0 14px; }
.helpc-fig img {
  display: block; width: 100%; max-width: 100%; height: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}
.helpc-blurb { margin-bottom: 10px; }
.helpc-points { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.helpc-tour-cta { margin-top: 16px; }
.helpc-empty { color: var(--text-faint); font-size: 13px; padding: 8px 0; }

@media (max-width: 640px) {
  /* Full-screen sheet, edge to edge — the TOC rail becomes a sticky chip strip. */
  .modal-overlay:has(.helpc) { padding: 0; }
  .helpc {
    height: 100dvh; max-width: none; border-radius: 0; border: none;
    max-height: none;
  }
  .helpc-head { flex-wrap: wrap; }
  .helpc-search { order: 3; flex-basis: 100%; }
  .helpc-body { flex-direction: column; }
  .helpc-nav {
    flex-direction: row; width: auto; overflow-x: auto; overflow-y: hidden;
    gap: 8px; padding: 10px 14px; border-right: none; border-bottom: 1px solid var(--border);
    position: sticky; top: 0; z-index: 1; background: var(--bg-elev);
    scrollbar-width: none;
  }
  .helpc-nav::-webkit-scrollbar { display: none; }
  .helpc-navitem {
    flex-shrink: 0; width: auto; white-space: nowrap;
    border: 1px solid var(--border); border-radius: 999px; padding: 7px 13px;
  }
  .helpc-main { padding: 18px 16px; }}

.btn-ghost, .btn-primary {
  padding: 9px 16px; border-radius: var(--radius-sm); cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 600; transition: all 0.15s;
}
.btn-ghost { background: transparent; border: 1px solid var(--border-strong); color: var(--text-dim); }
.btn-ghost:hover { color: var(--text); }
.btn-primary { background: var(--accent-grad); border: none; color: #fff; }
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:disabled { opacity: 0.55; cursor: default; transform: none; }
.btn-danger { background: var(--danger); }

/* ===================== GUIDED TOUR ===================== */
/* Full-screen click/scroll blocker. Transparent — the dimming comes from the
   highlight ring's huge spread box-shadow, so the spotlighted control stays bright.
   Own tier (600): above the header (20), filter drawer (121) and book/modal
   overlays (200/201) the tour points at, below toasts (1000). */
.tour-blocker { position: fixed; inset: 0; z-index: 600; transition: background 0.2s ease; }
/* Centered steps (welcome / hidden target) have no highlight ring to dim the page,
   so the blocker carries the scrim itself. */
.tour-blocker--dim { background: rgba(0, 0, 0, 0.6); }
.tour-highlight {
  position: fixed; z-index: 601; pointer-events: none; border-radius: var(--radius-sm);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6), 0 0 0 2px var(--accent);
  transition: left 0.25s ease, top 0.25s ease, width 0.25s ease, height 0.25s ease, opacity 0.2s ease;
}
.tour-tip {
  position: fixed; z-index: 602; width: min(340px, calc(100vw - 32px));
  height: min(232px, calc(100dvh - 16px));
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px 18px 14px; animation: fade 0.18s ease;
  display: flex; flex-direction: column; overflow: hidden;
}
/* No target (welcome / hidden control): float the tip in the middle of the screen. */
/* animation:none so the shared `fade` keyframe (which ends on transform:none)
   can't fight the centering transform mid-animation. */
.tour-tip--center { left: 50% !important; top: 50% !important; transform: translate(-50%, -50%); animation: none; }
.tour-skip {
  position: absolute; top: 10px; right: 10px;
  width: 28px; height: 28px; border-radius: 8px; cursor: pointer;
  background: transparent; border: none; color: var(--text-faint);
  display: flex; align-items: center; justify-content: center;
}
.tour-skip:hover { background: var(--bg-hover); color: var(--text); }
.tour-skip .icon { width: 15px; height: 15px; }
.tour-title { flex: 0 0 auto; font-family: "Newsreader", serif; font-size: 16px; font-weight: 700; margin: 0 28px 8px 0; }
.tour-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  font-size: 13px; line-height: 1.55; color: var(--text-dim); margin: 0 0 16px;
}
.tour-body b { color: var(--text); font-weight: 600; }
.tour-foot { flex: 0 0 auto; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.tour-dots { min-width: 0; flex: 1 1 auto; display: flex; gap: clamp(2px, 1vw, 6px); overflow: hidden; }
.tour-dot {
  flex: 1 1 6px; min-width: 2px; max-width: 6px; height: 6px;
  border-radius: 50%; background: var(--border-strong); transition: background 0.15s;
}
.tour-dot.active { background: var(--accent); }
.tour-nav { flex: 0 0 auto; display: flex; gap: 8px; }
.tour-nav .btn-ghost, .tour-nav .btn-primary { width: 60px; padding: 7px 0; font-size: 12.5px; }
.tour-back--placeholder { visibility: hidden; pointer-events: none; }

.help-tour-cta { margin: 0 0 22px; }
.help-tour-cta .btn-primary { display: inline-flex; align-items: center; gap: 7px; }
.help-tour-cta .btn-primary .icon { width: 15px; height: 15px; }

@media (prefers-reduced-motion: reduce) {
  .tour-highlight { transition: none; }
  .tour-tip { animation: none; }}

/* ===================== LOGIN ===================== */
.login-overlay {
  position: fixed; top: 0; left: 0; right: 0; z-index: 200;
  height: 100vh; height: 100dvh;
  /* Top-anchored (not center): the live cover wall can make the marketing column
     taller than the viewport, and centering a taller-than-viewport flex child
     clips its top out of scroll reach. Anchoring at the top keeps everything
     reachable and stops the sign-in card floating halfway down. */
  display: flex; align-items: flex-start; justify-content: center;
  background: var(--bg); padding: 44px 24px; overflow-y: auto;
}
/* The developer `display: flex` above outranks the UA `[hidden] { display: none }`,
   so `overlay.hidden = true` alone wouldn't actually dismiss it after sign-in. */
.login-overlay[hidden] { display: none; }

.sign-in-btn { order: 2; white-space: nowrap; }
#signInBtn[hidden] { display: none; }
.user-menu-wrap:has(#userMenuBtn[hidden]) { display: none; }
.login-overlay.is-prompt { z-index: 700; background: rgba(0, 0, 0, .6); padding: 20px; }
.login-overlay.is-prompt .login-layout {
  display: block; position: relative; width: 100%; max-width: 420px;
  padding: 24px; margin: auto; border: 1px solid var(--border);
  border-radius: 20px; background: var(--bg-elev);
}
.login-overlay.is-prompt #landingMarketing { display: none; }
.login-overlay.is-prompt .login-card { width: 100%; padding: 0; border: 0; box-shadow: none; background: transparent; }
.login-reason { margin: 0 44px 22px 0; color: var(--text); font-size: 18px; font-weight: 650; }
.login-overlay.is-prompt .login-note { color: var(--text-dim); }
#loginDismiss { position: absolute; right: 12px; top: 12px; font-size: 24px; }
/* Split landing: marketing pitch beside the sign-in card (stacks on mobile). */
.login-layout {
  display: flex; align-items: flex-start; gap: 52px;
  width: 100%; max-width: 920px; margin: 0 auto;
}
.login-marketing {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 18px;
}
/* The pane ships statically in index.html (SEO copy) with `hidden` until auth.js
   adopts it into the overlay — display:flex above would otherwise defeat it and
   leak the pitch under the logged-in app (same fix as .login-gallery[hidden]). */
.login-marketing[hidden] { display: none; }
.login-lockup { display: flex; align-items: center; gap: 14px; }
.login-cat {
  width: 84px; height: 84px; flex-shrink: 0; object-fit: contain;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.35));
}
[data-theme="light"] .login-cat { filter: drop-shadow(0 2px 8px rgba(60, 70, 120, 0.18)); }
.login-wordmark { height: 50px; width: auto; display: block; }
.login-headline {
  font-family: "Newsreader", serif; font-weight: 700;
  font-size: 34px; line-height: 1.1; letter-spacing: -0.01em;
  color: var(--text); margin: 0;
}
.login-pitch { color: var(--text-dim); font-size: 15px; line-height: 1.6; margin: 0; max-width: 46ch; }
.login-benefits {
  list-style: none; margin: 4px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 10px;
}
.login-benefits li {
  position: relative; padding-left: 22px;
  color: var(--text-dim); font-size: 14px; line-height: 1.5;
}
.login-benefits li::before {
  content: ""; position: absolute; left: 0; top: 7px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent-grad);
}
.login-benefits strong { color: var(--text); font-weight: 600; }
.login-card {
  flex: none; width: 100%; max-width: 340px;
  display: flex; flex-direction: column; gap: 14px;
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: 16px; padding: 32px 28px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}
.login-sub { text-align: center; color: var(--text-dim); font-size: 14px; margin: 0 0 4px; }
.login-note { text-align: center; color: var(--text-faint); font-size: 12px; margin: 2px 0 0; }
.login-link { color: var(--accent); font-weight: 600; text-decoration: none; }
.login-link:hover { text-decoration: underline; }
.login-hint { color: var(--text-faint); font-size: 12px; margin: -6px 0 0; }
/* Signup fine print: passive terms/privacy acceptance under the submit button. */
.login-fineprint { text-align: center; color: var(--text-faint); font-size: 11.5px; line-height: 1.5; margin: 8px 0 0; }
.login-fineprint a { color: var(--text-dim); text-decoration: underline; }
.login-fineprint a:hover { color: var(--accent); }
.login-field em { font-style: normal; color: var(--text-faint); text-transform: none; letter-spacing: 0; font-weight: 400; }
.login-confirm { color: var(--text-dim); font-size: 14px; line-height: 1.6; margin: 0; }
.login-confirm strong { color: var(--text); word-break: break-all; }
.login-turnstile { min-height: 65px; display: flex; justify-content: center; }
.login-turnstile:empty { min-height: 0; }
/* display:flex above defeats the `hidden` attribute; the sign-in card toggles
   this container (only shown once a login challenge is required), so restore it. */
.login-turnstile[hidden] { display: none; }
/* Static crawl-path link into the public /releases hub directory. */
.login-browse { margin: 0; font-size: 14px; }
.login-browse a { color: var(--accent); font-weight: 600; text-decoration: none; }
.login-browse a:hover { text-decoration: underline; }
/* Live "coming soon" cover wall on the landing — a public proof strip. Each cover
   links to that book's public /b/<id> page (no login). */
.login-gallery { margin-top: 4px; }
.login-gallery[hidden] { display: none; }
.login-gallery-label {
  font-size: 12px; color: var(--text-faint); margin: 0 0 8px;
  text-transform: uppercase; letter-spacing: 0.06em;
}
/* Auto-fill grid: covers grow to fill the column, so they read as a real shelf
   rather than tiny thumbnails, and reflow responsively. */
.login-cover-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 12px;
}
.login-cover {
  position: relative; display: block; border-radius: 7px;
  transition: transform 0.12s ease;
}
.login-cover:hover { transform: translateY(-2px) scale(1.04); }
.login-cover img {
  width: 100%; aspect-ratio: 460 / 215; height: auto; object-fit: cover; display: block;
  border-radius: 7px; background: var(--bg-elev);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.30);
}

@media (max-width: 760px) {
  .login-layout { flex-direction: column; gap: 28px; max-width: 380px; align-items: center; }
  .login-card { order: 1; }          /* sign-in reachable without scrolling */
  .login-marketing { order: 2; }
  .login-headline { font-size: 27px; }}
@media (max-width: 420px) {
  .login-benefits { display: none; } /* trim the pitch when it crowds */
}
.login-field { display: flex; flex-direction: column; gap: 6px; }
.login-field span { font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
.login-field input {
  background: var(--bg); border: 1px solid var(--border-strong);
  border-radius: 9px; padding: 10px 12px; color: var(--text); font-size: 14px;
}
.login-field input:focus { outline: none; border-color: var(--accent); }
.login-error {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border: 1px solid var(--danger); color: var(--danger);
  border-radius: 9px; padding: 8px 11px; font-size: 13px;
}
.login-btn {
  margin-top: 4px; padding: 11px; border: none; border-radius: 9px;
  background: var(--accent-grad); color: #fff; font-size: 15px; font-weight: 600;
  cursor: pointer; transition: transform 0.15s, opacity 0.15s;
}
.login-btn:hover { transform: translateY(-1px); }
.login-btn:disabled { opacity: 0.55; cursor: default; transform: none; }
/* "Continue with Google" block (auth.js mounts it when /api/auth-config says so).
   Flex container, so the [hidden] pairing below is load-bearing (the flex rule
   would otherwise defeat the attribute — see .login-marketing[hidden]). */
.login-oauth { display: flex; flex-direction: column; gap: 14px; }
.login-oauth[hidden] { display: none; }
/* Google-brand button: white with the four-color G, per their guidelines —
   deliberately NOT the accent gradient, so it reads as Google's affordance. */
.login-btn-google {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  background: #fff; color: #1f1f1f; border: 1px solid var(--border-strong);
  text-decoration: none;
}
.login-btn-google svg { flex-shrink: 0; }
.login-divider {
  display: flex; align-items: center; gap: 10px;
  color: var(--text-faint); font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.login-divider::before, .login-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}

/* ===================== ICONS ===================== */
/* Inline SVGs from icons.js. They draw in currentColor and size to 1em of the
   context, so a glyph inherits the color and scale of whatever holds it. */
.icon {
  display: inline-block; width: 1em; height: 1em;
  vertical-align: -0.125em; flex-shrink: 0;
}
/* Buttons that pair an icon with a text label: line them up with a small gap. */
.tab, .vt-btn { display: inline-flex; align-items: center; gap: 7px; }
/* Icon-only controls that weren't already flex-centered. */
.ghost-btn, .modal-x { display: inline-flex; align-items: center; justify-content: center; }
.studio-head h3 .icon { color: var(--accent); }
.studio-help .icon { width: 16px; height: 16px; }
/* Drag a cover to reorder. `can-drag` is set only for the styles that paint the
   plain grid, so touch panning is surrendered on exactly the graphics where a
   drag means something; the grab cursor tracks the pointer being over a cover. */
.studio-canvas.can-drag { touch-action: none; }
.studio-canvas.is-grabbable { cursor: grab; }
.studio-canvas.is-dragging { cursor: grabbing; }
.studio-style.active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, var(--bg));
  color: var(--text);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* paper — Scrapbook: ruled kraft, a taped note slip, matted tilted covers */
[data-kind="paper"]::before {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom,
    transparent 0 6px,
    color-mix(in srgb, var(--style-text) 13%, transparent) 6px 7px);
}
.studio-book-pick.on {
  opacity: 1; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
#studioCountVal { color: var(--accent); font-weight: 700; }
#studioFineTune { padding-left: 12px; border-left: 2px solid var(--border); }
/* chevron points right while collapsed (the week-section convention) */
.studio-group-toggle .icon {
  width: 14px; height: 14px;
  transform: rotate(-90deg); transition: transform 0.15s ease;
}
.studio-group-toggle[aria-expanded="true"] .icon { transform: none; }
/* The lifted row follows the pointer 1:1 (no transition) and sits above the rest. */
.studio-book.dragging {
  transition: none; z-index: 2; cursor: grabbing;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); border-color: var(--accent);
}
.studio-book-meta .t {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.studio-book-meta .a {
  font-size: 11.5px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.studio-action > .icon { width: 17px; height: 17px; margin-top: 2px; }

@media (max-width: 760px) {
  .studio-action > .icon { width: 17px; height: 17px; margin-top: 0; }}
.cs-thumb.is-active { border-color: var(--accent); }
.lists-help-link .icon { width: 13px; height: 13px; }
.lists-new .icon { width: 15px; height: 15px; }
.lists-help-point > span .icon { width: 17px; height: 17px; }
.booklist-copy .icon { width: 14px; height: 14px; }
.lists-toolbar .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;
}
.lists-search .icon {
  position: absolute; left: 13px; top: 50%; width: 17px; height: 17px;
  color: var(--text-faint); transform: translateY(-50%); pointer-events: none;
}
.lists-view-toggle button.is-active {
  background: color-mix(in srgb, var(--accent) 13%, var(--bg-elev)); color: var(--accent);
  font-weight: 650;
}
.lists-empty-spark .icon { width: 25px; height: 25px; }
.lists-empty-feature-icon .icon { width: 21px; height: 21px; }

/* Existing-list cards put the book art first. The whole card opens the public
   list; only Share + Make graphic stay visible, while management lives in •••. */
.list-card {
  position: relative; display: flex; flex-direction: column; min-width: 0; cursor: pointer;
  container-type: inline-size;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 17px;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.list-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: 0 14px 32px rgba(20, 24, 55, 0.12);
}
/* An open actions menu crosses the header/visual boundary. Lift the whole card
   above neighboring cards, then keep the actions stacking context above the
   shelf face (z:7) and interactive cover layers. */
.list-card:has(.list-card-menu:not([hidden])) { z-index: 20; }
.list-card.is-editing-details,
.list-card.is-editing-details:hover { transform: none; }
/* Once a card itself becomes narrow, keep the curated row inside the shelf's
   recessed bed—not merely inside the shelf's outer silhouette. Container
   width is the relevant constraint because desktop can also be two-column. */
@container (max-width: 880px) {
  .list-collage[data-n="8"] .collage-cover:first-child,
  .list-collage[data-n="8"] .collage-cover:last-child,
  .list-collage[data-n="9"] .collage-cover:first-child,
  .list-collage[data-n="9"] .collage-cover:last-child { display: none; }
  .list-collage[data-n="8"],
  .list-collage[data-n="9"] {
    /* 106px jacket + six gaps + rotated-corner allowance stays within the
       walnut asset's inset side walls throughout the two-column squeeze. */
    --spread: clamp(32px, calc(15.67cqw - 28px), 110px);
  }
}
/* Phone-width cards: any list of six or more collapses to a centered five-jacket
   fan — five well-separated covers read better than a crush. Even counts drop one
   extra edge and re-center with --fan-mid-adjust (mid shifts by half a step). A
   476px card (small desktop / two-column) is wider than this, so it keeps every
   jacket — see the 880px rule above for the only mid-width trim. */
@container (max-width: 450px) {
  .list-collage[data-n="6"] .collage-cover:first-child,
  .list-collage[data-n="7"] .collage-cover:first-child,
  .list-collage[data-n="7"] .collage-cover:last-child,
  .list-collage[data-n="8"] .collage-cover:nth-child(-n + 2),
  .list-collage[data-n="8"] .collage-cover:last-child,
  .list-collage[data-n="9"] .collage-cover:nth-child(-n + 2),
  .list-collage[data-n="9"] .collage-cover:nth-last-child(-n + 2) {
    display: none;
  }
  .list-collage[data-n="6"],
  .list-collage[data-n="8"] { --fan-mid-adjust: 0.5; }
}
.list-card:hover .collage-cover {
  transform: translateX(calc(-50% + (var(--i) - var(--mid) - var(--fan-mid-adjust, 0)) * (var(--spread) + 2px)));
}
.list-card:hover .collage-cover:hover { z-index: 10; }
.list-card:hover .collage-cover:hover .collage-cover-face {
  z-index: 10;
  transform: scale(1.025) rotate(var(--rotation, 0deg));
  box-shadow: 0 12px 24px rgba(10, 14, 38, 0.4);
}
.list-collage[data-n="1"] .collage-cover,
.list-card:hover .list-collage[data-n="1"] .collage-cover {
  transform: translateX(-50%);
}
.list-header-pin[aria-pressed="true"] .icon { fill: currentColor; }
.list-share .icon,
.list-header-pin .icon,
.list-more .icon { width: 18px; height: 18px; }
/* Copy confirmation: the icon flips to a tick and the button goes green for a
   beat. There's no room for a "Copied" label on an icon button — the toast
   carries the words. */
.list-share.is-copied {
  color: #168a56; border-color: color-mix(in srgb, #168a56 48%, var(--border));
  background: color-mix(in srgb, #168a56 10%, var(--bg-elev));
}
/* Ten items tall on a card that can sit anywhere down the grid, so the menu
   scrolls inside itself rather than running past the bottom of a phone. */
.list-card-menu {
  position: absolute; z-index: 8; top: 42px; right: 0; width: 178px; padding: 5px;
  max-height: min(70vh, 430px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg-elev); border: 1px solid var(--border-strong); border-radius: 11px;
  box-shadow: var(--shadow);
}
.list-card-menu[hidden] { display: none; }
.list-card-menu .shelf-menu-sep { margin: 4px 6px; }
.list-card-menu button {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 9px 10px;
  background: transparent; border: 0; border-radius: 7px; color: var(--text); cursor: pointer;
  font: inherit; font-size: 12.5px; text-align: left;
}
.list-card-menu button:hover, .list-card-menu button:focus-visible { outline: none; background: var(--bg-hover); }
.list-card-menu .icon { width: 15px; height: 15px; color: var(--text-dim); }
.list-card-menu .list-menu-delete, .list-card-menu .list-menu-delete .icon { color: var(--danger, #e5484d); }
.list-card-rename-icon .icon { width: 13px; height: 13px; }
.list-card-details-cancel .icon,
.list-card-details-save .icon { width: 13px; height: 13px; }
.list-card-details-form[aria-busy="true"] .list-card-details-save .icon { animation: list-save-pulse 0.8s ease-in-out infinite alternate; }
@keyframes list-save-pulse { to { transform: scale(0.76); opacity: 0.62; }}
.list-graphic-empty .icon { width: 17px; height: 17px; color: var(--accent); }
.list-graphic-edit .icon { flex: none; width: 11px; height: 11px; }
.lists-no-results .icon { width: 18px; height: 18px; color: var(--text-faint); }
.modal--list-theme .modal-head { align-items: flex-start; }
.modal--list-theme .modal-head h3 { font-size: 20px; }
.modal--list-theme .modal-head p { margin: 3px 0 0; color: var(--text-dim); font-size: 12.5px; }
.modal--list-theme .modal-body { overflow-y: auto; }
.list-theme-check .icon { width: 14px; height: 14px; stroke-width: 3; }
.modal--list-theme .modal-foot .btn-ghost,
.modal--list-theme .modal-foot .btn-primary { min-width: 108px; }
.modal--list .modal-body { flex: 1; overflow-y: auto; }
/* The save-status line sits left of the footer buttons (the foot is flex-end). */
.modal--list .modal-foot .list-save-msg { margin-right: auto; }
.modal--list .modal-foot .btn-primary { display: inline-flex; align-items: center; gap: 7px; }
.list-book {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg);
  position: relative;
  transition: transform 0.15s ease, border-color 0.15s ease;
  animation: fade 0.15s ease;
}
.list-book:hover { border-color: var(--border-strong); }
/* The lifted row follows the pointer 1:1 (no transition) and sits above the rest. */
.list-book.dragging {
  transition: none; z-index: 2; cursor: grabbing;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); border-color: var(--accent);
}
.list-book > img, .list-book-nocover {
  width: 46px; height: 69px; flex-shrink: 0; border-radius: 5px;
  object-fit: cover; background: var(--bg-elev-2);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
.list-book-meta .t {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.list-book-meta .a {
  font-size: 11.5px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.list-save-msg.err { color: var(--danger, #e5484d); }

@media (max-width: 560px) {
  .collage-cover,
  .list-card:hover .collage-cover {
    transform: translateX(calc(-50% + (var(--i) - var(--mid) - var(--fan-mid-adjust, 0)) * var(--mobile-spread)));
  }
  .list-card:hover .collage-cover:hover .collage-cover-face {
    transform: scale(1.02) rotate(var(--rotation, 0deg));
  }
  /* Give the actual text the scarce space while editing. The count/date come
     straight back when the edit closes; keeping them here would squeeze both
     fields to roughly half the card on a phone. */
  .list-card.is-editing-details .list-card-header { grid-template-columns: minmax(0, 1fr) auto; }
  .list-card.is-editing-details .list-card-status { display: none; }
  .modal--list-theme .modal-head { padding: 14px 16px; }
  .modal--list-theme .modal-head h3 { font-size: 19px; }
  .modal--list-theme .modal-body { padding: 16px; }
  .modal--list-theme .modal-foot { flex-wrap: wrap; padding: 12px 16px; }
  .modal--list-theme .modal-foot .btn-ghost,
  .modal--list-theme .modal-foot .btn-primary { flex: 1; min-height: 40px; }}

/* ---- Invite friends modal (referral link; see web/invite.js) ---- */
.invite { display: flex; flex-direction: column; gap: 14px; }
.invite-lede { font-size: 13.5px; color: var(--text-dim); line-height: 1.5; margin: 0; }
.invite-link { display: flex; gap: 8px; }
.invite-link input {
  flex: 1; min-width: 0; padding: 9px 11px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim);
  font-size: 12.5px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.invite-link .btn-primary { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.invite-share { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; }
.invite-count { font-size: 13px; color: var(--text); margin: 0; }
.invite-loading, .invite-error { font-size: 13px; color: var(--text-dim); margin: 0; }
.invite-error { color: var(--danger, #e5484d); }

/* ---- Contact & feedback modal (avatar menu; see openContactModal in web/account.js) ---- */
.contact-box { display: flex; flex-direction: column; gap: 14px; }
.contact-lede { font-size: 13.5px; color: var(--text-dim); line-height: 1.5; margin: 0; }
/* Wraps rather than shrinks: the address makes the primary button wide, and on a
   narrow phone sheet the two buttons stack instead of squeezing the email. */
.contact-row { display: flex; flex-wrap: wrap; gap: 8px; }
.contact-row .btn-primary,
.contact-row .btn-ghost {
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
}
.contact-note { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin: 0; }
.contact-note a { color: var(--accent); }

/* ---------- Home bootstrap (early paint) ---------- */
/* Small, fixed status on the pre-hydration Home. Fixed positioning keeps its
   arrival/removal out of the Home layout, so hydration causes no shift. */
.home-boot-status {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 120; display: inline-flex; align-items: center; gap: 7px;
  margin: 0; padding: 7px 11px; transform: translateX(-50%);
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: color-mix(in srgb, var(--bg-elev) 94%, transparent);
  color: var(--text-dim); box-shadow: 0 5px 18px rgba(0, 0, 0, 0.16);
  backdrop-filter: blur(8px); font-size: 11.5px; font-weight: 600;
  line-height: 1; white-space: nowrap; pointer-events: none;
}
.home-boot-status::before {
  content: ""; width: 9px; height: 9px; flex: none; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@media (max-width: 640px) {
  .home-boot-status { bottom: calc(66px + env(safe-area-inset-bottom)); }}
@media (prefers-reduced-motion: reduce) {
  .home-boot-status::before { animation: none; border-color: var(--accent); }}
/* Non-destructive "catalog failed after Home painted" retry bar (main.js
   showCatalogRetry) — floats over the readable Home snapshot. */
.catalog-retry {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 200;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--card, var(--bg));
  color: var(--text); font-size: 13.5px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
}
.catalog-retry button {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--accent, #7c5cff); color: #fff;
  padding: 6px 14px; font-size: 13px; cursor: pointer;
}

/* ---------- Near-first tail load (Stage 3) ---------- */
/* The quiet global pill for the boot-tail window (main.js updateFeedPill):
   same fixed placement as .home-boot-status, but it must accept clicks in its
   failed state (Retry button), so no pointer-events:none on the box. */
.feed-tail-status {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 120; display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 11px; transform: translateX(-50%);
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: color-mix(in srgb, var(--bg-elev) 94%, transparent);
  color: var(--text-dim); box-shadow: 0 5px 18px rgba(0, 0, 0, 0.16);
  backdrop-filter: blur(8px); font-size: 11.5px; font-weight: 600;
  line-height: 1; white-space: nowrap;
}
.feed-tail-status button {
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--accent, #7c5cff); color: #fff;
  padding: 4px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer;
}
@media (max-width: 640px) {
  .feed-tail-status { bottom: calc(66px + env(safe-area-inset-bottom)); }}
/* Shared spinner for the pill and the pending empty states. */
.feed-spin {
  flex-shrink: 0; width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
/* The "still loading, not empty" stand-in every zero-result view paints while
   the tail is in flight (filters.pendingCatalogHTML). */
.feed-pending-lead {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 17px; color: var(--text-dim); margin-bottom: 6px;
}
@media (prefers-reduced-motion: reduce) {
  .feed-spin { animation: none; border-color: var(--accent); }}
.shelf-head-actions button .icon { width: 15px; height: 15px; }
.shelf-view-tab.active { color: var(--text); }
.shelf-view-tab.active::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 3px;
  border-radius: 3px 3px 0 0; background: var(--accent-grad);
}
.shelf-metric.active {
  border-color: color-mix(in srgb, var(--accent) 70%, var(--border));
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--accent) 4%, var(--bg-elev)),
    color-mix(in srgb, var(--accent) 1.5%, var(--bg-elev)));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 10%, transparent),
    0 10px 26px -19px color-mix(in srgb, var(--accent) 45%, transparent);
}
.shelf-metric-icon .icon { width: 17px; height: 17px; }
.shelf-board-toolbar label .icon { width: 15px; height: 15px; color: var(--text-dim); }
.shelf-board-hint .icon { width: 14px; height: 14px; color: var(--accent); }
.shelf-board-column.can-drop {
  border-color: color-mix(in srgb, var(--lane-color) 42%, var(--border));
  background: color-mix(in srgb, var(--lane-color) 4%, var(--bg-elev-2));
}
.shelf-board-column.drag-over {
  border-color: var(--lane-color);
  background: color-mix(in srgb, var(--lane-color) 10%, var(--bg-elev-2));
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--lane-color) 18%, transparent),
    inset 0 3px 0 var(--lane-color);
  transform: translateY(-1px);
}
.shelf-board-column.is-origin { background: color-mix(in srgb, var(--bg-elev-2) 68%, transparent); }
.shelf-board-column.can-drop .shelf-board-lane-title > em { opacity: 1; transform: none; }
.shelf-board-card.is-dragging {
  z-index: 8; opacity: .42; transform: rotate(-1deg) scale(.985);
}
.shelf-board-card.drop-before::before,
.shelf-board-card.drop-after::after {
  content: ""; position: absolute; z-index: 9; left: -3px; right: -3px; height: 3px;
  border-radius: 999px; background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 13%, transparent);
}
.shelf-board-card.drop-before::before { top: -5px; }
.shelf-board-card.drop-after::after { bottom: -5px; }
.shelf-board-menu-button .icon { width: 16px; height: 16px; }
.shelf-board-grip .icon { width: 13px; height: 13px; }
.shelf-board-archive .icon { width: 12px; height: 12px; vertical-align: -2px; }
.shelf-triage-close .icon, .shelf-triage-skip .icon, .shelf-triage-undo .icon { width: 15px; height: 15px; }
.shelf-triage-actions .primary, .shelf-triage-done .primary { border-color: transparent; background: var(--accent-grad); color: #fff; }
.shelf-triage-actions .danger { color: var(--danger); background: color-mix(in srgb, var(--danger) 6%, var(--bg-elev)); }
.shelf-triage-done > .icon { width: 42px; height: 42px; color: var(--ok); }

/* Keep all five status cards on one row at tablet / small-laptop widths.
   The wide layout cannot fit its icon, count, and longer labels side by side
   here, so the copy becomes a compact, vertically centered two-line block. */
@media (max-width: 760px) {
  .shell:has(.shelf-triage-overlay) { z-index: 121; }
  body:has(.shelf-triage-overlay) .topbar { z-index: 122; }
  body:has(.shelf-triage-overlay) #tabsBottom { z-index: 122; }
}
/* A Flat-covers row that has just been decided: dimmed and out of play, but left
   in the list so the page height — and the admin's scroll position — never moves. */
.dq-flat-decided { opacity: 0.55; }
.dq-flat-decided .dq-flat-fig.live img { outline-color: var(--text-faint); }
.lists-segment button.is-active { background: var(--bg-hover); color: var(--text); font-weight: 650; }
/* .btn-ghost is not a flex container and sets no gap, so an icon + label pair
   renders with the two touching. Scoped rather than fixed on .btn-ghost itself,
   which is used all over the app without an icon. */
.sheet-crumb-actions .btn-ghost {
  display: inline-flex; align-items: center; gap: 7px; padding: 8px 13px;
}
.sheet-crumb-actions .btn-ghost svg { width: 14px; height: 14px; flex: none; }

/* ============================================================
   Admin ▸ Social — the Instagram posting plan (web/tabs/admin/social.js)
   ============================================================ */
.social-strip {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-bottom: 14px;
}
.social-strip-off {
  gap: 10px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warn) 12%, var(--bg-elev));
  border: 1px solid color-mix(in srgb, var(--warn) 40%, var(--border));
  font-size: 13px; color: var(--text-dim); line-height: 1.5;
}
.social-strip-off svg { width: 18px; height: 18px; flex: none; color: var(--warn); }
.social-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--bg-elev-2); border: 1px solid var(--border); color: var(--text-dim);
}
.social-chip svg { width: 14px; height: 14px; }
.social-chip code { font-size: 11px; }
.social-chip-ok   { color: var(--good);   border-color: color-mix(in srgb, var(--good) 45%, var(--border)); }
.social-chip-warn { color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
.social-chip-fail { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }

.social-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
/* .dl-btn is the gradient primary; these are its quieter siblings. */
.dl-btn.ghost {
  background: var(--bg-elev-2); color: var(--text);
  border: 1px solid var(--border-strong); box-shadow: none;
}
.dl-btn.danger {
  background: transparent; color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
  box-shadow: none;
}
.dl-btn.ghost svg, .dl-btn.danger svg { width: 15px; height: 15px; }
.dl-btn:disabled { opacity: 0.55; cursor: default; transform: none; }

.social-dim { color: var(--text-dim); font-size: 12.5px; }
.social-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
.social-dot-draft { background: var(--text-faint); }
.social-dot-sched { background: var(--accent-2); }
.social-dot-ok    { background: var(--good); }
.social-dot-warn  { background: var(--warn); }
.social-dot-fail  { background: var(--danger); }

/* --- cockpit --- */
.social-cockpit {
  display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 22px;
  align-items: start;
}
.social-preview {
  aspect-ratio: 4 / 5; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg-elev-2);
  overflow: hidden; display: flex; align-items: center; justify-content: center;
}
.social-preview-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.social-preview-empty {
  text-align: center; padding: 20px; color: var(--text-dim); font-size: 13px;
}
.social-preview-empty svg { width: 30px; height: 30px; opacity: 0.5; margin-bottom: 8px; }
.social-preview-empty p { margin: 3px 0; }
.social-preview-actions { margin-top: 10px; display: flex; gap: 8px; }

/* The phone frame: the preview column dressed as the post Instagram will
   render — account row, graphic, caption underneath. The inner .social-preview
   drops its own frame because the phone owns it. */
.social-phone {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev); overflow: hidden;
}
.social-phone .social-preview { border: 0; border-radius: 0; }
.social-phone.is-story .social-preview {
  aspect-ratio: 9 / 16;
  background: #08090c;
}
.social-phone.is-story .social-ig-caption { display: none; }
.social-ig-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; font-size: 12.5px;
  border-bottom: 1px solid var(--border);
}
.social-ig-head .social-dim { margin-left: auto; font-size: 11.5px; }
.social-ig-avatar {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  background: linear-gradient(120deg, var(--brand-c0, var(--accent)),
                                      var(--brand-c1, var(--accent-2)));
}
.social-ig-caption {
  padding: 9px 10px 11px; font-size: 12.5px; line-height: 1.5;
  max-height: 130px; overflow-y: auto; overflow-wrap: anywhere;
}
/* The caption's newlines ARE its paragraph shape (same rule as every render
   surface of copy) — but only on the text span, or the template's own
   indentation would paint as blank lines. */
#socCaptionPreviewText { white-space: pre-line; }
.social-ig-caption:has(#socCaptionPreviewText:empty) { display: none; }

.social-compose { display: flex; flex-direction: column; gap: 12px; }
.social-compose-head {
  display: flex; align-items: center; gap: 8px; font-size: 14px;
}
.social-error {
  display: flex; align-items: flex-start; gap: 8px; font-size: 13px;
  padding: 10px 12px; border-radius: var(--radius-sm); line-height: 1.5;
  background: color-mix(in srgb, var(--danger) 10%, var(--bg-elev));
  border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--border));
  color: var(--danger);
}
.social-error svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.social-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.social-field > span {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--text-faint); font-weight: 600;
}
.social-field input, .social-field textarea, .social-field select {
  background: var(--bg-elev-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 9px 11px;
  color: var(--text); font-family: inherit; font-size: 13px; line-height: 1.55;
  width: 100%;
}
.social-field textarea { resize: vertical; }
.social-field input:focus, .social-field textarea:focus, .social-field select:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
}
.social-field small.over { color: var(--danger); font-weight: 600; }
.social-field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.social-field-row .social-field { flex: 1 1 190px; }
.social-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.social-format-check {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 11px;
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--border));
  border-radius: var(--radius-sm);
  color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--bg-elev));
  font-size: 12px; line-height: 1.45;
}
.social-format-check[hidden] { display: none; }
.social-format-icon { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.social-format-icon[hidden], .social-format-check .social-write[hidden] { display: none; }
.social-format-icon svg { width: 16px; height: 16px; display: block; }
#socFormatMessage { flex: 1 1 auto; }
.social-format-check.is-ok {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 38%, var(--border));
  background: color-mix(in srgb, var(--good) 8%, var(--bg-elev));
}
.social-format-check .social-write { flex: none; }

.social-upnext { margin-top: 26px; display: flex; flex-direction: column; gap: 6px; }
.social-upnext h4 {
  margin: 0 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--text-faint);
}
.social-up {
  display: grid; grid-template-columns: 190px minmax(0, 1fr) auto;
  gap: 12px; align-items: center; text-align: left;
  padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border);
  color: var(--text); font-family: inherit; font-size: 13px; cursor: pointer;
}
.social-up:hover { background: var(--bg-hover); }
.social-up.active { border-color: var(--accent); }
.social-up-when { color: var(--text-dim); font-size: 12px; }
.social-up-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.social-up-state { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 12px; }

/* --- calendar --- */
.social-cal-head, .social-cal {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 7px;
}
.social-cal-head div {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-faint); text-align: center; padding-bottom: 6px;
}
.social-day {
  min-height: 108px; padding: 8px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 5px;
}
.social-day.empty { background: transparent; border-style: dashed; }
.social-day.past { opacity: 0.55; }
.social-day.today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.social-day.over { background: var(--bg-hover); border-color: var(--accent-2); }
.social-day-num {
  font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.social-slot {
  display: flex; align-items: center; gap: 6px; cursor: grab;
  padding: 6px 7px; border-radius: 7px; font-size: 12px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
}
.social-slot:hover { border-color: var(--border-strong); }
.social-slot-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.social-cal-hint { margin-top: 10px; }

@media (max-width: 900px) {
  .social-cockpit { grid-template-columns: minmax(0, 1fr); }
  .social-phone { width: 100%; max-width: 340px; }
  .social-phone .social-preview { width: 100%; max-width: none; }
  .social-up { grid-template-columns: minmax(0, 1fr); gap: 4px; }}
@media (max-width: 640px) {
  .social-cal-head { display: none; }
  .social-cal { grid-template-columns: repeat(2, minmax(0, 1fr)); }}
/* Caption assist + carousel filmstrip (Admin ▸ Social). */
.social-caption-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.social-write {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--bg-elev-2); border: 1px solid var(--border-strong);
  color: var(--accent); cursor: pointer; font-family: inherit;
}
.social-write:hover:not(:disabled) { background: var(--bg-hover); }
.social-write:disabled { opacity: 0.5; cursor: default; color: var(--text-faint); }
.social-write svg { width: 14px; height: 14px; }
.social-filmstrip {
  display: flex; gap: 5px; margin-top: 8px; overflow-x: auto; padding-bottom: 4px;
}
.social-filmstrip img {
  width: 42px; aspect-ratio: 4 / 5; object-fit: cover; flex: none;
  border-radius: 4px; border: 1px solid var(--border);
}
/* Admin ▸ Social — the idea rail. */
.social-ideas-panel {
  margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border);
}
.social-ideas-panel h4 {
  margin: 0 0 12px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--text-faint);
}
.social-ideas-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px;
}
/* The variant picker: freshly painted candidates, choose one or bail. */
.social-variants-overlay {
  position: fixed; inset: 0; z-index: 300;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.social-variants {
  background: var(--bg-elev); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px 20px 20px; max-width: 880px; width: 100%;
  max-height: 92vh; overflow-y: auto;
}
.social-variants-head {
  display: flex; align-items: baseline; gap: 12px; margin-bottom: 14px;
}
.social-variants-head h4 { margin: 0; font-size: 15px; }
.social-variants-head .modal-x { margin-left: auto; }
.social-variants-note {
  display: flex; align-items: flex-start; gap: 6px; margin: 0 0 12px;
}
.social-variants-note svg { width: 14px; height: 14px; flex: none; margin-top: 2px; }
.social-variants-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
}
.social-variant {
  display: flex; flex-direction: column; gap: 7px; padding: 8px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor: pointer; color: var(--text);
  font-family: inherit; font-size: 12px; text-align: center;
}
.social-variant:hover { border-color: var(--accent); }
.social-variant img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: 7px; display: block;
}
.social-ideas {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
}
.social-idea {
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 13px 14px;
  display: flex; flex-direction: column; gap: 8px;
}
.social-idea-head { font-size: 13.5px; font-weight: 650; line-height: 1.3; }
/* The model-pitched marker: enough to tell a pitched angle from a rule's. */
.social-idea-pitched svg {
  width: 13px; height: 13px; vertical-align: -2px; color: var(--accent);
}
.social-idea-why {
  margin: 0; font-size: 12.5px; color: var(--text-dim); line-height: 1.5;
}
.social-idea-covers { display: flex; gap: 4px; flex-wrap: wrap; }
.social-idea-covers img {
  width: 26px; aspect-ratio: 2 / 3; object-fit: cover;
  border-radius: 3px; border: 1px solid var(--border);
}
.social-idea .dl-btn { align-self: flex-start; margin-top: auto; }
/* Admin ▸ Social — the prompt inspector (debug affordance). */
.social-caption-actions { display: inline-flex; gap: 6px; align-items: center; }
.social-write.ghost { color: var(--text-dim); }
.social-write.ghost:hover:not(:disabled) { color: var(--text); }
.social-prompt {
  margin-top: 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-elev-2); padding: 12px 14px;
}
.social-prompt-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--text-dim); margin-bottom: 10px;
  font-variant-numeric: tabular-nums;
}
.social-prompt-head .social-write { margin-left: auto; }
.social-prompt h5 {
  margin: 12px 0 5px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-faint);
}
.social-prompt h5:first-of-type { margin-top: 0; }
.social-prompt pre {
  margin: 0; max-height: 260px; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; font-size: 11.5px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word; color: var(--text-dim);
}

/* Most Wanted — what people are waiting for, and roughly when. The bands reuse
   .week-section wholesale (see tabs/wanted.js), so only the intro, the ordering
   control and the band heading need rules of their own. */
.wanted-frame {
  margin-top: 12px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-elev); overflow: hidden;
}
/* The toolbar is a band across the top of the frame. --bg rather than
   --bg-elev-2, which is the same pure white as --bg-elev in the light theme
   and so would not step at all there. */
.wanted-intro {
  margin: 0; padding: 14px 18px; display: flex; align-items: flex-start; gap: 16px;
  flex-wrap: wrap; justify-content: space-between;
  border-bottom: 1px solid var(--border); background: var(--bg);
}
#wantedContent { padding: 16px 18px 6px; }
/* Caption left, ordering control centre, count right — the count is the half a
   reader checks against the wall, so it sits at the far edge rather than
   trailing the sentence. */
.wanted-caption { flex: 1 1 auto; min-width: 0; color: var(--text-dim);
  font-size: 12.5px; align-self: center; }
.wanted-count { flex: 0 0 auto; color: var(--text-faint); font-size: 12px;
  align-self: center; text-align: right; font-variant-numeric: tabular-nums; }
.wanted-tools { flex: 0 0 auto; min-width: 0; display: flex;
  align-items: center; gap: 10px; flex-wrap: wrap; }

/* The ordering control. The banding IS an ordering, so these are alternatives
   rather than a sort sitting beside a grouping (tabs/wanted.js). */
.wanted-modes { display: inline-flex; border: 1px solid var(--border);
  border-radius: 999px; overflow: hidden; }
.wanted-mode { padding: 5px 13px; border: 0; background: none; color: var(--text-dim);
  font: inherit; font-size: 12.5px; cursor: pointer; white-space: nowrap; }
.wanted-mode:hover { color: var(--text); }
.wanted-mode[aria-pressed="true"] { background: var(--accent-grad); color: #fff;
  font-weight: 600; }

.wanted-band { margin-bottom: 26px; }
.wanted-band.collapsed { margin-bottom: 0; }
.wanted-band.collapsed > .week-body { display: none; }
.wanted-head h2 { font-size: 22px; }
/* Releases gives every week heading a gradient accent bar as an identity
   marker. A band already has one, and it carries meaning: the dot is the band's
   own colour, repeated on every card in it. Two markers side by side just read
   as decoration, so the generic one goes. */
.wanted-head h2::before { display: none; }
/* The dot repeats what the band says, for anyone scanning rather than reading. */
.wanted-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.wanted-dot--now { background: var(--good); }
.wanted-dot--soon { background: var(--danger); }
.wanted-dot--later { background: var(--warn); }
.wanted-dot--none { background: var(--accent); }
.wanted-sub { color: var(--text-dim); font-size: 12px; }
.wanted-more { display: flex; justify-content: center; margin: 14px 0 4px; }
/* .ghost-btn is the app's 40px ICON button — fixed size, no padding — so a text
   label in one spills straight out of its border box. This is a text button. */
.wanted-more .btn-ghost { white-space: nowrap; }
.wanted-more-rest { color: var(--text-faint); margin-left: 7px; font-weight: 500; }
/* The date line, on every card in this tab. It sits ABOVE the meta block and
   takes the auto margin off it, so it stays pinned to the bottom of the body in
   both densities — Detailed keeps the meta rows under it, Compact hides them
   (`.wk-content.compact .card-meta`) and this line becomes the last thing in
   the card rather than floating up under the byline. */
.wanted-when {
  margin-top: auto; display: flex; align-items: center; gap: 7px;
  min-width: 0; font-size: 12px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* Truncation goes on the text, not the flex row — `text-overflow` does nothing
   on a flex container, where a bare text node is an anonymous item. It is only
   a backstop: the line is built from unbreakable parts (tabs/wanted.js) and
   wraps between them rather than clipping a date's year off. */
.wanted-when-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Each part is unbreakable; the separator between them is a real text node, so
   it both reads correctly aloud and is the one place the line may wrap. */
.wanted-when-part { white-space: nowrap; }
/* The meta block carries `margin-top: auto` of its own, and flexbox splits free
   space EQUALLY between competing auto margins rather than giving it all to the
   first — which left the date line hanging halfway up the card. Hand the whole
   push to the line above, so the two bottom-align as one block. */
#panel-wanted .card-meta { margin-top: 0; }
/* The meta block's own Release row now repeats this line, and says less — the
   line above it counts the days. Hidden here only; every other shelf keeps it. */
#panel-wanted .card-meta .row-release { display: none; }

/* Zero-height marker the flat ranking appends against. */
.wanted-sentinel { height: 1px; }
/* The ranking, in the BOTTOM-left of the cover well: the genre tag already owns
   the top-left and the watch paw the top-right. Reads as a position, not a
   score — the number IS the ordering this tab is built on. */
.wanted-rank {
  position: absolute; left: 0; bottom: 0; z-index: 2;
  padding: 3px 9px 4px; border-top-right-radius: var(--radius-sm);
  background: rgba(10, 12, 18, .82); color: #fff;
  font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
/* Where a rank came from, when it did NOT come from a wishlist. A line under the
   title rather than a second number on the cover: the badge counts this list,
   and "#1 on Nintendo eShop" is a fact about the game, not a rival position. */
.wanted-src {
  font-size: 10.5px; font-weight: 600; letter-spacing: .02em;
  color: var(--accent); align-self: flex-start;
}
@media (max-width: 640px) {
  .wanted-head h2 { font-size: 19px; }
  .wanted-sub { display: none; }
  /* Below this the bar stacks: control first, then what it is showing. */
  .wanted-tools { order: 0; width: 100%; }
  .wanted-caption { order: 1; }
  .wanted-count { order: 2; text-align: left; }}
/* ── My Watchlist ───────────────────────────────────────────────────────
   One state, grouped by whether anybody has dated the game yet. A row moves
   between groups because the catalogue changed, never because the reader
   filed it (web/tabs/watchlist.js). */
#panel-shelf { max-width: 1040px; margin: 0 auto; }
.wl-frame { margin-top: 12px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--bg-elev); overflow: hidden; }
.wl-tools { display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  padding: 12px 15px; border-bottom: 1px solid var(--border);
  background: var(--bg); }
/* Cap the search so the count keeps its place instead of being pushed off the
   right edge — the row wraps rather than clips at narrow widths. */
.wl-search { flex: 1 1 180px; max-width: 520px; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-elev); color: var(--text-dim); }
.wl-search input { flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--text); font: inherit; font-size: 14px; }
.wl-tools select { padding: 7px 11px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-elev); color: var(--text); font: inherit; font-size: 14px; }
.wl-count { flex: none; margin-left: auto; color: var(--text-dim); font-size: 12px; font-variant-numeric: tabular-nums; }

.wl-bulk { display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  padding: 10px 15px; border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--accent) 14%, transparent); font-size: 14px; }
.wl-bulk-act { padding: 5px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-elev); color: var(--text); font: inherit; font-size: 13px;
  cursor: pointer; }
.wl-bulk-act:hover { border-color: var(--accent); color: var(--accent); }

.wl-group { border-top: 1px solid var(--border); }
.wl-frame > .wl-group:first-child { border-top: 0; }
/* A flat order has no headings, so its first row owns the top edge. */
.wl-flat > .wl-row:first-child { border-top: 0; }
.wl-ghead { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 11px 15px; background: var(--bg); border: 0; color: inherit;
  cursor: pointer; }
.wl-ghead h3 { margin: 0; font-size: 14px; font-weight: 700; }
.wl-gn { color: var(--text-dim); font-size: 12px; font-variant-numeric: tabular-nums; }
.wl-gsub { color: var(--text-dim); font-size: 12px; }
.wl-chev { margin-left: auto; color: var(--text-faint); transition: transform .15s; }
.wl-group[data-expanded="false"] .wl-chev { transform: rotate(-90deg); }
.wl-group[data-expanded="false"] .wl-rows { display: none; }
.wl-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.wl-dot--out { background: var(--good); }
.wl-dot--soon { background: var(--danger); }
.wl-dot--dated { background: var(--warn); }
.wl-dot--undated { background: var(--accent); }

.wl-row { display: grid; grid-template-columns: auto 96px minmax(0, 1fr) auto auto;
  gap: 13px; align-items: center; padding: 9px 15px; border-top: 1px solid var(--border); }
.wl-row:first-child { border-top: 0; }
.wl-row:hover { background: var(--bg-hover); }
.wl-pick { width: 18px; height: 18px; border: 1.5px solid var(--border); border-radius: 5px;
  display: flex; align-items: center; justify-content: center; background: var(--bg-elev);
  color: var(--bg); cursor: pointer; flex: none; padding: 0; }
.wl-row[data-picked="1"] .wl-pick { background: var(--accent); border-color: var(--accent); }
.wl-art { width: 96px; aspect-ratio: 260 / 122; border-radius: 4px; overflow: hidden;
  background: var(--border); border: 0; padding: 0; cursor: pointer; flex: none; }
.wl-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wl-name { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.wl-title { text-align: left; padding: 0; border: 0; background: none; color: inherit;
  font: inherit; font-size: 16px; font-weight: 700; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-by { color: var(--text-dim); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The dot beside it carries the state in colour; the words carry it in words.
   Tinting the text as well pushed the amber and green tones under 4.5:1 at this
   size on the light ground — and said the same thing twice. */
/* The dot beside it carries the state in colour; the words carry it in words.
   Tinting the text as well pushed the amber and green tones under 4.5:1 at this
   size on the light ground — and said the same thing twice. */
.wl-when { display: flex; align-items: center; gap: 6px; font-size: 13px;
  color: var(--text-dim); }
.wl-when .wl-dot { width: 7px; height: 7px; }
.wl-note-chip { align-self: flex-start; margin-top: 4px; max-width: 100%; padding: 2px 8px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--bg-elev);
  color: var(--text-dim); font: inherit; font-size: 12px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Off is an invitation, on is a statement — so only the ON state gets the pill.
   Giving both the same outlined chip made every unmarked row read as though it
   were already preordered, which is the opposite of what it says. */
/* Off is an invitation, on is a statement — so only the ON state wears the
   pill. Giving both the same outlined chip made every unmarked row read as
   though it were already preordered. Reveal-on-hover would have fixed the look
   and hidden the feature from every touch device, so the off state stays
   visible and just gets out of the way. */
.wl-pre { padding: 5px 11px; border: 1px solid transparent; border-radius: 999px;
  background: none; color: var(--text-dim); font: inherit; font-size: 12px;
  white-space: nowrap; cursor: pointer; }
.wl-pre:hover { border-color: var(--warn); color: var(--warn);
  background: var(--bg-elev); }
.wl-pre[aria-pressed="true"] { border-color: var(--warn); color: var(--warn);
  background: color-mix(in srgb, var(--warn) 16%, transparent); }
.wl-acts { display: flex; gap: 2px; }
.wl-act { padding: 6px; border: 0; border-radius: 6px; background: none;
  color: var(--text-faint); cursor: pointer; line-height: 0; }
.wl-act:hover { color: var(--text); background: var(--bg-hover); }
.wl-act--drop:hover { color: var(--danger); }
.wl-note-edit { grid-column: 3 / -1; display: flex; gap: 7px; align-items: center;
  padding-bottom: 4px; }
.wl-note-edit input { flex: 1; min-width: 0; padding: 6px 10px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--bg-elev); color: var(--text); font: inherit;
  font-size: 13px; }
.wl-note-save, .wl-note-cancel { padding: 6px 11px; border: 1px solid var(--border);
  border-radius: 999px; background: var(--bg-elev); color: var(--text); font: inherit;
  font-size: 13px; cursor: pointer; }

.wl-blank { padding: 34px 6px 20px; text-align: center; }
.wl-blank h2 { margin: 0; font-size: 20px; }
.wl-blank p { margin: 8px auto 0; max-width: 52ch; color: var(--text-dim); font-size: 14px; }
.wl-sugg-wrap { margin-top: 24px; text-align: left; }
.wl-sugg-wrap h4, .wl-thin h4 { margin: 0 0 10px; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-dim); font-weight: 600; }
.wl-thin { margin: 15px; padding: 14px; border: 1px dashed var(--border);
  border-radius: var(--radius-sm); }
.wl-thin p { margin: 0 0 12px; color: var(--text-dim); font-size: 14px; }
.wl-sugg-grid { display: grid; gap: 11px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.wl-sugg { display: flex; flex-direction: column; border: 1px solid var(--border);
  border-radius: 10px; overflow: hidden; background: var(--bg-elev); }
.wl-sugg-art { aspect-ratio: 260 / 122; background: var(--border); border: 0;
  padding: 0; width: 100%; cursor: pointer; display: block; }
.wl-sugg-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wl-sugg-body { padding: 9px 11px; display: flex; flex-direction: column; gap: 2px;
  min-width: 0; flex: 1; }
.wl-sugg-title { text-align: left; padding: 0; border: 0; background: none;
  color: inherit; font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-sugg-title:hover { text-decoration: underline; }
.wl-sugg-when { color: var(--text-dim); font-size: 12px; }
.wl-sugg-add { border: 0; border-top: 1px solid var(--border); padding: 8px;
  background: none; color: var(--text-dim); font: inherit; font-size: 13px; cursor: pointer; }
.wl-sugg-add:hover { color: var(--accent); }

@media (max-width: 640px) {
  /* Four columns, two rows: identity across the top with the actions pinned
     right, the preorder chip tucked underneath. Letting the chip span the row
     pushes the actions onto a line of their own at the far left. */
  .wl-row { grid-template-columns: auto 72px minmax(0, 1fr) auto; gap: 6px 10px; }
  .wl-art { width: 72px; }
  .wl-pick, .wl-art, .wl-name, .wl-acts { grid-row: 1; }
  .wl-acts { grid-column: 4; align-self: start; }
  .wl-pre { grid-row: 2; grid-column: 3 / -1; justify-self: start; }
  .wl-row > span:empty { display: none; }
  .wl-note-edit { grid-column: 1 / -1; }}
