@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,500&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&family=IBM+Plex+Sans:wght@400;500;600&family=Cookie&display=swap');

:root {
  --paper: #F7F4EC;
  --canvas: #EDEAE0;
  --canvas-dot: #DCD7C8;
  --rail: #EFECE3;
  --ink: #221F1B;
  --ink-soft: #6B6459;
  /* Darkened from #9C9585 to clear 4.5:1 against the panel backgrounds it
     sits on (was ~2.5-2.7:1) -- see impeccable critique 2026-09-27. */
  --ink-faint: #6D685D;
  --rule: #DCD6C6;
  --accent: #C98A2E;
  /* --accent itself is under 3:1 against --paper/--rail, fine for the
     borders/dots/SVG strokes it's used for but not for text. --accent-text
     is the same hue darkened to clear 4.5:1 for actual text uses (the
     wordmark, page tags, etc.); --accent is left alone everywhere else. */
  --accent-text: #78521B;
  --accent-soft: #F0DBB4;
  --accent-2: #3C6E68;
  --accent-2-soft: #D7E6E2;
  --card-bg: #FFFDF7;
  --card-shadow: rgba(40, 32, 16, 0.14);
  --page-shadow: rgba(40, 32, 16, 0.16);
  --danger: #B54834;
  --page-canvas-bg: #FFFFFF;
  --highlight-tint: rgba(201, 138, 46, 0.42);
  --highlight-tint-strong: rgba(201, 138, 46, 0.7);
  --highlight-blend: multiply;
}

:root[data-theme="dark"] {
  --paper: #17150F;
  --canvas: #100F0B;
  --canvas-dot: #26241C;
  --rail: #1C1A13;
  --ink: #EDE7D8;
  --ink-soft: #A69C87;
  /* Lightened from #6E6553 to clear 4.5:1 against the dark panel
     backgrounds (was ~3.2:1) -- see impeccable critique 2026-09-27. */
  --ink-faint: #8B8375;
  --rule: #322E22;
  --accent: #D9A44C;
  /* Already clears 8.5:1 against dark backgrounds as text, so no darker
     variant is needed here -- accent-text just mirrors accent. */
  --accent-text: var(--accent);
  --accent-soft: #3A2F19;
  --accent-2: #5FA69C;
  --accent-2-soft: #16302C;
  --card-bg: #1F1D15;
  --card-shadow: rgba(0, 0, 0, 0.45);
  --page-shadow: rgba(0, 0, 0, 0.55);
  --danger: #E08669;
  --page-canvas-bg: #EDE7D8;
  /* The PDF page itself stays paper-colored even in dark mode (it's a
     rendered bitmap of the source document, not app chrome), so the
     highlight still needs a blend mode meant for a LIGHT background here —
     "screen" (meant for dark backgrounds) was nearly invisible against it,
     which is what made highlights look too faint in dark mode. */
  --highlight-tint: rgba(224, 130, 20, 0.45);
  --highlight-tint-strong: rgba(224, 130, 20, 0.8);
  --highlight-blend: multiply;
}

body { background: var(--paper); }

/* ---------- scrollbars ---------- */
/* Applies everywhere a native scrollbar can appear (the rail, the reading
   pane, the table-of-contents panel, a capped .card-quote, ...) so none of
   them fall back to the browser's default — full-width, stark white even
   in dark mode, as seen on the card-quote overflow above. Firefox gets the
   two-value shorthand; WebKit/Blink (Chrome, Safari, Edge) need the
   pseudo-elements, which have no per-element shorthand, so this is scoped
   with a universal selector rather than repeated on every scrollable class. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--ink-faint) transparent;
}
*::-webkit-scrollbar {
  width: 9px;
  height: 9px;
}
*::-webkit-scrollbar-track {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background-color: var(--ink-faint);
  border-radius: 20px;
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover {
  background-color: var(--ink-soft);
}
*::-webkit-scrollbar-corner {
  background: transparent;
}

.theme-toggle {
  margin-left: auto;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-soft);
  font-size: 13px;
  width: 32px;
  height: 32px;
  border-radius: 7px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--ink-faint); }
/* #tourHelpBtn and #installAppBtn reuse this class for their icon-button
   look, but flexbox splits available free space across EVERY auto margin
   in the row — with more than one `.theme-toggle` button carrying
   `margin-left: auto`, the free space was getting divided between them
   instead of collapsing to one gap, so the theme toggle drifted away from
   the other icon buttons instead of sitting next to them. Only the first
   icon button in the group should actually open the gap. */
#tourHelpBtn, #installAppBtn { margin-left: 0; }
.theme-toggle .sun { display: none; }
:root[data-theme="dark"] .theme-toggle .moon { display: none; }
:root[data-theme="dark"] .theme-toggle .sun { display: block; }

* { box-sizing: border-box; }
/* `overflow: hidden` was only on body, not html. That's normally enough to
   stop the page scrolling — until something calls scrollIntoView() on an
   element inside a deeply-nested scroll container (goToPage jumping to a
   distant page in a big PDF, for instance): when the browser walks up the
   ancestor chain looking for room to satisfy the scroll, a hidden-overflow
   body doesn't stop it from landing on the *next* scrollable box up,
   which was the <html> element itself, still at its default `visible`
   overflow. The whole .app flex column would get shoved up by however far
   html scrolled, taking the topbar off the top of the screen with it —
   the "breaking UI" report. Locking overflow on both closes that gap. */
html, body { height: 100%; margin: 0; overflow: hidden; }
body {
  background: var(--paper);
  color: var(--ink);
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
}

button { font-family: inherit; }

.app {
  display: flex;
  flex-direction: column;
  /* `100vh` on a real mobile browser is measured against the LARGEST
     possible viewport — the height you'd get if the address bar/bottom nav
     were fully hidden — not the shorter area actually visible while they're
     showing. Desktop Chrome's device-mode emulator has no such chrome to
     collapse, so it always renders the full 100vh and never reveals the
     gap; a real phone does, and since html/body below are overflow:hidden
     (this layout manages its own internal scrolling, pane by pane, rather
     than letting the whole page scroll) there's no page-level scroll to
     reach whatever spills past the real visible edge — it's just gone.
     `100dvh` tracks the browser's actual current visible area instead, so
     nothing sits below the fold. Kept 100vh first as a fallback for older
     browsers that don't understand dvh (they simply ignore the second
     declaration and keep the first). */
  height: 100vh;
  height: 100dvh;
}

/* ---------- top bar ---------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule);
  flex-shrink: 0;
  background: var(--paper);
  /* Higher than the phone-width rail drawer's z-index:40 (see responsive
     section) so the ☰ toggle that opens/closes that drawer always stays
     above it, rather than the drawer's own top:0 painting over it — which
     was the "no collapse button" bug on narrow windows: the only control
     was still there, just invisible underneath the open drawer. */
  position: relative;
  z-index: 41;
}
.wordmark { font-family: 'Fraunces', serif; font-weight: 600; font-size: 19px; }
.wordmark em { color: var(--accent-text); font-style: italic; }
.doc-title { font-size: 13px; color: var(--ink-soft); padding-left: 14px; border-left: 1px solid var(--rule); max-width: 40vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.import-btn {
  background: var(--ink);
  color: var(--paper);
  border: none;
  font-size: 12.5px;
  font-weight: 500;
  padding: 8px 14px;
  border-radius: 7px;
  cursor: pointer;
}
.import-btn:hover { background: var(--accent); color: var(--ink); }
.rail-toggle {
  display: none;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink);
  font-size: 12.5px;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
}

/* ---------- layout ---------- */
.body-row { display: flex; flex: 1; min-height: 0; position: relative; }

/* ---------- folder rail ---------- */
.rail {
  width: 232px;
  flex-shrink: 0;
  background: var(--rail);
  border-right: 1px solid var(--rule);
  padding: 16px 12px;
  overflow-y: auto;
  transition: width .16s ease, padding .16s ease, border-color .16s ease;
}
.rail.collapsed {
  width: 0;
  padding-left: 0;
  padding-right: 0;
  border-right-color: transparent;
  overflow: hidden;
}
.rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 2px 12px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
  font-weight: 600;
}
.rail-collapse {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 1px solid var(--rule);
  background: none;
  border-radius: 6px;
  color: var(--ink-soft);
  cursor: pointer;
}
.rail-collapse:hover { color: var(--ink); border-color: var(--ink-faint); }

/* Slim tab that reopens the rail once it's collapsed — mirrors
   .canvas-expand-tab on the opposite side of the layout. */
.rail-expand-tab {
  display: none;
  position: absolute;
  top: 50%; left: 0;
  transform: translateY(-50%);
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: none;
  border-radius: 0 8px 8px 0;
  padding: 14px 7px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-soft);
  cursor: pointer;
  box-shadow: 4px 0 12px -8px var(--card-shadow);
  z-index: 5;
}
.rail-expand-tab svg { flex-shrink: 0; }
.rail-expand-tab:hover { color: var(--ink); }
.body-row.rail-collapsed .rail-expand-tab { display: flex; }
.rail h3 {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
  font-weight: 600;
  margin: 4px 0 8px 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.rail h3 .add {
  cursor: pointer;
  font-size: 14px;
  color: var(--ink-faint);
  font-weight: 400;
}
.rail h3 .add:hover { color: var(--accent-text); }
.rail h3 .h3-actions { display: flex; align-items: center; gap: 10px; }
.rail-group { margin-bottom: 20px; }
.rail-tree { display: flex; flex-direction: column; gap: 1px; }

/* Storage indicator — the in-app echo of "nothing is uploaded": everything
   the library holds lives in this browser's own storage, so showing how
   much of it is in use (and that there's plenty of headroom) makes that
   claim checkable rather than just asserted. Hidden by default in the
   markup and only revealed once navigator.storage.estimate() resolves,
   since it's unsupported or blocked in some contexts (older Safari, a
   sandboxed artifact iframe) and a broken/empty stat is worse than none. */
.rail-storage {
  margin-top: 4px;
  padding: 10px 9px 9px;
  border-top: 1px solid var(--rule);
  font-size: 11px;
  color: var(--ink-faint);
}
.rail-storage-top {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.rail-storage-top svg { flex-shrink: 0; opacity: 0.7; }
/* Deliberately quiet: the app is for reading, so this is a small muted
   link rather than a button. It only picks up the Buy Me a Coffee yellow
   on hover/focus. The loud yellow button lives on the landing page. */
.rail-support {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 4px 9px 12px;
  padding: 3px 7px;
  border-radius: 6px;
  font-size: 11px;
  color: var(--ink-faint);
  text-decoration: none;
  transition: background .15s ease, color .15s ease;
}
.rail-support:hover,
.rail-support:focus-visible { background: #FFDD00; color: #000; }
.rail-storage-bar {
  height: 3px;
  border-radius: 999px;
  background: var(--rule);
  overflow: hidden;
}
.rail-storage-fill {
  height: 100%;
  width: 0%;
  min-width: 2px;
  border-radius: 999px;
  background: var(--accent);
  transition: width .3s var(--ease-out, ease);
}

.folder-row, .doc-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 7px;
  border-radius: 6px;
  font-size: 13px;
  color: var(--ink-soft);
  cursor: pointer;
  line-height: 1.3;
}
.folder-row:hover, .doc-row:hover { background: rgba(127,115,90,0.09); }
.row-actions {
  display: flex;
  gap: 5px;
  margin-left: auto;
  padding-left: 6px;
  opacity: 0;
  flex-shrink: 0;
}
.folder-row:hover .row-actions, .doc-row:hover .row-actions { opacity: 1; }
.row-actions span {
  font-size: 12px;
  color: var(--ink-faint);
  cursor: pointer;
  width: 16px;
  text-align: center;
}
.row-actions span:hover { color: var(--accent-text); }
.row-actions span[data-act="del"]:hover { color: var(--danger); }
.folder-row .chev { font-size: 10px; width: 10px; color: var(--ink-faint); transition: transform .12s; }
.folder-row.collapsed .chev { transform: rotate(-90deg); }
.folder-row .icon, .doc-row .icon { flex-shrink: 0; opacity: 0.75; }
.folder-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.folder-name[contenteditable="true"] { outline: 1px dashed var(--accent); border-radius: 3px; padding: 0 2px; }

.doc-row.active { background: var(--accent-soft); color: var(--ink); font-weight: 500; }
.rail-group.notes .doc-row.active { background: var(--accent-2-soft); }
.doc-row .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
.rail-group.notes .doc-row .dot { background: var(--accent-2); }

.children { margin-left: 16px; border-left: 1px solid var(--rule); padding-left: 6px; }
.children.hidden { display: none; }

.rail-empty {
  font-size: 12px;
  color: var(--ink-faint);
  font-style: italic;
  padding: 6px 7px;
}
.drop-target { outline: 1.5px dashed var(--accent); outline-offset: -2px; background: var(--accent-soft); }

/* ---------- pane resize handles ---------- */
/* Thin draggable dividers between rail|reading and reading|notes, wired up
   in app.js. Widened via a transparent ::before so the actual hit target
   is comfortable without the visible bar itself looking heavy. Hidden
   whenever there's nothing to drag against (the rail collapsed, the notes
   panel collapsed, or the layout has stacked vertically on a narrow
   screen — see the responsive section). */
.pane-resize {
  position: relative;
  flex-shrink: 0;
  width: 1px;
  background: var(--rule);
  cursor: col-resize;
  z-index: 3;
  /* Without this, a touch drag over the handle defaults to touch-action:
     auto, so the browser is free to read it as a native pan/scroll gesture
     before wirePaneResize's own pointer handlers (app.js) get a clean
     stream — the drag never starts, or stutters and drops. Same reasoning
     as .pdf-page-wrap and .card, which hit the identical issue. */
  touch-action: none;
}
.pane-resize::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -4px; right: -4px;
}
.pane-resize:hover, .pane-resize.dragging { background: var(--accent); }
.rail.collapsed + .pane-resize,
.body-row.notes-collapsed #canvasResize {
  display: none;
}

/* ---------- reading pane ---------- */
.reading {
  flex: 1 1 48%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--rule);
  position: relative;
  overflow: hidden;
}
.reading-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: auto; /* the page has a fixed reading width — scroll to it rather than letting it hide under the Notes pane on a narrower window */
  padding: 30px 26px 80px;
  position: relative;
  /* Same reasoning as .pdf-page-wrap's own touch-action (see there): keeps
     native touch gestures from fighting pdfview.js's pointer-driven
     pinch-zoom (_beginPinch/_updatePinch) if a second finger happens to
     land in this pane's padding rather than on the page itself. */
  touch-action: pan-y;
}

/* ---------- pdf toolbar ---------- */
.pdf-toolbar {
  display: none;
  align-items: center;
  gap: 4px;
  padding: 8px 10px;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
  flex-shrink: 0;
}
.pdf-toolbar.visible { display: flex; }
.tb-sep { width: 1px; align-self: stretch; margin: 4px 4px; background: var(--rule); flex-shrink: 0; }
.tb-btn {
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-soft);
  width: 28px;
  height: 28px;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  flex-shrink: 0;
}
.tb-btn:hover { color: var(--ink); border-color: var(--ink-faint); }
.tb-btn.small { width: 22px; height: 22px; font-size: 13px; }
.tb-zoom { font-size: 12px; color: var(--ink-soft); width: 40px; text-align: center; flex-shrink: 0; }
.tb-page-input {
  width: 38px;
  text-align: center;
  font: inherit;
  font-size: 12px;
  padding: 5px 2px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--ink);
}
.tb-page-count { font-size: 12px; color: var(--ink-faint); margin-right: 2px; }
.tb-search {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--ink-faint);
  flex: 1;
  min-width: 160px;
  border: 1px solid var(--rule);
  border-radius: 7px;
  padding: 0 7px;
  height: 28px;
}
.tb-search input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--ink);
  outline: none;
}
.tb-search-count { font-size: 11px; color: var(--ink-faint); white-space: nowrap; }

/* ---------- table of contents ---------- */
.toc-backdrop {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(20, 17, 10, 0.35);
  z-index: 39;
}
.toc-backdrop.open { display: block; }
.toc-panel {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 280px;
  max-width: 85%;
  background: var(--card-bg);
  border-right: 1px solid var(--rule);
  box-shadow: 8px 0 24px -12px var(--card-shadow);
  transform: translateX(-100%);
  transition: transform .16s ease;
  z-index: 40;
  display: flex;
  flex-direction: column;
}
.toc-panel.open { transform: translateX(0); }
.toc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 8px 10px 14px;
  border-bottom: 1px solid var(--rule);
  flex-shrink: 0;
}
.toc-head button {
  border: none;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 13px;
}
.toc-head button:hover { color: var(--ink); }
.toc-tabs { display: flex; gap: 4px; }
.toc-tab {
  font-family: 'Fraunces', serif;
  font-style: italic;
  font-size: 14.5px;
  padding: 5px 10px;
  border-radius: 6px;
  color: var(--ink-faint) !important;
}
.toc-tab.active { color: var(--ink) !important; background: var(--accent-soft); }
.toc-list { overflow-y: auto; padding: 8px; flex: 1; }
.toc-item {
  font-size: 13px;
  color: var(--ink-soft);
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.toc-item:hover { background: var(--accent-soft); color: var(--ink); }
.toc-empty { font-size: 12px; color: var(--ink-faint); font-style: italic; padding: 8px; }

.bookmark-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ink-soft);
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.bookmark-item:hover { background: var(--accent-soft); color: var(--ink); }
.bookmark-item .page-tag {
  flex-shrink: 0;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--accent-text);
  background: var(--accent-soft);
  border-radius: 4px;
  padding: 2px 5px;
}
.bookmark-item .label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bookmark-item .del {
  flex-shrink: 0;
  color: var(--ink-faint);
  opacity: 0;
  font-size: 13px;
  padding: 2px 4px;
}
.bookmark-item:hover .del { opacity: 1; }
.bookmark-item .del:hover { color: var(--danger); }

/* Filled/active star for "this page is bookmarked" */
#bookmarkPageToggle.active { color: var(--accent-text); }
#bookmarkPageToggle.active svg { fill: var(--accent); }

/* ---------- search highlights ---------- */
.search-layer {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
}
.search-hit {
  position: absolute;
  background: rgba(60, 110, 104, 0.35);
  border-radius: 2px;
}
.search-hit.active {
  background: rgba(217, 89, 42, 0.55);
  outline: 1.5px solid var(--danger);
}
.reading-empty {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 10px;
  color: var(--ink-faint);
  text-align: center;
}
.reading-empty .big { font-family: 'Fraunces', serif; font-style: italic; font-size: 19px; color: var(--ink-soft); }
.reading-empty button {
  margin-top: 6px;
  background: var(--ink);
  color: var(--paper);
  border: none;
  padding: 9px 16px;
  border-radius: 7px;
  font-size: 13px;
  cursor: pointer;
}
/* The only other place this claim shows up is a tooltip on .rail-storage
   (hover/long-press only, and hidden entirely until a folder exists) and a
   welcome-tour step that plays once, ever. This is the one copy of it that
   is always on screen from a person's very first moment in the app, before
   they've imported anything — so unlike the button above, it needs to read
   as normal body text, not `--ink-faint`'s decorative/secondary tone. */
.reading-empty-trust {
  margin-top: 4px;
  font-size: 12.5px;
  color: var(--ink-soft);
  max-width: 320px;
}

.pdf-page-wrap {
  max-width: 640px;
  margin: 0 auto 28px;
  position: relative;
  /* the page itself stays paper-colored even in dark mode — it's a
     rendered bitmap of the source document, not app chrome */
  background: var(--page-canvas-bg);
  border: 1px solid var(--rule);
  box-shadow: 0 10px 24px -12px var(--page-shadow);
  /* Without this, touch/pen drags over the page default to touch-action:
     auto, so the browser is free to claim the gesture for native panning
     the instant it looks scroll-like — the marquee-selection code in
     pdfview.js never even sees a full pointer stream, which is why
     dragging to select silently did nothing on a tablet or with a pencil.
     pan-y (not none) keeps that native vertical scroll working, but only
     for gestures the browser itself reads as mostly-vertical — the same
     split pdfview.js's own SCROLL_INTENT check makes — so anything with a
     real horizontal component is left for our pointer handlers, on touch,
     pen, and mouse alike. */
  touch-action: pan-y;
}
.pdf-page-wrap canvas { display: block; width: 100%; height: auto; }
.text-layer {
  position: absolute;
  top: 0; left: 0;
  right: 0; bottom: 0;
  overflow: hidden;
  line-height: 1;
  opacity: 1;
  /* Applies to the whole layer, not just the spans below: a pointerdown
     that lands in the small gap between/around glyphs (rather than on a
     span exactly) would otherwise still be natively selectable, so the
     browser starts its own text-selection drag at the same time as our
     custom marquee (pdfview.js). That collision — most visible once the
     notes panel has narrowed the reading column and a drag needs the
     browser to auto-scroll to keep up — makes Chromium cancel the pointer
     stream outright (a pointercancel with no further events), which reads
     as the drag silently doing nothing. Selection is a custom drag-marquee,
     not native Range, everywhere in this layer — see pdfview.js.
     */
  user-select: none;
  /* iOS/iPadOS Safari shows its own text-selection magnifier/callout on a
     long-press even with user-select:none on the span text itself (color:
     transparent doesn't stop it either) — it would pop up mid-drag and
     swallow the gesture our own marquee is tracking. */
  -webkit-touch-callout: none;
}
.text-layer span {
  color: transparent;
  position: absolute;
  white-space: pre;
  cursor: text;
  transform-origin: 0 0;
}

.marquee-box {
  position: absolute;
  display: none;
  background: rgba(201, 138, 46, 0.22);
  border: 1px solid var(--accent);
  border-radius: 2px;
  pointer-events: none;
  z-index: 15;
}

.highlight-layer {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
}
.highlight-rect {
  position: absolute;
  background: var(--highlight-tint);
  mix-blend-mode: var(--highlight-blend);
  border-radius: 2px;
  pointer-events: auto;
  cursor: pointer;
}
.highlight-rect.flash { animation: flash 1.1s ease; }
@keyframes flash {
  0% { background: var(--highlight-tint-strong); }
  100% { background: var(--highlight-tint); }
}

.page-num {
  text-align: center;
  font-size: 11px;
  color: var(--ink-faint);
  margin: -18px 0 24px;
}

/* .pull-btn is now the floating group's container (positioned/shown by
   pdfview.js exactly as the single button used to be) with two options
   inside it — pull the excerpt as plain text, or as a snapshot image of
   the marquee'd region regardless of whether text was found there. */
.pull-btn {
  position: absolute;
  display: none;
  align-items: center;
  gap: 4px;
  z-index: 20;
  white-space: nowrap;
}
.pull-btn-opt {
  display: flex;
  align-items: center;
  gap: 5px;
  background: var(--ink);
  color: var(--paper);
  font-size: 12px;
  font-weight: 500;
  padding: 7px 11px;
  border-radius: 7px;
  cursor: pointer;
  box-shadow: 0 6px 16px -6px var(--card-shadow);
  white-space: nowrap;
  border: none;
}
.pull-btn-opt:hover { background: var(--accent); color: var(--ink); }

/* ---------- canvas ---------- */
.canvas-wrap { flex: 1 1 52%; min-width: 0; display: flex; flex-direction: column; transition: flex-basis .16s ease; }
.canvas-wrap.collapsed { flex: 0 0 0; min-width: 0; overflow: hidden; }
.canvas-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule);
  flex-shrink: 0;
  gap: 10px;
}
.canvas-head h2 { font-family: 'Fraunces', serif; font-weight: 500; font-size: 15px; margin: 0; flex-shrink: 0; }
.canvas-head-right { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1 1 auto; justify-content: flex-end; }
/* The notes pane can be dragged very narrow (the rail/reading resize
   handles), independent of the viewport-width tiers below that hide this
   hint outright on tablet/phone. With no white-space/overflow handling,
   `.canvas-head-right`'s flex row had nothing else to give, so `.hint`
   just kept shrinking and wrapping — one word per line — down to a
   vertical column. Truncate it with an ellipsis on one line instead. */
.canvas-head .hint {
  font-size: 11px;
  color: var(--ink-faint);
  text-align: right;
  min-width: 0;
  flex-shrink: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.canvas-zoom-controls { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.canvas-zoom-controls .tb-zoom { width: 34px; }
.canvas-collapse {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--rule);
  background: none;
  border-radius: 6px;
  color: var(--ink-soft);
  cursor: pointer;
}
.canvas-collapse:hover { color: var(--ink); border-color: var(--ink-faint); }
.canvas-export {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--rule);
  background: none;
  border-radius: 6px;
  color: var(--ink-soft);
  cursor: pointer;
}
.canvas-export:hover { color: var(--ink); border-color: var(--ink-faint); }
.canvas-copy-ai {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  border: 1px solid var(--rule);
  background: none;
  border-radius: 6px;
  color: var(--ink-soft);
  font: inherit;
  font-size: 12px;
  white-space: nowrap;
  cursor: pointer;
}
.canvas-copy-ai:hover { color: var(--ink); border-color: var(--ink-faint); }
.canvas-copy-ai.is-done { color: var(--accent-text); border-color: var(--accent); }

/* Slim tab that reopens the notes panel once it's collapsed — lives
   outside .canvas-wrap so it stays visible while that panel is hidden. */
.canvas-expand-tab {
  display: none;
  position: absolute;
  top: 50%; right: 0;
  transform: translateY(-50%);
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-right: none;
  border-radius: 8px 0 0 8px;
  padding: 14px 7px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-soft);
  cursor: pointer;
  box-shadow: -4px 0 12px -8px var(--card-shadow);
}
.canvas-expand-tab svg { flex-shrink: 0; }
.canvas-expand-tab:hover { color: var(--ink); }
.body-row.notes-collapsed .canvas-expand-tab { display: flex; }
.canvas {
  position: relative;
  flex: 1;
  /* Panning is transform-driven (canvas.js), not native scrolling — the
     view is a clipped window onto an unbounded plane, so there's no
     scrollbar and no scroll range to run out of in any direction. */
  overflow: hidden;
  cursor: grab;
  background-color: var(--canvas);
  background-image: radial-gradient(var(--canvas-dot) 1.2px, transparent 1.2px);
  background-size: 22px 22px;
  touch-action: none;
}
.canvas.panning { cursor: grabbing; }
/* Zoom lives on its own wrapper, one level out from the pan transform
   (canvas.js keeps them separate on purpose — see the comment on
   `setZoom` there). Zero-size like `.canvas-inner`, purely a positioning
   anchor: `zoom` scales its rendering, not its own box, so it doesn't
   need dimensions of its own either. */
.canvas-zoom { position: relative; width: 0; height: 0; }
/* No fixed size: cards are positioned absolutely by their own left/top
   (world coordinates, which may be negative) and this layer is moved as a
   whole via `transform: translate(...)`, so it never needs to contain its
   content within an explicit box. */
.canvas-inner { position: relative; width: 0; height: 0; }
.canvas svg {
  position: absolute;
  top: 0; left: 0;
  /* Must be non-zero, or Chromium silently refuses to paint anything
     outside the svg's own box even with overflow:visible below — a real
     bug that made every link thread invisible from day one. The exact
     size doesn't matter (overflow:visible does the actual work of letting
     paths run anywhere, including negative coordinates); 1px just avoids
     the zero-size case. */
  width: 1px; height: 1px;
  overflow: visible;
  pointer-events: none;
  /* Threads read as running underneath the cards they connect (each one
     starts from a card's own center) — no z-index needed, since the svg
     already comes before the cards in DOM order and so paints behind them
     by default. */
}
/* The real (visible) path itself stays click-through (pointer-events:none)
   — it sits ON TOP of its .hit twin in paint order (added after it in the
   DOM), and if it accepted pointer events too it would swallow clicks meant
   for the wider .hit target beneath it, since both share the exact same
   centerline geometry. All click handling lives on .hit. */
.canvas svg path { fill: none; stroke: var(--accent); stroke-width: 1.6; opacity: 0.75; pointer-events: none; }
.canvas svg path.live { stroke: var(--accent-2); stroke-width: 2.75; stroke-dasharray: 6 4; opacity: 1; filter: drop-shadow(0 0 2px var(--paper)); }
/* A wide, invisible stroke layered under the real (thin) path so the
   thread is easy to click without making the visible line itself thick. */
.canvas svg path.hit { stroke: transparent; stroke-width: 16; fill: none; pointer-events: stroke; cursor: pointer; }
/* Hovering the (invisible) hit target brightens the real path drawn right
   after it — .hit can't show its own hover state since it's transparent. */
.canvas svg path.hit:hover + path { opacity: 1; stroke-width: 2.2; }
.canvas svg .link-arrow-head { fill: var(--accent); opacity: 0.85; }
.canvas svg .link-label {
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  fill: var(--ink-soft);
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: 4px;
  stroke-linejoin: round;
  text-anchor: middle;
  pointer-events: auto;
  cursor: pointer;
  user-select: none;
}
.canvas svg .link-label:hover { fill: var(--ink); }

/* Floating picker shown when a link is created or clicked — plain fixed
   DOM element (not part of the SVG), positioned near the pointer. */
.link-menu {
  position: fixed;
  z-index: 50;
  background: var(--card-bg);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 10px 30px -8px var(--card-shadow);
  padding: 10px;
  width: 210px;
  font-size: 12px;
}
.link-menu-types { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.link-menu-type {
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-soft);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px;
  cursor: pointer;
}
.link-menu-type:hover { border-color: var(--ink-faint); color: var(--ink); }
.link-menu-type.active { background: var(--accent); border-color: var(--accent); color: var(--ink); }
.link-menu-custom { margin-bottom: 8px; }
.link-menu-input {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 12px;
  background: var(--paper);
  color: var(--ink);
}
.link-menu-input:focus { outline: none; border-color: var(--accent); }
.link-menu-row { display: flex; align-items: center; gap: 6px; border-top: 1px solid var(--rule); padding-top: 8px; }
.link-menu-action {
  flex: 1 1 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  font-size: 11px;
  padding: 5px 4px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.link-menu-action:hover { background: var(--canvas); color: var(--ink); }
.link-menu-action.danger { color: #c0392b; }
.link-menu-action.danger:hover { background: rgba(192, 57, 43, 0.1); }

.card {
  position: absolute;
  width: 200px;
  background: var(--card-bg);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: 3px;
  padding: 11px 12px 10px;
  box-shadow: 0 6px 16px -8px var(--card-shadow);
  cursor: grab;
  user-select: none;
  touch-action: none;
}
.card:active { cursor: grabbing; }
.card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* The card currently waiting for a keyboard-chosen target (L was pressed
   on it; see canvas.js's _startKeyboardLink) — a stronger, dashed outline
   distinguishes "this is the link's source and I'm mid-gesture" from a
   plain focus ring, since both can be visible on the same card at once. */
.card.link-source {
  outline: 2px dashed var(--accent-2);
  outline-offset: 3px;
}
.card .del:focus-visible,
.card .link-nub:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.card-quote {
  font-family: 'Source Serif 4', serif;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink);
  /* A marquee drag over a large, multi-row/multi-column region (a whole
     title block plus a table, say) can pull in a very long excerpt. Without
     a cap the card just kept growing taller to fit it — often past the
     visible canvas area, reading as the card "overflowing". Cap the quote
     itself and let it scroll internally instead, so the card's overall
     size — and the always-visible note/meta footer below it — stays
     predictable regardless of how much text got pulled in. */
  max-height: 220px;
  overflow-y: auto;
  /* A scrollable box starts its own touch-action scope, so the card's
     touch-action: none didn't reach it: on a tablet, a finger on the
     quote was taken by the browser as a scroll and the card wouldn't drag
     unless grabbed by its footer. canvas.js (_wireCard) scrolls a long
     quote itself on a mostly-vertical touch swipe, and drags otherwise. */
  touch-action: none;
}
.card-image {
  display: block;
  width: 100%;
  max-height: 140px;
  object-fit: contain;
  border-radius: 4px;
  background: var(--paper-alt, rgba(0,0,0,0.03));
  border: 1px solid var(--rule);
  /* Belt-and-suspenders alongside the `draggable = false` set in JS —
     Safari in particular still needs this to fully suppress the
     browser's own native image-drag gesture. */
  -webkit-user-drag: none;
  user-drag: none;
}
.card-note {
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px dashed var(--rule);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-soft);
  min-height: 18px;
  outline: none;
  cursor: text;
}
.card-note:empty:before {
  content: attr(data-placeholder);
  color: var(--ink-faint);
  font-style: italic;
}
.card-note:focus { color: var(--ink); }
.card .meta { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; }
.card .page-tag { font-size: 10.5px; color: var(--accent-text); font-weight: 600; }
.card .card-actions { display: flex; align-items: center; gap: 6px; }
.card .link-nub { position: relative; width: 13px; height: 13px; border-radius: 50%; background: var(--paper); border: 1.5px solid var(--ink-faint); cursor: crosshair; }
.card .link-nub:hover { border-color: var(--accent-2); background: var(--accent-2-soft); }
/* Invisible larger hit area around the small visible circle — a pointer
   event anywhere in here still reports the real .link-nub as its target
   (pseudo-elements aren't separate event targets), so this makes the drag
   noticeably easier to start without changing how the nub looks. */
.card .link-nub::before {
  content: '';
  position: absolute;
  inset: -7px;
}
.card .del { font-size: 13px; color: var(--ink-faint); cursor: pointer; line-height: 1; }
.card .del:hover { color: var(--danger); }

.canvas-empty {
  position: absolute;
  top: 18px; left: 18px;
  font-family: 'Fraunces', serif;
  font-style: italic;
  color: var(--ink-faint);
  font-size: 13px;
  /* #canvasInner is zero-width, so without an explicit width this
     absolutely-positioned hint shrinks to one word per line. */
  width: 260px;
  max-width: calc(100vw - 72px);
  pointer-events: none;
}

/* ---------- modal (replaces window.prompt/confirm, which a sandboxed
   iframe — e.g. the published artifact — silently blocks) ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 17, 10, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease;
  padding: 16px;
}
.modal-overlay.open { opacity: 1; pointer-events: auto; }
.modal-box {
  background: var(--card-bg);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 20px 48px -16px var(--card-shadow);
  padding: 20px;
  width: 320px;
  max-width: 100%;
  transform: translateY(6px);
  transition: transform .12s ease;
}
.modal-overlay.open .modal-box { transform: translateY(0); }
.modal-message {
  font-size: 14px;
  color: var(--ink);
  line-height: 1.5;
  margin-bottom: 12px;
}
.modal-input {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 14px;
  padding: 9px 10px;
  border: 1px solid var(--rule);
  border-radius: 7px;
  background: var(--paper);
  color: var(--ink);
  margin-bottom: 16px;
}
.modal-input:focus { outline: none; border-color: var(--accent); }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
.modal-actions button {
  font: inherit;
  font-size: 13px;
  padding: 8px 14px;
  border-radius: 7px;
  cursor: pointer;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-soft);
}
.modal-actions .modal-cancel:hover { color: var(--ink); }
.modal-actions .modal-ok {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.modal-actions .modal-ok:hover { background: var(--accent); border-color: var(--accent); color: var(--ink); }
.modal-actions .modal-ok.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.modal-actions .modal-ok.danger:hover { filter: brightness(1.08); }

/* ---------- undo toast ---------- */
/* Sits above the modal overlay's z-index so an undo offered right after a
   confirm dialog closes (the common case: confirm -> delete -> toast) is
   never hidden behind it during the overlay's own fade-out transition. */
.undo-toast {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 12px);
  z-index: 1100;
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 999px;
  padding: 11px 10px 11px 18px;
  box-shadow: 0 16px 36px -14px var(--card-shadow);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  max-width: calc(100vw - 32px);
}
.undo-toast.open { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.undo-toast-msg { font-size: 13.5px; line-height: 1.3; }
.undo-toast-btn {
  flex-shrink: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--paper);
  background: none;
  border: 1px solid color-mix(in srgb, var(--paper) 35%, transparent);
  border-radius: 999px;
  padding: 6px 14px;
  cursor: pointer;
}
.undo-toast-btn:hover { background: color-mix(in srgb, var(--paper) 14%, transparent); border-color: var(--paper); }
@media (max-width: 480px) {
  .undo-toast { left: 16px; right: 16px; transform: translateY(12px); }
  .undo-toast.open { transform: translateY(0); }
}

/* ---------- welcome tour ---------- */
/* Same hand-built-overlay approach as the modal above, for the same reason
   (no native dialogs in a sandboxed artifact iframe). Two pieces sit above
   everything else: a highlight around the current target (`.tour-spotlight`,
   using a huge box-shadow spread to dim the rest of the page rather than a
   separate dim layer, so the target itself needs no z-index tricks to show
   through) or a plain full-screen dim (`.tour-dim`) for the target-less
   welcome/closing steps — and the tooltip card itself. */

.tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
}
.tour-overlay.open { opacity: 1; pointer-events: auto; }

.tour-dim {
  position: absolute;
  inset: 0;
  background: rgba(20, 17, 10, 0.55);
  display: none;
}

.tour-spotlight {
  position: fixed;
  display: none;
  border: 2px solid var(--accent);
  border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(20, 17, 10, 0.55);
  transition: left .25s ease, top .25s ease, width .25s ease, height .25s ease;
  pointer-events: auto; /* block interaction with the real app during the tour */
}

.tour-tooltip {
  position: fixed;
  z-index: 2001;
  width: 320px;
  max-width: calc(100vw - 32px);
  background: var(--card-bg);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: 0 20px 48px -16px var(--card-shadow);
  padding: 18px 20px 16px;
  transition: top .2s ease, left .2s ease;
}
.tour-tooltip.center {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
/* On phone the spotlighted target (the rail drawer, the stacked notes
   pane…) can fill nearly the whole screen, leaving nowhere reliable to
   anchor a floating tooltip next to it — so instead of placement math
   against a target that may BE the viewport, the tooltip becomes a fixed
   bottom sheet, a pattern that works regardless of what's highlighted
   above it. */
.tour-tooltip.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto;
  width: 100%;
  max-width: 100%;
  border-radius: 16px 16px 0 0;
  border-bottom: none;
  padding: 16px 18px calc(16px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -8px 28px -12px var(--card-shadow);
}

.tour-close {
  position: absolute;
  top: 10px;
  right: 12px;
  border: none;
  background: none;
  color: var(--ink-faint);
  font-size: 13px;
  cursor: pointer;
  padding: 4px;
  line-height: 1;
}
.tour-close:hover { color: var(--ink); }

.tour-eyebrow {
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--accent-text);
  font-weight: 600;
  min-height: 14px;
}
.tour-title { margin: 2px 0 6px; font-size: 16px; color: var(--ink); }
.tour-body { margin: 0 0 14px; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }

.tour-dots { display: flex; gap: 6px; margin-bottom: 14px; }
.tour-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--rule); }
.tour-dot.active { background: var(--accent); }

.tour-actions { display: flex; align-items: center; gap: 8px; }
.tour-actions button {
  font: inherit;
  font-size: 13px;
  padding: 8px 14px;
  border-radius: 7px;
  cursor: pointer;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-soft);
}
.tour-actions .tour-back { margin-right: auto; }
.tour-actions .tour-back:hover { color: var(--ink); }
.tour-actions .tour-skip {
  border-color: transparent;
  color: var(--ink-faint);
}
.tour-actions .tour-skip:hover { color: var(--ink-soft); }
.tour-actions .tour-next {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.tour-actions .tour-next:hover { background: var(--accent); border-color: var(--accent); color: var(--ink); }

/* ---------- responsive ---------- */

/* "Tablet" range (641–900px): still the full side-by-side, resizable
   3-pane layout — just tighter, so a couple of the wider toolbar bits get
   dropped. This tier used to not exist at all: anything under 900px went
   straight to the stacked/drawer phone layout below, which is why there
   was no way to collapse the rail without losing it behind an overlay —
   the rail/notes collapse buttons and the resize handles above all keep
   working exactly as they do on desktop. */
@media (max-width: 900px) and (min-width: 641px) {
  .doc-title { max-width: 22vw; }
  .canvas-head .hint { display: none; }
}

/* "Phone" range (≤640px): not enough width for a resizable 3-pane layout
   at all — reading and notes stack vertically, resize handles hide (there's
   nothing horizontal left to drag), and the rail becomes a full-height
   overlay drawer opened via the ☰ toggle in the topbar. The topbar keeps a
   higher z-index than the drawer (set globally, above) specifically so
   that toggle button stays clickable — and visible — once the drawer is
   open, instead of the drawer's own top:0 covering it. */
@media (max-width: 640px) {
  .rail-toggle { display: inline-block; }
  .rail {
    /* top:58px, not 0 — the topbar is `position:relative` with a HIGHER
       z-index (41 vs. this drawer's 40) specifically so its own ☰ toggle
       stays clickable and visible over the open drawer (see the topbar
       rule above). But that also means the topbar visually paints over
       whatever the drawer renders underneath it, so starting the drawer's
       own box at the actual top of the viewport just hides its first ~58px
       (the topbar's rendered height at this width) behind the topbar for
       no benefit — the folder list's first entries were getting clipped
       behind it. Starting the drawer below the topbar instead means every
       pixel of it is actually visible, and there's no interactive area
       hidden under an opaque sibling for a tap to miss. */
    position: fixed; top: 58px; bottom: 0; left: 0; z-index: 40;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: 0 0 24px var(--card-shadow);
  }
  .rail.open { transform: translateX(0); }
  .rail.collapsed { width: 232px; } /* the ☰ toggle above already handles show/hide at this width */
  .rail-head { display: none; } /* redundant with the ☰ Folders toggle in the topbar at this width */
  .rail-expand-tab { display: none !important; }
  .pane-resize { display: none; } /* nothing side-by-side left to drag once stacked */
  .body-row { flex-direction: column; }
  .reading { border-right: none; border-bottom: 1px solid var(--rule); flex: 1 1 auto; max-height: 50vh; }
  /* When Notes is collapsed there's nothing stacked below the reading pane
     any more, so the 50vh cap (sized for the split view) just leaves a dead
     empty gap at the bottom of the screen — let it fill instead. */
  .body-row.notes-collapsed .reading { max-height: none; }
  .canvas-wrap { flex: 1 1 auto; }
  .doc-title { display: none; }
  .canvas-head .hint { display: none; }

  /* .toc-panel/.toc-backdrop are `position: absolute` against `.reading`
     (their nearest positioned ancestor) so they can sit correctly inset
     within the split view on desktop/tablet. But `.reading` is capped to
     max-height:50vh here, and has overflow:hidden, so left as `absolute`
     the panel/backdrop would be clipped to just the top half of the
     screen — a modal drawer that stops partway down with the Notes pane
     exposed (and un-dimmed) underneath it. Promoting them to `fixed` here
     escapes that clipping so they cover the full viewport, the way `.rail`
     already does for the folder drawer at this width. */
  /* Same "start below the topbar, not under it" reasoning as `.rail`
     above applies here too — without it the panel's own Contents/
     Bookmarks tabs render underneath the higher-z-index topbar and can't
     be tapped. */
  .toc-backdrop, .toc-panel { position: fixed; top: 58px; }

  /* The floating "reopen Notes" tab is positioned relative to `.body-row`,
     which spans the *whole* stacked column (reading + notes), not just one
     pane. On desktop that's correct — right:0 + vertical centering puts it
     on the shared edge between two side-by-side panes. Stacked on phone,
     that same math centers it over the middle of the *reading* pane's own
     content, floating on top of PDF text with nothing to explain what it
     is. Move it to a small pill anchored to the bottom-right corner
     instead, out of the way of the page, still reachable with a thumb. */
  .canvas-expand-tab {
    /* Fully reset from the desktop version rather than layering on top of
       it: that one is a narrow vertical tab flush against the pane edge
       (padding 14px 7px, border-right:none, radius only on the left
       corners so it reads as part of that edge). Floating free as a
       corner pill here, those same values left a flat, borderless seam
       on the right side instead of a clean rounded cap — this sets every
       property the shape depends on explicitly instead of assuming what's
       left over from the desktop rule still fits. */
    top: auto; right: 14px; bottom: 14px; left: auto;
    transform: none;
    flex-direction: row;
    gap: 6px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 10px 16px 10px 14px;
    box-shadow: 0 6px 16px -6px var(--card-shadow);
  }

  /* Toolbar buttons are sized for a mouse cursor (28px / 22px) everywhere
     else; on a touchscreen those fall under the ~44px target Apple/Google
     both recommend, and packed at 4px apart they're an easy mis-tap. Widen
     the tap targets and give them a little more breathing room without
     changing anything about the desktop/tablet toolbar. */
  .pdf-toolbar { gap: 7px; padding: 9px 10px; }
  .tb-btn { width: 34px; height: 34px; font-size: 16px; }
  .tb-btn.small { width: 28px; height: 28px; font-size: 14px; }
  .tb-search { height: 34px; }

  /* The topbar (☰ Folders, wordmark, theme toggle, ? help, Import PDF) was
     already right at the edge of fitting on one line at narrow phone
     widths — adding the ? help button pushed it over, so both the
     "Folders" label and the "Import PDF" label started wrapping onto a
     second line inside their own buttons instead of staying on one row.
     Tighten the topbar's own spacing (not any individual control's
     touch-target size) to reclaim enough width to fit five items on one
     line down to ~360px-wide phones. */
  .topbar { gap: 6px; padding: 10px 8px; }
  .rail-toggle, .import-btn { white-space: nowrap; }
  .rail-toggle { padding: 6px 8px; }
  .import-btn { padding: 7px 10px; font-size: 11.5px; }
  /* The wordmark is purely decorative branding here (the doc title next to
     it is already hidden at this width) — a couple of points smaller buys
     back real width for the functional controls on either side of it,
     which matters down around 360px-wide phones. */
  .wordmark { font-size: 16px; }

  /* A 4th icon button (Install, shown only when the browser's own
     beforeinstallprompt fires — common on Android Chrome) can join
     theme/help on a phone that's already tight on width — the topbar
     fits 3 icon buttons plus the "☰ Folders" label at these widths, but
     not 4. `body.pwa-install-available` is toggled by app.js exactly
     when that button becomes visible, so the label only drops in the
     cases that actually need the width back; the common case (no install
     button shown) keeps the friendlier full label. */
  body.pwa-install-available .rail-toggle-label { display: none; }
  body.pwa-install-available .rail-toggle { padding: 6px; }
}
