/* Blanket [hidden] rule up front -- see Ceri's app-creator.css/admin.css for
   the exact bug this prevents: an author `display:` rule on the same
   element at equal specificity to the browser's built-in
   `[hidden] { display: none }` wins the cascade tie, so anything given its
   own `display: flex/grid/block` below MUST be re-hidden through this rule
   rather than relying on the attribute alone. */
[hidden] { display: none !important; }

* { box-sizing: border-box; }

:root {
  --bg: #1f1720;
  --panel: #2a1f2b;
  --page: #241a26;
  --accent: #7b2d58;
  --accent-bright: #c9557f;
  --text: #f6eef4;
  --muted: #c9b8c6;
  --border: rgba(246, 238, 244, 0.12);
}

html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  overflow: hidden;
}

.book-app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

.book-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 14px 16px 8px;
  flex-wrap: wrap;
}
.book-header .eyebrow {
  margin: 0;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-bright);
}
.book-header h1 {
  margin: 0;
  font-size: 1.15rem;
  flex: 1 1 auto;
}
/* Only ever shown to a verified host previewing before closure/purchase --
   see book.js's load(). Deliberately understated (no red/warning color):
   this isn't an error state, it's expected/normal for a host checking in
   mid-event. */
.host-preview-banner {
  margin: 0 16px 8px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  font-size: 0.8rem;
}
.filters-toggle {
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 0.85rem;
  cursor: pointer;
}
.filters-toggle[aria-expanded="true"] { background: var(--accent); }
/* Kerry: "that is supposed to be identical to the one in the keepsake
   gallery that is labeled Open Keepsake App and has a gold border that
   highlights on mouseover." The real button to match lives in the Keepsake
   export's own couple-gallery.html, generated inline by
   server/src/Services/ReplayExportService.php (search that file for
   keepsakeLink/.keepsake-link) -- values below (border color, background,
   hover state) are copied from its own inline <style> block verbatim,
   not re-guessed:
     .keepsake-link{border:2px solid #f7d788;border-radius:999px;
       background:#7b2d58;color:#fff;text-decoration:none;font-weight:900}
     .keepsake-link:hover,.keepsake-link:focus-visible{
       background:#96396c;outline:3px solid #fff;outline-offset:3px}
   (that page's own margin-top:18px/padding:12px 20px were sized for a
   standalone block below a page <h1>, not a button inline in a flex
   header row next to Filter -- padding kept close but not copied
   verbatim, everything about the button's own COLOR/border/hover is.)
   [hidden] is handled by the blanket rule at the top of this file, so no
   separate display override is needed here. */
.keepsake-link {
  display: inline-flex;
  align-items: center;
  border: 2px solid #f7d788;
  border-radius: 999px;
  padding: 6px 16px;
  background: #7b2d58;
  color: #fff;
  font-weight: 900;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
}
.keepsake-link:hover, .keepsake-link:focus-visible {
  background: #96396c;
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.filters-panel {
  padding: 10px 16px 14px;
  border-bottom: 1px solid var(--border);
}
.filters-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.filter-chip {
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 0.78rem;
  cursor: pointer;
}
.filter-chip[aria-pressed="true"] {
  background: var(--accent);
  color: var(--text);
  border-color: var(--accent-bright);
}
.filters-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  font-size: 0.8rem;
  color: var(--muted);
}
.filters-row label { display: flex; flex-direction: column; gap: 3px; }
.filters-row input, .filters-row select {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 6px 8px;
  font-size: 0.85rem;
}
.clear-filters {
  border: none;
  background: none;
  color: var(--accent-bright);
  cursor: pointer;
  font-size: 0.8rem;
  align-self: flex-end;
  padding: 6px 0;
}
.filter-summary { margin: 8px 0 0; font-size: 0.75rem; color: var(--muted); }

.book-stage {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 8px 8px 0;
}
.book-state {
  text-align: center;
  color: var(--muted);
  max-width: 32em;
  padding: 24px;
  font-size: 0.95rem;
  line-height: 1.5;
}

/* Kerry's three real book-photo backgrounds (background removed from each,
   see apps/book/images/book-cover-front.png / book-spread.png /
   book-cover-back.png) replace the single unified book-background.png this
   used to be built from -- a closed book has a genuinely different shape
   (portrait, one cover) than an open one (landscape, two pages), so one
   image could never really represent both. Which of the three is showing is
   driven entirely by data-book-view on this element (set by book.js's
   applyBookView(), called on load and every page flip): "cover-front" for
   the front cover, "cover-back" for the closing page, "spread" for every
   content page in between (see book.js's initPageFlip() -- usePortrait is
   now false, so content always renders as a two-page spread matching this
   artwork, never StPageFlip's own single-page-on-mobile fallback). Each
   state sets both aspect-ratio (the real width:height of that PNG, after
   background-removal cropping) and background-image, so .book-viewport's
   own shape genuinely changes between a narrow closed cover and a wide open
   spread as the reader flips through -- max-width/max-height + margin:auto
   (unchanged) keep it "contain"-fit and centered within .book-stage
   regardless of which shape is active or the device's own orientation.
   cover-front's numbers double as the default/fallback (applied before
   book.js's first applyBookView() call sets the real state on load).
   position: absolute + inset: 0 + margin: auto (rather than being a normal
   flex item of .book-stage, which is what this used to be) is deliberate:
   Kerry hit a real bug where the whole book rendered at roughly a quarter
   of the expected size, and StPageFlip's own inline styles on the page it
   rendered showed it had clamped to this app's configured minHeight/
   minWidth fallback almost exactly -- meaning it measured #pageSpread's
   real available box as far smaller than intended. Root cause: as a FLEX
   ITEM with no explicit width/height, "aspect-ratio" has no definite
   dimension to anchor to under flexbox's own sizing algorithm (flex items
   default to a content-based/fit-content size for width:auto, unlike
   normal block-flow children, which default to filling their container's
   width) -- with no real content of its own (its only children are
   position:absolute, which don't contribute to normal-flow sizing), this
   collapsed toward zero, and StPageFlip's own fallback numbers are what
   actually ended up on screen. position:absolute + inset:0 + margin:auto
   sidesteps flex sizing entirely -- this is the well-established, more
   robust pattern for "an aspect-ratio-locked box, centered, sized as large
   as possible within its container" specifically because an absolutely
   positioned box's sizing against its containing block's inset edges is a
   separate, more predictable algorithm than flex-item auto-sizing.
   .book-stage's own position:relative (unchanged, already there) is what
   makes it the containing block .book-viewport's inset:0 resolves against;
   .book-stage's display:flex/align-items/justify-content rules still
   center its OTHER children (the #bookLoading/#bookLocked/#bookEmpty
   states) exactly as before -- position:absolute takes .book-viewport out
   of that flow entirely, unaffected by them either way. */
.book-viewport {
  position: absolute;
  inset: 0;
  /* 95%, not 100% -- Kerry's explicit spec: the book should fill the
     viewable screen to 95%, leaving a small breathing margin around it
     rather than running edge to edge within .book-stage. */
  max-width: 95%;
  max-height: 95%;
  margin: auto;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  aspect-ratio: 688 / 900;
  background-image: url('images/book-cover-front.png');
}
.book-viewport[data-book-view="cover-back"] {
  aspect-ratio: 687 / 900;
  background-image: url('images/book-cover-back.png');
}
/* Kerry's second open-book photo (a cleaner, untilted, symmetric spread,
   dark teal/navy binder) replaces the first one -- same background-removal
   treatment (convex hull of the binder's own distinctly-blue pixels, since
   this photo's page-top edges also fade straight into its own soft gray
   vignette background with no drawn boundary, same issue as the first
   spread image), cropped to its opaque bounding box and resized to the
   same shared 900px height as the two cover images (Kerry: "scale them to
   this height," i.e. the covers -- already 900px tall from the original
   background-removal pass, so no change was needed on that side). Real
   aspect ratio 1394:900 (this photo reads notably wider/flatter than the
   first spread, 1250:900, since it has almost no tilt/perspective and a
   thinner binder). */
.book-viewport[data-book-view="spread"] {
  aspect-ratio: 1394 / 900;
  background-image: url('images/book-spread.png');
}
/* .page-zone (the old click-to-flip edge buttons) removed -- Kerry:
   "clicking should be reserved for zooming the image out and in... a
   click also changes the page if near the border, that is confusing."
   Those buttons overlapped the outer ~18% of each page, competing with
   the tiles underneath for clicks; book.js no longer creates them at all,
   and #bookViewport's HTML (index.html) no longer includes them. Page
   turning is swipe or the two footer arrow buttons only now. */

/* #pageSpread is the one element StPageFlip actually measures/renders into
   (see its own comment below) throughout the whole book -- cover pages and
   content spreads alike -- so, like .book-viewport above, its inset from
   the edges has to change per data-book-view too, to land inside whichever
   background's own drawn "page" area is currently showing. Each set of
   percentages was measured directly against that state's own PNG (the
   light gray/white cover surface, clear of the spine-edge gradient strip
   and outer border for the two cover states; the white interior of both
   open pages, clear of the binder frames on both sides and the bottom
   spine-crease shadow, for the spread state) -- see the deploy manifest
   entry for this change for the measurement approach. top/bottom use
   position offsets rather than padding for the same reason as before: CSS
   resolves ALL FOUR padding/margin percentages against the containing
   block's WIDTH, never its height, so position: absolute + explicit
   top/bottom/left/right is what makes each percentage resolve against the
   correct axis. cover-front's numbers are the default, applying before
   book.js's first data-book-view is set and reused as-is for cover-front
   itself; cover-back mirrors them (spine strip is on the right there
   instead of the left); spread is its own, close-to-symmetric set with a
   small center gutter left for the crease. */
/* IMPORTANT, read before touching these numbers again: don't try to solve
   cover/spread centering by tuning this box's own left/right/top/bottom.
   A prior attempt at exactly that (doubling this box's width so the HALF
   PageFlip actually draws a solo cover into would land on the real target
   area) looked right on paper from calculateBoundsRect() alone, but broke
   in practice -- Kerry saw it shift size/position, and drift again ~100ms
   after landing. Root cause, found by tracing further into
   HTMLPage.simpleDraw() (not just calculateBoundsRect()): a page's actual
   rendered width is calculateBoundsRect()'s `pageWidth`, which is
   `getBlockWidth()/2` ONLY when unclamped -- it's also capped by this
   PageFlip instance's own maxWidth setting, and separately REDUCED further
   whenever height-derived sizing (pageWidth/height ratio, using this
   instance's single configured width:height, not any given page's real
   image aspect ratio) exceeds getBlockHeight(). Both of those depend on
   the container's pixel size and this instance's fixed config in ways that
   don't scale proportionally with this box's own CSS percentages -- so no
   fixed percentage here can reliably predict what PageFlip will actually
   render at a given screen size. That's also what the ~100ms drift was:
   applyBookView()'s own deferred pageFlip.update() call re-running this
   same math against the container's real (by-then-settled) size, landing
   somewhere this box's percentages never accounted for.
   Given that, this box's own insets are back to simple, reasonable
   estimates of each state's real visible page area (not a doubling hack) --
   book.js's correctBookLayout() is what actually guarantees correct
   final placement now, by measuring the REAL rendered box PageFlip ends up
   with (via getBoundingClientRect(), whatever clamping produced it) and
   correcting book.js's own .book-cover-inner/.book-page-inner wrappers
   (never touched by PageFlip -- see the comment on those below) with a
   CSS transform computed from that measurement, every time PageFlip's
   own layout could have changed. Measuring reality instead of predicting
   it is what makes this robust regardless of PageFlip's own clamps. */
.page-spread {
  position: absolute;
  left: 9%;
  right: 4%;
  top: 5%;
  bottom: 5%;
}
.book-viewport[data-book-view="cover-back"] .page-spread {
  left: 4%;
  right: 9%;
  top: 5%;
  bottom: 5%;
}
.book-viewport[data-book-view="spread"] .page-spread {
  /* Re-measured against the new, untilted book-spread.png: its binder
     edges sit at a consistent ~2.5% from each side (no tilt/perspective
     variance across rows, unlike the first spread photo), page top/bottom
     margins ~1.5% -- 3%/3% leaves a safe margin clear of both. */
  left: 3.5%;
  right: 3.3%;
  top: 3%;
  bottom: 3%;
  /* No padding/gap/display rules here -- StPageFlip takes this element over
     directly (adds its own .stf__parent class + inline sizing styles, and
     builds .stf__wrapper/.stf__block/page-element children inside it), so
     anything book.css declares here only risks fighting the library's own
     box-model math rather than actually controlling layout -- the inset
     above is what actually sizes/positions the box StPageFlip measures. See
     .book-page-inner/.book-cover-inner below for the per-page styling that
     used to live directly on .book-page/.book-cover. correctBookLayout()
     in book.js handles the exact final left/right placement and the gap
     between the two page-grids -- these insets just need to be "reasonably
     close" so PageFlip's own pre-correction render isn't wildly off. */
}
/* .book-page/.book-cover are the actual elements handed to StPageFlip's
   loadFromHTML()/updateFromHtml() -- the library overwrites their inline
   `style` wholesale every frame while flipping (position/size/transform/
   clip-path/display -- see vendor/page-flip.js's HTMLPage.draw()), so any
   layout this app wants for a page's own content (flex column, the internal
   grid layout) has to live one level down on a plain child wrapper instead,
   or it'd get silently clobbered. Both wrappers just fill whatever box the
   library sized their parent to. Deliberately transparent with no border/
   shadow/rounded corners now (unlike the old solid-var(--page) card look)
   -- the "page" is the real book artwork sitting behind this element via
   .book-viewport's own background-image, so this just needs to host the
   interactive content exactly where that artwork's own drawn page already
   is, not draw a second page shape on top of it. */
/* width/height: 100% is this wrapper's OWN starting size (100% of whatever
   box PageFlip assigned its --left/--right/hard-density parent) -- book.js's
   correctBookLayout() then applies its own inline `transform` directly to
   this element after every PageFlip render that could have changed that
   parent box, to make its real on-screen box match an exact target derived
   from .book-viewport's own real (CSS-controlled, always predictable)
   size: for a cover (.book-cover-inner), translate + uniform scale,
   centered and scaled to fill .book-viewport; for a spread page
   (.book-page-inner), translate + independent scaleX/scaleY to hit an
   exact width and height computed from .book-viewport's own dimensions and
   a couple of fixed margins -- see correctBookLayout() in book.js for the
   full formula. transform-origin is set inline by that function too
   (varies by which correction is running), so none is set here. */
.book-page-inner, .book-cover-inner {
  width: 100%;
  height: 100%;
  background: transparent;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Kerry: "sometimes as I swipe the components of the book are highlighted
   and stay highlighted even after I let go... they should never stay or
   even be highlighted with that blue filter over them" -- mobile WebKit/
   Chrome's default tap-highlight (and, separately, native text/image
   selection triggered by a drag gesture that starts over text or an image)
   is what that blue overlay is; useMouseEvents:false (see book.js) turned
   off PageFlip's own pointer handling, but never addressed the browser's
   own default tap/selection feedback on the actual DOM underneath, which
   the custom swipe handler in book.js drags right across. -webkit-tap-
   highlight-color isn't inherited by descendants in every browser, so this
   is applied via a wildcard across the whole viewport rather than relying
   on inheritance from one rule. */
.book-viewport, .book-viewport * {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}
.book-viewport .page-grid,
.book-viewport .cover-photo,
.book-viewport .book-page-inner,
.book-viewport .book-cover-inner {
  -webkit-user-select: none;
  user-select: none;
}
.empty-page-message {
  margin: auto;
  padding: 24px;
  text-align: center;
  color: var(--muted);
  font-size: 0.95rem;
}

/* 2x2 grid of up to ITEMS_PER_PAGE (4) items per page -- see book.js's
   buildPage()/buildPageTile(). Each tile is independently clickable to open
   the full-size lightbox, with its own compact caption underneath rather
   than one caption block for the whole page (a page can now mix items from
   different guests/categories/times). Short trailing pages get empty filler
   tiles (.page-tile-empty) instead of stretching the real tiles to fill the
   gap. */
.page-grid {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 6px;
  padding: 6px;
}
/* Kerry: "both of the 2x2 image arrays could be made wider or separated
   more between them" -- and in later rounds, that the left array still
   wasn't landing far enough from the fold, that a size-and-position fix
   based on PageFlip's own natural rendering "is not responsive" (only
   correct at one screen size), and that she wanted page size/position
   derived directly from the book's own known width/height instead. Padding/
   margin tuning here turned out to be exactly as unreliable as the cover
   centering was (see the .page-spread comment above) for the same reason:
   the two page elements' real rendered width/position depend on PageFlip's
   own clamped internal math, not on these percentages. book.js's
   correctBookLayout() now computes each page's exact target size/position
   directly from .book-viewport's own real (fully CSS-controlled, always
   predictable) box -- a fixed margin used 3x across its width (outer-left,
   center fold, outer-right) and a fixed margin used 2x across its height
   (top, bottom) -- and resizes/repositions .book-page-inner (below) with a
   CSS transform to hit that target exactly, at any screen size. See
   correctBookLayout()'s own comment for the full formula. No CSS padding/
   margin hack is needed here anymore. */
/* Kerry: "blurring the edge looks worse, I think I need to crop the top a
   few pixels" -- a soft backdrop-filter blur ring was tried first per her
   original "blur the book border" request, but a straight crop reads
   better in practice. clip-path trims a few px off just the top of each
   page's own tile grid (deepest complaint was specifically the top edge),
   without the layout-shifting side effects a negative margin would cause. */
.book-viewport[data-book-view="spread"] .page-grid {
  clip-path: inset(6px 0 0 0);
}
.page-tile {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(246, 238, 244, 0.04);
}
.page-tile-empty { background: transparent; }
.page-media {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  position: relative;
  cursor: zoom-in;
}
/* object-fit: contain, not cover -- Kerry: "none of your images show the
   entire image, they are cropped at the top and bottom again." cover fills
   the tile completely by cropping whatever doesn't fit the tile's own
   aspect ratio; contain shrinks the whole image to fit inside the tile
   instead, so nothing gets cut off (letterboxed against .page-media's own
   #000 background on a tile/photo aspect mismatch instead). */
.page-media img, .page-media video {
  max-width: 100%;
  max-height: 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.tile-caption {
  padding: 4px 6px 6px;
  flex: 0 0 auto;
}
.tile-caption .caption-category {
  display: block;
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-bright);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile-caption .caption-text {
  margin: 0;
  font-size: 0.7rem;
  /* Was var(--text) (near-white) back when every page had a solid dark
     var(--page) card behind it -- now the page itself is the real light
     gray/white book artwork, so caption text needs to be dark to stay
     readable against it. */
  color: #2a1f2b;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Front cover / closing page -- book.js's buildCoverPage(), marked
   data-density="hard" and always shown solo (never paired with a content
   page in a two-page landscape spread) via StPageFlip's own showCover
   setting -- see book.js's initPageFlip(). Kerry's spec: the cover photo
   sits in the upper half, centered, with the couple's names in a fancy
   cursive wedding-invite font below it -- both directly on the real cover
   artwork behind this element (see .book-viewport's background-image),
   not on a solid card, so it reads like it's actually printed on the page.
   These percentages only resolve correctly because book.js's
   correctBookLayout() keeps .book-cover-inner (this element's own parent)
   scaled/positioned to match .book-viewport's real box every time PageFlip
   could have re-laid-out the cover -- see that function's own comment, and
   the .page-spread comment above, for why a plain percentage here couldn't
   reliably hit an exact target on its own before that existed. Kerry's
   exact numbers from a live screenshot ("image is 228 wide, book is 543
   wide -- make it 75% of the book's width and 20% down from the top"):
   75% width -> 12.5% margin on each side; 20% down -> 20% margin-top.
   Kerry then asked for the image "10% smaller but centered horizontally
   where it is, [same] distance from the top": both flex-basis (height)
   and width scaled by 0.9 (56% -> 50.4%, 75% -> 67.5%) so the box shrinks
   uniformly (keeping its own aspect/crop framing the same, just smaller)
   -- margin-top stays 20% (unchanged distance from the top), and the
   left/right margins move from 12.5% to 16.25% each (half of the 7.5
   points of width given back) so the box's own horizontal CENTER stays
   exactly where it was, not just its left edge. */
.cover-photo {
  flex: 0 0 50.4%;
  min-height: 0;
  margin: 20% 16.25% 0;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 6px 18px rgba(35, 25, 38, 0.28);
}
.cover-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.cover-text {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 4% 10% calc(4% + env(safe-area-inset-bottom));
  text-align: center;
}
/* Fancy cursive/script stack for the couple's names -- no custom webfont
   fetch (this sandbox's network access is limited to an allowlist, and a
   system-font stack avoids depending on it staying reachable at all);
   every platform this app targets (iOS/macOS Safari, Windows Chrome/Edge,
   Android Chrome) has at least one of these installed. */
.cover-names {
  margin: 0;
  font-family: "Brush Script MT", "Segoe Script", "Bradley Hand", "Apple Chancery", "Snell Roundhand", cursive;
  font-size: 2.1rem;
  font-weight: 400;
  color: #3a2a3d;
  line-height: 1.2;
}
.cover-date {
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: #7b2d58;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.cover-hint {
  margin: 14px 0 0;
  font-size: 0.76rem;
  color: #6b5f6e;
  font-style: italic;
}
/* Closing page (book-cover-inner-back) shows the couple's own "leaving on
   their honeymoon" photo once a PlatformHost has uploaded one (see
   admin.js's backCoverPhotoInput and PhotoBookService's
   backCoverImageUrl) -- same upper-half-photo/lower-text layout as the
   front cover for visual consistency. Until then buildCoverPage() renders
   no .cover-photo element at all for 'back', so .cover-text alone is
   centered in the full page height (justify-content: center here only
   takes effect when .cover-photo is genuinely absent from the DOM, not
   just empty). */
/* Kerry: "the Alexys and Daniel text is not centered. It should be
   centered very close to the bottom, The End text between the page
   arrows." flex-end (was center) pushes the whole names/date/hint group --
   .cover-photo too, on the rare page that already has a back-cover photo --
   down to the bottom of this page's own height, so "The End" (the last
   line, see buildCoverPage()'s hint text) lands close to the bottom edge,
   near the footer's prev/next arrows, instead of floating in the vertical
   middle of the page. */
.book-cover-inner-back { justify-content: flex-end; }
/* Kerry: "leave Alexys & Daniel text where it is from the bottom but make
   font 10% smaller" -- justify-content: flex-end above already anchors
   the whole names/date/hint group to the bottom, so that's untouched; this
   only scales the names text itself (2.1rem -> 1.89rem), scoped to the
   back cover specifically since that's the "from the bottom" text Kerry's
   describing -- the front cover's own .cover-names (not bottom-anchored)
   is unaffected. */
.book-cover-inner-back .cover-names { font-size: 1.89rem; }

.book-footer {
  position: relative;
  z-index: 100;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
}
.footer-nav {
  position: relative;
  z-index: 101;
  pointer-events: auto;
  touch-action: manipulation;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 999px;
  width: 38px;
  height: 38px;
  font-size: 1.1rem;
  cursor: pointer;
}
.footer-nav:disabled { opacity: 0.35; cursor: default; }
.page-indicator { margin: 0; font-size: 0.82rem; color: var(--muted); min-width: 6em; text-align: center; }

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(10, 6, 10, 0.92);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 20;
  padding: 24px;
}
/* .lightbox-frame shrink-wraps to the image's own real rendered size (a
   flex item in .lightbox's own column flex container, which centers its
   children rather than stretching them) -- .lightbox-close below is
   positioned against THIS box, not the full-viewport .lightbox, so it
   lands on the image's own corner instead of the far edge of the screen.
   See index.html's own comment on .lightbox-frame for the fuller story. */
.lightbox-frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  max-height: 100%;
}
.lightbox-content { max-width: 92vw; max-height: 78vh; display: flex; align-items: center; justify-content: center; }
.lightbox-content img, .lightbox-content video { max-width: 92vw; max-height: 78vh; object-fit: contain; }
/* Kerry: "clicking should be reserved for zooming the image out and in."
   book.js's toggleLightboxZoom() toggles this class on click; zoomed
   raises the max-width/max-height caps so the media shows noticeably
   larger, still capped well within the viewport (no scrolling needed --
   scrolling would carry .lightbox-close, anchored to this same box's
   corner, out of reach along with it). */
.lightbox-content:not(.is-zoomed) { cursor: zoom-in; }
.lightbox-content.is-zoomed { cursor: zoom-out; }
.lightbox-content.is-zoomed img, .lightbox-content.is-zoomed video {
  max-width: 98vw;
  max-height: 92vh;
}
.lightbox-caption { color: var(--muted); margin-top: 10px; font-size: 0.85rem; text-align: center; }
/* Kerry: the X was "far away and to the right on the desktop version"
   (positioned against the full-viewport .lightbox rather than the image
   itself -- a problem any time the image is smaller/narrower than the
   screen) and on mobile "way up at the top where the battery indicator
   is... almost impossible to click" (16px from the true top edge, no
   allowance for the status bar). Repositioned against .lightbox-frame
   (the image's own real box, see above) and enlarged well past the old
   34px square for a real tap target. */
.lightbox-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: rgba(20, 14, 20, 0.72);
  border: 2px solid rgba(255, 255, 255, 0.35);
  color: var(--text);
  width: 48px;
  height: 48px;
  border-radius: 999px;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}
