/* ════════════════════════════════════════════════════════════════════
   TUMBLE TALES — PUBLIC STYLESHEET
   Loaded ONLY on the no-login Tumble Tales pages, scoped to four body classes:
     body.tale-book   the book funnel      /tumble-tales/:slug
     body.tale-room   the library room     /tumble-tales
     body.tale-read   the free chapter     /tumble-tales/:slug/read
     body.tale-join   the grown-ups' list  /tumble-tales/join
   Consumer-only: it uses global design tokens from styles.css (loaded first) via
   var() references and reassigns shared tokens within the body-class scope — it
   declares no design-token block and no animation keyframes of its own (the
   page-turn reuses `ttPageIn` from styles.css).

   A warm tan-paper "storybook" ecosystem, distinct from the club public site
   (public.css, body.public-site) which is a light/white marketing sheet.

   FOUR SURFACES, FOUR KINDS OF THING — the reason one stylesheet serves all of
   them without them blurring into each other:
     the ROOM   is a shelf you browse      (wood + candlelight, §15–16)
     the OBJECT is a book you are holding  (a jacket with two faces, §05)
     the PAGE   is paper and type          (nothing but the story, §17)
     the DESK   is where a grown-up signs  (a sheet on the same wood, §18)

   Progressive enhancement: every page here is complete as plain HTML. The book
   page's base styles stack the two jacket faces as readable cards; taleBook.js
   adds `.is-enhanced` to fold them into one 3D board that turns over, and to
   reveal the two controls (turn, share) that would have nothing to do without
   it. The free chapter does the same with a different verb: its base styles print
   the whole chapter as one scroll, and taleRead.js adds `.is-enhanced` to page it
   ONE BOOK PAGE AT A TIME with the shared flip (§17, TT-51) — an enhancement, never
   a dependency. The room needs no script at all, and the join page needs only the
   shared capture controller, which submits its form without a navigation and is not
   required for it to work.

   ART IS ON HOLD: book covers do not exist (books.cover_image_url is NULL for
   every row), so a shelf spine is a DESIGNED board of bound cloth carrying a volume
   stamp and a vertical title — standing on a plank that is itself nothing but the
   wood ramp — and the book page's cover is a DESIGNED cloth jacket carrying the
   served character art and display type — not broken images and not apologies.
   Dropping real cover art in later means a background-image on the same face;
   nothing else moves.

   Breakpoints: sm=480px | md=640px | lg=768px | xl=1024px

   TABLE OF CONTENTS
   ──────────────────────────────────────────────────────────────────
   01  Scope + token reassignment
   02  Page shell
   03  Persistent chrome (topbar)
   04  Shared page atoms (kicker, title, prose, pull, pillar chip)
   05  The jacket (the book object)
   06  Flap, contents + colophon
   07  Story spread + journal + art
   08  Pillars rail
   09  Cast
   10  Closer (climax tease)
   11  Capture (form + success)
   12  Share (pass it on)
   13  Footer
   14  Responsive + reduced motion
   15  Library room (the shelf page)
   16  Book spines
   17  The free chapter (public read)
   18  The join page (the list, on its own page)
   ════════════════════════════════════════════════════════════════ */

/* === 01. SCOPE + TOKEN REASSIGNMENT === */
/* Warm tan-paper surface. --surface-page/--surface-card are already tan/cream in
   styles.css; the portal accent goes amber so the decorative warm accents on these
   four surfaces resolve warm within this scope only.

   --theme-glow IS SET SEPARATELY, because reassigning --portal-accent does NOT reach
   it: `--theme-glow: var(--portal-accent)` is declared on :root, so it substitutes
   :root's value no matter what a body class says. Every focus ring on all four Tales
   surfaces was therefore still sky-500 #38bdf8 — 1.91:1 on this cream ground, against
   Rule 13's 3:1 floor. Amber would not have fixed it either (1.92:1); the Valley red
   measures 4.57:1 here and is the book's own colour. */
body.tale-book,
body.tale-room,
body.tale-read,
body.tale-join {
    --portal-accent: var(--amber-500);
    --theme-glow:    var(--tale-red);
    --theme-bg-glow: color-mix(in srgb, var(--tale-red) 20%, transparent);
    /* CONTRAST FLOOR (Rule 13). The global --text-muted (#94a3b8) is tuned for the
       DARK portals; on this portal's cream ground (--surface-page #fff1d1) it renders
       at 2.29:1 — measured in-browser, against a 4.5:1 AA requirement for the small
       text every consumer of it here is. Reassigning the existing token inside this
       scope is the Rule 9 pattern (never a new token, never a per-component override):
       one line raises the free chapter's position counter, the cover meta line and the
       creative-prompt aside, plus the jacket meta, the capture disclaimer, the footer
       and the room's shelf note, from 2.29:1 to 6.77:1 — still plainly quieter than
       --text-primary at 11.32:1, which is what "muted" has to mean. */
    --text-muted: var(--slate-600);

    margin: 0;
    min-height: 100vh;
    background: var(--surface-page);
    color: var(--text-secondary);
    font-family: var(--font-tale-body);
    font-size: var(--fs-base);
    line-height: var(--line-height-normal);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* THE BOOK'S GROUND — reader only (body.tale-read), not the jacket or the library.
   Rule 9 reassignment of existing semantic tokens; no new tokens, no :root here.
   Measured on the new pair: --text-primary 10.76:1, --text-muted 4.61:1, both clear
   of the 4.5:1 AA floor for the small text that consumes them.

   IT MUST SIT AFTER THE SHARED body.tale-* BLOCK ABOVE, NOT BEFORE IT. Both selectors
   are (0,2,0), so with this block first the shared block's later --portal-accent and
   --text-muted simply won and two of these six reassignments were dead — measured
   in-browser as #f59e0b / #475569 on the live reader, not the warm pair declared here.

   --theme-glow IS REASSIGNED EXPLICITLY, and that is not redundant with
   --portal-accent. `--theme-glow: var(--portal-accent)` is declared on :root
   (styles.css), so the substitution resolves at :root — a body-scoped --portal-accent
   never reaches it. Without this line every focus ring on the reader painted
   sky-500 #38bdf8, which is 1.90:1 on this paper against Rule 13's 3:1 floor for a
   UI indicator.

   AND IT IS THE VALLEY RED, NOT THE GOLD, FOR A MEASURED REASON. Gold on this paper
   is 1.92:1 and amber 1.90:1 — i.e. swapping the ring to the warm accent would have
   changed its hue and fixed nothing. Measured against #faf0e0: --tale-red 4.54:1,
   --tale-ink-soft 4.61:1, --tale-ink 10.76:1, and those are the only candidates that
   clear 3:1. The red also carries .tale-skip, whose white label needs 4.5:1 on it:
   white on --tale-red is 5.12:1, where white on the gold is 2.16:1. --portal-accent
   stays gold because what consumes IT here is decorative, not an indicator. */
body.tale-read {
    --surface-page:  var(--tale-paper);
    --surface-card:  var(--tale-paper);
    --text-primary:  var(--tale-ink);
    --text-secondary: var(--tale-ink);
    --text-muted:    var(--tale-ink-soft);
    --portal-accent: var(--tale-gold);
    --theme-glow:    var(--tale-red);
    --theme-bg-glow: color-mix(in srgb, var(--tale-red) 20%, transparent);
}

.tale-skip {
    position: absolute;
    left: var(--space-3);
    top: calc(-1 * var(--space-12));
    /* Above --z-sticky on purpose: the persistent topbar IS sticky, and a skip link
       that lands behind the chrome it is trying to skip is not a skip link. */
    z-index: var(--z-overlay);
    padding: var(--space-2) var(--space-4);
    background: var(--theme-glow);
    color: var(--white);
    border-radius: var(--radius-sm);
    transition: top var(--duration-fast) var(--ease-smooth);
}
.tale-skip:focus { top: var(--space-3); }

/* === 02. PAGE SHELL === */
/* One ordinary document column. It used to be a paged stage — twelve sections, one
   shown at a time, a progress ribbon and a fixed Back / Next bar. That deck is
   retired (TT-53): the route is the book itself now, and the real page-turn lives
   one click away at /:slug/read. */
.tale-book__stage {
    width: 100%;
    max-width: 44rem;
    margin: 0 auto;
    padding: var(--space-6) var(--space-4) var(--space-10);
    box-sizing: border-box;
}

/* === 03. PERSISTENT CHROME (TOPBAR) === */

/* The always-there way back. ONE bar, carried by every public Tumble Tales surface —
   the room, the book object and the free read — so "how do I get out of here" has the
   same shape and the same position on all three: a top-left link naming where it
   lands, and a context chip opposite it. Before it, the only way back was a link at
   the very bottom of the document, which on a phone is unreachable. */
.tale-topbar {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    padding-top: calc(var(--space-2) + env(safe-area-inset-top, 0px));
    background: color-mix(in srgb, var(--surface-page) 92%, transparent);
    -webkit-backdrop-filter: blur(var(--blur-nav));
    backdrop-filter: blur(var(--blur-nav));
    border-bottom: var(--border-1) solid var(--surface-border);
}
.tale-topbar__back {
    display: inline-flex;
    align-items: center;
    /* min-width:0 is what makes the ellipsis below actually fire: a flex item
       defaults to min-width:auto and refuses to shrink under its own text. */
    flex: 0 1 auto;
    min-width: 0;
    min-height: var(--space-11);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background-color var(--duration-fast) var(--ease-smooth),
                color var(--duration-fast) var(--ease-smooth);
}
.tale-topbar__back:hover { background: color-mix(in srgb, var(--amber-500) 10%, transparent); }
.tale-topbar__back:active { color: var(--amber-500); }
.tale-topbar__badge {
    flex: 0 0 auto;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--amber-500) 15%, transparent);
    color: var(--text-primary);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    margin: 0;
}

/* === 04. SHARED PAGE ATOMS === */
.tale-kicker {
    display: inline-block;
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--amber-500);
    margin: 0 0 var(--space-3);
}
.tale-title {
    color: var(--text-primary);
    font-size: var(--fs-3xl);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
    text-wrap: balance;
    margin: 0 0 var(--space-4);
}
.tale-prose {
    color: var(--text-secondary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    text-wrap: pretty;
    margin: 0;
}
.tale-pull {
    color: var(--text-primary);
    font-size: var(--fs-xl);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-snug);
    font-style: italic;
    margin: var(--space-5) 0 0;
    padding-left: var(--space-4);
    border-left: 4px solid var(--amber-500);
}

/* === 05. THE JACKET (THE BOOK OBJECT) === */
/* The whole point of /tumble-tales/:slug: a book sat in your hands. Two faces of one
   board at real cover proportions (2 / 3), on a wood-and-candlelight cloth ground —
   the same three-step wood ramp the library shelf is cut from, so the book you pick
   up is made of the room you picked it up in.

   ART IS ON HOLD. books.cover_image_url is NULL for every row, so the front is a
   DESIGNED cover — cloth gradient, a bound spine edge, the served character art and
   display type — not a frame waiting on a file. A printed jacket arrives later as a
   background-image on this same face; nothing else moves.

   PROGRESSIVE ENHANCEMENT. Base (no JS) stacks the two faces as two readable cards
   and hides BOTH turn controls — the labelled button and the cover overlay (TT-59) —
   because with the faces stacked neither has anything to do. `.is-enhanced` folds
   them into one board that pivots about its spine. Neither face holds a focusable
   element, deliberately: the hidden side can never become a keyboard trap, so there
   is nothing to aria-hide and both faces stay in the accessibility tree throughout.
   The cover overlay is a SIBLING of the two faces, not a child of either, which is
   what keeps that true while still making the board itself tappable. */
.tale-jacket {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-5);
    justify-items: center;
    margin: 0 0 var(--space-10);
}

.tale-jacket__object {
    position: relative;
    width: 100%;
    max-width: 20rem;
}
/* PLACEHOLDER VALUE (Rule 7 — no new tokens this batch), following the reader's own
   perspective note in styles.css: calc(var(--space-16) * 12) is 768px, a shade under
   2.5× the board's width, which reads as picking a book up rather than a fisheye. */
.tale-book.is-enhanced .tale-jacket__object {
    perspective: calc(var(--space-16) * 12);
    /* THE BOARD IS THE TAP TARGET (TT-59). The whole jacket turns on a tap, both
       ways, so the pointer states belong to the BOARD — the thing that is actually
       under the finger — and not to the transparent button laid over it, which has
       no hit area by design (see .tale-jacket__cover). Only under `.is-enhanced`:
       with no script there is nothing to turn, so nothing may claim to be tappable.
       Paint-only feedback (a ring, no movement), which is why this needs no
       reduced-motion counterpart of its own — the reduced-motion block below takes
       the whole affordance away instead, because the turn itself is withdrawn. */
    cursor: pointer;
    border-radius: var(--radius-md);
    transition: box-shadow var(--duration-fast) var(--ease-smooth);
}
.tale-book.is-enhanced .tale-jacket__object:hover {
    box-shadow: 0 0 0 var(--border-2) color-mix(in srgb, var(--amber-500) 50%, transparent);
}
.tale-book.is-enhanced .tale-jacket__object:active {
    box-shadow: 0 0 0 var(--border-2) var(--amber-500);
}

.tale-jacket__flip {
    display: grid;
    gap: var(--space-4);
    width: 100%;
}
.tale-book.is-enhanced .tale-jacket__flip {
    display: block;
    position: relative;
    aspect-ratio: 2 / 3;
    transform-style: preserve-3d;
    transition: transform var(--duration-slow) var(--ease-smooth);
}
.tale-book.is-enhanced .tale-jacket__flip.is-flipped { transform: rotateY(180deg); }

.tale-jacket__face {
    position: relative;
    display: flex;
    flex-direction: column;
    aspect-ratio: 2 / 3;
    box-sizing: border-box;
    overflow: hidden;
    padding: var(--space-6) var(--space-5);
    border-radius: var(--radius-md);
    background: linear-gradient(155deg, var(--wood-500), var(--wood-700) 45%, var(--wood-900));
    color: var(--surface-page);
    text-align: center;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
}
.tale-book.is-enhanced .tale-jacket__face {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
}
.tale-book.is-enhanced .tale-jacket__face--back { transform: rotateY(180deg); }

/* The bound edge. On the front it falls on the left, where a spine sits when a book
   faces you; turn the book over and it is on the right, because that is where it
   goes. It is the one detail that stops the two faces reading as two cards. */
.tale-jacket__face::before {
    content: '';
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--space-3);
    background: linear-gradient(90deg,
        var(--wood-900),
        color-mix(in srgb, var(--wood-500) 50%, transparent));
}
.tale-jacket__face--back::before { inset: 0 0 0 auto; }

/* — front — */
.tale-jacket__series {
    color: var(--amber-400);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-widest);
    text-transform: uppercase;
    margin: 0 0 var(--space-3);
}
.tale-jacket__art {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 0 var(--space-4);
    background:
        radial-gradient(circle at 50% 45%,
            color-mix(in srgb, var(--amber-400) 30%, transparent),
            transparent 70%);
}
.tale-jacket__artimg {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.45));
}
.tale-jacket__artmark { font-size: var(--fs-6xl); line-height: 1; }
.tale-jacket__title {
    color: var(--surface-page);
    font-size: clamp(var(--fs-2xl), 7vw, var(--fs-4xl));
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
    text-wrap: balance;
    margin: 0 0 var(--space-2);
}
.tale-jacket__byline {
    color: var(--amber-400);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    margin: 0;
}

/* THE STATE PLATE (TT-56) — stamped on the cover of a book that is not written yet.
   Rule 13: the state is never colour alone, it is the WORD on the plate, and it is
   never a date (Quebec CPA ss.248-249 — this page is child-reachable, so no urgency,
   no countdown, no day to come back on). Dashed, like a proof mark, the same device
   the free read's draft stamp uses (§17), so "not finished" has one shape across the
   surface. `align-self` so the pill hugs its word instead of stretching the column. */
.tale-jacket__state {
    align-self: center;
    margin: var(--space-3) 0 0;
    padding: var(--space-1) var(--space-3);
    border: var(--border-1) dashed color-mix(in srgb, var(--amber-400) 50%, transparent);
    border-radius: var(--radius-pill);
    color: var(--amber-400);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
}

/* — the volume's cloth — */
/* The shelf binds each volume in its own colour (§16); the jacket carries the SAME
   six, so the object you open is recognisably the object you tapped, and Book Two and
   Book Five are not the same brown board. Only the MID-STOP of the face's own wood
   ramp is tinted — the bound edge, the shadow, the type and the 2/3 board are exactly
   where the rules above put them, and a real printed jacket still drops in as one
   background-image with nothing else moving. Same colour ladder and same 30% step as
   the open spines in §16, so the two surfaces cannot drift apart.
   Bounded to the six volumes that exist: a seventh keeps the plain board rather than
   wearing a class with no rule behind it (the view clamps `volumeClass` to 1–6). */
.tale-jacket--vol-1 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--amber-500) 30%, var(--wood-700)) 45%, var(--wood-900)); }
.tale-jacket--vol-2 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--emerald-500) 30%, var(--wood-700)) 45%, var(--wood-900)); }
.tale-jacket--vol-3 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--sky-500) 30%, var(--wood-700)) 45%, var(--wood-900)); }
.tale-jacket--vol-4 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--violet-500) 30%, var(--wood-700)) 45%, var(--wood-900)); }
.tale-jacket--vol-5 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--red-500) 30%, var(--wood-700)) 45%, var(--wood-900)); }
.tale-jacket--vol-6 .tale-jacket__face { background-image: linear-gradient(155deg, var(--wood-500), color-mix(in srgb, var(--indigo-400) 30%, var(--wood-700)) 45%, var(--wood-900)); }

/* — back — */
/* THE BACK IS A FIXED PANEL. The board is 2 / 3 at 20rem whatever is written on it,
   the same constraint a printed back cover has: the copy is WRITTEN to fit. New
   back-cover copy (taleBooks jacket.blurb / jacket.about.body) still has to be
   checked against that panel — roughly 180 characters of blurb and 150 of publisher
   note — not dropped in and hoped for.
   BUT A FIXED PANEL MUST NOT DESTROY TEXT. The face clips (`overflow: hidden`), and
   the panel's height is fixed in rem while its contents are sized in rem too — so a
   visitor at 200% text size (WCAG 2.1 AA 1.4.4, Resize Text) loses the publisher
   note and the plate off the bottom with no way to reach them. Measured at the
   default size the copy fills ~356px of the 432px content box; at 200% it needs
   ~712px. Letting the BACK face scroll its own box is the remedy already used one
   section down for exactly this ("a stop whose text outgrows a small phone scrolls
   INSIDE its own box", §17) — at the default size nothing overflows, so no scrollbar
   appears and the cover is unchanged. The front face is deliberately NOT given this:
   its art is `flex: 1 1 auto; min-height: 0`, so it absorbs the growth itself. */
.tale-jacket__face--back { overflow-y: auto; }

.tale-jacket__blurb {
    color: var(--surface-page);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
    text-wrap: pretty;
    margin: 0 0 var(--space-3);
}
.tale-jacket__quote {
    color: var(--amber-400);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-bold);
    font-style: italic;
    line-height: var(--line-height-snug);
    margin: 0 0 var(--space-4);
}
/* `margin-top: auto` is what pins the publisher note and the plate to the foot of
   the jacket however long the blurb runs — the same place they sit on a real one. */
.tale-jacket__about {
    margin: auto 0 var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--wood-900) 50%, transparent);
    text-align: left;
}
.tale-jacket__aboutlabel {
    color: var(--amber-400);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    margin: 0 0 var(--space-1);
}
.tale-jacket__abouttext {
    color: var(--surface-page);
    font-size: var(--fs-xs);
    line-height: var(--line-height-relaxed);
    margin: 0;
}
.tale-jacket__plate {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--surface-page) 15%, transparent);
    text-align: left;
}
/* A designed stand-in for the barcode a printed jacket carries: it says nothing and
   is hidden from assistive tech — it exists so the foot of a back cover looks like
   the foot of a back cover. Bars are token widths (2px on, 2px off), not raw px. */
.tale-jacket__bars {
    flex: 0 0 auto;
    width: var(--space-10);
    height: var(--space-6);
    border-radius: var(--radius-sm);
    background: repeating-linear-gradient(90deg,
        var(--surface-page) 0,
        var(--surface-page) var(--space-05),
        transparent var(--space-05),
        transparent var(--space-1));
}
.tale-jacket__platetext {
    color: var(--surface-page);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-snug);
    margin: 0;
}

/* — tap the cover itself (TT-59) — */
/* The keyboard and assistive-tech half of the board's own tap. It is a real
   <button> — named, focusable, Enter/Space-operable, carrying aria-pressed — laid
   over the board so its focus ring outlines the thing it turns.

   pointer-events: none IS THE POINT, not an oversight. A transparent button covering
   the board would become the hit target for every touch and wheel gesture aimed at
   what is underneath — and the back face is a real scroll container (see the
   `overflow-y: auto` note above, the WCAG 1.4.4 remedy for 200% text). Scroll
   chaining starts at the EVENT TARGET, so an overlay that took the touch would chain
   to the page and leave the publisher note clipped and unreachable for exactly the
   visitors that remedy exists for; it would also make the blurb unselectable. So the
   pointer path is a click listener on .tale-jacket__object (above), the keyboard path
   is this button, and the click that Enter/Space fires on it bubbles into the same
   listener — one handler, one state, no double-fire.

   Hidden until a script runs, for the same reason the turn button is: with both faces
   stacked there is nothing to turn, and an unreachable control must not be in the tab
   order either. `display: none` removes it from both. */
.tale-jacket__cover {
    display: none;
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    pointer-events: none;
}
.tale-book.is-enhanced .tale-jacket__cover { display: block; }

/* — turn it over — */
/* Hidden until a script is actually running, because without one there is nothing
   to turn: the base layout already shows both faces. The label is FIXED ("Turn it
   over" is true of either face) and state travels on aria-pressed alone (TT-61) —
   never on appearance alone (Rule 13). */
.tale-jacket__turn {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--space-11);
    min-width: var(--space-11);
    padding: var(--space-2) var(--space-5);
    border: var(--border-2) solid var(--surface-border);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    cursor: pointer;
    transition: transform var(--duration-fast) var(--ease-spring),
                background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth);
}
.tale-book.is-enhanced .tale-jacket__turn { display: inline-flex; }
.tale-jacket__turn:hover {
    border-color: var(--amber-500);
    background: color-mix(in srgb, var(--amber-500) 10%, var(--surface-card));
}
.tale-jacket__turn:active { transform: translateY(var(--press-sm)); }
.tale-jacket__turnicon { font-size: var(--fs-lg); line-height: 1; }

/* — the hand-sell: the one thing to do with the book you are holding — */
.tale-jacket__hand {
    max-width: 30rem;
    text-align: center;
}
.tale-jacket__tagline {
    color: var(--text-primary);
    font-size: var(--fs-xl);
    font-weight: var(--font-weight-bold);
    font-style: italic;
    line-height: var(--line-height-snug);
    text-wrap: balance;
    margin: 0 0 var(--space-2);
}
.tale-jacket__meta {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    margin: 0 0 var(--space-5);
}
/* The CTA is a full sentence and wraps to two lines on a narrow phone. .tale-btn
   centres it as a flex item; this centres the wrapped lines inside it, which the
   768px layout would otherwise leave ragged when the column turns left-aligned. */
.tale-jacket__open { text-align: center; }
.tale-jacket__note {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: var(--line-height-snug);
    margin: var(--space-3) 0 0;
}
/* "Not open yet" is a STATE, so it is carried by weight and wording as well as
   colour — there is no CTA next to it to borrow meaning from (Rule 13). */
.tale-jacket__note--closed {
    color: var(--text-primary);
    font-weight: var(--font-weight-semibold);
}

/* Primary CTA button — shared by the jacket, the colophon and the capture form. */
.tale-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 52px;
    padding: var(--space-4) var(--space-8);
    border: none;
    border-radius: var(--radius-pill);
    background: linear-gradient(135deg, var(--amber-400), var(--amber-500));
    color: var(--hq-brown-dark);
    font-family: inherit;
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-bold);
    cursor: pointer;
    box-shadow: 0 10px 24px color-mix(in srgb, var(--amber-500) 30%, transparent);
    transition: transform var(--duration-fast) var(--ease-spring),
                box-shadow var(--duration-base) var(--ease-smooth);
}
.tale-btn:hover { box-shadow: 0 14px 30px color-mix(in srgb, var(--amber-500) 50%, transparent); }
.tale-btn:active { transform: translateY(var(--press-md)); }
.tale-btn--full { width: 100%; }

/* A QUIET PILL — the same atom with half the voice (TT-58).
   It exists for exactly one kind of link: the one on a CHILD-REACHABLE page that is
   addressed to a grown-up — the room's link to the list, and the single action on
   the jacket of a book nobody has written yet. Quebec CPA ss.248-249 is the reason
   it is not the amber CTA above: on a page a child is reading, the grown-ups' ask
   must not be the loudest thing on it. The book is.
   A MODIFIER, not a second class in each file — two callers is the DRY threshold
   (Rule 2), and both want the identical object. Motion safety, the focus ring and
   the press are inherited from .tale-btn, including the reduced-motion block below,
   which re-declares .tale-btn's transition AFTER this rule and therefore still wins. */
.tale-btn--quiet {
    min-height: var(--space-11);
    padding: var(--space-2) var(--space-5);
    border: var(--border-1) solid var(--surface-border);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    text-align: center;
    box-shadow: none;
    transition: background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth),
                color var(--duration-fast) var(--ease-smooth),
                transform var(--duration-fast) var(--ease-spring);
}
.tale-btn--quiet:hover {
    border-color: var(--amber-500);
    color: var(--amber-500);
    box-shadow: none;
}
.tale-btn--quiet:active { background: color-mix(in srgb, var(--amber-500) 10%, transparent); }

/* === 06. FLAP, CONTENTS + COLOPHON === */
/* The rest of the object, in the order you meet it on a real one: the inside flap,
   then what happens, then the back of the book. These carry the copy that used to be
   paged as deck slides — the walk/gist fork that paged between them is gone with it,
   because the only fork that matters now is "read it" or "read about it", and that
   is the page's own shape. */

/* The skip link lands here, and .tale-topbar (§03) is painted over the top of the
   scrollport — without this the heading arrives underneath the bar. --space-15 (60px)
   is the bar's own box: --space-11 of link plus --space-2 above and below. */
.tale-flap {
    margin: 0 0 var(--space-10);
    scroll-margin-top: calc(var(--space-15) + env(safe-area-inset-top, 0px));
}
/* Spacing only — the flap's paragraphs ARE .tale-prose (§04) and must not be a
   second copy of it. This gives three stacked lines their gap and nothing else. */
.tale-flap__line { margin-bottom: var(--space-4); }

.tale-contents { margin: 0 0 var(--space-10); }
/* One beat of the story. The rule above it is the gutter between two spreads of a
   book you are leafing through — not a card, because a card would make five equal
   tiles out of one continuous read. */
.tale-chapter {
    margin: var(--space-8) 0 0;
    padding: var(--space-8) 0 0;
    border-top: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 30%, transparent);
}
.tale-chapter__title {
    color: var(--text-primary);
    font-size: var(--fs-2xl);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-snug);
    text-wrap: balance;
    margin: 0 0 var(--space-4);
}

/* The back of the book: the same single action, repeated where a reader who has read
   everything actually is. Share lives here rather than beside the jacket so the top
   of the page keeps exactly one primary action. */
.tale-colophon {
    margin: 0 0 var(--space-8);
    padding: var(--space-8) var(--space-5);
    border-radius: var(--radius-lg);
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--amber-500) 20%, transparent),
        color-mix(in srgb, var(--amber-400) 10%, transparent));
    text-align: center;
}
.tale-colophon__open { margin: 0 0 var(--space-6); }

/* === 07. STORY SPREAD + JOURNAL + ART === */
.tale-spread__art {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
    margin: 0 0 var(--space-5);
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--amber-500) 30%, transparent),
            color-mix(in srgb, var(--amber-500) 8%, transparent));
    display: flex;
    align-items: center;
    justify-content: center;
}
.tale-spread__img { width: 100%; height: 100%; object-fit: cover; }
.tale-spread__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
}
.tale-spread__placeholder-emoji { font-size: var(--fs-6xl); line-height: 1; }
.tale-spread__placeholder-name {
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* Tumble's in-story journal — the signature device (italic, preserved line breaks). */
.tale-journal {
    margin: var(--space-2) 0 var(--space-5);
    padding: var(--space-5) var(--space-6);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--amber-500) 10%, var(--surface-card));
    border: 1px dashed color-mix(in srgb, var(--amber-500) 40%, transparent);
    color: var(--text-primary);
    font-style: italic;
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    white-space: pre-wrap;
}

/* === 08. PILLARS RAIL === */
.tale-pillars {
    margin: 0 0 var(--space-10);
    text-align: center;
}
.tale-pillars__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-3);
    margin: var(--space-6) 0 0;
    text-align: left;
}
.tale-pillar {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
}
.tale-pillar__emoji {
    font-size: var(--fs-2xl);
    line-height: 1;
    flex-shrink: 0;
}
.tale-pillar__name {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-bold);
    margin: 0 0 2px;
}
.tale-pillar__rung {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--line-height-snug);
    margin: 0;
}
.tale-heart {
    margin: var(--space-6) 0 0;
    padding: var(--space-5) var(--space-6);
    border-radius: var(--radius-lg);
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--amber-500) 20%, transparent),
        color-mix(in srgb, var(--amber-400) 10%, transparent));
    text-align: center;
}
.tale-heart__label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--amber-500);
    margin: 0 0 var(--space-2);
}
.tale-heart__line {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-snug);
    margin: 0;
}

/* === 09. CAST === */
.tale-cast {
    margin: 0 0 var(--space-10);
    text-align: center;
}
.tale-cast__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
    margin: var(--space-6) 0 0;
    text-align: left;
}
.tale-cast__card {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
}
.tale-cast__portrait {
    width: 68px;
    height: 68px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--amber-500) 30%, transparent),
            color-mix(in srgb, var(--amber-500) 8%, transparent));
}
.tale-cast__img { width: 100%; height: 100%; object-fit: cover; }
.tale-cast__emoji { font-size: var(--fs-4xl); line-height: 1; }
.tale-cast__name {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-bold);
    margin: 0;
}
.tale-cast__virtue {
    color: var(--amber-500);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    margin: 2px 0 4px;
}
.tale-cast__note {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: var(--line-height-snug);
    margin: 0;
}
.tale-stages {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    margin: var(--space-6) 0 0;
}
.tale-stage-chip {
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--amber-500) 15%, transparent);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
}

/* === 10. CLOSER (climax tease) === */
.tale-closer {
    margin: 0 0 var(--space-10);
    text-align: center;
}

/* === 11. CAPTURE (form + success) === */
/* SHARED, and no longer used by the book page: the only two captures left on the
   public surface are the library room's series list and the grown-up handoff at the
   end of the free read (views/partials/taleCapture.ejs renders both). TT-53 removed
   the third — the book page sells nothing and collects nothing.
   Both remaining hosts centre their own section (.tale-notify, .tale-read__end), so
   the bare `.tale-capture` wrapper rule went with the slide it centred. */
.tale-capture__card {
    margin: var(--space-6) auto 0;
    max-width: 30rem;
    padding: var(--space-7) var(--space-6);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    box-shadow: 0 16px 40px color-mix(in srgb, var(--amber-500) 15%, transparent);
    text-align: left;
}
.tale-capture__sub {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    margin: 0 0 var(--space-5);
}
.tale-field { margin: 0 0 var(--space-4); }
.tale-field__label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
    margin: 0 0 var(--space-2);
}
.tale-field__input,
.tale-field__textarea {
    width: 100%;
    box-sizing: border-box;
    min-height: 48px;
    padding: var(--space-3) var(--space-4);
    border: var(--border-2) solid var(--surface-border);
    border-radius: var(--radius-md);
    background: var(--white);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--fs-base);
    transition: border-color var(--duration-fast) var(--ease-smooth),
                box-shadow var(--duration-fast) var(--ease-smooth);
}
.tale-field__textarea { min-height: 88px; resize: vertical; }
.tale-field__hint {
    margin: var(--space-2) 0 0;
    font-size: var(--fs-xs);
    line-height: var(--line-height-relaxed);
    color: var(--text-secondary);
}
.tale-field__input:focus,
.tale-field__textarea:focus {
    outline: none;
    border-color: var(--amber-500);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber-500) 20%, transparent);
}
/* Adult attestation (TT-16) — the required "I'm a parent or guardian" tick. The whole
   row IS the <label>, so the tap target is the full card, not the 20px box. */
.tale-field--check {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    min-height: var(--space-11);
    padding: var(--space-3) var(--space-4);
    border: var(--border-2) solid var(--surface-border);
    border-radius: var(--radius-md);
    background: var(--white);
    cursor: pointer;
    transition: border-color var(--duration-fast) var(--ease-smooth),
                box-shadow var(--duration-fast) var(--ease-smooth);
}
.tale-field--check:hover { border-color: var(--amber-500); }
.tale-field--check:active,
.tale-field--check:focus-within {
    border-color: var(--amber-500);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber-500) 20%, transparent);
}
.tale-field__check {
    flex: 0 0 auto;
    width: var(--space-5);
    height: var(--space-5);
    margin-top: var(--space-05);
    -webkit-appearance: auto;
    appearance: auto;
    accent-color: var(--amber-500);
    cursor: pointer;
}
.tale-field__check-text {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
}

/* Honeypot — off-screen but still submittable so bots fill it. */
.tale-honeypot {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.tale-capture__disclaimer {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--line-height-snug);
    margin: var(--space-4) 0 0;
}
.tale-capture__error {
    display: none;
    color: var(--color-danger);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    margin: var(--space-3) 0 0;
}
.tale-capture__error.is-visible { display: block; }
.tale-capture__noscript {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    margin: var(--space-3) 0 0;
}

/* Success panel — revealed by JS after a 200. */
.tale-capture__success {
    display: none;
    text-align: center;
    padding: var(--space-6) var(--space-4);
    animation: ttPageIn var(--duration-base) var(--ease-spring);
}
.tale-capture__success.is-visible { display: block; }
.tale-capture__success-emoji { font-size: var(--fs-6xl); line-height: 1; }
.tale-capture__success-title {
    color: var(--text-primary);
    font-size: var(--fs-2xl);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    margin: var(--space-3) 0 var(--space-2);
}
.tale-capture__success-body {
    color: var(--text-secondary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    max-width: 26rem;
    margin: 0 auto;
}

/* === 12. SHARE (pass it on) === */
/* Sits in the book object's colophon (§06), under the repeated "open the book" CTA —
   the last thing a reader who has read the whole page meets. Base (no-JS) mode prints
   the plain selectable URL and hides the button, which would have nothing to do;
   `.is-enhanced` reveals the button, and `.has-share` retires the printed URL once
   the script has confirmed a real navigator.share / clipboard path. */
.tale-share {
    margin: var(--space-6) auto 0;
    max-width: 30rem;
    text-align: center;
}
.tale-share__prompt {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    margin: 0 0 var(--space-3);
}
.tale-share__btn {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--space-11);
    min-width: var(--space-11);
    padding: var(--space-3) var(--space-6);
    border: var(--border-2) solid var(--amber-500);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--fs-base);
    font-weight: var(--font-weight-bold);
    cursor: pointer;
    transition: transform var(--duration-fast) var(--ease-spring),
                background-color var(--duration-fast) var(--ease-smooth),
                box-shadow var(--duration-base) var(--ease-smooth);
}
.tale-book.is-enhanced .tale-share__btn { display: inline-flex; }
.tale-share__btn:hover {
    background: color-mix(in srgb, var(--amber-500) 10%, var(--surface-card));
    box-shadow: 0 10px 24px color-mix(in srgb, var(--amber-500) 20%, transparent);
}
.tale-share__btn:active { transform: translateY(var(--press-sm)); }
/* Copied: the label + icon carry the message; the tint is only reinforcement. */
.tale-share__btn.is-copied { background: color-mix(in srgb, var(--amber-500) 20%, var(--surface-card)); }
.tale-share__icon { font-size: var(--fs-lg); line-height: 1; }

/* Reserved height so the announcement never shifts the slide underneath it. */
.tale-share__status {
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-snug);
    min-height: var(--space-6);
    margin: var(--space-3) 0 0;
}
.tale-share__fallback { margin: 0; }
.tale-share.has-share .tale-share__fallback { display: none; }
.tale-share__url {
    display: inline-block;
    max-width: 100%;
    padding: var(--space-2) var(--space-4);
    border: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 40%, transparent);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    word-break: break-all;
    cursor: text;
    user-select: all;
    transition: border-color var(--duration-fast) var(--ease-smooth),
                background-color var(--duration-fast) var(--ease-smooth);
}
.tale-share__url:hover,
.tale-share__url:active {
    border-color: var(--amber-500);
    background: color-mix(in srgb, var(--amber-500) 10%, var(--surface-card));
}

/* === 13. FOOTER === */
.tale-footer {
    text-align: center;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    padding: var(--space-8) var(--space-4) calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
}
.tale-footer__row { margin: 0 0 var(--space-2); }
.tale-footer__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--space-11);
    padding: var(--space-2) var(--space-4);
    color: var(--amber-500);
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    border-radius: var(--radius-pill);
    transition: background-color var(--duration-fast) var(--ease-smooth),
                color var(--duration-fast) var(--ease-smooth);
}
.tale-footer__link:hover { background: color-mix(in srgb, var(--amber-500) 10%, transparent); }
.tale-footer__link:active { color: var(--text-primary); }

/* === 14. RESPONSIVE + REDUCED MOTION === */
@media (min-width: 640px) {
    .tale-book__stage { padding-left: var(--space-6); padding-right: var(--space-6); }
    .tale-pillars__grid { grid-template-columns: 1fr 1fr; }
    .tale-cast__grid { grid-template-columns: 1fr 1fr; }
    .tale-title { font-size: var(--fs-4xl); }
}

/* Once there is room, the book and what to do with it sit side by side — the way a
   book is actually handed to you. The turn control tucks under the jacket it turns,
   and the hand-sell column spans both rows beside them. */
@media (min-width: 768px) {
    .tale-jacket {
        grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
        grid-template-areas:
            "object hand"
            "turn   hand";
        align-items: start;
        justify-items: start;
        column-gap: var(--space-8);
    }
    .tale-jacket__object { grid-area: object; }
    .tale-jacket__turn { grid-area: turn; }
    .tale-jacket__hand {
        grid-area: hand;
        text-align: left;
    }
}

/* xl: 1024px — the large-viewport tier (Rule 15 stops the ladder here, so 1280
   through 2560 are served by these VALUES, not by another query).
   The jacket's own max-width was already 22rem at this step, but the 768 block
   above caps its GRID COLUMN at 20rem, so the track won the cascade and the
   board never actually grew — the rule was inert. Stepping the track too is what
   makes the book art bigger on a desk. It costs the reading column nothing: the
   hand-sell column is 1fr beside it, and .tale-book__stage's 44rem measure — and
   every other prose measure on this surface — is deliberately left alone. */
@media (min-width: 1024px) {
    .tale-jacket { grid-template-columns: minmax(0, 22rem) minmax(0, 1fr); }
    .tale-jacket__object { max-width: 22rem; }
    .tale-title { font-size: var(--fs-5xl); }
}

/* Focus visibility — branded ring on every interactive element (Rule 13). */
.tale-btn:focus-visible,
.tale-jacket__cover:focus-visible,
.tale-jacket__turn:focus-visible,
.tale-field__input:focus-visible,
.tale-field__textarea:focus-visible,
.tale-field__check:focus-visible,
.tale-share__btn:focus-visible,
.tale-share__url:focus-visible,
.tale-topbar__back:focus-visible,
.tale-skip:focus-visible {
    outline: var(--border-2) solid var(--theme-glow);
    outline-offset: var(--outline-offset);
}

@media (prefers-reduced-motion: reduce) {
    /* THE JACKET DOES NOT TURN. A 600ms rotateY of a full-height board is a
       vestibular trigger with no safe reduced version — a shorter or softer spin is
       still a spin — so reduced motion gets the BASE presentation of the jacket
       instead: both faces stacked, fully readable, and no control offering to spin
       anything. Nothing is lost, because the stack IS the whole jacket; it is the
       same layout a visitor with JavaScript blocked already sees. */
    /* The BOARD stops claiming to be tappable too (TT-59): the jacket is not going
       to turn, so the cursor, the hover ring and the press ring all go with it, and
       the overlay button leaves the tab order the same way the visible one does.
       taleBook.js reads the same query from matchMedia and REFUSES the turn while it
       matches — CSS alone would leave the board's listener quietly toggling a class
       that now renders identically, i.e. a tap that appears to do nothing. */
    .tale-book.is-enhanced .tale-jacket__object {
        perspective: none;
        cursor: auto;
    }
    .tale-book.is-enhanced .tale-jacket__object:hover,
    .tale-book.is-enhanced .tale-jacket__object:active { box-shadow: none; }
    .tale-book.is-enhanced .tale-jacket__cover { display: none; }
    .tale-book.is-enhanced .tale-jacket__flip,
    .tale-book.is-enhanced .tale-jacket__flip.is-flipped {
        display: grid;
        aspect-ratio: auto;
        transform: none;
        transition: none;
    }
    /* Both faces, and the back one AGAIN at the weight its own rule carries. The
       back is rotated 180° so it reads correctly once the board has turned; leave
       that rule winning here and a reduced-motion visitor gets a mirrored back
       cover. Matching the specificity is the fix — not raising it. */
    .tale-book.is-enhanced .tale-jacket__face,
    .tale-book.is-enhanced .tale-jacket__face--back {
        position: relative;
        inset: auto;
        transform: none;
        backface-visibility: visible;
    }
    .tale-book.is-enhanced .tale-jacket__turn { display: none; }

    .tale-capture__success.is-visible { animation: none; }

    .tale-btn,
    .tale-field__input,
    .tale-field__textarea,
    .tale-field--check,
    .tale-share__btn,
    .tale-share__url,
    .tale-topbar__back {
        transition: opacity var(--duration-fast) var(--ease-smooth);
    }
    .tale-btn:active,
    .tale-share__btn:active { transform: none; }
}

/* === 15. LIBRARY ROOM (THE SHELF PAGE) === */
/* The series room at /tumble-tales: a header, then a piece of FURNITURE with books
   standing in it. The bookcase is built from the wood ramp alone — no texture art
   exists and none is coming (TT-23 is on hold), so the plank, the recess and the
   candlelight are pure CSS and are meant to look deliberate, not unfinished. */
/* MOBILE DENSITY (TT-49). The books are the product; the header is the label on
   the door. Every size and gap below is the PHONE value — the base layer — and the
   640px block at the foot of this file restores the roomier desktop rhythm. The
   spines and the free read's prose stay generous: the readers are 8–13 and legible
   type is a requirement there, not bloat. */
.tale-room__stage {
    width: 100%;
    max-width: 52rem;
    margin: 0 auto;
    padding: var(--space-4) var(--space-4) var(--space-10);
    box-sizing: border-box;
}

.tale-room__header {
    text-align: center;
    margin: 0 0 var(--space-6);
}
/* The series eyebrow's own bottom gap — the shared .tale-kicker rhythm is tuned for
   a full-bleed funnel slide, which is a looser room than this stacked header. */
.tale-room__kicker { margin-bottom: var(--space-2); }
.tale-room__title {
    color: var(--text-primary);
    /* Same ladder as the free read's title, deliberately: one display scale across
       the surface. 9vw put this at near-display size on a 390px phone. */
    font-size: clamp(var(--fs-3xl), 7vw, var(--fs-5xl));
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
    text-wrap: balance;
    margin: 0 0 var(--space-2);
}
.tale-room__lede {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-snug);
    font-style: italic;
    text-wrap: balance;
    margin: 0 0 var(--space-3);
}
.tale-room__blurb {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    max-width: 36rem;
    margin: 0 auto;
}

.tale-room__shelfnote {
    text-align: center;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    margin: 0 0 var(--space-3);
}

.tale-room__heart { margin: var(--space-8) 0 0; }

/* --- 15a. The bookcase --------------------------------------------------- */
/* The piece of furniture the books stand in. Three jobs, three layers, no art:
   the unlit ground (--wood-900), one warm source high on the left falling off into
   it (--amber-warm-30, the live cross-portal candle colour — the room mints nothing
   of its own for it), and a soft contact shadow so the case sits ON the page rather
   than being printed on it. */
.tale-bookcase {
    margin: 0;
    border-radius: var(--radius-md);
    background:
        radial-gradient(115% 85% at 16% 0%, var(--amber-warm-30) 0, transparent 60%),
        var(--wood-900);
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
    overflow: hidden;
}

/* The interior: the recess the books sit INSIDE. --shadow-inset is the system's
   only inset shadow and was minted with the wood ramp for exactly this — without it
   the spines read as stickers on a brown stripe instead of books in a shelf. */
.tale-bookcase__shelf {
    position: relative;
    padding: var(--space-7) 0 0;
    box-shadow: var(--shadow-inset);
}

/* THE PLANK. The whole reason the wood ramp is three tokens and not one: a lit
   front lip catching the candle (--wood-500), the plank face under it (--wood-700),
   and the fall-off into the unlit ground at the bottom (--wood-900). That ramp is
   what makes a flat gradient read as a solid board with a front edge.
   The negative top margin is not a nudge — it is the overlap that lets the rail
   above carry vertical padding (headroom for a focus ring and the lift-out hover,
   neither of which may be clipped by the scroller) while the books' feet still land
   exactly on the lip. */
.tale-bookcase__shelf::after {
    content: '';
    display: block;
    height: var(--space-6);
    margin-top: calc(-1 * var(--space-3));
    background: linear-gradient(to bottom,
        var(--wood-500) 0,
        var(--wood-500) var(--border-4),
        var(--wood-700) var(--border-4),
        var(--wood-700) 60%,
        var(--wood-900) 100%);
}

/* --- 15b. The rail ------------------------------------------------------- */
/* A HORIZONTAL SCROLLER, DELIBERATELY. Six spines at a width that keeps their type
   legible do not fit across a 390px phone, and shrinking them to fit turns every
   book into a sliver. Rule 15's carousel pattern instead: the thumb flicks book to
   book and the half-visible spine at the edge is the affordance that says there are
   more. From 640px up the same rail fits without scrolling and simply centres — one
   layout, no breakpoint swap.

   KEYBOARD. Every spine is a link, so Tab walks all six and the rail can never be a
   trap: there is no focusable-content gap and no tabindex hack holding it open.
   MEASURED CAVEAT: Chromium does not always scroll a horizontal flex scroller far
   enough on Tab — on a 390px viewport the fifth spine can sit ~26px past the edge
   with its ring still visible on three sides. That is the browser, not this rule
   (a bare 6-item flex scroller with no snap, no padding and no centring reproduces
   it exactly), and `mandatory`/`proximity`/`none` and every scroll-padding and
   scroll-margin value behave identically. It stays within WCAG 2.4.11 — the
   focused spine is never entirely hidden — so the pattern stands. */
.tale-shelf {
    list-style: none;
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-3) var(--space-4);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--space-4);
    /* Centres the row once it fits (desktop) without clipping the first spine when
       it does not (phone). A browser without `safe` drops the whole declaration and
       falls back to flex-start, which is the phone behaviour anyway. */
    justify-content: safe center;
}
.tale-shelf__item {
    flex: 0 0 auto;
    margin: 0;
    scroll-snap-align: start;
}

/* --- 15c. The shelf-edge plate ------------------------------------------- */
/* The little card slotted into the front rail of a library shelf. It is the room's
   one piece of business — the free chapter — kept as furniture rather than as a
   second stack of buttons. Rendered only for books that are genuinely readable, so
   with none the rail is bare wood and nothing appears. */
.tale-bookcase__plate {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-3) var(--space-4) var(--space-4);
}
.tale-bookcase__plate-item { margin: 0; }
.tale-bookcase__plate-link {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    min-height: var(--space-11);
    padding: var(--space-2) var(--space-4);
    border: var(--border-1) solid color-mix(in srgb, var(--wood-500) 50%, transparent);
    border-radius: var(--radius-pill);
    color: var(--cream-95);
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth),
                transform var(--duration-fast) var(--ease-spring);
}
.tale-bookcase__plate-link:hover {
    background: color-mix(in srgb, var(--amber-500) 15%, transparent);
    border-color: var(--wood-500);
}
.tale-bookcase__plate-link:active { transform: translateY(var(--press-sm)); }
.tale-bookcase__plate-title {
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-bold);
}
.tale-bookcase__plate-cta {
    color: var(--amber-400);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
}

/* --- 15d. The grown-ups' link -------------------------------------------- */
/* TT-58 — THE ROOM NO LONGER CARRIES A FORM, FOLDED OR OTHERWISE. What sat here was
   a <details> that unfolded the whole capture card in the middle of a shelf; it is
   now one link to /tumble-tales/join, the page that owns that form. The rules for
   the panel itself moved WITH it, to §18 — a section headed LIBRARY ROOM must not
   keep styling another page's component.
   The link is .tale-btn--quiet (§05), shared with the coming-soon jacket, so all
   that is left here is where it sits. */
.tale-room__join {
    margin: var(--space-5) 0 0;
    text-align: center;
}

/* === 16. BOOK SPINES === */
/* A spine is a BOOK STANDING UP — a narrow board of bound cloth, not a card lying
   on its back. Read top to bottom: a volume stamp at the head, the title set
   vertically down the board, a mark at the foot. The whole board is the link.
   The board is a deliberate placeholder for art that does not exist yet (every
   books.cover_image_url is NULL, TT-23 is on hold): when a real cover lands it goes
   inside this same box and nothing around it moves.
   Width and height are rem, not --space-*: they are the object's own dimensions,
   the way .tale-jacket__object and .tale-book__stage are sized. Padding, gaps and
   margins stay on the token scale. */
.tale-spine {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    box-sizing: border-box;
    /* 64px wide clears the 44px touch floor with room to spare, and six of these
       overflow a 390px phone on purpose — the rail scrolls. */
    width: 4rem;
    height: 12.5rem;
    padding: var(--space-3) var(--space-1);
    border-radius: var(--radius-xs);
    /* Unlit by default. A lit book overrides only the colour, below — which is what
       makes an unwritten book read as one standing in the gloom. */
    background-color: var(--wood-900);
    /* Cloth under a candle set high on the left: the leading edge catches the light,
       the trailing edge falls into the book's own shadow. */
    background-image: linear-gradient(to right,
        color-mix(in srgb, var(--wood-500) 30%, transparent) 0,
        transparent 35%,
        rgba(0, 0, 0, 0.3) 100%);
    border-left: var(--border-2) solid color-mix(in srgb, var(--wood-500) 50%, transparent);
    color: var(--cream-95);
    text-decoration: none;
    /* The book's own contact shadow on the plank (neutral black — Rule 8 tier 3). */
    box-shadow: 0 6px 10px rgba(0, 0, 0, 0.45);
    transition: transform var(--duration-fast) var(--ease-spring),
                box-shadow var(--duration-base) var(--ease-smooth);
}
/* Tilting a book out of the shelf, then pressing it back in. */
.tale-spine:hover {
    transform: translateY(calc(-1 * var(--press-md)));
    box-shadow: 0 12px 20px rgba(0, 0, 0, 0.55);
}
.tale-spine:active {
    transform: translateY(var(--press-sm));
    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
}

/* The finished book is the thick one on the shelf, and the tallest. */
.tale-spine--open {
    width: 5rem;
    height: 14rem;
}

/* LOCKED (Rule 13 — never colour alone). Three signals, only one of which is
   colour: the board stays unlit, the foot carries a padlock, and it says "Soon" in
   words, with the full sentence handed to a screen reader in the markup. */
.tale-spine--locked {
    color: var(--cream-70);
    border-left-color: color-mix(in srgb, var(--wood-500) 20%, transparent);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.55);
}
/* Books are not all the same height. Deterministic, designed variation — the volume
   order is the only thing it keys on, and it never touches the lit book, so a
   published Book Two simply grows to full height. */
.tale-shelf__item:nth-child(3n + 2) .tale-spine--locked { height: 13.25rem; }
.tale-shelf__item:nth-child(3n) .tale-spine--locked { height: 11.5rem; }

/* CLOTH IDENTITY — one bound colour per volume, mixed into the wood so the shelf
   stays a candlelit library rather than a row of brand swatches. A volume keeps its
   colour whether it is written or not; what changes is the LIGHT on it. A lit book
   takes 30% over the plank tone (the approved "strong tint" step); an unwritten one
   takes 10% over the unlit ground, which is enough to tell six books apart in the
   gloom and nowhere near enough to look available. Rule 8 ladder, both steps.
   Contrast holds at both ends: cream-95 on the lightest lit cloth (volume 1, amber
   over --wood-700) is 5.1:1, and cream-70 on the unlit cloth is ~8.8:1 — both above
   the 4.5:1 body floor. */
.tale-shelf__item:nth-child(1) .tale-spine--open { background-color: color-mix(in srgb, var(--amber-500) 30%, var(--wood-700)); }
.tale-shelf__item:nth-child(2) .tale-spine--open { background-color: color-mix(in srgb, var(--emerald-500) 30%, var(--wood-700)); }
.tale-shelf__item:nth-child(3) .tale-spine--open { background-color: color-mix(in srgb, var(--sky-500) 30%, var(--wood-700)); }
.tale-shelf__item:nth-child(4) .tale-spine--open { background-color: color-mix(in srgb, var(--violet-500) 30%, var(--wood-700)); }
.tale-shelf__item:nth-child(5) .tale-spine--open { background-color: color-mix(in srgb, var(--red-500) 30%, var(--wood-700)); }
.tale-shelf__item:nth-child(6) .tale-spine--open { background-color: color-mix(in srgb, var(--indigo-400) 30%, var(--wood-700)); }

.tale-shelf__item:nth-child(1) .tale-spine--locked { background-color: color-mix(in srgb, var(--amber-500) 10%, var(--wood-900)); }
.tale-shelf__item:nth-child(2) .tale-spine--locked { background-color: color-mix(in srgb, var(--emerald-500) 10%, var(--wood-900)); }
.tale-shelf__item:nth-child(3) .tale-spine--locked { background-color: color-mix(in srgb, var(--sky-500) 10%, var(--wood-900)); }
.tale-shelf__item:nth-child(4) .tale-spine--locked { background-color: color-mix(in srgb, var(--violet-500) 10%, var(--wood-900)); }
.tale-shelf__item:nth-child(5) .tale-spine--locked { background-color: color-mix(in srgb, var(--red-500) 10%, var(--wood-900)); }
.tale-shelf__item:nth-child(6) .tale-spine--locked { background-color: color-mix(in srgb, var(--indigo-400) 10%, var(--wood-900)); }

/* The volume stamp at the head. */
.tale-spine__no {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-6);
    height: var(--space-6);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--wood-500) 50%, transparent);
    color: var(--cream-95);
    font-size: var(--fs-xs);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-none);
}

/* The title, set down the board the way a real spine sets it. `vertical-rl` rotates
   the whole line (the English convention) rather than stacking letters like a shop
   sign, and text-align works on the inline axis — which is now the vertical one, so
   this is what centres the title between stamp and foot.

   IT WRAPS ACROSS THE BOARD, IT DOES NOT TRUNCATE. This was `white-space: nowrap`
   plus an ellipsis, which cut the one thing a spine exists to carry: on a 390px
   phone the third spine has ~90px of run between the stamp and the foot (11.5rem
   board, less padding, stamp, margins and foot) and "Book Three" needs ~90px at
   14px extrabold with 1px tracking — so it set as "Book Thr…". The shorter boards
   are DESIGNED variation, so no height would have fixed the set; the set had to
   stop being a single line.
   Wrapping in `vertical-rl` adds a COLUMN, not a row: the second line sits to the
   left of the first and reads as the next line once the head is tilted, which is
   the standard vertical-text flow. So "Book Three" now sets as Book / Three, both
   whole, and one-line titles that already fit are untouched — "Learning to Fall"
   is ~126px in a 142px run and still sets as one column.
   `break-word` covers the other end: a single word longer than the board's run
   (13+ characters) breaks across columns instead of running off the foot.
   THE CAP IS THREE LINES, in the element's own type rather than a magic pixel, so
   it rides the 640px font bump with no second rule (3 × --line-height-tight = 3.3em,
   plus a hair of slack so sub-pixel rounding cannot shave the third column). Three
   columns is what the board is physically wide enough to show — 3.4em is 48px inside
   54px of board here, 54px inside 66px at 640px — and the clip lands on a whole
   column, never half a glyph. A title past roughly 27 characters is longer than this
   furniture can carry; the full string is still what a screen reader announces and
   what the grown-ups' panel names back on tap. */
.tale-spine__title {
    flex: 1 1 auto;
    min-height: 0;
    writing-mode: vertical-rl;
    text-orientation: mixed;
    font-size: var(--fs-sm);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    line-height: var(--line-height-tight);
    text-align: center;
    /* A long title must never break the board (Rule 14 — edge cases are designed). */
    overflow: hidden;
    overflow-wrap: break-word;
    max-width: 3.4em;
    margin: var(--space-2) 0;
}

.tale-spine__foot {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-05);
}
.tale-spine__mark { font-size: var(--fs-lg); line-height: var(--line-height-none); }
.tale-spine__state {
    font-size: var(--fs-2xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    line-height: var(--line-height-none);
}

/* === 17. THE FREE CHAPTER (PUBLIC READ) === */
/* /tumble-tales/:slug/read.
   BASE = the whole chapter, server-rendered as one continuous scroll, readable with
   no script at all. ENHANCED (`.is-enhanced`, added by frontEnd/js/taleRead.js) = a
   book: one stop fills the viewport, the document itself stops scrolling, and turns
   run the SHARED page flip from styles.css §41 — the same one the athlete reader
   uses. Same two-mode contract the funnel uses in §02, same class name for it.
   Its top bar is the shared .tale-topbar in section 03, not a local one. */
.tale-read__stage {
    width: 100%;
    max-width: 40rem;
    margin: 0 auto;
    padding: var(--space-6) var(--space-4) var(--space-10);
    box-sizing: border-box;
    /* #tale-read-start lives on this element, and TWO links jump to it — the skip
       link on arrival and "↑ Read it again" at the end of the chapter. .tale-topbar
       (§03) is sticky over the top of the scrollport, so without this the book's
       title lands underneath the bar. Same box the enhanced stage subtracts in §02. */
    scroll-margin-top: calc(var(--space-15) + env(safe-area-inset-top, 0px));
}

.tale-read__cover {
    text-align: center;
    padding: 0 0 var(--space-6);
    border-bottom: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 30%, transparent);
    margin: 0 0 var(--space-6);
}
.tale-read__title {
    color: var(--text-primary);
    font-size: clamp(var(--fs-3xl), 7vw, var(--fs-5xl));
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
    text-wrap: balance;
    margin: 0 0 var(--space-2);
}
.tale-read__subtitle {
    color: var(--text-secondary);
    font-size: var(--fs-lg);
    font-style: italic;
    margin: 0 0 var(--space-3);
}
.tale-read__meta {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    margin: 0;
}

.tale-read__chapter {
    text-align: center;
    margin: var(--space-8) 0 var(--space-6);
}
.tale-read__chapter-eyebrow {
    color: var(--amber-500);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    margin: 0 0 var(--space-2);
}
/* One heading type, two jobs: the chapter card above and every front-matter leaf
   below (Contents, Preface, The Valley, the cast). Same size, same weight, same
   rhythm — a second near-identical block is how two headings on one book drift
   apart. Both carry margin:0; the block that FOLLOWS a heading owns the gap, which
   is the idiom the whole of §17 already uses. */
.tale-read__chapter-title,
.tale-read__front-title {
    color: var(--text-primary);
    font-size: var(--fs-2xl);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-snug);
    text-wrap: balance;
    margin: 0;
}
.tale-read__chapter-sub {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    margin: var(--space-2) 0 0;
}

/* ── FRONT MATTER (TT-54) — the leaves before Chapter One ────────────────
   Title page → contents → preface → the world → the cast. Each one is an ordinary
   `.tale-read__stop`, so each is a full leaf in the base scroll and exactly one
   turn of the paged book.

   THEY HAVE TO READ AS PAGES OF A BOOK, NOT SECTIONS OF A WEB PAGE. So there are no
   cards, no panels and no tiles here: type, a dashed rule between leaves, and white
   space — the same grammar the cover and the chapter card already use. The only
   framed object is the map plate, and that is a framed object in a real book too.

   EVERY LEAF IS PLACEHOLDER COPY AND WEARS THE STAMP THAT SAYS SO
   (`.tale-read__draft`), because the real front matter is still being written. */
.tale-read__front {
    margin: 0 0 var(--space-8);
    padding: var(--space-8) 0 0;
    border-top: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 30%, transparent);
}
/* A title page is centred, because a title page IS centred. */
.tale-read__front--title { text-align: center; }

/* ONE RULE PER SEAM. In the base scroll the cover already draws a dashed line under
   itself, so the first leaf drawing its own would put two of them 24px apart and
   read as a mistake. Every LATER seam is drawn once, by the leaf below it. (Paged,
   both are zeroed anyway — the turn is the separator.) */
.tale-read__cover + .tale-read__front { border-top: 0; }

/* The stamp. Quiet, but never invisible: it is information, not decoration, and it
   is the one thing on these leaves that must not be mistaken for finished copy.
   Dashed, like a proof mark — the shape says "draft" before the word does, which is
   the secondary signal Rule 13 asks for instead of colour alone. */
.tale-read__draft {
    display: inline-block;
    margin: 0 0 var(--space-5);
    padding: var(--space-1) var(--space-3);
    border: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 50%, transparent);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--amber-500) 10%, transparent);
    color: var(--text-primary);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
}

/* The two lines a TITLE PAGE carries that a cover does not. The title itself is the
   book's own and reuses .tale-title (§04). */
.tale-read__byline {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    margin: var(--space-6) 0 0;
}
.tale-read__imprint {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    letter-spacing: var(--letter-spacing-wider);
    margin: var(--space-2) 0 0;
}

/* The copyright notice. Quieter than the imprint above it and set apart, because it
   is a legal line rather than part of the title page's voice — in a printed book it
   would not share this leaf at all. */
.tale-read__copyright {
    color: var(--text-muted);
    font-size: var(--fs-xs);
    margin: var(--space-5) 0 0;
}

/* THE DEDICATION. Centred, unhurried, and deliberately NOT .tale-read__front-body:
   a dedication is not body copy that happens to be short. It is the one leaf whose
   white space is the design, so the generous margins here are load-bearing rather
   than decorative — tightening them would make it read as a paragraph. */
.tale-read__dedication {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    text-align: center;
    text-wrap: balance;
    margin: var(--space-6) auto 0;
    max-width: 30ch;
}

/* The line the book turns on, set apart from the dedication proper the way an
   epigraph is: italic, a rule above it, and more air than a paragraph break. */
.tale-read__epigraph {
    border-top: var(--border-1) solid var(--surface-border);
    color: var(--text-secondary);
    font-size: var(--fs-base);
    font-style: italic;
    line-height: var(--line-height-relaxed);
    text-align: center;
    text-wrap: balance;
    margin: var(--space-8) auto 0;
    max-width: 30ch;
    padding-top: var(--space-6);
}

/* A leaf's body copy. Not .tale-read__prose: that is MANUSCRIPT text, which honours
   the author's own line breaks (`white-space: pre-line`) and is the only thing on
   its page. A preface paragraph sits among headings, lists and a note, so it owns
   the gap above it instead. */
.tale-read__front-body {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    text-wrap: pretty;
    margin: var(--space-5) 0 0;
}

/* The map plate, and any framed plate a leaf carries: .tale-read__art (above) does
   the frame, this does the one thing a plate on a leaf needs that a full-page
   illustration does not — a gap from the paragraph above it. */
.tale-read__front-plate { margin: var(--space-5) 0 0; }

/* The italic line under a leaf that names what it is a stand-in for. */
.tale-read__fm-note {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-style: italic;
    line-height: var(--line-height-relaxed);
    margin: var(--space-6) 0 0;
}

/* A PRINTED table of contents: chapter number, title, and the beat it covers, each
   entry ruled off from the next. Two columns from the smallest screen up, because a
   number stacked above its own title stops reading as a contents page and starts
   reading as a list. */
.tale-read__toc {
    list-style: none;
    margin: var(--space-5) 0 0;
    padding: 0;
}
.tale-read__toc-item {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: var(--border-1) dashed color-mix(in srgb, var(--amber-500) 20%, transparent);
}
.tale-read__toc-num {
    grid-column: 1;
    color: var(--amber-500);
    font-size: var(--fs-xs);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    white-space: nowrap;
}
.tale-read__toc-title {
    grid-column: 2;
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-snug);
    text-wrap: pretty;
}
.tale-read__toc-note {
    grid-column: 2;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
}

/* One list, two leaves: the places on the world leaf and the cast on the next.
   An icon, a name, a line — identical in both, so it is written once. */
.tale-read__facts {
    list-style: none;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
    margin: var(--space-6) 0 0;
    padding: 0;
}
.tale-read__fact {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin: 0;
}
.tale-read__fact-icon {
    flex: 0 0 auto;
    font-size: var(--fs-2xl);
    line-height: var(--line-height-tight);
}
.tale-read__fact-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* Without this a flex item refuses to shrink under its own text and a long
       line pushes the row wider than the page. */
    min-width: 0;
}
.tale-read__fact-name {
    color: var(--text-primary);
    font-size: var(--fs-base);
    font-weight: var(--font-weight-bold);
}
.tale-read__fact-line {
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
}

/* THE WAY PAST THE FRONT MATTER. On every leaf, so a reader is never made to walk
   through five of them and a returning reader is never stuck in the preface. A
   quiet outlined pill — it is an exit, not an offer, and it must not out-shout the
   book it is standing in front of. 44px floor (Rule 13): this is a child's target. */
.tale-read__jump {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--space-11);
    margin: var(--space-5) 0 0;
    padding: var(--space-2) var(--space-4);
    border: var(--border-1) solid var(--surface-border);
    border-radius: var(--radius-pill);
    color: var(--text-primary);
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth),
                transform var(--duration-fast) var(--ease-spring);
}
.tale-read__jump:hover {
    background: color-mix(in srgb, var(--amber-500) 10%, transparent);
    border-color: var(--amber-500);
}
.tale-read__jump:active { transform: translateY(var(--press-sm)); }

.tale-read__page {
    /* The magic wash is painted on this element's ::before, so it needs a stacking
       context of its own. Harmless on every other page. */
    position: relative;
    margin: 0 0 var(--space-6);
    /* #tale-chapter-one sits on the FIRST of these, and every front-matter leaf's
       "Skip to Chapter One" link jumps to it. .tale-topbar (§03) is sticky over the
       top of the scrollport, so without this the chapter's first line lands
       underneath the bar — same box .tale-read__stage subtracts above. Costs the
       other pages nothing: nothing else ever anchors to one. */
    scroll-margin-top: calc(var(--space-15) + env(safe-area-inset-top, 0px));
}
/* ── THE MAGIC PAGE ───────────────────────────────────────────────────────────
   Author-set and PAGE-level (body.treatment === 'magic'), never an inline span:
   italics are the only span the prose has, so a magic beat is a property of the
   whole page. Three quiet moves, per the spec's "spend the boldness only there":
   a rose-gold wash BEHIND the ink, a soft glow on the plate, and slightly warmed
   text. Everything else on the page stays exactly as it is.

   The wash sits on a ::before at inset:0 — it does NOT bleed past the page box.
   That is deliberate: .tale-read__stop--paged sets overflow-y:auto, and when one
   axis is not `visible` the other computes to auto too, so a bleeding pseudo
   would raise a horizontal scrollbar on every magic page.

   STATIC, not animated — which satisfies Rule 13's motion gate by construction
   rather than by a fallback. A breathing glow would need a keyframe animation
   declared in styles.css (never here) plus a prefers-reduced-motion block.

   CONTRAST: the wash is capped at the 20% rung of the Rule 8 ladder and sits
   behind the text, so --text-primary keeps its 10.76:1 against the paper. */
.tale-read__page--magic::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: var(--z-ground);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(ellipse 70% 55% at 50% 38%,
            color-mix(in srgb, var(--rose-300) 20%, transparent) 0%,
            color-mix(in srgb, var(--rose-400) 10%, transparent) 55%,
            transparent 100%),
        linear-gradient(180deg,
            color-mix(in srgb, var(--rose-500) 8%, transparent) 0%,
            transparent 70%);
    pointer-events: none;
}
/* THE MOTES — the embers lifting off Rusty, which is the beat the prose describes.
   One field of soft dots rather than many elements: a handful of radial gradients on a
   single layer, drifting as a group. Inset from the page edge so the drift can never
   spill past the leaf and raise a scrollbar in paged mode.

   ON A REAL ELEMENT, NOT A PSEUDO, AND THAT IS NOT COSMETIC. `::before` holds the wash
   and `::after` is already taken by the paged top-pin spacer (§ further down), which
   works by being an in-flow flex item. Making that same `::after` absolute to paint
   motes removed it from flex flow and silently broke the pin on the two prose magic
   pages. Measured: first line at 267px / 327px from the leaf top instead of 24px. */
.tale-read__page--magic .tale-read__motes {
    position: absolute;
    inset: var(--space-4);
    z-index: var(--z-ground);
    pointer-events: none;
    background:
        radial-gradient(circle 3px at 18% 72%, color-mix(in srgb, var(--rose-300) 50%, transparent) 0%, transparent 100%),
        radial-gradient(circle 2px at 34% 44%, color-mix(in srgb, var(--rose-400) 50%, transparent) 0%, transparent 100%),
        radial-gradient(circle 4px at 57% 63%, color-mix(in srgb, var(--rose-300) 30%, transparent) 0%, transparent 100%),
        radial-gradient(circle 2px at 71% 31%, color-mix(in srgb, var(--rose-400) 50%, transparent) 0%, transparent 100%),
        radial-gradient(circle 3px at 85% 56%, color-mix(in srgb, var(--rose-500) 30%, transparent) 0%, transparent 100%),
        radial-gradient(circle 2px at 46% 18%, color-mix(in srgb, var(--rose-300) 50%, transparent) 0%, transparent 100%);
    /* --duration-float is 4000ms (styles.css, the ambient mascot float). No fallback
       value: writing `var(--duration-float, 6s)` reads as if 6s were the intent when
       the token is defined and the fallback is dead code. */
    animation: taleMoteDrift var(--duration-float) var(--ease-smooth) infinite;
}

/* MOTION SAFETY (Rule 13) — and it MUST sit after the rule above, not in the
   reduced-motion block earlier in this file: a media query adds no specificity, so a
   base rule declared later simply wins and the fallback silently never applies.
   The motes are not removed. The embers are part of what the gold page SAYS, so the
   field stays exactly where it is and only stops drifting, pinned at the fullest
   frame of the keyframe rather than the faded one. */
@media (prefers-reduced-motion: reduce) {
    .tale-read__page--magic .tale-read__motes {
        animation: none;
        opacity: 0.9;
        transform: none;
    }
}

/* The page's own content sits above the wash. The motes layer is a child too, so it
   would pick this up and paint OVER the prose — it is re-pinned to the ground plane
   by the rule above, which is one class more specific. */
.tale-read__page--magic > * { position: relative; z-index: var(--z-content); }

/* The plate on a magic page carries the glow the prose is describing. */
.tale-read__page--magic .tale-read__art {
    box-shadow:
        0 0 var(--space-6) color-mix(in srgb, var(--rose-400) 30%, transparent),
        0 0 var(--space-10) color-mix(in srgb, var(--rose-500) 20%, transparent);
}

/* Warmed, not recoloured — the ink stays the ink. */
.tale-read__page--magic .tale-read__prose { color: color-mix(in srgb, var(--tale-ink) 92%, var(--rose-500)); }

.tale-read__prose {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    text-wrap: pretty;
    white-space: pre-line;
    margin: 0;
}
.tale-read__prompt {
    color: var(--text-primary);
    font-size: var(--fs-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-snug);
    margin: 0 0 var(--space-3);
}
.tale-read__aside {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
    margin: 0;
}
.tale-read__caption {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    text-align: center;
    margin: var(--space-3) 0 0;
}

.tale-read__art {
    width: 100%;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--amber-500) 30%, transparent),
            color-mix(in srgb, var(--amber-500) 8%, transparent));
}
.tale-read__art-img { width: 100%; height: 100%; object-fit: cover; }

/* FULL-BLEED SCENE PLATE (page_type 'scene'). The reader's horizontal gutter is
   var(--space-4) in both modes — it lives on .tale-read__stage while scrolling and
   moves to .tale-read__stop--paged once enhanced — so one negative inline margin
   cancels whichever is in force. 16:9 per the art spec, against the inset
   illustration frame's 16:10. Square corners: a plate that touches the page edge
   must not keep a radius on the side it bleeds past. */
.tale-read__art--bleed {
    aspect-ratio: 16 / 9;
    margin-inline: calc(var(--space-4) * -1);
    margin-bottom: var(--space-5);
    width: auto;
    border-radius: 0;
    /* HOLD THE RATIO. In paged mode the leaf is a column flexbox and this plate is a
       flex item, so the default `flex-shrink: 1` + `min-height: 0` let it be squeezed
       by whatever prose shares the leaf — `aspect-ratio` loses to a resolved flex
       basis. Measured before this line, at 375x667: the Wall plate rendered 375x121,
       a 3.1:1 letterbox where 16:9 asks for 211px tall. A crushed plate is worse than
       a leaf that scrolls, and the stop already handles the overflow case properly
       (`overflow-y: auto` + `justify-content: safe center` starts it at the top). */
    flex: 0 0 auto;
}
.tale-read__art-icon { font-size: var(--fs-6xl); line-height: 1; }

/* The chapter-opener plate: same frame, sits ABOVE the title card instead of
   inside a page of its own, so it needs its own top/bottom rhythm rather than
   `.tale-read__page`'s. In the scroll, `.tale-read__chapter`'s own top margin
   (var(--space-8)) already makes the gap beneath it — zero this plate's own
   margin so the two don't stack. In the paged book, that chapter margin is
   zeroed (below) so the page's first element sets its own position; this plate
   supplies the gap instead. */
.tale-read__art--opener {
    margin: 0;
}
.tale-read__stop--paged .tale-read__art--opener {
    margin-bottom: var(--space-6);
}
.tale-read__caption--opener {
    font-style: italic;
    color: var(--text-muted);
}

/* The no-JS reveal. <details> owns its own state, so "tap to see" survives with
   no script on the page. */
.tale-read__reveal {
    border: var(--border-1) solid var(--surface-border);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    padding: 0 var(--space-4);
}
.tale-read__reveal-summary {
    display: flex;
    align-items: center;
    min-height: var(--space-11);
    color: var(--text-primary);
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-smooth);
}
.tale-read__reveal-summary:hover { color: var(--amber-500); }
.tale-read__reveal-summary:active { color: var(--text-secondary); }
.tale-read__answer {
    color: var(--text-secondary);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    margin: 0 0 var(--space-4);
}
.tale-read__choices {
    list-style: none;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
}
.tale-read__choice { margin: 0; }

.tale-read__end {
    text-align: center;
    margin: var(--space-8) 0 0;
    padding: var(--space-7) var(--space-5);
    border-radius: var(--radius-lg);
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--amber-500) 20%, transparent),
        color-mix(in srgb, var(--amber-400) 10%, transparent));
}
.tale-read__end-emoji { font-size: var(--fs-5xl); line-height: 1; margin: 0 0 var(--space-2); }
.tale-read__end-title {
    color: var(--text-primary);
    font-size: var(--fs-2xl);
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    text-wrap: balance;
    margin: 0 0 var(--space-3);
}
.tale-read__end-body {
    color: var(--text-secondary);
    font-size: var(--fs-lg);
    line-height: var(--line-height-relaxed);
    max-width: 28rem;
    margin: 0 auto var(--space-5);
}

/* THE GROWN-UP HANDOFF (TT-39).
   The line a CHILD reads, then the disclosure an ADULT opens. The reading surface
   may not sell (Quebec CPA ss.248-249 — see the view), so this is styled as a quiet
   panel inside the end card, not as a call to action: no button fill, no accent
   shout, nothing that competes with the story that just ended.
   The summary reuses .tale-read__reveal-summary — the same no-JS disclosure the
   story pages use — and adds only the pill framing, so its hover/active state, its
   focus ring and its reduced-motion fallback are inherited, not re-cut. */
.tale-read__handoff {
    color: var(--text-primary);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    max-width: 28rem;
    margin: 0 auto;
}
.tale-read__grownup {
    max-width: 30rem;
    margin: var(--space-4) auto 0;
    text-align: left;
}
.tale-read__grownup-summary {
    justify-content: center;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--white) 30%, transparent);
    transition: background-color var(--duration-fast) var(--ease-smooth),
                color var(--duration-fast) var(--ease-smooth);
}
.tale-read__grownup-summary:hover { background: color-mix(in srgb, var(--white) 50%, transparent); }

/* ── THE BOOK (TT-51) — one page at a time ────────────────────────────────
   Everything below applies only once taleRead.js has added `.is-enhanced`. Above
   this line is the base document: every stop printed, the page an ordinary scroll.

   THE DOCUMENT STOPS SCROLLING; A PAGE MAY. The body becomes a fixed-height column
   — topbar, stage, bottom bar — and the stage hands its whole remaining height to
   exactly one stop. A stop whose text outgrows a small phone scrolls INSIDE its own
   box, which is why `overflow-y` lives on the stop and never on the body.

   SPECIFICITY IS DELIBERATE. The shell rules (body, stage, bars) are scoped
   `.tale-read.is-enhanced …` because they must beat the base rules above. The STOP
   rules are a single class on purpose: styles.css §41 lifts the turning page with
   `.tt-turn-stage.is-turning .tt-turn--leaf` at three classes, and it has to
   outrank whatever the stop is wearing — otherwise the page being turned keeps its
   own overflow and height and never leaves the flow. Do not raise `--paged`. */
.tale-read.is-enhanced {
    height: 100vh;
    /* dvh second: it is the accurate one on a phone whose URL bar comes and goes,
       and the vh line above is the fallback for an engine that cannot parse it. */
    height: 100dvh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.tale-read.is-enhanced .tale-topbar { flex: 0 0 auto; }

.tale-read.is-enhanced .tale-read__stage {
    flex: 1 1 auto;
    /* Without this a flex child refuses to shrink below its content, and one long
       page would push the bottom bar off the screen instead of scrolling itself. */
    min-height: 0;
    overflow: hidden;
    /* The stops carry the gutter now — which is what makes the shared leaf's
       `inset: 0` land exactly on the page rect with nothing to override. */
    padding: 0;
}

/* One stop = one turn of the book. */
.tale-read__stop--paged {
    display: flex;
    flex-direction: column;
    /* `safe` falls back to start-alignment exactly when the content overflows, so a
       long page begins at its first line instead of being clipped symmetrically. */
    justify-content: center;
    justify-content: safe center;
    height: 100%;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-6) var(--space-4);
    box-sizing: border-box;
    animation: ttPageIn var(--duration-base) var(--ease-spring);
}

/* The cover's rule below the title separates it from the page that followed it in
   the scroll. As a page of its own it separates it from nothing. Same for the dashed
   rule above each front-matter leaf: paged, the TURN is what separates them. Two
   classes each, so the `--paged` stop rule above is not raised (see the block note). */
.tale-read__stop--paged.tale-read__cover {
    border-bottom: 0;
}
.tale-read__stop--paged.tale-read__front {
    border-top: 0;
}

/* The closing page keeps its warm gradient — it is the one stop that is meant to
   look like the end of something — but loses the rounded card framing, because it
   now fills the screen rather than sitting in a column of scrolling text. */
.tale-read__stop--paged.tale-read__end {
    border-radius: 0;
}

/* ── WHERE THE TEXT STARTS (TT-55) ───────────────────────────────────────
   A REAL BOOK'S TEXT STARTS AT THE TOP OF THE PAGE. The white space belongs
   BELOW it. Centring every stop meant a short page — one paragraph, "17 of 19" —
   floated in the middle of the screen with a void above and below it, and each
   turn re-centred the prose at a new height, so the eye had to re-find the first
   line every time. Pinned to the top, every page's first line lands in the same
   place and a turn reads as a turn.

   WHICH STOPS KEEP THEIR CENTRE, AND WHY:
   • The COVER is the jacket, and a jacket IS centred.
   • The TITLE PAGE (`--title`) is centred for the same reason a printed one is.
   • The CLOSING page is the one stop meant to read as a single composed card.
   • A PICTURE page is a full-page plate — also centred, because a lone art frame
     pinned to the top over a void reads as a mistake rather than a page.
   Those four are therefore untouched below. Prose stops change — and so does every
   OTHER front-matter leaf (`--leaf`: contents, preface, the world, the cast), which
   are pages of reading and must start where every page of reading starts.

   WHY A SPACER AND NOT `justify-content: flex-start`. The turn's leaf restates
   `justify-content: safe center` at three classes (styles.css §41) — an out-of-flow
   box pinned `inset: 0` needs it. A stop overriding justify-content would be
   re-centred for the length of every hinge and snap back on landing, and beating §41
   means raising this selector past three classes, which the block note above
   forbids. A flex spacer absorbs the free space whichever way the box is justified,
   so it is correct in flow AND mid-turn, with no cascade fight. It collapses to
   nothing when the page overflows — exactly what `safe center` already does there.
   `:has()` failing to parse simply leaves every stop centred, i.e. today.

   THE CHAPTER-OPENER PLATE IS EXEMPT FROM THIS EXCLUSION. It sits above a page
   of prose, not in place of one, so that page must still start at the top —
   unlike a genuine full-page PICTURE, which stays centred (comment above). The
   `:not(:has(... :not(.tale-read__art--opener)))` reads as "has no art OTHER
   THAN an opener plate": a page with only an opener still matches and gets the
   spacer; a page with a real `.tale-read__art` picture still does not. */
.tale-read__stop--paged.tale-read__page:not(:has(.tale-read__art:not(.tale-read__art--opener)))::after,
.tale-read__stop--paged.tale-read__front--leaf::after {
    content: '';
    flex: 1 1 auto;
}

/* In the scroll, the chapter card's top margin separates it from the previous page's
   prose. As the first thing on a page of its own it separates it from nothing — and
   it would drop this one page's first line lower than every other page's, which is
   the exact inconsistency the rule above exists to remove. */
.tale-read__stop--paged .tale-read__chapter {
    margin-top: 0;
}

/* ── THE BOTTOM BAR ──────────────────────────────────────────────────────
   Explicit Back / Forward and a position indicator, in the thumb zone. Hidden
   outright in base mode: a reader with no script must never be shown a control
   that cannot do anything. */
.tale-read__nav { display: none; }

.tale-read.is-enhanced .tale-read__nav {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
    max-width: 40rem;
    margin: 0 auto;
    padding: var(--space-2) var(--space-3);
    /* The home indicator sits where the thumb does. Raw env() matches the idiom
       already used by .tale-topbar and .tale-read__stage in this file — see the
       note in the session log about promoting it to a token. */
    padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    box-sizing: border-box;
    background: color-mix(in srgb, var(--surface-page) 92%, transparent);
    -webkit-backdrop-filter: blur(var(--blur-nav));
    backdrop-filter: blur(var(--blur-nav));
    border-top: var(--border-1) solid var(--surface-border);
}

/* Centre-tap reading mode: the page becomes the whole screen. Matched at four
   classes so it beats the reveal above rather than racing it, and reversible by
   tapping the centre again or by any arrow key (taleRead.js restores the chrome
   on every keyboard turn, so a keyboard reader is never stranded).

   EXCEPT THE WAY OUT OF THE LAST PAGE (TT-57). The closing stop carries no links of
   its own — the one ask on it is the grown-up's capture — so .tale-topbar is the only
   exit from it, and hiding it strands a reader in front of a form with nothing on
   screen saying how to leave. `.is-at-end` is on the body for exactly as long as the
   closing stop is the mounted one (taleRead.js updateChrome, re-derived on EVERY
   turn), so this holds however the reader got there — including the path that actually
   bites: turning the last page with the chrome already hidden.

   WRITTEN AS A NARROWER MATCH, NOT AS AN OVERRIDE. :not() here means the rule never
   claims the topbar on that stop, so there is no second rule undoing a first and
   nothing for a later edit to lose a specificity fight with (Rule 11). The bottom bar
   is deliberately NOT exempt: it is the reading affordance, the counter is not an
   exit, and the tap zones, swipes and arrow keys all still turn pages without it. */
.tale-read.is-enhanced.is-chromeless:not(.is-at-end) .tale-topbar,
.tale-read.is-enhanced.is-chromeless .tale-read__nav {
    display: none;
}

/* The colophon is a line of small print under a scrolling document. A book that
   fills the screen has no room for it, and the closing page already carries the two
   links a finished reader wants. It stays in the served HTML for the base document,
   for print and for anyone reading with scripts off. */
.tale-read.is-enhanced .tale-footer { display: none; }

.tale-read__navbtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    /* 44×44 floor (Rule 13) — this is a child's target on a phone. */
    min-height: var(--space-11);
    min-width: var(--space-11);
    padding: var(--space-2) var(--space-4);
    border: var(--border-2) solid var(--surface-border);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--fs-base);
    font-weight: var(--font-weight-semibold);
    cursor: pointer;
    transition: transform var(--duration-fast) var(--ease-spring),
                background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth);
}
.tale-read__navbtn:hover {
    background: color-mix(in srgb, var(--amber-500) 10%, var(--surface-card));
    border-color: var(--amber-500);
}
.tale-read__navbtn:active { transform: translateY(var(--press-sm)); }
/* Disabled is never colour alone (Rule 13): the cursor changes and the control
   stops responding to every input, and the counter still names where you are. */
.tale-read__navbtn:disabled {
    opacity: 0.4;
    cursor: default;
}
.tale-read__navbtn-label { line-height: var(--line-height-tight); }

/* "7 of 19" — a book tells you where you are. */
.tale-read__counter {
    flex: 0 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
    letter-spacing: var(--letter-spacing-wider);
}

/* Focus visibility for the room, read + join surfaces (Rule 13). Same branded ring
   as the funnel's controls above. The CTAs are omitted on purpose: they carry
   .tale-btn (including .tale-btn--quiet, §05), which the ring above already covers —
   a second selector for the same element is specificity noise, not safety.
   .tale-notify__summary stays in this list even though its rules moved to §18: one
   multi-selector ring is the point, and a second identical rule next to the
   component would be the duplication this list exists to prevent. */
.tale-spine:focus-visible,
.tale-bookcase__plate-link:focus-visible,
.tale-notify__summary:focus-visible,
.tale-footer__link:focus-visible,
.tale-read__reveal-summary:focus-visible,
.tale-read__navbtn:focus-visible,
.tale-read__jump:focus-visible {
    outline: var(--border-2) solid var(--theme-glow);
    outline-offset: var(--outline-offset);
}

@media (min-width: 640px) {
    .tale-room__stage {
        padding-top: var(--space-6);
        padding-left: var(--space-6);
        padding-right: var(--space-6);
    }
    .tale-read__stage { padding-left: var(--space-6); padding-right: var(--space-6); }

    /* ...and the full-bleed plate has to cancel the WIDER gutter to stay flush. Only
       in scroll mode: once taleRead.js enhances, the stage padding goes to 0 and the
       gutter moves to .tale-read__stop--paged, which stays var(--space-4) at every
       width — so the base negative margin is still the right one there. Without the
       `:not(.is-enhanced)` this would overshoot the paged leaf by 8px a side. */
    .tale-read:not(.is-enhanced) .tale-read__art--bleed {
        margin-inline: calc(var(--space-6) * -1);
    }

    /* The books grow before the room does. Six spines at this size still fit a
       640px viewport without scrolling, so the rail stops being a scroller and
       becomes a plain centred row — same markup, same snap points, no layout swap. */
    .tale-shelf { gap: var(--space-3); padding-left: var(--space-6); padding-right: var(--space-6); }
    .tale-spine { width: 4.75rem; height: 15rem; }
    .tale-spine--open { width: 5.75rem; height: 17rem; }
    .tale-shelf__item:nth-child(3n + 2) .tale-spine--locked { height: 15.75rem; }
    .tale-shelf__item:nth-child(3n) .tale-spine--locked { height: 13.75rem; }
    .tale-spine__title { font-size: var(--fs-base); }

    /* The room's header can breathe again once the shelf is no longer the only
       thing competing for a phone's first screen. */
    .tale-room__header { margin-bottom: var(--space-8); }
    .tale-room__lede { font-size: var(--fs-xl); }
    .tale-room__blurb { font-size: var(--fs-lg); }
    .tale-room__shelfnote { margin-bottom: var(--space-5); }
}

@media (min-width: 1024px) {
    .tale-shelf { gap: var(--space-4); }
    .tale-spine { width: 5.5rem; height: 17rem; }
    .tale-spine--open { width: 6.5rem; height: 19rem; }
    .tale-shelf__item:nth-child(3n + 2) .tale-spine--locked { height: 17.75rem; }
    .tale-shelf__item:nth-child(3n) .tale-spine--locked { height: 15.5rem; }
}

/* MOTION SAFETY (Rule 13). The room's one spatial move is the spine tilting out of
   the shelf on hover and pressing back in — small, but it is movement on six
   objects at once, which is exactly the kind of thing a vestibular trigger is. It
   is removed entirely here; the lit/unlit board, the cloth colour, the focus ring
   and the shadow still say everything the movement said.
   The paged book DOES move: each stop mounts on ttPageIn (a lift + fade) and the
   turn itself hinges in 3D. Both are removed here — the hinge by the shared block in
   styles.css §41, and the mount entrance below, flattened to a plain opacity fade
   with no translate and no scale. What is left of the room and the read page is the
   colour transition on each control, flattened to the same opacity-only rule the
   funnel's controls use. */
@media (prefers-reduced-motion: reduce) {
    .tale-spine,
    .tale-bookcase__plate-link,
    .tale-notify__summary,
    .tale-footer__link,
    .tale-read__reveal-summary,
    .tale-read__navbtn,
    .tale-read__jump {
        transition: opacity var(--duration-fast) var(--ease-smooth);
    }
    .tale-spine:hover,
    .tale-spine:active,
    .tale-bookcase__plate-link:active,
    .tale-read__navbtn:active,
    .tale-read__jump:active {
        transform: none;
    }

    /* A page arriving must not travel. fadeIn is opacity only (styles.css §18). */
    .tale-read__stop--paged {
        animation: fadeIn var(--duration-fast) var(--ease-smooth);
    }
}

/* === 18. THE JOIN PAGE (THE LIST, ON ITS OWN PAGE) === */
/* TT-58 — /tumble-tales/join. The owner's ask was that the capture card stop
   unfolding on the shelf page, so it has an address now, and the room and the five
   coming-soon jackets link to it.

   IT IS FURNITURE, NOT A FORM ON A BACKGROUND. Same materials as the bookcase in
   §15a — the unlit wood ground, one warm source high on the left falling off into
   it, a soft contact shadow so it sits ON the page — with a parchment card lying on
   top of it. A sign-up sheet on the librarian's desk. Nothing new is minted for it:
   the wood ramp, the candle colour and the inset are the same four tokens the shelf
   is cut from.

   WHAT MOVED HERE. .tale-notify* was §15d, styling the room's folded panel. The
   panel left that page and came to this one, so its rules came with it rather than
   staying under a LIBRARY ROOM heading that no longer renders them. The <details> is
   still the whole COPPA story: the browser owns open/closed, so on first paint this
   page holds zero inputs (Golden Rule 1). */
.tale-join__stage {
    width: 100%;
    max-width: 40rem;
    margin: 0 auto;
    padding: var(--space-5) var(--space-4) var(--space-10);
    box-sizing: border-box;
}

/* --- 18a. The desk ------------------------------------------------------- */
.tale-join__desk {
    margin: 0;
    padding: var(--space-3);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(115% 85% at 16% 0%, var(--amber-warm-30) 0, transparent 60%),
        var(--wood-900);
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
}

/* The sheet lying on it. --shadow-inset under the card is what seats it in the wood
   instead of printing it on top — the same trick that turns the shelf recess from a
   brown stripe into a box with depth.
   --surface-page, not --surface-card, and that is load-bearing: the shared capture
   card inside the disclosure IS --surface-card (§11), so a card on a card would
   dissolve into it. Three steps, darkest out: wood → page cream → card cream. */
.tale-join__plate {
    padding: var(--space-6) var(--space-5) var(--space-7);
    border-radius: var(--radius-md);
    background: var(--surface-page);
    box-shadow: var(--shadow-inset), 0 2px 10px rgba(0, 0, 0, 0.18);
    text-align: center;
}

/* --- 18b. What the page says --------------------------------------------- */
/* CONTRAST FLOOR (Rule 13) — WHY THE BODY COPY HERE IS --text-muted, NOT
   --text-secondary. Measured on this scope's ground (--surface-page #fff1d1):
   --text-secondary (#5c7ca8) is 3.83:1, under the 4.5:1 AA floor for text this size,
   while --text-muted — which §01 reassigns to --slate-600 inside these body classes
   precisely so that it is the DARKER of the two here — is 6.77:1. The name inverts
   against the ground, the numbers do not. Same reasoning keeps --amber-500 (1.92:1
   on cream) off every string on this page: it is a ground and an edge colour here,
   never type. */
.tale-join__title {
    color: var(--text-primary);
    /* Same display ladder as the room's title and the read's — one scale across the
       whole public surface, and a clamp so it never eats a 390px first screen. */
    font-size: clamp(var(--fs-2xl), 6vw, var(--fs-4xl));
    font-family: var(--font-tale-display);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
    text-wrap: balance;
    margin: 0 0 var(--space-3);
}
.tale-join__lede {
    color: var(--text-muted);
    font-size: var(--fs-base);
    line-height: var(--line-height-relaxed);
    text-wrap: pretty;
    margin: 0 auto var(--space-5);
    max-width: 32rem;
}

/* "You're asking about Book Two." — the one line that says the link a visitor
   followed was understood. Server-rendered from a closed whitelist, so it is a fact
   on the page and not a live region waiting for a script. */
.tale-join__context {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin: 0 0 var(--space-5);
}
.tale-join__context-book {
    color: var(--text-primary);
    font-weight: var(--font-weight-bold);
}

/* Three answers, not a paragraph. The marker is drawn here rather than typed into
   the copy so there is no decorative glyph for a screen reader to announce three
   times — and so the three lines stay one editable array in data/taleBooks.
   A flex row rather than an absolutely-positioned dot: it keeps every value on the
   token scale (an absolute marker needs an em offset to sit on the first line, and
   there is no token for that). */
.tale-join__promise {
    list-style: none;
    margin: 0 0 var(--space-6);
    padding: 0;
    text-align: left;
}
.tale-join__promise-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin: 0 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: var(--line-height-relaxed);
}
.tale-join__promise-item::before {
    content: '';
    flex: 0 0 auto;
    width: var(--space-2);
    height: var(--space-2);
    margin-top: var(--space-2);
    border-radius: var(--radius-pill);
    background: var(--amber-500);
}

/* --- 18c. The grown-ups' disclosure (moved from §15d) --------------------- */
/* The one gate on the page, and the reason a child can be sent here safely.
   <details> is the no-JS reveal: the browser owns open/closed, so a child sees a
   closed panel labelled for grown-ups and no input at all (COPPA, Golden Rule 1). */
.tale-notify {
    max-width: 30rem;
    margin: var(--space-5) auto 0;
    /* #join-list sits on this element and the page's skip link jumps straight to it.
       .tale-topbar (§03) is sticky over the top of the scrollport, so without this
       the summary a keyboard visitor just skipped to lands underneath the bar
       (WCAG 2.4.11). Same box .tale-flap and .tale-read__stage subtract. */
    scroll-margin-top: calc(var(--space-15) + env(safe-area-inset-top, 0px));
}
.tale-notify__summary {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--space-11);
    padding: var(--space-2) var(--space-4);
    border: var(--border-1) solid var(--surface-border);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: var(--fs-sm);
    font-weight: var(--font-weight-semibold);
    text-align: center;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-smooth),
                border-color var(--duration-fast) var(--ease-smooth),
                color var(--duration-fast) var(--ease-smooth);
}
.tale-notify__summary:hover { border-color: var(--amber-500); color: var(--amber-500); }
.tale-notify__summary:active { background: color-mix(in srgb, var(--amber-500) 10%, transparent); }
.tale-notify__body { text-align: left; }
/* The shared capture card (§11) carries its own top margin for the surfaces that
   stack it under prose. Inside the disclosure it is the only thing there, so the
   card's lift-off-the-page shadow is enough and the gap is the summary's. */
.tale-notify__body .tale-capture__card { margin-top: var(--space-4); }

/* --- 18d. The ways on ---------------------------------------------------- */
/* Two exits, neither of them a second ask. The second one exists because a CHILD can
   follow the link that lands here, and a page addressed to adults that strands them
   on it is still a page a child is on.
   Both wear .tale-btn--quiet (§05) rather than .tale-footer__link: that atom is
   --amber-500 on cream, which measures 1.92:1 and is the one thing a page built for
   this job must not do to its exits. Quiet pills are --text-primary at 11.32:1 and
   already carry the 44px target and the branded ring. */
.tale-join__ways {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-6) 0 0;
    text-align: center;
}
.tale-join__away {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin: 0;
}
.tale-join__waylinks {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
}

@media (min-width: 640px) {
    .tale-join__stage { padding: var(--space-8) var(--space-6) var(--space-10); }
    .tale-join__desk { padding: var(--space-5); }
    .tale-join__plate { padding: var(--space-8) var(--space-8) var(--space-9); }
    .tale-join__lede { font-size: var(--fs-lg); }
    .tale-join__promise-item { font-size: var(--fs-base); }
}

@media (min-width: 1024px) {
    .tale-notify { max-width: 34rem; }
}
