/* Philosopheed — dashboard styles. Palette per dataviz reference instance. */

/* Computer Modern (the LaTeX face), vendored; Century Schoolbook preferred
   where installed. UI chrome stays in the system sans; titles/headings and
   abstracts wear the serif. */
@font-face {
  font-family: "CMU Serif";
  src: url("../vendor/cmu-serif-500-roman.woff2") format("woff2");
  font-weight: 400 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "CMU Serif";
  src: url("../vendor/cmu-serif-700-roman.woff2") format("woff2");
  font-weight: 600 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "CMU Serif";
  src: url("../vendor/cmu-serif-500-italic.woff2") format("woff2");
  font-weight: 400 500; font-style: italic; font-display: swap;
}

:root {
  --page: #f9f9f7;
  --surface: #fcfcfb;
  --ink: #0b0b0b;
  --ink-2: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);
  --accent: #2a78d6;       /* series-1 blue */
  --accent-soft: #cde2fb;  /* blue-100 */
  --si: #4a3aa7;           /* violet — special-issue badge */
  --gold: #eda100;         /* rank badges top-3 */
  --shadow: 0 8px 30px rgba(11, 11, 11, 0.12);
  --serif: "Century Schoolbook", "New Century Schoolbook", "CMU Serif",
           Cambria, Georgia, serif;
  --zoom: 1;             /* card-grid zoom-out control */
  --card-min-w: 420px;   /* jgrid column minmax */
  --card-max-h: 430px;   /* jbody scroll cap (collapsed cards) */
  --feedwidth: 1380px;   /* feed container max-width (Display popover) */
  --page-edge: rgba(11, 11, 11, 0.07); /* book-card page-edge hairlines */
}
@media (prefers-color-scheme: dark) {
  :root {
    --page: #0d0d0d;
    --surface: #1a1a19;
    --ink: #ffffff;
    --ink-2: #c3c2b7;
    --muted: #898781;
    --grid: #2c2c2a;
    --baseline: #383835;
    --border: rgba(255, 255, 255, 0.10);
    --accent: #3987e5;
    --accent-soft: #10345c;
    --si: #9085e9;
    --gold: #c98500;
    --shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
    --page-edge: rgba(255, 255, 255, 0.06);
  }
}
:root[data-theme="light"] {
  --page: #f9f9f7; --surface: #fcfcfb; --ink: #0b0b0b; --ink-2: #52514e;
  --muted: #898781; --grid: #e1e0d9; --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10); --accent: #2a78d6; --accent-soft: #cde2fb;
  --si: #4a3aa7; --gold: #eda100; --shadow: 0 8px 30px rgba(11, 11, 11, 0.12);
  --page-edge: rgba(11, 11, 11, 0.07);
}
:root[data-theme="dark"] {
  --page: #0d0d0d; --surface: #1a1a19; --ink: #ffffff; --ink-2: #c3c2b7;
  --muted: #898781; --grid: #2c2c2a; --baseline: #383835;
  --border: rgba(255, 255, 255, 0.10); --accent: #3987e5; --accent-soft: #10345c;
  --si: #9085e9; --gold: #c98500; --shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  --page-edge: rgba(255, 255, 255, 0.06);
}

/* Dark-mode scrollbars — page, card lists, popovers, modals. Firefox via
   scrollbar-color, Chromium/WebKit via the ::-webkit-scrollbar* pseudo
   elements; both scoped to dark mode only (explicit data-theme="dark", or
   system dark with no explicit override — same scoping the colour
   variables above use, so an explicit ?data-theme="light" always wins). */
:root[data-theme="dark"] { scrollbar-color: #4a4a46 #1a1a19; }
:root[data-theme="dark"] ::-webkit-scrollbar { width: 12px; height: 12px; }
:root[data-theme="dark"] ::-webkit-scrollbar-track { background: #1a1a19; }
:root[data-theme="dark"] ::-webkit-scrollbar-thumb { background: #4a4a46; border-radius: 8px; border: 2px solid #1a1a19; }
:root[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: #5c5c57; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { scrollbar-color: #4a4a46 #1a1a19; }
  :root:not([data-theme="light"]) ::-webkit-scrollbar { width: 12px; height: 12px; }
  :root:not([data-theme="light"]) ::-webkit-scrollbar-track { background: #1a1a19; }
  :root:not([data-theme="light"]) ::-webkit-scrollbar-thumb { background: #4a4a46; border-radius: 8px; border: 2px solid #1a1a19; }
  :root:not([data-theme="light"]) ::-webkit-scrollbar-thumb:hover { background: #5c5c57; }
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--page);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font: inherit; color: inherit; cursor: pointer; }

.wrap { max-width: var(--feedwidth, 1380px); margin: 0 auto; padding: 0 24px; }
/* Feed width presets (Display popover; replaces the old Wide toggle) */
body[data-feedwidth="narrow"] { --feedwidth: 900px; }
body[data-feedwidth="default"] { --feedwidth: 1380px; }
body[data-feedwidth="wide"] { --feedwidth: 1760px; }
body[data-feedwidth="max"] { --feedwidth: none; }

/* ---------------------------------------------------------------- header */
header.site {
  position: sticky; top: 0; z-index: 40;
  background: var(--page);
  border-bottom: 1px solid var(--grid);
}
.site-row {
  display: flex; align-items: center; gap: 16px;
  padding: 10px 0; flex-wrap: wrap;
}
.brand { display: flex; align-items: baseline; gap: 10px; margin-right: 8px; }
.brand h1 { font-size: 23px; margin: 0; letter-spacing: -0.01em; font-family: var(--serif); }
.brand h1 .feed { color: var(--accent); }
.brand .tag { color: var(--muted); font-size: 12.5px; }

.seg {
  display: inline-flex; border: 1px solid var(--baseline);
  border-radius: 8px; overflow: hidden; background: var(--surface);
}
.seg button {
  border: 0; background: none; padding: 6px 13px;
  color: var(--ink-2); border-right: 1px solid var(--grid);
  white-space: nowrap;
}
.seg button:last-child { border-right: 0; }
.seg button.on { background: var(--accent); color: #fff; }
.seg button:not(.on):hover { background: var(--accent-soft); }
.seg.small button { padding: 4px 10px; font-size: 13px; }

.spacer { flex: 1; }
.searchbox {
  min-width: 260px; flex: 0 1 340px;
  padding: 7px 12px; border: 1px solid var(--baseline); border-radius: 8px;
  background: var(--surface); color: var(--ink); font: inherit;
}
.searchbox:focus { outline: 2px solid var(--accent); border-color: transparent; }
.iconbtn {
  border: 1px solid var(--baseline); background: var(--surface);
  border-radius: 8px; padding: 6px 10px; color: var(--ink-2);
}
.iconbtn:hover { background: var(--accent-soft); }
.iconbtn.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.iconbtn.on:hover { background: var(--accent); opacity: 0.92; }

/* mobile search-icon toggle button (v9) — desktop always shows the real
   .searchbox inline instead; see the mobile breakpoint below for the swap
   and the body.search-open reveal. */
.search-icon-btn { display: none; align-items: center; justify-content: center; }
.search-icon-btn.on, .search-icon-btn[aria-expanded="true"] { background: var(--accent-soft); }

/* display settings popover */
.display-wrap { position: relative; }
.popover {
  display: none; position: absolute; top: calc(100% + 8px); right: 0; z-index: 50;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 14px 16px; width: 320px;
}
.popover.show { display: block; }
.popover label { display: block; font-size: 12.5px; color: var(--ink-2); margin-bottom: 10px; }
.popover label:last-of-type { margin-bottom: 12px; }
.popover label span {
  float: right; font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600;
}
.popover input[type="range"] {
  width: 100%; accent-color: var(--accent); margin-top: 4px; display: block;
}
.popover-reset { width: 100%; }
.popover-label { font-size: 12.5px; color: var(--ink-2); margin: 2px 0 6px; }
.popover-seg { display: flex; width: 100%; margin-bottom: 12px; }
.popover-seg button { flex: 1; text-align: center; }

/* favourites popover */
.favorites-popover { width: 300px; max-height: 70vh; flex-direction: column; }
.favorites-popover.show { display: flex; }
.popover-head { font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 2px; }
.popover-sub { font-weight: 400; color: var(--muted); font-size: 11.5px; margin-left: 6px; }
.fav-list { overflow-y: auto; max-height: 320px; margin: 8px 0 10px; scrollbar-width: thin; }
/* NB: .fav-row and .fav-persist-row are <label> elements inside .popover, so
   the generic ".popover label { display: block }" rule above (meant for the
   Display popover's slider labels) outranks a plain ".fav-row" class alone
   on specificity — match its shape ("popover label.foo") to reliably win. */
.popover label.fav-row {
  display: flex; align-items: center; gap: 8px; padding: 5px 2px;
  border-bottom: 1px solid var(--grid); font-size: 13px; margin-bottom: 0;
}
.fav-row:last-child { border-bottom: 0; }
.fav-row input[type="checkbox"] { accent-color: var(--accent); flex: 0 0 auto; }
.fav-row .fav-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-2); text-align: left; float: none; font-weight: 400;
}
.fav-row.checked .fav-name { color: var(--ink); font-weight: 600; }
.fav-row .fav-rank {
  flex: 0 0 auto; min-width: 20px; text-align: center; font-variant-numeric: tabular-nums;
  color: var(--accent); font-weight: 700; font-size: 12.5px; float: none;
}
.fav-empty { color: var(--muted); font-size: 12.5px; padding: 10px 2px; }

/* favourites re-order — drag-to-rank list of the ticked favourites (pointer
   events; touch-capable), replacing the checkbox list until "Done" */
.fav-reorder-list { overflow-y: auto; max-height: 320px; margin: 8px 0 10px; scrollbar-width: thin; }
.fav-reorder-row {
  display: flex; align-items: center; gap: 8px; padding: 6px 4px;
  border-bottom: 1px solid var(--grid); font-size: 13px;
  background: var(--surface); user-select: none; cursor: grab; touch-action: none;
}
.fav-reorder-row:last-child { border-bottom: 0; }
.fav-reorder-row.dragging { opacity: 0.65; background: var(--accent-soft); cursor: grabbing; position: relative; z-index: 5; }
.fav-drag-handle { color: var(--muted); font-size: 15px; flex: 0 0 auto; }
.fav-reorder-row .fav-rank {
  flex: 0 0 auto; min-width: 20px; text-align: center; font-variant-numeric: tabular-nums;
  color: var(--accent); font-weight: 700; font-size: 12.5px;
}
.fav-reorder-row .fav-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-weight: 600; }

.cov-note {
  display: none; color: var(--muted); font-size: 12px; padding: 6px 0 0; line-height: 1.4;
}
.cov-note.show { display: block; }

.yearpick { display: none; align-items: center; gap: 8px; }
.yearpick.show { display: inline-flex; }
.yearpick input[type="number"] {
  width: 84px; padding: 5px 8px; border: 1px solid var(--baseline); border-radius: 8px;
  background: var(--surface); color: var(--ink); font: inherit; font-variant-numeric: tabular-nums;
}
.yearpick input[type="number"]:focus { outline: 2px solid var(--accent); border-color: transparent; }

/* custom time window ("Custom…") — amount + unit, styled to match .yearpick */
.custompick { display: none; align-items: center; gap: 8px; flex-wrap: wrap; }
.custompick.show { display: inline-flex; }
.custompick input[type="number"] {
  width: 64px; padding: 5px 8px; border: 1px solid var(--baseline); border-radius: 8px;
  background: var(--surface); color: var(--ink); font: inherit; font-variant-numeric: tabular-nums;
}
.custompick select {
  padding: 5px 8px; border: 1px solid var(--baseline); border-radius: 8px;
  background: var(--surface); color: var(--ink); font: inherit;
}
.custompick input[type="number"]:focus, .custompick select:focus {
  outline: 2px solid var(--accent); border-color: transparent;
}
.count-note { color: var(--muted); font-size: 12.5px; margin-left: auto; flex: 0 0 auto; white-space: nowrap; }

/* ------------------------------------------------------------- filter chips (v9) --
   Row 2 of the header: View / Subfield / Ranking / Window as pill-shaped
   "chip" buttons (uppercase key + current value + caret), each opening an
   attached popover menu of options. One menu open at a time — see the
   shared openPopover()/closeAllPopovers() plumbing in app.js, which also
   governs the Favourites/Display/⋯ popovers so everything header-related
   shares one mutual-exclusion + Esc/click-outside mechanism. */
.chiprow { display: flex; align-items: center; gap: 8px; padding: 8px 0 10px; flex-wrap: wrap; }
.chip-wrap { position: relative; }
.chip-btn {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--baseline); border-radius: 999px; background: var(--surface);
  padding: 5px 12px; color: var(--ink); font-size: 12.5px; white-space: nowrap;
}
.chip-btn:hover { background: var(--accent-soft); }
.chip-btn[aria-expanded="true"] { border-color: var(--accent); }
.chip-key {
  color: var(--muted); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.chip-val { font-weight: 600; }
.chip-caret { color: var(--muted); font-size: 10px; }

.chip-menu {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 55;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; min-width: 210px;
}
.chip-menu.show { display: block; }
.chip-menu-item {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  border-radius: 6px; padding: 8px 10px; font-size: 13.5px; color: var(--ink-2); white-space: nowrap;
}
.chip-menu-item:hover { background: var(--accent-soft); }
.chip-menu-item.on { background: var(--accent); color: #fff; }
.chip-menu-caption {
  color: var(--muted); font-size: 11.5px; padding: 8px 10px 4px; margin-top: 4px;
  border-top: 1px solid var(--grid);
}
.chip-menu .yearpick, .chip-menu .custompick { padding: 4px 10px 8px; }
/* "Since last visit" window preset (v11) — disabled + a muted hint when
   there's no eligible baseline yet (first visit / too fresh / safety-valve
   reset — see computeVisitBaseline() in app.js). */
.chip-menu-item:disabled, .chip-menu-item.disabled {
  color: var(--muted); cursor: default; opacity: 0.75;
}
.chip-menu-item:disabled:hover, .chip-menu-item.disabled:hover { background: none; }
.chip-menu-hint { display: block; font-size: 10.5px; color: var(--muted); font-weight: 400; margin-top: 1px; }

/* "⋯" overflow menu — always holds Theme + About; on mobile it additionally
   picks up Favourites/Display/3D (their OWN standalone header buttons hide
   below the breakpoint — see mobile rules) via .mobile-only-item. */
.more-menu { min-width: 190px; padding: 6px; }
.more-menu .menu-item {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  border-radius: 6px; padding: 9px 12px; font-size: 13.5px; color: var(--ink-2); white-space: nowrap;
}
.more-menu .menu-item:hover { background: var(--accent-soft); }
/* higher specificity than ".more-menu .menu-item" above (which also sets
   display:block) so this hide actually wins on desktop regardless of
   source order — a plain ".mobile-only-item{display:none}" here would
   silently lose that specificity fight and never hide anything. */
.more-menu .menu-item.mobile-only-item { display: none; }

/* ------------------------------------------------------------------ main */
main { padding: 18px 0 80px; }
#content { zoom: var(--zoom, 1); }
.status { color: var(--muted); padding: 30px 0; text-align: center; }

/* journal cards (venue view) — responsive grid, 3-up on wide screens. The
   minmax() floor is clamped to the container width via min(...,100%) (task
   3/v9) so a low card-width slider value (down to 160px) can still produce
   multiple columns on a narrow phone instead of forcing a fixed 1fr. */
.jgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--card-min-w, 420px), 100%), 1fr));
  gap: 14px;
  align-items: start;
}
.jcard, .tcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; overflow: hidden; position: relative;
  container-type: inline-size; /* narrow-card tightening below reacts to the CARD's own width, not the viewport */
}
.jcard.expanded { grid-column: 1 / -1; }

/* Book-style cards (default): coloured spine on the LEFT edge + a few
   layered page-edge hairlines along the top, evoking a thick journal issue
   seen face-on. Pure CSS (gradients only, no images) so it stays performant
   across dozens of cards. Basic style below reverts to a flat top bar. */
body[data-cardstyle="basic"] .jcard, body[data-cardstyle="basic"] .tcard {
  border-top: 4px solid var(--jc, var(--accent));
}
body:not([data-cardstyle="basic"]) .jcard,
body:not([data-cardstyle="basic"]) .tcard {
  border-left: 10px solid var(--jc, var(--accent));
  border-top: 1px solid var(--border);
}
body:not([data-cardstyle="basic"]) .jcard::before,
body:not([data-cardstyle="basic"]) .tcard::before {
  content: ""; position: absolute; top: 0; left: 10px; right: 0; height: 7px;
  background: repeating-linear-gradient(to bottom, var(--page-edge) 0 1px, transparent 1px 2px);
  pointer-events: none;
}
.jhead {
  display: flex; align-items: center; gap: 10px; padding: 8px 15px 6px;
  user-select: none;
}
.rankbadge {
  min-width: 32px; height: 32px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 14.5px;
  background: var(--jc, var(--accent)); color: var(--jc-text, #fff);
  font-variant-numeric: tabular-nums;
}
.rankbadge.unranked { background: var(--grid); color: var(--muted); font-size: 12px; }
.jname { font-weight: 700; font-size: 16px; line-height: 1.25; font-family: var(--serif); }
.jmeta { color: var(--muted); font-size: 12px; }
.jceased { color: var(--muted); font-size: 11px; border: 1px solid var(--baseline); border-radius: 5px; padding: 0 5px; }
/* .jright stacks two rows — [count + "View all"] then the sparkline below —
   so "View all" sits inline next to the count instead of directly on top of
   the sparkline (it used to stack as a 3rd row right above the graph, which
   read as crowding/overlapping it on narrower cards). jright-top wraps
   (v11) — without a wrap allowance here, its own natural (unwrapped) width
   becomes a hard floor on .jright's min-content size that .jhead's flex
   layout won't shrink below, so an added "+N new" chip (v11) can push
   "View all" straight past the card's right edge, silently clipped by the
   card's own overflow:hidden rather than actually appearing. Wrapping lets
   it drop to a second line instead at normal desktop card widths, well
   before the narrow-card container query below kicks in. */
.jright { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.jright-top { display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap; justify-content: flex-end; }
.viewall {
  border: 1px solid var(--baseline); background: var(--surface); color: var(--ink-2);
  border-radius: 6px; padding: 2px 8px; font-size: 11px; white-space: nowrap;
}
.viewall:hover { background: var(--accent-soft); }
.jcount { color: var(--ink-2); font-size: 12px; white-space: nowrap; }
.jcount b { font-size: 15px; color: var(--ink); }
/* "+N new" glanceable count (v11) — journal card headers only, next to the
   "N in window" count; absent entirely when there's nothing new (no chip,
   no layout shift). */
.newchip {
  display: inline-flex; align-items: center; background: var(--accent); color: #fff;
  border-radius: 999px; padding: 1px 8px; font-size: 10.5px; font-weight: 700;
  white-space: nowrap; letter-spacing: 0.02em;
}
.spark { display: block; }
.spark rect { fill: var(--jc, var(--accent)); }

.jbody {
  border-top: 1px solid var(--grid); padding: 2px 15px 10px;
  max-height: var(--card-max-h, 430px); overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin;
}
/* Expanded cards keep an internal scroll cap (not "none") — otherwise a
   journal with thousands of papers in the window (e.g. Synthese, 5-yr) balloons
   the whole page and pushes the Collapse control far below the fold. */
.jcard.expanded .jbody { max-height: min(72vh, 820px); overflow-y: auto; }
.paper .sichip {
  display: inline-block; background: var(--si); color: #fff; font-size: 9.5px;
  font-weight: 700; border-radius: 4px; padding: 0 5px; margin-right: 5px;
  vertical-align: 2px; letter-spacing: 0.03em;
}

/* narrow-card degrade (task 3/v9) — a low Card-width slider value (down to
   160px) can now put 2+ columns of cards on a phone; at ~210px and below the
   default paddings/font sizes start to crowd, so tighten the same way the
   640px viewport breakpoint already does for phones, but keyed to the
   CARD's own width via a container query (not the viewport) so it also
   applies on wide desktop viewports if the slider is pushed low there. */
@container (max-width: 210px) {
  /* the head's 3 pieces (rank badge, name, count+View-all+sparkline) don't
     all fit on one line this narrow — let .jright wrap onto its own row
     (full width, left-aligned) instead of getting clipped by the card's
     overflow:hidden; the sparkline is dropped entirely at this width (least
     essential info, and its own fixed SVG width doesn't shrink). */
  .jhead { padding: 6px 10px 4px; gap: 4px 6px; flex-wrap: wrap; }
  .jname, .tcard .jname { font-size: 13px; }
  .jmeta { font-size: 10px; }
  .rankbadge { min-width: 24px; height: 24px; font-size: 11.5px; border-radius: 7px; }
  .jright { flex: 1 1 100%; margin-left: 0; align-items: flex-start; gap: 2px; }
  .jright-top { gap: 4px 6px; flex-wrap: wrap; }
  .spark { display: none; }
  .jcount { font-size: 10.5px; }
  .jcount b { font-size: 12.5px; }
  .newchip { font-size: 9.5px; padding: 1px 6px; }
  .viewall { padding: 1px 6px; font-size: 10px; }
  .jbody { padding: 2px 10px 6px; }
  .paper { padding: 6px 3px; }
  .ptitle { font-size: 12.5px; }
  .pmeta { font-size: 10px; }
}

/* volume separator — marks a volume boundary in a single-journal paper list
   (journal cards, the "View all" popout); not used in the mixed-journal
   topic view. */
.volsep { display: flex; align-items: center; gap: 10px; margin: 10px 2px 4px; }
.volsep::before {
  content: ""; flex: 1 1 auto; height: 0; border-top: 3px double var(--baseline);
}
.volsep-label {
  flex: 0 0 auto; color: var(--muted); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
}

/* special issue group */
.sigroup {
  border: 1px solid var(--border); border-left: 3px solid var(--si);
  border-radius: 8px; margin: 10px 0; padding: 4px 12px 8px;
}
.sigroup .sihead {
  display: flex; align-items: center; gap: 8px; padding: 6px 0;
  font-weight: 600; font-size: 13.5px;
}
.sibadge {
  background: var(--si); color: #fff; font-size: 10.5px; font-weight: 700;
  border-radius: 5px; padding: 1px 7px; text-transform: uppercase; letter-spacing: 0.04em;
}

/* paper rows */
.paper {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 6px; border-radius: 8px; border-bottom: 1px solid var(--grid);
}
.paper:last-child { border-bottom: 0; }
.paper:hover { background: var(--accent-soft); }
.ptitle { font-weight: 600; font-size: 15px; color: var(--ink); line-height: 1.3; }
/* "new since your last visit" accent dot (v11) — shared by paperRow(), so
   venue/topic/All/favourites/search-results/journal-popout all get it for
   free. Absent entirely when a paper isn't flagged new (no reserved space). */
.newdot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); margin-right: 6px; vertical-align: middle; flex: 0 0 auto;
}
.pmeta { color: var(--muted); font-size: 12.5px; margin-top: 1px; }
.pmeta .pj { color: var(--ink-2); font-weight: 550; }
/* "Hide authors & dates" display toggle (task 4/v9) — .pmeta is the shared
   author+journal+date meta line rendered by paperRow() everywhere in 2D
   (venue cards, topic cards, All view, journal popout, search results), so
   one rule here covers all of them. The paper MODAL uses its own separate
   .authors/.venueline classes and is deliberately unaffected — it always
   keeps full info. */
body.hide-authors .pmeta { display: none; }
.showmore {
  margin: 8px 0 2px; background: none; border: 1px dashed var(--baseline);
  border-radius: 8px; width: 100%; padding: 7px; color: var(--accent); font-size: 13px;
}
.showmore:hover { background: var(--accent-soft); }

/* topic view — each topic renders as a .jcard (book/basic styling applies
   the same as venue cards), body internally scrollable and capped by the
   same --card-max-h slider used for journal cards. */
.tcard { margin-bottom: 16px; }
.tcard .jname { font-size: 17px; }

/* per-card drag-to-resize handle (topic cards) — bottom edge, pointer
   events (works with touch), adjusts that one card's .jbody height */
.card-resize-handle {
  height: 11px; cursor: ns-resize; touch-action: none;
  display: flex; align-items: center; justify-content: center;
  border-top: 1px solid var(--grid);
}
.card-resize-handle::before {
  content: ""; width: 36px; height: 4px; border-radius: 3px; background: var(--baseline);
}
.card-resize-handle:hover::before, .card-resize-handle:active::before { background: var(--accent); }

/* "All" view — every publication in the window as one flat, newest-first,
   cross-journal list (same jcard/tcard book-spine + paperRow treatment as
   the topic view, so it picks up card style / journal colour accents for
   free). Deliberately NOT height-capped like venue/topic cards: this is
   meant to be one continuous list browsed with the page scroll (which is
   also what its IntersectionObserver lazy-render watches), not a small
   internally-scrolling box — so --card-max-h is intentionally ignored here. */
.allbody { max-height: none; overflow: visible; }
.all-sentinel { height: 1px; }

/* chips */
.chip {
  display: inline-block; border: 1px solid var(--baseline); border-radius: 999px;
  padding: 1px 9px; font-size: 11.5px; color: var(--ink-2); margin: 2px 3px 2px 0;
}

/* --------------------------------------------------------------- popover */
.overlay {
  position: fixed; inset: 0; z-index: 90; display: none;
  background: rgba(0, 0, 0, 0.35); align-items: center; justify-content: center;
}
.overlay.show { display: flex; }
/* The paper detail modal can be opened FROM WITHIN the journal popout (a
   paper row inside #journal-modal), so it must stack strictly above it —
   otherwise it renders visible-but-uncoverable behind the popout. Bump only
   #paper-overlay's z-index above the shared .overlay baseline (90); the
   about modal never opens over another overlay, so it stays at 90. */
#paper-overlay { z-index: 95; }
.modal {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow); max-width: 760px; width: calc(100vw - 48px);
  max-height: 82vh; overflow-y: auto; padding: 26px 30px;
}
.modal h3 { margin: 0 0 6px; font-size: 21px; line-height: 1.35; font-weight: 600; }
.modal .authors { color: var(--ink-2); margin-bottom: 4px; }
.modal .venueline { color: var(--muted); font-size: 13px; margin-bottom: 12px; }
.modal .abstract { color: var(--ink-2); font-size: 15.5px; line-height: 1.55; }
.modal .abstract.none { color: var(--muted); font-style: italic; }
/* Serif toggle (default off — sans is the base look; body.serif restores the
   academic-serif treatment on paper titles + modal titles + abstracts only.
   Logo, journal names and the 3D panel heading stay serif unconditionally. */
body.serif .ptitle,
body.serif .modal h3,
body.serif .modal .abstract { font-family: var(--serif); }
.modal .silabel { color: var(--si); font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.modal .actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; }
/* journal popout ("View all") — bounded height, only the paper list scrolls;
   header (name + Close) is sticky so it's always reachable, however long the
   journal's paper list is for the selected window. */
.modal.journal-modal {
  max-width: 900px; max-height: 85vh; overflow: hidden; padding: 0;
  display: flex; flex-direction: column;
}
.modal.journal-modal .modal-sticky-head {
  position: sticky; top: 0; z-index: 2; flex: 0 0 auto;
  display: flex; align-items: flex-start; gap: 14px;
  background: var(--surface); border-bottom: 1px solid var(--grid);
  padding: 22px 30px 14px;
}
.modal.journal-modal .modal-sticky-head .headtext { flex: 1; min-width: 0; }
.modal.journal-modal .modal-sticky-head h3 { margin: 0 0 4px; }
.modal.journal-modal .modal-sticky-head .venueline { margin: 0; }
.modal.journal-modal .modal-sticky-head .closebtn { flex: 0 0 auto; margin-left: 0; }
.modal.journal-modal .modal-scroll { flex: 1 1 auto; overflow-y: auto; padding: 14px 30px 26px; }
.modal .doibtn {
  background: var(--accent); color: #fff; border: 0; border-radius: 8px;
  padding: 8px 16px; font-weight: 600; display: inline-block;
}
.modal .doibtn:hover { text-decoration: none; opacity: 0.92; }
.modal .closebtn { margin-left: auto; }
.about h3 { margin-top: 18px; }
.about p, .about li { color: var(--ink-2); font-size: 14px; }
.about .about-updated { color: var(--muted); font-size: 13px; }
.about ul { margin: 6px 0; padding-left: 22px; }
.about li { margin-bottom: 6px; }
.about li:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------------- 3D */
#three-wrap { position: fixed; inset: 0; z-index: 60; display: none; background: var(--page); }
#three-wrap.show { display: block; }
#three-canvas { position: absolute; inset: 0; }
.three-hud {
  position: absolute; top: 14px; left: 20px; right: 20px; z-index: 65;
  display: flex; gap: 12px; align-items: center; pointer-events: none;
}
.three-hud > * { pointer-events: auto; }
.three-hint {
  position: absolute; bottom: 16px; left: 0; right: 0; text-align: center;
  color: var(--muted); font-size: 12.5px; z-index: 65; pointer-events: none;
}
/* Reading panel — a screen-space overlay in front of the (unmoved) camera;
   see js/threeview.js openBook()/renderPanel(). Sized generously so it
   reads as the primary "book" surface, not a small side note. */
.three-panel {
  position: absolute; top: 66px; right: 20px; bottom: 20px; width: 460px; z-index: 66;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow); padding: 16px 18px; overflow-y: auto; display: none;
}
.three-panel.show { display: block; }
.three-panel .panel-headrow { display: flex; align-items: flex-start; gap: 10px; }
.three-panel h3 { margin: 0 0 2px; font-size: 18px; font-family: var(--serif); flex: 1; min-width: 0; }
.three-panel .panel-headrow .closebtn { flex: 0 0 auto; }
.three-panel .sub { color: var(--muted); font-size: 12.5px; margin-bottom: 10px; }

/* "Aim to view" (device-orientation look-around) — only ever shown on
   touch-capable devices (see js/app.js, which adds body.touch-device). */
#btn-aim { display: none; }
body.touch-device #btn-aim { display: inline-flex; }
#btn-aim.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.aim-status {
  position: absolute; top: 60px; left: 0; right: 0; text-align: center;
  color: var(--ink-2); font-size: 12.5px; z-index: 67; pointer-events: none;
  opacity: 0; transition: opacity 0.4s ease;
}
.aim-status.show { opacity: 1; }
/* the 3D canvas must own touch gestures itself (drag-to-look, pinch-to-zoom)
   rather than letting the browser scroll/zoom the page underneath it */
#three-canvas, #three-canvas canvas { touch-action: none; }

/* mobile-ish */
@media (max-width: 900px) {
  .three-panel { width: calc(100vw - 40px); }
  .searchbox { min-width: 160px; }
}

/* ---------------------------------------------------------------- mobile */
@media (max-width: 640px) {
  html, body { max-width: 100vw; overflow-x: hidden; } /* belt-and-braces: no page-level horizontal scroll */
  .wrap { padding: 0 8px; } /* tightened from 12px (v9) — see the jgrid gap note below re: 2-column phone cards */
  body { font-size: 13.5px; } /* base text/density — more fits per screen */
  .site-row { gap: 6px; padding: 6px 0; }
  .brand { margin-right: 0; }
  .brand h1 { font-size: 18px; } /* compact brand — was 23px, dominated the top row */
  /* segmented navs still used inside the Display popover (feed width / card
     style / font / details) mustn't run off the edge of a phone screen —
     wrap onto extra rows instead, packed tightly rather than stretched to
     fill each row */
  .seg { flex-wrap: wrap; }
  .seg button { flex: 0 1 auto; }
  .brand .tag { display: none; } /* header is crowded enough on a phone */
  /* NB: .spacer used to force flex-basis:100% here, pushing the search
     toggle + "⋯" onto their own row underneath the brand — a leftover from
     the pre-v9 header (when the full-width .searchbox still sat inline and
     needed the wrap). Since v9 hides .searchbox and swaps in a small
     search-ICON button at this breakpoint (see the combined breakpoint
     below), there's nothing left that needs the forced wrap: .spacer can
     stay a plain flexible spacer (its default flex:1 rule) so brand + the
     icon buttons share row 1, same shape as desktop. */
  .searchbox { min-width: 0; padding: 6px 10px; font-size: 13px; }

  /* compact controls: still tappable (~34-36px) but no longer eating a whole
     row each */
  .iconbtn, .seg button { padding: 6px 10px; min-height: 34px; font-size: 12.5px; }
  .seg.small button { padding: 5px 9px; font-size: 12px; min-height: 30px; }
  .rankbadge, .viewall { min-height: 28px; }
  .chip-btn { min-height: 32px; }

  .yearpick input[type="number"], .custompick input[type="number"] { width: 60px; }

  main { padding: 10px 0 60px; } /* reclaim vertical space the compacted header freed up */

  /* card grid: never wider than the viewport (still scrolls to N columns —
     see the combined breakpoint below and the container-query narrow-card
     rules above); the tightened .wrap padding + jgrid gap here are what let
     e.g. a 180px card-width setting fit 2 columns on a 390px phone. */
  .jgrid { gap: 8px; }
  .jcard, .tcard { width: 100%; border-radius: 9px; }

  /* denser cards: smaller heads/rows so more papers fit on screen at once */
  .jhead { padding: 6px 12px 5px; gap: 8px; }
  .jname, .tcard .jname { font-size: 14.5px; }
  .jmeta { font-size: 11px; }
  .jright, .jright-top { gap: 3px; }
  .rankbadge { min-width: 28px; height: 28px; font-size: 13px; }
  .jbody { padding: 2px 12px 8px; }
  .paper { padding: 7px 4px; }
  .ptitle { font-size: 13.5px; }
  .pmeta { font-size: 11px; }

  /* feed-width presets: Narrow/Default/Wide/Max all collapse to "fill the
     screen" here — max-width simply can't exceed the viewport, so "Max"
     is a no-op on phones exactly as it is on desktop. */
  .popover-seg { flex-wrap: wrap; }

  .modal { padding: 20px 18px; max-height: 88vh; }
  .modal.journal-modal .modal-sticky-head { padding: 16px 18px 12px; }
  .modal.journal-modal .modal-scroll { padding: 12px 18px 20px; }

  .three-hud { flex-wrap: wrap; gap: 8px; top: 10px; left: 10px; right: 10px; }
  .three-hint { font-size: 11.5px; padding: 0 10px; line-height: 1.4; }
  .three-panel { left: 10px; right: 10px; width: auto; top: auto; bottom: 10px; height: 46vh; }
}

/* short viewports (phones in landscape) — the 3D chrome needs its own pass
   here since width alone (844px on a landscape phone) doesn't trip the
   640px rule above */
@media (max-height: 480px) {
  .three-hud { top: 6px; left: 10px; right: 10px; flex-wrap: wrap; gap: 6px; }
  .three-hint { bottom: 6px; font-size: 11px; padding: 0 8px; }
  .three-panel { top: auto; bottom: 6px; right: 10px; left: auto; width: min(340px, 46vw); height: 82vh; padding: 10px 12px; }
  .aim-status { top: 44px; }

  /* 2D header: in landscape the header was eating over half the (very
     short) viewport before any content showed — squeeze rows/gaps further
     still, on top of the 640px-width rules above. */
  .site-row { gap: 4px; padding: 4px 0; }
  .brand h1 { font-size: 15px; }
  .iconbtn, .seg button { padding: 4px 8px; min-height: 27px; }
  .seg.small button { padding: 4px 7px; min-height: 24px; }
  .chiprow { gap: 4px 6px; padding: 4px 0 6px; }
  main { padding: 6px 0 40px; }
}

/* ------------------------------------------------------ v9 header chrome --
   Filter-chip header rebuild: applies at EITHER the width OR height mobile
   condition (landscape phones trip only the height one) — search collapses
   to an icon + slide-in row, Favourites/Display/3D move into the "⋯" menu,
   and the chip row + any open chip/more/popover menu adapt to a small
   screen. Consolidated into one combined-selector block (rather than
   duplicating per breakpoint, the old pattern here) since every rule below
   applies identically in both cases. */
@media (max-width: 640px), (max-height: 480px) {
  .searchbox { display: none; }
  .search-icon-btn { display: inline-flex; }
  body.search-open .searchbox {
    display: block; flex: 1 1 100%; order: 6; margin-top: 4px; min-width: 0;
  }

  /* Favourites/Display/3D standalone buttons hide here — reachable instead
     via the matching .mobile-only-item inside the "⋯" menu, which proxies a
     click onto the (still-present, still-functional, just hidden) button so
     all existing open/close/positioning logic is reused untouched. Only the
     BUTTON is display:none, not its wrapping .display-wrap — the popover is
     a sibling of the button (not a descendant), so it's unaffected and the
     wrap collapses to 0 size on its own once its only visible child is gone. */
  #btn-favorites, #btn-display, #btn-3d { display: none; }
  .more-menu .menu-item.mobile-only-item { display: block; }

  /* chip row: single non-wrapping scrollable strip, no visible scrollbar, a
     right-edge fade hints there's more to scroll to. Deliberately a solid
     ::after gradient overlay rather than mask-image/-webkit-mask-image on
     .chiprow itself: mask (like filter/transform) makes its element the
     CONTAINING BLOCK for position:fixed descendants — and .chip-menu (a
     descendant, via .chip-wrap) becomes position:fixed right below on this
     same breakpoint for its bottom-sheet treatment. That combination silently
     breaks the fixed positioning (it fixes relative to .chiprow's scrolling
     box instead of the viewport), making the menu unclickable/off-position.
     A pseudo-element avoids the trap entirely. */
  .chiprow {
    position: relative;
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; padding-bottom: 4px;
  }
  .chiprow::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 4px; width: 28px;
    background: linear-gradient(to right, transparent, var(--page));
    pointer-events: none;
  }
  .chiprow::-webkit-scrollbar { display: none; }
  .chip-wrap { flex: 0 0 auto; }

  /* popovers/menus become a fixed bottom sheet instead of an
     absolutely-positioned flyout anchored to a header button/chip — a
     230-300px flyout can't be reliably anchored on a 360-430px screen
     without risking overflow (.more-menu inherits this via its shared
     .popover base class already). */
  .popover, .favorites-popover, .chip-menu {
    position: fixed; top: auto; bottom: 10px; left: 10px; right: 10px;
    width: auto; max-width: none; max-height: 75vh; overflow-y: auto;
  }
}

