/* ====================================================================
   Practice Session — immersive full-bleed typing shell
   Ported from /design_handoff_practice_session/practice-session.css.
   Layered on top of /extensions/index_new/styles.css for design tokens
   (Slate & Coral palette, Newsreader serif, JetBrains Mono).

   Activation: a `body.ps-active` class is toggled by session.js.php the
   moment the user clicks Start Memorizing. While active:
     - The site's regular chrome (header, footer, left rail, top row,
       redesign nav/pastor banner/breadcrumb/hero/modes/more-verses) is
       hidden via the `.ps-hide-when-active` selectors below.
     - The session shell (`.ps-page`) reveals as a fixed full-bleed view.
     - The existing `.js-tiles-container` is relocated into `.ps-stage`
       so the existing typing/flip JS engine keeps working unmodified.
   ==================================================================== */

/* ---------- Activation: hide every chrome element while a session
              is live. The shell itself is `position:fixed inset:0` so
              we don't need a body margin reset.                        */
body.ps-active {
	overflow: hidden;
}
body.ps-active > .top-row,
body.ps-active > #left-side-condensed,
body.ps-active > .js-secondary-nav-wrapper,
body.ps-active > nav,
body.ps-active > header,
body.ps-active > footer,
body.ps-active > .header,
body.ps-active > .footer,
body.ps-active > .pcb-strip,
body.ps-active .pcb-strip,
body.ps-active .mv-root > nav,
body.ps-active .mv-root > footer,
body.ps-active .mv-root .pr-bc,
body.ps-active .mv-root .pr-hero-meta,
body.ps-active .mv-root .pr-modes,
body.ps-active .mv-root .pr-more,
body.ps-active .mv-root .pr-niv,
body.ps-active .js-practice-main-wrapper > div:not(.ps-page):not(.js-progress-overlay),
body.ps-active .js-immersive-header,
body.ps-active .mvr-design-toggle {
	display: none !important;
}

/* The redesigned landing page wraps the practice content in
   `.mv-root.pr-page > section.container` blocks. While active, hide the
   verse, the CTA row, the hero copy — but leave the relocated tile
   container reachable inside the shell. */
body.ps-active .mv-root .pr-hero .js-view-verse-container,
body.ps-active .mv-root .pr-hero .pr-cta-row {
	display: none !important;
}

/* The shell itself starts hidden and becomes a fixed full-bleed
   viewport-filler when `ps-active` is set. */
.ps-page {
	display: none;
}
body.ps-active .ps-page {
	display: flex;
	flex-direction: column;
	position: fixed;
	inset: 0;
	z-index: 9990;
	background: var(--bg, #FAFAF6);
	color: var(--text-1, #0B1426);
	min-height: 100%;
	overflow-y: auto;
	animation: ps-shell-in .22s ease-out both;
}
@keyframes ps-shell-in {
	from { opacity: 0; transform: scale(0.985); }
	to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
	body.ps-active .ps-page { animation: none; }
}

/* ---- Completion / progress overlay — promote both the redesign's
        `.pr-progress-overlay` and the legacy `.js-progress-overlay`
        (which is normally `position:absolute` inside the verse card)
        to a fixed viewport-centered modal so the celebration sits on
        top of the session shell rather than vanishing behind the
        hidden chrome. */
body.ps-active .js-progress-overlay {
	position: fixed !important;
	inset: 0 !important;
	top: 0 !important;
	left: 0 !important;
	width: 100% !important;
	height: 100% !important;
	z-index: 99999 !important;
	background: rgba(11, 20, 38, 0.55);
	padding: 24px;
	box-sizing: border-box;
	-webkit-backdrop-filter: blur(2px);
	backdrop-filter: blur(2px);
	align-items: center;
	justify-content: center;
}
body.ps-active .js-progress-overlay:not(.display-none) {
	display: flex !important;
}
/* Constrain the legacy overlay's inner card so it reads as a real
   modal instead of stretching across the viewport. The redesign's
   .pr-progress-card already self-constrains. */
body.ps-active .js-progress-overlay:not(.pr-progress-overlay) > * {
	max-width: 520px;
	width: 100%;
	margin: auto;
	position: relative;
}
@media (max-width: 640px) {
	body.ps-active .js-progress-overlay { padding: 16px; }
}

/* ---- Progress / completion card reskin ----
   The legacy card markup (practice_test.php) ships old indigo/gray utility
   classes (bg-F8F9FA, color-4F46E5, var(--score-new-bg), a cyan bar). Inside
   the session shell that clashes with the slate + coral system, so reskin the
   card to native tokens. Scoped to `body.ps-active` so the standalone practice
   page keeps its look, and `:not(.js-child-progress-overlay)` so the kids'
   playful gradient overlay is left alone. js- hooks are stable; inline styles
   (new-card bg, dots-marks colour) need `!important`. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) > div {
	background: var(--surface) !important;
	border: 1px solid var(--border);
	border-radius: var(--radius-lg) !important;
	box-shadow: var(--shadow-elev);
}
/* Score chips — old neutral, new coral. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-old-card {
	background: var(--surface-2) !important;
	border: 1px solid var(--border);
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-new-card {
	background: var(--brand-soft) !important;
	border: 1px solid color-mix(in oklab, var(--brand) 32%, transparent);
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-old-card > div:first-child {
	color: var(--text-3) !important;
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-new-card > div:first-child {
	color: var(--brand) !important;
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-score-from {
	color: var(--text-2) !important;
	font-family: var(--font-serif);
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-score-to {
	color: var(--brand) !important;
	font-family: var(--font-serif);
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-arrow { color: var(--text-3); }
/* Progress bar — track to a quiet surface, fill to the brand→accent sweep
   (mirrors the toolbar `.ps-progress`). */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) div:has(> .js-progress-bar-fill) {
	background: var(--surface-2) !important;
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-bar-fill {
	background: linear-gradient(90deg, var(--brand), var(--accent)) !important;
}
/* Stage dots + label. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-stage-label { color: var(--text-3) !important; }
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-hard-dots-marks { color: var(--brand) !important; }
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-hard-dots-text { color: var(--text-3) !important; }
/* Completion badge — coral-soft circle crowning the card, echoing the
   friends-page circular icon badges. Tokens resolve because the overlay
   now carries the .mv-root slate scope. */
.js-progress-overlay:not(.js-child-progress-overlay) .pl-prog-badge {
	width: 56px;
	height: 56px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--brand-soft);
	color: var(--brand);
	margin-bottom: 18px;
	box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand) 22%, transparent);
}
@media (max-width: 640px) {
	.js-progress-overlay:not(.js-child-progress-overlay) .pl-prog-badge { width: 48px; height: 48px; margin-bottom: 14px; }
}
/* Message line — focal serif statement (matches mv-root headings + the
   session shell's serif verse text), not flat utility body copy. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-message {
	color: var(--text-1) !important;
	font-family: var(--font-serif);
	font-size: 19px !important;
	line-height: 1.45 !important;
	letter-spacing: -0.01em;
	max-width: 400px;
	/* .mv-root p { margin:0 } now wins over the .mb20 utility, so restore
	   the gap to the countdown/up-next row explicitly. */
	margin: 0 0 20px !important;
}
@media (max-width: 640px) {
	body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-message { font-size: 17px !important; }
}
/* Up-next banner. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-up-next {
	background: var(--surface-2) !important;
	border: 1px solid var(--border);
}
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-up-next-label { color: var(--text-3) !important; }
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-up-next-verse { color: var(--text-1) !important; }
/* Countdown + pause. */
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-countdown { color: var(--text-3) !important; }
body.ps-active .js-progress-overlay:not(.js-child-progress-overlay) .js-progress-pause { color: var(--brand) !important; }

/* ---------- Toolbar (top) ---------- */
.ps-page .ps-toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-6);
	padding: var(--sp-5) var(--sp-10);
	border-bottom: 1px solid var(--border);
	background: var(--bg);
}
.ps-page .ps-tb-left,
.ps-page .ps-tb-right {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
}
.ps-page .ps-tb-exit {
	width: 36px; height: 36px;
	border-radius: 50%;
	background: var(--surface);
	border: 1px solid var(--border);
	color: var(--text-2);
	display: inline-flex; align-items: center; justify-content: center;
	cursor: pointer;
	transition: color .12s, background .12s, border-color .12s;
}
.ps-page .ps-tb-exit:hover { color: var(--text-1); border-color: var(--border-strong); }

.ps-page .ps-tb-ref {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	font-family: var(--font-serif);
	font-size: 17px;
	font-weight: 600;
	color: var(--text-1);
	padding: 6px 14px;
	border-radius: var(--radius-pill);
	background: var(--surface);
	border: 1px solid var(--border);
	white-space: nowrap;
}
.ps-page .ps-tb-ref .dot {
	width: 22px; height: 22px;
	border-radius: 50%;
	background: var(--brand-soft);
	color: var(--brand);
	display: inline-flex; align-items: center; justify-content: center;
}
.ps-page .ps-tb-ref .trans {
	font-family: var(--font-sans);
	font-size: 10px;
	font-weight: 700;
	letter-spacing: .1em;
	color: var(--text-3);
	background: var(--surface-2);
	padding: 3px 7px;
	border-radius: 4px;
	margin-left: 4px;
}

.ps-page .ps-tb-mode {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 12px;
	color: var(--text-3);
	letter-spacing: .04em;
	font-weight: 500;
}
.ps-page .ps-tb-mode strong {
	color: var(--text-1);
	font-weight: 600;
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

.ps-page .ps-tb-stats {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-5);
}
.ps-page .ps-tb-stat {
	display: inline-flex;
	align-items: baseline;
	gap: 4px;
	font-size: 12px;
	color: var(--text-3);
	letter-spacing: .06em;
	text-transform: uppercase;
	font-weight: 600;
}
.ps-page .ps-tb-stat .v {
	font-family: var(--font-serif);
	font-size: 18px;
	font-weight: 600;
	color: var(--text-1);
	text-transform: none;
	letter-spacing: 0;
}
.ps-page .ps-tb-stat .v em { font-style: italic; color: var(--brand); font-weight: 500; }
.ps-page .ps-tb-stat .v-sub { color: var(--text-3); font-weight: 500; }

.ps-page .ps-tb-icon-btn {
	width: 36px; height: 36px;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var(--border);
	color: var(--text-2);
	display: inline-flex; align-items: center; justify-content: center;
	cursor: pointer;
	transition: all .12s;
}
.ps-page .ps-tb-icon-btn:hover { color: var(--text-1); border-color: var(--border-strong); }

/* ---------- Top progress bar (thin, under toolbar) ---------- */
.ps-page .ps-progress {
	height: 3px;
	background: var(--surface-2);
	position: relative;
}
.ps-page .ps-progress > span {
	display: block;
	height: 100%;
	background: linear-gradient(90deg, var(--brand), var(--accent));
	width: 0%;
	transition: width .4s ease;
}

/* ---------- Context strip (collection name + position, logged-in only) ---------- */
.ps-page .ps-context {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-4);
	padding: 12px var(--sp-10);
	border-bottom: 1px solid var(--border);
	background: var(--bg);
}
.ps-page .ps-context-left {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	min-width: 0;
}
.ps-page .ps-context-ic {
	width: 24px;
	height: 24px;
	border-radius: 7px;
	background: var(--brand-soft);
	color: var(--brand);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}
.ps-page .ps-context-label {
	font-family: var(--font-sans);
	font-size: 13px;
	font-weight: 500;
	color: var(--text-3);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}
.ps-page .ps-context-label strong {
	color: var(--text-1);
	font-weight: 600;
}
.ps-page .ps-context-pos {
	font-family: var(--font-mono);
	font-size: 11px;
	font-weight: 500;
	color: var(--text-3);
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 8px;
	white-space: nowrap;
	flex-shrink: 0;
}
.ps-page .ps-context-pos strong {
	color: var(--text-1);
	font-weight: 700;
}
@media (max-width: 720px) {
	.ps-page .ps-context { padding: 10px var(--sp-4); }
	.ps-page .ps-context-label { font-size: 12px; }
}
@media (max-width: 480px) {
	.ps-page .ps-context-label {
		max-width: 40vw;
	}
}

/* ---------- Stage (verse + helper) ---------- */
.ps-page .ps-stage {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: var(--sp-16) var(--sp-10);
	position: relative;
}
.ps-page .ps-stage-meta {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-3);
	font-size: 12px;
	color: var(--text-3);
	letter-spacing: .12em;
	text-transform: uppercase;
	font-weight: 600;
	margin-bottom: var(--sp-8);
}
.ps-page .ps-stage-meta .pulse {
	width: 8px; height: 8px;
	border-radius: 50%;
	background: var(--brand);
	position: relative;
}
.ps-page .ps-stage-meta .pulse::after {
	content: "";
	position: absolute;
	inset: -4px;
	border-radius: 50%;
	background: var(--brand);
	opacity: 0.32;
	animation: ps-pulse 1.4s ease-out infinite;
}
@keyframes ps-pulse {
	0%   { transform: scale(0.6); opacity: 0.4; }
	100% { transform: scale(1.6); opacity: 0; }
}

/* The stage's tile slot — the relocated .js-tiles-container lives here. */
.ps-page .ps-stage .js-tiles-container,
.ps-page .ps-stage .js-practice-container {
	max-width: 1080px;
	width: 100%;
	margin: 0 auto;
	text-align: center;
}

/* ============================================================
   FLASH CARD IN-SHELL (2026-05-26)
   Flash Card relocates its EXISTING DOM into the stage: the verse card
   (.js-view-verse-container → .js-flash-card-front prompt + .js-flash-card-back
   answer), the reveal button, and the grade container. Lay them out centered
   and re-theme the flash markup's own hex color classes (color-111827 /
   color-394462) which would otherwise be invisible on the dark theme. The
   helper pills (hint/skip/listen) don't apply, so hide them.
   ============================================================ */
body.ps-flash .ps-page .js-ps-stage-slot {
	display: flex;
	flex-direction: column;
	align-items: center;
	width: 100%;
}
body.ps-flash .ps-page .ps-stage .js-view-verse-container {
	max-width: 760px;
	width: 100%;
	margin: 0 auto var(--sp-8);
	text-align: center;
}
body.ps-flash .ps-page .ps-stage .js-view-verse-container,
body.ps-flash .ps-page .ps-stage .js-view-verse-container p {
	color: var(--text-1) !important;
}
body.ps-flash .ps-page .ps-stage .js-flash-card-back p {
	color: var(--text-2) !important;
}
/* Hub card (.pl-verse-wrap) relocated into the stage: its text block is
   max-width 620px with no auto margins (fine on the left-aligned hub, but
   it hugs the left edge of the centered 760px stage card) and carries a
   48px landing bottom margin. Center it and let the stage own spacing. */
body.ps-flash .ps-page .ps-stage .pl-now-text {
	margin: 0 auto 20px;
}
body.ps-flash .ps-page .ps-stage .pl-now-ref {
	margin-bottom: 14px;
}
/* The flash-card answer is fully HIDDEN inside the stage until Reveal —
   the landing shows it normally; only the in-session card is a test.
   js_practice_flashcard's reveal strips .pl-fc-concealed. The recall
   prompt is the inverse: stage-only (display:none on the landing). */
body.ps-flash .ps-page .ps-stage .js-flash-card-conceal.pl-fc-concealed {
	display: none;
}
body.ps-flash .ps-page .ps-stage .pl-fc-prompt,
body.ps-flash .ps-page .ps-stage .pr-fc-prompt {
	display: block;
	margin: 0 0 8px 0;
}
body.ps-flash .ps-page .ps-stage .js-flash-card-reveal {
	margin: var(--sp-6) auto 0;
}
body.ps-flash .ps-page .ps-stage .js-flash-card-grade-container {
	margin-top: var(--sp-6);
	width: 100%;
}
body.ps-flash .ps-page .ps-helper { display: none; }

/* ============================================================
   LETTER TILES — Claude Design tile redesign (2026-05-25).
   The legacy engine emits <input class='tile-input'> inside
   <span class='word-group'> with optional .tile-punctuation /
   .tile-space / .tile-number siblings. We restyle that output
   into discrete letter tiles (30×40, 6px radius, surface fill)
   with per-tile state colors — green soft-fill for correct,
   red for just-typed-wrong, red-tinted fill for missed words.
   No ambient word-level wash; state lives on each tile.
   Supersedes the earlier flat underline-slot pass.
   ============================================================ */

/* Anti-flash: on "Begin Practicing" the engine reveals the freshly-built
   tiles in their original DOM spot ~1 frame BEFORE the session shell
   relocates them + sets body.ps-active, so for a moment they'd paint as
   default (unstyled) browser inputs. Keep the container transparent until
   the shell takes over. Uses opacity (not visibility/display) so the engine
   can still generate + focus the first tile underneath during that frame. */
body:not(.ps-active) .js-tiles-container { opacity: 0; }

/* Stage container — flex-wrap rows of word groups. Column gap is the
   visible spacing between adjacent words; row gap is the line-height
   between wrapped rows. */
body.ps-active .ps-page .js-tiles-container {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 16px 26px;
	padding: var(--sp-6) 0;
	min-height: 100px;
}

/* Word group — invisible grouping only. No pill, border, or background;
   state lives on the individual letter tiles. The tight 4px inner gap keeps
   a word's tiles together while the container's 22px column gap separates
   adjacent words (handoff: inter-letter 4px, inter-word 22px). */
body.ps-active .ps-page .js-tiles-container .word-group {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 0;
	border-radius: 0;
	position: relative;
	margin: 0;
	white-space: nowrap;
	vertical-align: top;
	background: transparent;
	box-shadow: none;
}

/* The space between words — a real gap. The legacy engine emits
   <span class='tile-space'> for non-punct-space mode; flex gap on the
   parent already handles spacing, so collapse the span. */
body.ps-active .ps-page .js-tiles-container .tile-space {
	display: none;
}

/* ---------- Letter tile (Claude Design tile redesign) ----------
   Discrete rectangular tiles: 30×40, 6px radius, 1px border, a surface
   fill, centered serif glyph. State lives per-tile (no ambient word
   wash). Engine→handoff state map:
     (none)    → empty / upcoming        :focus / .mobile-active → current
     .correct  → typed / word-done       .incorrect → just-typed-wrong
     .missed   → word marked wrong        .prefilled/.has-hint/.revealed → hint

   IMPORTANT: the legacy engine writes inline `border` shorthand on the
   `.correct` / `.missed` keystroke paths (js_practice_core_test.php :590,
   :600, :617, :659 — `.css('border', '1px solid var(--correct-border)')`),
   and inline styles beat class CSS. Every state rule below therefore sets
   its border with `!important` so the engine's inline border can't repaint
   the tile. Backgrounds are NOT set inline by the engine, so state fills
   win on specificity without `!important`. `.correct`/`.missed`/`.prefilled`
   tiles are also `disabled`, so `-webkit-text-fill-color` + `opacity:1`
   keep their text crisp instead of UA-greyed. */
body.ps-active .ps-page .js-tiles-container .tile-input {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 40px;
	margin: 0 !important;
	padding: 0;
	border: 1px solid color-mix(in oklab, var(--text-1) 20%, transparent) !important;
	border-radius: 6px !important;
	background: var(--surface);
	font-family: var(--font-serif);
	font-size: 24px;
	font-weight: 500;
	line-height: 1;
	text-align: center;
	color: var(--text-1);
	outline: none;
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	box-shadow: none;
	caret-color: transparent;
	-webkit-text-fill-color: var(--text-1);
	transition: color .12s, border-color .12s, background .12s, box-shadow .12s;
	vertical-align: top;
}

/* Empty / pending — no placeholder glyph shown. */
body.ps-active .ps-page .js-tiles-container .tile-input::placeholder {
	color: transparent;
	opacity: 0;
}

/* Current (active) tile — desktop focus + mobile active. No blinking caret;
   instead the tile "breathes": a brand-colored ring + soft outer glow +
   faint background tint that pulse to mark "type here." The pulse rides on
   box-shadow + background (not border-color) — the base border is set with
   `!important` to beat the engine's inline border, and in the CSS cascade
   `!important` author rules override animations, so an animated border-color
   wouldn't take. A pulsing box-shadow ring sidesteps that and reads like a
   pulsing outline anyway. The static box-shadow below is the reduced-motion
   fallback (shown when the animation is disabled). */
body.ps-active .ps-page .js-tiles-container .tile-input:focus,
body.ps-active .ps-page .js-tiles-container .tile-input.mobile-active:not(:focus) {
	border: 1.5px solid var(--brand) !important;
	background: color-mix(in oklab, var(--brand) 6%, var(--surface));
	caret-color: transparent;
	box-shadow: 0 0 0 2px color-mix(in oklab, var(--brand) 45%, transparent),
	            0 0 10px 2px color-mix(in oklab, var(--brand) 18%, transparent);
	animation: ps-active-pulse 1.4s ease-in-out infinite;
}
@keyframes ps-active-pulse {
	0%, 100% {
		box-shadow: 0 0 0 1px color-mix(in oklab, var(--brand) 32%, transparent),
		            0 0 6px 1px color-mix(in oklab, var(--brand) 12%, transparent);
		background: color-mix(in oklab, var(--brand) 5%, var(--surface));
	}
	50% {
		box-shadow: 0 0 0 2px color-mix(in oklab, var(--brand) 62%, transparent),
		            0 0 14px 3px color-mix(in oklab, var(--brand) 26%, transparent);
		background: color-mix(in oklab, var(--brand) 12%, var(--surface));
	}
}

/* Correct (typed / word-done) — green glyph on a soft-green tile. */
body.ps-active .ps-page .js-tiles-container .tile-input.correct {
	color: var(--success) !important;
	-webkit-text-fill-color: var(--success) !important;
	font-weight: 600;
	background: var(--success-soft);
	border-color: color-mix(in oklab, var(--success) 22%, transparent) !important;
	opacity: 1;
}

/* Word marked wrong — the missed letter shown red on a red-tinted tile. */
body.ps-active .ps-page .js-tiles-container .tile-input.missed {
	color: var(--danger) !important;
	-webkit-text-fill-color: var(--danger) !important;
	font-weight: 600;
	background: color-mix(in oklab, var(--danger) 12%, var(--surface));
	border-color: color-mix(in oklab, var(--danger) 28%, transparent) !important;
	opacity: 1;
}

/* Just-typed-wrong (transient — engine adds .incorrect ~350ms, then reverts
   or promotes to .missed). Red glyph (the wrong char), red border, neutral
   surface fill, one shake. */
body.ps-active .ps-page .js-tiles-container .tile-input.incorrect {
	color: var(--danger) !important;
	-webkit-text-fill-color: var(--danger) !important;
	font-weight: 600;
	background: var(--surface);
	border-color: var(--danger) !important;
	box-shadow: none;
	animation: ps-shake .35s ease-out;
}
@keyframes ps-shake {
	0%, 100% { transform: translateX(0); }
	25% { transform: translateX(-2px); }
	75% { transform: translateX(2px); }
}

/* Hint — first-letter-mode prefilled letters, placeholder hints, and the
   "Reveal" toggle all read as muted italic on a plain tile (no fill). */
body.ps-active .ps-page .js-tiles-container .tile-input.prefilled,
body.ps-active .ps-page .js-tiles-container .tile-input.has-hint,
body.ps-active .ps-page .js-tiles-container .tile-input.revealed {
	color: var(--text-muted) !important;
	-webkit-text-fill-color: var(--text-muted) !important;
	font-style: normal;
	background: var(--surface);
	border-color: color-mix(in oklab, var(--text-1) 20%, transparent) !important;
	opacity: 0.9;
}
body.ps-active .ps-page .js-tiles-container .tile-input.prefilled::placeholder,
body.ps-active .ps-page .js-tiles-container .tile-input.has-hint::placeholder {
	color: var(--text-muted) !important;
	-webkit-text-fill-color: var(--text-muted) !important;
	font-style: normal;
	opacity: 0.85;
}

/* Capitals get a touch more room if the engine ever tags them. */
body.ps-active .ps-page .js-tiles-container .tile-input.wide { width: 34px; }

/* ---------- Punctuation → inline serif glyph, glued to prior word ----------
   The engine appends `.tile-punctuation` inside the word-group after the
   last tile; the negative margin pulls it back over the 4px inter-tile gap
   so the comma/period reads as attached to the letter it follows. */
body.ps-active .ps-page .js-tiles-container .tile-punctuation {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 40px;
	min-width: 8px;
	padding: 0;
	margin-left: -2px;
	font-family: var(--font-serif);
	font-size: 32px;
	font-weight: 500;
	line-height: 1;
	color: var(--text-2);
	background: transparent;
	border: 0;
	border-radius: 0;
}

/* ---------- Verse number badge — leading "16" in a success-soft pill ---------- */
body.ps-active .ps-page .js-tiles-container .tile-number {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-family: var(--font-mono);
	font-size: 14px;
	font-weight: 600;
	color: var(--success);
	background: var(--success-soft);
	border-radius: 8px;
	padding: 0 8px;
	min-width: 28px;
	height: 40px;
	margin: 0 8px 0 0;
	letter-spacing: 0;
	border: 0;
}
body.ps-active .ps-page .js-tiles-container .tile-number.correct {
	color: var(--success);
	background: var(--success-soft);
}

/* ---------- Flip-card / flip-choose / flash-card carry-over ----------
   These three modes don't use letter slots — they keep their existing
   per-word `.flip-word` / `.choice-btn` / `.flash-card-word` rendering.
   Inside the new shell, give them a softer Newsreader treatment but
   leave the bg states (success-bg for done, missed-bg for wrong) alone
   so the JS engine's state colours still read clearly.                */
/* Flip modes pack whole-word tiles, not letter tiles, so they don't need the
   wide letter-tile column gap (which combined with the word margins felt far
   too spread). Tighten the grid when the stage is holding flip words. */
body.ps-active .ps-page .js-tiles-container:has(.flip-word) { gap: 8px; }

body.ps-active .ps-page .js-tiles-container .flip-word {
	font-family: var(--font-serif);
	font-size: 24px;
	font-weight: 500;
	color: var(--text-1);
	padding: 6px 12px;
	margin: 4px;
	border-radius: 8px;
	background: var(--surface-2);
	border: 1px solid var(--border);
	/* Transition only paint properties — NOT `all`. Animating width/layout
	   (which `all` did) made each reveal visibly wobble + shove neighbors.
	   Now a word reveal is a smooth color/background fade with no reflow. */
	transition: color .2s ease, background-color .2s ease, border-color .2s ease, box-shadow .2s ease;
	white-space: nowrap;
}
body.ps-active .ps-page .js-tiles-container .flip-word.hidden {
	color: transparent;
	background: var(--surface-2);
	border-color: var(--border-strong);
	min-width: 56px;
}
body.ps-active .ps-page .js-tiles-container .flip-word.revealed {
	color: var(--text-1);
	background: var(--surface);
}
body.ps-active .ps-page .js-tiles-container .flip-word.correct {
	color: var(--success);
	background: var(--success-soft);
	border-color: var(--success-soft);
}
body.ps-active .ps-page .js-tiles-container .flip-word.missed {
	color: var(--danger);
	background: rgba(220,38,38,0.10);
	border-color: rgba(220,38,38,0.20);
}
body.ps-active .ps-page .js-tiles-container .flip-word.active {
	color: var(--text-1);
	background: var(--brand-soft);
	border-color: var(--brand);
	box-shadow: 0 0 0 2px var(--brand-soft);
}
/* Connecting punctuation between split words (e.g. the em-dash in
   "saved—you"). Rendered as a bare glyph — no tile box — sized to match the
   serif word tiles so it reads as part of the line. */
body.ps-active .ps-page .js-tiles-container .flip-fixed-text {
	font-family: var(--font-serif);
	font-size: 24px;
	font-weight: 500;
	color: var(--text-2);
}
/* Append-reference punctuation (":" "-" of e.g. "3:5-6") starts hidden — but
   holds its layout space — until the flip sequence reaches it; the flip engine
   removes .flip-fixed-hidden as it passes each token. This rule must live HERE
   (session.css, loaded by the shell on every practice surface), not only in
   practice_test.php's <style> block, because the immersive session is also
   rendered by the newer practice_new/hub surfaces, which don't pull that block —
   so without this the JS added the class but nothing hid the glyph. */
body.ps-active .ps-page .js-tiles-container .flip-fixed-text.flip-fixed-hidden {
	visibility: hidden;
}
/* Flip & choose — the choices now render in an inline `.js-flipchoice-inline`
   block that sits BELOW the verse tiles (a sibling of `.js-tiles-container`,
   not a descendant), so the choice-btn rules are scoped to that container
   rather than the tiles container. (The "Choose the correct word" label's
   size/colour are set inline by the engine so they hold on both the session
   stage and the standalone practice page.) */
body.ps-active .ps-page .js-flipchoice-inline .choice-btn,
body.ps-active .ps-page .js-tiles-container .choice-btn {
	font-family: var(--font-sans);
	font-size: 16px;
	font-weight: 500;
	padding: 12px 26px;
	border-radius: 12px;
	background: var(--surface);
	border: 1.5px solid var(--border);
	color: var(--text-1);
	cursor: pointer;
	transition: border-color .15s ease, background .15s ease, color .15s ease, box-shadow .15s ease, transform .12s ease;
	min-width: 112px;
	box-shadow: 0 1px 2px rgba(11,20,38,0.04);
}
body.ps-active .ps-page .js-flipchoice-inline .choice-btn:hover,
body.ps-active .ps-page .js-tiles-container .choice-btn:hover {
	border-color: var(--brand);
	background: var(--brand-soft);
	color: var(--brand);
	box-shadow: 0 4px 14px -6px color-mix(in oklab, var(--brand) 55%, transparent);
	transform: translateY(-1px);
}
body.ps-active .ps-page .js-flipchoice-inline .choice-btn:active,
body.ps-active .ps-page .js-tiles-container .choice-btn:active {
	transform: translateY(0);
}
body.ps-active .ps-page .js-flipchoice-inline .choice-btn.correct-choice,
body.ps-active .ps-page .js-tiles-container .choice-btn.correct-choice {
	border-color: var(--success);
	background: var(--success-soft);
	color: var(--success);
	box-shadow: 0 0 0 3px color-mix(in oklab, var(--success) 18%, transparent);
	transform: none;
}
body.ps-active .ps-page .js-flipchoice-inline .choice-btn.wrong-choice,
body.ps-active .ps-page .js-tiles-container .choice-btn.wrong-choice {
	border-color: var(--danger);
	background: rgba(220,38,38,0.10);
	color: var(--danger);
	transform: none;
}
body.ps-active .ps-page .js-tiles-container .flash-card-word {
	font-family: var(--font-serif);
	font-size: 26px;
	color: var(--text-1);
}

/* ---------- Responsive tile sizing ---------- */
@media (max-width: 640px) {
	body.ps-active .ps-page .js-tiles-container .tile-input {
		width: 26px;
		height: 36px;
		font-size: 22px;
	}
	body.ps-active .ps-page .js-tiles-container .tile-punctuation {
		font-size: 28px;
		height: 36px;
	}
	body.ps-active .ps-page .js-tiles-container .tile-number {
		font-size: 12px;
		height: 36px;
	}
	body.ps-active .ps-page .js-tiles-container { gap: 12px 18px; }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	body.ps-active .ps-page .js-tiles-container .word-group::after { animation: none; opacity: 1; }
	body.ps-active .ps-page .js-tiles-container .tile-input.incorrect { animation: none; }
	body.ps-active .ps-page .js-tiles-container .tile-input:focus,
	body.ps-active .ps-page .js-tiles-container .tile-input.mobile-active:not(:focus) { animation: none; }
	body.ps-active .ps-page .js-tiles-container .word-group { transition: none; }
}

/* ---------- Helper line ---------- */
.ps-page .ps-helper {
	margin-top: var(--sp-12);
	text-align: center;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-4);
}
.ps-page .ps-helper-row {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-3);
	font-size: 12px;
	flex-wrap: wrap;
	justify-content: center;
}
.ps-page .ps-helper-pill {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 12px;
	color: var(--text-2);
	background: var(--surface);
	border: 1px solid var(--border);
	padding: 6px 12px;
	border-radius: var(--radius-pill);
	cursor: pointer;
	font-weight: 500;
	font-family: var(--font-sans);
}
.ps-page .ps-helper-pill:hover { color: var(--text-1); border-color: var(--border-strong); }
.ps-page .ps-helper-pill .kbd {
	font-family: var(--font-mono);
	font-size: 10px;
	color: var(--text-3);
	background: var(--surface-2);
	padding: 1px 5px;
	border-radius: 4px;
	border: 1px solid var(--border);
	margin-left: 4px;
}

/* ---------- Bottom mode rail ---------- */
.ps-page .ps-modebar {
	padding: var(--sp-6) var(--sp-10) var(--sp-8);
	border-top: 1px solid var(--border);
	background: var(--bg-deep);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-4);
}
.ps-page .ps-modebar-left,
.ps-page .ps-modebar-right {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	font-size: 12px;
	color: var(--text-3);
}
.ps-page .ps-modebar-tabs {
	display: inline-flex;
	background: var(--surface-2);
	padding: 4px;
	border-radius: var(--radius-pill);
	border: 1px solid var(--border);
	gap: 2px;
	flex-wrap: wrap;
}
.ps-page .ps-modebar-tab {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 12px;
	font-weight: 500;
	color: var(--text-2);
	background: transparent;
	border: 0;
	padding: 7px 14px;
	border-radius: var(--radius-pill);
	cursor: pointer;
	white-space: nowrap;
	font-family: var(--font-sans);
}
.ps-page .ps-modebar-tab:hover { color: var(--text-1); }
.ps-page .ps-modebar-tab.active {
	background: var(--surface);
	color: var(--text-1);
	font-weight: 600;
	box-shadow: 0 1px 2px rgba(0,0,0,0.06), 0 0 0 1px var(--border);
}
.theme-dark .ps-page .ps-modebar-tab.active,
[data-theme='dark'] .ps-page .ps-modebar-tab.active {
	background: var(--surface-elev);
}

.ps-page .ps-modebar-streak {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 12px;
	color: var(--text-3);
}
.ps-page .ps-modebar-streak strong { color: var(--brand); font-weight: 600; font-family: var(--font-serif); font-size: 14px; }
.ps-page .ps-modebar-label {
	letter-spacing: .1em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--text-3);
	font-size: 11px;
}

/* ---------- Listen player overlap ----------
   The shared listen_player is `position:fixed; bottom:0; z-index:9997`
   — above the shell (9990). When it's visible we lift the mode rail
   above it so the tabs stay reachable. Detected via `:has()` so the
   rail relaxes back automatically the moment the player closes. */
body.ps-active:has(.js-mv-listen-player:not(.display-none-important):not(.display-none)) .ps-page .ps-modebar {
	padding-bottom: calc(var(--sp-8) + 76px);
}

/* ---------- Responsive ---------- */
@media (max-width: 1180px) {
	.ps-page .ps-tb-stats { display: none; }
}
@media (max-width: 980px) {
	.ps-page .ps-tb-ref .trans { display: none; }
}
@media (max-width: 880px) {
	.ps-page .ps-toolbar { padding: var(--sp-3) var(--sp-4); gap: var(--sp-3); flex-wrap: wrap; }
	.ps-page .ps-tb-mode { display: none; }
	.ps-page .ps-stage { padding: var(--sp-8) var(--sp-4); }
	.ps-page .ps-modebar { padding: var(--sp-4) var(--sp-4) var(--sp-5); flex-direction: column; align-items: stretch; gap: var(--sp-3); }
	.ps-page .ps-modebar-left { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
	.ps-page .ps-modebar-tabs { overflow-x: auto; flex-wrap: nowrap; max-width: 100%; -webkit-overflow-scrolling: touch; }
	.ps-page .ps-modebar-right { justify-content: center; }
}
@media (max-width: 640px) {
	.ps-page .ps-helper { margin-top: var(--sp-6); }
	.ps-page .ps-helper-row { gap: var(--sp-2); }
	.ps-page .ps-helper-pill { padding: 6px 10px; font-size: 11px; }
	.ps-page .ps-helper-pill .kbd { display: none; }
	.ps-page .ps-stage-meta { margin-bottom: var(--sp-4); }
	.ps-page .ps-tb-ref { font-size: 15px; padding: 4px 10px; }
	.ps-page .ps-tb-ref .dot { width: 18px; height: 18px; }
}
@media (max-width: 420px) {
	.ps-page .ps-toolbar { padding: var(--sp-2) var(--sp-3); }
	.ps-page .ps-stage { padding: var(--sp-6) var(--sp-3); }
}

/* ---------- Dark theme polish ---------- */
.theme-dark .ps-page .ps-toolbar,
[data-theme='dark'] .ps-page .ps-toolbar { background: var(--bg); }
.theme-dark .ps-page .ps-tb-ref,
[data-theme='dark'] .ps-page .ps-tb-ref { background: var(--surface); }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
	.ps-page .ps-stage-meta .pulse::after { animation: none; opacity: 0; }
	.ps-page .ps-progress > span { transition: none; }
}
