/*** FIRST-PARTY FLIPBOOK VIEWER (2026-08-11, real page-curl physics via vendored StPageFlip;
 * excellence pass + spread-split/zoom-pan/contrast pass, both 2026-08-11) ***
 * Styles for Content/Scripts/flipbook-viewer.js's DOM. The turn itself (page geometry, shadows,
 * cover/portrait pairing) is drawn by the vendored library (Content/Scripts/page-flip.browser.js)
 * via inline styles on its own .stf__* elements inside .fbv-book - this file only lays out OUR
 * chrome around it (stage, nav, control bar, thumbnail drawer, zoom overlay, toast, fallback) and
 * the page content (img). #flipbook-viewer fills the same "calc(100vh - 60px)" area min-page.css
 * already gives #myFlipbook, below the 60px header (100% instead, in fullscreen - see bottom).
 */

#flipbook-viewer {
	position: relative;
	/* ⚠ DO NOT "fix" the drawer bounce with overflow:hidden here — MEASURED AND REJECTED
	   2026-08-14. The closed drawer is absolutely positioned just below this box's bottom edge (that
	   is how it slides up), and an out-of-flow element parked past the bottom contributes to this
	   box's SCROLLABLE OVERFLOW: clientHeight 840, scrollHeight 869, exactly one collapsed drawer of
	   slack. overflow:hidden was tried against precisely that and changed NOTHING — scrollHeight
	   stayed 869 and the box stayed programmatically scrollable, which is the same thing
	   scrollThumbIntoView's own comment recorded on 2026-08-13. The scroll here is never the user's;
	   it is focus() revealing its target. The fix is preventScroll at every overlay focus call
	   (openDrawer/closeDrawer/openZoom/closeZoom), not a clipping rule that only looks like one. */
	width: 100%;
	/* 100vh is a LIE on mobile (2026-08-11, "controls render off the bottom, under the phone's
	   home/back/task-manager strip"): it's the LARGEST possible viewport, including the area
	   behind the browser's own collapsible chrome (URL bar, gesture strip) - a stage sized off it
	   overflows the REAL visible area, and the bottom-anchored control pill lands under the
	   system strip. 100dvh (dynamic viewport height) tracks the ACTUAL visible box live as that
	   chrome collapses/expands. The 100vh rule stays first as the fallback for browsers that don't
	   support dvh (it simply never gets overridden there) - never the other way around. */
	height: calc(100vh - 60px);
	height: calc(100dvh - 60px);
	/* #0c1a2a: the site's #003865 brand navy pulled ~80% toward black - softer gallery-grade
	   contrast against white pages than flat black, visually continuous with the site's navy
	   header, brand-derived without reading as "a color". */
	background: #0c1a2a;
	overflow: hidden;
	-webkit-user-select: none; user-select: none;
	touch-action: pan-y;
}

.fbv-stage {
	position: absolute;
	top: 0; left: 0; right: 0; bottom: 0;
}

/* Minimal first-paint loading indicator (2026-08-13) - flipbook-viewer.js's init()/buildDom() put
   one of these up the moment there's a stage but nothing on it yet (manifest fetch, then
   buildPageFlip's own cover-ratio/inner-spread probes - up to several seconds on a slow origin),
   and hideLoadingIndicator() tears it down once the book is actually built. Three quiet dots, no
   text - a spinner reads as "still working" without narrating it. */
.fbv-loading {
	position: absolute;
	top: 50%; left: 50%;
	transform: translate(-50%, -50%);
	display: flex;
	gap: 8px;
	z-index: 15;
	pointer-events: none;
}
.fbv-loading span {
	width: 10px; height: 10px;
	border-radius: 50%;
	background: rgba(255,255,255,.55);
	animation: fbv-loading-bounce 1s ease-in-out infinite;
}
.fbv-loading span:nth-child(2) { animation-delay: .15s; }
.fbv-loading span:nth-child(3) { animation-delay: .3s; }
@keyframes fbv-loading-bounce {
	0%, 80%, 100% { transform: scale(.6); opacity: .4; }
	40% { transform: scale(1); opacity: 1; }
}
/* prefers-reduced-motion (2026-08-13): flipbook-viewer.js's own REDUCED_MOTION check builds this
   variant with a static word instead of the animated dots, same treatment every other motion path
   in that file already gets - see the media query at the bottom of this file too. */
.fbv-loading-static {
	color: rgba(255,255,255,.7);
	font-size: 13px;
	letter-spacing: .04em;
}

/* Solo-page centering (2026-08-11 fix, "cover renders lopsided/off-edge"): StPageFlip's box is
   ALWAYS two-pages-wide even when only one page is showing (the cover, or a trailing solo last
   page) - it renders the lone page in that half of the box and leaves the other half blank, which
   is correct per-library behavior but reads as "clipped/off-center" since the VISIBLE page sits
   off-center in the stage. Shifted by flipbook-viewer.js's updateSoloShift() via transform on THIS
   wrapper - never .fbv-book itself, which IS the element StPageFlip repositions every frame via
   inline left/top (the same cssText-clobbering hazard the half-crop fix ran into). transform
   doesn't affect the library's own offsetWidth/Height-based box math, only paint position, so this
   is purely cosmetic re-centering with zero effect on the library's internal geometry. */
.fbv-book-shift {
	width: 100%;
	height: 100%;
	/* opacity .12s ease (2026-08-11, "mis-sized book flickers on fullscreen exit"): the fade-BACK-in
	   half of flipbook-viewer.js's veilDuringSettle()/unveil() - the instant HIDE half always sets
	   transition:none itself first, so this only ever animates the return to visible, never the
	   hide. Owned entirely here (2026-08-13 hygiene pass: flipbook-viewer.js used to carry its own
	   VEIL_FADE_MS constant duplicating this number, unused since nothing read it - removed). */
	transition: transform .2s ease, opacity .12s ease;
}

/* Mount point handed to StPageFlip (autoSize: false - we size it, the library reads these live
   dimensions on load and on window resize to lay out pages and pick portrait vs landscape).
   Edge-to-edge WIDTH, CONTAINED height (2026-08-11 fix, "book overflows the viewport bottom"): the
   book fills the stage's full width but its HEIGHT stops short of the control bar's own band -
   StPageFlip's own stretch-mode math (Render.calculateBoundsRect) already maximizes the spread
   within whatever box it's given while preserving the page ratio (flipbook-viewer.js's detection -
   the cover's ratio, or the modal inner ratio when the PDF is spread-exported - see buildPageMap),
   so giving it a box that's already clear of the control bar is the whole fix - no page content can
   land under/behind the pill regardless of book ratio, split vs normal mode, or portrait/single-
   page mode, since it's all the SAME box math working from a smaller H. The nav arrows (vertically
   centered, unaffected) still float on top rather than reserving a side gutter. 76px = the control
   pill's own worst-case footprint (bottom:14px + ~40px pill height) plus real breathing room. */
.fbv-book {
	position: relative;
	width: 100%;
	height: calc(100% - 76px);
	/* ⚠ NEUTRALIZES StPageFlip's OWN INLINE min-width (2026-08-14). In stretch mode the library
	   writes `min-width: {setting.minWidth}px` straight onto this element, on top of using that
	   same number for its portrait breakpoint (blockWidth < 2 * minWidth). The two uses are in
	   direct conflict for any book that wants to be FORCED into single-page mode: clearing the
	   breakpoint means naming a minWidth above the viewport, and that same value then inflates
	   this box past the viewport, taking the rendered book off-screen with it (the solo-mode
	   books' first draft put the visible page at left: 49505px - correct geometry inside a
	   100000px stage). !important is required and is not a shortcut: an inline style loses to a
	   stylesheet declaration ONLY on this flag.
	   Overriding it costs nothing, because the min never applied in the first place - we set this
	   element's box ourselves and pass autoSize:false precisely so the library reads OUR
	   dimensions rather than imposing its own. min-height is deliberately left alone: it is not
	   part of the conflict and has been live and correct since the engine shipped. */
	min-width: 0 !important;
}

/* Belt-and-suspenders: the vendored library's own base CSS has an upstream typo
   (".sft__wrapper" instead of ".stf__wrapper") that silently drops its own width:100% rule.
   Harmless in practice (block-level elements default to full width), but pinned explicitly here
   so page layout never depends on that typo being harmless. */
.fbv-book .stf__wrapper,
.fbv-book .stf__block {
	width: 100%;
	height: 100%;
}

/* Loading choreography: the wrap's own background is a neutral, stage-adjacent placeholder tone
   (never white - a stark white pop-in reads as a flash) shown until the image fades in over it. */
.fbv-page-item {
	background: #16283a;
	box-shadow: 0 10px 40px rgba(0,0,0,.35);
	box-sizing: border-box;
	overflow: hidden;
	cursor: pointer;
}

.fbv-page-item img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain; /* zero-op when a page matches the book's ratio; contain-fits a rogue page */
	background: transparent;
	opacity: 0;
	transition: opacity .15s ease;
}
.fbv-page-item img.fbv-loaded { opacity: 1; }

/* Spread-split mode (2026-08-11): a PDF page that's a pre-composed two-panel spread renders as TWO
   book pages, each cropping its half out of the SAME fetched image via absolute positioning inside
   an overflow:hidden box - no server/grammar changes, purely a client-side crop. Sized by natural
   height (not a hardcoded 200% width) so an imperfect real-world ratio (not exactly 2.000x) still
   crops cleanly, just clamped by the container's own overflow:hidden either way.
   .fbv-half-crop (2026-08-11 fix, "wrong fragment on each half"): a dedicated inner wrapper, NOT
   .fbv-page-item itself, is the crop img's containing block. .fbv-page-item IS the element
   StPageFlip repositions every frame via inline left/top; giving IT position:relative would win
   over the library's own position:absolute (stylesheet, .stf__item) during the exact frames its
   own draw() path clears inline position, and break the library's placement of the page mid-flip.
   .fbv-half-crop fills .fbv-page-item via ordinary 100%/100% normal-flow sizing (works regardless
   of the parent's own position value) and is the ONLY thing that owns position:relative here. */
.fbv-page-item.fbv-half { overflow: hidden; }
.fbv-half-crop {
	position: relative;
	width: 100%;
	height: 100%;
	overflow: hidden;
}
.fbv-page-item.fbv-half .fbv-half-crop img {
	position: absolute;
	top: 0;
	left: 0;
	width: auto;
	height: 100%;
	max-width: none;
	/* object-fit: fill, NEVER none (2026-08-11 root cause, measured live on deployed v9): this
	   element's box derives from the image's own ratio (width:auto x height:100%), so fill is
	   proportionally exact. `none` paints the image at NATURAL size, centered and clipped -
	   silently cropping ~18% off every edge of the spread while every DOM rect still measures
	   "correct", which is exactly why three rounds of geometry fixes couldn't touch it. */
	object-fit: fill;
}
.fbv-page-item.fbv-half .fbv-half-crop img.fbv-half-right {
	left: auto;
	right: 0;
}

/* A page that exhausted its retries (flipbook-viewer.js: IMAGE_RETRY_DELAYS) - never a permanent
   hole, always a way back in. Tapping the page retries (handlePageActivate). */
.fbv-page-item.fbv-error::after {
	content: 'Tap to retry';
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	color: rgba(255,255,255,.85);
	font-size: 13px;
	background: rgba(0,0,0,.25);
}

/* Prev/next controls - overlay the book (z-index above the library's own internal layers, which
   run up to startZIndex(0)+10 for shadows during an active flip; without this headroom the
   flipping/shadow layers could paint OVER the buttons near the edges and steal clicks, exactly
   where the arrows sit), never a reserved side gutter.
   Contrast ruling (2026-08-11, Charles - REVERTED the light-frosted attempt): "the classic video-
   player treatment" - dark translucent chip, bright glyph. The light-pill version was the wrong
   axis to fix; the actual fix lives in the ICON INK (near-white, readable stroke weight - already
   thickened in the icon defs above), not the chip's own contrast against the stage. Same
   rgba(0,0,0,.35)-family look as the very first pass, size unchanged throughout (48px / 32px). */
.fbv-nav {
	position: absolute;
	top: 50%; transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 48px; height: 48px;
	border: 0;
	border-radius: 50%;
	background: rgba(0,0,0,.35);
	color: rgba(255,255,255,.92);
	cursor: pointer;
	z-index: 20;
	touch-action: manipulation;
	box-sizing: border-box;
}
.fbv-nav:hover { background: rgba(0,0,0,.55); }
.fbv-nav:active { background: rgba(0,0,0,.68); }
.fbv-prev { left: 16px; }
.fbv-next { right: 16px; }
/* Root cause confirmed (2026-08-11, Playwright measurement: svg rect was width:2 height:19 despite
   correct width="22"/height="22" attributes) - a site-global stylesheet rule shrinks the SVG along
   the flex container's main axis (some normalize/framework reset's flex-shrink default winning over
   the attribute). Pin BOTH the dimension and flex-shrink explicitly so no global rule can touch it. */
.fbv-nav svg { width: 22px; height: 22px; flex: none; }

/* Control bar: thumbnails/grid, page indicator (also opens the drawer), copy-link, zoom,
   fullscreen - one pill, same dark-chip/bright-glyph chrome language as the nav arrows.
   env(safe-area-inset-bottom) (2026-08-11, "controls off the bottom, under the phone's home/back/
   task-manager strip"): on gesture-navigation phones the pill's own 14px offset alone can still
   land under the OS's home-indicator strip - env() adds real device inset on top of it (0 on any
   device/browser without one, so this is a pure no-op there). Needs viewport-fit=cover on the
   page's own <meta name="viewport"> (Flipbook.vbhtml) or env() resolves to 0 everywhere. */
.fbv-controls {
	position: absolute;
	left: 50%; bottom: calc(14px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
	display: flex;
	align-items: center;
	gap: 2px;
	background: rgba(0,0,0,.35);
	border-radius: 20px;
	padding: 4px 6px;
	z-index: 20;
	max-width: calc(100% - 96px); /* stays clear of the nav arrows on narrow screens */
	box-sizing: border-box;
}

/* The page indicator - STATUS, not a control (2026-08-13, "remove the numbers completely, so the
   controls were simply buttons. then the page number could be displayed somewhere strategic
   outside the controls box and it wouldn't need to be clickable at all"). A quiet chip in the
   stage's bottom-left corner: same dark-chip chrome family as the pill so it reads as the same
   instrument panel, but SMALLER type (back to 13px - it stopped competing with the glyphs the
   moment it left their row), more muted ink, and pointer-events:none so it can never read as a
   button again. Same safe-area bottom offset as the pill, so the two ride the same baseline. */
.fbv-page-chip {
	position: absolute;
	right: 14px;
	bottom: calc(14px + env(safe-area-inset-bottom, 0px));
	z-index: 20;
	border-radius: 16px;
	background: rgba(0,0,0,.35);
	color: rgba(255,255,255,.75);
	font-size: 13px;
	padding: 6px 12px;
	white-space: nowrap;
	pointer-events: none;
	user-select: none;
}

.fbv-ctrl-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 32px; height: 32px;
	border: 0;
	border-radius: 50%;
	background: transparent;
	color: rgba(255,255,255,.92);
	cursor: pointer;
	touch-action: manipulation;
}
/* Same root cause/fix as .fbv-nav svg above - the control icons measured width:2 height:19. */
.fbv-ctrl-btn svg { width: 19px; height: 19px; flex: none; }
.fbv-ctrl-btn:hover {
	background: rgba(255,255,255,.24);
}
.fbv-ctrl-btn:active {
	background: rgba(255,255,255,.32);
}

/* Thumbnail drawer - position:absolute so it never reserves space when closed; slides up from the
   bottom, sits over the book. A backdrop dims the book so the drawer reads as a distinct layer. */
.fbv-drawer-backdrop {
	position: absolute;
	top: 0; left: 0; right: 0; bottom: 0;
	background: rgba(12,26,42,.55);
	z-index: 49;
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition: opacity .2s ease;
}
#flipbook-viewer.fbv-drawer-open .fbv-drawer-backdrop {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
}

.fbv-drawer {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	z-index: 51;
	background: #0c1a2a;
	border-top: 1px solid rgba(255,255,255,.12);
	transform: translateY(100%);
	visibility: hidden;
	pointer-events: none;
	transition: transform .25s ease;
	box-sizing: border-box;
	/* This container is now itself a programmatic focus target (2026-08-13, tabindex="-1" -
	   flipbook-viewer.js's openDrawer) - no ring around the whole panel; the real thumb buttons
	   inside still show their own focus/current-page styling. */
	outline: none;
}
#flipbook-viewer.fbv-drawer-open .fbv-drawer {
	transform: translateY(0);
	visibility: visible;
	pointer-events: auto;
}

.fbv-drawer-strip {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	padding: 14px;
	overflow-y: auto;
	overscroll-behavior: contain;
	max-height: inherit;
	box-sizing: border-box;
}

.fbv-thumb {
	position: relative;
	width: 70px;
	border: 2px solid transparent;
	border-radius: 4px;
	background: #16283a;
	padding: 0;
	cursor: pointer;
	overflow: hidden;
	touch-action: manipulation;
	flex: none;
	box-sizing: border-box;
}
.fbv-thumb img {
	display: block;
	width: 100%;
	height: auto;
	min-height: 50px;
}
/* Split-mode half thumbs (2026-08-11): same crop technique as the flip surface, at the thumb's own
   fixed width; the button's height is set inline (flipbook-viewer.js, from the cover ratio) since
   an overflow-cropped absolutely-positioned child can't otherwise communicate an intrinsic height. */
.fbv-thumb.fbv-half img {
	position: absolute;
	top: 0;
	left: 0;
	width: auto;
	height: 100%;
	min-height: 0;
	max-width: none;
	/* Audited (2026-08-11) against the same object-fit bug as the flip-surface rule above - this
	   one was never set to `none`, so it was already using the correct default (`fill`, exact here
	   since the box derives from the image's own ratio). Left explicit so nobody "fixes" it to
	   `none` by analogy with a differently-behaved rule elsewhere. */
	object-fit: fill;
}
.fbv-thumb.fbv-half img.fbv-half-right {
	left: auto;
	right: 0;
}
.fbv-thumb-num {
	position: absolute;
	left: 3px; bottom: 3px;
	font-size: 10px;
	color: #fff;
	background: rgba(0,0,0,.55);
	padding: 1px 4px;
	border-radius: 3px;
}
.fbv-thumb-current { border-color: #fff; }

/* A thumb that exhausted its one retry (flipbook-viewer.js: THUMB_RETRY_DELAY_MS) - scaled-down
   version of .fbv-page-item.fbv-error above (a glyph, not the full "Tap to retry" sentence - a
   70px-wide thumb has no room for it). Tapping it retries (buildDrawerThumbs' click handler). */
.fbv-thumb.fbv-thumb-error::after {
	content: '\21bb';
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	color: rgba(255,255,255,.85);
	font-size: 16px;
	background: rgba(0,0,0,.35);
}

/* Copy-link confirmation - unobtrusive, self-dismissing (flipbook-viewer.js clears it). */
.fbv-toast {
	position: absolute;
	left: 50%; bottom: 62px; transform: translateX(-50%) translateY(6px);
	background: rgba(0,0,0,.75);
	color: #fff;
	font-size: 13px;
	padding: 6px 14px;
	border-radius: 14px;
	z-index: 60;
	opacity: 0;
	pointer-events: none;
	transition: opacity .18s ease, transform .18s ease;
	white-space: nowrap;
}
.fbv-toast.fbv-toast-show {
	opacity: 1;
	transform: translateX(-50%) translateY(0);
}

/* Zoom overlay - the `zoom` size variant. Real drag-to-pan (2026-08-11, Charles: "you should be
   able to drag the image around") via flipbook-viewer.js's Pointer Events, not native scroll -
   overflow:hidden here, the image is positioned with a JS-driven transform instead. Same navy as
   the stage, at the same near-opaque weight the pure-black backdrop used. */
.fbv-zoom-layer {
	display: none;
	position: fixed;
	top: 0; left: 0; right: 0; bottom: 0;
	background: rgba(12,26,42,.92);
	overflow: hidden;
	z-index: 1000;
	cursor: grab;
	touch-action: none; /* we own single-pointer drag via Pointer Events; a second finger yields to native pinch */
	-webkit-user-select: none; user-select: none;
	/* Programmatic focus target (2026-08-13, tabindex="-1" - flipbook-viewer.js's openZoom) - no
	   ring around the whole overlay. */
	outline: none;
}
.fbv-zoom-layer.fbv-open {
	display: flex;
	align-items: center;
	justify-content: center;
}
.fbv-zoom-layer.fbv-zoom-dragging { cursor: grabbing; }
/* Full-spread zoom (2026-08-11, "drag/view across both pages"): the pan transform lives on THIS
   wrapper, not on either <img> - normal-mode spreads render TWO seam-adjacent images side by side
   (flipbook-viewer.js's openZoom), panned together as one canvas. Split mode / a solo cover/back /
   single-page mode: zoomImg2 is display:none, so this wraps just the one image exactly as before. */
.fbv-zoom-spread {
	display: flex;
	align-items: flex-start;
	will-change: transform;
}
.fbv-zoom-layer img {
	display: block;
	max-width: none;
	max-height: none;
	opacity: 0;
	transition: opacity .15s ease;
	-webkit-user-select: none; user-select: none;
	-webkit-user-drag: none;
}
.fbv-zoom-layer img.fbv-loaded { opacity: 1; }

.fbv-fallback {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 100%;
	gap: 16px;
	color: #fff;
	font-size: 16px;
	text-align: center;
	padding: 20px;
}

/* Fullscreen: the header-offset height reservation only makes sense OUTSIDE the Fullscreen API -
   inside it #flipbook-viewer IS the whole screen, so the -60px would leave a bare navy strip at
   the bottom. .fbv-fullscreen is toggled by flipbook-viewer.js's onFullscreenChange; the :fullscreen
   pseudo-classes are a CSS-only belt-and-suspenders for the same state. */
#flipbook-viewer.fbv-fullscreen,
#flipbook-viewer:fullscreen,
#flipbook-viewer:-webkit-full-screen {
	height: 100%;
}

@media screen and (max-width: 700px) {
	.fbv-nav { width: 38px; height: 38px; }
	.fbv-prev { left: 6px; }
	.fbv-next { right: 6px; }
	.fbv-thumb { width: 60px; }
}

@media (prefers-reduced-motion: reduce) {
	.fbv-page-item img,
	.fbv-zoom-layer img,
	.fbv-drawer,
	.fbv-drawer-backdrop,
	.fbv-toast,
	.fbv-book-shift {
		transition: none;
	}
	/* Defense in depth (2026-08-13): flipbook-viewer.js's own REDUCED_MOTION check already builds
	   the static-word variant instead of the animated dots, so these spans normally don't exist
	   under this media condition - this only matters if that JS check and the browser's own media
	   state were ever somehow out of step. */
	.fbv-loading span {
		animation: none;
	}
}
