/* =====================================================================
   Book Reader — the book arrives CLOSED in the centre of the room.
   Opening it flips the hard cover to the left: the inside of the cover
   lands on the left page, the title page is revealed on the right.
   Larger, premium book; floating white toolbar like the live site.
   ===================================================================== */

/* =====================================================================
   READING MODE — THE SINGLE SOURCE OF TRUTH
   Whether the reader shows one page or a two-page spread is declared HERE,
   once, and nowhere else. flipbook.js reads this property rather than carrying
   its own copy of the breakpoint, so the layout and the pagination model
   cannot drift apart.

   It is a mode, not a width: a 932x430 phone on its side is wider than 860px
   but has no room for a spread, and was measured rendering two 168px pages at
   17 characters a line. So height counts as much as width, and both values
   below are ones this stylesheet already used.
   ===================================================================== */
:root { --reader-single-page: 0; }
@media (max-width: 860px), (max-height: 520px) { :root { --reader-single-page: 1; } }

.page-reader {
  overflow-x: hidden;
  /* ============ THE CHROME, MEASURED RATHER THAN ESTIMATED ============
     Vertical space the book must leave for everything that is not the book.
     The book's width is derived from `(100vh - this) * 1.60`, so an estimate
     that is too large throws away height on every screen — and this one was.

     REPORTED FROM A SMALLER LAPTOP: the book looked far smaller than on the
     reporter's usual machine. Measured on the shipped build, the book's bottom
     edge sat 51.0px above the fold at 1920x1080, 1440x900, 1366x768, 1366x625
     and 1280x600 — the same 51 pixels every time, which is the signature of a
     constant rather than of a layout that adapts.

     WHAT IS ACTUALLY ABOVE AND BELOW THE BOOK, measured at 1440x900:

       site header            124   (--site-header-h)
       room padding-top        10
       toolbar                 35
       toolbar -> progress      2   (margin)
       progress bar             4
       progress -> stage       10   (margin)
       room padding-bottom     22
       ------------------------------
       total                  207   =  83 + var(--site-header-h)

     So the figure is 83, not 112. The 29px difference compounds through the
     1.60 multiplier into 46px of book width, and the 22px of padding below the
     book is now respected exactly rather than doubled up on.

     IF THE TOOLBAR OR THE PROGRESS BAR EVER CHANGES HEIGHT, THIS NUMBER MOVES
     WITH IT — it is a measurement of this layout, not a universal constant.
     Gate 68 requires the book to use at least 70% of the height available to
     it at every viewport it tests, so a figure that drifts far enough to shrink
     the book noticeably will fail the build; a small drift will not, and the
     number has to be re-measured by hand when the chrome changes. */
  --reader-chrome: calc(83px + var(--site-header-h));
  --reader-chrome-mobile: calc(150px + var(--site-header-h));
}
/* ============ A SHORT SCREEN GETS ITS HEIGHT BACK FROM THE CHROME ============
   The site header is a fixed 124px. On a 900px laptop that is 14% of the
   viewport; on the 625px viewport a 1366x768 laptop actually leaves after
   browser chrome and a taskbar it is TWENTY PERCENT, and the book is sized
   from what is left. Correcting --reader-chrome above recovered the space that
   was being miscounted; this recovers space that is genuinely being spent.

   ONLY ON SHORT VIEWPORTS, and only on the reader. A tall screen has room for
   the full header and keeps it, so nothing changes for anyone who was not
   affected. The logo shrinks with the bar rather than being cropped by it —
   its own clamp is restated at the smaller scale.

   `max-height: 760px` is deliberately above the 625px case rather than tuned
   to it: the waste is proportional, so the correction should begin before the
   screen gets small enough for someone to complain.

   NOT APPLIED TO `.page-static-book`. About Us, Contact Us and the login
   screen share this header, and their layout was signed off as it stands. */
@media (max-height: 760px) {
  body.page-reader {
    --site-header-base: 92px;
    --logo-size: clamp(58px, 5vw, 74px);
  }
}

.reader-room {
  flex: 1;
  position: relative;
  padding: 10px 0 22px;
  background: linear-gradient(var(--wall), var(--wall-dark));
  min-height: calc(100vh - var(--site-header-h));
  overflow: hidden;
}



/* blurred hints of the library behind the book */
.reader-backdrop { position: absolute; inset: 0; pointer-events: none; }
.reader-backdrop img { position: absolute; filter: blur(4px) brightness(.96); opacity: .85; }

/* ============================ floating toolbar ============================ */

.reader-toolbar {
  position: relative; z-index: 30;
  display: flex; justify-content: center; align-items: center;
  gap: 12px;
  padding: 6px 12px 8px;
  flex-wrap: wrap;
}
.tool {
  width: 48px; height: 48px; border-radius: 50%;
  background: #fff; border: 1.5px solid var(--maroon-dark);
  color: var(--maroon-dark);
  display: grid; place-items: center; cursor: pointer;
  position: relative;
  box-shadow: 0 6px 16px rgba(60, 38, 18, .35);
  transition: transform .15s, background .15s, color .15s;
}
.tool svg { width: 22px; height: 22px; fill: currentColor; }
.tool svg text { fill: currentColor; font-family: var(--font-ui); }
.tool:hover { transform: translateY(-2px); }
.tool.active { background: var(--maroon); border-color: var(--maroon); color: #fff; }

.btn-return {
  background: #fff; color: var(--maroon-dark);
  border: 1.5px solid var(--maroon-dark);
  border-radius: 26px; padding: 12px 26px;
  font-family: var(--font-ui); font-size: 16px; font-weight: 500;
  text-decoration: none; position: relative;
  box-shadow: 0 6px 16px rgba(60, 38, 18, .35);
  transition: background .15s, color .15s, transform .15s;
  /* THE LABEL IS TWO WORDS AND MUST STAY ON ONE LINE.
     The mobile toolbar is `flex-wrap: nowrap` with `overflow-x: auto`, and a
     flex item's default `flex-shrink: 1` applies inside it. With six or more
     tools this button was compressed from 125px to 86px and "Return Book"
     broke over two lines — which grew the WHOLE STRIP from 52px to 72px,
     taking 20px away from the book on exactly the crowded books (Special
     Events) that could least afford it. Measured, both numbers.
     The tools already carry `flex: 0 0 auto`; this one was simply missed. */
  flex: 0 0 auto;
  white-space: nowrap;
}
.btn-return:hover { background: var(--maroon); color: #fff; transform: translateY(-2px); }

.tool::after {
  content: attr(data-tip);
  position: absolute; top: calc(100% + 9px); left: 50%;
  transform: translateX(-50%) translateY(-3px);
  background: var(--maroon-dark); color: #fff;
  font-size: 11.5px; font-weight: 500; font-family: var(--font-ui);
  white-space: nowrap; padding: 5px 10px; border-radius: 6px;
  opacity: 0; pointer-events: none; transition: opacity .16s, transform .16s;
  z-index: 40;
}
.tool:hover::after { opacity: 1; transform: translateX(-50%) translateY(0); }

/* slim reading-progress line */
.read-progress {
  position: relative; z-index: 20;
  width: 230px; height: 4px; margin: 2px auto 10px;
  background: rgba(255, 250, 238, .4);
  border-radius: 3px; overflow: hidden;
}
.read-progress .bar {
  display: block; height: 100%; width: var(--progress, 0%);
  background: linear-gradient(90deg, var(--gold), #b98d2e);
  transition: width .35s ease;
}

/* ============================ the book ============================ */

.book-stage {
  position: relative; z-index: 10;
  display: flex; justify-content: center;
  perspective: 3000px;
}
.flipbook {
  /* Live-site proportions: each page is portrait (~0.72 w/h), so the whole
     spread is ~1.42:1. Derive width from the available height so pages
     always keep book proportions, then clamp by viewport width. */
  /* Spread aspect widened from 1.42 to 1.60 so each page gains width without
     the book growing taller. Each page is still portrait (0.80 w/h). */
  width: min(97vw, 1500px, calc((100vh - var(--reader-chrome, 200px)) * 1.60));
  transform-style: preserve-3d;   /* pass stage perspective down to the cover/sheets */
}

.book-open {
  position: relative;
  width: 100%;
  aspect-ratio: 1.60 / 1;
  height: auto;
  /* ============ A FLOOR THAT CANNOT EXCEED THE ROOM ============
     REPORTED FROM A SMALLER LAPTOP: about 10% of the bottom of the book was
     cut off with no way to scroll to it, and zooming the browser to 80% made
     it fit. The login screen looked squashed at the same size. One cause, and
     it is this line.

     `min-height: 480px` was a plain floor against the book collapsing. The
     WIDTH formula above already shortens the book correctly as the viewport
     gets shorter — but once it asks for a height below 480, this floor
     overrode it, and the book grew taller than the space the formula had just
     measured. `.reader-room` is `overflow: hidden`, so the excess had nowhere
     to go and nothing to scroll.

     MEASURED at 1366 wide, sweeping the viewport height:

       vh 720   book 774.4 x 484.0   floor idle        fits
       vh 700   book 742.4 x 480.0   formula wanted 464   -35 (still inside)
       vh 665                                             FIRST CLIPPED PIXEL
       vh 640   book 646.4 x 480.0   formula wanted 404   +25 past the fold
       vh 625   book 622.4 x 480.0   formula wanted 389   +40  (~8% of the book)

     It also broke the book's SHAPE: at vh 640 the spread measured 1.35:1
     against its 1.60:1 aspect, because the floor was stretching the height
     while the width kept shrinking. On the login screen — which is the same
     shell with a shorter chrome allowance — that is exactly the "compressed"
     appearance: 1.35:1 at 1280x600 against the same 1.60 target.

     And it explains the 80% zoom. Zooming out multiplies the CSS viewport
     height by 1.25, so a 640px window becomes 800 CSS pixels, the formula asks
     for more than 480, the floor stops binding, and the book fits. The zoom
     was not making the book smaller — it was lifting the viewport back above
     the point where this line takes over.

     So the floor is capped by the same height the width formula uses. It still
     does its job on a tall viewport, where the two clamps never meet, and it
     can no longer demand more room than exists. Nothing changes for anyone
     whose viewport already satisfied the formula: at 1440x900 and 1920x1080
     the measurements are identical before and after. */
  min-height: min(480px, calc(100vh - var(--reader-chrome, 236px)));
  max-height: 980px;
  border-radius: 10px;
  /* The gap between the book's edge and the paper. The turning sheet has to sit
     in exactly this gap to line up with the page it represents, so it is named
     once here and read there rather than written out again. It was written out
     again — as a literal 16px in .turning-sheet — and the two fell out of step
     the moment mobile changed the padding: the sheet ended up 8px inside the
     page on every side. */
  --book-pad: 16px;
  padding: var(--book-pad);
  display: grid;
  grid-template-columns: 1fr 1fr;
  transform-style: preserve-3d;
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  will-change: transform;
}
/* frame shadow lives on its own layer so it can fade (cheap opacity)
   instead of popping or repainting during the opening transform */
/* THE ONE-PHYSICAL-BOOK RULE
   The board exists as two halves. The RIGHT half is the closed book's own
   body: it sits beneath the cover and is only ever *revealed* by the cover
   lifting — it never appears out of nowhere. The LEFT half does not exist
   until the cover physically lands on it; because the cover's back face is
   identical to it, the hand-off swap is invisible. All state changes happen
   underneath the opaque cover — nothing fades in front of the user. */
.book-board {
  position: absolute; top: 0; bottom: 0; z-index: 0;
  pointer-events: none;
}
.board-left {
  left: 0; right: 50%; border-radius: 10px 0 0 10px;
  background: linear-gradient(135deg, #8e4144, var(--maroon) 90%);
}
.board-right {
  left: 50%; right: 0; border-radius: 0 10px 10px 0;
  background: linear-gradient(135deg, var(--maroon) -15%, var(--maroon-dark) 115%);
}
.book-shadow {
  /* drop shadow only — the cover carries its own shadow until the book
     is fully open, so this appears just as the cover lands */
  position: absolute; inset: 0; border-radius: 10px; z-index: 0;
  pointer-events: none;
}

/* gold corner clips */
.corner-clip {
  position: absolute; width: 42px; height: 42px; z-index: 7;
  background: linear-gradient(135deg, #ffe27a, var(--gold) 70%, #a87f22);
  pointer-events: none;
}
.clip-tl { top: -3px; left: -3px;  clip-path: polygon(0 0, 100% 0, 0 100%); border-radius: 8px 0 0 0; }
.clip-tr { top: -3px; right: -3px; clip-path: polygon(0 0, 100% 0, 100% 100%); border-radius: 0 8px 0 0; }
.clip-bl { bottom: -3px; left: -3px;  clip-path: polygon(0 0, 0 100%, 100% 100%); border-radius: 0 0 0 8px; }
.clip-br { bottom: -3px; right: -3px; clip-path: polygon(100% 0, 100% 100%, 0 100%); border-radius: 0 0 8px 0; }

.book-spine-shadow {
  position: absolute; top: 10px; bottom: 10px; left: 50%; width: 96px;
  transform: translateX(-50%);
  /* Realistic gutter: pages curve INTO the binding, so the shading is a
     deep, asymmetric valley (dark right at the fold, easing outward) with a
     thin catch-light on the crease itself where the paper turns back up. */
  background:
    linear-gradient(90deg,
      rgba(60, 35, 15, 0)     0%,
      rgba(60, 35, 15, .045) 22%,
      rgba(60, 35, 15, .12)  38%,
      rgba(54, 30, 12, .26)  46.5%,
      rgba(44, 24, 9,  .34)  49.2%,
      rgba(255, 250, 238, .30) 50%,
      rgba(44, 24, 9,  .34)  50.8%,
      rgba(54, 30, 12, .26)  53.5%,
      rgba(60, 35, 15, .12)  62%,
      rgba(60, 35, 15, .045) 78%,
      rgba(60, 35, 15, 0)   100%);
  z-index: 6; pointer-events: none;
}
/* The stitched binding itself: a narrow band of thread shadow running the
   full height of the fold, which is what sells it as a bound volume. */
.book-spine-shadow::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%; width: 3px;
  transform: translateX(-50%);
  background:
    repeating-linear-gradient(180deg,
      rgba(40, 22, 8, .30) 0 7px,
      rgba(40, 22, 8, .10) 7px 15px);
  opacity: .55;
}
.reader-room.night .book-spine-shadow {
  background:
    linear-gradient(90deg,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,.10) 22%,
      rgba(0,0,0,.26) 38%,
      rgba(0,0,0,.46) 46.5%,
      rgba(0,0,0,.58) 49.2%,
      rgba(255,240,215,.13) 50%,
      rgba(0,0,0,.58) 50.8%,
      rgba(0,0,0,.46) 53.5%,
      rgba(0,0,0,.26) 62%,
      rgba(0,0,0,.10) 78%,
      rgba(0,0,0,0) 100%);
}

.page-stack { position: absolute; top: 16px; bottom: 16px; width: 9px; z-index: 1; transition: opacity .5s; }
.stack-left  { left: 8px;  background: repeating-linear-gradient(90deg, #efe6d2 0 2px, #cbbfa4 2px 3px); border-radius: 4px 0 0 4px; }
.stack-right { right: 8px; background: repeating-linear-gradient(90deg, #cbbfa4 0 1px, #efe6d2 1px 3px); border-radius: 0 4px 4px 0; }
.book-open.at-start .stack-left { opacity: 0 !important; }
.book-open.at-end   .stack-right { opacity: 0 !important; }

.page {
  position: relative; z-index: 3;
  display: flex; flex-direction: column;
  /* SIDE MARGIN IS PROPORTIONAL, NOT FIXED.
     48px was a constant against a page whose width scales with the viewport,
     so the margin drifted with screen size — around 15% a side on a typical
     desktop page and more as the book narrowed, which is what made the column
     feel compressed. 8% holds the same proportion at every size and gives the
     column ~84% of the page — a typeset measure rather than a narrow ribbon
     with wide empty gutters. (This was 10%; see .sheet-face and .page-scroll
     below, both of which must agree with it.)

     Vertical stays in px on purpose: percentage padding resolves against the
     containing block's WIDTH in CSS, so a percentage here would tie the top
     and bottom margins to the page's width, which is not what is wanted.

     .sheet-face below MUST carry the identical value — it is the face of the
     turning sheet, and if the two disagree the text shifts sideways mid-turn.
     The pagination measurer needs no change: syncMeasurer() reads the live
     .page-body box, so it follows this automatically. */
  padding: 24px 8% 16px;
  overflow: hidden;
}
.page-left  { border-radius: 6px 0 0 6px; }
.page-right { border-radius: 0 6px 6px 0; }

/* page theme */
.book-open, .turning-sheet {
  --page-bg: #f0e6d2; --page-ink: #34281c; --page-accent: var(--maroon);
}
.reader-room.night .book-open,
.reader-room.night .turning-sheet {
  --page-bg: #241f19; --page-ink: #e8ded0; --page-accent: #e0a4a6;
}

.reader-room.night .page, .reader-room.night .sheet-face { background: var(--page-bg); }

.page-head {
  text-align: center;
  font-family: var(--font-ui);
  padding: 0 44px 6px;
  flex: 0 0 auto;
  min-height: 32px;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  white-space: nowrap;
  overflow: hidden;
}
.page-body {
  flex: 1 1 auto; overflow: hidden; position: relative;
  font-size: var(--reader-font-size, 14.5px);
  line-height: var(--reader-line-height, 1.66);
  /* Crisper glyph rendering on the warm parchment background */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
.page-num {
  flex: 0 0 auto; text-align: center; color: #8d7c64; padding-top: 7px;
}
.reader-room.night .page-num { color: #9a8d7c; }

/* ---------- the hard cover (front art / inside back) ---------- */

.book-cover {
  position: absolute;
  top: 0; bottom: 0; left: 50%; right: 0;
  z-index: 16;
  transform-style: preserve-3d;
  transform-origin: left center;
  cursor: pointer;
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  will-change: transform;
}
/* SCOPED to .book-cover — this rule is the flipbook's closed-book cover
   face. As a bare `.cover-face` it also matched every shelf book's
   <img class="cover-face"> in the reader's blurred BACKDROP (this file
   loads only on the reader page, so the live shelf pages were never
   affected — which is exactly why the backdrop kept breaking while the
   real pages looked right). position:absolute pulled each backdrop cover
   out of flow: its .cover-shell collapsed to 0x0, every shelf row
   collapsed to its padding (boards bunching together), and the covers
   anchored to the collapsed shells and piled across the boards. Never
   add a bare .cover-face / .book / .cover-shell rule to this file. */
.book-cover .cover-face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  border-radius: 0 10px 10px 0;
  overflow: hidden;
}

.cover-back {
  transform: rotateY(180deg);
  border-radius: 10px 0 0 10px;
  /* board lip identical to .board-left */
  background: linear-gradient(135deg, #8e4144, var(--maroon) 90%);
  overflow: visible;   /* the gold corner clips protrude 3px, like the real ones */
}
/* gold corner clips, mirroring .clip-tl / .clip-bl of the open book */
.cover-back::before {
  content: ''; position: absolute; top: -3px; left: -3px;
  width: 42px; height: 42px;
  background: linear-gradient(135deg, #ffe27a, var(--gold) 70%, #a87f22);
  clip-path: polygon(0 0, 100% 0, 0 100%);
  border-radius: 8px 0 0 0;
  z-index: 3;
}
.cover-back .inside-cover-face::before {
  content: ''; position: absolute; bottom: -19px; left: -19px;
  width: 42px; height: 42px;
  background: linear-gradient(135deg, #ffe27a, var(--gold) 70%, #a87f22);
  clip-path: polygon(0 0, 0 100%, 100% 100%);
  border-radius: 0 0 0 8px;
  z-index: 3;
}
/* No paper-stack decal needed: the book always opens onto (and closes
   from) the first spread, where the real left stack is hidden as well. */
/* exact replica of the spine gradient's LEFT half, so the fold shading is
   identical before and after the cover hand-off */
.cover-back::after {
  content: '';
  position: absolute; top: 10px; bottom: 10px; right: 0; width: 48px;
  /* Exact LEFT half of the rebuilt .book-spine-shadow gradient, so the fold
     shading is identical before and after the cover hand-off. Percentages
     are doubled because this element is half the spine's width. */
  background: linear-gradient(90deg,
    rgba(60, 35, 15, 0)     0%,
    rgba(60, 35, 15, .045) 44%,
    rgba(60, 35, 15, .12)  76%,
    rgba(54, 30, 12, .26)  93%,
    rgba(44, 24, 9,  .34)  98.4%,
    rgba(255, 250, 238, .30) 100%);
  z-index: 4;
}
.cover-back .inside-cover-face { inset: 16px; border-radius: 6px 0 0 6px; }
.inside-cover-face, .inside-cover-page {
  /* the actual BACK of the front cover: the same maroon board material as
     the cover front (mirrored), plain like a real hardcover, shaded gently
     toward the fold. It must read as one continuous piece with the frame. */
  position: absolute; inset: 0;
  background:
    linear-gradient(90deg, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, .16)),
    radial-gradient(120% 100% at 30% 35%, rgba(255, 235, 200, .07), transparent 55%),
    linear-gradient(135deg, #8e4144, var(--maroon) 60%, var(--maroon-dark) 115%);
}

.cover-art { position: relative; width: 100%; height: 100%; overflow: hidden; }
.cover-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cover-art img.img-missing { display: none; }
.cover-art .cover-fallback-title { display: none; }
.cover-art img.img-missing + .cover-fallback-title {
  display: grid; place-items: center; position: absolute; inset: 0;
  font-family: var(--font-display); font-size: clamp(34px, 4.6vw, 52px); font-weight: 700;
  color: var(--gold); text-align: center; padding: 24px;
  background:
    radial-gradient(circle at 50% 24%, rgba(255, 226, 150, .3), transparent 45%),
    linear-gradient(150deg, #6c2f31, #4c1d1f);
}

/* ---------- CLOSED / OPENING / CLOSING states ----------
   One physical book. Group definitions:
   LEFT-half  = board-left, clip-tl, clip-bl, stack-left, page-left
   RIGHT-half = board-right, clip-tr, clip-br, stack-right, page-right
   All visibility swaps are INSTANT and always occur beneath the opaque
   cover, so the user only ever sees the cover transform. */

.page-left, .page-right { opacity: 1; }
.book-board, .corner-clip, .page-stack, .book-spine-shadow,
.page-left, .page-right { transition: none; }

/* CLOSED: only the cover exists */
.flipbook.closed .book-open { transform: translateX(-25%); }
.flipbook.closed .book-board,
.flipbook.closed .corner-clip,
.flipbook.closed .book-spine-shadow,
.flipbook.closed .page-stack,
.flipbook.closed .page-left,
.flipbook.closed .page-right { opacity: 0; }
.flipbook.closed .book-shadow { opacity: 0; transition: none; }
.flipbook.closed .book-cover {
  box-shadow: -16px 24px 50px rgba(40, 22, 8, .55);
  border-radius: 6px 12px 12px 6px;
  transition: transform .3s ease;
}
.flipbook.closed .book-cover:hover { transform: rotateY(-8deg); }

/* OPENING: the RIGHT half (the book's body) is under the cover at frame 0
   and gets revealed by the swing; the LEFT half stays nonexistent — the
   swinging cover's back face IS the left board until it lands. */
.flipbook.opening .book-open { transform: translateX(0); }
.flipbook.opening .book-cover {
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  transform: rotateY(-180deg);
}
.flipbook.opening .book-cover:hover { transform: rotateY(-180deg); }
.flipbook.opening .board-right,
.flipbook.opening .clip-tr,
.flipbook.opening .clip-br,
.flipbook.opening .stack-right,
.flipbook.opening .book-spine-shadow,
.flipbook.opening .page-right { opacity: 1; }
.flipbook.opening .book-shadow { opacity: 1; transition: opacity .3s ease .78s; }

/* CLOSING: the cover reappears at -180 exactly over the left half (its back
   face matches it — invisible), so the LEFT half is dismissed instantly
   beneath it; the RIGHT half stays and is progressively covered by the
   sweep, then dismissed beneath the landed cover. */
.flipbook.closing .book-open { transform: translateX(-25%); }
.flipbook.closing .book-cover {
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  transform: rotateY(0deg);
  box-shadow: -16px 24px 50px rgba(40, 22, 8, .55);
  border-radius: 6px 12px 12px 6px;
}
.flipbook.closing .board-left,
.flipbook.closing .clip-tl,
.flipbook.closing .clip-bl,
.flipbook.closing .stack-left,
.flipbook.closing .page-left { opacity: 0; }
.flipbook.closing .board-right,
.flipbook.closing .clip-tr,
.flipbook.closing .clip-br,
.flipbook.closing .stack-right,
.flipbook.closing .page-right { opacity: 1; }
.flipbook.closing .book-spine-shadow { opacity: 0; transition: opacity .3s ease .6s; }
.flipbook.closing .book-shadow { opacity: 0; transition: opacity .22s ease; }

.flipbook.dragging .board-right,
.flipbook.dragging .clip-tr,
.flipbook.dragging .clip-br,
.flipbook.dragging .stack-right,
.flipbook.dragging .book-spine-shadow,
.flipbook.dragging .page-right { opacity: 1; }

/* OPEN: the cover is retired; every element of the open book takes over in
   the same frame, entirely beneath where the cover just landed */
.flipbook.open .book-cover { display: none; }
.flipbook.open .book-board,
.flipbook.open .corner-clip,
.flipbook.open .book-spine-shadow,
.flipbook.open .page-stack,
.flipbook.open .page-left,
.flipbook.open .page-right { opacity: 1; }
.flipbook.open .book-shadow { opacity: 1; }

/* instant-position helper used when starting the closing animation */
.flipbook.cover-noanim .book-cover { transition: none !important; transform: rotateY(-180deg); }

/* while dragging the cover, transforms follow the pointer directly */
.flipbook.dragging .book-cover,
.flipbook.dragging .book-open { transition: none !important; }
.flipbook.dragging .corner-clip,
.flipbook.dragging .book-spine-shadow,
.flipbook.dragging .page-stack { opacity: 1; transition: opacity .2s; }
.flipbook.dragging .page-right { opacity: 1; }
.flipbook.dragging .book-shadow { opacity: 1; transition: opacity .3s; }
.flipbook.dragging .book-cover:hover { transform: none; }

/* special left/right pages */
.page.is-inside-cover { padding: 0; background: none; box-shadow: none; }
.page.is-inside-cover .page-head, .page.is-inside-cover .page-num { display: none; }
.page.is-title .page-head, .page.is-title .page-num { display: none; }

.title-page {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 24px; text-align: center; padding: 30px;
}
.title-page .tp-flourish { color: var(--page-accent); font-size: 30px; letter-spacing: 4px; }
.title-page h1 {
  margin: 0;
  font-family: var(--font-display); font-weight: 700;
  /* Reduced: proportional to the wider book rather than dominating the page. */
  font-size: clamp(30px, 3.6vw, 46px);
  color: var(--page-ink);
  letter-spacing: 1px;
}
.title-page .tp-rule {
  width: 140px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--page-accent), transparent);
}
.title-page .tp-imprint {
  font-family: var(--font-ui); font-size: 12.5px; letter-spacing: 2.5px;
  text-transform: uppercase; color: #8d7c64;
}
.reader-room.night .title-page .tp-imprint { color: #9a8d7c; }

/* ---------- content typography ---------- */

.page-body p { margin: 0 0 12px; }

.blk-book-summary .summary-label,
.blk-chapter-summary .summary-label { font-weight: 700; display: block; margin-bottom: 4px; }


.page-body::before {
  content: ''; position: absolute; inset: 0;
  background: url('../images/creation-watermark.svg') center/82% no-repeat;
  opacity: .1; pointer-events: none;
}
.page.is-inside-cover .page-body::before, .page.is-title .page-body::before { display: none; }
.reader-room.night .page-body::before { opacity: .06; filter: invert(.9); }

/* ---------- turning sheet ---------- */

.turning-sheet {
  position: absolute;
  top: var(--book-pad); bottom: var(--book-pad);
  left: 50%; width: calc(50% - var(--book-pad));
  z-index: 12;
  transform-style: preserve-3d;
  transform-origin: left center;
  pointer-events: none;
  will-change: transform;
}
.sheet-face {
  position: absolute; inset: 0; backface-visibility: hidden;
  display: flex; flex-direction: column;
  /* MUST match .page exactly — see the note there. Both resolve their
     percentage against a box one page wide (.page against its grid area,
     .sheet-face against .turning-sheet), so the two stay in step. */
  padding: 24px 8% 16px; overflow: hidden;
}
.sheet-front { border-radius: 0 6px 6px 0; }
.sheet-back  { transform: rotateY(180deg); border-radius: 6px 0 0 6px; }
.sheet-face.is-inside-cover { padding: 0; }
.sheet-face.is-inside-cover .page-head, .sheet-face.is-inside-cover .page-num,
.sheet-face.is-title .page-head, .sheet-face.is-title .page-num { display: none; }

@keyframes flipForward  { 0% { transform: rotateY(0); }      100% { transform: rotateY(-180deg); } }
@keyframes flipBackward { 0% { transform: rotateY(-180deg); } 100% { transform: rotateY(0); } }
.turning-sheet.drag { animation: none; }
/* .42s is the DEFAULT; flipbook.js overrides the duration inline per turn —
   .42s for one page, .23s while a burst of clicks is draining. The value here
   still matters: it is what applies before any script runs and if the inline
   style is ever cleared. The easing is unchanged, so the motion is identical,
   only quicker. */
.turning-sheet.turn-forward  { animation: flipForward  .42s cubic-bezier(.45, .06, .28, .99) forwards; }
.turning-sheet.turn-backward { animation: flipBackward .42s cubic-bezier(.45, .06, .28, .99) forwards; }

.book-open.turning .page-left::after,
.book-open.turning .page-right::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, rgba(40, 20, 8, .10), transparent 50%);
  animation: turnShadow .68s ease forwards;
}
@keyframes turnShadow { from { opacity: .85; } to { opacity: 0; } }
/* faint curvature shade on the moving sheet itself */
.sheet-front::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, rgba(50, 28, 10, .07), transparent 30%);
}
.sheet-back::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(-90deg, rgba(50, 28, 10, .07), transparent 30%);
}

/* ---------- bookmark ribbon ---------- */
.bookmark-ribbon {
  position: absolute; top: -6px; left: 64px; width: 32px; height: 80px; z-index: 14;
  background: linear-gradient(#ffd75e, #d8a625);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 82%, 0 100%);
  box-shadow: 3px 3px 8px rgba(0, 0, 0, .35);
  cursor: pointer;
}
.bookmark-ribbon::before {
  content: ''; position: absolute; top: 11px; left: 50%; transform: translateX(-50%);
  width: 10px; height: 10px; border-radius: 50%;
  background: #a87b12;
}

/* ---------- page arrows: top corners of the pages ---------- */
/* NAVIGATION FOLLOWS THE READING MODE, NOT THE DEVICE.
   TWO-PAGE MODE keeps the original arrows in the upper corners of the spread.
   That is every viewport where both pages are visible -- desktop, laptop,
   tablet landscape AND landscape phone, which is a two-page spread too. An
   earlier revision moved these outside the book on the strength of an overlap
   measurement; the measurement was against .page-body's BOX, not its text, and
   the original placement sits in the page's top margin beside the running head
   where no line of prose reaches. Restored verbatim.

   SINGLE-PAGE MODE puts them below the book instead -- see the portrait block
   further down. With one page there is no upper-outer corner that is not also
   the top of the only column of text. */
.page-arrow {
  position: absolute; top: 30px; z-index: 22;
  width: 40px; height: 40px; border-radius: 50%;
  background: transparent; border: 0;
  color: var(--page-ink); cursor: pointer; display: grid; place-items: center;
  transition: transform .15s, opacity .2s;
}
.reader-room.night .page-arrow { color: #e8ded0; }
.page-arrow svg { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; }
.arrow-prev { left: 30px; }
.arrow-next { right: 30px; }
.page-arrow:hover { transform: scale(1.12); }
/* ON TOUCH, THE CIRCLE GROWS 4px AND DOES NOT MOVE.
   40px is the mark this reader has always used and the spread reads correctly
   with it, but it is under the 44px touch floor on a tablet or a landscape
   phone -- both of which show a two-page spread. Widening to 44px while pulling
   the offset from 30px to 28px keeps the CENTRE at exactly 50px from the corner,
   so the arrow lands in the same place it always has and is simply 2px larger in
   every direction. Portrait phones override this again below, where a single
   page gets its own 48px control under the book. */
@media (max-width: 1024px), (pointer: coarse) {
  .page-arrow { width: 44px; height: 44px; top: 28px; }
  .arrow-prev { left: 28px; }
  .arrow-next { right: 28px; }
}
.page-arrow[disabled] { opacity: .25; cursor: default; transform: none; }

/* ---------- in-book page overlays (cross references, search, chapters) ----------
   Rendered INSIDE a page so the feature feels part of the book. The host
   page gets .has-overlay, lifting it above the corner page-turn arrows —
   which removes the close-button / arrow conflict entirely (the arrow is
   covered while an overlay is open, and page turns close overlays anyway). */
.page.has-overlay { z-index: 26; }
.page-overlay {
  position: absolute; inset: 0; z-index: 24;
  background: var(--page-bg); color: var(--page-ink);
  padding: 30px 40px; overflow: auto;
}
.page-overlay h4 {
  margin: 2px 0 4px; text-align: center; font-size: 24px; font-family: var(--font-display);
}
.overlay-close {
  position: absolute; top: 14px; right: 16px;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1.5px solid var(--page-accent); background: transparent;
  color: var(--page-accent); cursor: pointer;
  display: grid; place-items: center; font-size: 15px;
  transition: background .15s, color .15s;
}
.overlay-close:hover { background: var(--page-accent); color: var(--page-bg); }
.overlay-rule {
  width: 120px; height: 2px; margin: 8px auto 18px;
  background: linear-gradient(90deg, transparent, var(--page-accent), transparent);
}
.ov-go {
  margin-top: 6px; border: 0; cursor: pointer;
  background: var(--page-accent); color: var(--page-bg);
  font-family: var(--font-ui); font-size: 15px; font-weight: 500;
  border-radius: 20px; padding: 11px 38px;
  transition: filter .15s;
}
.ov-go:hover { filter: brightness(1.12); }
.ov-hint { font-size: 12.5px; color: #8d7c64; text-align: center; }
.reader-room.night .ov-hint { color: #9a8d7c; }

/* in-book chapter list */
.ov-chapters { list-style: none; margin: 0; padding: 4px 0 0; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.ov-chapters button {
  border: 1.5px solid var(--page-accent); background: transparent; color: var(--page-accent);
  border-radius: 9px; padding: 9px 16px; cursor: pointer;
  font-family: var(--font-ui); font-size: 14px; font-weight: 500;
  transition: background .15s, color .15s;
}
.ov-chapters button:hover { background: var(--page-accent); color: var(--page-bg); }
.ov-empty { font-size: 13.5px; color: #8d7c64; text-align: center; }

/* ============================ measurement ============================ */
.page-measurer {
  position: absolute; visibility: hidden; pointer-events: none;
  left: -9999px; top: 0;
}
.page-measurer .page-body {
  /* MUST match the real page's overflow mode: with `hidden`, the last
     child's bottom margin is contained and counted by scrollHeight, just
     like on the visible pages. `visible` here caused pages to be packed
     3-12px too full (collapsed margins escaped the measurement). */
  overflow: hidden;
}

/* =====================================================================
   SINGLE-PAGE MODE IS IMMERSIVE: THE ROOM IS THE VIEWPORT, FULL STOP.
   The page used to scroll on every platform — by arithmetic, since
   body{min-height:100vh} plus a sticky header plus a footer exceeds the
   viewport (measured: 159px on a phone, footer-height exactly). iOS made it
   obvious by adding the URL-bar delta on top; Android merely hid it better.
   Sometimes-blocked / sometimes-not on Android was overscroll chaining.

   Here the room is pinned to the STABLE small viewport (svh; the vh line is
   the fallback for engines without it) and the viewport lock lives with the
   existing body.book-open-mode rule in main.css, extended to the root there. overscroll-behavior kills rubber-band chaining. No touch
   handlers, no position:fixed body, no browser sniffing — and none of it
   applies to desktop or tablet spreads, which keep their document flow and
   their reachable footer exactly as they are. */
@media (max-width: 860px), (max-height: 520px){
  .page-reader .reader-room {
    height: calc(100vh - var(--site-header-h));
    height: calc(100svh - var(--site-header-h));
    min-height: 0;
    overscroll-behavior: none;
  }
  /* The document itself is exactly the viewport, so there is NOTHING to
     scroll — belt to the root lock's braces. Measured before this line:
     html and body both computed overflow:hidden, and a wheel over a landscape
     phone still moved document.scrollingElement 104px (the footer's worth of
     overflow). Whatever the engine's reasoning, a page with zero overflow has
     zero scroll in every engine. The footer below the room becomes
     unreachable while the reader is open — which is exactly what the approved
     desktop lock already does, so this is consistency, not a new decision. */
  body.page-reader {
    /* THE ROOT CAUSE OF THE CLIPPED BOTTOM, AND THE WHOLE OF THE FIX.
       main.css gives every body `min-height: 100vh`. `min-height` BEATS
       `height`, so the pin below never actually applied: the used height was
       max(100svh, 100vh) = 100vh — the LARGE viewport, the one that assumes
       the browser's chrome has retracted. It never retracts here, because the
       reader locks scrolling.

       So the body was as tall as the screen WITHOUT the browser's bars, while
       the reader could only see the part with them. Everything measured off
       that: the room, the stage, `cqh`, the book. The overflow landed exactly
       where the reader looks last — the bottom edge, the lower gold corners,
       the page number and both arrows.

       It is proportional to how much chrome a browser shows, which is why
       Chrome-iOS (top bar AND bottom bar) was worst, Safari (bottom bar only)
       was borderline, and Android sat between them. Measured by simulating the
       chrome as a min-height excess: 70px below the fold for Chrome-iOS, 16px
       at 60px of chrome, 8px on Android — all to 0px with this one line.

       It survived every previous check because headless Chromium HAS no
       browser chrome: there 100vh == 100svh and the min-height is a no-op.
       Nothing measured in this sandbox could ever have shown it. */
    min-height: 0;
    height: 100vh;
    height: 100svh;
  }

  /* =====================================================================
     THE FOOTER LEAVES THE READER'S BUDGET — SINGLE-PAGE MODE ONLY.

     It was never reachable here. `body.book-open-mode { overflow: hidden }`
     locks the page, so while a book is open the footer cannot be scrolled to
     on ANY platform — desktop included. It was, however, still consuming
     layout: body is a flex column, so the footer's height came straight off
     .reader-room's flex share. Measured: 159px of a 568px viewport — 28% —
     spent on links nobody can reach while reading.

     That is what pushed the book past the bottom of its room and under the
     footer band: the room shrank by the footer's height while the book,
     sized from a viewport formula, did not.

     Declarative on purpose. `book-open-mode` already means "a book is open" —
     reader.php sets it for the reader page and shelf.js adds and removes it
     around the shelf open/close animation — so the footer returns by itself
     the moment the class goes, with no JS, no saved state and nothing to
     restore. Scoped to single-page mode, so desktop and landscape-tablet
     spreads keep their document flow and their footer exactly as approved. */
  body.book-open-mode > footer.site-footer { display: none; }
}

/* ---------- single-page cover behaviour and turn compositing ---------- */
@media (max-width: 860px), (max-height: 520px){
  /* THE BACK OF THE FRONT COVER IS NEVER PRESENTED ON A PHONE.
     A spread swings the board open and its inside faces the reader beside
     page 1 — that is what the inside face exists for. One page at a time has
     no "beside": the maroon inside filled the whole frame mid-rotation and
     read as a wrong page. The rotation and its timing are untouched (reader.js
     waits on this transform's transitionend); only the inside face is blank,
     so past 90deg the cover simply vanishes and the first page is already
     there. */
  .cover-back { opacity: 0; }
  .book-cover .cover-back * { visibility: hidden; }

  /* ============ AND SO THE LEFT BOARD HAS NOTHING HIDING IT ============
     THE MAROON HALF THAT DISAPPEARS DURING THE OPENING TURN.

     The one-physical-book rule keeps .board-left at opacity 0 through
     `.flipbook.opening` on purpose: on a spread, the swinging cover's BACK
     FACE is the left board, so revealing the real one underneath would be
     drawing the same maroon twice and the hand-off at 180deg is what makes
     the swap invisible.

     That reasoning depends entirely on the cover back being OPAQUE. Three
     lines above, single-page mode makes it transparent — for its own good
     reason, that a full-frame maroon inside face reads as a wrong page on a
     phone. Past 90deg there is then nothing over the left half, and the left
     half is still hidden, so the reader sees the room behind the book through
     it: the reported "half the maroon background missing", visible only while
     the transition runs and correct again once it ends.

     The left half is therefore revealed here on exactly the same states the
     right half already is. Nothing is being drawn twice, because on a phone
     there is no cover back to draw it. The desktop hand-off is untouched —
     this rule cannot apply there.

     `closing` is deliberately NOT included: the existing closing rule hides
     the left half beneath the returning cover, and on a phone the cover front
     IS opaque, so that sequence still works as written. */
  .flipbook.opening .board-left,
  .flipbook.opening .clip-tl,
  .flipbook.opening .clip-bl,
  .flipbook.opening .stack-left { opacity: 1; }

  /* Closing to the back: the instant-place frame must never show the FRONT
     board. It is a different cover on the other side of the book. */
  .flipbook.backcover-noanim .book-cover { display: none; }

  /* A fold that never passes 90deg has no visible reverse. Removing the back
     face removes backface-visibility from Safari's compositing entirely — one
     fewer 3D layer to promote on the first animation frame, which is where the
     background flash lives. */
  .turning-sheet .sheet-back { display: none; }
}
/* Promoted BEFORE the first turn, not on it: Safari creates the layer at
   animation start and paints it a frame late — the background shows through
   for exactly that frame. Scoped to the sheet; two small layers, held only
   while the reader is open. */
.turning-sheet { will-change: transform; }
.book-cover { will-change: transform; }

/* ============================ mobile ============================ */

/* Same condition as --reader-single-page above, because this IS that mode's
   layout. The two are one rule, written twice only because CSS cannot key a
   media query off a custom property; check.sh gate 33 asserts they match. */
@media (max-width: 860px), (max-height: 520px){
  /* THE HEIGHT TERM USED TO WIN OUTRIGHT, AND THAT IS THE BUG.
     `min(94vw, (100vh - 225px) * 0.64)` keeps the book short enough to fit
     vertically -- but on a SHORT screen it does that by making it absurdly
     narrow. Measured: 320x568 gave a 219px book and a 15-character line;
     844x390 landscape gave 105px, a 44px page, unusable.

     max() puts a floor under it: the book may become taller than the viewport
     and scroll, which is normal reading, but it may never become too narrow to
     read, which is not. 300px is the width at which a page still holds ~30
     characters per line at the reader's own font size. */
  /* A FIXED GUTTER, NOT A PERCENTAGE.
     97vw leaves 5px each side at 320px and 12px at 430px -- the narrower the
     phone, the tighter it looked, which is backwards. A calc() gutter gives
     every phone the same 17px margin, so the book sits ON the desk rather than
     against the edges of it. The readable floor drops to 286px to pay for it,
     which still measures ~33 characters per line at 320px. */
  .flipbook {
    /* Stated twice: the svh line wins where the unit exists, the vh line is
       the fallback. THE BUG THE svh EXISTS FOR: on the same iPhone, Safari
       resolves 100vh as the LARGE viewport and Chrome as a smaller one, and
       when this height term is the binding side of the min() the two browsers
       produce two different books from identical CSS. svh is the stable small
       viewport — one number per device, every browser. On Android and desktop
       svh equals vh, so nothing moves there.

       BOTH LINES ARE NOW ONLY A FALLBACK. They are kept for engines without
       container queries; where `cqh` is supported the block below overrides
       them entirely. See "THE BOOK IS MEASURED, NOT GUESSED" below for why. */
    width: min(calc(100vw - 30px),
               max(290px, calc((100vh - var(--reader-chrome-mobile, 225px)) * 0.64)));
    width: min(calc(100vw - 30px),
               max(290px, calc((100svh - var(--reader-chrome-mobile, 225px)) * 0.64)));
  }

  /* =====================================================================
     THE BOOK IS MEASURED, NOT GUESSED.

     The formula above is open-loop: it takes the viewport and subtracts a
     hand-maintained constant for the chrome (--reader-chrome-mobile, which is
     150px + the header). The room the book has to fit inside is NOT computed
     that way — body is a flex column and .reader-room is `flex: 1`, so its
     flex-basis of 0 discards the height declared on it and the room simply
     receives whatever is left after the header, the footer and any other
     sibling. Two different numbers for the same quantity, and the constant
     never included the footer (159px), the room's padding (32px), the toolbar
     (56px) or the progress bar (4px) — 251px of real chrome against 218px
     allowed for.

     So the book rendered LARGER than the room that holds it, and
     `.reader-room { overflow: hidden }` amputated the difference: the bottom
     gold corners, the page number and BOTH navigation arrows.

     This is not a WebKit defect. Measured in plain Chromium before this fix,
     the book was clipped at 7 of 11 supported viewports — 133px at 320x568,
     198px at 667x375, 199px at 844x390, 179px at 932x430, 28px at 390x844.
     Safari merely showed it worst, because its visible viewport is the
     shortest of the three browsers, which makes the same mismatch biggest.

     The fix closes the loop. The stage declares itself a size container and
     the book is sized from `cqh`/`cqw` — the stage's OWN measured box — so
     the quantity is read from the layout engine instead of being restated as
     a constant. There is nothing left to keep in step, and every engine
     computes it from the same box.

     `flex: 1 1 0` on the stage is required, not stylistic: with an `auto`
     basis a size container's query height stays indefinite and every `cqh`
     resolves to ZERO. That trap is recorded in A5 of the handover; this is
     the same one.

     Scoped to single-page mode. Desktop and landscape-tablet spreads keep the
     viewport formula and the approved geometry, untouched and proved so. */
  .page-reader .reader-room { display: flex; flex-direction: column; }
  .page-reader .reader-toolbar,
  .page-reader .read-progress { flex: 0 0 auto; }
  .page-reader .book-stage {
    flex: 1 1 0;
    min-height: 0;
    align-items: center;
    /* CLEARANCE FOR THE CORNER OVERHANG — and nothing more.
       `cqh` resolves against the stage's CONTENT box, so this padding both
       takes itself out of the height the book is sized from and returns itself
       as visible clearance.

       8px, sized to one real fact: .corner-clip sits 3px proud of .book-open at
       the top AND the bottom, so the gold corners extend 3px past any
       measurement taken of .flipbook. 8px covers that with margin to spare.

       It was briefly 12px while the bottom of the book was still being lost.
       That was compensation for the min-height defect above, not a requirement
       of the layout, and it came back down once the actual cause was found.

       Anything checking fit must measure the union of the book and its
       FURNITURE (.corner-clip, .book-board, .page, .page-arrow) — not
       .flipbook's rect, and not every descendant either: page CONTENT that
       overflows its own page box is clipped by the page and is a pagination
       matter, not a fit one. */
    padding-block: 8px;
  }

  /* SPACE COMES BACK FROM THE CHROME, NOT FROM THE BOOK.
     Trimmed: room padding 32px -> 12px, progress margin 10px -> 4px, toolbar
     padding 13px -> 6px. 33px returned to the book.

     THE TOOL BUTTONS ARE DELIBERATELY NOT SHRUNK. 44px is the touch floor this
     project audits against, and the toolbar felt cramped because "Return Book"
     was wrapping and adding a second line — a bug worth 20px — not because the
     buttons were too big. Fixing the wrap returns more space than shrinking
     every button to 38px would, and costs no accessibility. */
  .page-reader .reader-room { padding: 6px 0 6px; }
  .page-reader .read-progress { margin-bottom: 4px; }
  .page-reader .reader-toolbar { padding-top: 3px; padding-bottom: 3px; }

  /* =====================================================================
     3D PAINT ORDER, STATED EXPLICITLY.

     Inside a `transform-style: preserve-3d` context — which .book-open is —
     `z-index` is NOT the sort key. Children are ordered by their position in
     3D space, and every one of these sits at z = 0, so their order is
     genuinely ambiguous and engines are free to resolve it differently. Blink
     falls back to paint order; WebKit sorts strictly by depth.

     That ambiguity is what let the maroon boards paint OVER the paper while
     the turning sheet's layer was being retired. The primary fix is in
     flipbook.js (_fill no longer repaints a page that has not changed); this
     removes the ambiguity that made the repaint visible, so the boards cannot
     come forward even if a face is momentarily unrasterised.

     1px against a 3000px perspective scales the boards by 0.03% — 0.12px on a
     372px book. Nothing moves that a reader could see. */
  .page-reader .book-board { transform: translateZ(-1px); }
  .page-reader .page-left,
  .page-reader .page-right { transform: translateZ(0); }

  /* THE PAGE SHAPE IS DECLARED ONCE.
     Three blocks give the book three different shapes — 0.64 portrait, 0.74
     on short portrait screens, 1.00 on a landscape phone. The width formula
     has to use the SAME number as `aspect-ratio` or the book is sized for a
     shape it does not have. Naming it once and reading it in both places is
     the same discipline --reader-single-page applies to the reading mode, and
     it exists for the same reason: two copies of one number drift.

     ON :root, NOT ON .page-reader. This variable is READ by unscoped
     `.book-open` rules in this file and by the standard book box in mw.css,
     both of which also match STATIC book pages (Login, Register, Profile,
     About, Contact, the create forms, the guest books) — and those pages never
     carry .page-reader. Declared on .page-reader the variable was therefore
     undefined exactly where it was being read: `var(--book-ratio)` became
     invalid at computed-value time, `aspect-ratio` fell back to `auto`, and the
     same rules had already removed the min/max-height floors. Measured at
     932x430: every static book collapsed to 334x122 (ratio 2.74) while Plans,
     which borrows .page-reader, rendered 204x204.

     Moving it to :root changes NOTHING for the reader — .page-reader inherits
     from :root and nothing else declares this property, so every reader element
     computes the identical value. It simply makes the number reachable by the
     rules that were always meant to read it. */
  :root { --book-ratio: 0.64; }

  /* =====================================================================
     ONE STEP DOWN INSIDE THE PAGES.

     Body 14.5px -> 13.4px and the leading with it, headings to match. These
     are the two custom properties the reader already publishes and the
     pagination already measures against — `paginationSignature()` treats type
     size and leading as inputs, so changing them repaginates correctly and by
     design, with no new machinery. Nothing about the engine, the turn or the
     geometry moves.

     Roughly one line more per page and a calmer column. Held above 13px so it
     stays comfortably readable; this is a step, not a squeeze. */
  /* ON :root, NOT ON body — reader.js resolves these from
     getComputedStyle(document.documentElement), which is also where the
     landscape block declares them. Declared on body they compute correctly and
     are never read. */
  :root {
    --reader-font-base: 13.4px;
    --reader-line-base: 1.6;
  }
  .page-reader .page-head { font-size: 13px; }
  .page-reader .page-body h1 { font-size: 1.42em; }
  .page-reader .page-body h2 { font-size: 1.22em; }
  .page-reader .page-body h3 { font-size: 1.08em; }

  @supports (height: 1cqh) {
    .page-reader .book-stage { container-type: size; }
    .page-reader .book-stage .flipbook {
      /* the smaller of what the stage's width allows and what its height
         allows — so the book fits in both directions, always. */
      width: min(calc(100cqw - 30px), calc(100cqh * var(--book-ratio)));
    }
    /* THE FLOORS COME OFF WITH THE GUESSWORK.
       `min-height: 430px` existed to stop the open-loop formula collapsing the
       book on a short screen. It also guaranteed overflow on any viewport
       shorter than about 520px, which is every landscape phone. With the size
       taken from the stage the book can no longer collapse, so the floor has
       nothing left to protect and is removed rather than left to fight the
       measurement. */
    .page-reader .book-stage .book-open { min-height: 0; max-height: none; }
  }
  .book-open {
    grid-template-columns: 1fr;
    aspect-ratio: var(--book-ratio) / 1;
    height: auto;
    min-height: 430px;
    max-height: 780px;
  }
  .page-left { display: none; }
  .page-right { border-radius: 6px; box-shadow: none; }
  .book-spine-shadow, .stack-left { display: none; }

  /* closed front cover fills the frame on mobile. The BACK cover needs the
     same release, but its base rules are declared far below this block, so a
     matching override here silently loses on source order — it is stated at
     the end of the back-cover section instead, where it can win. */
  .book-cover { left: 0; }
  .book-cover .cover-face, .cover-back { border-radius: 8px; }
  .flipbook.closed .book-open { transform: none; }

  /* THE SLIDE BELONGS TO SINGLE-PAGE READING ONLY.
     With one page there is no facing page to fold against, so a lateral slide
     is the honest gesture and this is how the reader has always behaved on a
     phone. A landscape phone now shows a two-page SPREAD, and a spread that
     slides reads as a card being dealt rather than a page being turned -- the
     regression reported after Stage 1. Scoping to portrait puts the real flip
     back on every two-page layout, which is now the rule everywhere: the
     animation follows the reading mode, exactly as the navigation does. */
  /* THE SHEET IS THE PAGE, SO IT HAS TO BE THE SAME BOX.
     `left: 16px; width: calc(100% - 32px)` was the desktop inset copied across.
     Once mobile dropped the padding to 8px the sheet sat 8px inside the page on
     every side — measured 328x531 against a 344x547 page at 390px, and 262x262
     against 274x274 on a landscape phone. A turning page 16px smaller than the
     page it leaves behind reads as a card lifting off, not paper moving.

     Deriving both edges from --book-pad makes the sheet exactly the grid cell
     the page occupies, at any padding, with no second copy of the number.

     The hinge moves to the binding. `transform-origin: center` spun the sheet
     about its own midline, which is why a flip looked wrong here and a slide
     was chosen instead; on a single page the binding is the page's left edge,
     which is what `left center` now names. The slide is a pure translateX and
     ignores transform-origin, so this changes the geometry and not the current
     animation — verified frame by frame before and after. */
  .turning-sheet {
    left: var(--book-pad); right: var(--book-pad); width: auto;
    transform-origin: left center;
  }
  /* The page is rounded on all four corners in this mode, so the sheet is too.
     The desktop radii are for a bound edge that does not exist here. */
  .sheet-front, .sheet-back { border-radius: 6px; }

  /* THE TURN ENDS AT THE BINDING, BECAUSE THAT IS WHERE IT LEAVES THE SCREEN.
     The shared flipForward runs a full 180deg, which is right for a spread: the
     leaf lands on the facing page and every degree of it is visible. Here there
     is no facing page. Measured at 390x844: the leaf is fully visible to ~90deg,
     then swings out to relLeft -340px, entirely off-screen, for the rest of the
     turn. With the easing in use that put roughly 55% of the duration outside
     the viewport -- the page whipped away in about 225ms of a 500ms turn and the
     remaining 275ms showed a settled page with a shadow fading off it. Fast,
     then stalled.

     Stopping at the binding spends the whole duration on the part anyone can
     see, so the same 500ms now reads as one deliberate motion. Nothing is lost:
     at 90deg the leaf is edge-on and its projected width is zero, so it does not
     vanish, it finishes turning. The half that was cut was never on screen. */
  /* NAVIGATION BELONGS TO THE BOOK.
     A separate strip below the book cost 74px of a phone's height and read as
     a control bar bolted underneath rather than part of the object. These are
     the desktop arrows — same transparent circle, same ink colour, same 26px
     glyph — moved from the top corners to the bottom ones, because on a single
     page the top corners are where the page's title sits.

     Offsets are measured from --book-pad so the arrow keeps the same inset
     from the page edge at any padding. The box is 48px for the touch target
     while the glyph stays 26px, so the control is comfortable to hit without
     becoming a button sitting on the paper.

     Dropped to sit level with the page number rather than 14px above it: at
     +14px the glyphs rode close enough to the last line of text and to any
     image at the foot of the page to compete with them. At +2px the arrow's
     centre lands within 3px of the page number's, so the three read as one
     line of furniture along the bottom margin instead of two. */
  .page-arrow {
    top: auto; bottom: calc(var(--book-pad) + 2px);
    width: 48px; height: 48px;
  }
  .arrow-prev { left: calc(var(--book-pad) + 14px); right: auto; margin-left: 0; }
  .arrow-next { right: calc(var(--book-pad) + 14px); left: auto; margin-right: 0; }
  .page-arrow:hover { transform: none; }
  .page-arrow:active { transform: scale(.94); }

  /* The arrows stand in the page's bottom margin beside the page number, so
     that margin has to be deep enough that no line of text can reach them.
     Reserved on .page-body rather than .page, which keeps the page number at
     the foot of the page where it belongs instead of floating above a band of
     empty paper. */
  .page-body { padding-bottom: 34px; }

  @keyframes foldForward  { 0% { transform: rotateY(0); }       100% { transform: rotateY(-90deg); } }
  @keyframes foldBackward { 0% { transform: rotateY(-90deg); }  100% { transform: rotateY(0); } }
  .turning-sheet.turn-forward  { animation-name: foldForward; }
  .turning-sheet.turn-backward { animation-name: foldBackward; }

  .page { padding: clamp(12px, 3vw, 18px) clamp(13px, 5vw, 22px) clamp(10px, 2.5vw, 14px); }
  .sheet-face { padding: clamp(12px, 3vw, 18px) clamp(13px, 5vw, 22px) clamp(10px, 2.5vw, 14px); }
  .book-open { --book-pad: clamp(8px, 2vw, 16px); }
  .bookmark-ribbon { left: 26px; }
  .page-head { font-size: 19px; }
  /* Under the book, not over it. There is no room beside the book at 94vw, so
     the desktop treatment cannot apply; a centred pair below the page is also
     the easiest thing to reach with one thumb, which is what the brief asks
     for. Bottom-anchored inside .book-open's own padding band so it travels
     with the book instead of floating over whatever is behind it. */
  /* The progress indicator is NOT touched here. It belongs above the book,
     under the toolbar, exactly where it has always been -- it reads as part of
     the reader's header furniture, and moving it below turned it into a second
     thing competing with the navigation for the same strip of screen. */
  .btn-return { padding: 10px 18px; font-size: 14.5px; }

  /* THE TOOLBAR MUST NOT COST MORE SCREEN THAN THE BOOK.
     `flex-wrap: wrap` with 48px buttons and 12px gaps needs ~360px per row.
     Measured on a 320x568 phone: eleven tools wrapped onto THREE rows and
     consumed 63% of the viewport, so the book began below the fold and the
     reader had to scroll past a wall of controls to reach the first line. The
     controls were never over the text, so the audit's obstruction check passed
     -- it took a screenshot to see it.

     One row that scrolls sideways, instead of three that push the book away.
     Buttons stay at 44px (the touch floor), and -webkit-overflow-scrolling
     keeps the swipe native on iOS.

     `safe center`, NOT `flex-start` and NOT plain `center`. flex-start applied
     to every viewport under 860px, including the many where the strip never
     overflows at all: measured 0px of left gap against 551px of right gap on a
     portrait tablet, with the tools sitting 275px left of centre — the
     off-centre toolbar reported from a real device. Plain `center` would fix
     that and break the overflow case instead, because centred flex content that
     is wider than its box overflows in BOTH directions and the first button
     becomes unreachable behind the scroll origin. `safe` is exactly this
     distinction: centre while it fits, fall back to start when it does not. */
  .reader-toolbar {
    flex-wrap: nowrap;
    justify-content: safe center;
    gap: 9px;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 6px 12px 7px;
  }
  .reader-toolbar::-webkit-scrollbar { display: none; }
  .tool { width: 44px; height: 44px; flex: 0 0 auto; box-shadow: 0 3px 9px rgba(60,38,18,.28); }

  /* =====================================================================
     RETURN BOOK IS PINNED; THE TOOLS SCROLL UNDER IT.

     A module with many actions cannot fit on one line at any honest size.
     Six 44px tools plus gaps is already 309px, and Return Book is another
     125px — 434px on a 390px phone. Something has to give, and the previous
     answers both gave the wrong thing: wrapping cost a second row of height,
     and letting the strip scroll pushed the ONE control everybody needs off
     the right-hand edge.

     Making it `position: sticky` inside the strip's own scroll container
     resolves it without shrinking anything: the tools scroll horizontally
     while Return Book stays parked against the right edge, always visible and
     always on one line. Every touch target stays at 44px — nothing is reduced
     to buy space.

     The opaque background and the fade to its left are what make it readable
     as a pinned control rather than an overlap: tools pass underneath and are
     visibly cut off, not muddled. */
  .btn-return {
    position: sticky;
    right: 0;
    z-index: 2;
    flex: 0 0 auto;
    margin-left: 4px;
  }
  /* NO PAINTED FADE. An earlier version put a gradient to the left of this
     button to suggest the tools scrolling beneath it. It could not know
     whether there was anything to scroll, so on a toolbar with three tools —
     where the strip does not overflow at all — it rendered as a stray shadow
     against the cream, and read as a bug rather than an affordance.

     CSS cannot ask "is this container overflowing", and inventing a JS
     observer to answer it would be a lot of machinery for a hint. The button
     is already opaque with its own shadow, so a tool passing under it is
     plainly occluded; that IS the affordance, and it appears only when there
     is something to occlude. Nothing is drawn when nothing is scrolling. */
  .tool svg { width: 20px; height: 20px; }
  /* The hover tooltip is a mouse affordance; on a touch strip it only ever
     appears mid-scroll and clips against the strip's own overflow. */
  .tool::after { display: none; }
}


/* ---------- short portrait phones: let the nav strip fit ----------
   Found on an iPhone SE profile, not in the viewport matrix: at 320x568 the
   0.64 portrait page is 453px tall, and toolbar + book + navigation came to
   602px. The arrows landed 34px below the fold -- present, correct, and
   unreachable without scrolling away from the text.

   Widening the page's own aspect a little (0.64 -> 0.74) shortens it by ~60px
   and brings the whole reading unit back above the fold. The page is still
   clearly portrait, and reader.js simply paginates into slightly more pages,
   which is the behaviour it is built around. Nothing is taken from the reading
   width, which is what the small-phone work just bought. */
/* THE BOOK'S SHAPE IS A PROPERTY OF THE DEVICE, NOT OF THE BROWSER'S CHROME.
   This block used to read `(max-height: 700px)`. That is a measurement of how
   much room the BROWSER left, not of the phone — and the two disagree on the
   same handset: an iPhone 16 Pro gives Safari ~743px and Chrome ~694px, so
   Safari drew a 0.64 page and Chrome a squatter 0.74 one. Same device, same
   CSS, two different books, which is exactly the "smaller box rather than a
   full-sized book" reported for Chrome on iPhone.

   Keyed on width it cannot happen: a phone is the same width in every browser.
   360px keeps the narrow handsets (320px class) on the squatter page they were
   approved with, and puts every 390px-and-wider phone on 0.64 in ALL browsers.
   Approved geometry at 320x568, 390x844 and 430x932 is unchanged by this —
   verified. */
@media (max-width: 360px) and (orientation: portrait) {
  /* Shape only. The variable is what the width formula reads too, so the
     book is sized for the shape it actually has.
     On :root for the reason given at the 0.64 declaration above. */
  :root { --book-ratio: 0.74; }
  .book-open { aspect-ratio: var(--book-ratio) / 1; min-height: 0; }
}

/* ---------- landscape phone: short and wide ----------
   A phone on its side is 844x390. The mobile block above assumes a PORTRAIT
   single page (aspect 0.64), which on a 390px-tall viewport can only be
   satisfied by making the book narrow -- the measured result was a 44px page
   before the width floor, and a 268px page with it, still the narrowest reading
   surface on any supported device while 844px of width sat unused.

   The fix is to stop demanding a portrait page when the viewport is landscape.
   Height is taken from the viewport and the aspect ratio is released, so the
   page becomes short and wide: more pages, each comfortably readable. That is
   a shape change, not an architecture change -- reader.js paginates against
   whatever the live element measures, so a shorter page simply yields more of
   them, which is the behaviour READER_ARCHITECTURE.md §3 already relies on.

   Keyed on HEIGHT and orientation, not on a new width breakpoint, so it does
   not interact with the width scale. */
@media (max-height: 520px) and (orientation: landscape) {
  /* A PHONE ON ITS SIDE IS STILL A PHONE.
     This block used to restore a two-page spread here. flipbook.js never
     agreed: it filled only the right-hand page below 860px, so the measured
     result on a dynamic book at 844x390 was a visible 255px left page holding
     ZERO characters beside the text. The spread was a layout with no engine
     behind it.

     Single page instead, sized from height rather than width. A square page is
     what makes this work: at 844x390 it measures 274px against the spread's
     255px, so the reading surface got WIDER by dropping the second page, and
     for the first time it fits above the fold (294px allowance, 294px book)
     instead of running past it. The old objection -- that a single landscape
     page reads as a letterbox -- was measured against a 1.60 page. At 1.00 it
     still reads as a page.

     The width cap is gone from the query. Reading mode is decided by
     --reader-single-page, and a 932x430 phone on its side is over 860px wide
     while having less room than one at 844. Height and orientation are the
     honest terms here. */
  .book-open {
    grid-template-columns: 1fr;
    aspect-ratio: var(--book-ratio) / 1;
    height: auto;
    min-height: 0;
    max-height: none;
    --book-pad: 10px;
  }
  /* Derived from HEIGHT, so the book fits the screen it is on. A square page
     at 1.00 gives 294px at 844x390 and 334px at 932x430 -- both above the
     ~290px readable floor the portrait block established, and both inside the
     viewport, so nothing scrolls. The 290px floor is kept for very short
     screens, where a short scroll is still better than an unreadable line. */
  .flipbook {
    width: min(calc(100vw - 40px), max(290px, calc((100vh - 96px) * 1.00)));
    width: min(calc(100vw - 40px), max(290px, calc((100svh - 96px) * 1.00)));
  }
  /* A landscape phone reads a SQUARE page. Declared here so the width formula
     in the single-page block reads the same number `aspect-ratio` uses.
     On :root for the reason given at the 0.64 declaration above. */
  :root { --book-ratio: 1; }

  /* =====================================================================
     THE HEADER STOPS WEARING ITS DESKTOP HEIGHT ON A LANDSCAPE PHONE.

     Stage 1 gave the header a 68px mobile form, but keyed it on width alone
     (max-width: 760px). A phone on its side is 844 or 932 CSS px WIDE and only
     390 or 430 TALL, so it falls outside that query and gets the 124px desktop
     header — measured, 32% of a 390px viewport spent on a logo, on the one
     screen with the least height to spare. The reader has already decided this
     is a small screen: --reader-single-page is 1 here.

     Only the height is taken. The Stage 1 layout rules are NOT duplicated —
     nothing wraps at 844px because the header has ample width at that size,
     which is exactly why the desktop form fits there in the first place.
     Verified: one row, nothing escaping the header box, no overflow, at 667,
     844 and 932 wide.

     Scoped to body.page-reader so this is a READER decision, not a public-site
     breakpoint change. The site-wide question — whether the 760px header
     breakpoint should carry a height term — belongs to the header stage of the
     responsive work and is recorded in the handover as still open. */
  /* --site-header-base, not --site-header-h: the latter is now
     calc(base + notification bar) in main.css, and overwriting it here
     would drop the bar back out of the reader's own sizing. */
  body.page-reader { --site-header-base: 68px; --logo-size: 56px; }
  body.page-reader .site-header { flex-wrap: nowrap; gap: 10px; }
  /* Same relaxed cap as the <=760px block, for the same reason: the brand is
     absolutely centred and out of flow, so its size costs the reader nothing. */
  body.page-reader .brand img,
  body.page-reader .brand-fallback { max-height: calc(var(--site-header-base) - 8px); }

  /* Chrome tightens to buy the book its height back -- but NOT the tools,
     which stay at the 44px touch floor. Screen area is not worth taking from
     a control somebody has to hit with a thumb. */
  .reader-toolbar { padding: 3px 12px 4px; gap: 8px; }
  /* THE BOOK RUNS PAST THE FOLD HERE, SO THE NAVIGATION IS PINNED.
     With the book taller than a 390px viewport, a navigation strip that scrolls
     with it is a strip you cannot reach without scrolling away from what you
     are reading -- and the first screenshot showed the toolbar pushed off the
     top entirely. Fixing the arrows and the progress bar to the bottom edge
     keeps them under the thumb at all times while the page scrolls behind
     them. This is the only place in the reader that does this, and only because
     it is the only place where the book cannot fit. */
  /* LANDSCAPE KEEPS ITS CONTROLS ON THE PAGE, AND THIS IS THE ONE EXCEPTION.
     Everywhere else the arrows sit below the book, which is what one-handed use
     wants. Here the book is taller than a 390px viewport, so "below the book"
     means "below the fold" -- unreachable without scrolling away from the text.
     `position: fixed` cannot rescue it either: .flipbook carries
     transform-style: preserve-3d, and a transformed ancestor becomes the
     containing block for fixed descendants, so a fixed arrow measured 401px
     down a 390px screen.

     They therefore sit in the page's own bottom margin, beside the page number,
     with padding reserved so no line of text can reach them. Always visible,
     always reachable, never over a word. */
  /* NO BINDING, BECAUSE THERE IS NO FOLD.
     These two were restored when this block showed a spread. A single page has
     no valley and no left-hand block of turned pages, so the single-page block
     hides them and this no longer overrides it. */
  .book-stage { margin-bottom: 6px; }
  .page { padding: 12px 13px 10px; }

  /* TYPE SCALED TO THE PAGE, NOT TO THE SCREEN.
     14.5px on a 255px page is 31 characters a line and reads oversized -- the
     type looked right on a phone held upright and wrong on the same phone turned
     sideways, because the page got narrower while the words did not. 12.6px at a
     tighter leading gives ~37 characters and the density of a real page, while
     staying above the 12px floor below which iOS zooms on focus.

     reader.js reads these two values and sizes its pagination measurer from
     them, so the text and the page count cannot disagree. */
  :root {
    --reader-font-base: 12.6px;
    --reader-line-base: 1.56;
  }
}

/* =====================================================================
   PREMIUM FINISH PASS
   Sharper, more physical book: finer paper grain, a tighter spine valley,
   crisper page edges and a grounded contact shadow. Purely presentational —
   no geometry, no timing, no state classes are touched, so the opening and
   closing choreography is unchanged.
   ===================================================================== */

/* Paper: subtle laid-paper grain plus a gentle vignette toward the spine,
   so the surface catches light instead of reading as flat colour. */
.page, .sheet-face {
  background:
    /* top sheen */
    radial-gradient(120% 100% at 50% 0, rgba(255,255,255,.42), transparent 42%),
    /* very fine fibre grain */
    repeating-linear-gradient(94deg, rgba(140,110,70,.030) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(2deg,  rgba(140,110,70,.022) 0 1px, transparent 1px 4px),
    var(--page-bg);
  color: var(--page-ink);
}

/* Spine valley: the inner edge of each page darkens slightly, which is what
   makes a two-page spread read as one bound volume rather than two panels.
   NOTE: ::after on .page-left/.page-right is already used by the page-turn
   shadow animation, so this uses ::before to avoid overriding it. */
.page-left::before,
.page-right::before {
  content: ''; position: absolute; top: 0; bottom: 0; width: 30px;
  pointer-events: none; z-index: 1;
}
.page-left::before {
  right: 0;
  background: linear-gradient(90deg, transparent, rgba(74,48,22,.07));
  border-radius: 0 6px 6px 0;
}
.page-right::before {
  left: 0;
  background: linear-gradient(270deg, transparent, rgba(74,48,22,.07));
  border-radius: 6px 0 0 6px;
}
.reader-room.night .page-left::before {
  background: linear-gradient(90deg, transparent, rgba(0,0,0,.34));
}
.reader-room.night .page-right::before {
  background: linear-gradient(270deg, transparent, rgba(0,0,0,.34));
}
/* The valley is decorative only — the cover/title pages have their own art. */
.page.is-inside-cover::before, .page.is-title::before { display: none; }
/* Page content must sit above the valley wash */
.page-head, .page-body, .page-num { position: relative; z-index: 2; }

/* Crisper page edge: a hairline highlight on the outer edge reads as the
   cut edge of real paper catching the room light. */
.page-left  { box-shadow: inset 1px 0 0 rgba(255,252,244,.5); }
.page-right { box-shadow: inset -1px 0 0 rgba(255,252,244,.5); }

/* Deeper, better-grounded shadow: a tight contact shadow under the book plus
   a soft ambient one, instead of a single diffuse blur. */
.book-shadow {
  box-shadow:
    0 4px 10px rgba(40,22,8,.34),
    0 18px 34px rgba(46,26,10,.38),
    0 46px 90px rgba(52,30,12,.46);
}

/* Headings: keep them prominent and consistent across every page type. */
/* Headings stay clearly dominant over the reduced body size. */
.page-head {
  font-size: clamp(20px, 1.62vw, 28px);
  letter-spacing: .2px;
  color: var(--page-ink);
  font-weight: 600;
}

.page-num { font-size: 13px; letter-spacing: .3px; }

/* =====================================================================
   STATIC OPEN-BOOK PAGES (About Us, Contact Us)
   Reuses the reader's book shell with no flipbook engine: a fixed spread,
   logo on the left, content on the right.
   ===================================================================== */

.static-book-room { display: flex; align-items: flex-start; justify-content: center; }
.page-static-book .reader-room { padding-top: clamp(16px, 2.4vw, 34px); }

/* The static book has no toolbar, so it can claim more height. */
.page-static-book {
  --reader-chrome: calc(70px + var(--site-header-h));
  --reader-chrome-mobile: calc(90px + var(--site-header-h));
}

/* No cover, no page numbers, no turning sheet — the spread is always open. */
.static-book .book-cover,
.static-book .turning-sheet,
.static-book .page-arrow,
.static-book .page-num { display: none; }
.static-book .page-left,
.static-book .page-right { opacity: 1; }

.sb-body { font-size: clamp(12.5px, .93vw, 15px); line-height: 1.66; }

/* Right page may exceed one page of content; it scrolls within the page
   rather than overflowing the book. */
.sb-scroll { overflow-y: auto; padding-right: 6px; }
/* The shared scrollbar treatment lives in mw.css; .sb-scroll joins it there
   rather than carrying its own 7px/rgba variant. */

/* ---- left page: the logo plate ---- */
.sb-logo-plate {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: clamp(8px, 1vw, 14px); text-align: center; padding: clamp(16px, 2vw, 28px);
}
.sb-logo-plate img {
  /* logo.png is 176x179 intrinsic. Rendering it larger upscales and looks
     blurry, so 176px is a hard ceiling. */
  width: clamp(104px, 11vw, 176px); height: auto; display: block;
  filter: drop-shadow(0 6px 14px rgba(60, 36, 14, .28));
}
.sb-logo-plate img.img-missing { display: none; }
.sb-logo-fallback { display: none; font-size: 46px; color: var(--page-accent); }
.sb-logo-plate img.img-missing + .sb-logo-fallback { display: block; }
.sb-rule {
  width: clamp(70px, 7vw, 110px); height: 2px;
  background: linear-gradient(90deg, transparent, var(--page-accent), transparent);
}
.sb-imprint {
  font-family: var(--font-ui); font-size: clamp(10.5px, .82vw, 12.5px);
  letter-spacing: 2.4px; text-transform: uppercase; color: #8d7c64;
  max-width: 80%; line-height: 1.7;
}
.reader-room.night .sb-imprint { color: #9a8d7c; }

/* ---- right page: content ---- */
.sb-title {
  margin: 0 0 6px; font-family: var(--font-display); font-weight: 700;
  font-size: clamp(21px, 2.1vw, 30px); color: var(--page-ink); letter-spacing: .3px;
}
.sb-title-rule {
  display: block; width: 96px; height: 2px; margin-bottom: clamp(12px, 1.4vw, 20px);
  background: linear-gradient(90deg, var(--page-accent), transparent);
}
.sb-body p { margin: 0 0 clamp(10px, 1.1vw, 15px); }
.sb-body a { color: var(--page-accent); }
.sb-lede { font-size: 1.04em; }

/* ---- contact form ---- */
.sb-form { display: flex; flex-direction: column; gap: clamp(9px, 1vw, 14px); }
.sb-field { display: flex; flex-direction: column; gap: 5px; }
.sb-field > span {
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
  letter-spacing: .6px; text-transform: uppercase; color: #7d6a50;
}
.reader-room.night .sb-field > span { color: #b0a189; }
.sb-field input, .sb-field textarea {
  font-family: var(--font-ui); font-size: 15px;
  padding: 10px 12px; border-radius: 7px;
  border: 1px solid rgba(120, 86, 44, .38);
  background: rgba(255, 252, 244, .72);
  color: var(--page-ink);
  outline: none; width: 100%; box-sizing: border-box;
  transition: border-color .16s, box-shadow .16s;
}
.sb-field textarea { resize: vertical; min-height: 90px; }
.sb-field input:focus, .sb-field textarea:focus {
  border-color: var(--page-accent);
  box-shadow: 0 0 0 3px rgba(123, 51, 53, .13);
}
.reader-room.night .sb-field input,
.reader-room.night .sb-field textarea {
  background: rgba(255, 255, 255, .07); border-color: rgba(220, 200, 170, .28);
}
.sb-submit {
  align-self: flex-start; margin-top: 4px; cursor: pointer;
  font-family: var(--font-ui); font-size: 15px; font-weight: 500;
  padding: 11px 26px; border-radius: 24px;
  background: var(--maroon); color: #fff; border: 1.5px solid var(--maroon);
  transition: background .16s, transform .16s;
}
.sb-submit:hover { background: var(--maroon-dark); transform: translateY(-1px); }
.sb-note { margin: 2px 0 0; font-size: 12.5px; color: #8d7c64; }
.reader-room.night .sb-note { color: #9a8d7c; }

@media (max-width: 860px){
  /* Single page on phones: the logo plate would waste the whole screen, so
     the content page is the one that shows. */
  .static-book .page-left { display: none; }
  .static-book .page-right { border-radius: 6px; }
  .page-static-book .sb-title { font-size: clamp(20px, 5.4vw, 26px); }
}

/* =====================================================================
   COVER - premium Catholic library binding.
   Deep oxblood morocco leather with a gold tooled frame, blind-stamped
   inner rule, raised spine bands, a gilt centre medallion and satin
   ribbons. Presentational only: no geometry, state classes or timings are
   changed, so the open/close choreography is untouched.
   ===================================================================== */

.cover-front {
  /* Layered leather: directional sheen, fine morocco grain, and a rich
     oxblood body that is lighter at the fore-edge and deep at the spine. */
  background:
    linear-gradient(112deg,
      rgba(255,236,208,.14) 0 12%,
      rgba(255,236,208,.04) 24%,
      rgba(0,0,0,0) 48%),
    repeating-linear-gradient(37deg,
      rgba(0,0,0,.055) 0 1px, rgba(255,255,255,.012) 1px 3px),
    repeating-linear-gradient(-52deg,
      rgba(0,0,0,.045) 0 1px, rgba(255,255,255,.010) 1px 4px),
    radial-gradient(120% 90% at 78% 28%,
      rgba(168, 86, 82, .38), rgba(0,0,0,0) 62%),
    linear-gradient(128deg, #7d3235 0%, #6b2528 42%, #55191d 78%, #461216 100%);
  padding: clamp(15px, 2.5vw, 30px);
  padding-left: clamp(30px, 4.6vw, 58px);   /* spine band territory */
  box-shadow:
    inset -7px 0 14px rgba(0, 0, 0, .40),
    inset 5px 0 10px rgba(255, 220, 180, .10);
}

/* Gold tooled frame: a bright outer rule with a fine inner companion line,
   the classic double-fillet of a bound devotional volume. */
.cover-front::before {
  content: ''; position: absolute;
  top: clamp(9px, 1.5vw, 18px);
  bottom: clamp(9px, 1.5vw, 18px);
  left: clamp(24px, 3.8vw, 48px);
  right: clamp(9px, 1.5vw, 18px);
  border: 1.5px solid rgba(212, 172, 92, .92);
  border-radius: 2px;
  box-shadow:
    /* inner companion fillet */
    inset 0 0 0 1px rgba(0,0,0,.20),
    inset 0 0 0 4.5px rgba(212, 172, 92, .34),
    /* blind-stamped depression just outside the rule */
    0 0 0 1px rgba(0,0,0,.22),
    0 1px 2px rgba(255, 228, 170, .22);
  pointer-events: none; z-index: 4;
}

/* Spine: raised bands over a darker leather strip, with gilt rules between
   them - what reads instantly as a hand-bound book. */
.cover-front::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0;
  width: clamp(22px, 3.4vw, 42px);
  background:
    /* gilt rules bracketing each raised band */
    repeating-linear-gradient(180deg,
      rgba(0,0,0,0) 0 12%,
      rgba(212, 172, 92, .70) 12% 12.7%,
      rgba(255, 232, 176, .34) 12.7% 13.2%,
      rgba(0,0,0,0) 13.2% 19%,
      rgba(212, 172, 92, .70) 19% 19.7%,
      rgba(255, 232, 176, .30) 19.7% 20.2%,
      rgba(0,0,0,0) 20.2% 33%),
    /* raised band relief */
    repeating-linear-gradient(180deg,
      rgba(255, 214, 170, .12) 0 1px,
      rgba(0,0,0,.16) 1px 2px,
      rgba(0,0,0,0) 2px 44px),
    /* hinge shading: dark at the very edge, lifting toward the board */
    linear-gradient(90deg,
      rgba(0,0,0,.52) 0%,
      rgba(0,0,0,.30) 34%,
      rgba(255, 226, 186, .10) 76%,
      rgba(0,0,0,.26) 100%),
    linear-gradient(180deg, #6a2427, #4d1519);
  border-right: 1px solid rgba(212, 172, 92, .40);
  pointer-events: none; z-index: 3;
}

/* Artwork plate: seated inside the gold frame with a gilt keyline and a
   soft drop so it reads as an inlaid panel rather than a pasted image. */
.cover-art {
  border-radius: 1px;
  box-shadow:
    0 0 0 1px rgba(212, 172, 92, .62),
    0 0 0 2.5px rgba(0, 0, 0, .30),
    0 8px 20px rgba(0, 0, 0, .45);
}


/* Inside boards keep a matching gilt keyline for continuity. */
.inside-cover-face, .inside-cover-page {
  box-shadow: inset 0 0 0 1px rgba(212, 172, 92, .28);
}

/* =====================================================================
   NOTES PANEL - reuses the existing .page-overlay shell, so only the
   panel's own controls are styled here. Additive: no existing selector
   is modified.
   ===================================================================== */

/* .page-head is a positioning context for the in-page entry-edit icon. */
.page-head { position: relative; }

/* =====================================================================
 * THE BACK COVER — an exact mirror of the front cover board.
 *
 * The front board spans the RIGHT half, hinged on its left edge (the spine
 * at centre): rotateY(0) = closed, rotateY(-180deg) = swung open onto the
 * left, where its back face becomes the inside-front-cover.
 *
 * The back board spans the LEFT half, hinged on its right edge (the same
 * spine): rotateY(0) = closed from the back, rotateY(180deg) = swung open
 * onto the right, where its inside face lines up with the interior leaf.
 *
 * States mirror the front's exactly:
 *   (hidden)        default — the board only exists at the end of the book
 *   backcover-noanim  placed instantly at its open position (180deg)
 *   closing-back      animating 180deg -> 0deg, the book shutting from the back
 *   closed-back       shut, showing the back cover artwork
 * ===================================================================== */
.book-back-cover {
  display: none;
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 50%;
  z-index: 16;
  transform-style: preserve-3d;
  transform-origin: right center;      /* the spine, mirroring the front */
  cursor: pointer;
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  will-change: transform;
}
/* Shown only while closing from the back or closed on the back. */
.flipbook.backcover-noanim .book-back-cover,
.flipbook.closing-back .book-back-cover,
.flipbook.closed-back .book-back-cover { display: block; }

.book-back-cover .bc-face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  overflow: hidden;
}
/* Outer face = the artwork, framed EXACTLY like the front cover:
   .cover-front is a maroon board with padding clamp(18px, 3.4vw, 40px)
   around an inner .cover-art box, so the board is always visible as a
   border around the art. The previous absolute-inset art wrap bypassed
   that padding and stretched the image edge-to-edge — the borders were
   not "gone", they were covered. Same padding, mirrored inset shadows
   (the spine is on this board's RIGHT). */
.book-back-cover .bc-outer {
  background: linear-gradient(135deg, #8e4144, var(--maroon) 45%, var(--maroon-dark));
  border-radius: 12px 6px 6px 12px;
  padding: clamp(18px, 3.4vw, 40px);
  box-sizing: border-box;
  box-shadow:
    inset 6px 0 12px rgba(0, 0, 0, .35),
    inset -4px 0 8px rgba(255, 255, 255, .12);
}
/* Inside face = the leaf facing the reader at the end; rotated so it shows
   when the board is swung open, exactly as .cover-back does at the front.
   Carries the SAME chrome as the front's inside face, mirrored left-right:
   board-lip gradient, protruding gold corner clips (on the OUTER edge, which
   for this board is the right), inset inner face, and the spine-shading
   gradient on its spine edge (the left) — so front and back read as the two
   boards of one physical book, inside as well as out. */
.book-back-cover .bc-inside {
  transform: rotateY(180deg);
  border-radius: 6px 12px 12px 6px;
  background: linear-gradient(-135deg, #8e4144, var(--maroon) 90%);
  overflow: visible;                 /* the gold corner clips protrude 3px */
}
.book-back-cover .bc-inside::before {
  content: ''; position: absolute; top: -3px; right: -3px;
  width: 42px; height: 42px;
  background: linear-gradient(-135deg, #ffe27a, var(--gold) 70%, #a87f22);
  clip-path: polygon(0 0, 100% 0, 100% 100%);
  border-radius: 0 8px 0 0;
  z-index: 3;
}
.book-back-cover .bc-inside .inside-cover-face { inset: 16px; border-radius: 0 6px 6px 0; }
.book-back-cover .bc-inside .inside-cover-face::before {
  content: ''; position: absolute; bottom: -19px; right: -19px;
  width: 42px; height: 42px;
  background: linear-gradient(-135deg, #ffe27a, var(--gold) 70%, #a87f22);
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  border-radius: 0 0 8px 0;
  z-index: 3;
}
/* Spine shading on the LEFT edge of this face — the mirrored right half of
   the spine gradient, matching .cover-back::after on the front. */
.book-back-cover .bc-inside::after {
  content: '';
  position: absolute; top: 10px; bottom: 10px; left: 0; width: 48px;
  background: linear-gradient(-90deg,
    rgba(60, 35, 15, 0)     0%,
    rgba(60, 35, 15, .045) 44%,
    rgba(60, 35, 15, .12)  76%,
    rgba(54, 30, 12, .26)  93%,
    rgba(44, 24, 9,  .34)  98.4%,
    rgba(255, 250, 238, .30) 100%);
  z-index: 4;
}
/* A normal box INSIDE the board's padding — the .cover-art class it carries
   then does exactly what it does on the front (relative, 100%, overflow
   hidden, 3px radius). Absolute inset:0 here was what defeated the frame. */
.book-back-cover .bc-art-wrap { position: relative; width: 100%; height: 100%; }
.book-back-cover .bc-art {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  border-radius: 3px;
}
/* No back-cover image uploaded: the board still exists (every physical book
   has one) and shows the imprint instead of artwork. */
.book-back-cover .bc-art.img-missing { display: none; }
.book-back-cover .bc-art[src=""] { display: none; }
.book-back-cover .bc-fallback {
  position: absolute; inset: 0; display: none;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; text-align: center; padding: 10%;
}
.book-back-cover .bc-art.img-missing ~ .bc-fallback,
.book-back-cover .bc-art[src=""] ~ .bc-fallback { display: flex; }
.book-back-cover .bc-flourish { font-size: 30px; color: #e8cf96; opacity: .85; }
.book-back-cover .bc-imprint {
  font-family: var(--font-display, Georgia, serif);
  font-size: clamp(12px, 1.1vw, 15px);
  letter-spacing: .22em; text-transform: uppercase; color: #f0dcae;
}

/* Placed instantly at its open position so the swap under it is invisible —
   its inside face already matches the leaf it replaces. */
.flipbook.backcover-noanim .book-back-cover {
  transition: none !important;
  transform: rotateY(180deg);
}

/* CLOSING FROM THE BACK: the board sweeps right-to-left over the body. The
   RIGHT half is dismissed instantly beneath it (the board covers it at frame
   0); the LEFT half stays and is progressively covered. Mirror of .closing. */
.flipbook.closing-back .book-open { transform: translateX(25%); }
.flipbook.closing-back .book-back-cover {
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  transform: rotateY(0deg);
  box-shadow: 16px 24px 50px rgba(40, 22, 8, .55);
  border-radius: 12px 6px 6px 12px;
}
.flipbook.closing-back .book-cover { display: none; }
.flipbook.closing-back .board-right,
.flipbook.closing-back .clip-tr,
.flipbook.closing-back .clip-br,
.flipbook.closing-back .stack-right,
.flipbook.closing-back .page-right { opacity: 0; }
.flipbook.closing-back .board-left,
.flipbook.closing-back .clip-tl,
.flipbook.closing-back .clip-bl,
.flipbook.closing-back .stack-left,
.flipbook.closing-back .page-left { opacity: 1; }
.flipbook.closing-back .book-spine-shadow { opacity: 0; transition: opacity .3s ease .6s; }
.flipbook.closing-back .book-shadow { opacity: 0; transition: opacity .22s ease; }

/* CLOSED ON THE BACK: only the back board exists. Mirror of .closed. */
.flipbook.closed-back .book-open { transform: translateX(25%); }
.flipbook.closed-back .book-cover { display: none; }
.flipbook.closed-back .book-board,
.flipbook.closed-back .corner-clip,
.flipbook.closed-back .book-spine-shadow,
.flipbook.closed-back .page-stack,
.flipbook.closed-back .page-left,
.flipbook.closed-back .page-right { opacity: 0; }
.flipbook.closed-back .book-shadow { opacity: 0; transition: none; }
.flipbook.closed-back .book-back-cover {
  transform: rotateY(0deg);
  box-shadow: 16px 24px 50px rgba(40, 22, 8, .55);
  border-radius: 12px 6px 6px 12px;
  transition: transform .3s ease;
}
.flipbook.closed-back .book-back-cover:hover { transform: rotateY(8deg); }

/* REOPENING FROM THE BACK: the reverse sweep, mirroring .opening. */
.flipbook.opening-back .book-open { transform: translateX(0); }
.flipbook.opening-back .book-back-cover {
  display: block;
  transition: transform 1.05s cubic-bezier(.6, .04, .3, 1);
  transform: rotateY(180deg);
}
.flipbook.opening-back .book-back-cover:hover { transform: rotateY(180deg); }
.flipbook.opening-back .book-cover { display: none; }
.flipbook.opening-back .board-left,
.flipbook.opening-back .clip-tl,
.flipbook.opening-back .clip-bl,
.flipbook.opening-back .stack-left,
.flipbook.opening-back .book-spine-shadow,
.flipbook.opening-back .page-left { opacity: 1; }
.flipbook.opening-back .book-shadow { opacity: 1; transition: opacity .3s ease .78s; }

/* ---------- the back board in single-page mode ----------
   BOTH BOARDS FILL THE FRAME, NOT JUST THE FRONT.
   On a spread each cover is one half of the book: .book-cover runs from the
   spine to the right edge, .book-back-cover from the left edge to the spine.
   In single-page mode there is no spine, so each has to span the whole width.
   Only the front was ever released, which left the back pinned at right: 50% —
   a full-height board at half the width, which is the squashed portrait strip
   the back cover showed on a phone.

   This sits HERE, at the end of the back-cover section, and not with the front
   cover's matching override in the single-page block far above. Every rule it
   overrides is declared in the lines just above this one, and CSS resolves ties
   by source order: the same declarations placed earlier in the file parse
   cleanly, win nothing, and leave the back cover exactly as broken as before.
   Condition mirrors --reader-single-page. */
@media (max-width: 860px), (max-height: 520px) {
  .book-back-cover { right: 0; }
  .book-back-cover .bc-outer, .book-back-cover .bc-inside { border-radius: 8px; }
  /* The 25% shove that slides the body out from under a closing board is a
     spread manoeuvre; with one full-width page there is nothing to slide out
     of. Mirrors what mobile already does for .closed at the front. */
  .flipbook.closed-back .book-open,
  .flipbook.closing-back .book-open { transform: none; }
  .flipbook.closing-back .book-back-cover,
  .flipbook.closed-back .book-back-cover { border-radius: 8px; }

  /* NOTHING IS DISMISSED BEFORE THE BOARD ARRIVES TO COVER IT.
     On a spread the back board is the LEFT half, so at the start of the sweep
     it is flipped over onto the RIGHT half and already covering it — which is
     why .closing-back clears the right-hand furniture at frame 0.

     Here the board is the full width, hinged on its right edge, so at 180deg it
     sits entirely OFF-SCREEN to the right and covers nothing at all. Clearing
     the right-hand furniture then leaves the backdrop showing through: measured
     at 390x844, the board sat at x 368..729 against a book at 8..368, so the
     whole book was uncovered while .page-right and .board-right were already at
     opacity 0. On screen that is a maroon left half (.board-left, still opaque)
     beside a bare right half — the half-a-back-cover reported from the device.

     The front cover never showed this because .closing dismisses the LEFT
     furniture and keeps .page-right, and on a phone .page-right is the whole
     visible page: it stays put and hides the board that was cleared beneath it.
     This is that same behaviour, mirrored. The board covers everything when it
     lands, so nothing needs clearing early. */
  .flipbook.closing-back .board-right,
  .flipbook.closing-back .clip-tr,
  .flipbook.closing-back .clip-br,
  .flipbook.closing-back .stack-right,
  .flipbook.closing-back .page-right { opacity: 1; }

  /* REOPENING: the leaf under the lifting board is painted INSTANTLY.
     Measured on the real reopen path: the flipbook carries closed-back AND
     opening-back together for the whole transition, so closed-back's
     `.page-right { opacity: 0 }` — declared in the base section above — beat
     an opening-back override placed earlier in the file. Same source-order
     trap as the board's right:0; same cure: the override lives HERE, after
     everything it must out-rank. Worst frame went from 50% of the book
     covered (half board, half bare wall, the artefact filmed on the device)
     to full coverage. transition:none because the board hides the page at
     frame 0 — there is nothing for a fade to add. */
  .flipbook.opening-back .page-right,
  .flipbook.closed-back.opening-back .page-right { opacity: 1; transition: none; }
}

/* The on-board upload control is gone: the back cover's upload lives in the
   top toolbar (.tool-upload-back, reader.php), swapped with the front's by
   the book-closed-back room class — one entry point, one slot. */

/* ---- No text selection while a page is being dragged --------------------
   Set by flipbook.js when a corner drag ENGAGES and removed the moment it
   ends. A drag across text is also a selection gesture as far as the browser
   is concerned, so without this the poem highlighted blue as the sheet lifted.

   On the body rather than on .page: once the pointer is captured the drag can
   travel anywhere on screen, and selection would otherwise start on whatever
   it passes over. */
body.mw-dragging-page,
body.mw-dragging-page * {
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* The cursor says "you are holding the page", not "you are selecting text". */
body.mw-dragging-page { cursor: grabbing; }
