/*
 * Reader Demo block — a language picker paired with a small book-shaped
 * reader that previews a real excerpt in place, without navigating away.
 * Loaded automatically only on pages containing this block (block.json's
 * "style" key) — no manual conditional enqueue needed.
 *
 * The reader's own light/dark/e-ink toggle simulates the BOOK's own
 * reading modes — a deliberate, approved exception to this site's "no
 * dark theme on public pages" rule. It is scoped strictly to elements
 * inside .reader-demo__stage; nothing outside the stage ever changes
 * color based on it.
 *
 * See reader-demo.js for the fetch/pagination/toggle logic and
 * template-parts/reader-demo.php for the markup.
 */

.gt-reader-demo-block {
	width: 100%;
}

.reader-demo {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	align-items: center;
	width: 100%;
}

.reader-demo__picker {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	align-items: center;
	width: 100%;
}

.reader-demo__picker-label {
	margin: 0;
	font-size: var(--font-size-sm);
	color: var(--color-muted);
}

.reader-demo__pills {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	justify-content: center;
}

.reader-demo__pill {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	padding: 0.5rem 0.875rem;
	border: 1px solid var(--color-border);
	border-radius: 999px;
	background: var(--color-surface);
	color: var(--color-text);
	font-family: var(--font-sans);
	font-size: var(--font-size-sm);
	font-weight: 500;
	cursor: pointer;
	transition: border-color var(--transition), background var(--transition), color var(--transition);
}

.reader-demo__pill:hover {
	border-color: var(--color-stone-400);
}

.reader-demo__pill:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

.reader-demo__pill[aria-pressed="true"] {
	background: var(--color-stone-50);
	border-color: var(--color-text-strong);
	color: var(--color-text-strong);
	font-weight: 600;
}

.reader-demo__pill .lang-chip__icon,
.reader-demo__pill .lang-chip__icon--mask,
.reader-demo__pill .lang-chip__icon--svg {
	width: 16px;
	height: 16px;
	flex-shrink: 0;
}

.reader-demo__pill .lang-chip__region {
	color: var(--color-muted);
	font-weight: 400;
}

/* ---- Stage: the book-shaped reader itself ---- */

/*
 * Full width on purpose — do NOT add a max-width here.
 *
 * The v4 mockup capped this at 780px (its own callout #2), and that was
 * once reverted back to a cap on the strength of that callout. It was
 * wrong: full width is a later, deliberate decision that supersedes v4.
 * The mockup is a design reference, not a spec — where the shipped widget
 * disagrees with it, assume the shipped choice is the newer one unless
 * there's a reason to think otherwise.
 */
.reader-demo__stage {
	position: relative; /* positioning context for the narration pill/native card */
	width: 100%;
	display: flex;
	flex-direction: column;
	/* Animates a height set explicitly, in px, by reader-demo.js's own
	   swapStage() around every content swap (language change, loading →
	   loaded, loaded → unavailable). Harmless the rest of the time —
	   height never transitions to/from `auto` (the browser can't animate
	   an indeterminate end value), so this only ever engages during a
	   swap, never during an ordinary reflow (a window resize, a webfont
	   settling). Collapses to near-instant automatically under
	   prefers-reduced-motion via style.css's sitewide blanket override —
	   no separate handling needed here. */
	transition: height 260ms ease;
	/* Own background, matching the reading theme (below) — swapStage()
	   fades the OUTGOING .reader-demo__book (which carries the theme's
	   real background) down to opacity:0 before swapping in the new
	   content, and opacity fades an element's background right along
	   with everything else. Without this, that fade briefly reveals
	   whatever sits behind the stage itself — the page's own white
	   background — which read as "fading through white" even when
	   switching between two dark-themed views. Direct ask, 2026-09-08.
	   border-radius matches .reader-demo__book's own — without it this
	   background is a plain rectangle sitting behind book's rounded
	   corners, so book's own dark background fading to transparent
	   during a swap left the stage's square corners poking out past
	   book's round ones. Direct ask, 2026-09-08: "dark lost the border
	   radius." */
	background: var(--color-surface);
	border-radius: var(--border-radius-lg);
}
.reader-demo__stage[data-reader-theme="dark"] {
	background: #1c1917;
}
.reader-demo__stage[data-reader-theme="eink"] {
	background: #f4f4f4;
}

/* Only while a swap is actually animating (swapStage() toggles this) —
   clips the outgoing/incoming content during the height change so
   neither one visibly overflows the box mid-transition. Not left on
   permanently: the narration pill/native card and the zoom-modal scrim
   are both positioned relative to/near this element and may need to
   render outside its exact box at rest. */
.reader-demo__stage--animating {
	overflow: hidden;
}

/* Applied to whichever single child is the "current content" — the
   loading placeholder, the unavailable state, or the book itself — so
   swapStage() can fade the outgoing one out and the incoming one in
   without either being visible mid-crossfade at the same time (the swap
   is sequential: fully faded out, DOM swapped, then faded in — see that
   function's own comment for why simultaneous crossfade was skipped). */
.reader-demo__stage-content {
	transition: opacity 180ms ease;
}
.reader-demo__stage-content--hidden {
	opacity: 0;
}

.reader-demo__loading,
.reader-demo__unavailable {
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	min-height: 260px;
	padding: var(--space-6);
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius-lg);
	background: var(--color-surface);
	color: var(--color-muted);
	font-size: var(--font-size-sm);
}

.reader-demo__unavailable-inner p {
	margin: 0 0 var(--space-2);
}

.reader-demo__unavailable-inner p:last-child {
	margin-bottom: 0;
}

.reader-demo__unavailable-title {
	color: var(--color-text);
	font-weight: 600;
	font-size: var(--font-size-base);
}

.reader-demo__book {
	position: relative;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius-lg);
	background: var(--color-surface);
	box-shadow: 0 8px 24px rgba(28, 25, 23, 0.15);
	overflow: hidden;
}

/* The reader's own reading-mode palette — scoped to the stage only. */
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__book {
	background: #1c1917;
	border-color: #3a3530;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__el {
	color: #e8e4de;
}

/* E-ink is a NEUTRAL grey screen, never sepia — the whole point of the mode
   is the flat, slightly-grey, unlit look of real e-paper. Warm/cream values
   here read as a "sepia" reading theme instead, which is a different mode
   that this reader doesn't offer. Keep every value in this block on the
   neutral axis (equal R/G/B). */
.reader-demo__stage[data-reader-theme="eink"] .reader-demo__book {
	background: #f4f4f4;
	border-color: #c9c9c9;
}

.reader-demo__stage[data-reader-theme="eink"] .reader-demo__el {
	color: #1a1a1a;
}

/* Scoped to the specific COLORED content, not the whole page-clip — a
   filter on a shared ancestor can't be selectively un-applied by a
   descendant, so grayscaling the whole clip box grayscaled the beacon
   markers right along with the artifact images/ochre highlights they sit
   beside. Beacons are interaction chrome, not simulated page content —
   "beacons probably shouldn't be e-inkified" — so each colored piece gets
   its own filter directly, leaving its sibling beacon untouched. Plain
   text needs neither: it's already neutral via .reader-demo__el's own
   color:#1a1a1a below. */
.reader-demo__stage[data-reader-theme="eink"] .reader-demo__el-inner {
	filter: grayscale(1) contrast(0.92);
}
.reader-demo__stage[data-reader-theme="eink"] .reader-demo__defterm {
	filter: grayscale(1) contrast(0.92);
}

/* Positioning context for the page-turn chevrons — see reader-demo.js's
   buildSkeleton() for why they hang off this rather than off the book. */
.reader-demo__page-area {
	position: relative;
}

/* ---- Beacon + narration pill ----
 *
 * The universal replacement for the old artifact flyout AND the old
 * definition popover above (both removed in the same pass that added
 * this) — one mechanism, two targets. Ported from
 * /tmp/reader-demo-narration-mockup.html; see that file's own footnote
 * for the full spec. Two layers:
 *
 *   1. A BEACON sits directly at the target (an artifact's own corner, or
 *      right after a defined word) — a real <button>, the only UI on an
 *      artifact now (no more name/type chip). Clicking it (manual mode)
 *      toggles the pill below; in auto-demo it pulses continuously
 *      instead of waiting for a click (.reader-demo__beacon--auto).
 *   2. A single, FIXED-anchor narration pill per instance narrates
 *      whichever target is currently active, docked into the same margin
 *      band the status bar already occupies. Content swaps like
 *      subtitles — it no longer restates raw content, since that's now
 *      shown for real at the target (the native lookup card, below) or is
 *      self-evident for an artifact.
 *
 * Both an artifact beacon and a word beacon reuse the exact same classes
 * (BEACON_CLASS in reader-demo.js) — see reader-demo.js's toggleNarration/
 * openNarration/closeNarration for the one shared code path driving both.
 */
.reader-demo__beacon {
	position: absolute;
	z-index: 5;
	width: 22px;
	height: 22px;
	border: none;
	padding: 0;
	background: none;
	cursor: pointer;
}

.reader-demo__beacon-ring {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: var(--color-ochre);
	opacity: 0;
	animation: none;
}

/* Manual mode's one-time "introduce yourself" pulse — added once, by
   reader-demo.js's updateBeaconsForPage(), the first time this beacon's
   target lands on the visible page. `forwards` freezes the ring on the
   keyframe's own final (invisible) frame once the 3 cycles finish, which
   IS "settle to a static dot" — no separate settled state needed, the dot
   itself (below) was never part of the animation. Deliberately not
   infinite: continuous motion beside text someone is actually reading is
   the wrong call (see the CLAUDE.md-driven task note this shipped with). */
.reader-demo__beacon--pulse .reader-demo__beacon-ring {
	animation: reader-demo-beacon-pulse 2.2s ease-out 3 forwards;
}

/* Auto-demo's own continuous pulse for whichever beacon is its current
   target — added/removed by reader-demo.js as auto-demo moves on, which
   is what restarts the animation (same "class genuinely leaves and
   comes back" mechanism .reader-demo__zoom-btn--attention already uses).
   Declared after --pulse so it wins if a beacon somehow carries both. */
.reader-demo__beacon--auto .reader-demo__beacon-ring {
	animation: reader-demo-beacon-pulse 2.2s ease-out infinite;
}

@keyframes reader-demo-beacon-pulse {
	0%   { transform: scale(0.6); opacity: 0.5; }
	70%  { transform: scale(1.9); opacity: 0; }
	100% { transform: scale(1.9); opacity: 0; }
}

/* The pulse is a CSS `animation`, not a `transition` — style.css's
   sitewide prefers-reduced-motion override only zeroes transition-
   duration, so it never touches this. Needs its own override, same as
   the source mockup (/tmp/reader-demo-narration-mockup.html) already
   had for this exact rule. A visible static ring (not display:none) so
   the beacon still reads as a real, findable touch-point — just without
   the motion. */
@media (prefers-reduced-motion: reduce) {
	.reader-demo__beacon-ring {
		animation: none !important;
	}
	.reader-demo__beacon--pulse .reader-demo__beacon-ring,
	.reader-demo__beacon--auto .reader-demo__beacon-ring {
		opacity: 0.25;
	}
}

.reader-demo__beacon-dot {
	position: absolute;
	top: 6px;
	left: 6px;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: var(--color-ochre);
	box-shadow: 0 0 0 2px var(--color-surface);
}

.reader-demo__beacon:focus-visible .reader-demo__beacon-dot {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

/* Sits at the artifact's own corner — wrap already carries
   position:relative (see .reader-demo__el--artifact above), so this needs
   no JS-computed placement the way the old flyout did. */
/* Inset, not hanging off the corner — .reader-demo__el--artifact (this
   button's positioning parent) carries overflow:hidden for its own
   scale-to-fit clipping (see fitOneArtifact() in reader-demo.js), which
   clips ANY child that pokes outside its own box, negative offsets
   included. A beacon sitting half outside that box was silently getting
   its top sliced off. */
/* Sits to the LEFT of the "enlarge" zoom button
   (.reader-demo__zoom-btn: top:10px, right:10px, 28px square), not
   stacked on top of it — the two used to occupy nearly the same corner,
   making the zoom button hard to actually click. Direct ask,
   2026-09-08. Top offset centers the beacon (22px tall) against the
   zoom button's own vertical center (10 + 28/2 = 24px from the top);
   right offset clears the zoom button's own left edge (right: 38px)
   by a 6px gap. */
.reader-demo__beacon--on-artifact {
	top: 13px;
	right: 44px;
}

/* Inserted as a plain sibling right after its defterm <button> (see
   makeWordBeacon() in reader-demo.js) — sitting in the text flow, not
   absolutely positioned, so it naturally tracks the word through column
   reflow/pagination with no rect math needed. Nudged up and given a
   little breathing room so it doesn't collide with the word's own
   descenders. */
/* An inline sibling right after its defterm button (see makeWordBeacon()
   in reader-demo.js) — as a REAL 22px-wide inline box, it was shoving
   every word after it across the line, opening a visible gap in running
   text. Direct ask, 2026-09-08: "the beacon should not displace the
   text." Fixed with the standard "ghost width" trick: margin-right
   exactly cancels margin-left + the button's own width, so its total
   contribution to the line box is zero — it still paints at its real
   22px size (this is a margin, not a clip), just without pushing
   anything after it. Kept as a real inline box (not absolutely
   positioned against some ancestor) so it still naturally tracks the
   word through column reflow/pagination with no JS-computed rect math. */
.reader-demo__beacon--on-word {
	position: relative;
	top: -0.5em;
	margin-left: 1px;
	margin-right: -23px;
	vertical-align: baseline;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__beacon-ring,
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__beacon-dot {
	background: #d4a54a;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__beacon-dot {
	box-shadow: 0 0 0 2px #1c1917;
}

/* ---- Narration pill ----
 *
 * ONE shared instance, fixed-anchor — deliberately NOT computed from the
 * active target's own x/y (the mockup's own explicit call: "same spot
 * every time"). Docks into the page-area's own bottom margin (overlapping
 * the status bar band) by default, flips to a matching top band when the
 * active target sits in the page's lower half — same above/below flip
 * shape the old flyout/popover already used, just decided once per open
 * rather than continuously re-measured, since the pill's position is
 * fixed relative to the PAGE, not the target.
 *
 * Appended to .reader-demo__stage (same as the old flyout/popover were —
 * the book's own overflow:hidden, there for its rounded corners, would
 * clip this near an edge), and positioned absolute against it, so
 * "bottom"/"top" here read as the stage's own bottom/top — in practice
 * flush with the book's status bar band and the excerpt-title band,
 * respectively, since the book fills the stage.
 */
.reader-demo__narration-pill {
	position: absolute;
	z-index: 6;
	/* Centered on the page, not flush left — a fixed-anchor dock reads as
	   part of the book's own frame either way; centered is the calmer,
	   more "belongs here" placement. left:50% + translateX(-50%) rather
	   than left/right auto-margins so it can share one `transform` with
	   the open/close slide below (a second transform property would
	   silently overwrite the first, not combine with it). */
	left: 50%;
	max-width: min(300px, calc(100% - 2 * var(--space-4)));
	/* Set on the stage element by openNarration() (reader-demo.js) to the
	   real, live height of whatever band this would otherwise cover — the
	   statusbar's own Light/Dark/E-ink buttons here, the excerpt-title
	   band for the --top flip below. Falls back to the plain gap when
	   there's nothing to clear (removeProperty()'d). */
	bottom: var(--reader-demo-narration-reserve, var(--space-2));
	padding: 11px 14px;
	border-radius: 12px;
	background: var(--color-coffee);
	color: var(--color-surface);
	box-shadow: 0 10px 26px rgba(28, 25, 23, 0.22);
	display: flex;
	align-items: flex-start;
	gap: 9px;
	opacity: 0;
	visibility: hidden;
	transform: translate(-50%, 6px);
	transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 200ms;
}

.reader-demo__narration-pill--on {
	opacity: 1;
	visibility: visible;
	transform: translate(-50%, 0);
	transition: opacity 200ms ease, transform 200ms ease, visibility 0s;
}

.reader-demo__narration-pill--top {
	bottom: auto;
	top: var(--reader-demo-narration-reserve, var(--space-2));
}

.reader-demo__narration-pill--top.reader-demo__narration-pill--on {
	transform: translate(-50%, 0);
}
.reader-demo__narration-pill--top:not(.reader-demo__narration-pill--on) {
	transform: translate(-50%, -6px);
}

/* Content-swap transition — distinct from the open/close slide above.
   Fires when the pill is ALREADY open and openNarration() is called again
   for a NEW target (auto-demo moving from one page's beacon straight to
   the next's, pill never fully closing in between): without this, the
   text just silently changes underneath an unmoving pill, which reads as
   nothing happening at all rather than as new narration arriving. A
   quick fade+微 lift on the text/card content only — the pill's own
   position/open state doesn't re-animate, since it may not even be
   moving (same top/bottom band, new target). Direct ask, 2026-09-08:
   "from page to page it's just updating and there is change blindness". */
.reader-demo__narration-text,
.reader-demo__native-card {
	transition: opacity 140ms ease, transform 140ms ease;
}
.reader-demo__narration-pill--swap .reader-demo__narration-text,
.reader-demo__native-card--swap {
	opacity: 0;
	transform: translateY(3px);
}

.reader-demo__narration-text {
	flex: 1;
	min-width: 0;
	margin: 0;
	font-family: var(--font-serif);
	font-size: var(--font-size-sm);
	line-height: 1.45;
	color: var(--color-surface);
}

.reader-demo__narration-close {
	flex-shrink: 0;
	width: 22px;
	height: 22px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	margin: -4px -6px 0 0;
	border: none;
	border-radius: 50%;
	background: none;
	color: var(--color-surface);
	opacity: 0.55;
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
	transition: opacity var(--transition);
}
.reader-demo__narration-close:hover {
	opacity: 1;
}
.reader-demo__narration-close:focus-visible {
	opacity: 1;
	outline: 2px solid var(--color-surface);
	outline-offset: 1px;
}

/* Coffee is too close in value to this theme's near-black stage to read
   as a distinct object, so the pill lightens several steps rather than
   staying identical — direct port of the mockup's own dark-mode finding
   (neither the old flyout nor the old popover had ANY dark override). */
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__narration-pill {
	background: #6b5645;
	box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4);
}

@media (prefers-reduced-motion: reduce) {
	.reader-demo__narration-pill {
		transition: opacity 120ms ease, visibility 0s linear 120ms;
		transform: none;
	}
	.reader-demo__narration-pill--on {
		transition: opacity 120ms ease, visibility 0s;
		transform: none;
	}
}

/* ---- Simulated native dictionary lookup card ----
 *
 * New: the old definition popover only ever showed Omorphos's own coffee
 * bubble with a plain definition string — never "this is what tapping a
 * word looks like on your own device." This is that simulation layer,
 * shown ONLY for a word target, positioned at the word itself (unlike the
 * fixed-anchor pill, this has to look like it's actually sitting on the
 * page). Deliberately generic chrome, no real reading-app named or
 * imitated (trademark/brand-accuracy risk on a public marketing page) —
 * and deliberately NOT re-skinned for dark/e-ink: it's meant to look like
 * the reading app's OWN real UI, which doesn't adapt to a book's reading
 * theme either (the mockup's own explicit call).
 */
.reader-demo__native-card {
	position: absolute;
	z-index: 7;
	width: 216px;
	max-width: calc(100% - 16px);
	padding: 12px 13px 10px;
	border-radius: 12px;
	background: #fdfdfd;
	box-shadow: 0 10px 28px rgba(28, 25, 23, 0.18);
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
	opacity: 0;
	visibility: hidden;
	transition: opacity 200ms ease, visibility 0s linear 200ms;
}

.reader-demo__native-card--on {
	opacity: 1;
	visibility: visible;
	transition: opacity 200ms ease, visibility 0s;
}

.reader-demo__native-card-kicker {
	display: block;
	margin-bottom: 4px;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: #8a8a8e;
}

.reader-demo__native-card-word {
	margin: 0 0 4px;
	font-size: 16px;
	font-weight: 600;
	color: #1a1a1a;
}

.reader-demo__native-card-def {
	margin: 0;
	font-size: 13px;
	line-height: 1.4;
	color: #1a1a1a;
}

@media (prefers-reduced-motion: reduce) {
	.reader-demo__native-card {
		transition: opacity 120ms ease, visibility 0s linear 120ms;
	}
	.reader-demo__native-card--on {
		transition: opacity 120ms ease, visibility 0s;
	}
}

/* ---- Auto-demo play/pause control ----
 *
 * The WCAG 2.2.2 "Pause, Stop, Hide" control — required, not decoration,
 * since auto-demo changes content on its own for well over 5 seconds at a
 * stretch. Lives in the status bar (.reader-demo__statusbar, between the
 * Light/Dark/E-ink toggle and the page count) — inside the book's own
 * container, not floating below the language picker outside it. Direct
 * ask, 2026-09-08: "somewhere on the status bar". Sized down from the
 * original 34px to fit that bar's compact height; always visible and
 * always usable regardless of whether auto-demo has started itself yet.
 */
.reader-demo__autoplay-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	border: 1px solid var(--color-border);
	border-radius: 50%;
	background: var(--color-surface);
	color: var(--color-text);
	cursor: pointer;
	transition: background var(--transition), border-color var(--transition);
}

.reader-demo__autoplay-btn:hover {
	border-color: var(--color-stone-400);
}

.reader-demo__autoplay-btn:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

/* The statusbar simulates its own dark/e-ink look with hardcoded values
   (see .reader-demo__mode-btn's own dark override just below in the
   status-bar section) rather than the page's global --color-* tokens —
   this button needs the same explicit treatment now that it lives inside
   that bar instead of on the surrounding light page. */
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__autoplay-btn {
	background: #1c1917;
	border-color: #3a3530;
	color: #e8e4de;
}

.reader-demo__stage[data-reader-theme="eink"] .reader-demo__autoplay-btn {
	background: #e6e6e6;
	border-color: #c9c9c9;
	color: #1a1a1a;
}

.reader-demo__autoplay-icon--pause {
	display: none;
}
.reader-demo__autoplay-btn--playing .reader-demo__autoplay-icon--play {
	display: none;
}
.reader-demo__autoplay-btn--playing .reader-demo__autoplay-icon--pause {
	display: block;
}

/* The play triangle's own visual center sits slightly right of its box
   center (a wide flat left edge, a point on the right) — nudge it back a
   hair so it reads as centered inside the round button. */
.reader-demo__autoplay-icon--play {
	margin-left: 2px;
}

/*
 * Optional running head, off unless the block's "Show excerpt title"
 * toggle is on. Deliberately not on by default: the excerpt is expected
 * to be purpose-made demo content rather than a released book, so a title
 * band would be labelling something a visitor can't go and read.
 *
 * Sits above the fixed-height page-clip, so it only makes the book
 * taller — it can't affect the pagination invariant, which is purely
 * about width.
 */
.reader-demo__excerpt-title {
	margin: 0;
	padding: var(--space-4) var(--space-6) 0;
	text-align: center;
	font-family: var(--font-serif);
	font-style: italic;
	font-size: var(--font-size-xs);
	color: var(--color-muted);
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__excerpt-title {
	color: #a79e93;
}

/*
 * HORIZONTAL PADDING HERE WOULD BREAK PAGINATION — keep it at zero.
 *
 * clientWidth includes an element's own padding, but the column flow inside
 * starts inset by padding-left, so the two stop agreeing; worse, a
 * horizontally overflowing element's scrollWidth omits its trailing padding
 * entirely. Together that put the last page a few pixels off a column
 * boundary and clipped glyphs at both edges. The left/right page inset lives
 * on .reader-demo__el instead (see below) — the same split omorphos-studio's
 * real Reader uses (.reader__page-clip has no padding at all; .reader__el
 * carries --r-page-pad).
 *
 * Vertical padding is fine and stays here: it can't affect a horizontal
 * invariant, and reader-demo.js subtracts it when sizing the column flow.
 */
.reader-demo__page-clip {
	overflow: hidden;
	height: 320px;
	padding: var(--space-6) 0 var(--space-4);
}

@media (min-width: 700px) {
	.reader-demo__page-clip {
		height: 380px;
		padding: var(--space-8) 0 var(--space-4);
	}
}

.reader-demo__columns {
	height: 100%;
	column-fill: auto;
	/* Zero, and load-bearing. A real gap makes each column occupy
	   (columnWidth + gap) rather than a clean fraction of the page, so a
	   page turn stops being exactly one clientWidth and drifts by one gap
	   per turn. The visible gutter comes from .reader-demo__el's own
	   padding instead — CSS columns can't be padded per-column, so the
	   inset has to ride on the flowed children. */
	column-gap: 0;
	/* No overflow rule here on purpose — this element's own rendered
	   width needs to grow past .reader-demo__page-clip's width as more
	   columns are added (that's what makes page-clip's scrollLeft-based
	   paging possible). page-clip's own overflow:hidden is what clips
	   the visible viewport to one "page" at a time. */
}

/*
 * The page inset lives here, not on the scroll container — see the
 * .reader-demo__page-clip note above for why. Each column gets the same
 * left/right padding, which reads as the book's outer margin at the spread's
 * outer edges and as the gutter between the two facing columns.
 *
 * width:100% is load-bearing, not just max-width — inside a CSS-columns
 * container a block child sized only by max-width resolves to that literal
 * value regardless of the real (narrower) column, and overflows sideways
 * into the next column. Same finding as the Reader's own .reader__el.
 *
 * Deliberately NO break-inside:avoid-column: this wraps running prose, which
 * has to split freely across column boundaries — that's the entire point of
 * column-based pagination. Forcing a multi-column-long passage to stay in one
 * column is what produces truncated and overlapping text. Only genuinely
 * atomic content opts out (see --artifact below).
 */
.reader-demo__el {
	width: 100%;
	/* 40px clears the page-turn buttons, which sit 4px in from the book's
	   edge and are 36px wide — anything narrower and the ‹ / › circles
	   overlap the first character of every line they sit beside. */
	padding: 0 var(--space-10);
	color: var(--color-text);
	font-family: var(--font-serif);
	font-size: var(--font-size-base);
	line-height: 1.65;
}

/*
 * The fetched excerpt's own HTML comes straight from omorphos-studio's
 * EPUB serializers — real <p>/<h1-4>/<blockquote>/<ul,ol,li> markup, with
 * no styling of its own. Left unreset, every one of these tags would
 * inherit whatever this SITE's own style.css declares for a bare
 * heading/paragraph/list — this reader has its own book-like typographic
 * scale and shouldn't visually depend on the surrounding page's rules at
 * all. Every basic tag reset here, not just <p>.
 */
.reader-demo__el p,
.reader-demo__el ul,
.reader-demo__el ol,
.reader-demo__el blockquote,
.reader-demo__el h1,
.reader-demo__el h2,
.reader-demo__el h3,
.reader-demo__el h4 {
	margin: 0 0 0.25em;
	padding: 0;
	font-family: inherit;
	color: inherit;
}

/*
 * Book paragraph indent — the first paragraph of a run sits flush, every
 * one after it takes a first-line indent. Mirrors ProseBlock's own EPUB
 * rule exactly (`root p + p { text-indent: 1.5em }`, ProseBlock.tsx), so
 * a demo excerpt reads the way the real book does.
 *
 * The reset above strips every tag's own styling, which is what dropped
 * this in the first place — the indent lives in the EPUB stylesheet, and
 * this component deliberately doesn't load that (see the reset's own
 * note). So it has to be restated here.
 *
 * Structural selector on purpose, NOT `.ep-prose-block-root p + p`:
 * those class names are generated from the component's own name, and
 * this one has already changed once (ProseBlock was BodyParagraph until
 * recently, so the class was `ep-body-paragraph-root`). `p + p` can't go
 * stale that way. It also correctly leaves single-paragraph elements and
 * the SceneBreak ornament — itself a lone <p> in its own wrapper — with
 * no indent, since neither has a preceding paragraph sibling.
 *
 * The real book additionally runs paragraphs with no gap at all, letting
 * the indent do all the separating; this keeps the 0.25em from the reset
 * above, which is this component's own established look.
 */
.reader-demo__el p + p {
	text-indent: 1.5em;
}

.reader-demo__el p:last-child,
.reader-demo__el ul:last-child,
.reader-demo__el ol:last-child,
.reader-demo__el blockquote:last-child,
.reader-demo__el h1:last-child,
.reader-demo__el h2:last-child,
.reader-demo__el h3:last-child,
.reader-demo__el h4:last-child {
	margin-bottom: 0;
}

.reader-demo__el h1,
.reader-demo__el h2,
.reader-demo__el h3,
.reader-demo__el h4 {
	font-weight: 600;
	line-height: 1.3;
}

.reader-demo__el h1 { font-size: 1.4em; }
.reader-demo__el h2 { font-size: 1.25em; }
.reader-demo__el h3 { font-size: 1.1em; }
.reader-demo__el h4 { font-size: 1em; }

.reader-demo__el blockquote {
	padding-left: 1em;
	border-left: 2px solid var(--color-border);
	font-style: italic;
}

.reader-demo__el ul,
.reader-demo__el ol {
	padding-left: 1.25em;
}

.reader-demo__el li {
	margin: 0 0 0.4em;
}

.reader-demo__el li:last-child {
	margin-bottom: 0;
}

.reader-demo__el strong,
.reader-demo__el b {
	font-weight: 600;
}

.reader-demo__el em,
.reader-demo__el i {
	font-style: italic;
}

.reader-demo__el a {
	color: inherit;
	text-decoration: underline;
}

/* ---- Word Definitions ("tap to define") ----
 *
 * An ochre highlight — not the coffee dashed-underline this used to have.
 * Direct finding from the narration-mockup pass: coffee doesn't have
 * enough contrast against page text/background to work as an on-page
 * highlight. Many of these can exist throughout ordinary prose, so the
 * treatment still needs to read as "this word has more info", not "this
 * word is important content" — a quiet tint and underline, not a loud
 * callout.
 *
 * A real <button>, not a styled <span> — see applyWordDefinitions() in
 * reader-demo.js — so it needs a full reset back to plain inline text
 * before the highlight is applied on top. Its own beacon (see above) is
 * what ties it to the narration pill now; this highlight alone doesn't
 * need to imply "tap here" the way the old dotted underline did.
 */
.reader-demo__defterm {
	display: inline;
	margin: 0;
	padding: 0 3px;
	border: none;
	border-bottom: 2px solid var(--color-ochre);
	border-radius: 3px;
	background: var(--color-ochre-tint);
	color: inherit;
	font: inherit;
	line-height: inherit;
	text-align: inherit;
	cursor: pointer;
	-webkit-appearance: none;
	appearance: none;
}

.reader-demo__defterm:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 1px;
}

/* Ochre itself needs the same lightening-for-dark-backgrounds treatment
   the pill/beacon get — a solid tint reads muddy against a near-black
   page, so this switches to a translucent wash instead. */
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__defterm {
	background: rgba(212, 165, 74, 0.16);
	border-bottom-color: #d4a54a;
}

/* An image genuinely can't be split down the middle by a column break, so
   this is the one element type that opts into atomic treatment.
 *
 * Scale-to-fit is a port of the real Reader's own mechanism
 * (omorphos-studio ReaderApp.jsx's fitOne + reader.css's
 * .reader__el--atomic / .reader__el-inner). Exported artifacts are big —
 * the first real Spanish export is 1200x1418 — so at column width they
 * run several times a page tall. The transform goes on the INNER wrapper
 * so the outer box still contributes exactly one page's worth of column
 * height; transform doesn't affect layout, so the box just paints its
 * taller content shrunk to fit rather than clipping it.
 *
 * overflow:hidden here is only a safety clip for the window before the
 * fit pass has run (viewport height not yet known on first paint) — the
 * Reader's own comment makes the same point: internal scrolling is not
 * an option, because nothing in a click/swipe-paginated reader can
 * invoke it, so it would crop content invisibly. */
.reader-demo__el--artifact {
	position: relative;
	margin: 1em 0;
	break-inside: avoid-column;
}

/* The active-narration ring — a dedicated sibling of .reader-demo__el-fit
   (built once in renderExcerpt(), reader-demo.js), NOT a style on the wrap
   or either inner layer. Two real constraints forced this third layer
   rather than styling an existing one directly:
   1. The wrap (.reader-demo__el--artifact) spans the full column width,
      while the actual image is narrower and centered inside it — a ring
      on the wrap drew a box well past the image's real edges. Direct ask,
      2026-09-08: "the glow should apply to the image not to the artifact
      container. they're different things." Shrinking the WRAP itself to
      the image's width was tried first (width:fit-content, then
      display:table) and both destabilized the CSS-multicolumn pagination
      this component depends on (a table-display child broke
      break-inside:avoid-column's measurement entirely — confirmed live,
      not a hypothetical), so the wrap's own box-model is left exactly as
      pagination needs it.
   2. .reader-demo__el-fit carries overflow:hidden — putting the ring's
      box-shadow ON that element (or an ancestor of it) would self-clip it
      the same way it once did on the wrap (see that rule's own history).
   This ring element is positioned via inline style (left/top/width/
   height, set in setBeaconExpanded()/reader-demo.js from
   getBoundingClientRect() against the wrap) to match the image's real
   rendered box exactly, whatever that happens to be after scale-to-fit —
   not assumed from any element's own CSS box. pointer-events:none so it
   never intercepts a click meant for the beacon/zoom button sitting on
   top of it. */
.reader-demo__el-ring {
	position: absolute;
	pointer-events: none;
	border-radius: 4px;
	opacity: 0;
	transition: opacity 200ms ease;
}
.reader-demo__el-ring--active {
	opacity: 1;
	box-shadow: 0 0 0 2px var(--color-ochre), 0 0 18px 3px rgba(168, 120, 26, 0.45);
}
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__el-ring--active {
	box-shadow: 0 0 0 2px #d4a54a, 0 0 18px 3px rgba(212, 165, 74, 0.4);
}

/* Two layers, not one doing both jobs — see fitOneArtifact()'s own
   comment (reader-demo.js) for the bug this split fixes: a `transform`
   is paint-time only, it never shrinks the box overflow clips against,
   so capping height AND scaling the same element clipped a tall artifact
   (a postcard, say) to its full-size top slice before the shrink ever
   applied, then scaled that already-truncated sliver down even further.
   .reader-demo__el-fit is what fitOneArtifact() caps with an explicit
   `height` and is the only one of the two with overflow:hidden — a
   safety clip for the window before that fit pass has run, or for
   sub-pixel rounding once it has, not the primary sizing mechanism (the
   scale below already lands .reader-demo__el-inner at exactly this
   height in the normal case). Also what keeps both the artifact beacon
   and .reader-demo__el-ring above (both live outside this element, as
   wrap's children, not inside it) clear of any clipping — a real,
   empirically-confirmed CSS gotcha: overflow:hidden clips an element's
   own box-shadow too, despite box-shadow painting outside the border box
   (spec-wise it isn't "content" overflow, but browsers clip it anyway
   when both live on the same element) — see .reader-demo__el-ring's own
   comment for the full history of where the ring itself ended up. */
.reader-demo__el-fit {
	overflow: hidden;
}

/* .reader-demo__el-inner is what fitOneArtifact() scales — transform
   only, never a height cap (see .reader-demo__el-fit's own comment for
   why those two jobs can't share an element). */
.reader-demo__el-inner {
	display: flow-root;
	text-align: center;
	transform-origin: top center;
}

.reader-demo__el--artifact img {
	max-width: 100%;
	height: auto;
	border-radius: var(--border-radius);
	border: 1px solid var(--color-border);
}

/* "Enlarge" affordance — shown only while the fit pass has actually
   shrunk this artifact (data-zoomable="1"). Deliberately on the outer,
   untransformed wrapper so it stays a constant-size tap target no matter
   how far the content inside has been scaled down. */
.reader-demo__zoom-btn {
	--reader-demo-halo: rgba(28, 25, 23, 0.5);
	display: none;
	position: absolute;
	top: 10px;
	right: 10px;
	z-index: 2;
	width: 28px;
	height: 28px;
	align-items: center;
	justify-content: center;
	padding: 0;
	/* A ring, not `none` — this button has no single backdrop to tune
	   against. It sits over the artifact's own capture where that reaches
	   the corner, and over the page itself where it doesn't, so a fill
	   alone can always find a background it disappears into. The outline
	   is what keeps it legible either way, in every theme. */
	border: 1px solid rgba(255, 255, 255, 0.45);
	border-radius: 50%;
	background: rgba(28, 25, 23, 0.62);
	color: var(--color-surface);
	cursor: pointer;
	transition: background var(--transition), border-color var(--transition);
}

.reader-demo__el--artifact[data-zoomable="1"] > .reader-demo__zoom-btn {
	display: flex;
}

.reader-demo__zoom-btn:hover {
	background: rgba(28, 25, 23, 0.78);
}

/*
 * Attention cue — an expanding halo, three beats, then it rests.
 *
 * Deliberately NOT infinite: this is an invitation, not an alert, and a
 * marketing page with something pulsing on it forever reads as broken.
 * The class is added by updatePageUi() only while that artifact is the
 * one actually on the visible page, and removed when it isn't — so
 * paging to an artifact plays the cue fresh (the class genuinely leaves
 * and returns, which is what restarts a CSS animation), and it never
 * plays to an empty room on a page nobody is looking at.
 *
 * No prefers-reduced-motion block needed here: style.css's own sitewide
 * `*, *::before, *::after` override already collapses every animation on
 * the page to 0.01ms with !important.
 */
/* The halo colour comes from a custom property so the reader themes can
   retint it — a keyframe can't be themed directly, but the value it
   resolves is read per element, so the dark override below reaches it.
   Fading to `transparent` rather than to an explicit rgba(…, 0) keeps the
   end stop theme-agnostic; browsers interpolate colour in premultiplied
   alpha, so it fades out cleanly instead of drifting through grey. */
@keyframes reader-demo-zoom-attention {
	0%   { box-shadow: 0 0 0 0 var(--reader-demo-halo); }
	70%  { box-shadow: 0 0 0 11px transparent; }
	100% { box-shadow: 0 0 0 0 transparent; }
}

.reader-demo__zoom-btn--attention {
	animation: reader-demo-zoom-attention 1.9s ease-out 3;
}

/* Found it — stop asking. */
.reader-demo__zoom-btn:hover,
.reader-demo__zoom-btn:focus-visible {
	animation: none;
}

.reader-demo__zoom-btn:focus-visible {
	outline: 2px solid var(--color-surface);
	outline-offset: 2px;
}

/* Full-screen expanded view — not a centred card, so a tall artifact gets
   the most room possible at natural size before its own scroll kicks in.
   Same shape as the real Reader's ZoomModal. */
.reader-demo__zoom-scrim {
	position: fixed;
	inset: 0;
	/* 1000 matches .feature-image-lightbox-overlay, this theme's own
	   existing full-screen enlarge-an-image overlay — the closest
	   precedent, same job. The Reader's internal z-index of 40 is
	   meaningless out here: this scrim is appended to document.body, so it
	   competes directly with .site-header (sticky, z-index: 100) in the
	   root stacking context, and anything under 100 renders BEHIND the
	   header. Deliberately below the cookie banner (9000) and the site's
	   true modals (10000) — an image lightbox shouldn't outrank a consent
	   prompt. */
	z-index: 99999;
	display: flex;
	/* Centring is done with `margin: auto` on the image below, NOT with
	   align-items/justify-content here — that's the deliberate half of
	   this. A centred flex item that OVERFLOWS its container has its
	   overflow pushed equally off both ends, and the part above the top
	   edge is unreachable: you can't scroll back past a scroll
	   container's own start. `margin: auto` centres only while there's
	   spare room and collapses to 0 when there isn't, so a tall artifact
	   top-aligns and scrolls normally instead. Same fix, same reason, as
	   `.reader__terms-gate` in omorphos-studio's reader. */
	padding: var(--space-6);
	overflow: auto;
	background: rgba(23, 19, 16, 0.72);
	/* Resting = closed. openZoom() un-hides, forces a style flush so this
	   state gets a real frame, then adds --open so there's something to
	   transition from. */
	opacity: 0;
	transition: opacity 200ms ease;
}

.reader-demo__zoom-scrim--open {
	opacity: 1;
}

.reader-demo__zoom-scrim[hidden] {
	display: none;
}

/* 600px is the intrinsic width every Artifact component actually renders
   at (`t.width`, omorphos-studio's elements/Artifact/_tokens.js) — the
   capture is a raster of exactly that, so anything wider here is just
   upscaling a fixed-size PNG past its own resolution. `min()` keeps the
   narrow-viewport case working: on a phone the scrim's own padding still
   wins and the image fits the screen. */
/* The artifact settles up into place rather than appearing at full size
   with the backdrop — a touch slower than the scrim, and starting a
   hair small, so the two read as one movement instead of a hard cut.
   transform doesn't affect layout, so this can't disturb the `margin:
   auto` centring above. */
.reader-demo__zoom-frame {
	position: relative;
	margin: auto;
	max-width: min(600px, 100%);
	transform: scale(0.97) translateY(6px);
	transition: transform 260ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.reader-demo__zoom-scrim img {
	display: block;
	max-width: 100%;
	height: auto;
	border-radius: var(--border-radius);
	background: var(--color-surface);
}

.reader-demo__zoom-scrim--open .reader-demo__zoom-frame {
	transform: none;
}

/* E-ink applies to the enlarged artifact too — same filter the page-clip
   uses, so a tapped-open document reads exactly as it would on the device
   being simulated. Only the artifact, not the scrim or the close button:
   those are the site's own chrome, matching how the real Reader keeps its
   header in colour while greyscaling `.reader__content`. Dark mode needs
   nothing here — the scrim is already a dark wash, and the artifact itself
   is a captured document that looks the same in any theme. */
.reader-demo__zoom-scrim[data-reader-theme="eink"] img {
	filter: grayscale(1) contrast(0.92);
}

/* Overlaps the artifact's top-right corner (the scrim's own padding leaves
   room for the overhang), so it reads as part of the image. */
.reader-demo__zoom-close {
	position: absolute;
	top: -14px;
	right: -14px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
	width: 36px;
	height: 36px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--color-border);
	border-radius: 50%;
	background: var(--color-surface);
	color: var(--color-text);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
}

.reader-demo__zoom-close:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__el--artifact img {
	border-color: #3a3530;
}

/*
 * Enlarge button in dark mode. The light-mode fill is a dark translucent
 * ink, which vanishes against this theme's near-black page wherever the
 * artifact's own capture doesn't reach the corner behind it — and the
 * attention halo, also dark ink, vanishes with it.
 *
 * Fill goes more opaque so it reads as a real control rather than a smudge
 * on the page, and both the ring and the halo flip to the theme's own light
 * text colour: visible against the page, and still visible against a light
 * capture, since the fill behind them stays dark either way. Same
 * dark-chrome values .reader-demo__nav-btn already uses in this theme.
 *
 * E-ink needs nothing — its page is #f4f4f4, so the light-mode treatment
 * reads there unchanged (and its grayscale filter keeps a neutral halo
 * neutral).
 */
.reader-demo__stage[data-reader-theme="dark"] .reader-demo__zoom-btn {
	--reader-demo-halo: rgba(232, 228, 222, 0.5);
	background: rgba(28, 25, 23, 0.85);
	border-color: rgba(232, 228, 222, 0.5);
	color: #e8e4de;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__zoom-btn:hover {
	background: rgba(28, 25, 23, 0.95);
	border-color: #e8e4de;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__el blockquote {
	border-left-color: #3a3530;
}

/* Apple Books' own page-advance control, measured directly against a
   real reference screenshot: a soft VERTICAL PILL (roughly 2:1
   height:width, not the near-3:1 first guess) defined almost entirely by
   a gentle shadow — no visible border/outline at rest, just a faint
   near-white fill that barely separates from the page. The chevron
   inside is modest, spanning roughly half the pill's own height —
   NOT stretched edge to edge (see CHEVRON_PREV/CHEVRON_NEXT in
   reader-demo.js). border-radius: 999px on a box narrower than tall
   clamps to width/2 on each corner, which is what turns a plain rounded
   rectangle into a true stadium/pill shape — semicircular caps top and
   bottom, straight sides in between. */
.reader-demo__nav-btn {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 88px;
	border-radius: 999px;
	border: none;
	background: rgba(255, 255, 255, 0);
	color: var(--color-text);
	/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.10), 0 1px 3px rgba(0, 0, 0, 0.06); */
	cursor: pointer;
	transition: opacity var(--transition), background var(--transition), box-shadow var(--transition), transform var(--transition);
	z-index: 2;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__nav-btn {
	background: rgba(58, 53, 48, 0);
	color: #e8e4de;
}

.reader-demo__nav-btn:hover:not(:disabled) {
	background: rgba(255, 255, 255, 1);
	color: var(--color-text);
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14), 0 1px 4px rgba(0, 0, 0, 0.08);
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__nav-btn:hover:not(:disabled) {
	background: rgba(58, 53, 48, 1);
	color: #e8e4de;
}

.reader-demo__nav-btn:active:not(:disabled) {
	transform: translateY(-50%) scale(0.96);
}

.reader-demo__nav-btn:disabled {
	opacity: 0.3;
	cursor: default;
	box-shadow: none;
}

.reader-demo__nav-btn:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

/* Kept inside .reader-demo__el's own 40px page inset (see above) so the
   buttons sit in the book's margin rather than on top of the text. */
.reader-demo__nav-btn--prev {
	left: var(--space-1);
}

.reader-demo__nav-btn--next {
	right: var(--space-1);
}

/* ---- Status bar: mode toggle + page count ---- */

.reader-demo__statusbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-2) var(--space-4);
	border-top: 1px solid var(--color-border);
	background: #ffffff;
	font-family: var(--font-sans);
	font-size: var(--font-size-xs);
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__statusbar {
	background: #262220;
	border-color: #3a3530;
	color: #b8b2a8;
}

.reader-demo__stage[data-reader-theme="eink"] .reader-demo__statusbar {
	background: #e6e6e6;
	border-color: #c9c9c9;
}

/* Groups the play/pause control tight against the Light/Dark/E-ink
   toggle as one left-hand cluster, rather than letting the statusbar's
   own space-between spread it out to the middle of the bar. */
.reader-demo__statusbar-left {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.reader-demo__modes {
	display: flex;
	gap: var(--space-1);
}

.reader-demo__mode-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.3em;
	padding: 0.25rem 0.5rem;
	border-radius: var(--border-radius);
	border: 1px solid transparent;
	background: transparent;
	color: var(--color-muted);
	font-family: var(--font-sans);
	font-size: var(--font-size-xs);
	line-height: 1;
	cursor: pointer;
	transition: background var(--transition), color var(--transition), border-color var(--transition);
}

.reader-demo__mode-btn:hover {
	background: rgba(0, 0, 0, 0.05);
}

.reader-demo__mode-btn svg {
	flex-shrink: 0;
}

.reader-demo__mode-btn[aria-pressed="true"] {
	border-color: var(--color-border);
	background: var(--color-surface);
	color: var(--color-text);
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__mode-btn {
	color: #b8b2a8;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__mode-btn[aria-pressed="true"] {
	background: #1c1917;
	border-color: #3a3530;
	color: #e8e4de;
}

.reader-demo__mode-btn:focus-visible {
	outline: 2px solid var(--color-coffee);
	outline-offset: 2px;
}

.reader-demo__page-count {
	color: var(--color-muted);
	font-variant-numeric: tabular-nums;
	text-align: right;
	line-height: 1.35;
}

/* "2 pages left in chapter" — the reassurance a real reader gets from
   feeling the remaining paper, which a fixed-height viewport can't. */
.reader-demo__page-count-sub {
	display: block;
	font-size: 0.6875rem;
	opacity: 0.85;
}

.reader-demo__stage[data-reader-theme="dark"] .reader-demo__page-count {
	color: #b8b2a8;
}

/* ---- Secondary "See {language} books" link + hint ---- */

.reader-demo__see-link {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	font-size: var(--font-size-sm);
	color: var(--color-coffee);
	text-decoration: underline;
	text-decoration-color: var(--color-stone-400);
	text-underline-offset: 3px;
}

.reader-demo__see-link:hover {
	text-decoration-color: var(--color-terracotta);
}

/* Clarifies the excerpt isn't from a released book — direct ask,
   2026-09-08. Sits below the widget rather than above it: the point is
   to answer "wait, what book is this from?" right when that question
   would naturally occur (after reading the sample), not to hedge the
   hook before anyone's started reading. */
.reader-demo__note {
	display: flex;
	align-items: flex-start;
	justify-content: center;
	gap: var(--space-1);
	max-width: 460px;
	margin: 0;
	font-size: var(--font-size-xs);
	color: var(--color-muted);
	line-height: 1.5;
	text-align: center;
}

/* align-items: center (the original attempt) centers the icon against
   the FULL wrapped two-line block, leaving it floating between the
   lines instead of sitting with the first one — flex-start plus a
   small top nudge lines it up with the first line's cap-height instead,
   the normal icon-before-text alignment. */
.reader-demo__note svg {
	flex-shrink: 0;
	margin-top: 0.2em;
}

@media (prefers-reduced-motion: reduce) {
	.reader-demo__pill,
	.reader-demo__nav-btn,
	.reader-demo__mode-btn {
		transition: none;
	}
}

.ep-scene-break-root {
	margin: 2em 0 !important;
	text-align: center;
	font-size: var(--font-size-sm) !important;
	color: var(--color-muted) !important;
}