/* --------------------------------------------------------------------
   reader.css — the reading screen.

   The page itself is rendered by epub.js inside an iframe and styled from
   settings.js, not from here. Everything below is the chrome around it: the
   bars, the tap zones, the contents list and the panels.
   -------------------------------------------------------------------- */

#screen-reader { position: relative; overflow: hidden; }

/* Reading fills the window exactly, and the window never scrolls.

   A scrollbar beside a paginated book scrolls nothing anybody wants: the page
   is already exactly one screen, so the bar only ever moves the chrome a few
   pixels and takes width away from the text to do it. `100dvh` rather than
   `100vh` so a phone's collapsing address bar does not leave a permanent
   sliver of overflow. */
body[data-screen="reader"] {
  overflow: hidden;
  height: 100dvh;
}
#screen-reader { height: 100dvh; }

/* ---------------------------------------------------------------- */
/* Bars                                                              */
/* ---------------------------------------------------------------- */

.rd-bar {
  flex: none;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--bar-h);
  padding: 0 var(--sp-2) 0 var(--sp-2);
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}
.rd-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  padding: 0 var(--sp-2);
}
.rd-title > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#reader-title { font-size: var(--step--1); font-weight: 600; }
#reader-chapter { font-size: var(--step--2); }
/* Finding your way about the book: beside the arrow out, with the title.
   A hairline separates them from the title rather than a gap, because a gap
   this early in the bar reads as a mistake. */
.rd-bar-find {
  display: flex;
  gap: var(--sp-1);
  flex: none;
  padding-inline-end: var(--sp-3);
  margin-inline-end: var(--sp-1);
  border-inline-end: 1px solid var(--border);
}

/* The two right-hand groups: what you are doing, then how the page looks.
   The gap between them is what stops nine buttons reading as a wall. */
/* `--sp-3` and not `--sp-4`: enough of a gap to read as two groups, not so
   much that it reads as a missing button. */
.rd-bar-actions { display: flex; gap: var(--sp-3); flex: none; align-items: center; }
.rd-bar-group { display: flex; gap: var(--sp-1); align-items: center; }

.rd-foot {
  flex: none;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Above the home-indicator strip on a phone with no home button, where the
     taper would otherwise sit under the bar the system draws. */
  padding: var(--sp-1) var(--sp-4) calc(var(--sp-2) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  background: var(--bg-2);
}
/* ---------------------------------------------------------------- */
/* Contents, with search at the top                                  */
/* ---------------------------------------------------------------- */

/* The two halves of "where is it" in one panel. A rule between them, because
   a search with no results and a list of chapters would otherwise run
   together into one confusing column. */
.toc-body { gap: 0; padding: 0; }
/* Outside the scrolling part, so it is always there. See index.html. */
.toc-search {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
  border-bottom: 1px solid var(--border);
}
.toc-found {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.toc-found #search-status:empty { display: none; }
.toc-body .toc-list { padding: var(--sp-2) var(--sp-3) var(--sp-4); }

/* ---------------------------------------------------------------- */
/* The taper                                                         */
/* ---------------------------------------------------------------- */

/*
 * A candle that burns down, rather than a progress bar that fills up.
 *
 * §3 of the brief rules out a conventional progress bar anywhere in the
 * product, and this is the control the reader uses most — so it is not
 * removed, it is redrawn. The wax runs from the reading position to the end of
 * the book and *shortens* as you go; the part behind you has burnt away. The
 * flame sits at the join, which is the same flame, in the same amber, meaning
 * the same thing it means everywhere else: this is where you are.
 *
 * The `<input type="range">` is still underneath all of it at full size and
 * fully interactive. Keyboard, drag, click-to-seek, screen-reader
 * announcements and the value itself are the browser's, not ours. Only its
 * appearance is replaced — which is the whole of what was asked for.
 *
 * `--burnt` is 0 to 1, set by reader.js on every relocation.
 */
/*
 * A candle standing on a line, shortening as the book is read.
 *
 * The first version laid the candle down flat — a horizontal bar of wax that
 * got shorter from the left. It was legible but it was still a bar, and a
 * candle that burns sideways is a diagram of the idea rather than the idea. A
 * real taper stands up and gets shorter, so this one does: the handle *is* the
 * candle, full height at the first page and a stub at the last, with the flame
 * on top of it throughout.
 *
 * Everything is measured from the floor at 2px, so the candle grows and
 * shrinks downward from the flame and never drifts off the line.
 */
.rd-taper {
  position: relative;
  height: 30px;
  /* Tall at the start, a stub at the end. Not zero: this is also the handle
     you grab, and it has to stay catchable on the last page. */
  --candle-h: calc(21px - var(--burnt) * 15px);
}

/* The book's whole length, as one quiet line. The candle stands on it. */
.rd-taper-track {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  height: 1px;
  background: var(--border);
  pointer-events: none;
}

/* The part behind you, drawn slightly heavier — the line you have walked. */
.rd-taper::before {
  content: '';
  position: absolute;
  left: 0;
  bottom: 1px;
  width: calc(var(--burnt) * 100%);
  height: 2px;
  background: var(--border);
  border-radius: 1px;
  pointer-events: none;
}

.rd-taper-candle {
  position: absolute;
  left: calc(var(--burnt) * 100%);
  bottom: 2px;
  width: 7px;
  height: var(--candle-h);
  transform: translateX(-50%);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 1px 1px 0 0;
  pointer-events: none;
  transition: height var(--speed) var(--ease);
}

/* The wick, so the flame is attached to something rather than hovering. */
.rd-taper-candle::after {
  content: '';
  position: absolute;
  left: 50%;
  top: -2px;
  width: 1px;
  height: 3px;
  background: var(--fg-dim);
  transform: translateX(-50%);
}

/*
 * The flame burns down with the book.
 *
 * Square, because the flame's viewBox is square. It was 11 by 14, and an SVG
 * keeps its aspect ratio inside whatever box it is given — so the drawing was
 * 11 by 11, centred, with a pixel and a half of nothing underneath it, and the
 * flame appeared to hover above the candle.
 *
 * The size now runs from 16px at the first page down to 9px at the last. Not
 * to zero: a flame that guttered out entirely would be both a poor sign-off
 * for finishing a book and an invisible handle on the control you use to move
 * around it. Seven pixels is enough that the difference is plain at a glance
 * between a book you have started and one you are nearly through, which is
 * what the shrinking is for.
 *
 * `bottom` moves with it, so the base stays on the wax at 9px however small
 * the flame gets.
 */
/*
 * The flame rides on top of the candle, so its position is the candle's height
 * rather than a number of its own. `--candle-h` shrinks, the flame comes down
 * with it, and the two can never drift apart.
 *
 * The flame itself stays one size. A real one does — it is the candle that
 * gets shorter — and a flame that shrank as well would be doing the same job
 * twice and be a speck by the last chapter.
 */
.rd-taper-flame {
  position: absolute;
  left: calc(var(--burnt) * 100%);
  bottom: calc(2px + var(--candle-h) - 1px);
  width: 11px;
  height: 11px;
  transform: translateX(-50%);
  pointer-events: none;
  transition: bottom var(--speed) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .rd-taper-flame, .rd-taper-candle { transition: none; }
}
.rd-taper-flame .flame { display: block; width: 100%; height: 100%; }

/*
 * Mirrored, the book runs from right to left — and so does the invisible
 * slider on top, which the browser flips by itself. The drawing did not flip:
 * at a tenth of the way through, the flame stood near the left while a tap on
 * it asked for nine tenths, which jumps to the ending.
 */
[dir="rtl"] .rd-taper::before { left: auto; right: 0; }
[dir="rtl"] .rd-taper-candle,
[dir="rtl"] .rd-taper-flame {
  left: auto;
  right: calc(var(--burnt) * 100%);
  transform: translateX(50%);
}

/*
 * The real control, invisible and on top.
 *
 * `opacity: 0` rather than `appearance: none` plus a pile of vendor
 * pseudo-elements: the browser keeps drawing its own slider, we simply do not
 * show it, and every behaviour it has continues to work. The thumb is widened
 * so the grab area is comfortable even though what you see is a flame.
 */
.rd-slider {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* Focus has to be visible even though the control is not. The ring goes on the
   candle, because that is the thing the reader can see. */
.rd-taper:has(.rd-slider:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}
.rd-stats {
  display: flex;
  gap: var(--sp-4);
  font-size: var(--step--2);
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}
/* `inline-start`, so mirrored the time left sits at the far end as it should.
   And each figure keeps its own reading order: set right to left, "6 hr 14 min
   left" came out as "hr 14 min left 6". */
.rd-stats > span:last-child { margin-inline-start: auto; }
.rd-stats > span { unicode-bidi: plaintext; }

/* ---------------------------------------------------------------- */
/* Stage                                                             */
/* ---------------------------------------------------------------- */

.rd-stage {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  justify-content: center;
  background: var(--page-bg);
  /*
   * Deliberately not animated.
   *
   * Sliding the stage aside over 160ms looks nicer for 160ms and costs a full
   * re-pagination of the chapter on every frame of it — the ResizeObserver
   * fires throughout, and each call hands epub.js a new width to lay the whole
   * chapter out against. On a long chapter that is a stutter in exchange for a
   * flourish. The panel itself does not animate either, so the two move
   * together and the result reads as one change rather than a jump.
   */
}

/*
 * The page steps aside for a panel rather than being covered by it.
 *
 * `margin` and not `padding`: the turn-page zones are positioned against the
 * stage, and an absolutely positioned child is laid out against the padding
 * box — so padding would have left the forward zone underneath the panel,
 * which is the half of the complaint about not being able to turn the page.
 * A margin shrinks the box itself, so the zone moves with the edge.
 *
 * epub.js re-paginates on its own: the ResizeObserver on the stage already
 * calls `sizeViewer`, which hands it the new width.
 */
/* "right" and "left" are the end and start sides: mirrored for right-to-left,
   the panels swap sides (see `.panel-right`) and the page steps aside from the
   side they are actually on. */
body[data-panel="right"] .rd-stage { margin-inline-end: var(--panel-open-w, var(--panel-w)); }
body[data-panel="left"]  .rd-stage { margin-inline-start: var(--panel-open-w, var(--panel-w)); }

/* On a narrow screen there is no room to give away, so the panel covers the
   page as it always did and the reader closes it to carry on. */
@media (max-width: 860px) {
  body[data-panel="right"] .rd-stage,
  body[data-panel="left"] .rd-stage { margin-inline: 0; }
}


/* Width is set by reader.js from the measure setting. It is applied here
   rather than as a max-width on the book's <body>, because in paginated mode
   epub.js builds its CSS columns from that box and constraining it breaks
   pagination. */
.rd-viewer {
  width: 100%;
  height: 100%;
  min-width: 0;
  margin: 0 auto;
}
.rd-viewer iframe { border: 0; display: block; }

/* ---------------------------------------------------------------- */
/* Reading by candlelight                                            */
/* ---------------------------------------------------------------- */

/*
 * Two separate things, because they are two separate wishes.
 *
 * **Warmer paper** takes the blue out. That is what every operating system's
 * night mode does, and the only difference here is *where*: it sits on the
 * stage, so the bar, the panels and the reading mark keep their own colours. A
 * reader who warms the page at midnight should not lose the one amber thing on
 * screen that means something.
 *
 * **Pooled light** dims the edges and leaves the middle bright, the way one
 * flame on a desk lights a book. It does not warm anything.
 *
 * Either can be on without the other, and each has its own strength. Wanting a
 * page that is kinder at night is a different wish from wanting the room to
 * feel like it has a candle in it, and running them together meant neither
 * could be had on its own.
 *
 * ## Why overlays rather than a filter on the frame
 *
 * The book is in an iframe, and a `filter` on an element containing an iframe
 * forces it into its own compositing layer, which in paginated mode makes
 * epub.js re-measure the column box — a visible re-pagination every time a
 * slider moves. Painted overlays change nothing about layout and cost one
 * composited rectangle each.
 *
 * `pointer-events: none` on both, because the reader has to be able to click
 * the words underneath them.
 */

/*
 * Warmer paper.
 *
 * The colour is a pale cream, not an amber. Multiplying by a saturated orange
 * was the first attempt and it read as a colour effect — the page went orange
 * rather than warm, and turning it down only made it a weaker orange. This
 * leaves red alone, takes a little green and a good deal of blue, which is what
 * "less blue light" actually means and looks like nothing but warmer paper.
 *
 * Because the colour is gentle, the strength can run most of the way to solid
 * without ever looking like a filter.
 */
[data-candle="true"] .rd-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /*
   * Paler again, and this is the third attempt at it.
   *
   * A saturated amber was obviously wrong. A cream at #ffdca8 was still wrong
   * and less obviously so: it takes a third of the blue out at full strength,
   * which is more than a night-light does and enough to read as a tint over
   * paper that is already warm. This one leaves red alone, takes a twentieth of
   * the green and a sixth of the blue — the page goes warmer and never goes
   * orange, which is the whole of what was asked for twice.
   */
  background: #ffeeda;
  mix-blend-mode: multiply;
  opacity: var(--candle-warmth, 0.4);
}

/*
 * Pooled light.
 *
 * Three overlapping pools rather than one even ring, because a candle does not
 * light a desk evenly — it puts a bright patch a little off centre, a weaker
 * one beside it, and lets the corners go. The middle sixty per cent is left
 * completely clear so the text is never dimmed, whatever the strength, and on a
 * two-column spread both columns stay inside it.
 *
 * Still until asked. See `[data-flicker]` below for why the movement is its
 * own switch.
 */

/*
 * Where each pool of light sits, as animatable numbers.
 *
 * Registered with `@property` so the browser knows they are lengths and can
 * interpolate them. Left as ordinary custom properties they would jump between
 * keyframes instead of sliding, which is the difference between a candle and a
 * fault in the display.
 *
 * Moving the gradient's own centre rather than sliding the whole layer is the
 * important choice. A `transform` on the overlay is cheaper, and it drags the
 * *edges* of the layer with it — so the far side of the page would show a
 * bright strip where the shadow used to reach. Here the layer never moves and
 * only the light inside it does, which is also what actually happens in a room.
 */
@property --pool-x { syntax: '<percentage>'; inherits: true; initial-value: 43%; }
@property --pool-y { syntax: '<percentage>'; inherits: true; initial-value: 38%; }
@property --pool-x2 { syntax: '<percentage>'; inherits: true; initial-value: 63%; }
@property --pool-y2 { syntax: '<percentage>'; inherits: true; initial-value: 68%; }
@property --pool-lift { syntax: '<number>'; inherits: true; initial-value: 1; }
/*
 * A second brightness, because two animations cannot share one property.
 *
 * CSS does not add animations together — the last one declared wins the
 * property outright. The slow breathing and the fast tremor are genuinely
 * separate things a flame does at the same time, so they get a number each and
 * are multiplied where the colour is mixed.
 */
@property --pool-tremor { syntax: '<number>'; inherits: true; initial-value: 1; }

[data-vignette="true"] .rd-stage::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /*
   * Multiplied, and warm.
   *
   * Laid over the page as ordinary paint, a dark grey shadow turns cream paper
   * into grey paper — the page looked dirty rather than dim. Multiplying by a
   * warm brown darkens the same amount while keeping the colour of the paper,
   * which is what a shadow does and what a grey film does not.
   */
  mix-blend-mode: multiply;
  background:
    radial-gradient(64% 66% at var(--pool-x, 44%) var(--pool-y, 40%),
      rgb(74 44 14 / 0) 0%, rgb(74 44 14 / 0) 78%,
      rgb(74 44 14 / calc(var(--vignette-depth, 0.35) * 0.5 * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%),
    radial-gradient(56% 58% at var(--pool-x2, 60%) var(--pool-y2, 64%),
      rgb(74 44 14 / 0) 0%, rgb(74 44 14 / 0) 74%,
      rgb(74 44 14 / calc(var(--vignette-depth, 0.35) * 0.35 * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%),
    radial-gradient(112% 104% at 50% 48%,
      rgb(58 34 10 / 0) 0%, rgb(58 34 10 / 0) 72%,
      rgb(58 34 10 / calc(var(--vignette-depth, 0.35) * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%);
}

/*
 * The warm wash works on a dark theme too, and it took a try to see why.
 *
 * The obvious move is to screen a faint amber instead, on the reasoning that
 * multiplying a dark page only makes it darker. It does — and it washed the
 * whole page pale orange, which is the one thing nobody wants at midnight.
 *
 * Multiplying is right on both. On a dark theme the *text* is the lit part, so
 * multiply leaves the near-black background where it is and warms the letters,
 * which is exactly the same intent: less blue light coming off the screen.
 */

/* ---------------------------------------------------------------- */
/* The flame                                                         */
/* ---------------------------------------------------------------- */

/*
 * What a candle actually does to a page, and what it does not.
 *
 * The obvious implementation is a fast random twitch, and it is wrong in a way
 * that is obvious the moment you sit with it: it reads as a failing monitor. A
 * real flame on a desk, seen by somebody reading rather than somebody watching
 * the flame, behaves quite differently.
 *
 *  - Most of the movement is **slow**. The flame leans, the pool of light
 *    slides an inch across the page over a second or two, and comes back.
 *  - The brightness varies **a little, most of the time** — a few per cent —
 *    and the eye reads that as "alive" rather than as "flickering".
 *  - Every so often it **gutters**: a draught, a dip in the light, a lean, and
 *    a recovery over about half a second. That is the part people actually
 *    picture when they think of candlelight, and it is rare.
 *
 * So three separate motions at three different speeds, none of them a whole
 * multiple of another. Their periods are 13s, 8.3s and 3.7s, which means the
 * combination does not repeat for nearly twenty minutes — long enough that
 * there is no pattern to notice while reading.
 *
 * The two pools move on different clocks, so the light does not slide about as
 * one rigid shape. The brightness has its own clock again, with the guttering
 * written into it as two dips at uneven places rather than a regular beat.
 */
/*
 * Where the light wanders.
 *
 * The first version kept both pools within a few per cent of where they
 * started, which is what a flame in still air does and is not what this is
 * for — the whole page should feel lit from a source that moves, not just the
 * middle. So the travel is much wider now, and weighted: most keyframes stay
 * near the centre and a few take the light well out towards a corner before it
 * comes back, which is what a draught does to a flame on a desk.
 */
@keyframes candle-lean {
  0%    { --pool-x: 44%; --pool-y: 40%; }
  9%    { --pool-x: 52%; --pool-y: 34%; }
  18%   { --pool-x: 46%; --pool-y: 45%; }
  27%   { --pool-x: 33%; --pool-y: 41%; }   /* out to the left */
  36%   { --pool-x: 41%; --pool-y: 30%; }
  45%   { --pool-x: 49%; --pool-y: 38%; }
  54%   { --pool-x: 61%; --pool-y: 44%; }   /* and across to the right */
  63%   { --pool-x: 50%; --pool-y: 52%; }
  72%   { --pool-x: 42%; --pool-y: 36%; }
  81%   { --pool-x: 36%; --pool-y: 47%; }
  90%   { --pool-x: 47%; --pool-y: 38%; }
  100%  { --pool-x: 44%; --pool-y: 40%; }
}

@keyframes candle-drift {
  0%    { --pool-x2: 60%; --pool-y2: 64%; }
  12%   { --pool-x2: 51%; --pool-y2: 71%; }
  24%   { --pool-x2: 68%; --pool-y2: 58%; }
  35%   { --pool-x2: 73%; --pool-y2: 69%; }   /* the far corner */
  47%   { --pool-x2: 57%; --pool-y2: 76%; }
  59%   { --pool-x2: 44%; --pool-y2: 62%; }   /* and back across */
  71%   { --pool-x2: 63%; --pool-y2: 55%; }
  85%   { --pool-x2: 69%; --pool-y2: 67%; }
  100%  { --pool-x2: 60%; --pool-y2: 64%; }
}

/*
 * Brightness. Above 1 the shadows deepen, below 1 the page brightens — so the
 * numbers read backwards from the light: a gutter is a *rise* here.
 */
@keyframes candle-breathe {
  0%    { --pool-lift: 1; }
  7%    { --pool-lift: 0.93; }
  16%   { --pool-lift: 1.07; }
  24%   { --pool-lift: 0.97; }
  33%   { --pool-lift: 1.04; }
  41%   { --pool-lift: 1.31; }   /* a draught */
  46%   { --pool-lift: 1.09; }
  53%   { --pool-lift: 0.95; }
  62%   { --pool-lift: 1.02; }
  70%   { --pool-lift: 0.91; }
  79%   { --pool-lift: 1.24; }   /* and another, unevenly spaced */
  84%   { --pool-lift: 1.03; }
  92%   { --pool-lift: 0.98; }
  100%  { --pool-lift: 1; }
}

/*
 * The fast part, and the part to be careful with.
 *
 * A flame has a quick wobble on top of everything else, and without it the
 * light looks like something sliding rather than something burning. It is also
 * the component that turns into a strobe if it is overdone — so it is small
 * (three per cent) and not quite regular, which is enough to read as alive and
 * not enough to read as a fault.
 */
@keyframes candle-tremor {
  0%    { --pool-tremor: 1; }
  17%   { --pool-tremor: 1.03; }
  29%   { --pool-tremor: 0.98; }
  46%   { --pool-tremor: 1.02; }
  61%   { --pool-tremor: 0.97; }
  78%   { --pool-tremor: 1.03; }
  91%   { --pool-tremor: 0.99; }
  100%  { --pool-tremor: 1; }
}

[data-vignette="true"][data-flicker="true"] .rd-stage::before {
  animation:
    candle-lean 13s ease-in-out infinite,
    candle-drift 8.3s ease-in-out infinite,
    candle-breathe 3.7s ease-in-out infinite,
    candle-tremor 0.9s ease-in-out infinite;
}

/*
 * Nobody gets this without asking twice.
 *
 * A reader who has told their machine they do not want movement has told it
 * about exactly this: a slow animation at the edge of vision, running for as
 * long as the book is open. The pooled light stays; only the flame stops.
 */
@media (prefers-reduced-motion: reduce) {
  [data-vignette="true"][data-flicker="true"] .rd-stage::before { animation: none; }
}

/* ---------------------------------------------------------------- */
/* Light it yourself                                                 */
/* ---------------------------------------------------------------- */

/*
 * The dark you have to light your way out of.
 *
 * A third element rather than reusing the pooled light, because it is a
 * different thing: the pooling shapes light that is already there, and this
 * takes it away until the reader strikes a match. Layered over both, so it
 * darkens whatever they were already reading with.
 *
 * `--game-dark` never reaches 1. See the note in candle.js: a mode that can
 * hide somebody's book completely is a fault, not a game.
 */
[data-candlegame] .rd-stage {
  --cg-pad: clamp(12px, 2.4vw, 28px);
}

[data-candlegame] .rd-viewer::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  /*
   * Multiplied by a warm brown, for the same reason the pooled light is: a
   * neutral black film over cream paper makes the lit page look grey and
   * dusty rather than candlelit. This keeps the colour of the paper and takes
   * the light away, which is what a room without a candle in it does.
   */
  background: rgb(30 16 4 / var(--game-dark, 0.9));
  mix-blend-mode: multiply;
  /* Slow, because a candle does not switch on. Long enough to read as a flame
     catching and short enough not to feel like waiting. */
  transition: background var(--dur-4, 900ms) ease;
}

/*
 * A lit taper pools its light, whether or not the reader turned pooling on.
 *
 * The same drawing as "Pooled light" above, reached by a second selector
 * instead of copied. The game is a candle on a desk by definition — there is no
 * version of it where the light should be flat — so it borrows the shaping and
 * the flame, and the reader's own depth setting still decides how deep it goes.
 */
[data-candlegame="lit"] .rd-stage::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  mix-blend-mode: multiply;
  background:
    radial-gradient(64% 66% at var(--pool-x, 44%) var(--pool-y, 40%),
      rgb(74 44 14 / 0) 0%, rgb(74 44 14 / 0) 78%,
      rgb(74 44 14 / calc(var(--vignette-depth, 0.35) * 0.5 * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%),
    radial-gradient(56% 58% at var(--pool-x2, 60%) var(--pool-y2, 64%),
      rgb(74 44 14 / 0) 0%, rgb(74 44 14 / 0) 74%,
      rgb(74 44 14 / calc(var(--vignette-depth, 0.35) * 0.35 * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%),
    radial-gradient(112% 104% at 50% 48%,
      rgb(58 34 10 / 0) 0%, rgb(58 34 10 / 0) 72%,
      rgb(58 34 10 / calc(var(--vignette-depth, 0.35) * var(--pool-lift, 1) * var(--pool-tremor, 1))) 100%);
  animation:
    candle-lean 13s ease-in-out infinite,
    candle-drift 8.3s ease-in-out infinite,
    candle-breathe 3.7s ease-in-out infinite,
    candle-tremor 0.9s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  [data-candlegame="lit"] .rd-stage::before { animation: none; }
}

/*
 * The props.
 *
 * The layer itself takes no clicks — the reader has to be able to turn pages
 * through it — and each piece takes its own. `z-index` above the dark, so the
 * matchbox is the one thing clearly visible on an unlit page.
 */
.cg-layer {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}
.cg-layer button {
  position: absolute;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.5));
}
.cg-svg { display: block; overflow: visible; }

/* The match waiting to be struck, lying at the left. */
.cg-rest { left: var(--cg-pad); bottom: var(--cg-pad); width: 56px; }

/*
 * Where the taper stands: the bottom right, out of the way of the text.
 *
 * On a two-column spread it moves to the gutter between the columns, which is
 * where you would actually stand a candle on an open book and the one strip of
 * a spread that covers no words.
 */
.cg-taper { right: var(--cg-pad); bottom: var(--cg-pad); width: 54px; }
body[data-columns="2"] .cg-taper {
  right: auto;
  left: 50%;
  transform: translateX(-50%);
}

/*
 * The sheet that carries the match across the page.
 *
 * A cursor set on anything outside the book's iframe stops at the iframe's
 * edge, so the match only existed in the margins — the one place it was no use.
 * This lies over the whole stage while a match is burning and carries the
 * cursor with it.
 *
 * The hotspot is at the flame, not the middle of the picture, so the thing that
 * touches the wick is the fire.
 */
.cg-hand {
  position: absolute;
  inset: 0;
  display: none;
  cursor: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='26' height='45' viewBox='0 0 22 38'>\
<rect x='9.5' y='9' width='3' height='26' rx='1.4' fill='%23d9be93'/>\
<ellipse cx='11' cy='8' rx='3.4' ry='4.2' fill='%238d2f1c'/>\
<path d='M11 1.4c3.6 4.2 3.3 7.6 0 8.4-3.3-.8-3.6-4.2 0-8.4Z' fill='%23f2c14e'/>\
</svg>") 11 3, crosshair;
}
.cg-layer[data-phase="striking"] .cg-hand { display: block; pointer-events: auto; }

/*
 * The book does not move while you are carrying a lit match.
 *
 * The turn-the-page zones sit in the margins, which is exactly where somebody
 * carrying a match across the page is going to miss the taper — so a missed
 * click turned the page instead, and the reader came back to a lit candle over
 * the wrong paragraph. There is nothing to read in the dark anyway; page turns
 * come back the moment the taper catches.
 */
[data-candlegame="dark"] .rd-nav,
[data-candlegame="striking"] .rd-nav { pointer-events: none; }

/* E-paper puts the game away entirely — no dark, no match to strike — so it
   must not keep the page-turn zones switched off either: they went dead with
   nothing on screen to say why, and nothing that could bring them back. */
[data-epaper="true"][data-candlegame] .rd-nav { pointer-events: auto; }

/* Which props exist in which phase. The match on the table is there to be
   struck; once it is in your hand it is the cursor, and once the taper is lit
   there is nothing to strike. */
.cg-layer[data-phase="striking"] .cg-rest,
.cg-layer[data-phase="lit"] .cg-rest { display: none; }
.tp-flame { display: none; }
.cg-layer[data-phase="lit"] .tp-flame { display: block; }

/* Colours. Warm and low, because everything here is seen by candlelight. */
.mt-stick { fill: #d9bE93; }
.mt-head { fill: #8d2f1c; }
.mt-flame, .tp-flame { fill: #f2c14e; }
.tp-wax { fill: #efe3c8; }
.tp-wick { stroke: #3a2a18; stroke-width: 1.6; stroke-linecap: round; }
.tp-dish { fill: #5a4a38; }

/*
 * The taper gets shorter, and the flame comes down with it.
 *
 * `--wax` runs 1 to 0 over the hour. The wax is scaled from its base — which is
 * why the transform origin is the bottom — and the flame is moved down by the
 * height that was lost, so it stays sitting on the wick the whole way.
 */
.cg-layer .tp-wax {
  transform-origin: 22px 66px;
  transform: scaleY(var(--wax, 1));
  transition: transform 2s linear;
}
.cg-layer .tp-wick,
.cg-layer .tp-flame {
  transform: translateY(calc((1 - var(--wax, 1)) * 40px));
  transition: transform 2s linear;
}

/*
 * How much of the hour is left.
 *
 * A column of wax beside the taper, not a progress bar — this app does not have
 * progress bars, and a candle already has the right shape for the idea. It
 * empties from the top, which is the direction a candle goes.
 *
 * Only while something is burning: an empty gauge beside an unlit taper is a
 * measurement of nothing.
 */
.cg-gauge {
  position: absolute;
  display: none;
  width: 4px;
  height: 58px;
  border-radius: 2px;
  background: rgb(0 0 0 / 0.28);
  overflow: hidden;
  pointer-events: none;
}
.cg-layer[data-phase="lit"] .cg-gauge { display: block; }
.cg-gauge-wax {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(var(--wax, 1) * 100%);
  background: linear-gradient(to top, #d9a441, #f2c14e);
  transition: height 2s linear;
}

/* Beside the taper, on whichever side the taper is standing. */
.cg-gauge { right: calc(var(--cg-pad) + 60px); bottom: calc(var(--cg-pad) + 8px); }
body[data-columns="2"] .cg-gauge { right: auto; left: calc(50% + 38px); }
[dir="rtl"] .cg-gauge { right: auto; left: calc(var(--cg-pad) + 60px); }
[dir="rtl"] body[data-columns="2"] .cg-gauge { left: auto; right: calc(50% + 38px); }

/* Said once, and only in the dark. */
.cg-hint {
  position: absolute;
  left: 50%;
  bottom: calc(var(--cg-pad) + 8px);
  transform: translateX(-50%);
  margin: 0;
  font-size: var(--step--1);
  color: rgb(201 160 106 / 0.85);
  text-align: center;
  pointer-events: none;
  transition: opacity var(--dur-3, 400ms) ease;
}
.cg-layer[data-phase="lit"] .cg-hint { opacity: 0; }

/*
 * The flame on the taper moves, and so does the one on the match.
 *
 * The same idea as the pooled light and a much smaller version of it: a flame
 * that is perfectly still next to a page whose shadows are moving is the one
 * thing that would give the whole effect away.
 */
/* The taper's flame keeps the drop it has taken as the wax went down, so the
   wobble is added to where it already sits rather than replacing it. */
@keyframes cg-taper-flame {
  0%   { transform: translateY(calc((1 - var(--wax, 1)) * 40px)) scale(1, 1); }
  28%  { transform: translateY(calc((1 - var(--wax, 1)) * 40px)) scale(0.94, 1.09); }
  53%  { transform: translateY(calc((1 - var(--wax, 1)) * 40px)) scale(1.05, 0.95); }
  76%  { transform: translateY(calc((1 - var(--wax, 1)) * 40px)) scale(0.97, 1.05); }
  100% { transform: translateY(calc((1 - var(--wax, 1)) * 40px)) scale(1, 1); }
}
@media (prefers-reduced-motion: no-preference) {
  .cg-layer .tp-flame { transform-origin: 22px 17px; animation: cg-taper-flame 1.3s ease-in-out infinite; }
}

/*
 * E-paper mode is about maximum contrast, and any of these over the page is
 * the opposite of that. The switches stay available; they simply do nothing.
 */
[data-epaper="true"] .rd-stage::after,
[data-epaper="true"] .rd-stage::before,
[data-epaper="true"] .rd-viewer::after { display: none; }
[data-epaper="true"] .cg-layer { display: none; }

/* Turn-the-page zones.

   These live in the *margin beside* the book, never over it. `--rd-gutter` is
   measured by reader.js from the real gap between the stage and the page, and
   the stage guarantees a minimum, so the zone can always be hit and can never
   sit on a word.

   That overlap was a real fault rather than a cosmetic one. A zone stacked
   above the page eats the mousedown, so a word under it could not be selected
   at all — a drag starting near the edge turned the page instead of choosing
   a word, and in two columns the outer edge of each column is exactly where
   the text runs closest to the frame.

   No box, either: no background and no border, at rest or on hover. The
   arrow alone fades in. A translucent slab appearing over the edge of the
   text was the thing that read as "the button is on top of my book", and it
   was right. */
.rd-nav {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 10;
  width: var(--rd-gutter, 56px);
  border: 0;
  background: transparent;
  color: var(--page-fg);
  display: grid;
  place-items: center;
  font-size: 1.8rem;
  opacity: 0;
  transition: opacity 120ms ease;
}
.rd-nav-prev { left: 0; }
.rd-nav-next { right: 0; }

/*
 * Mirrored for a reader who reads right to left.
 *
 * `dir` flips everything the browser lays out for itself, and these are the few
 * things pinned to a physical side that it cannot know about. Going back must
 * be on the side the eye starts from, or the whole point of mirroring is lost
 * at exactly the control used most.
 */
[dir="rtl"] .rd-nav-prev { left: auto; right: 0; }
[dir="rtl"] .rd-nav-next { right: auto; left: 0; }
/* The arrows are drawings, not letters, so nothing mirrors them by itself: they
   moved sides and went on pointing the old way. */
[dir="rtl"] .rd-nav svg { transform: scaleX(-1); }
[dir="rtl"] .cg-rest { left: auto; right: var(--cg-pad); }
[dir="rtl"] .cg-taper { right: auto; left: var(--cg-pad); }
[dir="rtl"] body[data-columns="2"] .cg-taper { left: 50%; right: auto; }

/* The glyph, not a panel. */
.rd-nav:hover, .rd-nav:focus-visible { opacity: 0.5; background: transparent; }
.rd-nav:active { opacity: 0.8; }
.rd-nav:focus-visible { outline: 2px solid var(--focus, currentColor); outline-offset: -4px; }

body[data-flow="scrolled"] .rd-nav { display: none; }

/* E-ink users can't hover and partial refresh makes faint chrome worse, so
   the arrows become permanent and high-contrast — still without a box. */
[data-epaper] .rd-nav { opacity: 0.6; transition: none; }

.rd-loading {
  position: absolute;
  inset: 0;
  z-index: 15;
  display: grid;
  place-items: center;
  background: var(--page-bg);
  color: var(--fg-dim);
  font-size: var(--step--1);
}
.rd-loading[hidden] { display: none; }

/* ---------------------------------------------------------------- */
/* Contents                                                          */
/* ---------------------------------------------------------------- */

.toc-list { padding: var(--sp-2); gap: 0; }

.toc-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  padding-inline-start: calc(var(--sp-3) + var(--toc-level, 0) * 0.85rem);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  text-align: start;
}
.toc-go {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  text-align: start;
}
.toc-summarise { flex: none; opacity: 0; }
.toc-item:hover .toc-summarise,
.toc-item:focus-within .toc-summarise { opacity: 1; }
[data-epaper] .toc-summarise { opacity: 1; }
.toc-item:hover { background: var(--bg-3); }
.toc-item[aria-current="true"] { background: var(--bg-3); }
.toc-item[aria-current="true"] .toc-label { font-weight: 600; }

.toc-row { display: flex; align-items: baseline; gap: var(--sp-2); }
.toc-label { flex: 1; font-size: var(--step--1); line-height: 1.3; }
.toc-pct { flex: none; font-size: var(--step--2); color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.toc-item .progress { height: 2px; }

/* A chapter you have finished should be legible as finished at a glance,
   without reading a single number. */
.toc-item[data-done="true"] .toc-label { color: var(--fg-dim); }
.toc-item[data-done="true"] .toc-pct::after { content: " ✓"; color: var(--ok); }

/* ---------------------------------------------------------------- */
/* Search and bookmark results                                       */
/* ---------------------------------------------------------------- */

.result-list:empty { display: none; }
.result-bookmark { display: flex; align-items: flex-start; gap: var(--sp-2); }
.result-bookmark .result-body { flex: 1; min-width: 0; }
.result-del { flex: none; opacity: 0; }
.result:hover .result-del, .result:focus-within .result-del { opacity: 1; }
[data-epaper] .result-del { opacity: 1; }

/* A visible, permanent reminder that the ceiling is off — the spec is
   emphatic that spoiler state must never be ambient. */
/* On the contents panel, where search lives now. It was written for the old
   separate search panel, which is an empty shell — so ticking "Search the whole
   book" changed nothing on screen at all. */
#toc-panel[data-scope="whole"] .toc-search {
  border-bottom-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-2));
}
#toc-panel[data-scope="whole"] .panel-head h2 { color: var(--danger); }

/* ---------------------------------------------------------------- */
/* Skins                                                             */
/* ---------------------------------------------------------------- */

/* The picker lives only in the reading-settings panel. Skins are a considered
   choice, not something to nudge by accident while turning pages, so they get
   no permanent button in the bar. The 1/2/3 keys remain for anyone who wants
   to flick between them. */

.skin-list { display: flex; flex-direction: column; gap: var(--sp-1); }

.skin-option {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  color: inherit;
  text-align: start;
}
/* The fold holding the themes nobody needs on the way in. */
.set-more > summary {
  cursor: pointer;
  font-size: var(--step--1);
  color: var(--fg-dim);
  padding: var(--sp-1) 0;
}
.set-more > summary:hover { color: var(--fg); }
.set-more .theme-grid { margin-top: var(--sp-2); }

.skin-option:hover { background: var(--bg-3); }
.skin-option[aria-pressed="true"] { border-color: var(--accent); background: var(--bg-3); }
.skin-option[aria-pressed="true"] .skin-option-name { font-weight: 600; }

.skin-option-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.skin-option-name { font-size: var(--step--1); }
.skin-option-desc { font-size: var(--step--2); color: var(--fg-dim); line-height: 1.35; }

.skin-option kbd {
  font: var(--step--2)/1 var(--font-mono);
  padding: 3px 5px;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--fg-dim);
  background: var(--bg);
}

.skin-note { font-size: var(--step--2); line-height: 1.4; }

/* ---------------------------------------------------------------- */
/* Settings panel groups                                             */
/* ---------------------------------------------------------------- */

.set-group { display: flex; flex-direction: column; gap: var(--sp-2); }

/*
 * The explanation under a setting, on a line of its own.
 *
 * It ran straight on from the setting's name in the same line — "Right to left
 * every book mirrors Taper's own bars…", "Illustrate chapters as I finish them
 * Two requests…" — which read as one confused sentence. The name is the
 * control; the grey line under it says what it does.
 */
.panel-body .check > span > em.dim { display: block; margin-top: 2px; }

/* Notes in a panel are set like notes: a step smaller than the panel's own
   controls, not a step larger, which is how they came out at the page's
   ordinary size beside checkbox labels a size down. */
.panel-body .set-group p { font-size: var(--step--1); line-height: 1.5; }
.set-group + .set-group { border-top: 1px solid var(--border); padding-top: var(--sp-4); }
.set-group h3 {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--fg-dim);
  font-weight: 600;
}
.set-group h3 em { text-transform: none; letter-spacing: 0; font-style: normal; }

/* ---------------------------------------------------------------- */
/* Code block affordances (added by content.js inside the iframe host)*/
/* ---------------------------------------------------------------- */

/* Full-screen code viewer lives in the parent document, because an overlay
   inside a paginated iframe would be clipped by the column it sits in. */
.code-zoom { max-width: min(1100px, calc(100vw - 2rem)); }
.code-zoom pre {
  margin: 0;
  padding: var(--sp-4);
  border-radius: var(--radius-sm);
  background: var(--page-code-bg);
  color: var(--page-code-fg);
  border: 1px solid var(--border);
  overflow: auto;
  max-height: 70vh;
  font: var(--step--1)/1.5 var(--font-mono);
  tab-size: 4;
}

/* ---------------------------------------------------------------- */
/* Small screens                                                     */
/* ---------------------------------------------------------------- */

@media (max-width: 720px) {
  .rd-stats { gap: var(--sp-3); }
  .rd-foot { padding: var(--sp-1) var(--sp-3) calc(var(--sp-2) + env(safe-area-inset-bottom, 0px)); }
  #reader-chapter { display: none; }
}

/* ------------------------------------------------------------------ */
/* Immersive reading (Phase 6 polish)                                  */
/* ------------------------------------------------------------------ */

/* Full screen gives the window; this gives the page the rest of it.
   The bars do not disappear — they collapse, and come back the moment the
   pointer or the keyboard goes near them. A control you cannot get back to
   is not a control, and readers do still need the contents list. */
/* The bars leave the layout entirely — they are lifted out of the flex column
   and slid off their own edge, so the page gets the whole window rather than
   the window minus two collapsed strips.

   The first attempt collapsed them to `max-height: 0` with `overflow: hidden`
   and put a hairline in a pseudo-element for the pointer to find. That
   hairline was clipped by the very `overflow: hidden` that collapsed the bar,
   so there was nothing to hover and no way back except the keyboard. The
   reveal strips below are real elements outside the bars, which is why they
   work. */
body[data-immersive="true"] .rd-bar,
body[data-immersive="true"] .rd-foot {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 55;
  transition: transform 160ms ease;
  will-change: transform;
}
body[data-immersive="true"] .rd-bar { top: 0; transform: translateY(-100%); }
body[data-immersive="true"] .rd-foot { bottom: 0; transform: translateY(100%); }

/* In full screen the bars are lifted out of the flow, so there is no gap
   between them for a panel to sit in — it takes the whole height instead. */
body[data-immersive="true"] .panel { top: 0; bottom: 0; }

/*
 * A margin above and below the page in full screen.
 *
 * With the bars gone the text ran from the very top edge of the screen to the
 * very bottom, the first and last lines pressed against the glass. A book has
 * a head and a foot margin for the same reason it has side margins.
 *
 * Padding on the stage rather than on the book: the page-turn zones and the
 * candle effects are laid over the stage and keep covering all of it, and
 * reader.js takes the padding off the height it gives the page, so a page is
 * still exactly one screen of text.
 */
body[data-immersive="true"] .rd-stage { padding-block: clamp(18px, 4vh, 44px); }

/* Invisible strips along the very top and bottom edge. Reaching for the edge
   of the screen is the gesture people already have for this. */
.rd-reveal { display: none; }
body[data-immersive="true"] .rd-reveal {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  height: 14px;
  z-index: 56;
}
body[data-immersive="true"] .rd-reveal-top { top: 0; }
body[data-immersive="true"] .rd-reveal-bottom { bottom: 0; }

/*
 * Not while a panel is open.
 *
 * In full screen a panel runs to the top of the screen, and the strip that
 * summons the bar lay over the top of the panel's own header. Reaching for the
 * panel's × crossed the strip, the bar slid down over the header, and its
 * last button — full screen — landed exactly where the × had been: closing the
 * panel left full screen instead. With a panel open, the panel's header is
 * what is at the top of the screen, and it is what the pointer is reaching for.
 */
body[data-immersive="true"][data-panel] .rd-reveal { display: none; }

/* Sibling selectors, which is why the strips are first in the markup. */
body[data-immersive="true"] .rd-reveal-top:hover ~ .rd-bar,
body[data-immersive="true"] .rd-bar:hover,
body[data-immersive="true"] .rd-bar:focus-within { transform: translateY(0); }

body[data-immersive="true"] .rd-reveal-bottom:hover ~ .rd-foot,
body[data-immersive="true"] .rd-foot:hover,
body[data-immersive="true"] .rd-foot:focus-within { transform: translateY(0); }

/* Brought back by a tap on the text, where nothing can hover. See `showBars`
   in reader.js. */
body[data-immersive="true"][data-bars="shown"] .rd-bar,
body[data-immersive="true"][data-bars="shown"] .rd-foot { transform: translateY(0); }

/* On a touch screen the way out is the only control on screen; a quarter
   opacity is a hint for a mouse, and very nearly nothing on e-paper. */
@media (hover: none) {
  .rd-exit { opacity: 0.6; }
}

/* The one control that stays. Without it, leaving full screen means knowing
   that "f" does it, or finding an edge that reveals a bar. */
.rd-exit {
  position: absolute;
  top: 10px;
  /* The end of the bar, where full screen itself sits — not a fixed right,
     which in right-to-left put it over the Library button instead. */
  inset-inline-end: 12px;
  z-index: 57;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--page-fg);
  opacity: 0.25;
  font-size: 1.1rem;
  transition: opacity 120ms ease;
}
.rd-exit:hover, .rd-exit:focus-visible { opacity: 0.9; }
.rd-exit[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  body[data-immersive="true"] .rd-bar,
  body[data-immersive="true"] .rd-foot,
  .rd-exit { transition: none; }
}

/* ------------------------------------------------------------------ */
/* Narrow screens                                                      */
/* ------------------------------------------------------------------ */

/* The reader bar holds nine controls. On a phone they add up to more than the
   screen is wide, so the last two — full screen and settings — were simply off
   the edge with no way to reach them.

   The book's title goes first, because it is the one thing on that bar the
   reader can already see: it is on the page in front of them. */
@media (max-width: 600px) {
  .rd-title { display: none; }

  .rd-bar { padding-inline: var(--sp-1); gap: var(--sp-1); }

  /* The reader bar's own actions only. Panel headers reuse the class for their
     buttons, and this rule was squeezing them too: on a phone the vocabulary
     panel's Practise and Export slid off the edge of a row that could not be
     scrolled back to them. */
  .rd-bar > .rd-bar-actions {
    flex: 1;
    /* `safe`: when the row is wider than the room, it starts from the left
       and scrolls, instead of spilling off the left edge — where a scrolling
       row cannot be scrolled back to, so the companion button was half cut
       off and unreachable on a narrow phone. */
    justify-content: flex-end;
    justify-content: safe flex-end;
    /* The grouping gap is a luxury of a wide bar. On a phone the room it costs
       is worth more than the breathing space it buys. */
    gap: var(--sp-2);
    /* A safety valve rather than the plan: if a future control pushes the row
       over again, it scrolls instead of vanishing. */
    overflow-x: auto;
    scrollbar-width: none;
  }
  .rd-bar-actions::-webkit-scrollbar { display: none; }
  .rd-bar-group { gap: 2px; }
  .rd-bar-find { gap: 2px; padding-inline-end: var(--sp-1); }

  /* Still a comfortable touch target at 44px in each direction, just not a
     wide one. */
  .rd-bar-actions .btn-icon,
  .rd-bar-find .btn-icon { width: 38px; padding: 0; }

  /* One column of text on a phone regardless of what the book asks for. */
  .rd-foot { padding-inline: var(--sp-2); }
}

/* The smallest phones. After the rule above rather than before it: it sat
   earlier in this file, where the 38px above silently undid it. */
@media (max-width: 420px) {
  .rd-bar-actions .btn-icon,
  .rd-bar-find .btn-icon { width: 36px; }
}

/* A 320px phone: the gaps give up the last few pixels so full screen is on
   the bar rather than a scroll away. */
@media (max-width: 360px) {
  .rd-bar { gap: 2px; }
  .rd-bar > .rd-bar-actions { gap: var(--sp-1); }
  .rd-bar-find { padding-inline-end: 2px; margin-inline-end: 0; }
}

/* ------------------------------------------------------------------ */
/* Location key                                                        */
/* ------------------------------------------------------------------ */

/* Highlights, under the bookmarks in the Marks panel. */
.marks-highlights { margin-top: var(--sp-4); }
.mark-body {
  border: 0;
  background: transparent;
  color: inherit;
  text-align: start;
  padding: 0;
  width: 100%;
}
/* The sentence itself is the label, so it reads as a quotation rather than as
   a row in a table. */
.mark-quote { font-style: italic; line-height: 1.4; }
.result-bookmark .result-meta { margin-top: 2px; }

.location-key { margin-top: var(--sp-5); }

.lk-current {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin: var(--sp-2) 0 var(--sp-3);
}

.lk-code {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  letter-spacing: 0.08em;
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-3);
  user-select: all;
}

/* The spelled form is the one people read aloud, so it gets to be ordinary
   text rather than a monospaced code. */
.lk-code-words {
  font-family: var(--font-ui);
  font-size: var(--step--1);
  letter-spacing: 0;
}


/* ------------------------------------------------------------------ */
/* The audiobook player (audiobook.js)                                  */
/* Inside the footer, above the taper, so the page makes room for it.  */
/* ------------------------------------------------------------------ */

.listen-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-1) 0 var(--sp-2);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--sp-1);
}
.listen-bar[hidden] { display: none; }
.lb-controls, .lb-extra { display: flex; align-items: center; gap: var(--sp-1); }
.lb-extra { margin-inline-start: auto; flex-wrap: wrap; justify-content: flex-end; }
.lb-info {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  min-width: 0;
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
}
.lb-time { white-space: nowrap; }
.lb-where { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 16rem; }
.lb-status { white-space: nowrap; font-size: var(--step--2); }
.lb-btn { width: var(--tap); }
.lb-rate { min-width: 3.2rem; font-variant-numeric: tabular-nums; }
#btn-listen.is-on { background: color-mix(in srgb, currentColor 14%, transparent); }

/* Choosing a sentence: the page says it is waiting for a tap. */
body[data-listen-pick] #viewer { outline: 2px dashed var(--accent); outline-offset: 4px; cursor: crosshair; }

.lb-panel h3 { margin: var(--sp-2) 0 0; font-size: var(--step-0); }
.lb-table-wrap { max-height: 40vh; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.lb-table { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.lb-table td { padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: middle; }
.lb-table tr:last-child td { border-bottom: 0; }
.lb-file { overflow-wrap: anywhere; }
.lb-table .select { width: 100%; min-width: 9rem; }
.lb-state { width: 1.5rem; text-align: center; }

/* On a phone: the essentials on one line, the rest on the next. */
@media (max-width: 600px) {
  .listen-bar { gap: var(--sp-1) var(--sp-2); }
  .lb-wide { display: none; }
  .lb-info { flex: 1 1 0; flex-direction: column; gap: 0; }
  .lb-where { max-width: 100%; font-size: var(--step--2); }
  .lb-extra { flex-basis: 100%; }
  .lb-extra .btn-sm { padding-inline: var(--sp-2); }
}
[data-epaper] body[data-listen-pick] #viewer { outline-style: solid; }