/* ============================================================
   Bushwhack reader
   ============================================================ */

:root {
  /* The two colours the dark palette is built from: the sheet the text sits
     on, and the darker ground behind it. */
  --base:       #020308;
  --base2:      #000000;

  --paper:      #faf8f4;
  --paper-2:    #f2eee6;
  --ink:        #23211d;
  --ink-soft:   #4a463f;
  --muted:      #857e73;
  --rule:       #ddd6c9;
  --accent:     #4a6f45;
  --accent-end: var(--accent);
  --main:       var(--accent);

  --face-serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --face-sans:  ui-sans-serif, -apple-system, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;

  --face-mono:  ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* One family / weight / style per slot, written by the reader from the
     catalogue. The values here are only what shows before script runs. */
  --f-title: var(--face-serif);    --w-title: 400;    --s-title: normal;
  --f-body: var(--face-serif);     --w-body: 400;     --s-body: normal;
  --f-subhead: var(--face-serif);  --w-subhead: 600;  --s-subhead: normal;
  --f-quote: var(--face-serif);    --w-quote: 400;    --s-quote: normal;
  --f-tocitem: var(--face-serif);  --w-tocitem: 400;  --s-tocitem: normal;
  --f-topbar: var(--face-sans);    --w-topbar: 400;   --s-topbar: normal;
  --f-pagert: var(--face-serif);   --w-pagert: 400;   --s-pagert: normal;
  --f-pagerk: var(--face-sans);    --w-pagerk: 400;   --s-pagerk: normal;
  --f-barbtn: var(--face-sans);    --w-barbtn: 500;   --s-barbtn: normal;
  --f-settings: var(--face-sans);  --w-settings: 500; --s-settings: normal;
  --f-code: var(--face-mono);      --w-code: 400;     --s-code: normal;

  --body-size:  1.175rem;
  --measure:    34rem;
  --para-gap:   1.6em;

  /* How far the reading text is pulled toward the run's colour. 0% leaves it
     neutral grey; 100% is the raw stops, which on a near-black page is more
     colour than most eyes want to read a page of. */
  --text-tint:  60%;

  /* What a word rises to under the cursor — a step up in value, not a hue
     change, so it reads as lit rather than as a different colour. */
  /* how far a word lifts in value under the cursor, and how long the cursor
     must rest before it gets there */
  --word-lift:  60%;
  --word-dwell: 2.6s;

  /* Set per piece by the reader; these keep the text legible if no gradient
     is configured at all. */
  --text-from:  var(--ink);
  --text-to:    var(--ink);

  /* The sheet the text sits on is exactly the background colour that was set.
     Nothing is tinted into it. The ground behind it is the same colour taken
     down, so the sheet reads as a page lying on something darker. */
  /* Dimming is done by mixing toward the panel, not by going transparent —
     these resolve against each entry's own accent at the point of use. */
  --toc-idle:   color-mix(in oklab, var(--accent), var(--sheet) 14%);
  --toc-read:   color-mix(in oklab, var(--accent), var(--sheet) 34%);

  --sheet:      var(--paper);
  --backdrop:   color-mix(in oklab, var(--paper), #000 55%);
  --scroll-line: var(--rule);

  --toc-w: min(21rem, 84vw);

  --bar: 52px;
  --bar2: 32px;
  --spine-w: 34px;
  --scrollbar-w: .25in;
  /* How far anything pinned to the right edge has to stand off it: the margin
     plus the scrollbar it must not sit over. Everything on that edge reads this
     one value, so the Settings button and its panel can't drift apart. */
  --edge-gap: calc(.75rem + var(--scrollbar-w));
  --cover-inset: 1.5rem;
  --cursor-star:  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cdefs%3E%3CradialGradient id='g'%3E%3Cstop offset='.3' stop-color='%2346c890' stop-opacity='.4'/%3E%3Cstop offset='1' stop-color='%2346c890' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='12' cy='12' r='11.5' fill='url(%23g)'/%3E%3Cpath d='M12 3.2C12.55 8.2 15.8 11.45 20.8 12 15.8 12.55 12.55 15.8 12 20.8 11.45 15.8 8.2 12.55 3.2 12 8.2 11.45 11.45 8.2 12 3.2Z' fill='%2346c890'/%3E%3C/svg%3E") 12 12, auto;
  --cursor-click: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cdefs%3E%3CradialGradient id='g'%3E%3Cstop offset='.3' stop-color='%2346c890' stop-opacity='.4'/%3E%3Cstop offset='1' stop-color='%2346c890' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='12' cy='12' r='11.5' fill='url(%23g)'/%3E%3Cpath d='M12 3.2C12.55 8.2 15.8 11.45 20.8 12 15.8 12.55 12.55 15.8 12 20.8 11.45 15.8 8.2 12.55 3.2 12 8.2 11.45 11.45 8.2 12 3.2Z' fill='%2346c890'/%3E%3C/svg%3E") 12 12, pointer;
  /* One run, shared by the title page's frame, title and bar. Doubled back on
     itself so a sweep across it never jumps at the turn. */
  --sweep: linear-gradient(90deg, var(--accent), var(--accent-end), var(--accent));

  /* The same idea for a piece title, but staying in the main colour: it runs out
     to a lightened version of itself and back, so what passes over the letters is
     a change in value rather than a change of colour. */
  /* A band of light that travels over the run rather than replacing it — this
     lifts the value of whatever it crosses instead of introducing a colour. */
  --pass: linear-gradient(100deg,
    transparent 38%,
    rgba(255, 255, 255, .45) 50%,
    transparent 62%);

  --sweep-lit: linear-gradient(90deg,
    var(--accent),
    color-mix(in oklab, var(--accent), #fff 62%),
    var(--accent));
}

/* --- themes --- */
[data-theme="sepia"] {
  --paper: #f4ecdc; --paper-2: #ebe0c9; --ink: #3b3226; --ink-soft: #5a4e3c;
  --muted: #8b7c62; --rule: #ddceb0; --accent: #6b5426;
}
/* Night: deep and cool, closer to looking up than to a dimmed page. */
/* Everything in the dark palette is lifted off one base colour, so setting
   the background is enough — panels, rules, the scrollbar and the muted type
   all step away from it by fixed amounts and stay in register. */
[data-theme="dark"] {
  --paper:    var(--base);
  --ink:      #cfd3df;
  --paper-2:  color-mix(in oklab, var(--paper), var(--ink) 6%);
  --rule:     color-mix(in oklab, var(--paper), var(--ink) 13%);
  --muted:    color-mix(in oklab, var(--paper), var(--ink) 42%);
  --ink-soft: color-mix(in oklab, var(--ink), var(--paper) 26%);
  --backdrop: var(--base2);
}
@media (prefers-color-scheme: dark) {
  [data-theme="auto"] {
    --paper:    var(--base);
    --ink:      #cfd3df;
    --paper-2:  color-mix(in oklab, var(--paper), var(--ink) 6%);
    --rule:     color-mix(in oklab, var(--paper), var(--ink) 13%);
    --muted:    color-mix(in oklab, var(--paper), var(--ink) 42%);
    --ink-soft: color-mix(in oklab, var(--ink), var(--paper) 26%);
    --backdrop: var(--base2);
  }
}

/* --- reading prefs --- */
[data-size="s"]     { --body-size: 1.02rem; }
[data-size="m"]     { --body-size: 1.175rem; }
[data-size="l"]     { --body-size: 1.32rem; }
[data-size="xl"]    { --body-size: 1.5rem; }
[data-measure="narrow"] { --measure: 29rem; }
[data-measure="normal"] { --measure: 34rem; }
[data-measure="wide"]   { --measure: 41rem; }

/* --- base --- */
* { box-sizing: border-box; }
html, body { height: 100%; }
html {
  -webkit-text-size-adjust: 100%;
  /* The document itself never scrolls; the scroll lives in #main. A document
     scrollbar runs the full height of the window and takes its width out of the
     title bar, which then stops short of the right edge. */
  overflow: hidden;
}

/* Both layers step together, but the pass rides a wider image than the run
   beneath it, so the same travel carries it twice as far across the box. */
@keyframes cover-pass {
  from { background-position: 0% 50%, 0% 50%; }
  to   { background-position: 100% 50%, 100% 50%; }
}

@keyframes cover-sweep {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .cover-title, .cover-rule, .cover-leaf::before { animation: none; }
  html { scroll-behavior: auto; }
  * { transition: none !important; }
}

body {
  margin: 0;
  cursor: var(--cursor-star);
  background: var(--backdrop);
  color: var(--ink);
  font-family: var(--f-body);
  font-weight: var(--w-body);
  font-style: var(--s-body);
  font-size: var(--body-size);
  line-height: 1.7;
  transition: background .3s ease, color .3s ease;
}

svg {
  width: 20px; height: 20px; display: block;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

/* --- scrollbar ---
   A quarter inch, square, and quiet: it sits in the page's own greys so it
   reads as edge rather than as one more coloured thing competing with the
   text. It only comes up under the cursor. */
/* Chrome ignores every ::-webkit-scrollbar rule the moment the standard
   scrollbar-color is set, so that fallback is fenced off to browsers
   without the pseudo-elements. Firefox can colour it but not widen it. */
@supports not selector(::-webkit-scrollbar) {
  #main { scrollbar-width: auto; scrollbar-color: var(--rule) transparent; }
}

::-webkit-scrollbar { width: var(--scrollbar-w); }

/* Kept together and in descending order: whichever is narrowest and still
   matches has to be the one that wins. Split across the breakpoint blocks
   further down, these resolved by their order in the file instead. */
@media (max-width: 820px) { :root { --scrollbar-w: 12px; } }
@media (max-width: 700px) { :root { --scrollbar-w: 6px; } }
@media (max-width: 640px) { :root { --scrollbar-w: 0px; } }
::-webkit-scrollbar-track {
  background: var(--backdrop);
  border-left: 1px solid var(--scroll-line);
}
::-webkit-scrollbar-thumb {
  background: var(--rule);
  border-radius: 0;
  transition: background .25s ease;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); }

/* A wide bar is a design choice on a desktop and an obstruction on a phone. */
@media (max-width: 700px) {
  ::-webkit-scrollbar-thumb { border: 0; }
  ::-webkit-scrollbar-track { border-left: 0; }
}

/* --- progress ---
   Lives inside the title bar, snapped to its bottom edge with the separator
   below it. Being part of the bar is what keeps it clear of the contents
   drawer, which now opens beneath the whole assembly. */
.progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--backdrop); pointer-events: none;
  transition: opacity .25s ease;
}

/* Nothing has been read yet with the book shut, so there's no progress to show. */
.cover-up .progress { opacity: 0; }
/* Sized to the viewport, not to the bar, so scrolling uncovers a fixed run
   of colour rather than squeezing one into the fill. */
.progress span {
  display: block; height: 100%; width: 0;
  background-image: linear-gradient(90deg, var(--accent), var(--accent-end));
  background-size: 100vw 100%;
  background-repeat: no-repeat;
  transition: width .12s linear;
}

/* --- top bar --- */
.topbar {
  position: fixed; top: 0; left: 0; right: 0; height: var(--bar); z-index: 50;
  display: flex; align-items: center; gap: .5rem;
  /* the 2px reserves the strip the progress bar sits in */
  padding: 0 .6rem 2px;
  background: var(--backdrop);
  border-bottom: 1px solid var(--rule);
}

.topbar-title {
  flex: 1; min-width: 0; text-align: center;
  font-family: var(--f-topbar); font-size: .78rem; letter-spacing: .04em;
  font-weight: var(--w-topbar); font-style: var(--s-topbar);
  color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.booktitle {
  text-transform: uppercase; letter-spacing: .12em;
  font-weight: 600; color: var(--ink-soft);
}
.topbar-title .sep {
  margin: 0 .45rem;
  color: color-mix(in oklab, var(--muted), var(--backdrop) 45%);
}

/* --- the button row under the bar --- */
/* Contents, and whatever the dev layer stacks under it, ride together as a
   column flush to the left edge — and travel as one when a left panel opens. */
.subbar-left {
  display: flex; flex-direction: column; align-items: flex-start; gap: .4rem;
  pointer-events: none;
  transition: transform .28s cubic-bezier(.32, .72, 0, 1);
}
.subbar-left .barbtn { pointer-events: auto; }

.subbar {
  position: fixed; top: calc(var(--bar) + 12px); left: 0; right: 0;
  height: var(--bar2); z-index: 55;
  display: flex; justify-content: space-between; align-items: flex-start;
  /* clear of the scrollbar on the right, which now runs inside the scroll
     directly beneath this row */
  padding: 0 var(--edge-gap) 0 .75rem;
  pointer-events: none;           /* only the buttons themselves catch clicks */
}
.barbtn {
  pointer-events: auto;
  height: var(--bar2); padding: 0 .85rem; cursor: var(--cursor-click);
  background: var(--sheet); color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 0;
  font-family: var(--f-barbtn); font-size: .66rem;
  letter-spacing: .14em; text-transform: uppercase;
  font-weight: var(--w-barbtn); font-style: var(--s-barbtn);
  transition: color .18s ease, border-color .18s ease,
              transform .28s cubic-bezier(.32, .72, 0, 1);
}
.barbtn:hover { color: var(--ink); }

/* Rides out to sit just clear of the open drawer, still the control that
   closes it. */
/* The bar beside the scroll opens and closes the book on a desktop. There's no
   room for it on a phone, so the book gets a button in this row instead — which
   is also what makes the row read as three across rather than two apart. */
#coverBtn, #fullBtn { display: none; }

.toc-open .subbar-left { transform: translateX(var(--toc-w)); }

/* Lift the row over the drawer so the button stays clickable while open. */
.toc-open .subbar { z-index: 75; }
.barbtn[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.barbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.iconbtn {
  flex: none; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 0;
  background: none; border: 0; color: var(--ink-soft); cursor: var(--cursor-click);
  transition: background .18s ease, color .18s ease;
}
.iconbtn:hover { background: var(--paper-2); color: var(--ink); }
.iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- table of contents --- */
/* Comes up beneath the title bar rather than over it. */
.toc {
  position: fixed; top: var(--bar); bottom: 0; left: 0; z-index: 70;
  width: var(--toc-w);
  background: var(--sheet); border-right: 1px solid var(--rule);
  transform: translateX(-101%);
  transition: transform .28s cubic-bezier(.32, .72, 0, 1);
  display: flex; flex-direction: column;
}
.toc.open { transform: none; }

/* With the book shut the drawer is still the full height of the window and
   still says Bushwhack across the top — only the entries are folded away. They
   unfold when the book opens, each one a beat behind the one above it. */
.toc-list li {
  overflow: hidden;
  max-height: 6.5rem;
  transition: max-height .34s cubic-bezier(.32, .72, 0, 1), opacity .26s ease;
  transition-delay: calc(var(--i, 0) * 16ms);
}
.cover-up .toc-list li {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
  /* folding away happens all at once; it's the unfolding that's worth watching */
  transition-delay: 0s;
}
.toc-list {
  list-style: none; margin: 0; padding: 1rem 0 3rem;
  overflow-y: auto; flex: 1; counter-reset: toc;
}
.toc-list li { counter-increment: toc; }
.toc-list button {
  display: grid; grid-template-columns: 2.2rem 1fr; align-items: baseline; gap: .1rem;
  width: 100%; text-align: left; cursor: var(--cursor-click);
  background: none; border: 0; border-left: 2px solid transparent;
  padding: .58rem 1.35rem .58rem 1rem;
  /* Each entry carries its own point on the run, so the contents shows the
     whole progression at once. */
  font-family: var(--f-tocitem); font-size: .97rem; line-height: 1.35;
  font-weight: var(--w-tocitem); font-style: var(--s-tocitem);
  color: var(--toc-idle);
  transition: background .15s ease, color .15s ease;
}
.toc-list button::before {
  content: counter(toc, decimal-leading-zero);
  font-family: var(--face-sans); font-size: .68rem; letter-spacing: .06em;
  color: color-mix(in oklab, var(--muted), var(--sheet) 30%);
}
.toc-list button:hover { background: var(--paper-2); color: var(--accent); }
.toc-list li.current button {
  color: var(--accent); border-left-color: var(--accent); font-weight: 600;
}
.toc-list li.current button::before { color: var(--accent); }
.toc-list li.read button { color: var(--toc-read); }

/* The book's name over its contents. Doubles as the way back to the cover. */
.toc-mast {
  flex: none; width: 100%; text-align: left; cursor: var(--cursor-click);
  display: flex; flex-direction: column; align-items: flex-start; gap: .3rem;
  background: none; border: 0; border-bottom: 1px solid var(--rule);
  padding: 1.05rem 1.35rem .95rem 1rem;
  font-family: var(--f-title); font-weight: var(--w-title); font-style: var(--s-title);
  font-size: 1.1rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent);
  transition: background .15s ease;
}
.toc-mast-hint {
  font-family: var(--face-sans); font-size: .55rem; font-weight: 400;
  letter-spacing: .2em; text-transform: uppercase;
  color: color-mix(in oklab, var(--muted), var(--sheet) 28%);
}
.toc-mast:hover { background: var(--paper-2); }
.toc-mast:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The book's spine, down the left edge of the scroll. Stays while you read;
   click it and the cover falls shut again. */
.spine {
  position: fixed; top: var(--bar); bottom: 0;
  /* hard against the outside of the sheet's left edge, never over it */
  left: max(0px, calc(50% - (var(--measure) / 2 + 4rem) - var(--spine-w)));
  width: var(--spine-w);
  z-index: 45;                     /* beside the scroll, under the bars */
  display: block; padding: 0; cursor: var(--cursor-click);
  background: var(--paper);
  /* running into the title bar, it needs no top edge of its own — one there
     would only double the bar's own bottom rule */
  border: 0;
  border-right: 1px solid var(--rule);
  transition: background .15s ease;
}
.spine:hover { background: var(--paper-2); }

/* With the book already shut there's nothing for it to close, so it stops
   offering itself — no hover, no pointer, nothing to click. */
.cover-up .spine { pointer-events: none; }
.spine:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Narrower than this there's no room beside the sheet. The masthead over the
   contents still closes the book. */
@media (max-width: 820px) { .spine { display: none; } }

/* --- the title page --- */
/* A cover the width of the sheet, hinged on its left edge. Flat ground, a
   hairline on each side and a bar for the spine — nothing soft-edged. */
.cover {
  position: fixed; bottom: 0;
  /* up against the title bar, like the side bar beside it — the button row
     floats over it and stays visible */
  top: var(--bar);
  /* exactly the sheet's own box, and opaque: the margin around the frame has to
     cover the text under it, not let it show through at the edges */
  left: max(0px, calc(50% - (var(--measure) / 2 + 4rem)));
  width: min(100%, calc(var(--measure) + 8rem));
  background: var(--sheet);
  /* The cover lands right on top of the sheet's own right border and would
     paint it out. It carries the line itself instead — same colour, same pixel,
     so it hands back over cleanly when the cover goes. Nothing is needed on the
     other two sides: the side bar draws the left, and the top runs into the
     title bar's own rule. */
  border-right: 1px solid var(--rule);
  transition: background .9s ease;
  /* over the sheet, under the bars and the drawers */
  z-index: 40;
  display: grid; align-items: stretch;
  perspective: 2200px;
}
.cover[hidden] { display: none; }
.cover-up #main { overflow: hidden; }

.cover-leaf {
  position: relative;
  width: 100%; margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 1.1rem; text-align: center; cursor: var(--cursor-click);
  /* nothing lights up on it — no focus ring, no tap flash, no text selection */
  user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  /* the side padding matches the frame's inset, so the title and the bar span
     exactly the frame's width and all three sample the run at the same points */
  padding: 15vh var(--cover-inset) 2rem;
  background: var(--sheet);
  border: 0;
  transform-origin: left center;
  backface-visibility: hidden;
  /* Closing: the cover is back almost at once and then swings shut, so you
     watch it fall rather than watching it appear. */
  transition: transform .9s cubic-bezier(.4, 0, .2, 1), opacity .2s ease;
}
/* The frame. A gradient can't be animated through border-image, so it's a
   masked overlay instead — the run painted edge to edge with its middle
   punched out, leaving a 2px border. */
.cover-leaf::before {
  content: ''; position: absolute; inset: var(--cover-inset); z-index: 1;
  padding: 2px; pointer-events: none;
  background-image: var(--pass), var(--sweep);
  background-size: 300% 100%, 200% 100%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  animation: cover-pass 14s ease-in-out infinite alternate;
}

/* Swings open and out of the way. */
/* Mid-swing the ground is clear, so closing the book doesn't black the scroll
   out before the cover has started moving — the two arrive together. */
.cover.opening { background: transparent; }
/* Opening: it stays solid the whole way round and only goes in the last few
   degrees. Stops just short of edge-on — past 90 the leaf turns its back face to
   us and would vanish outright instead of fading. */
.cover.opening .cover-leaf {
  transform: rotateY(-88deg);
  opacity: 0;
  transition: transform .9s cubic-bezier(.4, 0, .2, 1), opacity .12s ease .7s;
}

.cover-title {
  margin: 0; align-self: stretch;      /* full width, so it maps like the frame */
  font-family: var(--f-title); font-weight: var(--w-title); font-style: var(--s-title);
  font-size: clamp(2.4rem, 11vw, 4.6rem); line-height: 1.05; letter-spacing: .04em;
  color: var(--accent);                /* stands in where the clip is unsupported */
  background-image: var(--pass), var(--sweep);
  background-size: 300% 100%, 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: cover-pass 14s ease-in-out infinite alternate;
}
/* Full width so it samples the same run, but only its middle is painted —
   the bar reads short while sitting on the same colours as the frame above it. */
.cover-rule {
  display: block; align-self: stretch; height: 1px; padding: 0 24%;
  background-image: var(--pass), var(--sweep);
  background-size: 300% 100%, 200% 100%;
  background-clip: content-box;
  animation: cover-pass 14s ease-in-out infinite alternate;
}
.cover-hint {
  margin: 1.6rem 0 0;
  font-family: var(--face-sans); font-size: .62rem;
  letter-spacing: .2em; text-transform: uppercase;
  color: color-mix(in oklab, var(--muted), var(--sheet) 34%);
}

@media (prefers-reduced-motion: reduce) {
  /* the opening rule carries its own transition and outweighs .cover-leaf here */
  .cover, .cover-leaf, .cover.opening .cover-leaf { transition: none; }
}

/* --- settings --- */
.settings {
  /* Hangs from the Settings button rather than from the bar. */
  position: fixed; top: calc(var(--bar) + 12px + var(--bar2) + 18px);
  right: var(--edge-gap); z-index: 70;
  width: min(20rem, calc(100vw - 1.2rem - var(--scrollbar-w)));
  background: var(--backdrop); border: 1px solid var(--rule); border-radius: 0;
  padding: .35rem .95rem .85rem;
  visibility: hidden; transform: translateY(-8px);
  transition: transform .2s cubic-bezier(.32, .72, 0, 1), visibility .2s;
}
.settings.open { visibility: visible; transform: none; }
.set-row { padding: .6rem 0; border-bottom: 1px solid var(--rule); }
.set-row:last-child { border-bottom: 0; }
.set-label {
  display: block; margin-bottom: .45rem;
  font-family: var(--f-settings); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}
.seg { display: flex; gap: 3px; background: var(--paper-2); border-radius: 0; padding: 3px; }
.seg button {
  flex: 1; padding: .4rem .2rem; cursor: var(--cursor-click);
  border: 0; border-radius: 0; background: none; color: var(--ink-soft);
  font-family: var(--f-settings); font-size: .78rem;
  font-weight: var(--w-settings); font-style: var(--s-settings);
  transition: background .15s ease, color .15s ease;
}
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] {
  background: var(--paper); color: var(--ink); font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--rule);
}

.set-val { float: right; letter-spacing: .08em; color: var(--ink-soft); }

/* Volume. Flat like everything else — a hairline track and a square thumb,
   with no filled portion, since a fill would mean a gradient. */
.slider {
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; height: 14px;
  background: none; cursor: var(--cursor-click);
}
.slider::-webkit-slider-runnable-track { height: 1px; background: var(--rule); }
.slider::-moz-range-track { height: 1px; background: var(--rule); }
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 10px; height: 10px; margin-top: -5px; border: 0; border-radius: 0;
  background: var(--ink-soft); transition: background .15s ease;
}
.slider::-moz-range-thumb {
  width: 10px; height: 10px; border: 0; border-radius: 0;
  background: var(--ink-soft); transition: background .15s ease;
}
.slider:hover::-webkit-slider-thumb, .slider:focus::-webkit-slider-thumb { background: var(--ink); }
.slider:hover::-moz-range-thumb, .slider:focus::-moz-range-thumb { background: var(--ink); }

/* --- the page --- */
/* Clears the bar, its rule, and the progress line beneath them. */
/* The scroll. Fixed below the title bar and scrolling its own contents, so the
   scrollbar starts under the bar rather than running the whole window height and
   shortening it. */
#main {
  position: fixed;
  top: var(--bar); left: 0; right: 0; bottom: 0;
  overflow-y: auto; overflow-x: hidden;
  scroll-behavior: smooth;
  /* Hold the scrollbar's space whether or not a piece is long enough to need
     one. Without this a short piece drops the bar, the box widens by its full
     quarter inch, and the centred sheet jumps sideways between pieces. */
  scrollbar-gutter: stable;
}

/* A sheet on darker ground. Separated by flat colour and a hairline —
   no shadow, nothing soft-edged. */
.sheet {
  max-width: calc(var(--measure) + 8rem);
  margin: 0 auto;
  min-height: 100%;
  background: var(--sheet);
  /* No top edge of its own: the title bar's bottom rule sits directly above
     this and already does the separating. A second one only doubles it. */
  border-left: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
}

@media (max-width: 640px) {
  .sheet { border-left: 0; border-right: 0; }
}

.page {
  max-width: var(--measure);
  margin: 0 auto;
  padding: clamp(2.5rem, 8vw, 5rem) 1.5rem 1rem;
  font-family: var(--f-body);
  font-weight: var(--w-body);
  font-style: var(--s-body);

  /* Fallback: a flat tint, for anywhere the text can't hold a gradient. */
  color: color-mix(in oklab, var(--ink), var(--accent) var(--text-tint));
}

/* Solid, at the piece's own point in the run — the title states the colour
   the text then fades away from. */
.page h1 {
  margin: 0 0 2.4rem; text-align: center; text-wrap: balance;
  font-family: var(--f-title);
  /* Cormorant sits small on the body and is finely drawn, so it wants more
     size and a little more air than a text face would. */
  font-size: clamp(2.15rem, 5.8vw, 3rem); line-height: 1.08;
  font-weight: var(--w-title);
  font-style: var(--s-title);
  text-transform: uppercase;
  letter-spacing: .07em;
  text-indent: .07em;
  color: var(--main);                  /* stands in where the clip is unsupported */

  /* Clipped to the letters, so the light passes through the type itself. The
     keyframes are the cover's — the motion is the same, only the run differs. */
  background-image: var(--sweep-lit);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: cover-sweep 9s ease-in-out infinite alternate;
}
/* Where the rule under the title used to be. Same three layers as a lens
   flare — a hairline core, a softer glow the same shape, and a flat bloom
   behind — but all in the main colour, and it stands in flow rather than
   sitting behind anything, so it reads as the ornament it replaced. */
.page h1::after {
  content: ""; display: block;
  width: min(18rem, 60%); height: 26px;
  margin: 1.4rem auto 0;

  /* The colour that travels — the same run the title is swept with. */
  background-image: var(--sweep-lit);
  background-size: 200% 100%;
  background-repeat: no-repeat;

  /* And the shape it shows through. All three are ellipses covering the whole
     box, so the falloff is smooth in both directions. The earlier version used
     flat gradients pressed into 2px and 11px strips — but a linear gradient only
     fades along its own axis, so top and bottom were cut off square. */
  -webkit-mask-image:
    radial-gradient(ellipse 50% 8% at 50% 50%,
      #fff 0%, rgba(255, 255, 255, .72) 38%, rgba(255, 255, 255, .18) 68%, transparent 85%),
    radial-gradient(ellipse 45% 30% at 50% 50%,
      rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, .16) 52%, transparent 78%),
    radial-gradient(ellipse 38% 78% at 50% 50%,
      rgba(255, 255, 255, .26) 0%, rgba(255, 255, 255, .08) 46%, transparent 76%);
  mask-image:
    radial-gradient(ellipse 50% 8% at 50% 50%,
      #fff 0%, rgba(255, 255, 255, .72) 38%, rgba(255, 255, 255, .18) 68%, transparent 85%),
    radial-gradient(ellipse 45% 30% at 50% 50%,
      rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, .16) 52%, transparent 78%),
    radial-gradient(ellipse 38% 78% at 50% 50%,
      rgba(255, 255, 255, .26) 0%, rgba(255, 255, 255, .08) 46%, transparent 76%);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;

  transform-origin: center;
  /* one breathes it, the other walks the light along it — different properties,
     so they don't fight */
  animation: title-flare 7s ease-in-out infinite,
             cover-sweep 9s ease-in-out infinite alternate;
}

/* It breathes rather than travels, and goes all the way up at the top of the
   swell — no ceiling on this one. */
@keyframes title-flare {
  0%, 100% { opacity: .62; transform: scaleX(.94); }
  50%      { opacity: 1;   transform: scaleX(1.05); }
}

@media (prefers-reduced-motion: reduce) {
  .page h1, .page h1::after { animation: none; }
  .page h1::after { opacity: 1; }
}
.page h2 {
  margin: 2.4em 0 .8em; line-height: 1.35;
  font-family: var(--f-subhead);
  font-size: 1.16em; font-weight: var(--w-subhead);
  font-style: var(--s-subhead); letter-spacing: .01em;
}
.page h3 {
  margin: 2.4em 0 .8em;
  font-family: var(--f-subhead); font-size: .74em;
  font-weight: var(--w-subhead); font-style: var(--s-subhead);
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}

/* --- verse --- */
.stanza { margin: 0 0 1.55em; }
.stanza:last-child { margin-bottom: 0; }

/* Leading whitespace in the source becomes a real indent (--ind).
   A line too long for the column wraps with a hanging indent, so a
   runover reads as continuation rather than as a new line. */
/* Flush left throughout. A line too long for the column wraps back to the
   margin rather than hanging under itself — only whitespace the author typed
   (--ind) moves a line in. */
.line {
  display: block;
  padding-left: var(--ind, 0em);
  text-indent: 0;
}

.page h2 + .stanza,
.page h3 + .stanza { margin-top: -.2em; }

/* --- prose ---
   Paragraphs are separated by space, not by an indent. These are discrete
   blocks that want air around them, not a continuous column of narration. */
.page > p {
  margin: 0 0 var(--para-gap);
  hanging-punctuation: first last;
}
.page > p:last-child { margin-bottom: 0; }

/* --- words ---
   Every word is its own element so it can answer the cursor. inline-block is
   what makes it transformable; the whitespace between words is left as real
   text nodes, so wrapping and spacing are untouched. */
.page .w {
  display: inline-block;
  /* An inline-block is a block container, so it would inherit the hanging
     indent from .line and subtract it from its own shrink-to-fit width —
     collapsing short words to zero and stacking them on their neighbours. */
  text-indent: 0;
  /* --wc is this word's own point on the run, set by the reader. Owning an
     opaque colour is what lets it wobble without losing its fill. */
  color: var(--wc, inherit);
  /* the way back down: quick */
  transition: color .4s ease-out;
}
/* The ramp up is the dwell itself — the longer the cursor rests, the further
   along this transition the word gets, so value builds rather than snapping. */
.page .w:hover {
  color: color-mix(in oklab, var(--wc), #fff var(--word-lift));
  transition: color var(--word-dwell) linear;
  animation: wobble .45s ease-in-out;
}

@keyframes wobble {
  0%, 100% { transform: rotate(0) translateY(0); }
  20%      { transform: rotate(-1.1deg) translateY(-.6px); }
  40%      { transform: rotate(.8deg) translateY(.4px); }
  60%      { transform: rotate(-.5deg) translateY(-.3px); }
  80%      { transform: rotate(.25deg) translateY(.15px); }
}

@media (prefers-reduced-motion: reduce) {
  .page .w:hover { animation: none; }
}

/* Reader can switch the whole effect off; the words stay split but stop
   answering the cursor. */
[data-words="off"] .page .w:hover {
  color: var(--wc, inherit);
  animation: none;
}

.page em { font-style: italic; }
.page strong { font-weight: 600; }
.page a { color: var(--accent); text-underline-offset: 2px; }

.page blockquote {
  margin: 1.6em 0; padding-left: 1.2em;
  border-left: 2px solid var(--rule);
  color: var(--ink-soft);
  font-family: var(--f-quote); font-weight: var(--w-quote); font-style: italic;
}
.page blockquote p { text-indent: 0; }
.page blockquote .stanza { margin-bottom: 0; }

.page hr.scene { border: 0; margin: 2em 0; text-align: center; }
.page hr.scene::before {
  content: "\2042"; color: var(--muted); font-size: 1.1em; letter-spacing: .5em;
}

.page code {
  font-family: var(--f-code);
  font-size: .87em; background: var(--paper-2);
  padding: .12em .35em; border-radius: 0;
}

/* --- empty state --- */
.empty { max-width: 34rem; margin: 0 auto; padding: 5rem 1.5rem; }
.empty h1 { text-align: left; margin-bottom: 1rem; }
.empty h1::after { display: none; }
.empty p { text-indent: 0; margin: 0 0 1rem; color: var(--ink-soft); }
.empty pre {
  background: var(--paper-2); border: 1px solid var(--rule); border-radius: 0;
  padding: 1rem 1.15rem; overflow-x: auto;
  font-family: var(--f-code);
  font-size: .82rem; line-height: 1.65; color: var(--ink-soft);
}

/* --- pager --- */
.pager {
  max-width: var(--measure); margin: 3rem auto 0;
  padding: 3rem 1.5rem 2.25rem;
  display: flex; gap: 1rem;
  border-top: 1px solid var(--rule);
}
.pagerbtn {
  flex: 1 1 0; min-width: 0; text-align: left;
  background: none; border: 0; cursor: var(--cursor-click); padding: 1.35rem 0 0;
  font-family: inherit; color: var(--ink-soft);
  display: flex; flex-direction: column; gap: .3rem;
  transition: color .18s ease;
}
.pagerbtn.next { text-align: right; align-items: flex-end; }
.pagerbtn:hover { color: var(--accent-end); }
.pagerbtn[disabled] { visibility: hidden; }
.pager-k {
  font-family: var(--f-pagerk); font-size: .64rem; letter-spacing: .18em;
  font-weight: var(--w-pagerk); font-style: var(--s-pagerk);
  text-transform: uppercase; color: var(--muted);
}
.pager-t {
  font-family: var(--f-pagert); font-weight: var(--w-pagert);
  font-style: var(--s-pagert);
  /* the run ends here — the text above has faded to this by now */
  color: var(--accent-end);
  font-size: 1.02rem; line-height: 1.3; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ============================================================
   Touch and small screens
   ============================================================ */

/* No hover on touch — a word answers a tap instead. */
@media (hover: none) {
  .page .w:hover { color: var(--wc, inherit); animation: none; }
}

.page .w.lit {
  color: color-mix(in oklab, var(--wc), #fff var(--word-lift));
  animation: wobble .45s ease-in-out;
  transition: color .2s linear;
}
[data-words="off"] .page .w.lit { color: var(--wc, inherit); animation: none; }
@media (prefers-reduced-motion: reduce) { .page .w.lit { animation: none; } }

/* --- tablet ---
   Only genuinely narrow windows give up the margins. At 1024 this was firing
   on ordinary desktop windows, flattening the sheet to full width and leaving
   no ground either side. */
@media (max-width: 820px) {
  /* Below this the bar beside the scroll is gone, so the row becomes a bar in
     its own right: Cover, Contents and Settings in a solid strip under the title
     bar. It used to be a transparent strip with the buttons floating loose over
     the text, which is what made them look like they were cutting into it.
     Everything below — the scroll, the cover, the drawer — starts under it. */
  :root { --navbar: calc(var(--bar2) + 12px); }

  .subbar {
    top: var(--bar);
    height: var(--navbar);
    align-items: center;
    padding: 0 var(--edge-gap) 0 .6rem;
    background: var(--sheet);
    border-bottom: 1px solid var(--rule);
    pointer-events: auto;       /* it's a surface now, not a gap with buttons in it */
  }
  .subbar-left { flex-direction: row; gap: .4rem; }

  #main, .cover, .toc { top: calc(var(--bar) + var(--navbar)); }

  /* the drawer opens beneath the row, so nothing has to slide out of its way */
  .toc-open .subbar-left { transform: none; }

  #coverBtn { display: block; }
  /* only where the browser can actually do it — the script adds the class */
  .can-fullscreen #fullBtn { display: block; }

  .sheet { max-width: none; margin: 0 1.25rem; }
}

/* --- phone: the text and nothing else --- */
@media (max-width: 640px) {
  :root { --bar: 46px; --bar2: 30px; --toc-w: 86vw; }

  /* full bleed — no sheet edges, no wide scrollbar stealing width */
  .sheet { margin: 0; border-left: 0; border-right: 0; }
  #main { scrollbar-gutter: auto; scrollbar-width: none; }

  .page {
    max-width: none;
    padding: 2.25rem 1.15rem 1rem;
  }
  .pager { max-width: none; padding: 2rem 1.15rem 4rem; }

  /* the contents pulls over most of the screen; the button rides to its edge
     but is clamped so it can always be tapped to close */

  .topbar-title { font-size: .72rem; }
  .barbtn { padding: 0 .7rem; font-size: .62rem; }
  .subbar { padding: 0 .6rem; }
  .settings { right: calc(.6rem + var(--scrollbar-w)); left: .6rem; width: auto; }
}

/* --- firelight ---
   Sits on the ground behind everything. The sheet is lifted above it, so the
   glow only shows in the margins either side of the reading column. */
.firelight {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none;
}
.firelight[hidden] { display: none; }

.sheet { position: relative; z-index: 1; }

/* ============================================================
   Reader feedback
   ============================================================ */
.fb {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1.5rem clamp(3rem, 9vw, 5rem);
  font-family: var(--f-settings);
}
/* Closed by default — an offer, not a form in the way of the writing. */
.fb.shut { padding-top: .25rem; }
.fb-open {
  width: 100%; padding: .7rem 1rem; cursor: var(--cursor-click);
  background: none; border: 1px dashed var(--rule); border-radius: 0;
  color: var(--muted);
  font-family: inherit; font-size: .66rem;
  letter-spacing: .14em; text-transform: uppercase;
  transition: color .18s ease, border-color .18s ease;
}
.fb-open:hover { color: var(--accent); border-color: var(--accent); }
.fb:not(.shut) { padding-top: 1.75rem; border-top: 1px solid var(--rule); margin-top: 1rem; }
.fb-head {
  margin: 0 0 1.5rem;
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 600; color: var(--muted);
}

.fb-row { margin-bottom: 1.4rem; }
.fb-q {
  display: block; margin-bottom: .55rem;
  font-size: .8rem; line-height: 1.45; color: var(--ink-soft);
}

.fb-scale { display: flex; gap: 3px; }
.fb-n {
  flex: 1; min-width: 0; height: 30px; cursor: var(--cursor-click);
  background: none; border: 1px solid var(--rule); border-radius: 0;
  color: var(--muted);
  font-family: inherit; font-size: .72rem;
  font-variant-numeric: tabular-nums;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.fb-n:hover { color: var(--ink); border-color: var(--muted); }
.fb-n.on { background: var(--accent); border-color: var(--accent); color: var(--sheet); }

.fb-yn { display: flex; gap: 3px; }
.fb-choice {
  min-width: 5rem; height: 30px; cursor: var(--cursor-click);
  background: none; border: 1px solid var(--rule); border-radius: 0;
  color: var(--muted);
  font-family: inherit; font-size: .72rem; letter-spacing: .06em;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.fb-choice:hover { color: var(--ink); border-color: var(--muted); }
.fb-choice.on { background: var(--accent); border-color: var(--accent); color: var(--sheet); }

.fb-field { display: block; margin-bottom: 1.4rem; }
.fb-input {
  display: block; width: 100%;
  padding: .6rem .7rem;
  background: var(--paper-2); border: 1px solid var(--rule); border-radius: 0;
  color: var(--ink);
  font-family: var(--f-body); font-size: .92rem; line-height: 1.6;
  resize: vertical;
}
textarea.fb-input { min-height: 4.5rem; }
.fb-input:focus { outline: none; border-color: var(--accent); }
.fb-input::placeholder { color: var(--muted); }

.fb-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: .8rem;
  padding-bottom: .5rem;
}
.fb-status { flex: 1; font-size: .72rem; color: var(--muted); }
.fb-send {
  padding: .55rem 1.2rem; cursor: var(--cursor-click);
  background: var(--accent); border: 1px solid var(--accent); border-radius: 0;
  color: var(--sheet);
  font-family: inherit; font-size: .7rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
}
.fb-send:hover { filter: brightness(1.08); }
.fb-send[disabled] { background: none; color: var(--muted); border-color: var(--rule); cursor: default; }
.fb-send.ghost {
  background: none; border-color: var(--rule); color: var(--muted);
  filter: none;
}
.fb-send.ghost:hover { color: var(--ink); }

.fb.done { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-bottom: .5rem; }
.fb-thanks { margin: 0; font-size: .82rem; color: var(--accent); }

@media (max-width: 640px) {
  /* That third value used to be 0, which left the review button flush with the
     bottom of the window — nothing under it, and a phone's own browser chrome
     sits over that strip. The inset also keeps it clear of the home indicator,
     which matters because the viewport is set to cover. */
  .fb {
    max-width: none;
    padding: 1.75rem 1.15rem calc(3rem + env(safe-area-inset-bottom, 0px));
  }
  .fb-n { font-size: .66rem; }
}

/* --- the pointer --- */
/* A little star instead of the arrow, sitting in a faint halo of its own colour.
   The tokens below are only a fallback: the reader redraws the star on the book's
   main colour at load, since an image cursor can't read a custom property. Kept as tokens rather than a rule of its own, so that every clickable
   thing in the stylesheets reads one and a new button picks it up without having
   to out-rank anything. Both hold the same star and differ only in the fallback
   they fall back to when the image can't be drawn. */
textarea, input[type="text"], input[type="search"], .vedit { cursor: text; }

/* The trail. Same star the cursor is drawn from, so the two always agree —
   the reader publishes it as --star-img whenever the colour is applied. */
.trail {
  position: fixed; inset: 0; z-index: 300;
  pointer-events: none; overflow: hidden;
}
.trail-star {
  position: absolute;
  width: var(--size); height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
  background: var(--star-img) center / contain no-repeat;
  animation: trail-fade .85s ease-out forwards;
}

@keyframes trail-fade {
  from { opacity: .8; transform: translate(0, 0) rotate(0) scale(1); }
  to   { opacity: 0; transform: translate(var(--drift), 15px) rotate(var(--spin)) scale(.28); }
}
