/* =====================================================================
   MemorableWorld — main stylesheet (from the OFE baseline)
   Matched to the live site room: cream header + ceiling with recessed
   lights, tan walls with panel seams, two ornate daily frames (left),
   framed stained glass + church window (center), shelves with tilted
   3D books and under-shelf light cones (right), wood plank floor.
   ===================================================================== */

:root {
  --cream:        #f7ecdd;
  --cream-deep:   #efe0c9;
  --ceiling:      #f0e9dc;
  --maroon:       #7b3335;
  --maroon-dark:  #5e2426;
  --wall:         #e9e1d2;
  --wall-dark:    #ded4c1;
  --wall-edge:    #cabfa8;
  --frame-brown:  #7a5c3c;
  --frame-inner:  #63503a;
  --gold:         #d8b24a;
  --frame-gold:   #c9a24e;
  --floor-1:      #c8a06b;
  --floor-2:      #b78e58;
  --ink:          #3c2a1e;
  --frame-text:   #f2e6cf;
  --font-ui:      'Poppins', system-ui, sans-serif;
  --font-display: 'Cormorant Garamond', Georgia, serif;

  /* ===================== THE QUOTE / POEM TYPE SCALE =====================
     ONE SCALE, READ BY BOTH KINDS OF PAGE. A quote page and a poem page are
     the same component with different content — pagination.js builds both out
     of `.poem-page` — but their sizes were stated separately in mw.css and had
     drifted apart: measured at 1440, the quote's heading was 25px against the
     poem title's 23px and its body 18px against the poem's 16px, so a quote
     read as a larger thing than a poem for no reason anyone had decided.

     Stating the scale once is what stops that recurring. It is not one size
     for everything — the hierarchy is deliberate and each step is a token:

       --rp-title    the poem's name, and the quote's author heading, which
                     occupies the same slot in the same markup
       --rp-author   the poem's author line. Kept ABOVE the body at every
                     width, so "the attribution is more prominent than the
                     text" is true by measurement and not only by weight
       --rp-body     the poem or the quote itself
       --rp-kicker   the "Quote" eyebrow on a diary quote page — a
                     letter-spaced uppercase micro-label, not read text

     Floors respect the 12px policy in HANDOVER A7b for everything a reader
     actually reads. Below 861px reader.css's `.page-reader .page-body h3`
     (0,2,1) outranks `.poem-title` (0,1,0) and sets the heading itself; the
     floors here are chosen so the two meet without a step at that boundary.

     REVISED, August 2026, at the client's measurement: body 13 -> 14 and
     author 13.5 -> 14.5, both at the top of the scale. **Only the maxima
     moved.** The floors and the vw slopes are untouched, so the phone renders
     exactly what it did before and no breakpoint, wrap point or pagination
     input changes below about 1330px. `--rp-author` keeps the steeper slope
     (1.1vw against 1.05vw) and the higher floor, which is what makes
     author > body true at EVERY width rather than only at the top.
     ===================================================================== */
  --rp-title:  clamp(15px, 1.5vw, 18.5px);
  --rp-author: clamp(12.5px, 1.1vw, 14.5px);
  --rp-body:   clamp(12px, 1.05vw, 14px);
  --rp-kicker: 9.5px;
  /* Height of the sticky site header. Single source of truth: the reader
     overlay, the embedded reader frame and the reader room all offset by
     this so nothing ever sits under the header. Sized to fit the logo
     (square artwork) plus its vertical padding without clipping. */
  --site-header-base: 124px;

  /* ===================== CHROME HEIGHT, NOT HEADER HEIGHT =====================
     --site-header-h is what every book formula subtracts, so it must mean
     "vertical space owned by site chrome", not "height of one element". The
     subscription notification bar is chrome: it sits with the header, above
     the page content, on every page. Declaring it here is what lets it exist
     WITHOUT any page recalculating anything — the reader room, --reader-chrome
     and the static-book box all read this variable and shrink by exactly the
     right amount, with no edit to any of them.

     --mw-subbar-h is 0px unless a bar is actually rendered, so with no bar
     this is calc(124px + 0px) — identical to the plain 124px it replaced.
     That equivalence is the whole safety argument: absent bar, absent change.

     THE BAR'S HEIGHT IS DECLARED, NOT MEASURED. The rendered bar is given
     `height: var(--mw-subbar-h)` so the number here and the pixels on screen
     cannot drift; a bar sized by its own content would make this arithmetic a
     guess. */
  --mw-subbar-h: 0px;
  --site-header-h: calc(var(--site-header-base) + var(--mw-subbar-h));
}

/* THE CALC IS REPEATED ON body, AND THAT REPETITION IS LOAD-BEARING.
   A custom property is substituted where it is DECLARED, not where it is read.
   Declared only on :root, --site-header-h resolves --mw-subbar-h against
   :root — which is always 0px, because the class that switches the bar on
   (`has-subbar`) is on <body>. The bar rendered at its full 40px while every
   book was still sized as though chrome were 124px, so the page grew by
   exactly the bar's height: the very regression this design exists to prevent.
   Caught by reading the computed value rather than trusting the arithmetic.

   Re-declaring here makes body the element the substitution happens at, where
   --mw-subbar-h is 40px when a bar is present and 0px when it is not.
   Everything inside body inherits the resolved value from here. */
body {
  --site-header-h: calc(var(--site-header-base) + var(--mw-subbar-h));
  --logo-size: clamp(74px, 6.4vw, 96px);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
/* ============ THE CANVAS IS PAINTED BEFORE ANYTHING ELSE IS ============
   `body` sets `background: var(--wall)` and CSS propagates that to the canvas
   when the root has none — which is true, and which is why this looked
   finished. Propagation happens once the BODY's box exists. Between two
   documents the browser has a window where the new root is styled and the
   body is not yet laid out, and it fills the canvas with its own default,
   which is white.

   That is the brief light flash on ordinary page-to-page navigation, and it is
   most visible going to or from the reader, where it lands between two
   different room treatments and reads as "the background switched".

   Stating it on the ROOT removes the window: there is no moment at which the
   canvas has no colour to use. `body` keeps its own declaration — this does
   not replace it, and any page that overrides the body background still
   wins over its own box. */
html { background: var(--wall); }
/* Nothing may scroll the page sideways. Decorative layers (the perspective
   floor, overlapping shelf books, wide backdrops) deliberately extend past
   their containers; this clips them at the viewport instead of letting the
   document stretch and drag horizontally.
   overflow-x: clip (not hidden) is used so it does NOT create a scroll
   container, which would break `position: sticky` on the site header. */
/* Fallback first for browsers without `clip`; `clip` then wins where
   supported. `hidden` on <html> alone does not create a scroll container
   for the sticky header, so this ordering is safe. */
html { overflow-x: hidden; overflow-x: clip; }
body { overflow-x: hidden; overflow-x: clip; max-width: 100%; }
body {
  font-family: var(--font-ui);
  color: var(--ink);
  background: var(--wall);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
img { max-width: 100%; }
a { color: inherit; }

/* ============================ header ============================ */

.site-header {
  /* The ELEMENT's own height, so --site-header-base, not the chrome total:
     --site-header-h now also covers the notification bar, and using it here
     made the header grow by the bar's height as well as the bar existing —
     the page gained 80px for a 40px bar. */
  height: var(--site-header-base);
  box-sizing: border-box;
  background: var(--cream);
  box-shadow: 0 2px 8px rgba(74, 52, 32, .3);
  position: sticky;
  top: 0;
  z-index: 60;
}

.brand { display: flex; align-items: center; text-decoration: none; }
.brand img { height: 60px; width: auto; display: block; }
.brand .brand-fallback { display: none; }
.brand img.img-missing { display: none; }
.brand img.img-missing + .brand-fallback {
  display: grid; place-items: center;
  width: 56px; height: 56px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #a3542f, var(--maroon-dark));
  color: var(--gold); font-size: 30px;
  border: 3px solid var(--gold);
}

.site-nav { display: flex; align-items: center; }
.nav-link {
  display: inline-flex; align-items: center; gap: 6px;
  text-decoration: none; font-weight: 500;
}
.nav-link:hover { color: var(--maroon); }
.ic { width: 16px; height: 16px; fill: currentColor; }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; border: 0; cursor: pointer; text-decoration: none;
  font-family: var(--font-ui); font-size: 13.5px; font-weight: 500;
  border-radius: 7px; padding: 10px 18px;
  transition: background .18s;
}
.btn-solid { background: var(--maroon); color: #fff; }
.btn-solid:hover { background: var(--maroon-dark); }
.btn-solid .ic { width: 15px; height: 15px; }

.global-search { position: relative; display: flex; align-items: center; }
.search-btn { color: var(--maroon); margin-right: 8px;
  display: grid; place-items: center; cursor: pointer;
}
.search-btn:hover { background: var(--maroon); color: #fff; }
.search-input {
  height: 42px; width: clamp(120px, 16vw, 220px); border: 1px solid #d9c8b0; border-radius: 8px;
  padding: 0 12px; font-family: var(--font-ui); font-size: 14px; background: #fff;
  outline: none;
}
.search-input:focus { border-color: var(--maroon); }
.search-results {
  position: absolute; top: calc(100% + 6px); right: 0; width: 340px; max-height: 400px;
  overflow: auto; background: #fff; border-radius: 10px;
  box-shadow: 0 14px 40px rgba(50, 32, 16, .4); z-index: 80;
  border-top: 5px solid var(--maroon);
}
.search-results a {
  display: flex; align-items: center; gap: 14px; padding: 10px 14px;
  text-decoration: none; border-bottom: 1px solid #eee2d2; font-size: 15px;
}
.search-results a:hover { background: var(--cream); }
.search-results img { width: 42px; height: 60px; object-fit: cover; border-radius: 3px; box-shadow: 1px 2px 6px rgba(0,0,0,.35); }
.search-results .no-hit { padding: 14px; color: #8b7862; font-size: 14px; }

.nav-toggle { display: none; }

/* ============================ the room ============================ */

.library-room {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  background: linear-gradient(var(--wall), var(--wall-dark));
}

/* ceiling with recessed lights */
.room-ceiling {
  height: 46px;
  background: linear-gradient(var(--ceiling), #ded2bd);
  border-bottom: 5px solid #c6b69c;
  display: flex; justify-content: space-around; align-items: center;
  box-shadow: inset 0 -12px 18px rgba(120, 95, 60, .28);
  flex: 0 0 auto;
}
.ceiling-light {
  width: 30px; height: 11px; border-radius: 50%;
  background: radial-gradient(#fffbe8 0 30%, #b7a88d 75%);
  box-shadow:
    0 0 0 2px rgba(140, 120, 90, .5),
    0 10px 26px 8px rgba(255, 244, 205, .45);
}

.room-body {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: minmax(280px, 330px) minmax(360px, 1fr) minmax(340px, 390px);
  gap: 0;
  align-items: start;
}
.room-col { padding: 34px 26px 26px; }
.room-col + .room-col { border-left: 2px solid rgba(90, 65, 40, .22); }

/* -------- left: two ornate daily frames -------- */

.daily-panel { display: flex; flex-direction: column; gap: 38px; }

.daily-frame {
  position: relative;
  background:
    linear-gradient(150deg, #967a52, #7a5c3c 55%, #8d6f48);
  padding: 13px;
  border-radius: 3px;
  box-shadow:
    0 12px 28px rgba(52, 35, 18, .45),
    inset 0 0 0 2px rgba(240, 220, 170, .3),
    inset 0 0 12px rgba(40, 25, 10, .5);
}
.frame-inner {
  background: linear-gradient(#6c5940, #59462f);
  border: 1px solid rgba(240, 220, 170, .28);
  border-radius: 2px;
  color: var(--frame-text);
  text-align: center;
  padding: 22px 18px 24px;
  position: relative;
  box-shadow: inset 0 0 26px rgba(25, 15, 5, .45);
}
.flourish-fallback {
  display: block; color: var(--frame-gold); font-size: 20px; letter-spacing: 5px;
  margin-bottom: 8px; font-family: var(--font-display);
}

.daily-section h3 {
  margin: 2px 0 8px;
  font-size: 15.5px; font-weight: 700; letter-spacing: .4px;
  color: #ffffff;
}
.daily-section + .daily-section { margin-top: 20px; }
.daily-title { margin: 0 0 5px; font-size: 13px; font-weight: 500; color: #f0e3c8; }
.daily-excerpt { margin: 0 0 12px; font-size: 12.6px; line-height: 1.55; color: #e2d2b0; }
.btn-read {
  background: var(--maroon); color: #fff; font-size: 12px;
  padding: 7px 24px; border-radius: 15px;
}
.btn-read:hover { background: var(--maroon-dark); }

.frame-scroll-end { margin: 18px auto 0; width: 72%; height: 14px; position: relative; }
.frame-scroll-end::before {
  content: ''; position: absolute; left: 0; right: 0; top: 6px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--frame-gold), transparent);
}
.frame-scroll-end::after {
  content: '❧ ❧'; position: absolute; left: 50%; top: -3px; transform: translateX(-50%);
  color: var(--frame-gold); font-size: 13px; letter-spacing: 10px; padding: 0 8px;
}

/* -------- center: framed windows -------- */

.room-windows { display: flex; flex-direction: column; gap: 26px; align-items: center; }

.window-frame {
  width: 100%;
  background: linear-gradient(150deg, #a98f6d, #937856);
  border: 2px solid #866c4c;
  padding: 12px;
  border-radius: 3px;
  box-shadow:
    0 12px 26px rgba(52, 35, 18, .4),
    inset 0 0 0 2px rgba(255, 240, 205, .25);
}

/* The openings' fallback is now the colour beneath the scene itself
   (.mw-scene-view in mw.css), so there is no separate fallback element to
   hide. Removing the rule rather than leaving it: a selector matching nothing
   is a question for whoever reads it next. */

/* -------- right: shelves with tilted 3D books -------- */

.shelf-wall { position: relative; }

.shelf { position: relative; margin-bottom: 46px; }
.shelf-books {
  display: flex; align-items: flex-end; justify-content: center; gap: 3px;
  padding: 0 14px;
  position: relative; z-index: 2;
  perspective: 900px;
}

.book {
  position: relative;
  width: 50px;
  text-decoration: none;
  transform-style: preserve-3d;
  transform: rotateY(-17deg);
  transform-origin: center;
  transition: transform .22s ease;
}
/* A hovered book must clear the neighbours overlapping it on both sides. */
.book:hover { transform: rotateY(-6deg) translateY(-7px) scale(1.07); z-index: 30; }

.book .cover-face {
  display: block; width: 100%; height: 78px; object-fit: cover;
  border-radius: 1px 3px 3px 1px;
  box-shadow: 5px 5px 10px rgba(40, 24, 8, .5);

}
/* spine on the left edge for the 3D standing-book look */
.book::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: -5px; width: 6px;
  background: linear-gradient(90deg, #5c1f21, #8e3b3e);
  transform: rotateY(-58deg);
  transform-origin: right center;
  border-radius: 2px 0 0 2px;
}
/* page block on the right edge */
.book::after {
  content: ''; position: absolute; top: 2px; bottom: 2px; right: -3px; width: 3px;
  background: repeating-linear-gradient(180deg, #f1e8d4 0 2px, #d6c9ac 2px 3px);
  transform: rotateY(38deg);
  transform-origin: left center;
}

.book .book-spine-fallback { display: none; }
.book img.img-missing { display: none; }
.book img.img-missing + .book-spine-fallback {
  display: grid; place-items: center; text-align: center;
  width: 100%; height: 78px; border-radius: 1px 3px 3px 1px;
  background: linear-gradient(160deg, #8e3b3e, var(--maroon-dark));
  color: var(--gold); font-family: var(--font-display); font-size: 9.5px;
  padding: 3px; line-height: 1.2;
  box-shadow: 5px 5px 10px rgba(40, 24, 8, .5);
}

/* The tooltip must float clear of the books overlapping this one on either
   side, so it is lifted well above the shelf's own stacking level. */
.book-tooltip {
  position: absolute; left: 50%; bottom: calc(100% + 9px);
  transform: translateX(-50%) translateY(4px);
  background: var(--maroon-dark); color: #fff; font-size: 11.5px; font-weight: 500;
  padding: 5px 10px; border-radius: 6px; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity .18s, transform .18s;
  z-index: 40;
}
.book-tooltip::after {
  content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 5px solid transparent; border-top-color: var(--maroon-dark);
}
.book:hover .book-tooltip { opacity: 1; transform: translateX(-50%) translateY(0); }

/* shelf plank: original image with CSS fallback */
.shelf-board { position: relative; z-index: 3; margin-top: -2px; min-height: 12px; }
.shelf-board img { display: block; width: 100%; }
.shelf-board img.img-missing { display: none; }
.shelf-board img.img-missing + .board-fallback { display: block; }
.board-fallback {
  display: none; height: 12px; border-radius: 2px;
  background: linear-gradient(#b28a58, #8a6236 60%, #6d4b26);
  box-shadow: 0 7px 12px rgba(52, 32, 12, .5);
}

/* -------- floor -------- */
.room-floor {
  flex: 0 0 auto;
  border-top: 4px solid #8a6236;
}

/* ============================ footer ============================ */

.site-footer {
  background: var(--cream);
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 28px; font-size: 13.5px;
  position: relative; z-index: 5;
}
.site-footer a { text-decoration: none; }
.site-footer a:hover { color: var(--maroon); }
.social { display: flex; gap: 10px; }
.social a {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1.5px solid var(--ink);
  display: grid; place-items: center;
}
.social svg { width: 16px; height: 16px; fill: currentColor; }
.social a:hover { border-color: var(--maroon); color: var(--maroon); }

/* ============================ responsive ============================ */

@media (max-width: 1180px){
  .room-body { grid-template-columns: minmax(260px, 300px) 1fr minmax(310px, 350px); }
  .book { width: 45px; }
  .book .cover-face, .book img.img-missing + .book-spine-fallback { height: 71px; }
}

@media (max-width: 1010px){
  .room-body { grid-template-columns: minmax(260px, 320px) 1fr; }
  .room-windows-col { display: none; }
}

/* THE HEADER IS KEYED ON THE SPACE IT HAS, THE ROOM ON THE WIDTH IT HAS.
   These used to be one `max-width: 760px` block, and that conflated two
   different questions. A phone on its side is 844 or 932 CSS px WIDE and only
   390 or 430 TALL, so it failed the width test and took the 124px DESKTOP
   header — a third of the viewport with the least to spare. The reader already
   fixed this for itself (reader.css, `body.page-reader`), which left every
   other page — Login, Register, Profile, the homepage — still wearing it.

   Splitting the block rather than adding a landscape term to the whole of it
   is the point: the declarations below the split (`room-body` to one column,
   `.book { width: 44px }`, the stacked footer) describe a NARROW viewport, and
   a landscape phone is not narrow. Applying them at 844px wide would have
   collapsed the room and shrunk every shelf book for no reason.

   Nothing here is a new declaration; the rules are the ones that were already
   in this block. Only the question they are asked has changed. The landscape
   term is spelled exactly as reader.css spells it, so there is one definition
   of "landscape phone" in the codebase. */
@media (max-width: 760px), (max-height: 520px) and (orientation: landscape){
  .site-header { flex-wrap: wrap; }
  .nav-toggle {
    display: inline-flex; flex-direction: column; gap: 4px;
    margin-left: auto; background: none; border: 0; cursor: pointer; padding: 8px;
  }
  .nav-toggle span { width: 22px; height: 2.5px; background: var(--ink); border-radius: 2px; }

  .site-nav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch;
    background: var(--cream); padding: 14px 22px 20px;
    box-shadow: 0 14px 24px rgba(60, 42, 30, .3);
    z-index: 40;
  }
  .site-nav.open { display: flex; }
  .global-search { margin-left: 0; order: 3; flex: 1 1 100%; width: 100%; min-width: 0; margin-top: 10px; }
  .search-btn { flex: 0 0 auto; }
  .search-input { flex: 1 1 auto; width: auto; min-width: 0; }
  .search-results { width: 100%; }
}

/* THE ROOM, THE SHELF BOOKS AND THE FOOTER — width only, deliberately.
   See the note above the header block. */
@media (max-width: 760px){
  .room-body { grid-template-columns: 1fr; }
  .room-col + .room-col { border-left: 0; }
  .shelf-wall-col { order: -1; }
  .book { width: 44px; }
  .book .cover-face, .book img.img-missing + .book-spine-fallback { height: 69px; }
  .room-floor { height: 64px; }
  .site-footer { flex-direction: column; gap: 10px; text-align: center; }
}

@media (prefers-reduced-motion: reduce){
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}
.window-frame.img-missing { display: none; }

@media (max-width: 1010px){
}
@media (max-width: 760px){
}

@media (prefers-reduced-motion: reduce){
  .take-clone { transition: none !important; }
}

/* homepage stained pair is now one provided image */

/* =====================================================================
   HOMEPAGE REPLICATION PASS — geometry measured from the client's
   reference screenshot (1206px-wide capture) and the live site DOM.
   Every value below derives from measurement, not judgment.
   Reference numbers (at 1206px): left col 8-300 (frames 292w), frames
   gap 7px; center unit 365-838 (473w, 39.2%), stained pair = provided
   pane x2, unit->window gap 14px, window image area 431x234 (1.84);
   right col 858-1200, books h~100 plain images, plank 9px, cones 57h,
   shelf gap 59px, jesus ~66w; floor 111px dark red-brown planks.
   All rules scoped to .page-home — no other page is affected.
   ===================================================================== */

/* header: logo LEFT | nav CENTER | search RIGHT (matches live) */
.site-header { display: flex; align-items: center; gap: 18px; }
.site-nav { flex: 1; justify-content: center; }
.global-search { margin-left: 0; }

.page-home .room-col + .room-col { border-left: 0; }

/* LEFT: frames at measured width and near-touching vertical rhythm */
.page-home .daily-panel { gap: 8px; }

.page-home .daily-section h3 { margin: 0 0 6px; }
.page-home .daily-excerpt { margin-bottom: 9px; }
.page-home .daily-section + .daily-section { margin-top: 12px; }
.page-home .frame-scroll-end { margin-top: 12px; }

/* CENTER: unit width 39.2% of the page, left-aligned to measured x */
.page-home .room-windows-col { padding: 26px 0 0 clamp(40px, 5.97vw, 72px); }
.page-home .room-windows { align-items: flex-start; }
.page-home .window-frame { border-width: 1px; width: 100%; }
.page-home .shelf { margin-bottom: 0; }
.page-home .shelf + .shelf { margin-top: 0; }
.page-home .shelf-books {
  perspective: none;
}
.page-home .book {
  transform: none;
  transition: transform .18s ease;
}
.page-home .book:hover { transform: translateY(-5px); }
.page-home .book::before, .page-home .book::after { content: none; }
.page-home .book .cover-face,
.page-home .book img.img-missing + .book-spine-fallback {
  /* drop-shadow, NOT box-shadow: the cover art is a transparent PNG of a
     CURVED book, and box-shadow shadows the element's rectangle regardless of
     what is actually drawn — which is what put every book inside a visible
     rectangular container. drop-shadow follows the alpha channel, so the
     shadow hugs the book's real silhouette. */
  filter: drop-shadow(2px 4px 6px rgba(40, 24, 8, .45));
}

.page-home .shelf-board { margin-top: -1px; }

/* FLOOR: measured dark red-brown horizontal planks, ~25px rows, 111px */

@media (max-width: 1010px){
  .page-home .room-body { grid-template-columns: minmax(240px, 292px) 1fr; }
}
@media (max-width: 760px){
  .page-home .room-body { grid-template-columns: 1fr; }
  /* A GRID ITEM DEFAULTS TO `min-width: auto`, WHICH MEANS "never narrower
     than my content". With the room collapsed to one column the shelves and
     panels inside set a min-content width of 411px, so the column stayed 411px
     wide inside a 320px phone and the overflow was swallowed by the body's
     `overflow-x: hidden` — invisible to an overflow audit, plainly visible as
     content running off the side of the screen. This is the standard grid
     blow-out and `min-width: 0` is its standard remedy: the column may now be
     as narrow as the viewport, and the things inside it lay out to fit. */
  .page-home .room-col { min-width: 0; }

  /* THE COLUMN PADDING IS ASYMMETRIC BY DESIGN — DESKTOP DESIGN.
     `.page-home .room-col` is `… 30px 0 12px 16px`: padding on the LEFT and
     NONE on the right. In a three-column room that is correct — each column
     is separated from the one before it and the room's own edge supplies the
     rest. Stacked into one column on a phone it means every panel sits inset
     on the left and flush against the right edge, which is exactly the "cut
     off near the edges" reported for the Quote and Poem frames. Made
     symmetric here, where the columns are stacked and the reason for the
     asymmetry no longer exists. */
  .page-home .room-col { padding-left: 14px; padding-right: 14px; }

  /* DECORATIVE PANELS DO NOT EARN THEIR HEIGHT ON A PHONE.
     Two blocks sit under the shelves on every room page: the window frames
     (.room-windows-col) and, on the module landing pages, the left column in
     its "room pictures" variant. Both are pure decoration — full-width
     photographs a visitor must scroll past to reach anything they can act on.

     The DAILY variant of that column is different: Quote of the Day and Poem
     of the Week are real content and stay. The markup already knew which
     variant it was rendering — `$leftPanel === 'daily'` — it just never said
     so in a class. It does now, so this is one rule keyed on what the column
     IS, rather than a list of page names to keep in step. */
  .room-windows-col { display: none; }
  .page-home .room-left-col.is-pictures { display: none; }
  .page-home .room-windows-col { padding-left: 12px; padding-right: 12px; }
  .page-home .room-windows { max-width: none; }
  .page-home .book .cover-face,
  .page-home .book img.img-missing + .book-spine-fallback { height: 84px; }
  .site-nav { justify-content: flex-start; }
}

/* homepage overflow guards (scoped) — invisible tooltips and shelf rows
   must never widen the page; the room already clips to the viewport */
.page-home .library-room { overflow-x: clip; }
.page-home .shelf-wall { overflow: hidden; }
.page-home .shelf-books { row-gap: 6px; }

.page-home .book-tooltip { display: none; }
.page-home .book:hover .book-tooltip { display: block; }

/* =====================================================================
   PROPORTIONAL SCALING PASS — from the client's side-by-side screenshots.
   Root cause fixed here: the previous pass used fixed pixel caps measured
   at 1206px, so at wider laptops the columns grew while content did not,
   creating the stretched, empty look. Everything below is proportional to
   the viewport, matching the live site's ratios at ANY width:
     left col ~22.5vw | gap ~4.5vw | center ~40vw | gap ~3.5vw | right
     books ~7.6vw tall, jesus ~5.5vw, nav/logo at live scale,
     search = icon only until clicked (live behaviour).
   Later rules override the earlier fixed-cap block; nothing else changed.
   ===================================================================== */

/* header at live scale */
.site-header { padding: 10px clamp(18px, 3vw, 44px); }
/* The logo artwork is square, so it is sized by HEIGHT and allowed to find
   its own width. Sizing it by width previously let it grow taller than the
   header and clip. */
.brand img, .brand-fallback {
  height: var(--logo-size); width: auto;
  max-height: calc(var(--site-header-base) - 20px);
  object-fit: contain;
}
.site-nav { gap: clamp(14px, 1.8vw, 30px); }
.nav-link { font-size: clamp(14px, 1.15vw, 17.5px); }
.site-nav .btn { font-size: clamp(13.5px, 1.05vw, 16.5px); padding: 11px 22px; }

/* search: icon only until opened (matches live) */
.global-search .search-input,
.global-search .search-results { display: none; }
.global-search.open .search-input { display: block; }
.global-search.open .search-results:not([hidden]) { display: block; }
.search-btn {
  width: 46px; height: 46px; border-radius: 10px;
  border: 1px solid #d9c8b0; background: #fff;
}

/* homepage columns: proportional, close together like live.

   SCOPED TO DESKTOP, AND THAT SCOPING IS THE FIX, NOT A TWEAK.
   This declaration sits ~50 lines BELOW the two media queries that were meant
   to collapse the room — `minmax(240px,292px) 1fr` at <=1010px and `1fr` at
   <=760px — and carries the same specificity. Later and equal wins, so it beat
   both of them at EVERY width and the homepage stayed three columns down to
   320px, where they measured 72px / 142px / 106px. That is why the shelves
   showed slivers of books with unreadable spines and the panels were squeezed
   into ~140px: the mobile layout existed, was correct, and was never applied.

   Exactly the failure mode the CSS notes call out — a later unconditional rule
   silently discarding an earlier conditional one. A rule that describes the
   desktop room now says so, and the two collapse rules below it can do their
   job. Desktop is unchanged: >=1011px still resolves to 22.5% / 44.5% / 1fr. */
@media (min-width: 1011px) {
  .page-home .room-body {
    grid-template-columns: 22.5% 44.5% 1fr;
    column-gap: 0;
  }
}
/* THE ASYMMETRIC COLUMN PADDING BELONGS TO THE MULTI-COLUMN ROOM.
   `… 30px 0 12px 16px` — padding on the LEFT, none on the right — is correct
   while the columns sit side by side: each is separated from the one before it
   and the room's edge supplies the rest. Stacked into one column on a phone it
   inset every panel on the left and left it flush against the right edge,
   which is the "cut off near the edges" reported for the Quote and Poem frames.

   Scoped, not overridden. It is the SECOND declaration in this file to sit
   below the mobile rules with equal specificity and quietly beat them — the
   grid-template-columns above was the first. A rule that describes the
   multi-column room now only applies where there is one. */
@media (min-width: 761px) {
  .page-home .room-col { padding: clamp(16px, 2vw, 30px) 0 12px clamp(8px, 1vw, 16px); }
}
.page-home .daily-panel { max-width: none; width: 100%; }

/* left frames scale with the column */
.page-home .daily-frame { padding: clamp(9px, 1vw, 15px); }
.page-home .frame-inner { padding: clamp(12px, 1.4vw, 22px); }
.page-home .daily-section h3 { font-size: clamp(13px, 1.1vw, 17px); }
/* TYPOGRAPHY FLOOR: this is body copy on the homepage, not a caption, so the
   clamp floor rises to 12px. The upper bound is untouched, so desktop is
   unchanged and only the narrowest viewports move. */
.page-home .daily-excerpt { font-size: clamp(12px, .95vw, 14.5px); }

/* center: fills its 40% column, offset like live (gap ~4.5vw) */
.page-home .room-windows-col { padding-left: clamp(30px, 4.7vw, 74px); }
.page-home .room-windows { max-width: none; width: 100%; gap: clamp(10px, 1.2vw, 20px); }
.page-home .window-frame { padding: clamp(8px, .9vw, 14px); }

.page-home .shelf-board { min-height: clamp(8px, .8vw, 13px); }
.page-home .board-fallback { height: clamp(8px, .8vw, 13px); }

/* =====================================================================
   Inner / category pages: same proportional treatment (live ratios)
   ===================================================================== */

/* left decoratives: arched glasses ~10.5vw, window scene ~21vw */

/* center column: stained pair + map fill the column */

/* right: jesus constrained, maroon heading (as live), big covers */

/* =====================================================================
   MOBILE HEADER — ONE ROW, AND A HEIGHT THAT MATCHES IT
   THE BUG THIS BLOCK EXISTS FOR: .site-header has a FIXED
   height: var(--site-header-h), and the earlier mobile block gave it
   flex-wrap: wrap with .global-search at order: 3; flex: 1 1 100%. The search
   therefore wrapped onto a second row that the header had no height to hold.
   Measured at 390x844: header 0..124px, search row 112..158px — 34px of it
   hanging outside the header, landing on whatever followed. On the reader that
   is the toolbar, which is the header/search/toolbar overlap reported from a
   real device. It was never a z-index problem.

   The fix is to stop wrapping, not to grow the header. Desktop already shows
   the search as an icon that expands when tapped — home.js toggles .open on
   .global-search and closes it on an outside click, site-wide, today. Mobile
   was the odd one out in forcing the input permanently open, and that is the
   only reason it needed a row of its own. Using the same behaviour on a phone
   costs nothing, adds no JavaScript, and lets the whole header be one row.

   That in turn allows the height to come down from 124px to 68px — 22% of a
   320x568 phone's viewport reclaimed on EVERY page. --site-header-h is the
   single source of truth the reader room, the reader overlay and the embedded
   frame all offset by, so they follow automatically.

   THE LANDSCAPE TERM: this block must match the header block above it exactly,
   or a landscape phone gets the hamburger and the one-row layout while keeping
   the 124px height they were sized to replace. The two blocks are one rule
   split only by where they sit in this file.

   DESKTOP IS UNTOUCHED: every pinned desktop viewport (1024x768, 1280x800,
   1440x900) is taller than 520px and matches neither term. A desktop window
   deliberately dragged below 520px tall does now take the mobile header — at
   that height 124px is 24% of the viewport, so that is the better outcome, and
   it is the same trade the reader already makes.
   ===================================================================== */
@media (max-width: 760px), (max-height: 520px) and (orientation: landscape){
  :root {
    /* The BASE moves; --site-header-h stays the calc above, so the bar is
       still added on top at this breakpoint too. */
    --site-header-base: 68px;
    --logo-size: 56px;
  }

  /* THE LOGO GROWS; THE HEADER DOES NOT.
     .site-header has a FIXED height and .brand is absolutely centred inside
     it, so the logo is out of flow and its size cannot change the header's
     height — the reader's vertical budget is untouched by this. The only thing
     holding it at 46px was the shared `max-height: header - 20px` cap, which
     is sized for a logo sitting IN the flow beside the controls. Centred and
     out of flow it only needs to clear the header's edges, so the cap is
     relaxed to leave 4px top and bottom.

     Side effect worth having: the brand link was 43x46, under the 44px touch
     floor and counted in the responsive audit. At 56px square it clears it. */
  .brand img, .brand-fallback { max-height: calc(var(--site-header-base) - 8px); }

  /* Nothing wraps, so the fixed height is honoured and nothing escapes it. */
  .site-header { flex-wrap: nowrap; gap: 10px; }

  /* Back in the row, at its natural width, exactly as on desktop. */
  .global-search {
    order: 0; flex: 0 0 auto; width: auto; min-width: 0; margin-top: 0;
  }
  /* Opened, it takes the space the collapsed nav is no longer using. */
  .global-search.open { flex: 1 1 auto; }
  .global-search.open .search-input { width: 100%; min-width: 0; }

  /* .search-btn carries margin-right: 8px to separate it from the input. With
     the input closed there is nothing to separate, and that margin left the
     visible button 8px short of the header's padding edge while its container
     sat flush — the header read as slightly misaligned on the right. Restored
     the moment the search opens. */
  .global-search:not(.open) .search-btn { margin-right: 0; }

  /* SEARCH LEFT, LOGO CENTRED, MENU RIGHT.
     The brand is centred ABSOLUTELY, not with flex maths: true centre of the
     header regardless of how the side controls differ in width. When the
     search opens it expands over the logo; the input is opaque, so the logo
     simply gives way to it — no second layout, no JS. */
  .site-header { justify-content: space-between; }
  .brand {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
  }
  .global-search { order: -1; }
  .global-search.open { z-index: 2; }
  .global-search.open .search-input { position: relative; z-index: 2; }
  .nav-toggle { margin-left: 0; }

  /* 38x32 was under the 44px touch floor on every page of the site. The bars
     are unchanged; only the box around them grows. */
  .nav-toggle {
    width: 44px; height: 44px; padding: 0;
    align-items: center; justify-content: center;
  }
}

/* =====================================================================
   Refinement round (client items 1-4 homepage, 1-2 inner pages)
   ===================================================================== */

/* HOME 1+4: natural floor — warm wood with a gentle curved transition
   into the room instead of flat blocks */

/* HOME 2+4: taller centre window so the unit's bottom lines up with the
   second left frame; church scene fills more of the column */

.page-home .book { justify-content: center; }
.page-home .book .cover-face {
  height: clamp(84px, 7.6vw, 128px);
  width: auto; max-width: 100%; object-fit: contain; object-position: bottom;
}
.page-home .book img.img-missing + .book-spine-fallback { max-width: 100%; }

/* INNER 1: the two arched glasses sit side by side, window scene below */

/* =====================================================================
   SAME-PAGE BOOK EXPERIENCE — the reader opens over this page in a
   transparent frame; the room blurs, nothing navigates.
   ===================================================================== */
body.book-open-mode { overflow: hidden; }
/* THE LOCK MUST ALSO HOLD THE ROOT, OR iOS SCROLLS ANYWAY.
   The body rule above is the lock this site has always had, and on desktop
   and Android it is what makes the reader feel pinned. iOS Safari is the
   documented exception: overflow:hidden on BODY does not stop touch scrolling
   of the viewport there — which is exactly the report: "Android locked,
   iPhone always scrolls". Locking the root as well is the standards-based
   completion of the existing mechanism: same class, no scripts, no touch
   handlers, and pages without book-open-mode are untouched.
   overscroll-behavior stops rubber-band chaining, which is the "sometimes it
   scrolls" on Android. Engines without :has() simply keep today's behaviour. */
html:has(body.book-open-mode) { overflow: hidden; overscroll-behavior: none; }

/* ============ THE JOLT AT THE START OF THE OPENING FLIGHT ============
   MEASURED CAUSE, not a taste adjustment. shelf.js reads the clicked cover's
   box with getBoundingClientRect() and computes the flight's destination from
   window.innerWidth — and THEN adds `book-open-mode`, which is the rule above.
   Removing the scrollbar widens the viewport by its own width (~15px on this
   project's desktop target; the reader's own editor measures the same gutter),
   so the entire room re-lays out half that distance to the right one frame
   after the origin and destination boxes were measured. The clone therefore
   starts from a slot that has just moved and flies to a centre computed for
   the narrower viewport. That is the visible hitch as a book lifts off.

   Reserving the gutter permanently means locking scroll changes nothing about
   the layout, so the two measurements stay true. One declaration, no script,
   and it fixes every scroll lock in the application at once rather than the
   shelf alone. Engines without scrollbar-gutter keep exactly today's
   behaviour, and overlay-scrollbar platforms (iOS, Android) never had the
   shift because their scrollbars take no space. */
html { scrollbar-gutter: stable; }

/* SMOOTHNESS OF THE OPENING BLUR.
   Animating filter: blur() re-rasterises the whole subtree every frame, and
   .library-room holds the window art, three shelves and every cover — which is
   what made the background feel stiff rather than fluid while a book opened.

   Two changes, neither of them architectural:
   1. The room is promoted to its own compositor layer (translateZ + backface)
      so the blur rasterises once per frame on the GPU instead of on the main
      thread alongside the cover's flight.
   2. The blur, the scrim and the cover flight now share ONE easing curve and
      start together, with the blur finishing a little ahead of the cover so
      the background has settled by the time the book lands. Three different
      durations on three different curves was the "stiffness". */
.library-room {
  transition: filter .42s cubic-bezier(.4, .05, .3, 1);
  transform: translateZ(0);
  backface-visibility: hidden;
}
body.book-open-mode .library-room {
  filter: blur(6px) brightness(.82);
  transition: filter .42s cubic-bezier(.4, .05, .3, 1);
  will-change: filter;
  pointer-events: none;
}

/* The scrim and the reader frame both start BELOW the sticky header, so the
   header stays legible and clickable while a book is open. The room behind
   still blurs in full — only the reader layer is inset. */
.reader-overlay {
  position: fixed; inset: var(--site-header-h) 0 0 0; z-index: 90;
  background: rgba(40, 26, 14, .34);
  opacity: 0; transition: opacity .42s cubic-bezier(.4, .05, .3, 1);
  pointer-events: none;
  will-change: opacity;
}
.reader-overlay.show { opacity: 1; }

/* ============ THE WAIT THAT NOTHING USED TO COVER ============
   MEASURED GAP, not a precaution. The sequence when a book is opened is:
   flight (500ms, navigation dispatched at 410) -> the browser holds THIS
   document's last painted frame until the reader is ready to commit -> reader
   paints. That middle step is server render plus network, it is unbounded on
   shared hosting, and until now the frame being held showed a blurred room and
   a motionless cover with NOTHING to say a book was on its way. The
   destination cannot cover it — the destination has not started painting — so
   it has to be covered from here.

   A CSS animation on the outgoing document keeps running while that frame is
   held, which is what makes this work without a line of JavaScript.

   DELAYED PAST THE FLIGHT ON PURPOSE. At .42s it appears just as the cover
   lands, so the flight is never competing with a spinner; a book that opens
   promptly shows one for a moment or not at all. Deliberately the same 28px
   ring, the same colours and the same opaque plate as the reader's own boot
   loader in mw.css, so when the reader does paint, the indicator does not
   appear to move or change — it simply continues. Keep the two in step. */
/* ============ THERE IS EXACTLY ONE LOADING RING, AND IT IS THE READER'S ============
   REMOVED. This overlay used to draw a second ring while the browser held this
   document's last frame. Three rounds were spent trying to make two rings in
   two documents read as one — matching their size, then their colours, then
   their position, then their timing — and the operator still saw "a loader,
   then it disappears while I'm looking at the blurred room, then another
   loader". The honest conclusion is that the premise was wrong: two elements in
   two documents cannot be made continuous, because the gap between them is a
   navigation and nothing can paint across it.

   So there is one. `body.reader-booting .book-stage::after` in mw.css appears
   with the reader's first paint and stays until `state.paginated`. Nothing
   before it, nothing after it.

   THE HELD FRAME IS NOT EMPTY, which is what makes this safe. The flight now
   runs to completion (see shelf.js) and lands the cover on the exact box the
   reader will draw its closed book in — verified 0.0px at 1440x900 and
   1280x800. So during the wait the visitor is looking at the book's cover
   sitting where the book is about to be, not at a bare room. Adding a spinner
   on top of that was what created the second loading stage.

   `is-waiting` is left on the element in shelf.js deliberately: it still marks
   the OPENING overlay apart from the RETURN one, and re-adding a ring here
   without that distinction would put a spinner back into the close animation.
   Gate 59 asserts this overlay draws nothing at all. */

/* ============ THE HESITATION WHEN A BOOK OPENS, ROOT-CAUSED ============
   This transitioned `left`, `top`, `width` and `height`. All four are LAYOUT
   properties: the browser re-runs layout and paint for the element on every
   frame of the animation, on the main thread — and it did so for 500ms while
   `.library-room` was simultaneously animating `filter: blur(6px)` across the
   window art, three shelves and every cover. Two expensive main-thread
   animations at once is the stutter that was reported between pages and books,
   and `will-change: left, top, width, height` could not help, because there is
   no compositor fast path for those properties to be promoted to.

   The flight is now a TRANSFORM. shelf.js sizes the clone at its DESTINATION
   box once, then applies the inverse translate+scale that puts it back over its
   origin, then transitions to identity — the standard FLIP inversion. Nothing
   about the movement the user sees changes: same box, same easing, same 500ms.
   The difference is that transform and opacity are composited, so the whole
   flight runs off the main thread and the blur has it to itself.

   `transform-origin: 0 0` is load-bearing: the inverse translation is computed
   from the top-left corner, so a centred origin would offset the scale. */
.take-clone {
  position: fixed; z-index: 95; object-fit: contain;
  filter: drop-shadow(0 24px 48px rgba(30, 16, 4, .55));
  transform-origin: 0 0;
  /* .5s, down from .58s. The flight is what the page waits on before it
     navigates, so every millisecond here is a millisecond of background
     transition the reader watches before the book appears — the delay reported
     on static guest books. Still long enough to read as a glide. */
  transition: transform .5s cubic-bezier(.4,.05,.3,1);
  will-change: transform;
  pointer-events: none; border-radius: 4px;
}

@media (prefers-reduced-motion: reduce){
  .take-clone, .reader-overlay, .library-room, body.book-open-mode .library-room { transition: none !important; }
}

/* embedded reader: transparent page so the blurred shelf shows through.
   Inside the frame the viewport is already header-less, so the room fills
   it exactly — no header offset is subtracted twice. */

/* =====================================================================
   FLOOR - light honey oak, laid in perspective and curving into the room.
   The reference shows boards that fan outward toward the viewer and a soft
   elliptical falloff at the corners rather than a hard rectangle, so the
   floor is built from a perspective-scaled layer inside a masked block.
   Applies to the homepage and every inner page.
   ===================================================================== */
.room-floor {
  position: relative;
  height: clamp(96px, 11vw, 168px);
  overflow: hidden;
  background: linear-gradient(180deg, #c89c66 0%, #d5ab73 55%, #dcb782 100%);
  /* Corners ease away so the floor reads as a room, not a printed band. */
  -webkit-mask-image: radial-gradient(128% 118% at 50% 6%, #000 62%, rgba(0,0,0,.55) 84%, rgba(0,0,0,0) 100%);
          mask-image: radial-gradient(128% 118% at 50% 6%, #000 62%, rgba(0,0,0,.55) 84%, rgba(0,0,0,0) 100%);
  box-shadow: inset 0 9px 16px rgba(122, 86, 46, .26);
}

/* The boards themselves, tilted in 3D so they converge toward the wall.
   Oversized and offset upward so the near edge fills the full width. */
.room-floor::before {
  content: ''; position: absolute;
  left: -32%; right: -32%; top: -84%; bottom: -30%;
  transform: perspective(320px) rotateX(58deg);
  transform-origin: 50% 0;
  background:
    /* plank end joints */
    repeating-linear-gradient(90deg,
      transparent 0 148px, rgba(140, 100, 54, .42) 148px 150px),
    /* long board seams */
    repeating-linear-gradient(180deg,
      rgba(146, 108, 60, .34) 0 1.5px, transparent 1.5px 34px),
    /* alternating board tone so planks read individually */
    repeating-linear-gradient(180deg,
      rgba(255, 236, 205, .10) 0 34px,
      rgba(150, 108, 58, .07) 34px 68px),
    linear-gradient(180deg, #bd9160 0%, #cda36c 46%, #dcb782 100%);
  pointer-events: none;
}

/* Contact shadow where the wall meets the floor, plus a gentle sheen. */
.room-floor::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(96, 64, 30, .40) 0,
      rgba(96, 64, 30, .12) 9%,
      rgba(0,0,0,0) 24%),
    radial-gradient(78% 128% at 50% 104%,
      rgba(255, 246, 226, .20), rgba(255, 246, 226, 0) 66%);
}

/* When a live-site decorative image was never downloaded, the template omits
   the <img> altogether (no 404). The adjacent-sibling fallback rules cannot
   match in that case, so the CSS-drawn plank and light cones are shown
   whenever they are the only child present. */
.shelf-board > .board-fallback:first-child { display: block; }

/* Narrow screens: the enlarged shelf sizes would overflow, so both page
   types step back down. Declared last so it wins. */
@media (max-width: 760px){
  .page-home .book .cover-face,
  .page-home .book img.img-missing + .book-spine-fallback { height: clamp(88px, 20vw, 112px); }
  .page-home .shelf-books { gap: -3px; flex-wrap: wrap; row-gap: 10px; }
  .room-floor { height: clamp(72px, 18vw, 96px); }
}

/* =====================================================================
   HOMEPAGE SHELF BOOKS - same behaviour as the inner/category pages.

   The homepage shelves hold 6 and 7 books; the inner pages chunk at 6.
   Sizing by viewport width alone therefore overflowed the 7-book shelf at
   every common laptop width. Instead each book takes a SHARE OF THE ROW
   (flex-basis in %), so the row can never be wider than its container no
   matter how many books it holds. The cover fills that share by height and
   keeps its own aspect ratio.
   ===================================================================== */
.page-home .shelf-books {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  justify-content: center;
  gap: 0;
  max-width: 100%;
}
.page-home .book {
  /* Each book claims an equal SHARE of the row (not a pixel width), so a
     7- or 8-book shelf simply gives each book a slightly smaller share than
     a 6-book one instead of pushing past the shelf edge. The negative
     margin below overlaps them, which buys back the width the covers need
     to stay large. */
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  display: block;
  position: relative;
}
/* OVERLAP RULE REMOVED (was: .page-home .shelf-books > .book + .book
   { margin-left: -12% }). Its comment claimed the percentage was "of the
   book's own width" — percentage margins actually resolve against the
   CONTAINING BLOCK's width, so on a ~364px shelf row this was ~-44px:
   nearly half of a 98px cover. That produced the books-glued-together
   look, and it put the row's wrap threshold within a few pixels of the
   available width, so any tiny width difference (a scrollbar's worth)
   flipped a book onto a second row — which is exactly why the reader's
   blurred backdrop laid out differently from the live page. The intended
   gentle lean lives in mw.css (.shelf-books .book + .book, -3px), which
   this rule was silently out-ranking. */

.page-home .book .cover-face,
.page-home .book img.img-missing + .book-spine-fallback {
  /* Width-driven: the cover fills its share of the row and takes its height
     from the artwork's own aspect ratio, so it can never overflow. The
     height clamp is only an upper bound for very wide screens. */
  width: 100%;
  height: auto;
  max-height: clamp(96px, 8.6vw, 158px);
  object-fit: contain;
  object-position: bottom;
  border-radius: 0;
  box-shadow: 2px 4px 9px rgba(40, 24, 8, .46);
}
.page-home .book img.img-missing + .book-spine-fallback {
  width: 100%; height: clamp(84px, 7.4vw, 128px);
}

/* Inner pages use the identical mechanism, so both page types behave the
   same whether a shelf holds 6, 7 or 8 books. */
/* -12% overlap removed here too — same containing-block percentage problem
   as the .page-home copy above; the -3px lean in mw.css now applies. */

/* Overlap requires explicit layering. */
.page-home .shelf-books > .book:hover { z-index: 30; }
.page-home .book-tooltip { z-index: 40; }

/* =====================================================================
   HOMEPAGE SHELF WIDTH RECLAIM
   The homepage shelf column carried a large asymmetric left padding
   (up to 54px) plus 1vw on the right, leaving ~70px less usable width
   than the inner pages. That deficit — not the book rules — is why the
   homepage covers rendered smaller: flex shrink was compensating.
   Trimming the padding gives the books room WITHOUT widening the shelf
   or the column itself.
   ===================================================================== */
.page-home .shelf-wall-col {
  padding: clamp(10px, 1.2vw, 18px) clamp(6px, .6vw, 10px) 0 clamp(8px, .9vw, 14px);
}
.page-home .shelf-books { padding: 0 2px; }

/* =====================================================================
   INNER PAGE DENSITY (restored)
   The decorative light cones under each inner-page shelf reserved up to
   72px of vertical space per shelf, pushing three-shelf sections well
   past the fold. They are removed on category pages only — the homepage
   keeps them, since that is the hero view. Spacing above and between
   shelves is tightened to match, and the Jesus artwork rides up with it.
   Nothing about the shelf or book sizing changes here.
   ===================================================================== */

/* =====================================================================
   HORIZONTAL OVERFLOW - belt and braces.
   The page must never scroll or drag sideways. Guards are applied at
   three levels so a single missed element cannot stretch the document:
   the document itself, the page shell, and each shelf row (whose
   negative gaps and hover transforms intentionally exceed their box).
   ===================================================================== */
.library-room,
.reader-room,
.site-header,
.site-footer { max-width: 100%; }

/* Shelf rows are NOT clipped: the hover lift and the book tooltip (which is
   nowrap and centred, so it extends sideways on the end books) must be able
   to escape the row. The document-level guard above already prevents any of
   that from stretching the page, so clipping here would only cost the
   tooltip with no benefit. */
.shelf-books { max-width: 100%; }
