/*
 * Reader Spread — two-page book view rendered into a fresh Tauri
 * webview window. Mirrors the typography of the Monk Reader HTML
 * template (Literata, raised caps, italic chapter titles, dinkus
 * scene break) so reading the in-app preview feels the same as
 * reading the eventual EPUB / HTML export.
 *
 * Layout is one CSS multi-column container (`.book`) sitting
 * inside a `overflow: hidden` viewport. JS sets `--col-w` so
 * exactly two columns + one gap fit between the page margins;
 * `--offset` is the negative translate that brings the active
 * spread (2-column pair) into view.
 *
 * All length tokens (--col-w, --gap, --margin-h, --margin-v)
 * live on :root so the script can set them once per resize.
 */

:root {
  --page-bg: #faf6ec;       /* book cream */
  --page-text: #1a1815;
  --muted: #6a6a66;
  --rule: #c8c0ad;
  /* Inline link ink. Two values so the colour reads at AA
     contrast on each background — the UA's #0000EE default sits
     fine on cream but turns to a near-invisible navy blur on
     the dark mode's near-black page bg. Picked to land in the
     same hue family as the editor's mauve accent so the Reader
     reads as part of Monk's chrome rather than as a generic
     hypertext document. Dark variant overrides on html.dark. */
  --link-color: #6c45a1;

  /* Surface tone behind the closed book on the cover spread —
     mirrors the editor's Catppuccin Latte bg so the reader's
     cover presentation blends into the surrounding window chrome
     ("the desk the book sits on"). Dark-mode override further
     down switches to the Mocha bg. */
  --surface-bg: #eff1f5;

  /* Height of the in-document title strip. 44 px on every
     viewport — same as the coarse-pointer touch-target sizing
     so desktop and mobile read at one consistent chrome
     scale. The earlier 32 px desktop default predated the A−
     / A+ font-size buttons; once those landed the 32 px row
     felt cramped, and matching mobile reads cleaner. */
  --titlebar-h: 44px;

  /* Defaults — the script overwrites these every resize. */
  --col-w: 32em;
  --gap: 96px;
  --margin-h: 64px;
  --margin-v: 72px;
  --offset: 0px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  /* No pinch-zoom (the reader has its own font-size controls, and a
     stray pinch breaks the column pagination): pan-x pan-y permits
     panning (inert here — overflow is hidden) while blocking
     pinch-zoom AND double-tap zoom in browsers that honour
     touch-action, and drops the legacy 300ms tap delay so the
     page-turn zones respond instantly. Paired with the viewport
     meta + gesturestart handler in the template. */
  touch-action: pan-x pan-y;
  /* svh (small viewport height) with a vh fallback: on mobile
     browsers 100vh is the LARGE viewport — the height with the URL
     bar hidden — so vh-sized pages overflow and crop at the bottom
     while the browser chrome is showing. svh never exceeds the
     visible area, and unlike dvh it doesn't resize as the chrome
     collapses, so the column pagination stays put mid-read. */
  height: 100vh;
  height: 100svh;
  width: 100vw;
  overflow: hidden;
  background: var(--page-bg);
  color: var(--page-text);
  /* The whole book follows Settings → Reader → Font — including the
     title page, chapter titles, chapter headings, and chapter body.
     Chrome (titlebar, chapters menu, page numbers, hint pill) sets
     its own font-family back to Literata further down so the window
     UI stays visually stable across font choices. */
  font-family: var(--reader-font);
  font-optical-sizing: auto;
  /* Driven by --reader-font-size — set on :root by the template
     (default 18px) and updated by the Reader's ⌘+= / ⌘+- /
     ⌘+0 keyboard shortcuts. Persisted under
     monk.reader.fontSize in localStorage so the writer's
     chosen body size carries across Reader opens. */
  font-size: var(--reader-font-size, 18px);
  line-height: 1.6;
}

/* Brief blink-suppression — the script flips body.ready once
   columns have laid out. Without this, the unstyled column
   strip flashes for one frame before the transform settles.
   The initial opacity is 0.001 rather than 0 so WebKit actually
   composites the layer during the warm-up paint pass — a
   transparent layer can be skipped from compositing entirely,
   which means the tile cache stays cold and the writer's first
   navigation to chapter 1 still hitches. 0.001 is
   indistinguishable from invisible but counts as "visible"
   for the compositor's purposes. */
body {
  opacity: 0.001;
  transition: opacity 80ms ease-out;
}
body.ready {
  opacity: 1;
}

/* ---- In-document title strip --------------------------------- */
/* Top-of-page chrome strip carrying the book title (centred),
   the chapters dropdown (left), the prev/next nav (right) and
   the theme toggle (right). The live in-app Reader's variant of
   this rule keeps 80 px of left padding to clear the macOS
   traffic-light overlay that paints over the WebviewWindow's
   top-left corner; the export runs in a regular browser tab
   where there's no overlay, so left/right padding collapses to
   the same 16 px on both sides for visual symmetry. */
.titlebar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--titlebar-h);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  font-family: "Literata", Georgia, serif;
  /* Pinned to a px value rather than `0.78em` so the titlebar's
     text scale stays the same on desktop and mobile. The earlier
     em-based size inherited from --reader-font-size (18 px on
     desktop, 16 px on phone) and made the chrome text visibly
     smaller on phones — confusing because the titlebar is UI,
     not body prose, and shouldn't shrink with the reader's font
     preference. */
  font-size: 13px;
  font-style: italic;
  letter-spacing: 0.12em;
  color: var(--muted);
  background: transparent;
  user-select: none;
  -webkit-user-select: none;
  /* Drag region — CSS app-region is unreliable for
     programmatically-spawned WebviewWindows on Tauri 2 / macOS,
     so the actual drag is wired up in JS via a mousedown handler
     that invokes plugin:window|start_dragging. Keep the CSS too
     for parity with the editor's chrome and so the intent reads
     from the styles alone. */
  -webkit-app-region: drag;
  app-region: drag;
  z-index: 10;
}
/* Book title — absolutely positioned at the left of the titlebar,
   immediately past the chapters-btn (left: 14 px, width: 26 px →
   right edge ≈ 40 px; this sits at 48 px for a small gap). The
   `right: 84 px` reserves room for the prev/next nav buttons on
   the far right (each 26 px, ending at right: 14 px → leftmost
   button left edge ≈ right: 70 px; +14 px breathing).

   The live in-app Reader uses larger left padding (120 px) to
   clear the macOS traffic-light overlay; this template runs in a
   regular browser tab where the overlay doesn't exist, so the
   title sits tight against the chapter button. */
.titlebar-title {
  position: absolute;
  /* Left edge sits just past the chapters button (which itself
     starts at --margin-h, the same horizontal margin the body
     prose lines up against). 40 px = 36 px button width + 4 px
     gap. Right reserves room for the six-button cluster
     (nav-next, nav-prev, theme-btn, font-larger, font-smaller,
     site-btn) at 40 px each. */
  left: calc(var(--margin-h) + 40px);
  right: calc(var(--margin-h) + 236px);
  top: 50%;
  transform: translateY(-50%);
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Single-chapter export — the chapters-btn isn't emitted, so the
   title pulls left to the same `--margin-h` slot the button would
   have anchored to. Avoids an empty 40 px strip between the leading
   page edge and the title. */
.titlebar.no-chapters-btn .titlebar-title {
  left: var(--margin-h);
}

/* ---- Chapters dropdown button -------------------------------- */
/* Pinned to the leading edge of the titlebar at --margin-h —
   the same horizontal offset the body prose lines up against,
   so the chapter button visually anchors to the writing column
   instead of the screen edge. The live in-app Reader pushes
   this further right (~80 px) to clear the macOS traffic-light
   overlay; the export runs in a regular browser tab where that
   overlay doesn't exist. */
.chapters-btn {
  position: absolute;
  left: var(--margin-h);
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: default;
  -webkit-app-region: no-drag;
  app-region: no-drag;
  transition: background-color 100ms ease, color 100ms ease,
    border-color 100ms ease;
}
.chapters-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.08);
}
.chapters-btn[aria-expanded="true"] {
  background: rgba(0, 0, 0, 0.06);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.10);
}
.chapters-btn svg {
  display: block;
  width: 20px;
  height: 20px;
}

/* ---- Prev / next page buttons --------------------------------- */
/* Twin of the chapters button on the right edge of the titlebar.
   Same no-drag carve-out, same muted color, same hover affordance.
   Sit side by side with a small gap between them. */
.nav-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: default;
  -webkit-app-region: no-drag;
  app-region: no-drag;
  transition: background-color 100ms ease, color 100ms ease,
    border-color 100ms ease;
}
.nav-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.08);
}
.nav-btn:active {
  background: rgba(0, 0, 0, 0.07);
}
.nav-btn svg {
  display: block;
  width: 20px;
  height: 20px;
}
.nav-prev {
  right: calc(var(--margin-h) + 40px);
}
.nav-next {
  right: var(--margin-h);
}

/* ---- Theme toggle (dark / light) ----------------------------- */
/* Sits to the left of the prev / next pair. Same shape, same
   muted-on-transparent affordance — reads as a peer of the page
   navigation buttons. Two icons stacked in the DOM (sun + moon);
   CSS picks which one paints based on `html.dark`, so the swap
   on click is a paint, not a DOM mutation. */
/* ---- Font-size adjust (coarse-pointer only) ------------------ */
/* Two small text-label buttons sitting on the left of the right-
   cluster: A− shrinks the body font one step, A+ grows it one
   step. Wired to bumpFontSize(-1) / bumpFontSize(+1) in the JS,
   the same functions the keyboard ⌘+= / ⌘+- shortcuts use.
   Visible on every viewport — desktop already has the keyboard
   path (⌘+=, ⌘+-, ⌘+0), but the buttons are useful as
   discoverable affordances for readers who don't know the
   shortcuts exist. */
.font-size-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: default;
  font-family: "Literata", Georgia, serif;
  letter-spacing: 0;
  -webkit-app-region: no-drag;
  app-region: no-drag;
  transition: background-color 100ms ease, color 100ms ease,
    border-color 100ms ease;
}
.font-size-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.08);
}
.font-size-btn:active {
  background: rgba(0, 0, 0, 0.07);
}
html.dark .font-size-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
}
html.dark .font-size-btn:active {
  background: rgba(255, 255, 255, 0.10);
}
/* A− reads as a smaller A — set its label font-size below the
   1em titlebar default. A+ goes one step over so the size
   differential telegraphs direction without a separate icon. */
.font-size-smaller {
  right: calc(var(--margin-h) + 160px);
  font-size: 0.78em;
}

/* ---- Site link (Ishmael mark) --------------------------------- */
/* Leftmost slot of the right-edge cluster (the 40px rhythm the
   other buttons follow). The glyph is the Ishmael character mask
   cut from --monk-logo-uri and painted with currentColor so it
   tints like every sibling icon; the pointer cursor is a real
   link affordance — this is a browser, and the anchor opens the
   project site in a new tab. */
.site-btn {
  position: absolute;
  right: calc(var(--margin-h) + 200px);
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background-color 100ms ease, color 100ms ease,
    border-color 100ms ease;
}
.site-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.08);
}
.site-btn:active {
  background: rgba(0, 0, 0, 0.07);
}
html.dark .site-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
}
html.dark .site-btn:active {
  background: rgba(255, 255, 255, 0.10);
}
.site-btn-mask {
  width: 19px;
  height: 20px;
  background: currentColor;
  -webkit-mask: var(--monk-logo-uri) center / contain no-repeat;
  mask: var(--monk-logo-uri) center / contain no-repeat;
}
.font-size-larger {
  right: calc(var(--margin-h) + 120px);
  font-size: 1em;
}
/* (Coarse-pointer overrides for the titlebar chrome live at the
   end of this file — moved past .theme-btn / .nav-btn / etc.
   definitions so the same-specificity cascade lets them win
   without an artificial bump.) */

.theme-btn {
  position: absolute;
  right: calc(var(--margin-h) + 80px);
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: default;
  -webkit-app-region: no-drag;
  app-region: no-drag;
  transition: background-color 100ms ease, color 100ms ease,
    border-color 100ms ease;
}
.theme-btn:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--page-text);
  border-color: rgba(0, 0, 0, 0.08);
}
.theme-btn:active {
  background: rgba(0, 0, 0, 0.07);
}
/* Icon swap — sun in light mode, moon in dark mode. Names the
   *current* theme, matching the macOS menu-bar convention.
   `display: block` lives on the icon classes themselves (rather
   than a `.theme-btn svg { display: block }` umbrella) so the
   show/hide rules carry the same per-selector specificity and
   the source-order tie-break does its job. The umbrella rule
   would be (0,1,1) and stomp the (0,1,0) hide rules — same
   specificity trap the `[hidden]` ties memory flags. */
.theme-icon-sun {
  display: block;
  width: 20px;
  height: 20px;
}
.theme-icon-moon {
  display: none;
  width: 20px;
  height: 20px;
}
html.dark .theme-icon-sun {
  display: none;
}
html.dark .theme-icon-moon {
  display: block;
}

/* ---- Chapters dropdown menu ----------------------------------- */
/* Drops down from beneath the chapter button. Shares the button's
   --margin-h offset so the menu hangs straight off it; matches
   the body prose left edge too. max-height bounds the list so
   long books scroll inside the menu rather than blowing past the
   viewport. Faint border + subtle shadow read as "popover"
   without competing with the page typography. */
.chapters-menu {
  position: fixed;
  top: calc(var(--titlebar-h) + 6px);
  left: var(--margin-h);
  z-index: 20;
  min-width: 240px;
  max-width: min(380px, 60vw);
  max-height: calc(100vh - var(--titlebar-h) - 24px);
  max-height: calc(100svh - var(--titlebar-h) - 24px);
  overflow-y: auto;
  padding: 6px;
  background: var(--page-bg);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
  font-family: "Literata", Georgia, serif;
  font-size: 0.95rem;
  line-height: 1.35;
}

/* Book-title header row — hidden on wide viewports (the title
   lives in the titlebar there); the narrow-mode media block below
   swaps the two. Set apart from the chapter rows: italic, muted,
   slightly small, with a hairline rule underneath — reads as a
   running head for the list, not a clickable entry. */
.chapters-menu-book-title {
  display: none;
  padding: 6px 10px 8px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  font-style: italic;
  font-size: 0.88em;
  letter-spacing: 0.04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chapters-menu-item {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--page-text);
  font: inherit;
  text-align: left;
  cursor: default;
  transition: background-color 100ms ease, color 100ms ease;
}
.chapters-menu-item:hover {
  background: rgba(0, 0, 0, 0.05);
}
.chapters-menu-item.active {
  background: rgba(124, 94, 138, 0.14); /* accent-tinted */
  color: var(--page-text);
  font-weight: 500;
}

.chapters-menu-number {
  flex: 0 0 auto;
  min-width: 1.8em;
  text-align: right;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-size: 0.85em;
}
.chapters-menu-item.active .chapters-menu-number {
  color: var(--page-text);
}

.chapters-menu-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.viewport {
  position: relative;
  /* Subtract the title strip so the book pages don't slide under
     it. width stays full-bleed because the strip is centred and
     has its own horizontal padding for the traffic lights. */
  height: calc(100vh - var(--titlebar-h)); /* svh below; see html/body */
  height: calc(100svh - var(--titlebar-h));
  margin-top: var(--titlebar-h);
  width: 100vw;
  overflow: hidden;
}

/* ---- Multi-column page strip ----------------------------------- */
/* `.book` is the column container. Padding reserves the outer page
   margins. column-width is set in pixels via the JS variable so the
   browser lays out exactly two columns per viewport width; column-
   fill: auto fills each column to the container height before
   moving on. The container's natural width is *much* wider than the
   viewport — content extends to however many columns are needed —
   and the transform translates the strip left to show the active
   spread. Translation is instant per the writer's no-animation
   preference; spread changes snap without easing. */
/* The .book element is now just a viewport — fixed size, one
   active panel at a time. Earlier shape gave it the column-
   fill / column-width / translateX of the entire book in one
   container; resize and font-size reflows then had to repaginate
   every chapter in a single pass. The .panel.chapter rule below
   moves the multi-column container down to each chapter, so the
   per-chapter reflow only touches the active chapter's contents. */
.book {
  position: relative;
  height: 100%;
  width: 100%;
  overflow: hidden;
}

/* ---- Panels --------------------------------------------------- */
/* Each top-level article (cover, title-page, every chapter) is a
   panel. Default state is display: none — only the one with
   .active is laid out and painted. position: absolute + inset 0
   means the panel fills the viewport when active; the parent
   .book clips overflow.
   `will-change: transform` is here rather than on .book because
   the active chapter panel is the element that animates, and
   marking will-change on the right element lets the browser
   keep a single composited layer per panel instead of one for
   the whole strip. */
.panel {
  position: absolute;
  inset: 0;
  display: none;
  background: var(--page-bg);
  /* overflow stays visible — the chapter panel's column strip is
     wider than the panel box and extends horizontally past
     panel.right. The transform on .panel.chapter translates the
     panel itself off to the left for paginated spreads, which
     puts panel.right past the viewport's left edge; the columns
     that should be visible are positioned PAST the panel's
     right edge by the column engine. Clipping at .panel would
     hide those columns before they reach the viewport. .book
     (already overflow: hidden) handles the actual viewport
     clipping. */
  will-change: transform;
}
.panel.active {
  display: block;
}

/* Chapter panels — internal multi-column for pagination. The
   column strip is wider than the panel (extends past the right
   edge) and translateX slides it leftward so each spread shows
   2 columns.
   `column-fill: auto` needs the container's height to be
   resolved so the column engine knows when to wrap to the next
   column. inset: 0 from .panel sets top + bottom, which gives
   the panel a resolved height in principle — but some browsers
   under `contain: layout` / `position: absolute` only honour
   that height for box-model layout, not as the height the
   multi-column engine sees. Pinning `height: 100%` explicitly
   makes the resolution unambiguous: without it, the engine
   would treat the column container as having `height: auto` and
   produce one tall column the height of the entire chapter
   body, then split THAT into N tall-and-narrow columns to fit
   horizontally — which renders most of the strip as blank
   pages because the per-column content count is near-zero. */
.panel.chapter {
  height: 100%;
  padding: var(--margin-v) var(--margin-h);
  column-width: var(--col-w);
  column-gap: var(--gap);
  column-fill: auto;
  transform: translateX(var(--offset));
}

/* Cover / title-page / Part-divider panels — single-page
   presentations. The wide-layout variant lands them on the
   *right* page of a two-page spread with a blank verso on the
   left, the way a printed book opens. The narrow-layout
   variant (single page per spread) just fills the page; the
   right-page offset doesn't apply. */
.panel.cover.active,
.panel.title-page.active {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--margin-v) var(--margin-h);
}
@media (min-width: 721px) and (min-aspect-ratio: 1/1) {
  .panel.cover.active,
  .panel.title-page.active,
  .panel.section-part.active {
    /* Push the centred content over to the right page. Left
       padding = margin-h + one column width + the inter-column
       gap, so the centring lands inside what would have been
       column 2 of a chapter spread. The verso (left) page reads
       as blank — same convention as the strip-based reader,
       just achieved via padding instead of an inserted blank. */
    padding-left: calc(
      var(--margin-h) + var(--col-w) + var(--gap)
    );
    padding-right: var(--margin-h);
  }
  /* Chapter panels open on a recto — same convention as cover /
     title-page / Part dividers, achieved here via a pseudo
     element that claims column 0 of the chapter's multi-column
     strip. `break-after: column` pushes the chapter-heading to
     column 1 (the right page of spread 0); column 0 reads as a
     blank verso. Excludes .section-part (which is display: flex,
     not multi-column — handled by the padding rule above). The
     pseudo is the FIRST flowing element in the column container,
     so the heading naturally lands in column 1 once the column
     break fires.
     Narrow mode (single-page spreads) intentionally skips this:
     a blank wedge would just be an extra blank page with no
     recto/verso convention to justify it. */
  .panel.chapter:not(.section-part)::before {
    content: "";
    display: block;
    break-after: column;
  }
}

/* ---- Spine shadow --------------------------------------------- */
/* A subtle dark band centred horizontally — sits in the column
   gap between the two pages and reads as the binding of an open
   book. The gradient is symmetric: darkest at the exact centre
   (the binding), fading to transparent on both sides. Widths
   scale with `--gap` so the shadow always lives in the actual
   gutter, not a fixed pixel band that drifts as the layout
   reflows.
   `position: fixed` (not absolute inside .viewport) so the shadow
   spans the full window height — including up behind the title
   strip, where stopping at the viewport's top edge would otherwise
   leave a visible seam that breaks the "open book" effect. The
   titlebar's z-index: 10 keeps its text painted ON TOP of the
   spine, so readability isn't dimmed. Page chrome (page numbers,
   running header) is position: fixed too and lands above the
   spine via source order. */
.spine {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* Asymmetric gradient — simulates light coming from the upper
     LEFT (the convention for most book mock-ups and for left-to-
     right Western reading habits). The left tail is shorter and
     lighter (page surface facing the light is brighter, shadow
     drops off quickly), the right tail is longer and darker
     (page surface in the shadow of the binding ridge holds the
     occlusion further across the gutter). Centre peak stays at
     the binding axis at 0.09. Tweak `1.5`/`0.8` to drag the
     light source angle; flip the two sides' values to invert it. */
  background: linear-gradient(
    to right,
    transparent calc(50% - var(--gap) * 0.8),
    rgba(0, 0, 0, 0.01) calc(50% - var(--gap) * 0.4),
    rgba(0, 0, 0, 0.03) calc(50% - var(--gap) * 0.15),
    rgba(0, 0, 0, 0.065) 50%,
    rgba(0, 0, 0, 0.05) calc(50% + var(--gap) * 0.2),
    rgba(0, 0, 0, 0.025) calc(50% + var(--gap) * 0.65),
    transparent calc(50% + var(--gap) * 1.5)
  );
}

/* ---- Blank-page placeholders ---------------------------------- */
/* Two of these can appear in the document: one before the cover
   (forces the cover onto column 1 = right of spread 0), and one
   between the cover and the title page when a cover is present
   (forces the title page onto column 3 = right of spread 1). The
   `&nbsp;` content gives the element a non-zero box so the column
   engine recognises it as occupying a column — without that the
   browser collapses the empty column and the next element bleeds
   onto the slot we wanted blank. `visibility: hidden` keeps the
   text invisible while preserving the layout space, and
   `break-after: column` forces the next element to start fresh on
   the column after. */
.blank {
  /* `break-before: column` + `break-after: column` is exactly
     enough — the blank's box is small but it sits at the top of
     a fresh column (break-before forces a new column) and the
     break-after prevents any following content from flowing
     into the remaining space, so the column reads as empty.
     Deliberately NOT pinning `height: 100%`: in WebKit's
     multi-column engine, a transition from a height-constrained
     element to an unconstrained one (e.g. `.title-page` /
     recto-blank → `.chapter`) silently inserts a phantom empty
     column between them, which throws off the offset tracking
     in the recto-promotion fixup (one inserted blank ends up
     shifting subsequent chapters by *two* columns, not one).
     Keeping the blank as an unconstrained-height box matches
     the chapter article it precedes, so the transition stays
     clean. */
  break-before: column;
  break-after: column;
  visibility: hidden;
}

/* ---- Cover ----------------------------------------------------- */
/* Image scales to fit, preserving aspect ratio. Flat — no drop
   shadow — so the cover reads as itself rather than a faux-3D
   book object on the page. The `.panel.cover.active` rule above
   handles the display + alignment; the legacy `display: flex` /
   `break-before: column` declarations that used to live here
   were dropped on the move to the per-panel model — they fought
   the .panel display: none rule at the same specificity, source-
   order won, and the cover kept rendering even when the panel
   below it had `.active`. */
.cover img {
  max-width: 90%;
  max-height: 90%;
  height: auto;
  display: block;
  border-radius: 2px;
}

/* ---- Title page ------------------------------------------------ */
/* Centred metadata block — series, title, subtitle, author. The
   `.panel.title-page.active` rule above sets display: flex; this
   one only adds the inside-the-flex-column variants. Same
   rationale as .cover for dropping the legacy `display: flex`
   here. */
.title-page {
  flex-direction: column;
  text-align: center;
}
.title-page-series {
  margin: 0 0 2.5em;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  font-size: 0.85em;
  color: var(--muted);
}
.title-page-title {
  margin: 0 0 0.5em;
  font-size: 2.4em;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
}
.title-page-subtitle {
  margin: 0 0 2em;
  font-style: italic;
  color: var(--muted);
  font-size: 1.15em;
}
.title-page-author {
  margin: 3em 0 0;
  font-style: italic;
  font-size: 1.05em;
}

/* ---- Ishmael-attribution logo on the title-page verso -------- */
/* A clickable "Made with Ishmael" logo. Sits on the blank verso (left
   page) of the title-page spread in wide mode. The element is
   absolute-positioned so it doesn't disturb the title block's
   flex-centring on the recto (right page). Two stacked children
   share the same source image (the transparent-background Ishmael
   figure):
     - .monk-logo-silhouette   uses the image as a CSS mask and
                               paints --page-text through its alpha,
                               so the default look is a flat
                               silhouette in the reader's current
                               text colour.
     - .monk-logo-color        is the raw colour layer, opacity 0
                               by default, fading in on hover.
   Cross-fading the two layers (rather than animating a single
   filter chain) gives a clean transition because mask-image is
   not interpolable. The shared asset means there's one image to
   ship, not two.
   The size (128 × 192, the figure's 2:3 ratio) and centring are
   computed against the verso half of the title-page panel — width
   = margin-h + col-w + gap (the same offset the wide-mode
   `padding-left` reserves), so the logo lands optically in the
   middle of the verso regardless of column-width changes from
   font-size shifts. */
.panel.title-page .monk-logo {
  position: absolute;
  top: 50%;
  left: calc((var(--margin-h) + var(--col-w) + var(--gap)) / 2);
  transform: translate(-50%, -50%);
  display: block;
  width: 128px;
  height: 192px; /* 2:3, matching the portrait */
  /* Keep the anchor invisible to the layout pass — display is
     only a containment box for the two layered children. */
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}
.monk-logo-silhouette,
.monk-logo-color {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Both layers fade together — duration tuned to feel deliberate
     without dragging out the way the page-turn snaps elsewhere. */
  transition: opacity 200ms ease;
  pointer-events: none;
}
.monk-logo-silhouette {
  background-color: var(--page-text);
  /* `--monk-logo-uri` is `url("data:image/webp;base64,…")` — set in
     the inline `<style>` block in index.html.j2 from a Jinja include
     of `assets/monk-logo.b64`. We deliberately don't reference the
     asset via a file path here: file:// pages in Chromium reject
     `mask: url("./assets/…")` under the null-origin policy, and
     even `<img src="./assets/…">` fails the same way, so an export
     opened off disk would render no logo at all. data: URIs are
     exempt from origin checks. */
  -webkit-mask: var(--monk-logo-uri) center / contain no-repeat;
  mask: var(--monk-logo-uri) center / contain no-repeat;
  opacity: 1;
}
.monk-logo-color {
  background: var(--monk-logo-uri) center / contain no-repeat;
  opacity: 0;
}
.panel.title-page .monk-logo:hover .monk-logo-silhouette,
.panel.title-page .monk-logo:focus-visible .monk-logo-silhouette {
  opacity: 0;
}
.panel.title-page .monk-logo:hover .monk-logo-color,
.panel.title-page .monk-logo:focus-visible .monk-logo-color {
  opacity: 1;
}
/* Narrow-mode (single-page spread) hides the logo — there is no
   verso to fill, and overlaying the logo on the title block itself
   would compete with the writer's metadata. */
@media (max-width: 720px), (max-aspect-ratio: 1/1) {
  .panel.title-page .monk-logo {
    display: none;
  }
}

/* ---- Chapter --------------------------------------------------- */
/* Each chapter starts on a fresh column (break-before: column).
   The heading sits flush at the column top with a generous gap
   before the first paragraph; the body flows freely through
   subsequent columns.
   `break-after: column` mirrors `break-before` so a chapter sits
   between explicit column boundaries on both sides. Without the
   matching break-after, WebKit's multi-column engine appears to
   treat a preceding `.blank`'s `break-after` and a chapter's
   `break-before` as TWO independent breaks (inserting a phantom
   empty column between them) rather than merging them — every
   recto-blank inserted by `fixupRectoChapters` then shifts
   subsequent chapters by two columns instead of one and the
   offset math falls apart. The same symmetric break shape is
   already present on `.cover` and `.title-page`, and those
   transitions show no phantom in measurement. */
/* Chapter-internal containment. The earlier break-before /
   break-after column rules were here when all chapters flowed
   into one strip on .book; with the per-panel model each chapter
   is its own column container, so the column-break rules are
   irrelevant. The contain: layout style still helps the browser
   confine layout / style invalidation to the chapter subtree on
   any inside-chapter mutation. */
.chapter {
  contain: layout style;
}
/* ---- Shared prose typography ----------------------------------
   The chapter/Part heading type and the whole .chapter-body prose
   region are shared verbatim with the in-app Reader
   (templates/internal/reader-spread/style.css) via
   templates/_shared/reader-prose.css — edit typography THERE so
   both surfaces stay in step. Surface-specific rules (panels,
   tap zones, chrome, cover presentation, ...) stay in this file.
   Rendered through MiniJinja (see template.toml: this file is a
   `template` entry, not a `copy`). */
/*
 * Shared Reader prose typography — the `.chapter-body` reading
 * region plus the chapter/Part heading type. Included (via a
 * Jinja include tag) by BOTH reader stylesheets:
 *
 *   - templates/internal/reader-spread/style.css   (in-app Reader)
 *   - templates/html/book/monk-reader/style.css    (HTML export)
 *
 * The two surfaces render the same book typography; before this
 * file existed the rules were duplicated and kept drifting — a
 * fix landed in one file and got missed in the other. Everything
 * here must be identical on both surfaces. Layout/chrome (columns
 * engine, panels, titlebar, cover presentation, nav buttons,
 * theme vars) stays in each template's own style.css.
 *
 * Divergent-rule convention: each template stamps a surface class
 * on <body> — `body.reader-app` (in-app Reader window) and
 * `body.reader-export` (exported HTML). When a prose rule must
 * genuinely differ per surface, express both variants here keyed
 * off those classes rather than forking the rule back into the
 * local files. (No rule needs it yet: the one known divergence —
 * the in-app Reader inherits `text-wrap: pretty` from its
 * html/body rule while the export deliberately stays on the
 * greedy default wrapper for resize performance — lives on the
 * body root in each local file, outside this region.)
 *
 * IMPORTANT: this file is rendered through MiniJinja, so its
 * content must not contain Jinja delimiter sequences (an open
 * brace followed by a percent sign or a second open brace) —
 * CSS never needs them, but keep comments clear of them too.
 */

/* ---- Chapter heading ------------------------------------------- */
/* Centred heading block at the top of each chapter's first page:
   small-caps chapter number over an italic title. */
.chapter-heading {
  text-align: center;
  margin: 1em 0 3em;
  padding-top: 1em;
}
.chapter-number {
  margin: 0 0 1em;
  font-size: 0.78em;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 400;
}
.chapter-title {
  margin: 0;
  font-size: 1.55em;
  font-weight: 500;
  font-style: italic;
  line-height: 1.25;
  color: var(--page-text);
}

/* ---- Part divider typography ----------------------------------- */
/* The panel/flex layout that centres a Part divider on its own
   page is surface-specific and lives in each local style.css;
   the type treatment of the divider itself is shared. */
.section-part .chapter-heading {
  /* Override the regular chapter-heading top padding — for a
     Part page we want the title block sized to its content and
     vertically centred by the flex column above, not pushed
     down by chapter-heading's 1em top padding. */
  margin: 0;
  padding-top: 0;
  max-width: 32em;
}
.section-part .chapter-title {
  /* Larger, weightier, upright — Part titles read as display
     typography rather than the body-italic chapter heads. */
  font-size: 2.4em;
  font-weight: 600;
  font-style: normal;
  letter-spacing: 0.02em;
}
.section-part .chapter-subtitle {
  /* Italic line under the title — same "set-apart" voice the
     title page's series + author lines have, dialled to a
     comfortable distance from the title above. */
  margin: 1em 0 0;
  font-size: 1.1em;
  font-style: italic;
  font-weight: 400;
  line-height: 1.4;
  color: var(--muted);
}
/* A Part divider has no body and no raised-cap; suppress the
   .chapter-body slot just in case the template fell through to
   it (e.g. a writer flipped a populated chapter to Part). The
   template already gates the body block on `section != "Part"`,
   so this rule is belt-and-braces against future template
   refactors. */
.section-part .chapter-body {
  display: none;
}

/* Inline links inside the chapter body — both external
   (http(s)…) and intra-book (book:…) wear the same accent ink
   so the reader's eye groups them as "linked". Underline is
   the UA default; we just colour the ink. --link-color
   resolves per theme (see :root and html.dark in the local
   stylesheets) so the ink stays at WCAG AA against either page
   background. */
.chapter-body a {
  color: var(--link-color);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}
.chapter-body a:hover {
  text-decoration-thickness: 2px;
}

/* ---- Chapter body ---------------------------------------------- */
.chapter-body {
  hyphens: auto;
  /* Don't hyphenate the last line of a paragraph — the final line
     reads as the resolution, and a trailing hyphen there is the
     kind of break a typesetter would manually push to the next
     paragraph. Safari 17 / Chrome / Firefox honour this; older
     engines ignore the property and fall back to the default
     last-line-may-hyphenate behaviour. */
  hyphenate-limit-last: always;
  /* font-family inherits from body, which is var(--reader-font). */
}
.chapter-body p {
  margin: 0;
  text-indent: 1.5em;
  /* Default alignment falls back to left — the body gets
     .align-justify or .align-ragged from the template based on
     Settings → Reader → Justify text, and the rule below keys
     off whichever class is present. The default here is a
     safety net for older snapshots that don't carry either
     class; a ragged column is the least-surprising fallback. */
  text-align: left;
  /* text-wrap is a deliberate per-surface divergence, decided on
     each body root (outside this file): the in-app Reader
     (body.reader-app) sets `text-wrap: pretty` on html/body and
     it rides in here via inheritance — per-panel rendering
     scopes the reflow cost to one chapter. The export
     (body.reader-export) stays on the default greedy wrapper:
     `pretty` was tried there and dominated continuous-resize /
     font-size-tap reflows on long books. */
}
body.align-justify .chapter-body p {
  text-align: justify;
}
body.align-ragged .chapter-body p {
  text-align: left;
}
/* List items stay left-aligned even when the body is justified —
   bullet rows look broken under justify. Specificity (0,3,3)
   beats the (0,2,2) of the body.align-justify rule above. */
body.align-justify .chapter-body li,
body.align-justify .chapter-body li p {
  text-align: left;
}
/* Classic novel convention: flush first paragraph after a heading,
   scene break, or full-width figure. Embedded images and their
   `<figure>` wrappers (when captioned) create the same kind of
   visual reset that a scene divider does, so the paragraph
   immediately after one reads as fresh prose rather than
   continuation — matches the convention every trade-fiction
   typesetter uses. Every heading level (h1–h6) counts — sub-
   headings open a fresh section the same way a chapter title
   does. */
.chapter-body > p:first-of-type,
.chapter-body > h1 + p,
.chapter-body > h2 + p,
.chapter-body > h3 + p,
.chapter-body > h4 + p,
.chapter-body > h5 + p,
.chapter-body > h6 + p,
.chapter-body > .scene-break + p,
.chapter-body > .page-break + p,
.chapter-body > p.caption + p,
.chapter-body > p.subtitle + p,
.chapter-body > p.epigraph + p,
.chapter-body > p.attribution + p,
.chapter-body > blockquote + p,
.chapter-body > img + p,
.chapter-body > figure + p,
/* A list visually breaks the prose flow, so the paragraph that
   follows it reads as starting fresh and the first-line indent
   would compete with the list-vs-prose separation. */
.chapter-body > ul + p,
.chapter-body > ol + p {
  text-indent: 0;
}

/* A heading that opens a fresh page after a page break doesn't need its
   usual top margin: the break already starts a new column/page, so the
   margin just pushes the heading down from the page top. Zero it out
   (higher specificity than the base h2–h4 margin rule below wins). */
.chapter-body > .page-break + h1,
.chapter-body > .page-break + h2,
.chapter-body > .page-break + h3,
.chapter-body > .page-break + h4,
.chapter-body > .page-break + h5,
.chapter-body > .page-break + h6 {
  margin-top: 0;
}

/* Subtitle: a tagline beneath the chapter title or a section
   header inside the body. Italic centered display type, sized
   between body and chapter-title so it reads as set-apart but
   not as another heading. text-indent: 0 overrides the body-p
   rule above; vertical margin gives it breathing room from the
   surrounding prose. */
.chapter-body p.subtitle {
  text-indent: 0;
  text-align: center;
  font-size: 1.2em;
  font-style: italic;
  font-weight: 400;
  line-height: 1.3;
  margin: 1.2em 0 1em;
}

/* Epigraph: the short quotation opening a chapter — inset from both
   margins, italic, full text colour (meant to be read, unlike the
   muted blockquote). Mirrors the editor's treatment. */
.chapter-body p.epigraph {
  text-indent: 0;
  font-style: italic;
  /* Generous top margin sets the pair off from what precedes it; the
     bottom is mostly consumed by the attribution's tuck below. */
  margin: 1.6em 2em 1.2em;
}

/* Attribution: the "— Author, Source" credit under a quote — flush
   right in letterspaced small caps, mirroring the editor. The bottom
   margin sets the credit (and the quotation it closes) off from the
   prose that resumes below — the base `.chapter-body p` margin is 0,
   which left the next paragraph crowding it. */
.chapter-body p.attribution {
  text-indent: 0;
  text-align: right;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.08em;
  margin-bottom: 1.2em;
}
/* Tuck the credit up under the quotation it belongs to. */
.chapter-body blockquote + p.attribution {
  margin-top: -0.6em;
}
/* The epigraph pairing tucks tighter (the negative top collapses
   against the epigraph's 1.2em bottom → ~0.3em gap, one set piece),
   pulls its right edge in to match the epigraph's inset, and gives
   the pair as a whole clear air below before the prose resumes. */
.chapter-body p.epigraph + p.attribution {
  margin-top: -0.9em;
  margin-right: 2em;
  margin-bottom: 1.6em;
}
/* Alignment guard: the align-mode rules (`body.align-justify
   .chapter-body p`, (0,2,2)) outgun the display blocks' (0,2,1)
   alignment, and one mode class is always present — a one-line
   paragraph under justify renders as its own last line, i.e. LEFT.
   Re-assert per mode so subtitles stay centred, epigraphs ragged,
   and attributions flush right regardless of the justify setting. */
body.align-justify .chapter-body p.subtitle,
body.align-ragged .chapter-body p.subtitle {
  text-align: center;
}
body.align-justify .chapter-body p.epigraph,
body.align-ragged .chapter-body p.epigraph {
  text-align: left;
}
body.align-justify .chapter-body p.attribution,
body.align-ragged .chapter-body p.attribution {
  text-align: right;
}

/* First-letter cap on the first BODY paragraph of each chapter.
   The cap is a "narrative begins here" signal — typographically
   it never applies to set-apart matter like a subtitle,
   epigraph, or caption, even when those land first inside the
   chapter body.
   Implementation: a `<span class="dropcap">` is wrapped around
   just the first Unicode letter of the first body paragraph
   (see wrapInitialCap in reader-spread.html.j2). The wrap skips
   any leading punctuation — opening quotes, parens, brackets,
   ellipses — so a paragraph that starts with `"Hello` enlarges
   only the H, with the opening quote staying at body size right
   beside it. (Pure CSS `::first-letter` couldn't do this; the
   spec defines it to *include* the punctuation block.)
   The style is mode-gated by a class on <body>:
     - body.cap-raised: larger inline letter on the first line
       (the historical Reader default).
     - body.cap-drop: floated multi-line cap, classic book-design
       opening.
     - body.cap-none: nothing — the letter renders at body size
       (no rule fires for this class). */
body.cap-raised .chapter-body .dropcap {
  font-size: 3.2em;
  /* 0, not ~1: the cap's inline box must not size the line box,
     or its descent pushes the second line down and breaks the
     leading rhythm (masked at 1.65 spacing, visible at 1.2/1.4
     and in the EPUB preview). The oversized glyph simply rises
     out of a normal-height first line. */
  line-height: 0;
  font-weight: 600;
  margin-right: 0.05em;
  vertical-align: -0.06em;
  color: inherit;
  font-style: normal;
}
/* Raised-cap height ladder — Settings exposes the multiple
   directly; the value rides on <body data-cap-scale>. The
   unattributed rule above stays as the fallback for surfaces
   that don't set the attribute (exported HTML books). */
body.cap-raised[data-cap-scale="2"] .chapter-body .dropcap {
  font-size: 2em;
}
body.cap-raised[data-cap-scale="2.5"] .chapter-body .dropcap {
  font-size: 2.5em;
}
body.cap-raised[data-cap-scale="3"] .chapter-body .dropcap {
  font-size: 3em;
}
body.cap-raised[data-cap-scale="3.5"] .chapter-body .dropcap {
  font-size: 3.5em;
}
body.cap-drop .chapter-body .dropcap {
  float: left;
  /* 2-line drop — the modern paperback convention. A 3-line cap
     (font-size ~4em / line-height 0.85) is the classical book-
     design alternative; switch back to those numbers if the
     Reader's typographic stance shifts toward fine-press. */
  font-size: 2.8em;
  line-height: 0.88;
  font-weight: 600;
  /* Vertical fine-tune so the cap's top sits flush with the
     first text line's cap height (rather than its ascender) and
     its bottom rests on the second line's baseline. The 0.03em
     base is calibrated for Literata at 2 lines; the two custom
     properties layer the pixel-probed corrections for deeper
     caps (line-height shifts) and for the ornate faces (whose
     ascender metrics park the ink well above Literata's) so the
     depth x face combinations compose without a rule per pair. */
  margin-top: calc(0.03em + var(--cap-lines-nudge, 0em) + var(--cap-font-nudge, 0em));
  margin-right: 0.08em;
  /* Mirror the nudges so the float's total vertical footprint
     (margin-top + box + margin-bottom) stays constant — the
     corrections move the ink, not the number of lines the cap
     displaces. Without this a nudged face (e.g. Yinit's 0.278em)
     pushes the wrap onto an extra line. */
  margin-bottom: calc(-0.04em - var(--cap-lines-nudge, 0em) - var(--cap-font-nudge, 0em));
  color: inherit;
  font-style: normal;
}
/* Drop-cap depth ladder (<body data-cap-lines>). The 2-line size
   is the hand-tuned base rule above; deeper caps trade a slightly
   tighter line-height for a flush top — the 3-line numbers are
   the ones the base rule's comment records as the fine-press
   alternative. */
body.cap-drop[data-cap-lines="3"] .chapter-body .dropcap {
  font-size: 4.1em;
  line-height: 0.86;
  --cap-lines-nudge: -0.041em;
}
body.cap-drop[data-cap-lines="4"] .chapter-body .dropcap {
  font-size: 5.4em;
  line-height: 0.85;
  --cap-lines-nudge: -0.051em;
}
/* Hanging punctuation in cap-drop mode. When the paragraph
   opens with quote / paren / bracket / ellipsis before the
   first letter, those characters used to sit visually in front
   of the floated dropcap — reading order was correct but the
   layout shoved the cap to the right of the punctuation. Now
   the leading punct hangs in the page margin to the left of
   the column.
   The technique: absolute-position the punct outside the
   column. The dropcap-host class (added by wrapFirstLetter in
   the bootstrap JS) gives the paragraph position: relative so
   the absolute child anchors to it. `right: 100%` aligns the
   punct's right edge with the column's left edge; the small
   extra `+ 0.08em` puts a hair of space between the punct and
   the column. Because position: absolute removes the punct
   from the inline flow, the floated dropcap that follows is
   layout-neutral and sits at the column's left edge as if
   the punct weren't there. */
body.cap-drop .chapter-body p:has(.dropcap),
body.cap-drop .chapter-body p.dropcap-host {
  position: relative;
}
body.cap-drop .chapter-body .dropcap-punct {
  position: absolute;
  right: calc(100% + 0.08em);
  top: 0;
  font-size: 1em;
  font-weight: inherit;
  font-style: inherit;
  color: inherit;
  white-space: nowrap;
}
/* Ornate cap typefaces (Settings → Reader → Cap typeface) — the
   face rides <body data-cap-font>, same channel as the size
   ladders. Weight resets to normal: the base cap rules ask for
   600 and synthetic bolding mangles a single-weight ornate face.
   The stack falls back to the reading face per-glyph, so a
   letter the face lacks (or a missing font file) renders
   plainly rather than as tofu. Surfaces that never set the
   attribute (exported HTML books) keep the reading face. */
body[data-cap-font="berkshire-swash"] .chapter-body .dropcap {
  font-family: "Berkshire Swash", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="carved-medieval"] .chapter-body .dropcap {
  font-family: "Carved Medieval", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="marcellus-sc"] .chapter-body .dropcap {
  font-family: "Marcellus SC", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="cinzel-decorative"] .chapter-body .dropcap {
  font-family: "Cinzel Decorative", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="uncial-antiqua"] .chapter-body .dropcap {
  font-family: "Uncial Antiqua", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="unifraktur"] .chapter-body .dropcap {
  font-family: "UnifrakturMaguntia", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="goudy-initialen"] .chapter-body .dropcap {
  font-family: "Goudy Initialen", var(--reader-font, serif);
  font-weight: normal;
}
body[data-cap-font="yinit"] .chapter-body .dropcap {
  font-family: "Yinit", var(--reader-font, serif);
  font-weight: normal;
}
/* Ornate-face vertical corrections (drop mode only) — pixel-probed
   ink-top offsets vs Literata; see the margin-top calc in the base
   cap-drop rule. */
body.cap-drop[data-cap-font="berkshire-swash"] .chapter-body .dropcap {
  --cap-font-nudge: 0.079em;
}
body.cap-drop[data-cap-font="carved-medieval"] .chapter-body .dropcap {
  --cap-font-nudge: 0.159em;
}
body.cap-drop[data-cap-font="marcellus-sc"] .chapter-body .dropcap {
  --cap-font-nudge: 0.079em;
}
body.cap-drop[data-cap-font="cinzel-decorative"] .chapter-body .dropcap {
  --cap-font-nudge: 0.179em;
}
body.cap-drop[data-cap-font="goudy-initialen"] .chapter-body .dropcap {
  --cap-font-nudge: 0.119em;
}
body.cap-drop[data-cap-font="uncial-antiqua"] .chapter-body .dropcap {
  --cap-font-nudge: 0.099em;
}
body.cap-drop[data-cap-font="unifraktur"] .chapter-body .dropcap {
  --cap-font-nudge: 0.159em;
  /* Flourish ink overhangs the advance width — widen the
     clearance so the tips don't touch the text. */
  margin-right: 0.12em;
}
body.cap-drop[data-cap-font="yinit"] .chapter-body .dropcap {
  --cap-font-nudge: 0.278em;
  /* Flourish ink overhangs the advance width — widen the
     clearance so the tips don't touch the text. */
  margin-right: 0.13em;
}

/* Engine-computed drop caps — `initial-letter` sizes and aligns
   the cap to span exactly N lines, correct for any body face and
   line spacing, so the hand-tuned float geometry above becomes
   the fallback (Firefox and Chromium on the HTML export; any
   engine without the property). Three load-bearing details:
     - WebKit only honours initial-letter on ::first-letter, not
       on spans, so the enhancement rides the paragraph's pseudo
       and the .dropcap span is neutralised alongside it.
     - ::first-letter includes preceding punctuation by spec,
       which would drop-cap an opening quote — punct-led
       paragraphs carry .dropcap-punct-host (added by the wrap
       JS) and keep the float path with its hanging-punct
       treatment.
     - The ornate families must be restated on the pseudo — the
       span's data-cap-font rules don't reach it.
     - The gate is the -webkit- form ONLY, deliberately: Blink
       implements just the unprefixed property, and its alignment
       math rides a different metric that parks the cap visibly
       high of the text top. Here (unlike EPUB) the typography is
       pinned, so the calibrated float is pixel-right in Chromium
       — better than Blink's enhancement. WebKit (the in-app
       Reader, Safari on the export) keeps the engine path. */
@supports (-webkit-initial-letter: 2) {
  body.cap-drop .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
    -webkit-initial-letter: 2 2;
    initial-letter: 2 2;
    /* em on an initial-letter pseudo resolves against the BODY
       font size, not the engine-computed cap size — so the
       clearance is (cap/body size ratio) x (gap in cap-relative
       em), both ridden in as custom properties: the depth rules
       set the ratio, faces with ink overhanging their advance
       width (Yinit, Unifraktur) raise the gap. */
    margin-right: calc(var(--cap-fs-ratio, 2.8) * var(--cap-gap, 0.08) * 1em);
    font-weight: 600;
    /* Upright always — a cap is a display versal, not part of an
       italic opening sentence's emphasis. The float path's base
       rules carry the same declaration. */
    font-style: normal;
  }
  body.cap-drop[data-cap-lines="3"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
    -webkit-initial-letter: 3 3;
    initial-letter: 3 3;
    --cap-fs-ratio: 4.1;
  }
  body.cap-drop[data-cap-lines="4"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
    -webkit-initial-letter: 4 4;
    initial-letter: 4 4;
    --cap-fs-ratio: 5.4;
  }
body.cap-drop[data-cap-font="berkshire-swash"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Berkshire Swash", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="carved-medieval"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Carved Medieval", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="marcellus-sc"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Marcellus SC", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="cinzel-decorative"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Cinzel Decorative", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="goudy-initialen"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Goudy Initialen", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="uncial-antiqua"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Uncial Antiqua", var(--reader-font, serif);
  font-weight: normal;
}
body.cap-drop[data-cap-font="unifraktur"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "UnifrakturMaguntia", var(--reader-font, serif);
  font-weight: normal;
  --cap-gap: 0.12;
}
body.cap-drop[data-cap-font="yinit"] .chapter-body p.dropcap-host:not(.dropcap-punct-host)::first-letter {
  font-family: "Yinit", var(--reader-font, serif);
  font-weight: normal;
  --cap-gap: 0.13;
}
  /* Neutralise the float span on enhanced paragraphs — its
     ladder font-size, float, and nudge margins only make sense
     for the fallback path. */
  body.cap-drop .chapter-body p.dropcap-host:not(.dropcap-punct-host) .dropcap {
    float: none;
    font-size: inherit;
    line-height: inherit;
    margin: 0;
    font-weight: inherit;
  }
}

body.cap-raised[data-cap-font="yinit"] .chapter-body .dropcap {
  margin-right: 0.13em;
}
body.cap-raised[data-cap-font="unifraktur"] .chapter-body .dropcap {
  margin-right: 0.12em;
}
/* body.cap-none — intentionally no rule. The first letter
   renders at the surrounding paragraph size. */

.chapter-body em,
.chapter-body i {
  font-style: italic;
}
.chapter-body strong,
.chapter-body b {
  font-weight: 700;
}

.chapter-body h2,
.chapter-body h3,
.chapter-body h4 {
  margin: 2em 0 0.6em;
  font-weight: 600;
  text-align: left;
  text-indent: 0;
}
.chapter-body h2 { font-size: 1.35rem; }
.chapter-body h3 { font-size: 1.18rem; }
.chapter-body h4 { font-size: 1.05rem; }

.chapter-body blockquote {
  margin: 1.5em 2em;
  padding-left: 1em;
  border-left: 2px solid var(--rule);
  font-style: italic;
  color: var(--muted);
  text-indent: 0;
}
.chapter-body blockquote p {
  text-indent: 0;
}

/* Lists — mirror the editor-side rules. Reset text-indent and
   paragraph margins so list rows read as single typographic lines
   rather than full-margin justified body paragraphs. The padding
   keeps the marker glyph in the prose column. */
.chapter-body ul,
.chapter-body ol {
  margin: 1em 0;
  padding-left: 2em;
  text-indent: 0;
}
.chapter-body ul { list-style: disc; }
.chapter-body ol { list-style: decimal; }
.chapter-body li {
  margin: 0.25em 0;
  text-align: left; /* override .chapter-body p's justify */
  text-indent: 0;
}
.chapter-body li > p {
  margin: 0;
  text-indent: 0;
  text-align: left;
}

/* Scene break — the editor exports a <div class="scene-break"> with
   an inline SVG; we hide the SVG and render a centred dinkus via
   ::before so the look is consistent regardless of the editor's
   drawing. break-inside: avoid keeps the dinkus on one column. */
.chapter-body .scene-break {
  margin: 2em 0;
  text-align: center;
  color: var(--muted);
  letter-spacing: 0.6em;
  text-indent: 0;
  break-inside: avoid;
}
.chapter-body .scene-break svg {
  display: none;
}
.chapter-body .scene-break::before {
  content: "* * *";
}

/* Images — constrain to column size and avoid splitting across
   columns. Captions sit centred underneath in muted italic. */
.chapter-body figure {
  margin: 2em auto;
  text-align: center;
  text-indent: 0;
  break-inside: avoid;
}
.chapter-body img,
.chapter-body figure img {
  max-width: 100%;
  border-radius: 2px;
  /* Sizing comes from per-image inline style emitted by the
     media-src rewrite (inlineMediaSrcDataUri in the live Reader,
     the export's sibling-file rewrite in monk-reader) based on
     the Image Properties dialog: `width: 100%; height: auto;
     max-height: NNvh; object-fit: contain` (NN default 30). The
     image grows to the column width capped at the writer-set
     viewport-height percentage; a square / tall image grows to
     the cap then sits centred, aspect intact. */
}
/* Dark mode: invert embedded SVG diagrams / line art so a black-
   stroke-on-transparent figure stays visible against the dark
   reading surface. invert(1) flips luminance; hue-rotate(180deg)
   keeps coloured strokes close to their intended hue rather than
   cyan-shifting. Targets data-extension="svg" rather than the
   src URL — the Reader inlines each image's bytes as a data:
   URI, so the src never carries the file extension. ImageNode's
   renderHTML stamps the extension onto the saved HTML, and the
   inliner preserves attributes, so the data-attribute rides
   through to the rendered DOM. Mirrors the editor-side rule.
   Excludes the cover by design — that image is a designed
   asset meant to look like a printed cover regardless of
   theme, never line art. */
html.dark .chapter-body img[data-extension="svg"],
html.dark .chapter-body figure img[data-extension="svg"] {
  filter: invert(1) hue-rotate(180deg);
}
.chapter-body figcaption,
.chapter-body p.caption {
  margin: 0.7em auto 2em;
  max-width: 80%;
  font-size: 0.9em;
  font-style: italic;
  color: var(--muted);
  text-align: center;
  text-indent: 0;
}

/* ---- Manual: keyboard shortcuts ------------------------------- */
/* `<kbd class="hotkey">` is emitted by the Help → Manual… pipeline
   (see src/hotkey-format.ts). Modelled on GitHub's kbd styling — a
   subtle pill with a thin border, a slightly darker bottom edge,
   and a 1-px inset bottom shadow so the box reads like a key
   that's been pressed flush. Inter for the face so the macOS
   symbols (⌘ ⌃ ⌥ ⇧ ↵ ⎋) render with their designed glyphs rather
   than a monospace font's stylised approximations. */
.chapter-body kbd.hotkey {
  display: inline-block;
  padding: 0.15em 0.45em;
  margin: 0 0.1em;
  border: 1px solid var(--rule);
  border-bottom-color: color-mix(in srgb, var(--text) 30%, var(--rule));
  border-radius: 5px;
  background: color-mix(in srgb, var(--text) 4%, var(--bg));
  box-shadow: inset 0 -1px 0
    color-mix(in srgb, var(--text) 25%, var(--rule));
  color: var(--text);
  /* System stack, deliberately NOT the bundled Inter: Inter draws the
     filled cursor-key triangles (▲▼◀▶) near full-height, while the
     system font's sit at the modifier glyphs' size — and the editor's
     keys panel + the EPUB pill already render on the system stack, so
     this keeps every surface's pills matching. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 0.82em;
  font-style: normal;
  font-weight: 500;
  line-height: 1;
  vertical-align: 0.08em;
  white-space: nowrap;
  /* `text-indent` is an inherited property, and `display:
     inline-block` makes this kbd establish its own block
     formatting context — so the 1.5em first-line indent from
     `.chapter-body p` was riding INTO every pill, padding the
     glyph rightward inside the box. Resetting to 0 here pulls
     the symbol back against the left padding where it belongs. */
  text-indent: 0;
}
html.dark .chapter-body kbd.hotkey {
  background: color-mix(in srgb, var(--text) 8%, var(--bg));
}

/* ---- Inline marks --------------------------------------------- */
/* Small caps (⇧⌘K in the editor). `small-caps` (not
   `all-small-caps`) keeps existing capitals at their full
   uppercase size and renders only lowercase letters as small caps
   — the classic typographer's behaviour. */
.chapter-body .small-caps {
  font-variant-caps: small-caps;
}
/* Monospace (⇧⌘M in the editor). Semi-bold weight compensates for
   monospace fonts' typically thinner default strokes so the mark
   reads as a clear typographic departure from the serif body. */
.chapter-body .monospace {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, "Liberation Mono", monospace;
  font-weight: 600;
}

/* ---- Part dividers -------------------------------------------- */
/* `.section-part` articles render as standalone divider pages —
   the writer's title sits centred on its own column with no
   chapter-numbered prefix, no body prose, and an optional
   subtitle paragraph italic below.
   In the per-panel model a Part divider behaves like the cover
   and title-page: single full-viewport centred presentation, no
   internal pagination. The `display: flex` lives ONLY on the
   `.active` variant so the same-specificity cascade trap that
   bit `.cover` / `.title-page` doesn't recur — an inactive Part
   panel needs to inherit `display: none` from `.panel`, not
   override it with the centring flex. */
.panel.section-part.active {
  display: flex;
}
.section-part {
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
/* .section-part heading typography is shared — see
   templates/_shared/reader-prose.css (included above). */

/* ---- Margin chrome -------------------------------------------- */
/* Fixed overlay on top of the spread. Pointer-events: none so the
   click-zones below catch every click. Slots sit in the page's
   outer margins — book title in the top-left, chapter title in
   the top-right, page numbers in the bottom outer corners. */
.chrome {
  position: fixed;
  /* Anchored below the title strip so chrome.top/bottom math is
     relative to the visible book area, not the whole window — the
     running header sits a comfortable distance below the strip
     and the page numbers below the bottom margin. */
  top: var(--titlebar-h);
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  font-family: "Literata", Georgia, serif;
  font-size: 0.78em;
  color: var(--muted);
  letter-spacing: 0.12em;
}
.chrome-header,
.chrome-footer {
  position: absolute;
  max-width: 40vw;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chrome-header {
  top: calc(var(--margin-v) / 2);
  font-style: italic;
}
.chrome-footer {
  bottom: calc(var(--margin-v) / 2);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.05em;
}
.chrome-left  { left:  var(--margin-h); text-align: left;  }
.chrome-right { right: var(--margin-h); text-align: right; }

/* ---- Onboarding hint ------------------------------------------ */
/* "◀ ▶ to navigate" — a pill at the bottom of the viewport that
   tells the writer how to flip pages on first open. JS fades it
   in shortly after the book settles and removes it on the first
   key press (or after a 5-second timeout). System font rather
   than Literata so the toast reads as OS-level chrome, distinct
   from the book's serif typography. */
.hint {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding: 8px 18px;
  background: rgba(26, 24, 21, 0.78);
  color: rgba(255, 255, 255, 0.94);
  border-radius: 999px;
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
    sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 240ms ease;
  z-index: 30;
}
.hint.visible {
  opacity: 1;
}
.hint-key {
  /* Tighten the gap between the two arrow glyphs so they read as
     "left-right" rather than two unrelated symbols. */
  font-size: 0.95em;
  line-height: 1;
}
.hint-key + .hint-key {
  margin-left: -0.25em;
}
.hint-text {
  margin-left: 0.25em;
}

/* ---- Touch tap-zones ------------------------------------------ */
/* Two full-height regions covering the left and right thirds of
   the viewport — taps in either turn the page. Shown only on
   coarse-pointer devices (the JS unhides the container at boot);
   on desktops the keyboard-cued .hint pill above does the
   onboarding instead. Pointer-events on the container are off so
   the middle third (titlebar buttons, body links, theme toggle)
   stays interactive. */
.tap-zones {
  position: fixed;
  /* Carve out the titlebar strip from the top so the chapters
     button (left edge), nav-prev/next pair and theme toggle
     (right edge) all stay tap-able on touch devices — the
     tap zones below would otherwise sit above the titlebar
     at z-index 12 (titlebar is 10) and swallow the tap. */
  inset: var(--titlebar-h) 0 0 0;
  z-index: 12;
  pointer-events: none;
}
.tap-zone {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 33.3333vw;
  margin: 0;
  padding: 0;
  border: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: transparent;
  cursor: pointer;
  pointer-events: auto;
  /* WebKit's grey flash on tap reads as broken interaction
     against the rest of Monk's silent transitions. */
  -webkit-tap-highlight-color: transparent;
  /* No focus ring on touch — taps shouldn't paint a keyboard
     affordance over the left / right thirds of the page. */
  outline: none;
  transition: color 320ms ease;
}
.tap-zone:focus,
.tap-zone:focus-visible {
  outline: none;
}
/* Scrim sits on a pseudo-element so the button keeps its
   edge-to-edge hit target (finger taps near the screen edge
   still register) while the visible fill inset 12 px from
   every edge with a 14 px border-radius — reads as a rounded
   panel, not a slab flush against the chrome. The hand SVG
   still flex-centres inside the button, which lands it at the
   centre of the scrim too (both are centred in the same box). */
.tap-zone::before {
  content: "";
  position: absolute;
  inset: 12px;
  border-radius: 14px;
  background: transparent;
  pointer-events: none;
  transition: background-color 320ms ease;
}
.tap-zone-prev { left: 0; }
.tap-zone-next { right: 0; }
.tap-hint-hand {
  width: 72px;
  height: 72px;
  /* Anchor for the press animation — translateY pivots from the
     glyph centre, so the visual "down/up" reads correctly. */
  transform-origin: 50% 50%;
  /* Soft drop-shadow so the hand lifts off the page bg below
     it — small radius, low opacity, no offset — just enough
     penumbra to catch the eye without reading as a UI badge. */
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.22));
  animation: tap-hint-press 1.4s ease-in-out infinite;
}
/* The glyph's paint lives on the path as a CSS `fill`, NOT via the
   markup's fill="currentColor": WebKit doesn't reliably repaint a
   currentColor presentation attribute when a state class changes
   the inherited color, which left the hand wearing the light
   theme's ink on dark-mode iPhones. A CSS fill wins over the
   attribute and repaints per-state correctly. Resting transparent →
   visible over the same 320 ms as the scrim. */
.tap-hint-hand path {
  fill: transparent;
  transition: fill 320ms ease;
}
/* Press / release cycle. Down-and-into-the-page on the press
   (translateY + small scale-down), back up on release. The
   slight scale on press sells the depth — without it, the
   translate alone reads as a slide rather than a tap. */
@keyframes tap-hint-press {
  0%, 100% {
    transform: translateY(0) scale(1);
  }
  45% {
    transform: translateY(5px) scale(0.92);
  }
}
/* Visible state — 5-second onboarding cue. JS adds `.visible` to
   the container at mount and removes it on any user interaction
   or after a 5-second timeout. Scrim sits at a luminance the eye
   reads as "subtle tint" against either page bg, on the inset +
   rounded pseudo-element so it floats inside the third rather
   than touching the screen edges. Color stays on the button so
   the hand SVG inherits currentColor for the icon fill. */
.tap-zones.visible .tap-zone {
  color: var(--page-text);
}
.tap-zones.visible .tap-zone::before {
  background: rgba(26, 24, 21, 0.06);
}
html.dark .tap-zones.visible .tap-zone::before {
  background: rgba(255, 255, 255, 0.04);
}
/* Hand wears the theme's text colour — the same ink as the prose
   (light in dark mode, dark in light mode), so the cue reads as
   part of the page rather than a foreign UI badge. The scrim +
   drop-shadow carry the separation. */
.tap-zones.visible .tap-hint-hand path {
  fill: var(--page-text);
}

/* ---- Narrow-window fallback ----------------------------------- */
/* When the viewport is portrait or below ~720 px wide, two pages
   don't fit comfortably. The JS layout pass detects the same
   media query and switches to one page per spread — it sets
   `--col-w` to the full viewport-content width and uses a
   single-column spread shift. We don't override column-count /
   column-width here; doing so used to force `column-count: 1` +
   `column-width: auto` which produced a single tall column with
   no horizontal flow at all, so page turns went nowhere.

   We do still hide the right-page chrome slots — there is no
   right page in narrow mode, so the page-number and running-
   header on that side would dangle off the viewport with no
   content to label. */
@media (max-width: 720px), (max-aspect-ratio: 1/1) {
  /* Narrow viewports: the six-button cluster leaves no room for
     the titlebar title, so it re-homes as the header row of the
     chapters menu. Single-chapter exports have no chapters menu
     (no-chapters-btn) — there the title keeps its titlebar slot,
     which is free of the left-side button anyway and still
     ellipsizes against the cluster. */
  .titlebar:not(.no-chapters-btn) .titlebar-title {
    display: none;
  }
  .chapters-menu-book-title {
    display: block;
  }
  .chrome-right.chrome-header,
  .chrome-right.chrome-footer {
    display: none;
  }
  /* No spine in single-page mode — there's no facing page to
     bind to, so the shadow would just be a stripe down the
     middle of the one visible column. */
  .spine {
    display: none;
  }
}

/* ---- Dark mode ------------------------------------------------ */
/* Activated by `html.dark` (initial state from main.ts's
   effectiveDark(); flipped at runtime by the theme-btn handler).
   The class lives on <html>, not <body>, so the four palette
   tokens cascade from the root into both html and body — the
   body's opacity-fade first-frame would otherwise let the
   `:root` cream defaults flash through the (still-invisible)
   body, which is what the earlier body.dark version exposed.
   Patches the handful of hover/active rules that hard-coded
   rgba(0, 0, 0, ...) — those go invisible on a dark background,
   so they re-state as rgba(255, 255, 255, ...) under .dark.
   The cover's box-shadow stays black on purpose (a real
   drop-shadow under the cover artwork reads naturally even on
   dark), and the spine gradient is also kept black: a slightly
   darker stripe between two dark pages still cues the binding
   without competing with the prose. */
html.dark {
  --page-bg: #1a1815;
  --page-text: #e8e3d3;
  --muted: #8c867a;
  --rule: #3a3530;
  /* Catppuccin Mocha bg behind the closed-book photo. */
  --surface-bg: #1e1e2e;
  /* Lifted mauve — the light-mode #6c45a1 sits at ~3:1 on the
     dark page (below WCAG AA), so dark mode picks a higher-
     value tint that lands at ~7:1. */
  --link-color: #c9a8e6;
}
html.dark .chapters-btn:hover,
html.dark .nav-btn:hover,
html.dark .theme-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
}
html.dark .chapters-btn[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.16);
}
html.dark .nav-btn:active,
html.dark .theme-btn:active {
  background: rgba(255, 255, 255, 0.10);
}
html.dark .chapters-menu-item:hover {
  background: rgba(255, 255, 255, 0.06);
}
/* The active chapter row in the dropdown — keep it accent-tinted
   (same purple cast as light mode), just dialled up enough to
   carry across the darker bg. */
html.dark .chapters-menu-item.active {
  background: rgba(176, 142, 196, 0.20);
}

/* Spine shadow on a dark book — the light-mode gradient is all
   rgba(0,0,0,…) on cream, which blends into the dark page and
   loses the crease. Physical reading of the same scene: the
   page surfaces curl up toward the binding and catch ambient
   light, so they read *lighter* than the flat surface near the
   gutter, while the binding well itself is the deepest shadow.
   So the dark variant pairs white highlights on the page
   surfaces with a near-black crease at the binding axis. Same
   asymmetric drop-off as the light version (light source upper-
   left; left side gets the highlight, right side stays in the
   binding ridge's shadow), so the two read as the same book
   under two lighting conditions rather than two different
   objects. */
html.dark .spine {
  background: linear-gradient(
    to right,
    transparent calc(50% - var(--gap) * 0.8),
    rgba(255, 255, 255, 0.020) calc(50% - var(--gap) * 0.4),
    rgba(255, 255, 255, 0.045) calc(50% - var(--gap) * 0.15),
    rgba(0, 0, 0, 0.45) 50%,
    rgba(0, 0, 0, 0.20) calc(50% + var(--gap) * 0.2),
    rgba(0, 0, 0, 0.05) calc(50% + var(--gap) * 0.65),
    transparent calc(50% + var(--gap) * 1.5)
  );
}

/* ---- Cover-spread presentation -------------------------------
   On spread 0 with a cover present, the standard two-page open-
   book layout gives way to a "closed book on a desk" photo: the
   blank-novel.png asset shows a cream-jacketed paperback at an
   angle, and the writer's cover artwork is laid over the photo's
   front face with a perspective transform so it reads as if the
   artwork is printed on the book itself.
   The overlay is a fixed-position layer over the whole viewport.
   It only paints when html.cover-spread is set (toggled in
   goToSpread); on any other spread it stays display: none and
   the standard spread chrome (page columns + spine crease)
   resumes underneath.
   The standard centre-crease spine is also hidden on the cover
   spread — the book is closed, there's no binding axis to cue. */
.cover-presentation {
  position: fixed;
  inset: 0;
  background: var(--surface-bg);
  display: none;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  /* Above .spine (z-index 0) and below the titlebar (z-index 10)
     so the title strip + traffic-light overlay stay legible. */
  z-index: 5;
}
html.cover-spread .cover-presentation {
  display: flex;
}
html.cover-spread .spine {
  display: none;
}
/* Book-shape container sized off the novel-template.webp photo's
   intrinsic 1024 × 1536 aspect ratio (≈ 2:3 portrait). Only one
   dimension is set explicitly (height) so the aspect-ratio
   always resolves cleanly — the `min(...)` picks the smaller of
   "85 % viewport height" and "85 % viewport width converted to
   the photo's height", so the book is comfortably framed on both
   tall and narrow windows without the box ever de-aspecting and
   breaking the perspective corner percentages. The photo lives
   on a ::before pseudo so we can hang a drop-shadow filter on
   it without that shadow also halo-ing the cover artwork. */
.cover-presentation-book {
  position: relative;
  aspect-ratio: 1024 / 1536;
  /* Sized to leave a margin around the book within the viewport (was
     full-bleed at 125vh). min() picks the smaller of a viewport-height
     fraction, a hard pixel ceiling (so it doesn't blow up at 4k), and
     the width-derived height for narrow windows — the box keeps the
     photo's aspect so the perspective corner percentages stay valid. */
  height: min(95vh, 1026px, calc(95vw * 1536 / 1024));
}
/* Book photo + ambient shadow. drop-shadow() follows the PNG's
   alpha channel, so the shadow tracks the book's actual
   silhouette (the curved bottom + slight angle) rather than the
   rectangular box. Pseudo-element rather than the container so
   the shadow filter doesn't also halo the cover artwork stacked
   on top — ::before paints first in source order, the artwork's
   absolute box paints over it. */
.cover-presentation-book::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("./assets/novel-template.webp") no-repeat center / contain;
  filter: drop-shadow(0 14px 32px rgba(0, 0, 0, 0.30));
  pointer-events: none;
}
/* The writer's cover artwork is mapped onto the photo's front-
   face quadrilateral via a CSS `matrix3d()` transform computed in
   JS (see updateCoverPerspective in reader-spread.html.j2). JS
   sets the element's width/height to the face's intrinsic
   dimensions (in container pixels) so the source rect for the
   matrix matches the face's aspect ratio — `object-fit: contain`
   then letterboxes the writer's artwork within that rect without
   cropping, regardless of the artwork's own aspect ratio.
   transform-origin must stay at 0,0 because the matrix maps
   absolute pixel coordinates — moving the origin would shift the
   output by the same amount.
   Slight translucency so the photo's cover texture bleeds
   through the artwork — gives the impression that the artwork is
   printed on (not pasted over) the worn paperback's cover. */
.cover-presentation-art {
  position: absolute;
  top: 0;
  left: 0;
  /* object-fit: cover so the artwork fills the cover-face's
     aspect-ratio rect entirely — zoomed in if its own aspect
     ratio differs from the face's. Slight cropping at the
     bleed edges is preferable to letterboxing for a book
     cover, where the writer expects the artwork to extend to
     the trim line. */
  object-fit: cover;
  /* Tiny corner radius so the artwork picks up the rounded
     trim of a real paperback cover (book covers aren't
     pixel-sharp corners; even a 2 px radius softens the
     square-cut look). border-radius is applied in the
     element's local box before matrix3d, so the rounded
     corners ride through the perspective projection. */
  border-radius: 2px;
  transform-origin: 0 0;
  /* Inset alpha fade — see the #cover-edge-fade SVG filter in
     reader-spread.html.j2. The filter blurs the painted alpha
     of the actual artwork and re-composites it inward so the
     soft edge sits inside the box rather than at it. */
  filter: url(#cover-edge-fade);
}