/* ═══════════════════════════════════════════════════════
   Storybook Unified — Cover-as-page + state transitions
   Loads after book-landing.css and desk-scene.css
   ═══════════════════════════════════════════════════════ */

/* ─── Cover page inside book-page-stack ─── */
.book-cover-page {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transform-origin: left center;
    transition: z-index 0s;
}

/* Interior fill panel — seals the back of the cover board at Z=0.
   The front face is at Z=+board-thickness and edges rotate backward from Z=0,
   so this panel fills the hollow interior, preventing see-through gaps at
   corners and edges when viewed at oblique angles. */
.book-cover-page::before {
    content: '';
    position: absolute;
    inset: -2px;
    background: var(--book-cover-dark);
    border-radius: 4px 8px 8px 4px;
    pointer-events: none;
}

.book-cover-page-front {
    position: absolute;
    inset: -0.5px;
    /* Push face to the FRONT of the board. Edges rotate backward from Z=0
       to Z=-board-thickness, so the face at Z=+board-thickness sits flush
       with the front edge — like a real book cover board. */
    transform: translateZ(var(--book-cover-board-thickness, 6px));
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;

    /* Rich leather book cover with subtle grain texture */
    background:
        /* Leather grain noise */
        var(--env-paper-grain),
        /* Worn leather highlights and shadows */
        radial-gradient(ellipse at 30% 20%, rgba(255,255,255,0.06) 0%, transparent 50%),
        radial-gradient(ellipse at 70% 80%, rgba(0,0,0,0.12) 0%, transparent 50%),
        /* Base leather gradient */
        linear-gradient(145deg,
            color-mix(in srgb, var(--book-cover-leather) 90%, white) 0%,
            var(--book-cover-leather) 25%,
            var(--book-cover-dark) 75%,
            color-mix(in srgb, var(--book-cover-dark) 85%, black) 100%);
    background-size: 200px 200px, 100% 100%, 100% 100%, 100% 100%;
    border-radius: 4px 8px 8px 4px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 30px;
    box-shadow:
        2px 2px 8px rgba(0, 0, 0, 0.2),
        inset 0 0 40px rgba(0, 0, 0, 0.15),
        inset 0 0 80px rgba(0, 0, 0, 0.08);
}

/* Paper grain texture overlay on cover */
.book-cover-page-front::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--env-paper-grain);
    opacity: var(--book-cover-grain-opacity);
    pointer-events: none;
    border-radius: inherit;
}

/* Gold frame inset on cover */
.book-cover-page-front::after {
    content: '';
    position: absolute;
    inset: 16px;
    border: 2px solid var(--book-gold-leaf);
    border-radius: 4px;
    opacity: var(--book-cover-gold-opacity);
    pointer-events: none;
}

.book-cover-page-back {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    /* Same forward push as the front face, plus 180deg flip for backface */
    transform: translateZ(var(--book-cover-board-thickness, 6px)) rotateY(180deg);
    border-radius: 4px 8px 8px 4px;
}

/* Marbled endpaper pattern */
.book-cover-endpaper {
    width: 100%;
    height: 100%;
    background:
        radial-gradient(ellipse at 20% 50%, color-mix(in srgb, var(--color-sage) 15%, transparent) 0%, transparent 60%),
        radial-gradient(ellipse at 80% 30%, color-mix(in srgb, var(--color-gold) 12%, transparent) 0%, transparent 50%),
        radial-gradient(ellipse at 50% 80%, color-mix(in srgb, var(--color-lavender) 10%, transparent) 0%, transparent 55%),
        linear-gradient(135deg, var(--book-page-cream), color-mix(in srgb, var(--book-page-cream) 85%, var(--color-warm-charcoal)));
    /* Subtle marbled texture */
    background-size: 100% 100%, 100% 100%, 100% 100%, 100% 100%;
}

.book-cover-endpaper::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--env-paper-grain);
    opacity: 0.08;
}

/* ─── Cover board thickness edges ─────────────────────────────
   Four perpendicular surfaces giving the cover real depth during 3D rotation.
   Without these, the cover reads as a "knife blade" when edge-on at ~90deg.
   Each edge is rotated 90deg around its anchored edge, forming the sides
   of the cover board. Uses the same leather dark color for visual continuity. */

.book-cover-edge {
    position: absolute;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    pointer-events: none;
}

/* Spine edge (left) — most visible when cover is at 90deg.
   This is the edge that connects the cover to the book's binding. */
.book-cover-edge--spine {
    top: -2px;
    bottom: -2px;
    left: 0;
    width: var(--book-cover-board-thickness, 4px);
    transform-origin: left center;
    transform: rotateY(-90deg);
    background: linear-gradient(to bottom,
        color-mix(in srgb, var(--book-cover-dark) 85%, var(--book-cover-leather)),
        color-mix(in srgb, var(--book-cover-leather) 70%, black) 30%,
        color-mix(in srgb, var(--book-cover-leather) 80%, black) 50%,
        color-mix(in srgb, var(--book-cover-leather) 70%, black) 70%,
        color-mix(in srgb, var(--book-cover-dark) 85%, var(--book-cover-leather)));
    border-radius: 4px 0 0 4px;
    box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3),
                1px 0 2px rgba(0, 0, 0, 0.15);
}

/* Fore edge (right) — visible at extreme rotation angles */
.book-cover-edge--fore {
    top: -1px;
    bottom: -1px;
    right: 0;
    width: var(--book-cover-board-thickness, 4px);
    transform-origin: right center;
    transform: rotateY(90deg);
    background: linear-gradient(to bottom,
        var(--book-cover-dark),
        color-mix(in srgb, var(--book-cover-leather) 60%, black) 50%,
        var(--book-cover-dark));
    border-radius: 0 8px 8px 0;
    box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3),
                -1px 0 2px rgba(0, 0, 0, 0.15);
}

/* Head edge (top) — visible when viewing angle tilts down (rotateX > 0).
   Extends full width to seal corner gaps between face and backing panels.
   Overlaps with fore edge at the corner — same dark color prevents Z-fighting artifacts. */
.book-cover-edge--head {
    top: 0;
    left: -1px;
    right: -1px;
    height: var(--book-cover-board-thickness, 4px);
    transform-origin: top center;
    transform: rotateX(90deg);
    background: var(--book-cover-dark);
    border-radius: 4px 8px 0 0;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.25),
                0 1px 2px rgba(0, 0, 0, 0.15);
}

/* Tail edge (bottom) — visible when viewing angle tilts up.
   Extends full width to seal corner gaps. */
.book-cover-edge--tail {
    bottom: 0;
    left: -1px;
    right: -1px;
    height: var(--book-cover-board-thickness, 4px);
    transform-origin: bottom center;
    transform: rotateX(-90deg);
    background: var(--book-cover-dark);
    border-radius: 0 0 8px 4px;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.25),
                0 -1px 2px rgba(0, 0, 0, 0.15);
}

/* ── Inner cover edges — visible when cover is flipped past 90° ──
   Same Z-span as outer edges (Z=0 to Z=+board-thickness) but opposite
   rotation, so they face the viewer when the cover is open (90-180°).
   translateZ(board-thickness) shifts the edge forward before the rotation,
   collapsing the total cover depth to 1× board-thickness (matching the back cover).
   backface-visibility: visible because these are interior surfaces — the thin
   edge plane needs to render from both sides to appear solid. */

.book-cover-edge-inner {
    backface-visibility: visible;
    -webkit-backface-visibility: visible;
}

.book-cover-edge-inner--spine {
    top: -2px;
    bottom: -2px;
    left: 0;
    width: var(--book-cover-board-thickness, 4px);
    transform-origin: left center;
    transform: translateZ(var(--book-cover-board-thickness, 6px)) rotateY(90deg);
    background: linear-gradient(to bottom,
        color-mix(in srgb, var(--book-cover-dark) 85%, var(--book-cover-leather)),
        color-mix(in srgb, var(--book-cover-leather) 70%, black) 30%,
        color-mix(in srgb, var(--book-cover-leather) 80%, black) 50%,
        color-mix(in srgb, var(--book-cover-leather) 70%, black) 70%,
        color-mix(in srgb, var(--book-cover-dark) 85%, var(--book-cover-leather)));
    border-radius: 4px 0 0 4px;
    box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3),
                1px 0 2px rgba(0, 0, 0, 0.15);
}

.book-cover-edge-inner--fore {
    top: -1px;
    bottom: -1px;
    right: 0;
    width: var(--book-cover-board-thickness, 4px);
    transform-origin: right center;
    transform: translateZ(var(--book-cover-board-thickness, 6px)) rotateY(-90deg);
    background: linear-gradient(to bottom,
        var(--book-cover-dark),
        color-mix(in srgb, var(--book-cover-leather) 60%, black) 50%,
        var(--book-cover-dark));
    border-radius: 0 8px 8px 0;
    box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3),
                -1px 0 2px rgba(0, 0, 0, 0.15);
}

.book-cover-edge-inner--head {
    top: 0;
    left: -1px;
    right: -1px;
    height: var(--book-cover-board-thickness, 4px);
    transform-origin: top center;
    transform: translateZ(var(--book-cover-board-thickness, 6px)) rotateX(-90deg);
    background: var(--book-cover-dark);
    border-radius: 4px 8px 0 0;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.25),
                0 1px 2px rgba(0, 0, 0, 0.15);
}

.book-cover-edge-inner--tail {
    bottom: 0;
    left: -1px;
    right: -1px;
    height: var(--book-cover-board-thickness, 4px);
    transform-origin: bottom center;
    transform: translateZ(var(--book-cover-board-thickness, 6px)) rotateX(90deg);
    background: var(--book-cover-dark);
    border-radius: 0 0 8px 4px;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.25),
                0 -1px 2px rgba(0, 0, 0, 0.15);
}

/* ═══════════════════════════════════════════════════════
   Cascade Flyleaves — decorative pages between cover and content.
   They create the "leafing through" sensation during open/close.
   4 flyleaves turn in staggered cascade after the cover opens,
   giving the book physical depth and the invitation a sense
   of being discovered a few pages in.
   ═══════════════════════════════════════════════════════ */

.book-flyleaf {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transform-origin: left center;
    pointer-events: none;
}

/* Per-flyleaf Z-depth positioning — distributes pages across spine thickness.
   In a real hardcover book, pages attach at different points across the spine
   width rather than all hinging from a single line. Flyleaf 1 (outermost,
   closest to cover) sits near the front face of the spine; flyleaf 4 (innermost,
   closest to content) sits near the back. --flyleaf-z-closed is the position in
   the unturned stack, --flyleaf-z-open is the stacking position when turned.
   These variables are referenced in the flyleafTurnOpen/Close keyframes so
   each flyleaf resolves its own Z-depth independently.
   OPEN Z-ORDER: Decorative flyleaves 1-3 use negative Z to sit behind content
   pages. Flyleaf 4 (ToC) uses a small positive Z (+0.5px) to remain visible as
   the left page of spread 0. The 0.5px value is close enough to content pages
   (Z ≈ 0-2.5px) that any spine-edge overlap is subpixel and invisible, while
   being positive enough to render in front of the viewport background plane.
   Negative Z caused the flyleaf to be occluded by the Z=0 viewport plane. */
.book-flyleaf--1 { --flyleaf-z-closed: calc(var(--book-closed-page-depth) * 0.82); --flyleaf-z-open: -8px; }
.book-flyleaf--2 { --flyleaf-z-closed: calc(var(--book-closed-page-depth) * 0.62); --flyleaf-z-open: -6px; }
.book-flyleaf--3 { --flyleaf-z-closed: calc(var(--book-closed-page-depth) * 0.42); --flyleaf-z-open: -4px; }
.book-flyleaf--4 { --flyleaf-z-closed: calc(var(--book-closed-page-depth) * 0.22); --flyleaf-z-open: 0.5px; }

.book-flyleaf-front,
.book-flyleaf-back {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    border-radius: 4px 8px 8px 4px;
    overflow: hidden;
}

.book-flyleaf-back {
    transform: rotateY(180deg);
}

/* ─── Flyleaf 4 back-face content ───────────────────────────────
   The Table of Contents + close button live on flyleaf 4's back face,
   physically turning with the page during the open animation.
   Uses the same pattern as .book-page-back-content (absolute, z:2,
   rotateY(180deg) to un-mirror content on the turned back face). */
.book-flyleaf-back-content {
    position: absolute;
    inset: 0;
    z-index: 2;
    /* No rotateY needed — the parent .book-flyleaf-back already has
       rotateY(180deg) for the 3D flip. Content inherits the back face's
       local coordinate space and reads correctly when the flyleaf is
       turned. Adding another 180deg would double-mirror the text. */
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: clamp(12px, 3%, 32px);
    background-image: url('/images/backgrounds/left-page-linen.jpg');
    background-size: cover;
    background-position: center;
    background-color: var(--book-page-cream);
    border-radius: 4px 8px 8px 4px;
    pointer-events: auto;
}

/* Flyleaf back content (Table of Contents) is now the physical left page
   of Spread 0 — visible in all settled states. No longer hidden in favour
   of a BookInfoPanel overlay. No transition — play mode handles animation
   timing during open. Prevents TOC flash when play-mode !important drops. */
.book-desk-scene--settling .book-flyleaf-back-content,
.book-desk-scene--book .book-flyleaf-back-content {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: none;
}

/* Only show at desktop widths where two-page spread is visible.
   Below 1200px, only the right page is rendered. */
@media (max-width: 1199px) {
    .book-flyleaf-back-content {
        display: none;
    }
}

/* Close button on flyleaf — inherits .book-close-btn styles,
   positioned at the bottom of the ToC content area. */
.book-close-btn--flyleaf {
    margin-top: auto;
    position: relative;
    bottom: auto;
    left: auto;
    transform: none;
}

/* Hide flyleaf close button during scrub/play — the overlay close button
   (inside .book-left-overlay) takes over with navReveal animation timing.
   Without this, both buttons stack at 100% scrub because the scene state
   class (--book) is suppressed during scrub/play. */
.book-desk-scene--scrub .book-close-btn--flyleaf,
.book-desk-scene--play .book-close-btn--flyleaf {
    display: none;
}

/* ─── Flyleaf textures ─── */

/* Marbled endpaper — matches the inside-cover endpaper.
   This is naturally the first thing you see when the cover opens. */
.book-flyleaf-texture--endpaper {
    width: 100%;
    height: 100%;
    background:
        radial-gradient(ellipse at 20% 50%, color-mix(in srgb, var(--color-sage) 15%, transparent) 0%, transparent 60%),
        radial-gradient(ellipse at 80% 30%, color-mix(in srgb, var(--color-gold) 12%, transparent) 0%, transparent 50%),
        radial-gradient(ellipse at 50% 80%, color-mix(in srgb, var(--color-lavender, #d4cce8) 10%, transparent) 0%, transparent 55%),
        linear-gradient(135deg, var(--book-page-cream), color-mix(in srgb, var(--book-page-cream) 85%, var(--color-warm-charcoal)));
}

.book-flyleaf-texture--endpaper::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--env-paper-grain);
    opacity: 0.08;
}

/* Plain cream paper — clean inner pages */
.book-flyleaf-texture--cream {
    width: 100%;
    height: 100%;
    background:
        var(--env-paper-grain),
        linear-gradient(170deg, var(--book-page-cream) 0%, color-mix(in srgb, var(--book-page-cream) 95%, #e0d5c8) 100%);
    background-size: 200px 200px, 100% 100%;
}

/* Aged cream with subtle foxing — slightly warmer, older feeling */
.book-flyleaf-texture--aged {
    width: 100%;
    height: 100%;
    background:
        var(--env-paper-grain),
        radial-gradient(circle at 30% 70%, rgba(180, 160, 130, 0.08) 0%, transparent 40%),
        radial-gradient(circle at 75% 25%, rgba(180, 160, 130, 0.06) 0%, transparent 35%),
        linear-gradient(170deg,
            color-mix(in srgb, var(--book-page-cream) 92%, #d4c8b0) 0%,
            color-mix(in srgb, var(--book-page-cream) 88%, #d0c0a8) 100%);
    background-size: 200px 200px, 100% 100%, 100% 100%, 100% 100%;
}

/* ─── Flyleaf page-edge vignette — subtle shadow giving each page a tactile edge ─── */
.book-flyleaf-front::after,
.book-flyleaf-back::after {
    content: '';
    position: absolute;
    inset: 0;
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.06),
                inset 3px 0 8px rgba(0, 0, 0, 0.04);
    pointer-events: none;
    border-radius: inherit;
}

/* ─── Flyleaf cascade: opening ─────────────────────────────────
   Each flyleaf turns 0→-180deg with staggered delays.
   The cascade starts 900ms after the cover begins swinging
   (when the cover is past ~100deg and the interior is fully exposed).
   Easing is lighter and more fluid than the cover (paper vs board).
   NO OPACITY FADE — flyleaves are naturally visible in the stack
   beneath the cover. They are revealed physically as the cover
   lifts away, not spawned or faded in. */

:not(.book-desk-scene--play) .book-flyleaf--opening {
    /* 'both' fill mode applies the 0% keyframe during the delay,
       keeping pages at rotateY(0deg) — sitting flat in the stack,
       naturally hidden behind the cover. */
    animation: flyleafTurnOpen 0.9s cubic-bezier(0.28, 0.84, 0.42, 1) both;
}

/* Staggered delays: proportional to --book-closed-open-duration so they scale
   when the user adjusts book speed. First flyleaf starts at ~64% of cover duration,
   each subsequent one is +18% later, creating the "finding the right page" sensation. */
:not(.book-desk-scene--play) .book-flyleaf--1.book-flyleaf--opening {
    animation-delay: calc(var(--book-closed-open-duration) * 0.64);
}
:not(.book-desk-scene--play) .book-flyleaf--2.book-flyleaf--opening {
    animation-delay: calc(var(--book-closed-open-duration) * 0.82);
}
:not(.book-desk-scene--play) .book-flyleaf--3.book-flyleaf--opening {
    animation-delay: calc(var(--book-closed-open-duration) * 1.0);
}
:not(.book-desk-scene--play) .book-flyleaf--4.book-flyleaf--opening {
    animation-delay: calc(var(--book-closed-open-duration) * 1.18);
}

/* Flyleaf page turn — lighter than cover (paper vs board).
   Pure rotateY — no rotateX/skewY which cause spine detachment under
   perspective projection (same B7 fix as pageFoldForward/Backward).
   Z-DEPTH uses per-flyleaf variables (--flyleaf-z-closed / --flyleaf-z-open)
   so each page hinges from its own position across the spine thickness,
   matching how real hardcover pages are bound at different spine depths.
   Z is HELD at --flyleaf-z-closed through the visible swing (0–44%, up to
   -96deg) then drops to --flyleaf-z-open after perpendicular — the page is
   edge-on at 90deg so the depth transition is imperceptible. This mirrors
   the cover's Z-depth handling in coverPageOpen. */
@keyframes flyleafTurnOpen {
    0%   { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(0deg); }
    3%   { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(-2deg); }
    14%  { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(-22deg); }
    /* Z drops to 0 (spine back edge) before the surface can intersect the spine
       wall. At -40deg the flyleaf is still nearly edge-on, masking the Z shift.
       This is physically correct: a detaching page separates from the back of
       the stack, not the front. */
    21%  { transform: translateZ(0px) rotateY(-40deg); }
    28%  { transform: translateZ(0px) rotateY(-62deg); }
    44%  { transform: translateZ(0px) rotateY(-96deg); }
    58%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-132deg); }
    72%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-160deg); }
    85%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-176deg); }
    /* Settle to -180deg without overshooting past it. The original -182deg
       overshoot caused backface-visibility:hidden to trigger on .book-flyleaf-back
       (net rotation -2deg puts the back face toward the viewer), hiding the ToC
       content on flyleaf 4's back. A gentle ease from -176→-180 still feels
       organic without crossing the visibility threshold. */
    93%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-179.5deg); }
    100% { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-180deg); }
}

/* ─── Flyleaf settled state ─── */
/* preserve-3d is critical: the play-mode animation rule includes it, but without it
   here the 3D context breaks when play mode drops — backface-visibility:hidden fails
   and the TOC (flyleaf 4 back face) briefly disappears then fades back.
   transition:none prevents the browser from interpolating between the play-mode
   animation fill and this static rule (even though values match, some browsers briefly
   re-interpolate on the animation→static switch). */
.book-flyleaf--turned {
    transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-180deg);
    transform-style: preserve-3d;
    transition: none;
}

/* ─── Flyleaf cascade: closing (reverse) ───────────────────────
   Flyleaves close in reverse order (4 → 3 → 2 → 1) before the cover
   swings shut. This creates the "pages settling back" sensation.
   Reverse of the opening cascade with its own weight distribution
   — closing motion is front-loaded for a snappier gather-back feel. */

:not(.book-desk-scene--play) .book-flyleaf--closing {
    animation: flyleafTurnClose 0.9s cubic-bezier(0.28, 0.84, 0.42, 1) forwards;
    /* Hold turned position during animation-delay so outer flyleaves
       don't snap to rotateY(0deg) before their staggered close begins. */
    transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-180deg);
}

/* Reverse stagger: flyleaf 4 (innermost) first, flyleaf 1 (outermost) last.
   Each ~14% of page-turn-duration apart — slightly tighter than opening for a snappier close.
   Scales with --book-page-turn-duration so custom speeds stay in sync. */
:not(.book-desk-scene--play) .book-flyleaf--4.book-flyleaf--closing {
    animation-delay: 0s;
}
:not(.book-desk-scene--play) .book-flyleaf--3.book-flyleaf--closing {
    animation-delay: calc(var(--book-page-turn-duration) * 0.14);
}
:not(.book-desk-scene--play) .book-flyleaf--2.book-flyleaf--closing {
    animation-delay: calc(var(--book-page-turn-duration) * 0.29);
}
:not(.book-desk-scene--play) .book-flyleaf--1.book-flyleaf--closing {
    animation-delay: calc(var(--book-page-turn-duration) * 0.43);
}

/* Closing reversal: Z stays at --flyleaf-z-open while the page is on the
   turned side (0–44%), then rises to --flyleaf-z-closed after passing
   perpendicular (-86deg at 44%) — each page returns to its own spine depth. */
@keyframes flyleafTurnClose {
    0%   { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-180deg); }
    /* Gentle lift before closing — stays within visible range to avoid
       backface-visibility:hidden triggering on .book-flyleaf-back content. */
    3%   { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-179.5deg); }
    14%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-160deg); }
    28%  { transform: translateZ(var(--flyleaf-z-open, -2px)) rotateY(-120deg); }
    44%  { transform: translateZ(0px) rotateY(-86deg); }
    /* Z stays at 0 through the spine intersection zone, then rises to
       --flyleaf-z-closed once the page is nearly closed (-40deg to -22deg)
       and edge-on enough to mask the shift. Mirror of flyleafTurnOpen 21%. */
    58%  { transform: translateZ(0px) rotateY(-50deg); }
    72%  { transform: translateZ(0px) rotateY(-20deg); }
    79%  { transform: translateZ(0px) rotateY(-12deg); }
    86%  { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(-4deg); }
    93%  { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(2deg); }
    100% { transform: translateZ(var(--flyleaf-z-closed, 2px)) rotateY(0deg); }
}

/* ─── Mobile adjustments — flyleaves ─── */
@media (max-width: 768px) {
    .book-flyleaf-front,
    .book-flyleaf-back {
        border-radius: 2px 6px 6px 2px;
    }
}

/* ─── Cover name reveal animation ─── */
.book-cover-page--name-reveal {
    /* Cover stays closed but name writes in */
}

/* ─── Cover opening animation ─── */
:not(.book-desk-scene--play) .book-cover-page--opening {
    animation: coverPageOpen 1.6s cubic-bezier(0.23, 0.12, 0.28, 1) forwards;
}

/* Cover opens on its spine hinge — translateZ positions the cover at the
   front of the page stack, rotateY swings it around the left edge
   (transform-origin: left center).

   HINGE MATH: With transform: translateZ(Z) rotateY(angle), the CSS
   transform-origin places the hinge (left-center) at Z = Z in parent space.
   If Z changes during rotation, the hinge slides along the spine — visible
   in orbit mode as the cover disconnecting from the 3D spine.

   FIX: Z is held CONSTANT on each side of perpendicular (90deg). The entire
   Z transition (page-depth+2 → 0) is concentrated into a narrow 4% window
   at the perpendicular crossing (44-48%) where the cover is edge-on from
   ALL viewing angles, making the shift imperceptible.
     0-44%:  Z = pd+2 → hinge at spine FRONT face (closed side)
     44-48%: Z drops pd+2 → 0 (cover is edge-on, minimal screen area)
     48-100%: Z = 0   → hinge at spine BACK face (open side)
   This mimics real book physics: the effective hinge transitions from the
   front to the back of the spine as the cover passes vertical.

   Overshoot at 92% (-183deg) adds natural follow-through before settling.
   No rotateX — causes top/bottom edge separation from the spine. */
@keyframes coverPageOpen {
    0%    { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(0deg); }
    /* Anticipation — barely perceptible preparatory lift */
    4%    { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(1deg); }
    /* Slow initial release — hinge locked at spine front face */
    12%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-10deg); }
    /* Accelerating */
    24%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-38deg); }
    /* Peak acceleration — Z still held, cover <90deg */
    36%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-68deg); }
    /* Begin perpendicular approach — Z held at full depth */
    40%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-78deg); }
    /* ↓ PERPENDICULAR CROSSING — graduated Z transition over 8% window.
       Z shifts from full depth → half → 0 while cover passes through edge-on.
       The wider window (was 4%, now 8%) and intermediate step eliminate any
       visible hinge disconnect, even with the ambient glow removed. */
    44%   { transform: translateZ(calc(var(--book-closed-page-depth) * 0.5 + 1px)) rotateY(-85deg); }
    48%   { transform: translateZ(0px) rotateY(-95deg); }
    /* Z now at 0 — hinge locked at spine back face for open-side rotation */
    60%   { transform: translateZ(0px) rotateY(-122deg); }
    /* Decelerating toward rest */
    72%   { transform: translateZ(0px) rotateY(-148deg); }
    /* Approaching final position */
    84%   { transform: translateZ(0px) rotateY(-172deg); }
    /* Subtle overshoot — cover drifts 3deg past flat */
    92%   { transform: translateZ(0px) rotateY(-183deg); }
    100%  { transform: translateZ(0px) rotateY(-180deg); }
}

/* Cover settled in turned state — at Z=0 (back of book) since the opening
   animation transitions the cover from Z=page-depth to Z=0. The cover is
   physically behind the pages where it belongs in an open book. */
.book-cover-page--turned {
    transform: translateZ(0px) rotateY(-180deg);
    pointer-events: none;
}

/* ─── Cover closing animation (reverse of open) ───
   The 1.0s delay allows flyleaves to close first (4-leaf reverse cascade),
   then the cover swings shut on top. The static transform holds the
   turned position during the delay so there's no visual jump.
   Mirror of coverPageOpen: Z held constant on each side of 90deg,
   rapid transition at perpendicular crossing. See coverPageOpen for
   the full hinge-math explanation. No rotateX. */
:not(.book-desk-scene--play) .book-cover-page--closing {
    animation: coverPageClose 1.6s cubic-bezier(0.23, 0.12, 0.28, 1) 1.0s forwards;
    transform: translateZ(0px) rotateY(-180deg);
    pointer-events: none;
}

@keyframes coverPageClose {
    0%    { transform: translateZ(0px) rotateY(-180deg); }
    /* Slight anticipation — tiny lift before closing */
    4%    { transform: translateZ(0px) rotateY(-183deg); }
    /* Begin closing — hinge locked at spine back face */
    16%   { transform: translateZ(0px) rotateY(-172deg); }
    /* Accelerating — Z held at 0, cover still past 90deg */
    28%   { transform: translateZ(0px) rotateY(-148deg); }
    /* Past midpoint */
    40%   { transform: translateZ(0px) rotateY(-122deg); }
    /* Approaching perpendicular — Z held at 0 until edge-on */
    48%   { transform: translateZ(0px) rotateY(-97deg); }
    /* ↓ PERPENDICULAR CROSSING — graduated Z transition over 8% window.
       Mirror of coverPageOpen: Z rises 0 → half → full over 48-56%.
       The wider window and intermediate step eliminate hinge disconnect. */
    52%   { transform: translateZ(calc(var(--book-closed-page-depth) * 0.5 + 1px)) rotateY(-92deg); }
    56%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-85deg); }
    /* Z now at pd+2 — hinge locked at spine front face for closing rotation */
    64%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-68deg); }
    76%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-38deg); }
    88%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(-10deg); }
    /* Gentle settle — cover comes to rest at +2px buffer */
    96%   { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(1deg); }
    100%  { transform: translateZ(calc(var(--book-closed-page-depth) + 2px)) rotateY(0deg); }
}

/* ─── Storybook state classes ─── */

/* Cover state: hide book navigation, show landing elements */
.storybook--cover .book-nav,
.storybook--cover .book-nav-ribbons {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

.storybook--cover .book-page-sticky-notes,
.storybook--closing .book-page-sticky-notes,
.storybook--settling .book-page-sticky-notes,
.storybook--cover .book-page-ribbon-container,
.storybook--cover .book-page-ribbon-container-back,
.storybook--closing .book-page-ribbon-container,
.storybook--closing .book-page-ribbon-container-back,
.storybook--settling .book-page-ribbon-container,
.storybook--settling .book-page-ribbon-container-back {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* Opening state: hide navigation (visible only in Book state).
   Without this, nav ribbons snap to visible when entering Opening
   (no rule existed), get distorted by 3D perspective transforms during
   the cover swing, then abruptly vanish — causing a "spread and disappear" flash. */
.storybook--opening .book-nav,
.storybook--opening .book-nav-ribbons {
    opacity: 0;
    pointer-events: none;
}

/* Closing state: hide navigation (same as cover) */
.storybook--closing .book-nav,
.storybook--closing .book-nav-ribbons {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* Settling state: hide navigation (shown after Book state is reached) */
.storybook--settling .book-nav,
.storybook--settling .book-nav-ribbons {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* Table/PickingUp/SettingDown states: hide navigation (same as cover) */
.storybook--table .book-nav,
.storybook--table .book-nav-ribbons,
.storybook--pickingup .book-nav,
.storybook--pickingup .book-nav-ribbons,
.storybook--settingdown .book-nav,
.storybook--settingdown .book-nav-ribbons {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

.storybook--table .book-page-sticky-notes,
.storybook--table .book-page-ribbon-container,
.storybook--table .book-page-ribbon-container-back,
.storybook--pickingup .book-page-sticky-notes,
.storybook--pickingup .book-page-ribbon-container,
.storybook--pickingup .book-page-ribbon-container-back,
.storybook--settingdown .book-page-sticky-notes,
.storybook--settingdown .book-page-ribbon-container,
.storybook--settingdown .book-page-ribbon-container-back {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

/* Opening state: fade out landing decorations */
.storybook--opening .landing-candle-flame,
.storybook--opening .landing-tea-steam {
    animation: storybook-fade-out 0.8s ease-out forwards;
}

/* Hide page-attached elements during Opening state before play mode activates.
   Without this, ribbons appear in the 3D closed angle during the 2.2s name reveal
   phase (no storybook--opening rule hides them), then abruptly vanish when play mode
   hides pages. Hiding from the start prevents the "visible → spread → vanish" flash. */
.storybook--opening .book-page-ribbon-container,
.storybook--opening .book-page-ribbon-container-back,
.storybook--opening .book-page-sticky-notes {
    opacity: 0;
    visibility: hidden;
}

@keyframes storybook-fade-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* Book state: show navigation, hide landing elements.
   Nav and ribbons appear AFTER the book has fully settled — the 0.6s delay
   separates "book settling" from "UI elements appearing" so they don't
   contribute to a composited state-swap feeling. */
.storybook--book .book-nav,
.storybook--book .book-nav-ribbons {
    opacity: 1;
    pointer-events: auto;
    /* No transition — play mode handles animation timing during open/close.
       Book state holds final values immediately. The previous 0.6s delay
       caused a visible flash when play-mode !important overrides dropped. */
    transition: none;
}

.storybook--book .book-page-sticky-notes,
.storybook--book .book-page-ribbon-container,
.storybook--book .book-page-ribbon-container-back {
    opacity: 1;
    pointer-events: auto;
    transition: none;
}

/* Landing effects hidden in book/settling state (redundant safety for when they're removed from DOM) */
.storybook--settling .landing-candle-flame,
.storybook--settling .landing-tea-steam,
.storybook--book .landing-candle-flame,
.storybook--book .landing-tea-steam {
    display: none;
}

/* ─── Left page overlay — positioned on the left half of .book-frame ─── */
.book-left-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    height: 100%;
    pointer-events: none;
    z-index: 210;
    /* During scrub/play, .book-frame has transform-style: preserve-3d.
       In a 3D context z-index alone doesn't control ordering — elements
       are sorted by translateZ. Push the overlay in front of all 3D book
       elements (max depth ~120px). In 2D contexts this is a visual no-op. */
    transform: translateZ(150px);
}

/* ─── Admin Close Book Button ─────────────────────────────────
   Pill-shaped frosted glass button on the left page of the open spread.
   Lives inside .book-left-overlay (left 50% of .book-frame) so the
   left:50% centering works regardless of .book-frame-right width animation. */

.book-close-btn {
    position: absolute;
    bottom: 32px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
    pointer-events: auto;

    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 26px;

    border: 1.5px solid rgba(212, 184, 150, 0.5);
    border-radius: 30px;
    background: rgba(250, 245, 240, 0.7);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);

    color: rgba(139, 107, 71, 0.85);
    font-family: var(--font-heading, 'Playfair Display', serif);
    font-size: 0.82rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;

    cursor: pointer;
    transition: all 0.3s ease;
    opacity: 0;
    animation: bookCloseBtnFadeIn 0.5s ease 0.8s forwards;
}

.book-close-btn:hover {
    background: rgba(212, 175, 55, 0.15);
    border-color: rgba(212, 175, 55, 0.5);
    color: var(--book-gold-detail, rgba(212, 175, 55, 0.85));
    transform: translateX(-50%) translateY(-1px);
    box-shadow: 0 4px 12px rgba(212, 175, 55, 0.12);
}

.book-close-btn:active {
    transform: translateX(-50%) translateY(0);
}

.book-close-btn__icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.book-close-btn__text {
    line-height: 1;
}

@keyframes bookCloseBtnFadeIn {
    from { opacity: 0; transform: translateX(-50%) translateY(4px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.storybook--closing .book-close-btn,
.storybook--opening .book-close-btn,
.storybook--cover .book-close-btn {
    opacity: 0 !important;
    pointer-events: none;
    animation: none;
}

/* During page turns/drags/scrubs, hide close button. The overlay sits above
   the viewport stacking context, so turning pages can't occlude it visually.
   animation: none overrides the bookCloseBtnFadeIn fill:forwards state which
   otherwise keeps opacity: 1 regardless of the explicit opacity: 0 rule. */
.storybook:has(.book-page--turning) .book-close-btn,
.storybook:has(.book-page--dragging) .book-close-btn,
.storybook:has(.book-page--scrubbing) .book-close-btn {
    opacity: 0 !important;
    pointer-events: none;
    animation: none;
    transition: opacity 0.15s ease;
}

/* Override: during open scrub, reveal the close button tied to the depth track.
   Uses navReveal (0-90% hidden, 100% visible) so the button appears only when
   the book is nearly fully open (~90%+ depth). This overrides the state-hiding
   rule (.storybook--cover) because the scrub attribute selector has higher specificity. */
.book-desk-scene--scrub[data-scrub-mode="open"] .book-close-btn {
    animation: navReveal 1s linear paused both !important;
    animation-delay: calc(-1s * var(--timeline-depth)) !important;
}

/* Override: during open play, reveal with same navReveal timing as the hardcover. */
.book-desk-scene--play[data-play-mode="open"] .book-close-btn {
    animation: navReveal var(--timeline-play-duration, 3.4s) linear both !important;
    animation-play-state: running !important;
}

@media (prefers-reduced-motion: reduce) {
    .book-close-btn {
        animation-duration: 0.01ms;
        opacity: 1;
    }
}

/* ─── Guest view simulation — hide ribbon labels ───────────────────
   When data-guest-view is set (admin simulating unauthenticated state),
   ribbon labels are hidden. Labels fade in via transition when the
   attribute is removed (auto-disabled when animation scrub/play starts). */
.storybook[data-guest-view] .book-ribbon-label {
    opacity: 0 !important;
}

/* ─── Spine glow during cover open ─── */
.storybook--opening .book-spine-3d-glow {
    animation: spine3dGlowSync 1.2s ease-in-out;
}

/* ─── Visual effect container (DB-driven effects) ─── */
.visual-effect {
    position: absolute;
    pointer-events: none;
}

.visual-effect img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* ─── Reduced motion ─── */
@media (prefers-reduced-motion: reduce) {
    .book-cover-page--opening,
    .book-cover-page--closing {
        animation-duration: 0.01s;
        animation-delay: 0s;
    }

    .book-flyleaf--opening,
    .book-flyleaf--closing {
        animation-duration: 0.01s !important;
        animation-delay: 0s !important;
    }

    .storybook--opening .landing-candle-flame,
    .storybook--opening .landing-tea-steam {
        animation-duration: 0.01s;
    }

    /* Table scene: disable glow pulse, hint card, and pickup/setdown transitions */
    .book-hint-card {
        animation-duration: 0.01ms !important;
    }

    .book-desk-scene--table .book-3d-depth::before {
        animation-duration: 0.01ms !important;
    }

    .book-desk-scene--pickingup .book-frame,
    .book-desk-scene--settingdown .book-frame,
    .book-desk-scene--settling .book-frame {
        transition-duration: 0.01ms !important;
    }

    /* DB-driven visual effects: disable all animations (inline styles need !important) */
    .visual-effect,
    .visual-effect * {
        animation-duration: 0.01ms !important;
        animation-delay: 0ms !important;
        transition-duration: 0.01ms !important;
    }
}

/* ─── Mobile adjustments ─── */
@media (max-width: 768px) {
    .book-cover-page-front {
        padding: 30px 20px;
    }

    .book-cover-page-front::after {
        inset: 10px;
    }
}

@media (max-width: 480px) {
    .book-cover-page-front {
        padding: 24px 16px;
    }

    .book-cover-page-front::after {
        inset: 8px;
    }
}

/* ══════════════════════════════════════════════════════════════════
   CINEMATIC BOOK OPENING — Enhanced particle, glow, and flutter effects
   ══════════════════════════════════════════════════════════════════ */

/* --- Gold particle burst from spine during cover open --- */
.book-opening-particles {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 50;
    overflow: visible;
}

.book-open-mote {
    position: absolute;
    left: 0;
    top: 50%;
    width: clamp(4px, 0.6vw, 8px);
    height: clamp(4px, 0.6vw, 8px);
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(212, 175, 55, 0.95) 0%,
        rgba(212, 175, 55, 0.4) 50%,
        transparent 100%);
    opacity: 0;
    pointer-events: none;
}

/* Only animate in Opening state — gated: play mode handles its own mote timing */
.storybook--opening .book-desk-scene:not(.book-desk-scene--play) .book-open-mote {
    animation: openBurstMote 1.6s ease-out forwards;
}

@keyframes openBurstMote {
    0% {
        opacity: 0;
        transform: translate(0, 0) scale(0);
    }
    10% {
        opacity: 0.9;
        transform: translate(var(--mote-dx-1, 15px), var(--mote-dy-1, -8px)) scale(1);
    }
    35% {
        opacity: 0.8;
        transform: translate(var(--mote-dx-2, 60px), var(--mote-dy-2, -30px)) scale(1.1);
    }
    65% {
        opacity: 0.4;
        transform: translate(var(--mote-dx-3, 120px), var(--mote-dy-3, -50px)) scale(0.8);
    }
    100% {
        opacity: 0;
        transform: translate(var(--mote-dx-4, 180px), var(--mote-dy-4, -70px)) scale(0.3);
    }
}

/* Per-mote trajectory randomization via CSS variables */
.book-open-mote--1  { --mote-dx-1: 12px; --mote-dy-1: -20px; --mote-dx-2: 55px; --mote-dy-2: -60px; --mote-dx-3: 110px; --mote-dy-3: -90px; --mote-dx-4: 160px; --mote-dy-4: -110px; top: 30%; animation-delay: 0.3s; }
.book-open-mote--2  { --mote-dx-1: 18px; --mote-dy-1: -5px;  --mote-dx-2: 70px; --mote-dy-2: -15px; --mote-dx-3: 140px; --mote-dy-3: -25px; --mote-dx-4: 200px; --mote-dy-4: -30px; top: 48%; animation-delay: 0.35s; width: clamp(3px, 0.5vw, 6px); height: clamp(3px, 0.5vw, 6px); }
.book-open-mote--3  { --mote-dx-1: 10px; --mote-dy-1: 15px;  --mote-dx-2: 45px; --mote-dy-2: 40px;  --mote-dx-3: 95px;  --mote-dy-3: 65px;  --mote-dx-4: 140px; --mote-dy-4: 80px;  top: 65%; animation-delay: 0.4s; }
.book-open-mote--4  { --mote-dx-1: 20px; --mote-dy-1: -30px; --mote-dx-2: 80px; --mote-dy-2: -70px; --mote-dx-3: 150px; --mote-dy-3: -100px;--mote-dx-4: 210px; --mote-dy-4: -120px;top: 25%; animation-delay: 0.32s; }
.book-open-mote--5  { --mote-dx-1: 8px;  --mote-dy-1: 10px;  --mote-dx-2: 35px; --mote-dy-2: 30px;  --mote-dx-3: 75px;  --mote-dy-3: 50px;  --mote-dx-4: 120px; --mote-dy-4: 65px;  top: 72%; animation-delay: 0.45s; width: clamp(5px, 0.7vw, 10px); height: clamp(5px, 0.7vw, 10px); }
.book-open-mote--6  { --mote-dx-1: 15px; --mote-dy-1: -12px; --mote-dx-2: 60px; --mote-dy-2: -35px; --mote-dx-3: 120px; --mote-dy-3: -55px; --mote-dx-4: 175px; --mote-dy-4: -70px; top: 40%; animation-delay: 0.38s; }
.book-open-mote--7  { --mote-dx-1: 22px; --mote-dy-1: 5px;   --mote-dx-2: 85px; --mote-dy-2: 10px;  --mote-dx-3: 160px; --mote-dy-3: 15px;  --mote-dx-4: 220px; --mote-dy-4: 18px;  top: 52%; animation-delay: 0.42s; width: clamp(3px, 0.4vw, 5px); height: clamp(3px, 0.4vw, 5px); }
.book-open-mote--8  { --mote-dx-1: 14px; --mote-dy-1: 25px;  --mote-dx-2: 50px; --mote-dy-2: 55px;  --mote-dx-3: 100px; --mote-dy-3: 80px;  --mote-dx-4: 150px; --mote-dy-4: 95px;  top: 78%; animation-delay: 0.48s; }
.book-open-mote--9  { --mote-dx-1: 16px; --mote-dy-1: -40px; --mote-dx-2: 65px; --mote-dy-2: -80px; --mote-dx-3: 130px; --mote-dy-3: -110px;--mote-dx-4: 190px; --mote-dy-4: -130px;top: 20%; animation-delay: 0.36s; }
.book-open-mote--10 { --mote-dx-1: 6px;  --mote-dy-1: -8px;  --mote-dx-2: 25px; --mote-dy-2: -20px; --mote-dx-3: 55px;  --mote-dy-3: -35px; --mote-dx-4: 90px;  --mote-dy-4: -45px; top: 38%; animation-delay: 0.5s; width: clamp(2px, 0.35vw, 4px); height: clamp(2px, 0.35vw, 4px); }
.book-open-mote--11 { --mote-dx-1: 20px; --mote-dy-1: 18px;  --mote-dx-2: 75px; --mote-dy-2: 45px;  --mote-dx-3: 140px; --mote-dy-3: 70px;  --mote-dx-4: 195px; --mote-dy-4: 85px;  top: 58%; animation-delay: 0.44s; }
.book-open-mote--12 { --mote-dx-1: 10px; --mote-dy-1: -18px; --mote-dx-2: 40px; --mote-dy-2: -45px; --mote-dx-3: 85px;  --mote-dy-3: -70px; --mote-dx-4: 130px; --mote-dy-4: -90px; top: 33%; animation-delay: 0.52s; width: clamp(5px, 0.65vw, 9px); height: clamp(5px, 0.65vw, 9px); }
.book-open-mote--13 { --mote-dx-1: 25px; --mote-dy-1: -2px;  --mote-dx-2: 90px; --mote-dy-2: -5px;  --mote-dx-3: 170px; --mote-dy-3: -8px;  --mote-dx-4: 240px; --mote-dy-4: -10px; top: 50%; animation-delay: 0.34s; border-radius: 50% 30%; }
.book-open-mote--14 { --mote-dx-1: 12px; --mote-dy-1: 35px;  --mote-dx-2: 48px; --mote-dy-2: 70px;  --mote-dx-3: 90px;  --mote-dy-3: 95px;  --mote-dx-4: 135px; --mote-dy-4: 110px; top: 82%; animation-delay: 0.55s; width: clamp(3px, 0.45vw, 6px); height: clamp(3px, 0.45vw, 6px); }
.book-open-mote--15 { --mote-dx-1: 18px; --mote-dy-1: -25px; --mote-dx-2: 72px; --mote-dy-2: -55px; --mote-dx-3: 135px; --mote-dy-3: -80px; --mote-dx-4: 195px; --mote-dy-4: -100px;top: 28%; animation-delay: 0.4s; }
.book-open-mote--16 { --mote-dx-1: 8px;  --mote-dy-1: 22px;  --mote-dx-2: 32px; --mote-dy-2: 50px;  --mote-dx-3: 65px;  --mote-dy-3: 75px;  --mote-dx-4: 100px; --mote-dy-4: 90px;  top: 70%; animation-delay: 0.58s; border-radius: 30% 50%; }

/* --- Enhanced spine glow during cover open --- gated: play mode handles its own timing */
.storybook--opening .book-desk-scene:not(.book-desk-scene--play) .book-spine-3d-glow {
    animation: coverOpenSpineGlow 1.6s ease-in-out forwards !important;
}

@keyframes coverOpenSpineGlow {
    0%   { opacity: 0; filter: blur(4px); }
    15%  { opacity: 0.3; filter: blur(6px); }
    35%  { opacity: 0.7; filter: blur(10px); }
    50%  { opacity: 1; filter: blur(12px); }
    70%  { opacity: 0.5; filter: blur(8px); }
    100% { opacity: 0; filter: blur(4px); }
}

/* --- Frame ambient glow during opening --- REMOVED.
   box-shadow on a preserve-3d element renders on the 2D bounding rectangle,
   not the 3D silhouette. This caused: (1) glow detaching from the book during
   the cover swing, (2) visual reference that exposed the Z-depth hinge shift,
   (3) compositor artifacts (flickering) from animating box-shadow alongside
   3D child rendering. Play mode glow in animation-timeline.css is unaffected. */

/* --- Physical settle breath — book exhales as it lands flat --- */
/* Starts from the Settling state so it overlaps the tail end of
   the 3D→flat transition. This makes the "arrive" feel organic
   rather than a separate animation that starts after the book is flat. */
/* Gated: play mode handles breathe via ::after with scrub-track timing */
.storybook--settling .book-desk-scene:not(.book-desk-scene--play) .book-frame {
    animation: bookSettleBreathe 1.8s cubic-bezier(0.22, 0.68, 0.18, 1) 0.4s forwards;
}

.storybook--book .book-desk-scene:not(.book-desk-scene--play) .book-frame {
    animation: bookFrameExpand 1.2s cubic-bezier(0.22, 0.68, 0.18, 1) forwards;
}

@keyframes bookSettleBreathe {
    0%   { /* Inherits from CSS transition — don't override transform here */ }
    40%  { box-shadow: 0 3px 12px rgba(0,0,0,0.14), 0 10px 36px rgba(0,0,0,0.16); }
    70%  { box-shadow: 0 2px 8px rgba(0,0,0,0.12), 0 8px 32px rgba(0,0,0,0.18); }
    100% { box-shadow: 0 2px 8px rgba(0,0,0,0.12), 0 8px 32px rgba(0,0,0,0.18),
                       0 16px 48px -8px rgba(0,0,0,0.14), 0 24px 64px -16px rgba(0,0,0,0.08); }
}

@keyframes bookFrameExpand {
    0%   { transform: rotateX(var(--book-open-rotate-x)) rotateY(var(--book-open-rotate-y)) rotateZ(var(--book-open-rotate-z)); }
    40%  { transform: rotateX(var(--book-open-rotate-x)) rotateY(var(--book-open-rotate-y)) rotateZ(var(--book-open-rotate-z)) scale(1.005, 1.003); }
    70%  { transform: rotateX(var(--book-open-rotate-x)) rotateY(var(--book-open-rotate-y)) rotateZ(var(--book-open-rotate-z)) scale(0.999, 1.001); }
    100% { transform: rotateX(var(--book-open-rotate-x)) rotateY(var(--book-open-rotate-y)) rotateZ(var(--book-open-rotate-z)); }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
    .storybook--opening .book-open-mote,
    .storybook--opening .book-spine-3d-glow,
    .storybook--opening .book-frame,
    .storybook--book .book-frame {
        animation-duration: 0.01ms !important;
    }
}
