/* ============================================================================
 * MYSTRIX READER — the web reader's design system.
 *
 * IMP-0619. Built from docs/design/MYSTRIX_DESIGN_LANGUAGE.md; the palette, the
 * three type roles, the 12/16px radius and the forking-rule signature below are
 * that brief's decisions, not new ones. The component shapes ARE new — the
 * brief explicitly leaves them to each dependent improvement ("Concrete
 * component shapes ... are each dependent improvement's own job to design using
 * these tokens").
 *
 * WHY THIS FILE EXISTS
 * IMP-0619's complaint, in the code: welcome introduced a bespoke serif hero,
 * glass surfaces and 20 gradients; discovery drew cards one way and worlds drew
 * them another; the player alone carried five
 * font-family stacks and its own colour literals; pages.css and
 * creator-profile.css each defined an overlapping profile-card system. Four
 * routes, four products. root.css named `--text-muted` and gave it a pink, and
 * carried five "accent" colours whose only job was that a default was needed.
 * This file is the one place those decisions now live.
 *
 * DESIGN DIRECTION — AND HOW IT DIFFERS FROM THE STUDIO
 * The governing metaphor is an illuminated manuscript read in low light. The
 * creator studio (IMP-0626, creator/app/static/css/studio.css) is where that
 * manuscript is AUTHORED: it is dense (13px base, 32px controls), it is a
 * multi-pane workspace, and its signature is the UNFORKED margin rule down each
 * panel edge. This file is where the manuscript is READ, and the brief is
 * explicit that these are "deliberately DIFFERENT densities for different jobs":
 *
 *   - Density is generous. Base UI size is 16px, the canonical control is 40px
 *     tall, and the page is a single focal column, not a workspace.
 *   - The READING SERIF is the lead role here, not a borrowed preview font.
 *     Source Serif 4 sets every word of story prose; Inter is demoted to chrome.
 *   - The signature is the FORKING RULE (§8): the trunk line that splits at a
 *     choice. The studio's margin rule deliberately never forks; this is where
 *     it does. It is the one place the product spends motion, so every state
 *     component here (§7) is quiet by construction.
 *
 * SECTIONS
 *   1. Fonts (self-hosted)      6. Controls: buttons, fields, chips, badges
 *   2. Tokens                   7. State: empty, loading, skeleton
 *   3. Base / type roles        8. THE FORKING RULE — the signature
 *   4. Page + stage layout      9. Prose: narration, dialogue, recap
 *   5. Cards, panels, hero     10. Utilities
 * ==========================================================================*/


/* ---------------------------------------------------------------------------
 * 1. FONTS — self-hosted variable woff2, script-subsetted.
 *
 * Per the brief these are self-hosted rather than pulled from a third-party
 * CDN, following the precedent BUG-1627 set on this very service (the session
 * player is the authenticated reader and used to load an unpinned CDN script).
 *
 * These files are a COPY of the ones creator/app/static/fonts/ carries for
 * IMP-0626, not a second download of a different cut. main and creator are
 * separate origins — mystrix.ink and creator.mystrix.ink — so a font referenced
 * across them would need CORS on the other vhost and would still be a second
 * connection and a second cache entry for the reader. A copy inside this
 * service's own static tree is the cheaper and simpler answer.
 *
 * Source Serif 4 is new here: the studio only ever needed a fallback stack for
 * its preview pane, because the reading-serif role belongs to this surface.
 * Its italic is shipped too — story prose uses emphasis, and a synthesised
 * oblique of a reading serif is exactly the tell the brief is trying to avoid.
 *
 * Audit #1787260300587 fixes BUG-4499: every maintained face filename includes the
 * first 16 hex characters of its SHA-256, so cached bytes cannot cross releases and
 * preloaded Latin URLs remain byte-identical to their @font-face requests.
 *
 * Each face is split by unicode-range, so a reader in a Latin locale downloads
 * only the Latin subset. Scripts these faces do not cover (Arabic, Bengali,
 * Hindi, Marathi, Urdu, Chinese, Japanese — 7 of the product's 22 UI locales)
 * fall through to the system stack, which is correct: no glyph is faked.
 * Licence text for all three ships alongside them (OFL-*.txt).
 * ------------------------------------------------------------------------ */

/* --- Source Serif 4: THE READING FACE. Story prose, and nothing else. --- */
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-latin-f2ea9c12d2fe9bd3.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-latin-ext-155f6e701097cc26.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-cyrillic-e553fa829fd9ae09.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-cyrillic-ext-c4fbd9994039466f.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F,
    U+FE2E-FE2F;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-greek-d69b8909cbccf594.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
    U+03A3-03FF;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-vietnamese-e08ad84517b0cf6f.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: italic;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-latin-italic-96e6393209728ab9.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: italic;
  font-weight: 200 900;
  font-display: swap;
  src: url('../fonts/source-serif-4-var-latin-ext-italic-2078240cee94e627.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Fraunces: the display face. Titles and choice labels, used with
   restraint — large sizes and generous tracking, never body copy. --- */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-var-latin-7234ed860a9cc830.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-var-latin-ext-a2930b27d13a228b.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-var-vietnamese-f18853f63a870ebe.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}

/* --- Inter: the UI/utility face. Chrome, controls, data, timestamps. --- */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-latin-3100e775e8616cd2.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-latin-ext-34b9c504cab7a73e.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-cyrillic-71d5ee93cc1e9f1d.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-cyrillic-ext-ca157063339ac4ad.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F,
    U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-greek-1be3448e292fbf05.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
    U+03A3-03FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-vietnamese-5c66f9e07e90c6d4.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}


/* ---------------------------------------------------------------------------
 * 2. TOKENS
 *
 * The brief's seven colours are declared in root.css under the brief's own
 * names (--ink, --parchment, --amethyst, --amethyst-deep, --ember, --verdigris,
 * --error) because root.css is this service's declared single source of truth
 * and the existing reader CSS consumes its shared sitewide roles. Everything
 * derived — surfaces, rules,
 * type scale, space, motion — lives here under --rd-*.
 * ------------------------------------------------------------------------ */

:root {
  --reading-column: var(--rd-column);
  /* --- Surfaces. Every step is a violet-cast lift off --ink, never a neutral
     gray: the brief calls for "near-black with a violet cast". The reader gets
     a shallower stack than the studio because it has far fewer simultaneous
     panes to separate — depth here would be noise at reading distance. */
  --rd-surface: var(--theme-surface);         /* the leaf: cards, panels */
  --rd-surface-raised: var(--theme-surface-raised); /* hover, nested, sticky chrome */
  --rd-veil: var(--theme-veil);                /* recessed: inputs, cover wells */
  --rd-canvas: var(--theme-canvas);            /* page beneath the reading leaf */

  /* Rules (borders). "Rule" is the manuscript word for a ruled line, and the
     forking rule is this surface's signature — so borders share its vocabulary
     rather than being called "border-color-2". */
  --rd-rule: var(--theme-rule);
  --rd-rule-soft: var(--theme-rule-soft);
  --rd-rule-strong: var(--theme-rule-strong);

  /* Text. Contrast floors verified against every surface above:
     text 14.9:1, muted 8.0:1, dim 5.8:1, faint 4.9:1. */
  --rd-text: var(--theme-text);
  --rd-text-muted: var(--theme-text-muted);
  --rd-text-dim: var(--theme-text-dim);
  --rd-text-faint: var(--theme-text-faint);
  --rd-on-accent: var(--ink);    /* type sitting ON amethyst/ember/verdigris */

  /* Accent channels. Declared once in root.css beside the seven hexes they
     belong to; named here so a --rd-* tint never has to reach across files
     for a literal. root.css's 130 --tint-* aliases resolve through the same
     channel tokens, which is what keeps a tint from drifting off its base. */
  --rd-amethyst-rgb: var(--amethyst-rgb);
  --rd-ember-rgb: var(--ember-rgb);
  --rd-verdigris-rgb: var(--verdigris-rgb);
  --rd-error-rgb: var(--error-rgb);
  --rd-ink-rgb: var(--ink-rgb);
  --rd-parchment-rgb: var(--parchment-rgb);

  /* Two alphas per accent — a wash for surfaces, a line for borders — plus a
     glow for focus. Nothing else. This is what stops the grab-bag reappearing:
     there is no third value to reach for. */
  --rd-amethyst-wash: rgba(var(--rd-amethyst-rgb), 0.10);
  --rd-amethyst-wash-strong: rgba(var(--rd-amethyst-rgb), 0.18);
  --rd-amethyst-line: rgba(var(--rd-amethyst-rgb), 0.32);
  --rd-amethyst-glow: rgba(var(--rd-amethyst-rgb), 0.22);
  --rd-ember-wash: rgba(var(--rd-ember-rgb), 0.10);
  --rd-ember-line: rgba(var(--rd-ember-rgb), 0.30);
  --rd-verdigris-wash: rgba(var(--rd-verdigris-rgb), 0.10);
  --rd-verdigris-line: rgba(var(--rd-verdigris-rgb), 0.30);
  --rd-error-wash: rgba(var(--rd-error-rgb), 0.10);
  --rd-error-line: rgba(var(--rd-error-rgb), 0.32);
  --rd-scrim: rgba(var(--rd-ink-rgb), 0.72);
  --rd-scrim-strong: rgba(var(--rd-ink-rgb), 0.92);

  /* --- IMP-0736: light theme -----------------------------------------------
     Four "-fg" tokens for the ONE case a light background actually breaks: an
     accent hue used as TEXT/icon colour directly on a page or card surface
     (a hover title, a dialogue tag, the chosen fork label). Default here is
     the plain accent, unchanged from before this improvement — dark mode
     never touches these. The light override two sections down repoints them
     at the *-deep variants (root.css) so the same rules read correctly on a
     light surface without a single component rule needing an if/else. A
     filled control (amethyst button background, an accent progress fill) is
     a DIFFERENT contrast pair — foreground text against THAT fill, not
     against the page — and is unaffected in both themes, so those keep using
     the plain --amethyst/--ember/--verdigris/--error tokens directly. */
  --rd-amethyst-fg: var(--theme-primary);
  --rd-ember-fg: var(--theme-secondary);
  --rd-verdigris-fg: var(--theme-success);
  --rd-error-fg: var(--theme-error);

  /* Two components are deliberately theme-INVARIANT and must not follow
     --rd-text/--rd-surface into light mode:

     1. THE HERO (§5) sits on a photograph under a fixed ink scrim regardless
        of site theme — a scrim built for light-on-dark cannot flip to
        light-on-light without redrawing the photo treatment, so its
        foreground and fallback-well colours are pinned here at their current
        (dark-mode) values rather than aliased to --rd-text/--rd-surface-raised/
        --rd-veil/--rd-rule-strong, which DO move in light mode.
     2. BADGES (§6) are photographic-overlay chips (rating, tier, "new") drawn
        over a cover image of unknown colour, so they carry their own fixed
        dark translucent chip (`rgba(var(--rd-ink-rgb), 0.72)`, already
        theme-invariant) and need a foreground pinned to match it. */
  --rd-hero-fg: #efeaf7;
  --rd-hero-fg-muted: #b3abc7;
  --rd-hero-bg: #0e0c14;
  --rd-hero-fallback-a: #1b1725;
  --rd-hero-fallback-b: #0e0c14;
  --rd-hero-glyph: #372e4c;
  --rd-badge-fg: #b3abc7;

  /* --- Type -------------------------------------------------------------- */
  --rd-font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --rd-font-reading: 'Source Serif 4', 'Iowan Old Style', Georgia, serif;
  --rd-font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', sans-serif;
  --rd-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
    monospace;

  /* Type scale. The base is 16px, not the studio's 13px: this is read, not
     operated. Display sizes are fluid because a story title is the page's
     one hero moment and has to hold a 400px phone and a 1600px desktop. */
  --rd-text-xs: 0.75rem;      /* 12px — timestamps, counts */
  --rd-text-sm: 0.8125rem;    /* 13px — captions, meta */
  --rd-text-md: 0.875rem;     /* 14px — dense labels */
  --rd-text-base: 1rem;       /* 16px — the reader's workhorse UI size */
  --rd-text-lg: 1.0625rem;    /* 17px — lead UI copy */

  /* Reading role. --rd-prose is the size a scene is set at; the reader can
     scale it from the session's text-size setting by overriding this one
     custom property rather than by re-declaring font sizes per block. */
  --rd-prose: 1.125rem;       /* 18px */
  --rd-prose-sm: 1.0625rem;
  --rd-prose-lg: 1.25rem;

  --rd-display-xs: 1.0625rem; /* Fraunces: choice labels, card titles */
  --rd-display-sm: 1.25rem;   /* Fraunces: panel + section titles */
  --rd-display-md: clamp(1.5rem, 1.15rem + 1.4dvw, 2rem);  /* page titles */
  --rd-display-lg: clamp(2rem, 1.3rem + 3dvw, 3.25rem);    /* story hero */

  --rd-leading-tight: 1.15;
  --rd-leading-ui: 1.5;
  --rd-leading-prose: 1.75;   /* a page of narrative, not a paragraph of UI */
  --rd-tracking-display: -0.02em;
  --rd-tracking-ui: -0.006em;
  --rd-tracking-eyebrow: 0.08em;

  --rd-weight-normal: 400;
  --rd-weight-medium: 500;
  --rd-weight-semi: 600;
  --rd-weight-bold: 700;

  /* --- Space. 4px grid. The reader starts where the studio ends: its
     smallest routine gap is the studio's mid step, which IS the "generous
     whitespace" the brief asks the reader-facing product for. */
  --rd-space-1: 4px;
  --rd-space-2: 8px;
  --rd-space-3: 12px;
  --rd-space-4: 16px;
  --rd-space-5: 24px;
  --rd-space-6: 32px;
  --rd-space-7: 48px;
  --rd-space-8: 64px;
  --rd-space-9: 96px;

  /* --- Controls. 40px is the reader's canonical target: comfortable with a
     thumb, and 8px taller than the studio's, which is the density difference
     made concrete. */
  --rd-control-h-sm: 32px;
  --rd-control-h: 40px;
  --rd-control-h-lg: 48px;
  --rd-control-pad-x: var(--rd-space-4);

  /* --- Radius. The brief keeps the product's existing 12/16 and says not to
     relitigate them. The smaller steps are for controls nested inside. */
  --rd-radius-xs: 6px;
  --rd-radius-sm: 8px;
  --rd-radius: 12px;
  --rd-radius-lg: 16px;
  --rd-radius-pill: 999px;

  /* --- Composition. One focal column, per the brief's layout principles. */
  --rd-measure: 66ch;         /* prose line length */
  --rd-column: 720px;         /* the reading column */
  /* IMP-1066: a reader page fills the window rather than sitting in a 1180px strip.
     1180px was chosen as a reading measure, but it is not the measure that protects
     reading -- `--rd-measure` and `.reader-prose--wide`'s own 76ch cap do that, and they
     hold at any container width. What the fixed ceiling actually produced on a common
     1920px display was 740px of empty gutter beside every catalogue grid, card rail and
     two-up detail page: "it leaves black space", reported against the story page, the
     browse pages and the logged-out landing page alike.

     `min()` rather than a larger constant so the gutters stay proportional instead of
     becoming a hairline on a narrow laptop; `.reader-page` still supplies its own
     `padding-inline` inside this. `.reader-page--narrow` (860px) is unchanged -- the pages
     that genuinely want a strip ask for one by name. */
  --rd-page: min(1760px, 94dvw); /* the widest a reader page ever gets */

  /* --- The forking rule: the reader's signature dimensions (see §8). */
  --rd-rule-w: 2px;           /* the trunk's stroke */
  --rd-fork-gutter: 28px;     /* the margin the trunk runs down */
  --rd-fork-reach: 18px;      /* how far a branch travels off the trunk */
  --rd-fork-radius: 12px;     /* the bend where a branch leaves the trunk */

  /* --- Motion. Everything is fast and small EXCEPT --rd-dur-fork, which is
     the one place the product spends its motion budget. */
  --rd-ease: cubic-bezier(0.2, 0, 0, 1);
  --rd-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --rd-dur-fast: 120ms;
  --rd-dur: 200ms;
  --rd-dur-slow: 340ms;
  --rd-dur-fork: 520ms;

  /* --- Elevation. Low light: shadows are deep and soft, never gray. */
  --rd-shadow-sm: 0 1px 2px rgba(var(--rd-ink-rgb), 0.5);
  --rd-shadow: 0 8px 28px rgba(var(--rd-ink-rgb), 0.55);
  --rd-shadow-lg: 0 24px 64px rgba(var(--rd-ink-rgb), 0.65);
  --rd-focus-ring: 0 0 0 3px var(--rd-amethyst-glow);
}


/* ---------------------------------------------------------------------------
 * 2b. LIGHT THEME LEAF.
 *
 * Mode selection and palette values live once in root.css's --theme-* roles.
 * Every --rd-* colour above is an alias of those sitewide roles. The only
 * reader-specific light rule left here gives transparent manuscript stages a
 * physical leaf behind dark prose; it does not define another palette.
 * ------------------------------------------------------------------------ */

/* .reader-stage and .reader-fork (§4/§8) are deliberately transparent in dark
   mode, where they sit directly on the reader's ink canvas. In light mode the
   transcript they carry (.reader-beat, .message-content, .dialogue-text — all
   repointed at --rd-text, which is now --ink) needs its own light leaf even
   when embedded by a consumer with different surrounding chrome. Giving them
   --rd-surface is the same leaf every other reader component already uses, so
   the live transcript looks like one continuous page. Dark mode is untouched:
   both selectors are gated the same two ways as the tokens above. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .reader-stage,
  :root:not([data-theme="dark"]) .reader-fork {
    background: var(--rd-surface);
    border-radius: var(--rd-radius-lg);
    padding-block: var(--rd-space-4);
  }
}
:root[data-theme="light"] .reader-stage,
:root[data-theme="light"] .reader-fork {
  background: var(--rd-surface);
  border-radius: var(--rd-radius-lg);
  padding-block: var(--rd-space-4);
}


/* ---------------------------------------------------------------------------
 * 3. BASE / TYPE ROLES
 *
 * main.css:18 and session-runtime.css:5 both set the body font to the system UI
 * stack — the exact stack the brief names as the "tiny system UI" problem.
 * reader.css loads after both and installs the three real roles.
 * ------------------------------------------------------------------------ */

body {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  line-height: var(--rd-leading-ui);
  letter-spacing: var(--rd-tracking-ui);
  font-feature-settings: 'cv05' 1, 'cv08' 1;
  -webkit-font-smoothing: antialiased;
}

/* Display role. Restraint is enforced by scope: Fraunces is reachable only
   through .reader-display and the components below that opt into it. */
.reader-display {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0;
}

/* READING ROLE — the one this surface exists for. Everything a reader is
   actually reading (scene text, dialogue, recap, synopsis, excerpt) is set
   here; nothing else is. */
.reader-prose {
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose);
  line-height: var(--rd-leading-prose);
  letter-spacing: 0;
  color: var(--rd-text);
  max-width: var(--rd-measure);
  font-variant-numeric: oldstyle-nums;
}
.reader-prose p { margin: 0 0 1em; }
.reader-prose p:last-child { margin-bottom: 0; }
.reader-prose em,
.reader-prose i { font-style: italic; }
.reader-prose strong,
.reader-prose b { font-weight: var(--rd-weight-semi); }
/* Wide variant for a hero synopsis that is not inside the reading column. */
.reader-prose--wide { max-width: 76ch; }
.reader-prose--sm { font-size: var(--rd-prose-sm); }
.reader-prose--muted { color: var(--rd-text-muted); }

/* An eyebrow: the small tracked-out label above a title. Inter, never Fraunces
   — the brief reserves the display face for large sizes. */
.reader-eyebrow {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-text-faint);
  margin: 0;
}

.reader-num { font-variant-numeric: tabular-nums; }
.reader-mono { font-family: var(--rd-font-mono); font-size: var(--rd-text-sm); }


/* ---------------------------------------------------------------------------
 * 4. PAGE + STAGE LAYOUT
 *
 * "Generous whitespace and a single clear focal column" (brief, Density). A
 * reader page is one column with an optional aside; it is never a workspace.
 * ------------------------------------------------------------------------ */

.reader-page {
  max-width: var(--rd-page);
  margin-inline: auto;
  padding-inline: var(--rd-space-4);
  padding-block-end: var(--rd-space-8);
}

/* Audit #1787260301009 fixes BUG-4871: dom.js injects this control into every reader surface, outside the
 * page-specific stylesheet that raised the rest of the mobile targets.  The visible
 * multiplication glyph can stay small; its dismiss action still owns a complete target. */
.toast-close {
  display: inline-grid;
  place-items: center;
  flex: 0 0 44px;
  min-inline-size: 44px;
  min-block-size: 44px;
  padding: 0;
  line-height: 1;
}

/* IMP-1397: ONE owner for compact horizontal breathing room.
 *
 * Three owners were each taking a share of a phone's width and compounding: `.content`
 * padding, this page's `94vw` ceiling, and this page's own `padding-inline`. Measured on the
 * shipped build at 320px the page box was 296 wide but its grids and cards sat at x=28 with
 * width 264 -- 82.5% of the viewport, and 84.4/85.6/86.6% at 360/390/430. That is contained
 * rather than overflowing, so nothing looked broken; it just quietly spent a sixth of the
 * scarcest axis on nested gutters.
 *
 * Below the reader's own compact breakpoint the page spans the viewport and owns the single
 * gutter, which is the larger of the reading gutter and the device's safe area. `.content`
 * gives up its inline padding for the same routes so the two cannot stack again.
 *
 * The safe area is read as the LARGER of the two horizontal insets and applied to both
 * sides, so this stays correct in RTL: `env(safe-area-inset-left)` is physical and does not
 * flip with writing direction, and pairing a physical inset with a logical side is how a
 * notch ends up guarded on the wrong edge. Symmetry costs nothing on a device whose insets
 * differ only by rotation, and the properties stay logical for the RTL gate.
 */
@media (max-width: 768px) {
  /* The BUG-4871 audit gives base-derived reader pages (never the standalone story player)
     one mobile control floor.  Small buttons remain typographically small without
     exposing a small hit box, and fields/selects inherit the same token. */
  body.reader-shell {
    --rd-control-h-sm: 44px;
    --rd-control-h: 44px;
  }

  .reader-page {
    --rd-safe-inline: max(
      env(safe-area-inset-left, 0px),
      env(safe-area-inset-right, 0px)
    );
    max-width: 100%;
    padding-inline: max(var(--rd-space-1), var(--rd-safe-inline));
  }

  /* A narrow page asks for a strip by name, so it keeps its measure; it simply stops
     paying the outer gutter twice. */
  .reader-page--narrow {
    max-width: min(860px, 100%);
  }

  /* The other half of "one owner": the shell stops adding a gutter of its own around a page
     that now provides it. Scoped by containment rather than by a list of page names, so a
     new reader route is covered by being one, not by being remembered here. */
  .content:has(.reader-page) {
    padding-inline: 0;
  }

  /* IMP-1439: art and card surfaces use the single 4px page edge above.  Labels
     and prose keep one 8px logical inset instead of accumulating page, panel,
     card, toolbar, and action gutters. */
  .reader-card__body,
  .reader-card__actions,
  .reader-panel,
  .reader-hero {
    padding-inline: var(--rd-space-2);
  }

  .reader-toolbar {
    padding-inline: var(--rd-space-1);
  }

  .reader-search__clear,
  .reader-chip button {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
  }

  .reader-search .reader-input {
    padding-inline-end: calc(44px + var(--rd-space-3));
  }
}

@media (max-width: 992px) and (max-height: 500px) and (orientation: landscape) {
  body.reader-shell {
    --rd-control-h-sm: 44px;
    --rd-control-h: 44px;
  }

  .reader-search__clear,
  .reader-chip button {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
  }
}
.reader-page--narrow { max-width: 860px; }

/* Vertical rhythm between page sections. One value, so a reader moving between
   routes never meets a different spacing system. */
.reader-stack { display: flex; flex-direction: column; gap: var(--rd-space-5); }
.reader-stack--roomy { gap: var(--rd-space-6); }
.reader-stack--tight { gap: var(--rd-space-3); }

/* Two-up: main column plus a sticky aside (story detail's CTA rail). */
.reader-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--rd-space-6);
  align-items: start;
}
.reader-split__main { min-width: 0; }
.reader-split__aside {
  display: flex;
  flex-direction: column;
  gap: var(--rd-space-4);
  position: sticky;
  top: var(--rd-space-5);
  min-width: 0;
}
@media (max-width: 900px) {
  .reader-split { grid-template-columns: minmax(0, 1fr); }
  .reader-split__aside { position: static; }
}

/* --- Page header. One system, replacing .browse-intro / .page-header /
   .section-header / .detail-header and the four title sizes between them.

   The short amethyst rule at the inline-start of the underline is where the
   trunk line starts on a reader screen — the same line that forks at a choice
   and that the studio carries, unforked, down every panel edge. -------------*/
.reader-page-header {
  display: flex;
  align-items: flex-end;
  gap: var(--rd-space-5);
  flex-wrap: wrap;
  padding-block: var(--rd-space-6) var(--rd-space-4);
  margin-block-end: var(--rd-space-5);
  border-block-end: 1px solid var(--rd-rule-soft);
  position: relative;
}
.reader-page-header::after {
  content: '';
  position: absolute;
  inset-block-end: -1px;
  inset-inline-start: 0;
  width: 56px;
  height: var(--rd-rule-w);
  background: var(--amethyst);
  border-radius: var(--rd-radius-pill);
}
.reader-page-header__content { flex: 1 1 360px; min-width: 0; }
.reader-page-header__title {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-md);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0;
}
.reader-page-header__subtitle {
  margin: var(--rd-space-2) 0 0;
  font-size: var(--rd-text-lg);
  line-height: var(--rd-leading-ui);
  color: var(--rd-text-muted);
  max-width: 68ch;
}
.reader-page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--rd-space-3);
  flex-wrap: wrap;
}

/* --- Section heading inside a page. --------------------------------------*/
.reader-section-header {
  display: flex;
  align-items: baseline;
  gap: var(--rd-space-3);
  margin-block-end: var(--rd-space-4);
}
.reader-section-header__title {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-sm);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0;
}
.reader-section-header__rule { flex: 1; height: 1px; background: var(--rd-rule-soft); }

/* --- THE STAGE. The live manuscript column: the transcript being read, or
   any single-column run of prose. It carries the trunk of the forking rule
   down its inline-start edge (§8), which is why it, and not .reader-page, is
   what the session transcript uses. --------------------------------------- */
.reader-stage {
  position: relative;
  max-width: var(--rd-column);
  margin-inline: auto;
  padding-inline-start: var(--rd-fork-gutter);
  padding-inline-end: var(--rd-space-2);
}
.reader-stage::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--rd-rule-w);
  border-radius: var(--rd-radius-pill);
  /* The trunk fades in and out at the column's ends rather than butting into
     the chrome — a ruled margin on a leaf, not a table border. */
  background: linear-gradient(
    to bottom,
    rgba(var(--rd-amethyst-rgb), 0) 0%,
    var(--rd-amethyst-line) 6%,
    var(--rd-amethyst-line) 94%,
    rgba(var(--rd-amethyst-rgb), 0) 100%
  );
  pointer-events: none;
}
/* A stage with no live choice pending keeps the trunk dimmer still — the line
   only reaches full amethyst where the story is actually branching. */
.reader-stage--quiet::before { opacity: 0.55; }


/* ---------------------------------------------------------------------------
 * 5. CARDS, PANELS, HERO
 *
 * THE canonical card. Discovery drew one card, worlds drew another, the creator
 * profile drew a third, and welcome drew a
 * fourth in glass. One shape now: an ink leaf with a hairline rule, a 3:4 cover
 * well, a Fraunces title and an Inter meta row.
 *
 * The cover is 3:4 (portrait) rather than the studio's 16:9. A story cover is
 * a book jacket; the studio's is a workspace thumbnail. That is the difference
 * between browsing a shelf and managing assets, and it is deliberate.
 * ------------------------------------------------------------------------ */

.reader-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--rd-space-4);
}
.reader-grid--wide { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
@media (max-width: 600px) {
  .reader-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--rd-space-3); }
  /* Audit #1787260300399 fixes BUG-4385; Audit #1787260300400 fixes BUG-4388.
     These two current result grids can legitimately contain one choice; span
     that only child across the tracks instead of reserving a blank second card. */
  #pilots-grid > .reader-card:only-child,
  #creators-grid > .reader-card:only-child { grid-column: 1 / -1; }
}

.reader-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--rd-surface);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--rd-dur) var(--rd-ease),
              background-color var(--rd-dur) var(--rd-ease),
              transform var(--rd-dur) var(--rd-ease);
}
.reader-card:hover {
  background: var(--rd-surface-raised);
  border-color: var(--rd-rule-strong);
}
.reader-card:focus-within {
  border-color: var(--amethyst);
  box-shadow: var(--rd-focus-ring);
}
/* Selected / active — the same amethyst wash the studio uses, so a reader who
   also creates never learns two "this one" languages. */
.reader-card--active {
  border-color: var(--rd-amethyst-line);
  background: var(--rd-amethyst-wash);
}
/* Locked: the reader's current access entitlement does not include this story. */
.reader-card--locked .reader-card__cover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(var(--rd-ink-rgb), 0.55);
}

.reader-card__cover {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  background: var(--rd-veil);
  background-size: cover;
  background-position: center;
  overflow: hidden;
}
.reader-card__cover img {
  width: 100%;
  height: 100%;
  /* Audit #1787260306212 (BUG-9970): preserve the approved composition
     inside the shared portrait well, including landscape artwork and titles. */
  object-fit: contain;
  /* The stored low-resolution preview paints behind the lazy image (BUG-7368). */
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  display: block;
  transition: filter var(--rd-dur) var(--rd-ease);
}
/* Audit #1787260303576 fixes BUG-7429 §9.3.6.6: pointer hover brightens the cover slightly. */
.reader-card:hover .reader-card__cover img,
.reader-card:hover .reader-card__cover .story-spectrum {
  filter: brightness(1.08);
}
/* IMP-1759: an artless published Story is intentionally audio-led, not a blank slab. */
.story-spectrum {
  /* Server and client normally set these identity values inline. Defaults keep
     the stylesheet contract valid during first paint and defensive rendering. */
  --story-hue: 270;
  --story-accent-hue: 32;
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  background:
    radial-gradient(circle at 74% 20%, hsl(var(--story-accent-hue) 78% 62% / 0.38), transparent 42%),
    linear-gradient(145deg, hsl(var(--story-hue) 52% 18%), hsl(var(--story-accent-hue) 46% 10%));
}
.story-spectrum::before {
  content: '';
  position: absolute;
  inline-size: 76%;
  aspect-ratio: 1;
  border: 1px solid hsl(var(--story-accent-hue) 80% 78% / 0.24);
  border-radius: 50%;
  box-shadow: 0 0 3rem hsl(var(--story-hue) 88% 60% / 0.22);
}
.story-spectrum__bars {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(3px, 0.45rem, 8px);
  inline-size: 48%;
  block-size: 34%;
}
.story-spectrum__bar {
  inline-size: 14%;
  block-size: 100%;
  border-radius: 999px;
  background: linear-gradient(to top, hsl(var(--story-hue) 80% 58%), hsl(var(--story-accent-hue) 92% 80%));
  box-shadow: 0 0 1rem hsl(var(--story-accent-hue) 90% 68% / 0.28);
  transform-origin: bottom;
  animation: story-spectrum-pulse 1.05s ease-in-out infinite alternate;
}
.story-spectrum__bar:nth-child(1) { block-size: 44%; animation-delay: -0.7s; }
.story-spectrum__bar:nth-child(2) { block-size: 76%; animation-delay: -0.25s; }
.story-spectrum__bar:nth-child(3) { block-size: 100%; animation-delay: -0.85s; }
.story-spectrum__bar:nth-child(4) { block-size: 64%; animation-delay: -0.4s; }
.story-spectrum__bar:nth-child(5) { block-size: 86%; animation-delay: -0.6s; }
@keyframes story-spectrum-pulse {
  from { transform: scaleY(0.42); opacity: 0.72; }
  to { transform: scaleY(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .story-spectrum__bar {
    animation: none;
    transform: none;
  }
}
.reader-card__badges {
  position: absolute;
  inset-block-start: var(--rd-space-2);
  inset-inline-start: var(--rd-space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--rd-space-1);
  z-index: 1;
}
.reader-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--rd-space-2);
  padding: var(--rd-space-3) var(--rd-space-4) var(--rd-space-4);
  flex: 1;
  min-width: 0;
}
.reader-card__title {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0;
}
.reader-card__title a { color: inherit; text-decoration: none; }
.reader-card__title a:hover { color: var(--rd-amethyst-fg); }
.reader-card__byline {
  margin: 0;
  min-width: 0;
  font-size: var(--rd-text-sm);
  color: var(--rd-text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reader-card__byline a { color: var(--rd-text-muted); text-decoration: none; }
.reader-card__byline a:hover { color: var(--rd-amethyst-fg); }
/* A card's blurb is prose the reader reads, so it is set in the reading serif
   at a small size — this is where the discovery grid stops looking like a
   product catalogue and starts looking like a shelf. */
.reader-card__blurb {
  margin: 0;
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose-sm);
  line-height: 1.55;
  color: var(--rd-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.reader-card__meta,
.reader-card__social-proof {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rd-space-3);
  margin-block-start: auto;
  padding-block-start: var(--rd-space-2);
  font-size: var(--rd-text-xs);
  color: var(--rd-text-faint);
  font-variant-numeric: tabular-nums;
}
.reader-card__meta i { margin-inline-end: var(--rd-space-1); }
.reader-card__social-proof {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rd-space-3);
  margin: 0;
  color: var(--rd-text-faint);
  font-size: var(--rd-text-xs);
  font-variant-numeric: tabular-nums;
}
.reader-card__social-proof .fa-star { color: var(--rd-amethyst-fg); }
/* IMP-1150: the rating-tier line and the navigable tag chips that the canonical
   card contract carries. Both sit in the body block, before the social proof
   row, so a card carries cover -> title -> byline -> blurb -> tier -> tags -> plays
   in that order on first paint. The chip row uses the same .reader-chip the filter
   bar already uses, so a tag click and a filter click look like one thing. */
.reader-card__rating,
.reader-card__tags {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rd-space-2);
}
.reader-card__rating .reader-badge {
  text-transform: none;
  letter-spacing: var(--rd-tracking-ui);
}
.reader-card__tags {
  padding-block-start: var(--rd-space-1);
}
.reader-card__tags .reader-chip {
  font-size: var(--rd-text-xs);
  padding-block: 2px;
}
.reader-card__actions {
  display: flex;
  align-items: center;
  gap: var(--rd-space-2);
  padding: var(--rd-space-3) var(--rd-space-4);
  border-block-start: 1px solid var(--rd-rule-soft);
}
.reader-card__actions .reader-btn--grow { flex: 1; }
/* Audit #1787260306210 (BUG-9966): actions have their own surface after the
   story copy, so touch, pointer and keyboard users see the same unobstructed art. */
/* Audit #1787260306270 fixes BUG-9968: Home detail navigation uses the title
   (IMP-2202); keep its primary control on the body surface, outside artwork. */
.reader-card__hover {
  position: static;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--rd-space-2);
  min-width: 0;
  padding: var(--rd-space-3) var(--rd-space-4);
  border-block-start: 1px solid var(--rd-rule-soft);
  opacity: 1;
}
.reader-card__hover .reader-btn {
  min-width: 0;
  max-width: 100%;
  height: auto;
  min-height: var(--rd-control-h-sm);
  padding-block: var(--rd-space-2);
  line-height: var(--rd-leading-ui);
  white-space: normal;
  overflow-wrap: anywhere;
}
/* --- Panel: a card that is not in a grid. The story detail's sections, the
   CTA rail, a settings group. Same leaf, no cover. ------------------------ */
.reader-panel {
  background: var(--rd-surface);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius);
  padding: var(--rd-space-4);
  min-width: 0;
}
.reader-panel--roomy { padding: var(--rd-space-5); }
.reader-panel--flush { padding: 0; }
.reader-panel--quiet { background: transparent; border-color: var(--rd-rule-soft); }
/* Accent variants — strictly the brief's three roles, no others. */
.reader-panel--warn { border-color: var(--rd-ember-line); background: var(--rd-ember-wash); }
.reader-panel--good { border-color: var(--rd-verdigris-line); background: var(--rd-verdigris-wash); }
.reader-panel--danger { border-color: var(--rd-error-line); background: var(--rd-error-wash); }
.reader-panel__title {
  display: flex;
  align-items: center;
  gap: var(--rd-space-2);
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-sm);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0 0 var(--rd-space-3);
}
.reader-panel__title > i { font-size: 0.8em; color: var(--rd-amethyst-fg); flex: none; }
.reader-panel__note {
  margin: 0;
  font-size: var(--rd-text-sm);
  line-height: var(--rd-leading-ui);
  color: var(--rd-text-muted);
}

/* --- Hero: the story-detail cover moment. The one place a reader page is
   allowed to be cinematic, and the only place --rd-display-lg is used. ---- */
.reader-hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  min-height: clamp(280px, 42dvh, 460px);
  padding: var(--rd-space-6) var(--rd-space-5) var(--rd-space-5);
  border-radius: var(--rd-radius-lg);
  overflow: hidden;
  /* IMP-0736: --rd-hero-bg, not --rd-veil — the hero is theme-invariant (see
     the token comment in §2), so its own well colour is pinned rather than
     following light mode. */
  background: var(--rd-hero-bg);
}
.reader-hero__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
/* The scrim. A single vertical ink gradient — not the three stacked radial
   gradients each detail/hero surface used to invent for itself. */
.reader-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    var(--rd-scrim-strong) 0%,
    var(--rd-scrim) 45%,
    rgba(var(--rd-ink-rgb), 0.25) 100%
  );
}
.reader-hero__content {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--rd-space-2);
  max-width: 40ch;
  min-width: 0;
}
.reader-hero__title {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-lg);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: 1.02;
  /* IMP-0736: --rd-hero-fg, not --rd-text — see the §2 token comment. This
     text sits on a photo behind a fixed dark scrim in both themes. */
  color: var(--rd-hero-fg);
  margin: 0;
  text-wrap: balance;
}
.reader-hero__tagline {
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose);
  font-style: italic;
  line-height: 1.45;
  color: var(--rd-hero-fg-muted);
  margin: 0;
}
.reader-hero__byline {
  font-size: var(--rd-text-sm);
  color: var(--rd-hero-fg-muted);
  margin: 0;
}
.reader-hero__byline a { color: var(--amethyst); text-decoration: none; }
.reader-hero__byline a:hover { text-decoration: underline; }
.reader-hero__badges { display: flex; flex-wrap: wrap; gap: var(--rd-space-1); }
/* No artwork: the hero degrades to a quiet parchment-less well with the
   title's initial, rather than a grey box. */
.reader-hero__fallback {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--rd-font-display);
  font-size: clamp(4rem, 18dvw, 9rem);
  /* IMP-0736: pinned hero tokens, not --rd-rule-strong/--rd-surface-raised/
     --rd-veil — same theme-invariance as the rest of §5's hero (see §2). */
  color: var(--rd-hero-glyph);
  background: linear-gradient(160deg, var(--rd-hero-fallback-a), var(--rd-hero-fallback-b));
}


/* ---------------------------------------------------------------------------
 * 6. CONTROLS — buttons, fields, chips, badges, toolbar
 * ------------------------------------------------------------------------ */

.reader-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--rd-space-2);
  height: var(--rd-control-h);
  padding-inline: var(--rd-space-4);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  font-weight: var(--rd-weight-medium);
  letter-spacing: var(--rd-tracking-ui);
  line-height: 1;
  white-space: nowrap;
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius-sm);
  background: var(--rd-surface-raised);
  color: var(--rd-text);
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--rd-dur-fast) var(--rd-ease),
              border-color var(--rd-dur-fast) var(--rd-ease),
              color var(--rd-dur-fast) var(--rd-ease);
}
.reader-btn:hover { background: var(--rd-rule); border-color: var(--rd-rule-strong); color: var(--rd-text); }
.reader-btn:focus-visible { outline: none; box-shadow: var(--rd-focus-ring); }
.reader-btn:disabled,
.reader-btn[disabled] { opacity: 0.45; cursor: not-allowed; }

.reader-btn--primary {
    background: var(--amethyst-deep);
    border-color: var(--amethyst-deep);
    color: var(--parchment);
}
.reader-btn--secondary {
    background: var(--rd-surface-raised);
    border-color: var(--rd-rule-strong);
    color: var(--rd-text);
}
.reader-btn--secondary:hover {
    background: var(--rd-rule);
    color: var(--rd-text);
}
/* Audit #1787260302700 fixes BUG-6474: the fill is theme-invariant, so the hovered
   label keeps the fill's own --parchment pair; --rd-text turned to ink in light theme. */
.reader-btn--primary:hover {
  background: var(--amethyst-deep);
  border-color: var(--amethyst-deep);
  color: var(--parchment);
}
.reader-btn--ghost { background: transparent; color: var(--rd-text-muted); }
.reader-btn--ghost:hover { background: var(--rd-surface-raised); color: var(--rd-text); }
.reader-btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--rd-text-muted);
  padding-inline: var(--rd-space-3);
}
.reader-btn--quiet:hover { background: var(--rd-surface-raised); color: var(--rd-text); }
.reader-btn--danger { background: transparent; border-color: var(--rd-error-line); color: var(--rd-error-fg); }
.reader-btn--danger:hover { background: var(--rd-error-wash); }
/* Ember, the brief's warmth accent, for a highlighted secondary action. It is
   outlined, not filled, so it never competes with --rd-btn--primary. */
.reader-btn--warm { background: transparent; border-color: var(--rd-ember-line); color: var(--rd-ember-fg); }
.reader-btn--warm:hover { background: var(--rd-ember-wash); }
.reader-btn--lg { height: var(--rd-control-h-lg); font-size: var(--rd-text-lg); padding-inline: var(--rd-space-5); }
.reader-btn--sm { height: var(--rd-control-h-sm); font-size: var(--rd-text-sm); padding-inline: var(--rd-space-3); }
.reader-btn--icon { padding-inline: 0; width: var(--rd-control-h); flex: none; }
.reader-btn--icon.reader-btn--sm { width: var(--rd-control-h-sm); }
.reader-btn--block { width: 100%; }

/* Fields. One primitive, recessed into the page like a ruled blank. */
.reader-field { display: flex; flex-direction: column; gap: var(--rd-space-2); min-width: 0; }
.reader-field__label {
  font-size: var(--rd-text-md);
  font-weight: var(--rd-weight-medium);
  color: var(--rd-text-muted);
  line-height: var(--rd-leading-tight);
}
.reader-field__hint { margin: 0; font-size: var(--rd-text-sm); color: var(--rd-text-faint); }

.reader-input,
.reader-select,
.reader-textarea {
  width: 100%;
  min-width: 0;
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  line-height: var(--rd-leading-ui);
  letter-spacing: var(--rd-tracking-ui);
  color: var(--rd-text);
  background: var(--rd-veil);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius-sm);
  padding: 0 var(--rd-control-pad-x);
  height: var(--rd-control-h);
  appearance: none;
  transition: border-color var(--rd-dur-fast) var(--rd-ease),
              box-shadow var(--rd-dur-fast) var(--rd-ease);
}
.reader-textarea {
  height: auto;
  min-height: 96px;
  padding: var(--rd-space-3) var(--rd-control-pad-x);
  resize: vertical;
  line-height: var(--rd-leading-prose);
}
.reader-select {
  /* Chevron drawn in the rule colour so the control has no second palette. */
  background-image: linear-gradient(45deg, transparent 50%, var(--rd-text-faint) 50%),
                    linear-gradient(135deg, var(--rd-text-faint) 50%, transparent 50%);
  background-position: calc(100% - 18px) calc(50% + 1px), calc(100% - 13px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-inline-end: var(--rd-space-6);
  cursor: pointer;
}
[dir='rtl'] .reader-select {
  background-position: 18px calc(50% + 1px), 13px calc(50% + 1px);
}
.reader-input::placeholder,
.reader-textarea::placeholder { color: var(--rd-text-faint); }
.reader-input:hover,
.reader-select:hover,
.reader-textarea:hover { border-color: var(--rd-rule-strong); }
.reader-input:focus,
.reader-select:focus,
.reader-textarea:focus {
  outline: none;
  border-color: var(--amethyst);
  box-shadow: var(--rd-focus-ring);
}

/* A search field with its glyph inside the well. */
.reader-search { position: relative; display: flex; align-items: center; min-width: 0; flex: 1 1 260px; }
.reader-search > i {
  position: absolute;
  inset-inline-start: var(--rd-space-3);
  color: var(--rd-text-faint);
  pointer-events: none;
  font-size: var(--rd-text-md);
}
.reader-search .reader-input { padding-inline-start: var(--rd-space-6); }
.reader-search__clear {
  position: absolute;
  inset-inline-end: var(--rd-space-2);
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--rd-radius-pill);
  background: transparent;
  color: var(--rd-text-faint);
  cursor: pointer;
}
.reader-search__clear:hover { background: var(--rd-rule); color: var(--rd-text); }

/* Toolbar: the filter/search bar above a grid. */
.reader-toolbar {
  display: flex;
  align-items: center;
  gap: var(--rd-space-3);
  flex-wrap: wrap;
  padding: var(--rd-space-3);
  background: var(--rd-surface);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius);
  margin-block-end: var(--rd-space-4);
}
.reader-toolbar__group { display: flex; align-items: center; gap: var(--rd-space-2); flex-wrap: wrap; min-width: 0; }
.reader-toolbar__group--grow { flex: 1 1 260px; }
.reader-toolbar .reader-select { width: auto; min-width: 148px; }

/* Chips: tags, active filters, facets. One control for all three. */
.reader-chips { display: flex; flex-wrap: wrap; gap: var(--rd-space-2); min-width: 0; }
.reader-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--rd-space-2);
  height: var(--rd-control-h-sm);
  padding-inline: var(--rd-space-3);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-md);
  line-height: 1;
  color: var(--rd-text-muted);
  background: var(--rd-surface);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius-pill);
  text-decoration: none;
  cursor: pointer;
  max-width: 100%;
  transition: border-color var(--rd-dur-fast) var(--rd-ease),
              color var(--rd-dur-fast) var(--rd-ease),
              background-color var(--rd-dur-fast) var(--rd-ease);
}
.reader-chip:hover { border-color: var(--rd-rule-strong); color: var(--rd-text); }
/* Audit #1787260306213 (BUG-9984): authored safety copy must keep every line
   inside its boundary at enlarged text sizes, with the next chip flowing below. */
.reader-chip--boundary,
.reader-card__boundaries .reader-chip--boundary {
  display: block;
  height: auto;
  min-height: var(--rd-control-h-sm);
  padding-block: 0.35em;
  line-height: var(--rd-leading-ui);
  white-space: normal;
  overflow-wrap: anywhere;
  cursor: default;
}
.reader-chip--boundary > i { margin-inline-end: var(--rd-space-1); }
.reader-chip.is-active,
.reader-chip--active {
  background: var(--rd-amethyst-wash);
  border-color: var(--rd-amethyst-line);
  color: var(--rd-text);
}
.reader-chip button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  border-radius: var(--rd-radius-pill);
  background: transparent;
  color: var(--rd-text-faint);
  font-size: var(--rd-text-sm);
  cursor: pointer;
}
.reader-chip button:hover { background: var(--rd-amethyst-wash-strong); color: var(--rd-text); }

/* Badges: rating, access, language, "new". Read-only, never clickable. */
.reader-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--rd-space-1);
  height: 22px;
  padding-inline: var(--rd-space-2);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  font-weight: var(--rd-weight-medium);
  line-height: 1;
  white-space: nowrap;
  border-radius: var(--rd-radius-pill);
  background: rgba(var(--rd-ink-rgb), 0.72);
  border: 1px solid var(--rd-rule);
  /* IMP-0736: --rd-badge-fg, not --rd-text-muted — a badge is a photographic-
     overlay chip (§2) with a fixed dark translucent background regardless of
     theme, so its foreground is pinned to match rather than following
     --rd-text-muted into light mode, which would leave light text on this
     still-dark chip. The accent variants below keep the plain hues for the
     same reason: they sit on the same fixed-dark chip in both themes. */
  color: var(--rd-badge-fg);
  backdrop-filter: blur(6px);
}
.reader-badge--brand { background: var(--rd-amethyst-wash); border-color: var(--rd-amethyst-line); color: var(--amethyst); }
.reader-badge--good { background: var(--rd-verdigris-wash); border-color: var(--rd-verdigris-line); color: var(--verdigris); }
/* Ember is "warmth/highlight" per the brief: an active cinematic scene, a
   "new" marker. It is not a warning colour here. */
.reader-badge--warm { background: var(--rd-ember-wash); border-color: var(--rd-ember-line); color: var(--ember); }
.reader-badge--danger { background: var(--rd-error-wash); border-color: var(--rd-error-line); color: var(--error); }

/* A stat pair (players, rating). Tabular so a column of them lines up. */
.reader-stats { display: flex; gap: var(--rd-space-5); flex-wrap: wrap; }
.reader-stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.reader-stat__value {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-sm);
  font-weight: var(--rd-weight-semi);
  font-variant-numeric: tabular-nums;
  color: var(--rd-text);
  line-height: 1;
}
.reader-stat__label {
  font-size: var(--rd-text-xs);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-text-faint);
}

/* Progress: a rule that fills. Same primitive as everything else here. */
.reader-progress { display: flex; align-items: center; gap: var(--rd-space-3); font-size: var(--rd-text-xs); color: var(--rd-text-faint); font-variant-numeric: tabular-nums; }
.reader-progress__track { flex: 1; height: var(--rd-rule-w); background: var(--rd-rule); border-radius: var(--rd-radius-pill); overflow: hidden; }
.reader-progress__fill { height: 100%; background: var(--amethyst); border-radius: var(--rd-radius-pill); transition: width var(--rd-dur-slow) var(--rd-ease); }


/* ---------------------------------------------------------------------------
 * 7. STATE COMPONENTS — empty, loading, skeleton
 *
 * The brief is explicit that state screens are "deliberately UNDECORATED
 * relative to the signature moment, not competing with it". The studio drew
 * that conclusion as a dashed blank leaf; the reader draws it differently on
 * purpose, and the difference is the point:
 *
 *   A reader is not looking at an unwritten page they are about to fill in —
 *   they are looking for something to read and there is nothing here. So the
 *   reader's empty state has NO enclosure at all: no dashed border, no box,
 *   no fill. It is a short centred line of type under a single hairline rule.
 *   It reads as a gap in the shelf rather than a blank form, and it is quieter
 *   than the studio's, which is the correct relationship for a surface whose
 *   whole job is to keep the reader's attention on prose.
 *
 * Replaces .browse-empty (48px glyph, own padding), .session-error's icon
 * block, the four hand-rolled skeleton-card copies in stories.html, and the
 * spin keyframe duplicated in mobile.css and session-runtime.css.
 * ------------------------------------------------------------------------ */

.reader-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--rd-space-3);
  padding: var(--rd-space-7) var(--rd-space-4);
  background: transparent;
  border: 0;
  color: var(--rd-text-muted);
}
/* The hairline: the one mark an empty state gets. */
.reader-empty::before {
  content: '';
  width: 56px;
  height: 1px;
  background: var(--rd-rule-strong);
  margin-block-end: var(--rd-space-1);
}
.reader-empty__icon {
  font-size: 20px;
  line-height: 1;
  /* Colour, not opacity: the app previously mixed 0.4/0.5/0.6/none, so the
     same glyph rendered at four weights on adjacent screens. */
  color: var(--rd-text-faint);
  opacity: 1;
}
.reader-empty__title {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-sm);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: var(--rd-leading-tight);
  color: var(--rd-text);
  margin: 0;
}
.reader-empty__body {
  margin: 0;
  font-size: var(--rd-text-base);
  line-height: var(--rd-leading-ui);
  color: var(--rd-text-muted);
  max-width: 48ch;
}
.reader-empty__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--rd-space-3);
  flex-wrap: wrap;
  margin-block-start: var(--rd-space-2);
}
.reader-empty--page { padding-block: var(--rd-space-9); }
.reader-empty--sm { padding: var(--rd-space-5) var(--rd-space-3); }
.reader-empty--sm .reader-empty__title { font-size: var(--rd-text-lg); }
/* Screens toggle these with .hidden or [hidden]; a display rule here would
   otherwise beat the utility and leave them permanently visible. */
.reader-empty[hidden],
.reader-loading[hidden] { display: none; }

/* --- Loading: THE RULING LINE, not a spinner. The same trunk the forking
   rule uses, being drawn. A 2px line cannot out-compete the signature, which
   is exactly why it is the right loading metaphor for this surface. ------- */
.reader-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--rd-space-3);
  padding: var(--rd-space-6) var(--rd-space-4);
  color: var(--rd-text-muted);
  font-size: var(--rd-text-md);
}
.reader-loading::before {
  content: '';
  display: block;
  width: 112px;
  height: var(--rd-rule-w);
  border-radius: var(--rd-radius-pill);
  background: var(--rd-rule);
  background-image: linear-gradient(90deg, transparent, var(--amethyst), transparent);
  background-repeat: no-repeat;
  background-size: 40% 100%;
  animation: reader-rule-draw 1.4s var(--rd-ease) infinite;
}
.reader-loading--sm { padding: var(--rd-space-4); }
.reader-loading--sm::before { width: 72px; }
.reader-loading-inline {
  display: inline-block;
  vertical-align: middle;
  width: 56px;
  height: var(--rd-rule-w);
  border-radius: var(--rd-radius-pill);
  background: var(--rd-rule);
  background-image: linear-gradient(90deg, transparent, var(--amethyst), transparent);
  background-repeat: no-repeat;
  background-size: 40% 100%;
  animation: reader-rule-draw 1.4s var(--rd-ease) infinite;
}
@keyframes reader-rule-draw {
  0%   { background-position: -60% 0; }
  100% { background-position: 160% 0; }
}

/* Skeleton — one system. The four hand-copied skeleton cards in stories.html
   and the shimmer in discovery.css collapse to this. */
.reader-skeleton {
  background: var(--rd-surface);
  background-image: linear-gradient(90deg, transparent, var(--rd-surface-raised), transparent);
  background-repeat: no-repeat;
  background-size: 50% 100%;
  border-radius: var(--rd-radius-xs);
  animation: reader-rule-draw 1.6s var(--rd-ease) infinite;
}
.reader-skeleton--text { height: 12px; width: 60%; }
.reader-skeleton--title { height: 17px; width: 45%; }
.reader-skeleton--chip { height: var(--rd-control-h-sm); width: 84px; border-radius: var(--rd-radius-pill); }
.reader-skeleton--cover { aspect-ratio: 3 / 4; width: 100%; border-radius: 0; }
.reader-skeleton--block { height: 120px; width: 100%; border-radius: var(--rd-radius-sm); }

/* The brief spends the product's motion budget on the forking rule alone. If
   the OS asks for less motion, the state animations stop entirely and the
   line simply sits at rest. root.css zeroes durations globally; this is
   explicit so the intent survives a refactor of that rule. */
@media (prefers-reduced-motion: reduce) {
  .reader-loading::before,
  .reader-loading-inline,
  .reader-skeleton {
    animation: none;
    background-image: none;
  }
  .reader-loading::before,
  .reader-loading-inline { background: var(--amethyst); }
  .reader-skeleton { background: var(--rd-surface-raised); }
}


/* ===========================================================================
 * 8. THE FORKING RULE — the reader surface's signature element.
 *
 * The brief, verbatim: "A thin vertical --amethyst line runs down the left edge
 * of the active scene/manuscript column. At a choice point, the line visibly
 * SPLITS — animates into two (or more) branches, one per choice, each branch
 * leading to its own choice label set in Fraunces at a small size. Selecting a
 * choice collapses the unchosen branches back into the single trunk line as the
 * story continues."
 *
 * HOW IT IS BUILT
 * The trunk is .reader-stage::before (§4) — it runs the full height of the
 * transcript. The choice panel is fixed to the bottom of the player, but it is
 * centred on the SAME reading column, so a trunk stub at the same inline-start
 * offset reads as one continuous line arriving at the fork:
 *
 *      │            ← .reader-stage::before  (the manuscript column's trunk)
 *      │            ← .reader-fork__trunk    (the stub, in the choice panel)
 *      ├──▸ Follow the light        ┐
 *      ├──▸ Turn back               ├ .reader-fork__branch, one per choice
 *      └──▸ Say nothing             ┘
 *
 * Each branch's connector is one box drawing an inline-start edge and a
 * block-end edge with a rounded corner — a line leaving the trunk and bending
 * out to its label. It is REVEALED by clip-path from the top down, so the eye
 * reads it as the trunk growing down and curving outward, staggered per branch.
 * That is the split; nothing is faked with a background image.
 *
 * On selection: the chosen branch's connector and label stay lit while every
 * other connector runs its reveal backwards and its label fades — the unchosen
 * branches collapse back into the trunk, exactly as the brief describes, and
 * the trunk stub extends past the fork so the story continues on one line.
 *
 * This is the ONLY animated ornament in the reader. §7's state components are
 * deliberately undecorated so this is never in competition.
 * ======================================================================== */

.reader-fork {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--rd-space-2);
  max-width: var(--rd-column);
  margin-inline: auto;
  padding-inline-start: var(--rd-fork-gutter);
  padding-block: var(--rd-space-4) var(--rd-space-3);
  min-width: 0;
}

/* THE TRUNK. One continuous amethyst line down the fork's inline-start edge,
   picking up where the stage's trunk left off and running PAST every take-off
   point — so what the eye sees is a single line that branches, not a stack of
   separate ticks. It fades in at the top so it reads as the same line arriving
   rather than a rule that starts here. */
.reader-fork__trunk {
  position: absolute;
  inset-inline-start: 0;
  inset-block: 0;
  width: var(--rd-rule-w);
  border-radius: var(--rd-radius-pill);
  background: linear-gradient(
    to bottom,
    rgba(var(--rd-amethyst-rgb), 0) 0%,
    var(--amethyst) 18%,
    var(--amethyst) 100%
  );
  pointer-events: none;
}

/* The question being branched on. Prose, so it is set in the reading serif —
   a branch question is part of the story, not a form legend. */
.reader-fork__question {
  margin: 0 0 var(--rd-space-1);
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose-sm);
  line-height: 1.5;
  color: var(--rd-text);
  max-width: var(--rd-measure);
}
.reader-fork__helper {
  margin: 0 0 var(--rd-space-1);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-text-faint);
}
.reader-fork__note {
  margin: var(--rd-space-1) 0 0;
  font-size: var(--rd-text-sm);
  color: var(--rd-text-faint);
  max-width: var(--rd-measure);
}

.reader-fork__branches {
  display: flex;
  flex-direction: column;
  gap: var(--rd-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

/* A BRANCH. Its connector is an L of two 2px edges with a rounded corner: it
   runs down the trunk's own x from the top of this row, bends, and reaches
   --rd-fork-reach out to the label. Because the trunk above is continuous and
   the connector shares its x, the pair reads as ONE line that splits — the
   connector is the part of the line that leaves.

   The reveal is top-down (clip-path), staggered per branch, so the split is
   drawn rather than appearing. */
.reader-fork__branch {
  position: relative;
  padding-inline-start: var(--rd-fork-reach);
  min-width: 0;
}
.reader-fork__branch::before {
  content: '';
  position: absolute;
  inset-inline-start: calc(-1 * var(--rd-fork-gutter));
  inset-block-start: 0;
  width: calc(var(--rd-fork-gutter) + var(--rd-fork-reach));
  height: 50%;
  min-height: 18px;
  border-inline-start: var(--rd-rule-w) solid var(--amethyst);
  border-block-end: var(--rd-rule-w) solid var(--rd-amethyst-line);
  border-end-start-radius: var(--rd-fork-radius);
  pointer-events: none;
  clip-path: inset(0 0 0 0);
  animation: reader-fork-split var(--rd-dur-fork) var(--rd-ease-out) both;
  transition: border-color var(--rd-dur) var(--rd-ease), opacity var(--rd-dur) var(--rd-ease);
}
/* The stagger. Six is the practical ceiling for an authored branch; a
   pathological seventh and beyond simply share the last delay. */
.reader-fork__branch:nth-child(1)::before { animation-delay: 0ms; }
.reader-fork__branch:nth-child(2)::before { animation-delay: 70ms; }
.reader-fork__branch:nth-child(3)::before { animation-delay: 140ms; }
.reader-fork__branch:nth-child(4)::before { animation-delay: 210ms; }
.reader-fork__branch:nth-child(5)::before { animation-delay: 280ms; }
.reader-fork__branch:nth-child(n + 6)::before { animation-delay: 350ms; }

@keyframes reader-fork-split {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* The choice itself. NOT a pill-button list — the brief is explicit that "a
   choice is a fork, not a form control". It is a label at the end of a branch:
   Fraunces at a small size, on a leaf that only gains a border once the reader
   is pointing at it. */
.reader-fork__choice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  min-height: var(--rd-control-h-lg);
  padding: var(--rd-space-2) var(--rd-space-3);
  text-align: start;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--rd-radius-sm);
  color: var(--rd-text);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--rd-dur-fast) var(--rd-ease),
              border-color var(--rd-dur-fast) var(--rd-ease),
              opacity var(--rd-dur) var(--rd-ease);
}
.reader-fork__label {
  font-family: var(--rd-font-display);
  font-optical-sizing: auto;
  font-size: var(--rd-display-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-display);
  line-height: 1.25;
  color: var(--rd-text);
}
.reader-fork__hint {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-sm);
  line-height: 1.35;
  color: var(--rd-text-muted);
}

.reader-fork__choice:hover {
  background: var(--rd-amethyst-wash);
  border-color: var(--rd-amethyst-line);
}
/* Hovering anywhere on the branch lights its whole connector, so the line and
   the label it leads to are one object rather than a rule beside a button. */
.reader-fork__branch:hover::before { border-color: var(--amethyst); }
.reader-fork__choice:focus-visible {
  outline: none;
  background: var(--rd-amethyst-wash);
  box-shadow: var(--rd-focus-ring);
}
.reader-fork__branch:focus-within::before { border-color: var(--amethyst); }

/* --- Resolution. The reader has chosen; the fork collapses back to the one
   trunk and the story continues on it. ----------------------------------- */
.reader-fork.is-resolved .reader-fork__trunk {
  height: 100%;
  background: linear-gradient(
    to bottom,
    rgba(var(--rd-amethyst-rgb), 0) 0%,
    var(--amethyst) 18%,
    var(--amethyst) 100%
  );
}
.reader-fork__branch.is-chosen::before { border-color: var(--amethyst); }
.reader-fork__branch.is-chosen .reader-fork__label { color: var(--rd-amethyst-fg); }
/* Unchosen branches run the split backwards and fade — they collapse INTO the
   trunk rather than merely disappearing, which is the whole idea. */
.reader-fork.is-resolved .reader-fork__branch:not(.is-chosen)::before {
  animation: reader-fork-collapse var(--rd-dur-slow) var(--rd-ease) both;
}
.reader-fork.is-resolved .reader-fork__branch:not(.is-chosen) .reader-fork__choice {
  opacity: 0.28;
}
@keyframes reader-fork-collapse {
  from { clip-path: inset(0 0 0 0); }
  to   { clip-path: inset(0 0 100% 0); }
}
.reader-fork__choice:disabled { cursor: default; }
.reader-fork__choice:disabled:hover { background: transparent; border-color: transparent; }

/* A branch waiting on the server. The rule pulses along its own stroke — the
   same ruling-line metaphor §7 uses, at branch scale. */
.reader-fork__branch.is-pending::before { border-color: var(--amethyst); }
.reader-fork__branch.is-pending .reader-fork__label::after {
  content: '';
  display: inline-block;
  vertical-align: middle;
  margin-inline-start: var(--rd-space-2);
  width: 24px;
  height: var(--rd-rule-w);
  border-radius: var(--rd-radius-pill);
  background: var(--rd-rule);
  background-image: linear-gradient(90deg, transparent, var(--amethyst), transparent);
  background-repeat: no-repeat;
  background-size: 45% 100%;
  animation: reader-rule-draw 1.1s var(--rd-ease) infinite;
}

/* Reduced motion: the fork still FORKS — the geometry is the signature, the
   animation is only its entrance. Lines are simply already drawn. */
@media (prefers-reduced-motion: reduce) {
  .reader-fork__branch::before,
  .reader-fork.is-resolved .reader-fork__branch:not(.is-chosen)::before {
    animation: none;
    clip-path: none;
  }
  .reader-fork.is-resolved .reader-fork__branch:not(.is-chosen)::before { opacity: 0.2; }
  .reader-fork__branch.is-pending .reader-fork__label::after {
    animation: none;
    background-image: none;
    background: var(--amethyst);
  }
}

/* Narrow viewports: the gutter tightens but the fork never becomes a stack of
   pills — the branch geometry is the product's signature at every width. */
@media (max-width: 600px) {
  .reader-fork {
    --rd-fork-gutter: 20px;
    --rd-fork-reach: 12px;
    --rd-fork-radius: 10px;
  }
}


/* ---------------------------------------------------------------------------
 * 9. PROSE COMPONENTS — the transcript's reading grammar
 *
 * IMP-0618 owns the player's full composition; what belongs HERE is the shared
 * grammar the rest of the reader also has to speak, so a scene block, a recap
 * and a story synopsis are the same typographic object wherever they appear.
 *
 * The rule the brief sets and these encode: dialogue is not a chat bubble.
 * A speaker is an attribution line above the words, in Inter, small and
 * tracked — the way a script or a novel's dialogue tag reads — and the speech
 * itself is prose in the reading serif. No tails, no left/right lanes, no
 * per-speaker geometry.
 * ------------------------------------------------------------------------ */

/* A beat: one unit of the manuscript. Narration, dialogue or the reader's own
   action are the same object with different attribution. */
.reader-beat {
  position: relative;
  padding-block: var(--rd-space-3);
  min-width: 0;
}
.reader-beat + .reader-beat { border-block-start: 1px solid var(--rd-rule-soft); }
.reader-beat__attribution {
  display: flex;
  align-items: center;
  gap: var(--rd-space-2);
  margin-block-end: var(--rd-space-1);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-text-faint);
}
.reader-beat__text {
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose);
  line-height: var(--rd-leading-prose);
  color: var(--rd-text);
  white-space: pre-wrap;
  max-width: var(--rd-measure);
}
/* Dialogue: the attribution is amethyst, and the words are the only italic in
   the transcript. That is the entire distinction — no bubble. */
.reader-beat--dialogue .reader-beat__attribution { color: var(--rd-amethyst-fg); }
.reader-beat--dialogue .reader-beat__text { font-style: italic; }
/* The reader's own action. Ember is the brief's warmth accent; this is a
   highlight moment in the manuscript, not a structural UI state. */
.reader-beat--action .reader-beat__attribution { color: var(--rd-ember-fg); }
.reader-beat--action .reader-beat__text {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  line-height: var(--rd-leading-ui);
  color: var(--rd-text-muted);
}
/* A system aside — nothing to read here, so it is the quietest thing on the
   page and never uses the reading face. */
.reader-beat--system .reader-beat__text {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-md);
  color: var(--rd-text-faint);
}

/* --- Transcript renderer -------------------------------------------------
   session-runtime.js rebuilds the transcript client-side after hydration and
   emits `.message-content` / `.scene-narration` / `.dialogue-text`. These prose
   selectors share the reading role. Without this the server's
   first paint would be Source Serif 4 and the hydrated page would silently
   swap to Georgia — the single most visible instance of "typography changes by
   block type" that IMP-0619 is filed about.

   `--reading-column` is what session-runtime.css and mobile.css both measure
   the stage and the choice panel against; it is given the system's value here
   so the transcript, the fork and the input all sit on one column. --------- */
.message-content,
.scene-narration,
.dialogue-text,
.scene-dialogue .dialogue-text,
.recap-content {
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose);
  line-height: var(--rd-leading-prose);
  letter-spacing: 0;
}
.message-user .message-content,
.message-system .message-content {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  line-height: var(--rd-leading-ui);
}
/* Speaker attributions are Inter, small and tracked — a dialogue tag, not a
   chat-bubble nameplate. They were a 800-weight uppercase seafoam/wisteria
   pair keyed to which side of the column the bubble sat on. */
.dialogue-speaker,
.scene-dialogue .dialogue-speaker,
.scene-dialogue.dialogue-right .dialogue-speaker,
.user-action-prefix {
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-amethyst-fg);
}
.user-action-prefix { color: var(--rd-ember-fg); }
/* The passive-continue pill was the one place a 5th font stack was inlined. */
.message-passive-continue {
  font: var(--rd-weight-medium) var(--rd-text-sm) / 1.2 var(--rd-font-ui);
}


/* A pull quote: the story-detail hook, the excerpt, the cliffhanger line. */
.reader-quote {
  margin: 0;
  padding-inline-start: var(--rd-space-4);
  border-inline-start: var(--rd-rule-w) solid var(--rd-amethyst-line);
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose-lg);
  font-style: italic;
  line-height: 1.5;
  color: var(--rd-text);
  max-width: var(--rd-measure);
}

/* A recap: what happened before. Prose, set back from the trunk. */
.reader-recap {
  background: var(--rd-surface);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius);
  padding: var(--rd-space-4);
}
.reader-recap__label {
  display: flex;
  align-items: center;
  gap: var(--rd-space-2);
  margin-block-end: var(--rd-space-2);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-xs);
  font-weight: var(--rd-weight-semi);
  letter-spacing: var(--rd-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--rd-text-faint);
}
.reader-recap__body {
  margin: 0;
  font-family: var(--rd-font-reading);
  font-size: var(--rd-prose-sm);
  line-height: var(--rd-leading-prose);
  color: var(--rd-text-muted);
}


/* ---------------------------------------------------------------------------
 * 10. UTILITIES
 * ------------------------------------------------------------------------ */

.reader-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The rare light surface. Per the brief, --parchment is for "cinematic hero
   moments, printed/export views, nothing else", so it is reachable only
   through this one opt-in class and carries its own foreground pair —
   amethyst-deep, because plain amethyst on parchment is 2.7:1.

   IMP-0736: --rd-rule/--rd-rule-strong below now match the sitewide light
   theme's values instead of their original #d9d0be/#c3b8a2 (1.3:1 / 1.7:1 —
   short of WCAG 1.4.11's 3:1 for a control boundary, unnoticed before
   because this class had no --rd-input/--rd-btn border in regular use). The
   veil and accent-fg tokens are added for the same reason: this opt-in class
   predates the light theme below and is otherwise a second, slightly
   different light palette living beside it. */
.reader-parchment {
  background: var(--parchment);
  color: var(--ink);
  --rd-text: var(--ink);
  --rd-text-muted: #4a4238;
  --rd-text-faint: #6b6155;
  --rd-surface: #ffffff;
  --rd-surface-raised: #f5f1fc;
  --rd-veil: #ece4d3;
  --rd-rule: #8a7c62;
  --rd-rule-soft: #e7dfd0;
  --rd-rule-strong: #7d715c;
  --rd-amethyst-fg: var(--amethyst-deep);
  --rd-ember-fg: var(--ember-deep);
  --rd-verdigris-fg: var(--verdigris-deep);
  --rd-error-fg: var(--error-deep);
}
.reader-parchment .reader-btn--primary {
  background: var(--amethyst-deep);
  border-color: var(--amethyst-deep);
  color: var(--parchment);
}

/* ===========================================================================
 * 11. THE COMMERCIAL OVERLAY — the one Mystrix-owned boundary surface (IMP-1661).
 *
 * The provider adapter builds the DOM (role="dialog", a visible Close control,
 * a slot host); this stylesheet owns only its reading-surface appearance. The
 * overlay exists exactly while one presentation is live: it is appended by the
 * adapter and removed on every terminal outcome, and the coordinator holds the
 * next turn while it stands. The reader's dismissal control is visible for the
 * whole presentation, keyboard-reachable, and never the provider's own chrome.
 *
 * The scrim is the page beneath the reading leaf, veiled — the story is
 * still there when the break ends, so the overlay reads as a held pause, not
 * a navigation.
 * ------------------------------------------------------------------------ */
.mystrix-ad-overlay {
  position: fixed;
  inset: 0;
  /* Above the page chrome, below sitewide modal layers (main.css's 1000). */
  z-index: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--rd-space-5);
  background: rgba(var(--rd-ink-rgb), 0.72);
}

.mystrix-ad-overlay[hidden] {
  display: none;
}

.mystrix-ad-panel {
  display: grid;
  gap: var(--rd-space-4);
  width: min(24rem, 100%);
  padding: var(--rd-space-5);
  background: var(--rd-surface-raised);
  border: 1px solid var(--rd-rule-strong);
  border-radius: var(--rd-radius);
  box-shadow: 0 18px 48px rgba(var(--rd-ink-rgb), 0.5);
}

.mystrix-ad-slot {
  display: grid;
  place-items: center;
  min-height: 15rem;           /* the 300x250 display format's home */
  background: var(--rd-veil);
  border: 1px solid var(--rd-rule);
  border-radius: var(--rd-radius-sm);
  overflow: hidden;
}

.mystrix-ad-dismiss {
  justify-self: end;
  min-height: var(--rd-control-h);   /* §6's touch target */
  padding: 0 var(--rd-space-4);
  font-family: var(--rd-font-ui);
  font-size: var(--rd-text-base);
  font-weight: var(--rd-weight-medium);
  letter-spacing: var(--rd-tracking-ui);
  color: var(--rd-text);
  background: transparent;
  border: 1px solid var(--rd-rule-strong);
  border-radius: var(--rd-radius-sm);
  cursor: pointer;
}

.mystrix-ad-dismiss:hover {
  background: var(--rd-amethyst-wash);
}

.mystrix-ad-dismiss:focus-visible {
  outline: 2px solid var(--amethyst-deep);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  .mystrix-ad-overlay {
    padding: var(--rd-space-3);
  }

  .mystrix-ad-panel {
    padding: var(--rd-space-4);
  }

  .mystrix-ad-slot {
    min-height: 13rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mystrix-ad-overlay,
  .mystrix-ad-panel {
    transition: none;
  }
}

/* Standard Google display has its own pane beside unobscured publisher content.
   This format is refused before SDK load below 1000x500 or in fullscreen/native
   shells. Direct house campaigns retain their independently supported modal. */
body.session-runtime-body.has-gpt-publisher-pane { --gpt-publisher-pane-size: 360px; }
body.session-runtime-body.has-gpt-publisher-pane #session-main {
  inline-size: calc(100% - var(--gpt-publisher-pane-size));
  margin-inline-end: var(--gpt-publisher-pane-size);
}
body.session-runtime-body.story-player-enhanced.has-gpt-publisher-pane #session-main {
  inline-size: calc(100dvw - var(--player-nav-rail-size, var(--reader-primary-nav-size, 6rem)) - var(--gpt-publisher-pane-size));
}
body.session-runtime-body.has-gpt-publisher-pane .session-header,
body.session-runtime-body.has-gpt-publisher-pane .session-footer {
  inset-inline-end: var(--gpt-publisher-pane-size);
}
.mystrix-ad-overlay.mystrix-ad-gpt-boundary {
  inset-block: 0;
  inset-inline: auto 0;
  inline-size: 360px;
  box-sizing: border-box;
  padding: 16px;
  background: var(--rd-surface-raised);
  border-inline-start: 1px solid var(--rd-rule-strong);
}
.mystrix-ad-gpt-boundary .mystrix-ad-panel {
  width: 100%;
  box-sizing: border-box;
  padding: 12px;
  gap: 20px;
  box-shadow: none;
}
.mystrix-ad-gpt-boundary .mystrix-ad-slot {
  inline-size: 300px;
  min-block-size: 250px;
  border: 0;
  border-radius: 0;
}
.mystrix-ad-gpt-label { font-family: var(--rd-font-ui); color: var(--rd-text); }

.mystrix-direct-ad-image { display: block; max-width: 100%; height: auto; }
.mystrix-direct-ad-panel {
  max-height: 100%; min-height: 0; grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden; color: var(--rd-text);
}
.mystrix-direct-ad-header { display: flex; align-items: center; justify-content: space-between; gap: var(--rd-space-3); }
.mystrix-direct-ad-label { font-family: var(--rd-font-ui); font-size: var(--rd-text-md); }
.mystrix-direct-ad-content { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-inline-end: 4px; }
.mystrix-direct-ad-content p { overflow-wrap: anywhere; }
.mystrix-direct-ad-content:focus-visible { outline: 2px solid var(--amethyst-deep); outline-offset: -2px; }
.mystrix-direct-ad-panel--reporting { grid-template-rows: auto minmax(0, 1fr) auto; }
.mystrix-ad-report { min-width: 0; border-top: 1px solid var(--rd-rule); padding-top: var(--rd-space-2); }
.mystrix-ad-report-toggle { min-height: 44px; }
.mystrix-ad-report-form { display: flex; align-items: end; flex-wrap: wrap; gap: var(--rd-space-2); padding-top: var(--rd-space-2); }
.mystrix-ad-report-reason { flex: 1 1 110px; }
.mystrix-ad-report-form select, .mystrix-ad-report-send { min-height: 44px; }
.mystrix-ad-report-status { display: block; font-size: var(--rd-text-md); color: var(--rd-text-muted); }
.mystrix-ad-report-status:empty { display: none; }
.mystrix-ad-report-form[hidden] { display: none; }
