/* ==========================================================================
   Codex Academy — component layer (cx-)
   --------------------------------------------------------------------------
   Lifted from design-docs/ui-redesign/mockups/components.css (DESIGN.md §2-3)
   for the phase-by-phase UI migration (DESIGN.md §9). Loaded AFTER
   styles.css. Everything is namespaced `cx-` / `--cx-`, so with the UI switch
   off (src/ui/uiVersion.js) nothing in the game uses it and nothing changes:
   the only global effects are custom properties on :root and @font-face
   declarations, which load nothing until a cx- element asks for the font.
   Differences from the mockup file: phonics ink classes are `cx-ink-*`
   (were `ink-*`), the self-hosted Lexend is declared here as 'CX Lexend' (so
   the legacy UI keeps its Google-hosted 'Lexend' untouched), and the last
   section wires the legacy training HUD into the v2 HUD (html.cx-v2-hud).

   Rules this file follows (see DESIGN.md §1, §2):
   - Frame carries STATE, glyph ink carries PHONICS CONTENT.
       live  = cyan    (tappable, idle)
       look  = amber   (look here: next letter, hint, DI correction)
       correct = green
       wrong = red     (the only red; means "that was wrong" and nothing else)
       pick  = white   (your pending choice / selected / marked)
   - Sizes are design px at 1024x768 multiplied by --s, so larger desktop
     screens scale up instead of floating small in a big room.
   - Desktop browser first (Kelsey 2026-09-26): hover feedback is allowed,
     under (hover:hover) only.
   - Press feedback is the `.is-pressed` class or :active.
   ========================================================================== */

/* ---------------------------------------------------------------- fonts -- */
/* Self-hosted Lexend (SIL OFL 1.1, fonts/lexend/OFL.txt), variable 400-700.
   Separate family name so the legacy UI's Google-hosted 'Lexend' is not
   affected. font-display: block — the fallback "ɑ" must never flash in. */
@font-face {
    font-family: 'CX Lexend';
    font-style: normal;
    font-weight: 400 700;
    font-display: block;
    src: url('fonts/lexend/lexend-latin-400-700.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: 'CX Lexend';
    font-style: normal;
    font-weight: 400 700;
    font-display: block;
    /* latin-ext: the magic-e macron vowels ā ī ō ū live here */
    src: url('fonts/lexend/lexend-latin-ext-400-700.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;
}

/* Disguises' handwriting faces (UIA-6): the legacy screen named Caveat,
   Dancing Script, Indie Flower and Shadows Into Light but never loaded them,
   so they fell back to the system's ornate script. Self-hosted latin
   subsets from Google Fonts, all SIL OFL 1.1 (fonts/handwriting/OFL-*.txt).
   Declared under their own names, so the legacy screen's inline
   font-family finds them too (v1 and v2). Nothing downloads until a tile
   asks for the face; training.loadDisguiseFonts() waits for them. Lora and
   Patrick Hand come from the Google Fonts link in index.html, as before.
   Indie Flower was dropped 2026-09-28 (its "a" reads as a schwa; BUGS DISG-1). */
@font-face {
    font-family: 'Caveat';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url('fonts/handwriting/caveat-latin-400.woff2') format('woff2');
}
@font-face {
    font-family: 'Dancing Script';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url('fonts/handwriting/dancing-script-latin-400.woff2') format('woff2');
}
@font-face {
    font-family: 'Shadows Into Light';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url('fonts/handwriting/shadows-into-light-latin-400.woff2') format('woff2');
}

/* -------------------------------------------------------------- tokens -- */
:root {
    /* 1 design px. Landscape: the 1024x768 canvas fitted to the viewport.   */
    --s: min(calc(100vw / 1024), calc(100vh / 768));

    /* Surfaces */
    --cx-void: #04070c;
    --cx-bg-1: #08111b; /* panel */
    --cx-bg-2: #0d1a27; /* tile */
    --cx-bg-3: #14273b; /* raised */

    /* Ink */
    --cx-ink: #eef7ff;
    --cx-ink-2: #a9bfd2;
    --cx-ink-3: #5f778b;
    --cx-line-rgb: 126, 214, 255;

    /* State roles (rgb triplets so alpha can vary per use) */
    --cx-live-rgb: 0, 221, 255; /* #00ddff — existing --neon-blue */
    --cx-correct-rgb: 0, 255, 136; /* #00ff88 — existing --accent */
    --cx-wrong-rgb: 255, 51, 102; /* #ff3366 — existing --error; THE red */
    --cx-look-rgb: 255, 206, 58; /* #ffce3a — new: look here / hint */
    --cx-pick-rgb: 255, 255, 255; /* your pick */
    --cx-live: rgb(var(--cx-live-rgb));
    --cx-correct: rgb(var(--cx-correct-rgb));
    --cx-wrong: rgb(var(--cx-wrong-rgb));
    --cx-look: rgb(var(--cx-look-rgb));

    /* Phonics ink (teaching content; same everywhere a word is shown) */
    --cx-ink-cluster: #7eeaff; /* digraph / grouped letters */
    --cx-ink-magic: #ff8a3d; /* magic-e long vowel (with macron)  */
    --cx-ink-sight: #cda6ff; /* sight word */
    --cx-ink-silent: rgba(238, 247, 255, 0.34);

    /* Teams (review-level race only; never used as a state colour) */
    --cx-team-a-rgb: 63, 140, 255;
    --cx-team-b-rgb: 255, 94, 58;

    /* Type */
    --cx-font: 'CX Lexend', 'Lexend', Verdana, sans-serif;
    --cx-font-display: 'Orbitron', 'CX Lexend', sans-serif;

    /* Radii */
    --cx-r-sm: calc(8 * var(--s));
    --cx-r-md: calc(14 * var(--s));
    --cx-r-lg: calc(22 * var(--s));

    /* Motion */
    --cx-t-press: 90ms;
    --cx-t-state: 200ms;
    --cx-t-enter: 360ms;
    --cx-t-shake: 320ms;
    --cx-t-sweep: 640ms;
    --cx-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
    --cx-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (orientation: portrait) {
    :root {
        /* Portrait: fit a 768x1024 canvas instead, so tiles don't shrink */
        --s: min(calc(100vw / 768), calc(100vh / 1024));
    }
}

/* ------------------------------------------------------------ keyframes -- */
@keyframes cx-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(calc(-7 * var(--s))); }
    40% { transform: translateX(calc(6 * var(--s))); }
    60% { transform: translateX(calc(-4 * var(--s))); }
    80% { transform: translateX(calc(2 * var(--s))); }
}
@keyframes cx-lock {
    0% { transform: scale(1); }
    40% { transform: scale(1.07); }
    100% { transform: scale(1); }
}
@keyframes cx-bob {
    0%, 100% { transform: translate(-50%, 0); }
    50% { transform: translate(-50%, calc(-6 * var(--s))); }
}
@keyframes cx-beacon {
    0% { opacity: 0.9; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.35); }
}
@keyframes cx-eq {
    0%, 100% { transform: scaleY(0.35); }
    50% { transform: scaleY(1); }
}
@keyframes cx-ripple {
    0% { opacity: 0.8; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.6); }
}
@keyframes cx-sweep {
    0% { transform: translateX(-120%) skewX(-18deg); }
    100% { transform: translateX(260%) skewX(-18deg); }
}
@keyframes cx-spin {
    to { transform: rotate(360deg); }
}
@keyframes cx-breathe {
    0%, 100% { opacity: 0.55; }
    50% { opacity: 1; }
}

/* ---------------------------------------------------------------- reset -- */
/* Same box model as the game (styles.css sets * { box-sizing: border-box }) */
.cx-screen,
.cx-screen *,
.cx-screen *::before,
.cx-screen *::after {
    box-sizing: border-box;
}
/* Every control here is a real <button>: strip the UA chrome once. */
.cx-btn,
.cx-go,
.cx-comm,
.cx-gear,
.cx-codexbtn,
.cx-decide > *,
.cx-choice,
.cx-tile,
.cx-mode > * {
    -webkit-appearance: none;
    appearance: none;
    border: 0;
    margin: 0;
    font: inherit;
    font-family: var(--cx-font);
    cursor: pointer;
}

/* --------------------------------------------------------------- screen -- */
.cx-screen {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: var(--cx-void);
    color: var(--cx-ink);
    font-family: var(--cx-font);
    letter-spacing: 0.01em;
    -webkit-font-smoothing: antialiased;
}

/* Backdrop: the room the phase happens in. Always full-bleed (behind the
   HUD too, so there is never a seam), always dimmed + vignetted so UI
   reads, never interactive. */
.cx-backdrop {
    position: absolute;
    inset: 0;
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
}
.cx-backdrop::after {
    content: '';
    position: absolute;
    inset: 0;
    background:
        linear-gradient(to bottom, rgba(4, 7, 12, 0.78) 0, rgba(4, 7, 12, 0) calc(120 * var(--s))),
        radial-gradient(ellipse 75% 70% at 50% 52%, rgba(4, 7, 12, 0.18), rgba(4, 7, 12, 0.72)),
        rgba(4, 7, 12, 0.3);
}
.cx-backdrop--calm::after {
    /* for photo scenes where the photo IS the content: only the top scrim */
    background: linear-gradient(to bottom, rgba(4, 7, 12, 0.7) 0, rgba(4, 7, 12, 0) calc(110 * var(--s)));
}
.cx-backdrop--lights-off {
    filter: brightness(0.18) saturate(0.5);
}
.cx-backdrop--mirror {
    transform: scaleX(-1);
}
/* Letterbox fill for coordinate scenes: the same image, blurred and dark,
   behind an aspect-locked .cx-scene. Static, so it rasterises once. */
.cx-backdrop--fill {
    filter: blur(calc(22 * var(--s))) brightness(0.42) saturate(1.1);
    transform: scale(1.12);
}
.cx-backdrop--fill::after {
    background: linear-gradient(to bottom, rgba(4, 7, 12, 0.55) 0, rgba(4, 7, 12, 0) calc(120 * var(--s)));
}

/* Content area: everything below the HUD band */
.cx-content {
    position: absolute;
    top: calc(80 * var(--s));
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: calc(12 * var(--s)) calc(32 * var(--s)) calc(28 * var(--s));
    gap: calc(24 * var(--s));
}

/* ------------------------------------------------------------------ HUD -- */
.cx-hud {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: calc(80 * var(--s));
    padding: 0 calc(16 * var(--s));
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    z-index: 50;
    pointer-events: none;
}
.cx-hud > * {
    pointer-events: auto;
}
.cx-hud__left {
    justify-self: start;
    display: flex;
    align-items: center;
    gap: calc(14 * var(--s));
}
.cx-hud__right {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: calc(22 * var(--s));
}

/* Comm: the handler's channel. Replaces the old "?" help button.
   Tapping it replays the current activity's spoken instruction; it shows
   the "speaking" state whenever the handler (narration) is talking, which
   makes it the game-wide "listen" signal (it owns #audio-indicator). */
.cx-comm {
    --b: calc(56 * var(--s));
    position: relative;
    width: var(--b);
    height: var(--b);
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: var(--cx-live);
    background: radial-gradient(circle at 50% 35%, rgba(20, 44, 64, 0.95), rgba(6, 14, 22, 0.95));
    box-shadow:
        inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.55),
        0 0 calc(12 * var(--s)) rgba(var(--cx-live-rgb), 0.25);
}
.cx-comm svg {
    width: 60%;
    height: 60%;
}
.cx-comm::before,
.cx-comm::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.7);
    opacity: 0;
    pointer-events: none;
}
.cx-comm.is-speaking {
    color: #fff;
    box-shadow:
        inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 1),
        0 0 calc(20 * var(--s)) rgba(var(--cx-live-rgb), 0.55);
}
.cx-comm.is-speaking::before {
    animation: cx-ripple 1.4s ease-out infinite;
}
.cx-comm.is-speaking::after {
    animation: cx-ripple 1.4s ease-out 0.7s infinite;
}
/* static stand-in used by the mockups to show the ripple mid-flight */
.cx-comm.is-speaking.is-frozen::before {
    animation: none;
    opacity: 0.55;
    transform: scale(1.22);
}
.cx-comm.is-speaking.is-frozen::after {
    animation: none;
    opacity: 0.25;
    transform: scale(1.45);
}

/* Mission rail: lesson progress. Fixed length whatever the phase count,
   segments = phases that WILL run, ending in the sigil being earned. */
.cx-rail {
    display: flex;
    align-items: center;
    gap: calc(12 * var(--s));
}
.cx-rail__track {
    display: flex;
    gap: calc(4 * var(--s));
    width: calc(380 * var(--s));
    align-items: center;
}
.cx-rail__seg {
    flex: 1;
    height: calc(6 * var(--s));
    border-radius: calc(3 * var(--s));
    background: rgba(var(--cx-line-rgb), 0.2);
    position: relative;
    overflow: hidden;
}
.cx-rail__seg.is-done {
    background: var(--cx-correct);
    box-shadow: 0 0 calc(8 * var(--s)) rgba(var(--cx-correct-rgb), 0.55);
}
.cx-rail__seg.is-now {
    height: calc(10 * var(--s));
    background: rgba(var(--cx-live-rgb), 0.35);
    box-shadow:
        inset 0 0 0 calc(1.5 * var(--s)) var(--cx-live),
        0 0 calc(10 * var(--s)) rgba(var(--cx-live-rgb), 0.5);
}
/* remediation: done once, will be redone. Kept, not erased. */
.cx-rail__seg.is-redo {
    background: transparent;
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-correct-rgb), 0.75);
}
/* (.cx-rail prefix: .cx-sigil below sets its own --h and would win) */
.cx-rail .cx-rail__goal {
    --h: calc(40 * var(--s));
}

/* Codex button: hex with the collection count */
.cx-codexbtn {
    --h: calc(58 * var(--s));
    position: relative;
    width: calc(var(--h) * 1.1547);
    height: var(--h);
    display: grid;
    place-items: center;
}
.cx-codexbtn::before,
.cx-codexbtn::after {
    content: '';
    position: absolute;
    clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
}
.cx-codexbtn::before {
    inset: 0;
    background: linear-gradient(160deg, rgba(var(--cx-live-rgb), 0.95), rgba(var(--cx-correct-rgb), 0.8));
}
.cx-codexbtn::after {
    inset: calc(2.5 * var(--s));
    background: radial-gradient(circle at 50% 30%, #12304a, #06101a);
}
.cx-codexbtn span {
    position: relative;
    z-index: 1;
    font-family: var(--cx-font);
    font-weight: 700;
    font-size: calc(21 * var(--s));
    color: var(--cx-correct);
    text-shadow: 0 0 calc(8 * var(--s)) rgba(var(--cx-correct-rgb), 0.6);
}
.cx-codexbtn.is-receiving::before {
    background: var(--cx-look);
    filter: drop-shadow(0 0 calc(12 * var(--s)) var(--cx-look));
}

/* Parent gear: deliberately quiet — it's not for the child */
.cx-gear {
    --b: calc(44 * var(--s));
    width: var(--b);
    height: var(--b);
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: var(--cx-ink-3);
    background: rgba(4, 9, 14, 0.55);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.22);
}
.cx-gear svg {
    width: 50%;
    height: 50%;
}

/* ---------------------------------------------------------------- panel -- */
/* The instrument panel every non-scene exercise sits on: solid enough that
   the busy command-centre art never competes with letters (no
   backdrop-filter — too slow on older iPads). Corner brackets = spy HUD. */
.cx-panel {
    --c: rgba(var(--cx-live-rgb), 0.85);
    --bk: calc(26 * var(--s));
    position: relative;
    border-radius: var(--cx-r-md);
    padding: calc(34 * var(--s)) calc(40 * var(--s));
    background: linear-gradient(180deg, rgba(9, 20, 31, 0.93), rgba(5, 11, 18, 0.95));
    box-shadow:
        inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.2),
        0 calc(24 * var(--s)) calc(60 * var(--s)) rgba(0, 0, 0, 0.55);
}
.cx-panel::before {
    content: '';
    position: absolute;
    inset: calc(-4 * var(--s));
    pointer-events: none;
    --w: calc(2.5 * var(--s));
    background:
        linear-gradient(var(--c), var(--c)) left top / var(--bk) var(--w),
        linear-gradient(var(--c), var(--c)) left top / var(--w) var(--bk),
        linear-gradient(var(--c), var(--c)) right top / var(--bk) var(--w),
        linear-gradient(var(--c), var(--c)) right top / var(--w) var(--bk),
        linear-gradient(var(--c), var(--c)) left bottom / var(--bk) var(--w),
        linear-gradient(var(--c), var(--c)) left bottom / var(--w) var(--bk),
        linear-gradient(var(--c), var(--c)) right bottom / var(--bk) var(--w),
        linear-gradient(var(--c), var(--c)) right bottom / var(--w) var(--bk);
    background-repeat: no-repeat;
    filter: drop-shadow(0 0 calc(4 * var(--s)) rgba(var(--cx-live-rgb), 0.6));
}
.cx-panel--quiet::before {
    --c: rgba(var(--cx-live-rgb), 0.4);
}
/* Docks: controls that belong to a panel sit ON its edge, centred.
   Top dock = where the sound comes from (replay). Bottom dock = the way
   out (continue / confirm) and the in-phase counter. Space is reserved
   from the first frame so nothing jumps when a control appears. */
.cx-dock-top,
.cx-dock-bottom {
    position: absolute;
    left: 50%;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: calc(20 * var(--s));
}
.cx-dock-top {
    top: 0;
    transform: translate(-50%, -50%);
}
.cx-dock-bottom {
    bottom: 0;
    transform: translate(-50%, 50%);
}
.cx-panel.has-top {
    padding-top: calc(62 * var(--s));
}
.cx-panel.has-bottom {
    padding-bottom: calc(60 * var(--s));
}

/* ------------------------------------------------------------ the tile -- */
/* One letter tile for the whole game. 1–3 letters, sized by --t.
   data-len="2|3" (or .is-2/.is-3) shrinks the glyph so ck / ang / igh fit.
   States: (idle) · is-standby · is-next · is-sounding · is-sounded ·
   is-correct · is-wrong · is-hint · is-selected · is-disabled · is-silent */
.cx-tile {
    --t: calc(88 * var(--s));
    --k: 0.56;
    --rgb: var(--cx-live-rgb);
    --edge: 0.72;
    --halo: 0.3;
    position: relative;
    flex: none;
    width: var(--t);
    height: var(--t);
    border-radius: calc(var(--t) * 0.17);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--cx-font);
    font-weight: 600;
    font-size: calc(var(--t) * var(--k));
    line-height: 1;
    color: var(--cx-ink);
    background: linear-gradient(180deg, rgba(21, 41, 61, 0.97), rgba(8, 17, 27, 0.97));
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgba(var(--rgb), var(--edge)),
        inset 0 calc(1.5 * var(--s)) 0 calc(2.5 * var(--s)) rgba(255, 255, 255, 0.05),
        0 0 calc(16 * var(--s)) rgba(var(--rgb), var(--halo));
    text-shadow: 0 0 calc(12 * var(--s)) rgba(var(--rgb), 0.35);
    transition:
        box-shadow var(--cx-t-state) var(--cx-ease),
        transform var(--cx-t-press) var(--cx-ease),
        background var(--cx-t-state) var(--cx-ease),
        opacity var(--cx-t-state) var(--cx-ease);
    user-select: none;
    -webkit-user-select: none;
}
.cx-tile[data-len='2'],
.cx-tile.is-2 {
    --k: 0.44;
    letter-spacing: -0.01em;
}
.cx-tile[data-len='3'],
.cx-tile.is-3 {
    --k: 0.345;
    letter-spacing: -0.02em;
}
.cx-tile--tall {
    height: calc(var(--t) * 1.16);
}
/* Word tiles in a strip grow sideways for clusters instead of shrinking */
.cx-tile--flex {
    width: auto;
    min-width: var(--t);
    padding: 0 calc(var(--t) * 0.16);
    --k: 0.56 !important;
}
.cx-tile.is-pressed {
    transform: scale(0.94);
}
/* listen phase: still tappable (AUDIO-1) but not yet "lit" */
.cx-tile.is-standby {
    --edge: 0.42;
    --halo: 0;
}
.cx-tile.is-next {
    --rgb: var(--cx-look-rgb);
    --edge: 1;
    --halo: 0.45;
}
/* pointer chevron lives on the tile itself, so it is always centred on it
   and is part of the tile's hit area (tapping the arrow = tapping the tile) */
.cx-tile.is-next::before {
    content: '';
    position: absolute;
    left: 50%;
    bottom: calc(100% + 10 * var(--s));
    width: calc(30 * var(--s));
    height: calc(22 * var(--s));
    background: var(--cx-look);
    clip-path: polygon(0 0, 100% 0, 50% 100%);
    filter: drop-shadow(0 0 calc(6 * var(--s)) rgba(var(--cx-look-rgb), 0.8));
    transform: translate(-50%, 0);
    animation: cx-bob 1.2s var(--cx-ease) infinite;
}
.cx-tile.is-sounding {
    --edge: 1;
    --halo: 0.65;
    background: linear-gradient(180deg, rgba(0, 90, 120, 0.95), rgba(8, 30, 45, 0.97));
    transform: translateY(calc(-4 * var(--s)));
}
.cx-tile.is-sounded {
    --rgb: var(--cx-correct-rgb);
    --edge: 0.8;
    --halo: 0.2;
}
.cx-tile.is-correct {
    --rgb: var(--cx-correct-rgb);
    --edge: 1;
    --halo: 0.5;
    background: linear-gradient(180deg, rgba(0, 110, 70, 0.9), rgba(4, 40, 30, 0.96));
    animation: cx-lock 260ms var(--cx-ease);
}
.cx-tile.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    --edge: 1;
    --halo: 0.5;
    background: linear-gradient(180deg, rgba(110, 16, 42, 0.9), rgba(40, 6, 18, 0.96));
    animation: cx-shake var(--cx-t-shake) var(--cx-ease);
}
.cx-tile.is-hint {
    --rgb: var(--cx-look-rgb);
    --edge: 1;
    --halo: 0.6;
}
.cx-tile.is-hint::after {
    content: '';
    position: absolute;
    /* the ring is a spread shadow from the tile's own edge, not a box
       outside it, so it never counts as the tile's content overflowing */
    inset: 0;
    border-radius: inherit;
    box-shadow: 0 0 0 calc(4 * var(--s)) rgba(var(--cx-look-rgb), 0.8);
    animation: cx-beacon 1.1s ease-out 3;
    pointer-events: none;
}
.cx-tile.is-selected {
    --rgb: var(--cx-pick-rgb);
    --edge: 1;
    --halo: 0.35;
    transform: translateY(calc(-6 * var(--s)));
}
.cx-tile.is-disabled {
    --edge: 0.18;
    --halo: 0;
    opacity: 0.4;
    pointer-events: none;
}
/* dim a non-answer after the round resolves */
.cx-tile.is-dim {
    --edge: 0.25;
    --halo: 0;
    opacity: 0.55;
}
/* silent e: present, readable, quiet. Dashed frame, dim ink, never italic */
.cx-tile.is-silent {
    --edge: 0;
    --halo: 0;
    color: var(--cx-ink-silent);
    text-shadow: none;
    background: rgba(8, 17, 27, 0.7);
}
.cx-tile.is-silent::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: calc(2 * var(--s)) dashed rgba(238, 247, 255, 0.3);
    pointer-events: none;
}
/* bare glyph cell: same letterform, no box (grids of foreign glyphs,
   message text). Hit area stays the full cell. */
.cx-tile--bare {
    background: transparent;
    --edge: 0;
    --halo: 0;
    text-shadow: none;
}
.cx-tile--bare.is-correct,
.cx-tile--bare.is-wrong,
.cx-tile--bare.is-hint {
    --edge: 1;
}

/* grid cell: a foreign-glyph cell in the intercept grid. Same letterform
   rules, quieter frame; states as for the tile. */
.cx-tile--cell {
    --edge: 0.2;
    --halo: 0;
    color: var(--cx-ink-2);
    text-shadow: none;
    background: rgba(12, 26, 40, 0.85);
}
.cx-tile--cell.is-correct,
.cx-tile--cell.is-wrong,
.cx-tile--cell.is-hint {
    color: var(--cx-ink);
}
.cx-tile--cell.is-scramble {
    color: rgba(169, 191, 210, 0.45);
    filter: blur(calc(0.6 * var(--s)));
}
/* empty slot (transcribe, lost badges, word being built): dashed, no glyph */
.cx-tile.is-empty {
    --edge: 0;
    --halo: 0;
    background: rgba(5, 12, 20, 0.6);
}
.cx-tile.is-empty::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: calc(2.5 * var(--s)) dashed rgba(var(--cx-line-rgb), 0.45);
    pointer-events: none;
}
.cx-tile.is-empty.is-next::after {
    border-color: var(--cx-look);
}

/* phonics ink, used inside tiles and words */
.cx-ink-cluster { color: var(--cx-ink-cluster); }
.cx-ink-magic { color: var(--cx-ink-magic); }
.cx-ink-sight { color: var(--cx-ink-sight); }
.cx-ink-silent { color: var(--cx-ink-silent); }

/* sound bars that appear inside anything that is making sound */
.cx-waves {
    --wh: calc(16 * var(--s));
    display: inline-flex;
    align-items: center;
    gap: calc(3 * var(--s));
    height: var(--wh);
}
.cx-waves b {
    display: block;
    width: calc(3.5 * var(--s));
    height: 100%;
    border-radius: calc(2 * var(--s));
    background: currentColor;
    transform-origin: 50% 50%;
    animation: cx-eq 0.9s ease-in-out infinite;
}
.cx-waves b:nth-child(2) { animation-delay: -0.3s; }
.cx-waves b:nth-child(3) { animation-delay: -0.6s; }
.cx-waves b:nth-child(4) { animation-delay: -0.15s; }
.cx-waves.is-frozen b { animation: none; }
.cx-waves.is-frozen b:nth-child(1) { transform: scaleY(0.45); }
.cx-waves.is-frozen b:nth-child(2) { transform: scaleY(1); }
.cx-waves.is-frozen b:nth-child(3) { transform: scaleY(0.7); }
.cx-waves.is-frozen b:nth-child(4) { transform: scaleY(0.35); }
.cx-tile > .cx-waves {
    position: absolute;
    left: 50%;
    bottom: calc(var(--t) * 0.08);
    transform: translateX(-50%);
    color: var(--cx-live);
    --wh: calc(var(--t) * 0.13);
}

/* -------------------------------------------------------- word strip -- */
/* A decodable word: a row of tiles, left to right. Same in blend,
   comprehension, lineups, transcribe slots and the word chain. */
.cx-word {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(12 * var(--s));
    transition: gap var(--cx-t-enter) var(--cx-ease);
}
/* the squish: tiles fuse into one capsule = one word */
.cx-word.is-blended {
    gap: 0;
    filter: drop-shadow(0 0 calc(14 * var(--s)) rgba(var(--cx-correct-rgb), 0.55));
}
.cx-word.is-blended .cx-tile {
    --rgb: var(--cx-correct-rgb);
    --halo: 0;
    width: auto;
    min-width: 0;
    padding: 0 calc(var(--t) * 0.05);
    border-radius: 0;
    background: linear-gradient(180deg, rgba(0, 96, 62, 0.95), rgba(4, 36, 27, 0.97));
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct);
}
.cx-word.is-blended .cx-tile:first-child {
    padding-left: calc(var(--t) * 0.32);
    border-radius: calc(var(--t) * 0.17) 0 0 calc(var(--t) * 0.17);
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct),
        inset calc(2.5 * var(--s)) 0 0 var(--cx-correct);
}
.cx-word.is-blended .cx-tile:last-child {
    padding-right: calc(var(--t) * 0.32);
    border-radius: 0 calc(var(--t) * 0.17) calc(var(--t) * 0.17) 0;
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct),
        inset calc(-2.5 * var(--s)) 0 0 var(--cx-correct);
}

/* ---------------------------------------------------------- icon buttons -- */
.cx-btn {
    --b: calc(64 * var(--s));
    --rgb: var(--cx-live-rgb);
    position: relative;
    flex: none;
    width: var(--b);
    height: var(--b);
    border-radius: 50%;
    display: inline-grid;
    place-items: center;
    color: rgb(var(--rgb));
    background: radial-gradient(circle at 50% 30%, rgba(22, 46, 68, 0.97), rgba(6, 13, 21, 0.97));
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgba(var(--rgb), 0.75),
        0 0 calc(14 * var(--s)) rgba(var(--rgb), 0.3),
        0 calc(6 * var(--s)) calc(16 * var(--s)) rgba(0, 0, 0, 0.45);
    transition: transform var(--cx-t-press) var(--cx-ease), box-shadow var(--cx-t-state);
}
.cx-btn svg {
    width: 58%;
    height: 58%;
    overflow: visible;
}
.cx-btn.is-pressed {
    transform: scale(0.93);
}
.cx-btn.is-disabled {
    opacity: 0.35;
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--rgb), 0.35);
    pointer-events: none;
}

/* Replay: the target sound. One look, one place (top dock of the panel). */
.cx-replay .w1,
.cx-replay .w2 {
    opacity: 0.35;
}
.cx-replay.is-playing {
    color: #fff;
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgb(var(--rgb)),
        0 0 calc(26 * var(--s)) rgba(var(--rgb), 0.65),
        0 calc(6 * var(--s)) calc(16 * var(--s)) rgba(0, 0, 0, 0.45);
}
.cx-replay.is-playing .w1,
.cx-replay.is-playing .w2 {
    opacity: 1;
    animation: cx-breathe 0.8s ease-in-out infinite;
}
.cx-replay.is-playing .w2 {
    animation-delay: 0.25s;
}
.cx-replay.is-playing.is-frozen .w1,
.cx-replay.is-playing.is-frozen .w2 {
    animation: none;
}
.cx-replay.is-playing::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 calc(2 * var(--s)) rgba(var(--rgb), 0.6);
    animation: cx-ripple 1.2s ease-out infinite;
}
.cx-replay.is-playing.is-frozen::after {
    animation: none;
    opacity: 0.4;
    transform: scale(1.3);
}
/* a new target sound was loaded (gridSweep review grid): one amber ping */
.cx-replay.is-new {
    --rgb: var(--cx-look-rgb);
}
.cx-replay.is-new::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 calc(3 * var(--s)) rgba(var(--cx-look-rgb), 0.7);
    animation: cx-beacon 1s ease-out 2;
}

/* Go: the one solid-filled control in the game. Continue (play glyph) or
   confirm (check glyph). Only rendered when it works — never visible-dead,
   never invisible-live. */
.cx-go {
    --b: calc(88 * var(--s));
    position: relative;
    flex: none;
    width: var(--b);
    height: var(--b);
    border-radius: 50%;
    display: inline-grid;
    place-items: center;
    color: #04151d;
    background: radial-gradient(circle at 50% 30%, #7ff1ff, var(--cx-live) 60%, #00a9c9);
    box-shadow:
        0 0 0 calc(5 * var(--s)) rgba(var(--cx-live-rgb), 0.18),
        0 0 calc(28 * var(--s)) rgba(var(--cx-live-rgb), 0.6),
        0 calc(8 * var(--s)) calc(20 * var(--s)) rgba(0, 0, 0, 0.5);
    transition: transform var(--cx-t-press) var(--cx-ease);
}
.cx-go svg {
    width: 44%;
    height: 44%;
}
.cx-go--play svg {
    transform: translateX(6%);
}
.cx-go--step {
    --b: calc(64 * var(--s));
}
/* quiet go: "next line / next page" while reading. Same shape, but it must
   not outshine the words the child is supposed to decode. */
.cx-go--quiet {
    color: var(--cx-live);
    background: radial-gradient(circle at 50% 30%, rgba(22, 46, 68, 0.97), rgba(6, 13, 21, 0.97));
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgba(var(--cx-live-rgb), 0.8),
        0 0 calc(12 * var(--s)) rgba(var(--cx-live-rgb), 0.3);
}
.cx-go.is-pressed {
    transform: scale(0.93);
}
.cx-go.is-disabled {
    color: var(--cx-ink-3);
    background: rgba(var(--cx-live-rgb), 0.12);
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.3);
    pointer-events: none;
}

/* ------------------------------------------------------------- yes / no -- */
/* Admit / reject, and every other binary choice. No on the left, yes on
   the right (arrow keys and narration assume it). Different shape of
   glyph, different colour, different side. Neither is red: red = wrong. */
.cx-decide {
    display: flex;
    gap: calc(40 * var(--s));
}
.cx-decide > * {
    --rgb: var(--cx-pick-rgb);
    width: calc(148 * var(--s));
    height: calc(104 * var(--s));
    border-radius: var(--cx-r-lg);
    display: grid;
    place-items: center;
    color: rgb(var(--rgb));
    background: linear-gradient(180deg, rgba(21, 41, 61, 0.97), rgba(8, 17, 27, 0.97));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--rgb), 0.8),
        0 0 calc(18 * var(--s)) rgba(var(--rgb), 0.25),
        0 calc(8 * var(--s)) calc(20 * var(--s)) rgba(0, 0, 0, 0.5);
    transition: transform var(--cx-t-press) var(--cx-ease);
}
.cx-decide svg {
    width: calc(48 * var(--s));
    height: calc(48 * var(--s));
}
.cx-decide__no {
    --rgb: 214, 228, 240;
}
.cx-decide__yes {
    --rgb: var(--cx-correct-rgb);
    background: linear-gradient(180deg, rgba(0, 70, 46, 0.95), rgba(4, 28, 22, 0.97));
}
.cx-decide > .is-pressed {
    transform: scale(0.95);
}
.cx-decide > .is-hidden {
    visibility: hidden; /* keeps its slot: no layout jump when revealed */
}
.cx-decide > .is-named {
    /* highlighted while the narration names it */
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgb(var(--rgb)),
        0 0 0 calc(6 * var(--s)) rgba(var(--rgb), 0.22),
        0 0 calc(30 * var(--s)) rgba(var(--rgb), 0.55);
    transform: translateY(calc(-4 * var(--s)));
}
.cx-decide > .is-disabled {
    opacity: 0.35;
    pointer-events: none;
}

/* ---------------------------------------------------- choice cards -- */
/* Bigger pickable options: words (minimal pairs), big icons (placement,
   review choice, team select). Same skin as the tile. */
.cx-choice {
    --rgb: var(--cx-live-rgb);
    --edge: 0.7;
    --halo: 0.25;
    position: relative;
    min-width: calc(200 * var(--s));
    height: calc(150 * var(--s));
    padding: 0 calc(36 * var(--s));
    border-radius: var(--cx-r-lg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: calc(14 * var(--s));
    font-family: var(--cx-font);
    font-weight: 600;
    font-size: calc(64 * var(--s));
    color: var(--cx-ink);
    background: linear-gradient(180deg, rgba(21, 41, 61, 0.97), rgba(8, 17, 27, 0.97));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--rgb), var(--edge)),
        0 0 calc(22 * var(--s)) rgba(var(--rgb), var(--halo)),
        0 calc(10 * var(--s)) calc(26 * var(--s)) rgba(0, 0, 0, 0.5);
}
.cx-choice.is-named {
    --edge: 1;
    --halo: 0.6;
    transform: translateY(calc(-6 * var(--s)));
}
.cx-choice.is-correct { --rgb: var(--cx-correct-rgb); --edge: 1; --halo: 0.55; }
.cx-choice.is-wrong { --rgb: var(--cx-wrong-rgb); --edge: 1; --halo: 0.5; animation: cx-shake var(--cx-t-shake); }
.cx-choice.is-hint { --rgb: var(--cx-look-rgb); --edge: 1; --halo: 0.55; }
.cx-choice.is-dim { opacity: 0.45; --halo: 0; }
.cx-choice svg {
    width: calc(72 * var(--s));
    height: calc(72 * var(--s));
    color: var(--cx-live);
}

/* -------------------------------------------------------- agent card -- */
/* One agent for every phase that has people: gate, operatives, sound match,
   lost badges, which-spy, race. Portrait circle (no square card around
   it), optional letter badge docked under the chin, state on the ring. */
.cx-agent {
    --a: calc(168 * var(--s));
    --rgb: var(--cx-line-rgb);
    --ring: 0.4;
    position: relative;
    flex: none;
    width: var(--a);
    height: var(--a);
    transition: transform var(--cx-t-state) var(--cx-ease);
}
.cx-agent__portrait {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    overflow: hidden;
    background: #0b1826;
    box-shadow:
        0 0 0 calc(3 * var(--s)) rgba(var(--rgb), var(--ring)),
        0 0 calc(22 * var(--s)) rgba(var(--rgb), calc(var(--ring) * 0.6)),
        0 calc(10 * var(--s)) calc(24 * var(--s)) rgba(0, 0, 0, 0.5);
}
.cx-agent__portrait img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* 400x218 character JPGs: portrait is centred; crop to the middle square */
.cx-agent__portrait img.is-wide {
    object-position: 50% 30%;
    transform: scale(1.02);
}
.cx-agent .cx-tile {
    --t: calc(var(--a) * 0.36);
    position: absolute;
    left: 50%;
    bottom: calc(var(--a) * -0.12);
    margin-left: calc(var(--t) / -2);
    z-index: 2;
}
/* heard: a small waves notch at 1 o'clock, stays for the round */
.cx-agent__heard {
    position: absolute;
    top: calc(var(--a) * 0.02);
    right: calc(var(--a) * 0.02);
    width: calc(var(--a) * 0.24);
    height: calc(var(--a) * 0.24);
    border-radius: 50%;
    display: none;
    place-items: center;
    background: #06121c;
    color: var(--cx-live);
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.9);
    z-index: 3;
}
.cx-agent__heard .cx-waves {
    --wh: calc(var(--a) * 0.1);
}
.cx-agent.is-heard .cx-agent__heard,
.cx-agent.is-speaking .cx-agent__heard {
    display: grid;
}
.cx-agent.is-speaking {
    --rgb: var(--cx-live-rgb);
    --ring: 1;
    transform: translateY(calc(-6 * var(--s)));
}
.cx-agent.is-speaking::before {
    content: '';
    position: absolute;
    inset: calc(-3 * var(--s));
    border-radius: 50%;
    box-shadow: 0 0 0 calc(3 * var(--s)) rgba(var(--cx-live-rgb), 0.6);
    animation: cx-ripple 1.2s ease-out infinite;
}
.cx-agent.is-speaking.is-frozen::before {
    animation: none;
    opacity: 0.35;
    transform: scale(1.14);
}
.cx-agent.is-heard {
    --rgb: var(--cx-live-rgb);
    --ring: 0.7;
}
/* marked as impostor / chosen: white ring + reticle over the portrait */
.cx-agent.is-marked,
.cx-agent.is-selected {
    --rgb: var(--cx-pick-rgb);
    --ring: 1;
    transform: translateY(calc(-6 * var(--s)));
}
.cx-agent__reticle {
    position: absolute;
    inset: 0;
    display: none;
    color: #fff;
    z-index: 1;
    pointer-events: none;
}
.cx-agent.is-marked .cx-agent__reticle {
    display: block;
}
.cx-agent.is-marked .cx-agent__portrait img {
    filter: saturate(0.5) brightness(0.8);
}
.cx-agent.is-correct {
    --rgb: var(--cx-correct-rgb);
    --ring: 1;
}
.cx-agent.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    --ring: 1;
    animation: cx-shake var(--cx-t-shake);
}
.cx-agent.is-hint {
    --rgb: var(--cx-look-rgb);
    --ring: 1;
}
.cx-agent.is-disabled {
    opacity: 0.4;
}
.cx-agent.is-dragging {
    transform: scale(1.08) translateY(calc(-10 * var(--s)));
    z-index: 10;
}
.cx-agent.is-dragging .cx-agent__portrait {
    box-shadow:
        0 0 0 calc(3 * var(--s)) #fff,
        0 calc(24 * var(--s)) calc(40 * var(--s)) rgba(0, 0, 0, 0.7);
}
/* a commit button docked under a listened-to agent (which-spy) */
.cx-agent > .cx-go {
    position: absolute;
    left: 50%;
    top: calc(100% + 18 * var(--s));
    transform: translateX(-50%);
}

/* ------------------------------------------------------------------ pips -- */
/* In-activity counter ("found 2 of 6"). Diamonds, inside the phase panel.
   Deliberately a different shape and place from the HUD rail. */
.cx-pips {
    display: inline-flex;
    align-items: center;
    gap: calc(9 * var(--s));
    padding: calc(9 * var(--s)) calc(16 * var(--s));
    border-radius: 999px;
    background: rgba(5, 11, 18, 0.94);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.22);
}
.cx-pips > i {
    width: calc(12 * var(--s));
    height: calc(12 * var(--s));
    border-radius: calc(2 * var(--s));
    transform: rotate(45deg);
    background: rgba(var(--cx-line-rgb), 0.12);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.35);
}
.cx-pips > i.is-done {
    background: var(--cx-correct);
    box-shadow: 0 0 calc(8 * var(--s)) rgba(var(--cx-correct-rgb), 0.7);
}
.cx-pips > i.is-now {
    background: transparent;
    box-shadow: inset 0 0 0 calc(2 * var(--s)) var(--cx-live), 0 0 calc(8 * var(--s)) rgba(var(--cx-live-rgb), 0.5);
}
.cx-pips > .sep {
    width: calc(8 * var(--s));
}

/* ----------------------------------------------------------- sigil hex -- */
/* The object a sigil IS: codex cell, HUD rail goal, success badge,
   new-sigil reveal, review prompt. Flat-top hex to match the codex. */
.cx-sigil {
    --h: calc(80 * var(--s));
    --rgb: var(--cx-live-rgb);
    position: relative;
    isolation: isolate;
    flex: none;
    width: calc(var(--h) * 1.1547);
    height: var(--h);
    display: grid;
    place-items: center;
    font-family: var(--cx-font);
    font-weight: 600;
    font-size: calc(var(--h) * 0.5);
    line-height: 1;
    color: #e9fdff;
    text-shadow:
        0 0 calc(var(--h) * 0.06) rgba(var(--rgb), 1),
        0 0 calc(var(--h) * 0.16) rgba(var(--rgb), 0.8);
    filter: drop-shadow(0 0 calc(var(--h) * 0.12) rgba(var(--rgb), 0.45));
}
.cx-sigil[data-len='2'] { font-size: calc(var(--h) * 0.4); }
.cx-sigil[data-len='3'] { font-size: calc(var(--h) * 0.31); letter-spacing: -0.02em; }
.cx-sigil::before,
.cx-sigil::after {
    content: '';
    position: absolute;
    clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
    z-index: -1;
}
.cx-sigil::before {
    inset: 0;
    background: linear-gradient(160deg, rgba(var(--rgb), 1), rgba(var(--rgb), 0.55));
}
.cx-sigil::after {
    inset: calc(var(--h) * 0.045);
    background: radial-gradient(circle at 50% 35%, rgba(18, 52, 78, 1), rgba(5, 14, 24, 1) 75%);
}
.cx-sigil.is-current {
    --rgb: var(--cx-look-rgb);
}
.cx-sigil.is-locked {
    color: transparent;
    text-shadow: none;
    filter: none;
}
.cx-sigil.is-locked::before {
    background: rgba(var(--cx-line-rgb), 0.22);
}
.cx-sigil.is-locked::after {
    background: rgba(5, 12, 20, 0.92);
}
.cx-sigil.is-earned {
    --rgb: var(--cx-correct-rgb);
}
.cx-sigil.is-pending {
    /* the goal at the end of the rail before it's earned */
    color: rgba(238, 247, 255, 0.55);
    text-shadow: none;
    filter: none;
}
.cx-sigil.is-pending::before {
    background: rgba(var(--cx-live-rgb), 0.5);
}

/* ----------------------------------------------------------- scene frame -- */
/* Coordinate-bearing images (comprehension scenes, lineups, chase maps).
   INVARIANT: the <img> box is exactly the painted image. The frame is
   sized by fitScene() to the image's natural aspect ratio inside the space
   available (letterbox, never crop); hit-testing and overlays use the
   img rect / an SVG with viewBox 0 0 100 100 in image percent. The border
   is painted by ::after ABOVE the image, so it never shifts the img box. */
.cx-scene {
    position: relative;
    flex: none;
    width: var(--scene-w, 960px);
    height: var(--scene-h, 540px);
    border-radius: calc(10 * var(--s));
    overflow: hidden;
    background: #000;
    box-shadow:
        0 0 calc(30 * var(--s)) rgba(var(--cx-live-rgb), 0.22),
        0 calc(18 * var(--s)) calc(50 * var(--s)) rgba(0, 0, 0, 0.6);
}
.cx-scene > img {
    display: block;
    width: 100%;
    height: 100%;
}
.cx-scene::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.6);
    pointer-events: none;
    z-index: 5;
}
.cx-scene__overlay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 4;
}
/* explore mode: the whole frame changes, not just a button */
.cx-scene.is-explore::after {
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) var(--cx-live),
        inset 0 0 calc(60 * var(--s)) rgba(var(--cx-live-rgb), 0.35);
}
.cx-scene.is-explore::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    background:
        repeating-linear-gradient(to bottom, rgba(0, 221, 255, 0.07) 0 calc(2 * var(--s)), transparent calc(2 * var(--s)) calc(5 * var(--s))),
        linear-gradient(rgba(0, 60, 90, 0.25), rgba(0, 60, 90, 0.25));
    mix-blend-mode: screen;
}

/* tap marker on a scene: always inside the frame (clamped), never clipped */
.cx-mark {
    position: absolute;
    width: calc(56 * var(--s));
    height: calc(56 * var(--s));
    margin: calc(-28 * var(--s)) 0 0 calc(-28 * var(--s));
    border-radius: 50%;
    z-index: 6;
    box-shadow: 0 0 0 calc(3 * var(--s)) currentColor, 0 0 calc(18 * var(--s)) currentColor;
    display: grid;
    place-items: center;
}
.cx-mark svg {
    width: 50%;
    height: 50%;
}
.cx-mark.is-correct { color: var(--cx-correct); background: rgba(0, 60, 40, 0.6); }
.cx-mark.is-wrong { color: var(--cx-wrong); background: rgba(70, 0, 20, 0.6); }
.cx-mark.is-probe { color: var(--cx-live); background: rgba(0, 40, 60, 0.5); }

/* chase / race badge chip carried by a runner: dark chip so the glyph
   reads against cyan line art at every perspective scale */
.cx-chip {
    --c: calc(44 * var(--s));
    --rgb: var(--cx-live-rgb);
    position: absolute;
    min-width: var(--c);
    height: var(--c);
    padding: 0 calc(6 * var(--s));
    border-radius: calc(var(--c) * 0.28);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--cx-font);
    font-weight: 700;
    font-size: calc(var(--c) * 0.62);
    line-height: 1;
    color: #fff;
    background: #050b12;
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgba(var(--rgb), 0.95),
        0 0 calc(10 * var(--s)) rgba(var(--rgb), 0.6),
        0 calc(3 * var(--s)) calc(8 * var(--s)) rgba(0, 0, 0, 0.8);
}
.cx-chip[data-len='2'] { font-size: calc(var(--c) * 0.5); }
.cx-chip.is-correct { --rgb: var(--cx-correct-rgb); background: #053d27; }
.cx-chip.is-wrong { --rgb: var(--cx-wrong-rgb); background: #3d0615; }

/* -------------------------------------------------------- mode switch -- */
/* Two-state segmented control (lineup ask/accuse, scene explore/answer).
   The active side is filled; the scene itself also changes (see above). */
.cx-mode {
    display: inline-flex;
    padding: calc(5 * var(--s));
    gap: calc(4 * var(--s));
    border-radius: 999px;
    background: rgba(5, 11, 18, 0.94);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-live-rgb), 0.45);
}
.cx-mode > * {
    width: calc(66 * var(--s));
    height: calc(58 * var(--s));
    border-radius: 999px;
    display: grid;
    place-items: center;
    color: var(--cx-live);
}
.cx-mode svg {
    width: calc(30 * var(--s));
    height: calc(30 * var(--s));
}
.cx-mode > .is-on {
    color: #04151d;
    background: var(--cx-live);
    box-shadow: 0 0 calc(16 * var(--s)) rgba(var(--cx-live-rgb), 0.6);
}

/* ------------------------------------------------------- timer fuse -- */
/* Timed rounds: one fuse that runs around the thing being timed. Drains
   cyan -> amber. Never red: running out of time is not "wrong".
   Implementation: SVG rect, pathLength=100, stroke-dashoffset transition. */
.cx-fuse {
    position: absolute;
    inset: calc(-14 * var(--s));
    width: calc(100% + 28 * var(--s));
    height: calc(100% + 28 * var(--s));
    pointer-events: none;
    overflow: visible;
}
.cx-fuse rect,
.cx-fuse path {
    fill: none;
    stroke-width: 5;
    vector-effect: non-scaling-stroke;
    stroke-linecap: round;
}
.cx-fuse .track {
    stroke: rgba(var(--cx-line-rgb), 0.16);
}
.cx-fuse .burn {
    stroke: var(--cx-live);
    filter: drop-shadow(0 0 5px rgba(var(--cx-live-rgb), 0.9));
    stroke-dasharray: 100 100;
}
.cx-fuse.is-low .burn {
    stroke: var(--cx-look);
    filter: drop-shadow(0 0 5px rgba(var(--cx-look-rgb), 0.9));
}

/* "your turn" sheen: one pass of light across the live items when the
   prompt audio ends. Applied to a container for 640ms, then removed. */
.cx-yourturn > .cx-tile::after,
.cx-yourturn .cx-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent);
    width: 45%;
    animation: cx-sweep var(--cx-t-sweep) var(--cx-ease) 1 both;
    pointer-events: none;
}
.cx-yourturn.is-frozen .cx-tile {
    overflow: hidden;
}
.cx-yourturn.is-frozen .cx-tile::after {
    animation: none;
    transform: translateX(80%) skewX(-18deg);
}

/* ------------------------------------------------------- reduced motion -- */
@media (prefers-reduced-motion: reduce) {
    .cx-screen *,
    .cx-screen *::before,
    .cx-screen *::after {
        animation-iteration-count: 1 !important;
        animation-duration: 1ms !important;
    }
}
@media (hover: hover) {
    .cx-tile:hover,
    .cx-btn:hover,
    .cx-choice:hover {
        filter: brightness(1.12);
    }
}

/* ------------------------------------------------ review-mode goal hex -- */
/* (from mockups/screens.css) the rail's goal in a codex review is violet
   and carries an "again" mark, so a review is visibly a review. */
.cx-sigil.is-review {
    --rgb: 205, 166, 255;
}
.cx-sigil.is-review::after {
    background: radial-gradient(circle at 50% 35%, #2a1d44, #0d0818 75%);
}
.cx-rail.is-review-mode .cx-rail__seg.is-now {
    background: rgba(205, 166, 255, 0.35);
    box-shadow:
        inset 0 0 0 calc(1.5 * var(--s)) #cda6ff,
        0 0 calc(10 * var(--s)) rgba(205, 166, 255, 0.5);
}
.cx-rail.is-review-mode::after {
    content: '';
    flex: none;
    width: calc(22 * var(--s));
    height: calc(22 * var(--s));
    margin-left: calc(-18 * var(--s));
    margin-top: calc(-26 * var(--s));
    border-radius: 50%;
    background: #0d0818
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19.2 12.4a7.2 7.2 0 1 1-2.1-5.3M19.6 3.6v4.8h-4.8' fill='none' stroke='%23cda6ff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
        center/70% no-repeat;
    box-shadow: 0 0 0 calc(1.5 * var(--s)) #cda6ff;
}

/* ======================================================================
   v2 HUD on the legacy training screen (UI step 1, DESIGN.md §4)
   ----------------------------------------------------------------------
   Active only when <html> has .cx-v2-hud (src/ui/uiVersion.js). src/ui/hud.js
   reuses the legacy elements so every id contract survives (#help-btn,
   #progress-dots, #training-settings-btn, #audio-indicator.hidden) and swaps
   their legacy classes for cx- ones, so no styles.css rule applies to them.
   ====================================================================== */
html.cx-v2-hud #training-screen .training-hud.cx-hud {
    /* in flow (not absolute) so .training-content keeps "everything below
       the 80-design-px band", like .cx-content in the mockups */
    position: relative;
    flex: none;
    top: auto;
    height: calc(80 * var(--s));
    padding: 0 calc(16 * var(--s));
    gap: 0;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    justify-content: stretch;
    font-family: var(--cx-font);
    z-index: 50;
}
/* the band's own pointer area is only its buttons */
html.cx-v2-hud #training-screen .training-hud.cx-hud .cx-rail {
    pointer-events: none;
}
html.cx-v2-hud .cx-comm,
html.cx-v2-hud .cx-codexbtn,
html.cx-v2-hud .cx-gear {
    padding: 0;
    cursor: pointer;
    transition:
        transform var(--cx-t-press) var(--cx-ease),
        filter var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-hud .cx-codexbtn {
    background: none;
    color: inherit;
}
html.cx-v2-hud .cx-comm:active,
html.cx-v2-hud .cx-codexbtn:active,
html.cx-v2-hud .cx-gear:active {
    transform: scale(0.94);
}
@media (hover: hover) {
    html.cx-v2-hud .cx-comm:hover,
    html.cx-v2-hud .cx-codexbtn:hover {
        filter: brightness(1.18);
    }
    html.cx-v2-hud .cx-gear:hover {
        color: var(--cx-ink-2);
    }
}
html.cx-v2-hud .cx-comm:focus-visible,
html.cx-v2-hud .cx-codexbtn:focus-visible,
html.cx-v2-hud .cx-gear:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}
/* Speaking state: mirrored from #audio-indicator (hud.js), so the comm
   button is where "someone is talking" shows on the training screen. */
html.cx-v2-hud .cx-comm.is-speaking svg {
    color: #fff;
}
/* The legacy bottom-left pill stays for the platformer; on the training
   screen its job moves to the comm button. Its id and .hidden contract are
   untouched (audio.js still toggles it; audio-hang.spec still reads it). */
html.cx-v2-hud #training-screen.active ~ #audio-indicator {
    display: none;
}
/* The right-edge codex tab gives way to the HUD codex button while a
   lesson is up; the expanded codex (opened from the HUD button) is
   unchanged until the codex step (§9.2 step 4). */
html.cx-v2-hud #training-screen.active ~ #codex-sidebar.codex-collapsed {
    display: none;
}
/* no rail on screens that are not lessons (placement, Passover) */
html.cx-v2-hud #training-screen .cx-rail.is-off {
    visibility: hidden;
}
/* codex button: receiving a newly earned sigil */
html.cx-v2-hud .cx-codexbtn.is-receiving {
    animation: cx-lock 600ms var(--cx-ease-spring) 1;
}
@media (prefers-reduced-motion: reduce) {
    html.cx-v2-hud .cx-comm.is-speaking::before,
    html.cx-v2-hud .cx-comm.is-speaking::after {
        animation-iteration-count: 1;
    }
}

/* ======================================================================
   v2 letter phases (UI step 2, DESIGN.md §5.2): new-sigil intro,
   acquisition (+ reward), symbol pick, grid sweep, rapid decode.
   ----------------------------------------------------------------------
   Markup: src/ui/letterPhases.js. Every tappable is an outer
   <button class="<legacy class> cx-hit"> (hunt-option, sweep-cell,
   rapid-option, transmission-cell keep their tests and recipes working)
   holding one .cx-tile. The first rule strips whatever styles.css gives
   the legacy class, so only the inner tile is styled. Each part is scoped
   to its html.cx-v2-<part> class; with the part off none of this applies.
   ====================================================================== */
/* ends at -101, not -100 (with the 100/101 dash pattern below): at exactly
   -100 a zero-length dash is left at 12 o'clock and its round cap draws a dot */
@keyframes cx-fuse-burn {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: -101; }
}
@keyframes cx-round-in {
    from { opacity: 0; transform: translateY(calc(8 * var(--s))); }
    to { opacity: 1; transform: none; }
}
@keyframes cx-glyph-in {
    from { opacity: 0.2; }
    to { opacity: 1; }
}
@keyframes cx-coin-rise {
    from { opacity: 0; transform: translateY(calc(48 * var(--s))) scale(0.6); }
    to { opacity: 1; transform: none; }
}

html.cx-v2-newsigil .cx-hit,
html.cx-v2-acquisition .cx-hit,
html.cx-v2-symbolpick .cx-hit,
html.cx-v2-gridsweep .cx-hit,
html.cx-v2-rapiddecode .cx-hit {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: inline-flex;
    width: auto;
    height: auto;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    line-height: 1;
    text-transform: none;
    text-shadow: none;
    transform: none;
    animation: none;
    transition: none;
    opacity: 1;
    filter: none;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
}
html.cx-v2-newsigil .cx-hit:disabled,
html.cx-v2-acquisition .cx-hit:disabled,
html.cx-v2-symbolpick .cx-hit:disabled,
html.cx-v2-gridsweep .cx-hit:disabled,
html.cx-v2-rapiddecode .cx-hit:disabled {
    cursor: default;
}
html.cx-v2-acquisition .cx-hit:active > .cx-tile,
html.cx-v2-symbolpick .cx-hit:active > .cx-tile,
html.cx-v2-gridsweep .cx-hit:active > .cx-tile,
html.cx-v2-rapiddecode .cx-hit:active > .cx-tile {
    transform: scale(0.94);
}
html.cx-v2-acquisition .cx-hit:focus-visible,
html.cx-v2-symbolpick .cx-hit:focus-visible,
html.cx-v2-gridsweep .cx-hit:focus-visible,
html.cx-v2-rapiddecode .cx-hit:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}

/* the stage fills the lesson content area and centres the panel; its
   padding leaves room for the docks that sit on the panel's edges */
.cx-stage {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    min-height: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(40 * var(--s)) calc(32 * var(--s)) calc(36 * var(--s));
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
.cx-stage .cx-panel {
    flex: none;
}
.cx-stage .cx-replay {
    cursor: pointer;
}
/* the "your turn" sheen stays inside each tile */
.cx-stage .cx-yourturn .cx-tile {
    overflow: hidden;
}

/* grids */
.cx-grid {
    position: relative;
    display: grid;
}
.cx-grid--pick,
.cx-grid--rapid {
    grid-template-columns: repeat(2, auto);
    gap: calc(28 * var(--s));
}
.cx-grid--acq {
    grid-template-columns: repeat(6, auto);
    gap: calc(12 * var(--s));
    transition: opacity var(--cx-t-enter) var(--cx-ease);
}
.cx-grid--sweep {
    grid-template-columns: repeat(4, auto);
    gap: calc(14 * var(--s));
}
/* a new round fades in instead of snapping (the panel stays) */
.cx-grid.is-entering {
    animation: cx-round-in 220ms var(--cx-ease) both;
}

/* rapid decode: the fuse runs round the tiles; tiles and fuse keep their
   space but stay hidden until 500ms after the sound starts */
.cx-panel.cx-rapid {
    padding-left: calc(56 * var(--s));
    padding-right: calc(56 * var(--s));
}
.cx-grid--rapid.is-concealed > .cx-hit,
.cx-grid--rapid.is-concealed > .cx-fuse {
    visibility: hidden;
}
/* the fuse is drawn in real pixels (letterPhases.sizeFuse), so no
   non-scaling-stroke: that makes Chrome ignore pathLength for the dashes */
.cx-stage .cx-fuse path {
    vector-effect: none;
    stroke-width: calc(5 * var(--s));
}
/* dash 100 + gap 101: with the burn ending at offset -101 nothing is lit at
   either end (a 100/100 pattern re-lights the first 1% at 12 o'clock) */
.cx-stage .cx-fuse .burn {
    stroke-dasharray: 100 101;
}
.cx-fuse.is-idle .burn {
    stroke-dashoffset: 0;
}
.cx-fuse.is-stopped .burn {
    animation-play-state: paused !important;
}
.cx-panel.is-timeout .cx-grid--rapid {
    filter: saturate(0.8);
}

/* new-sigil intro: the unresolved hex where the replay node will be */
.cx-incoming {
    position: relative;
    display: grid;
    place-items: center;
}
.cx-incoming > span {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    color: var(--cx-live);
}
.cx-incoming > span svg {
    width: calc(36 * var(--s));
    height: calc(36 * var(--s));
    animation: cx-spin 2.4s linear infinite;
}
.cx-tile.is-swap {
    animation: cx-glyph-in 200ms ease-out both;
}

/* acquisition reward: grid dims, the coin rises, then flies to the HUD */
.cx-grid--acq.is-dimmed {
    opacity: 0.16;
}
.cx-reward {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    z-index: 60; /* above the HUD band while it flies there */
}
.cx-reward__rise {
    animation: cx-coin-rise 420ms var(--cx-ease-spring) both;
}
.cx-coin {
    display: block;
    width: calc(300 * var(--s));
    height: auto;
    filter: drop-shadow(0 0 calc(30 * var(--s)) rgba(var(--cx-live-rgb), 0.55))
        drop-shadow(0 calc(12 * var(--s)) calc(20 * var(--s)) rgba(0, 0, 0, 0.7));
}
.cx-coin.cx-sigil {
    width: calc(var(--h) * 1.1547);
    filter: drop-shadow(0 0 calc(30 * var(--s)) rgba(var(--cx-live-rgb), 0.55));
}

/* ======================================================================
   v2 shell, UI steps 3-4 (DESIGN.md §5.1, §3.17-3.18)
   ----------------------------------------------------------------------
   success (level complete / review complete), transitions (phase fades,
   rail motion, platformer -> lesson cross-fade), remediation (hollow
   earned segments: .cx-rail__seg.is-redo above, driven by hud.renderRail),
   codex (full-screen overlay on the painted plate Kelsey keeps), settings
   (parents' slate). Markup: src/ui/shell.js, src/ui/screens.js. Each part
   is scoped to its html.cx-v2-<part> class.
   ====================================================================== */

/* a sigil making its sound (success hex, codex cell, codex dock) */
@keyframes cx-sigil-speak {
    0%,
    100% {
        transform: scale(1.06);
    }
    50% {
        transform: scale(1.12);
    }
}

/* ------------------------------------------------------------ success -- */
@keyframes cx-success-land {
    from {
        opacity: 0;
        transform: scale(0.6);
    }
    to {
        opacity: 1;
        transform: none;
    }
}
@keyframes cx-burst-in {
    from {
        opacity: 0;
        transform: scale(0.7) rotate(-12deg);
    }
    to {
        opacity: 1;
        transform: none;
    }
}
@keyframes cx-go-in {
    from {
        opacity: 0;
        transform: translateY(calc(10 * var(--s)));
    }
}
html.cx-v2-success .cx-stage--success {
    flex-direction: column;
    gap: calc(22 * var(--s));
}
html.cx-v2-success .cx-success {
    position: relative;
    display: grid;
    place-items: center;
    width: calc(420 * var(--s));
    height: calc(330 * var(--s));
}
html.cx-v2-success .cx-success__burst {
    position: absolute;
    inset: calc(-40 * var(--s));
    background: repeating-conic-gradient(from 0deg, rgba(var(--cx-live-rgb), 0.16) 0 4deg, transparent 4deg 15deg);
    -webkit-mask: radial-gradient(circle, transparent 28%, #000 32%, transparent 68%);
    mask: radial-gradient(circle, transparent 28%, #000 32%, transparent 68%);
    animation: cx-burst-in 700ms var(--cx-ease) 1 both;
    pointer-events: none;
}
html.cx-v2-success .cx-success.is-review .cx-success__burst {
    background: repeating-conic-gradient(from 0deg, rgba(205, 166, 255, 0.16) 0 4deg, transparent 4deg 15deg);
}
html.cx-v2-success .cx-success__sigil {
    animation: cx-success-land 520ms var(--cx-ease-spring) 1 both;
}
html.cx-v2-success .cx-success__sigil.is-speaking {
    animation: cx-sigil-speak 900ms ease-in-out infinite;
    filter: drop-shadow(0 0 calc(22 * var(--s)) rgba(var(--rgb), 0.85));
}
html.cx-v2-success .cx-stage--success .cx-go {
    cursor: pointer;
    animation: cx-go-in 360ms var(--cx-ease) 1 both;
}
html.cx-v2-success .cx-stage--success .cx-go:focus-visible {
    outline: calc(3 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(4 * var(--s));
}
@media (hover: hover) {
    html.cx-v2-success .cx-stage--success .cx-go:hover {
        filter: brightness(1.1);
    }
}

/* -------------------------------------------------------- transitions -- */
@keyframes cx-panel-rise {
    from {
        opacity: 0;
        transform: translateY(calc(14 * var(--s)));
    }
}
@keyframes cx-screen-in {
    from {
        opacity: 0;
    }
}
@keyframes cx-seg-fill {
    from {
        transform: scaleX(0.15);
    }
}
@keyframes cx-seg-light {
    from {
        filter: brightness(2.4);
    }
}
/* phase start: the v2 panel rises into place as the content fades up
   (training.enterContent). backwards fill: nothing is left on the panel
   once the 360 ms are over. */
html.cx-v2-transitions #training-content.cx-enter > .cx-stage > .cx-panel {
    animation: cx-panel-rise 360ms var(--cx-ease) 1 backwards;
}
/* platformer -> lesson: the lesson fades in over the platformer, which is
   fading out underneath (styles.css .screen), instead of a hard cut */
html.cx-v2-transitions #training-screen.active {
    animation: cx-screen-in 300ms ease 1;
}
/* rail: a finished segment fills green from the left, the new current
   segment flares once (hud.renderRail marks a changed segment data-anim) */
html.cx-v2-transitions .cx-rail__seg[data-anim='fill'] {
    transform-origin: left center;
    animation: cx-seg-fill 360ms var(--cx-ease) 1;
}
html.cx-v2-transitions .cx-rail__seg[data-anim='light'] {
    animation: cx-seg-light 600ms ease-out 1;
}

/* -------------------------------------------------------------- pause -- */
/* codex or settings open over a lesson: everything animated stops with it
   (lessonPause adds .cx-paused; timers and audio pause in JS) */
html.cx-v2-codex #training-screen.cx-paused *,
html.cx-v2-codex #training-screen.cx-paused *::before,
html.cx-v2-codex #training-screen.cx-paused *::after,
html.cx-v2-settings #training-screen.cx-paused *,
html.cx-v2-settings #training-screen.cx-paused *::before,
html.cx-v2-settings #training-screen.cx-paused *::after {
    animation-play-state: paused !important;
}

/* -------------------------------------------------------------- codex -- */
@keyframes cx-codex-in {
    from {
        opacity: 0;
    }
}
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded {
    position: fixed;
    inset: 0;
    transform: none;
    z-index: 100;
    display: block;
    background: radial-gradient(ellipse at 50% 45%, #0a1826, #02060b 78%);
    font-family: var(--cx-font);
    animation: cx-codex-in 200ms var(--cx-ease) 1;
    /* the legacy sidebar animates every property over 0.3 s; opened from
       the platformer (tab visible) the plate was measured mid-growth */
    transition: none;
}
/* no "CODEBREAKER" header; the tab gives way to the close while open */
html.cx-v2-codex #codex-sidebar.cx-codex .codex-header {
    display: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-toggle {
    display: none;
}
/* the slot: everything under the 80-design-px band */
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-panel {
    position: absolute;
    left: calc(28 * var(--s));
    right: calc(28 * var(--s));
    top: calc(80 * var(--s));
    bottom: calc(44 * var(--s));
    width: auto;
    height: auto;
    max-height: none;
    padding: 0;
    opacity: 1;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    display: grid;
    place-items: center;
    overflow: visible;
    transition: none;
}
/* the plate: Kelsey's painted codex (glowing hex underlays, light trails),
   letterboxed to the slot by fitScene (--scene-w / --scene-h) */
html.cx-v2-codex #codex-sidebar.cx-codex .codex-grid.cx-codex__plate {
    position: relative;
    width: var(--scene-w, 100%);
    height: var(--scene-h, auto);
    margin: 0;
    border-radius: calc(10 * var(--s));
    background-image: url('images/codex_grid.jpg');
    background-size: 100% 100%;
    background-repeat: no-repeat;
    box-shadow: 0 calc(20 * var(--s)) calc(60 * var(--s)) rgba(0, 0, 0, 0.6);
}
/* one close, in the codex button's slot of the HUD (16 pad + 44 gear +
   22 gap + half the 67-wide codex hex - half the 58 close = 86.5) */
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-close-tab.cx-btn {
    --b: calc(58 * var(--s));
    position: absolute;
    left: auto;
    top: calc(11 * var(--s));
    right: calc(86.5 * var(--s));
    transform: none;
    width: var(--b);
    height: var(--b);
    padding: 0;
    border: 0;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 0;
    color: var(--cx-live);
    background: radial-gradient(circle at 50% 30%, rgba(22, 46, 68, 0.97), rgba(6, 13, 21, 0.97));
    cursor: pointer;
    z-index: 110;
}
/* collapsed: no close at all (.cx-btn's display: inline-grid used to beat the
   legacy .codex-close-tab display: none, leaving a stray X beside the codex
   tab on the street / Night Lamp; BUGS CODEX-X-1) */
html.cx-v2-codex #codex-sidebar.cx-codex:not(.codex-expanded) .codex-close-tab.cx-btn {
    display: none;
}
/* (the legacy .codex-close-tab:hover swaps the ring for a flat glow; the
   pointer is often right there, having just clicked the codex button) */
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-close-tab.cx-btn,
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-close-tab.cx-btn:hover {
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) rgba(var(--cx-live-rgb), 0.75),
        0 0 calc(14 * var(--s)) rgba(var(--cx-live-rgb), 0.3),
        0 calc(6 * var(--s)) calc(16 * var(--s)) rgba(0, 0, 0, 0.45);
    background: radial-gradient(circle at 50% 30%, rgba(22, 46, 68, 0.97), rgba(6, 13, 21, 0.97));
}
html.cx-v2-codex #codex-sidebar.cx-codex.codex-expanded .codex-close-tab.cx-btn:active {
    transform: scale(0.93);
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-close-tab.cx-btn:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}
/* a cell: the legacy .codex-hex box (7% x 11% at hexPositions) holding one
   .cx-sigil. The legacy clip-path / fill / pulse are switched off. */
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex.cx-codex__hex {
    position: absolute;
    width: 7%;
    height: 11%;
    padding: 0;
    border: 0;
    margin: 0;
    display: grid;
    place-items: center;
    background: none;
    clip-path: none;
    box-shadow: none;
    text-shadow: none;
    animation: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex.is-locked {
    cursor: default;
    pointer-events: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex .cx-sigil {
    --h: calc(var(--scene-h, 536px) * 0.118);
    transition:
        transform var(--cx-t-state) var(--cx-ease),
        filter var(--cx-t-state) var(--cx-ease);
}
/* the painted underlay glows around each collected sigil: a lighter,
   slightly see-through fill instead of the solid panel fill */
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex .cx-sigil::after {
    background: radial-gradient(circle at 50% 35%, rgba(18, 52, 78, 0.9), rgba(5, 14, 24, 0.82) 75%);
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex.is-selected .cx-sigil {
    transform: scale(1.1);
    filter: drop-shadow(0 0 calc(14 * var(--s)) rgba(var(--rgb), 0.9));
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-sigil.is-speaking {
    animation: cx-sigil-speak 900ms ease-in-out infinite;
    filter: drop-shadow(0 0 calc(18 * var(--s)) rgba(var(--rgb), 1));
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex:focus-visible {
    outline: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex:focus-visible .cx-sigil {
    filter: drop-shadow(0 0 calc(3 * var(--s)) #fff) drop-shadow(0 0 calc(10 * var(--s)) rgba(var(--rgb), 1));
}
@media (hover: hover) {
    html.cx-v2-codex #codex-sidebar.cx-codex .codex-hex:not(.is-locked):hover .cx-sigil {
        transform: scale(1.08);
        filter: drop-shadow(0 0 calc(14 * var(--s)) rgba(var(--rgb), 0.85));
    }
    html.cx-v2-codex #codex-sidebar.cx-codex .cx-btn:hover,
    html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__docksigil:hover {
        filter: brightness(1.18);
    }
}
/* page 2: chevrons on the plate's left/right edges, two pips centred in the
   band above it (where the HUD rail sits on a lesson screen) */
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page {
    position: absolute;
    top: 50%;
    z-index: 5;
    padding: 0;
    border: 0;
    cursor: pointer;
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page[hidden] {
    display: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page--prev {
    left: 0;
    transform: translate(-40%, -50%);
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page--next {
    right: 0;
    transform: translate(40%, -50%);
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page--prev:active {
    transform: translate(-40%, -50%) scale(0.93);
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__page--next:active {
    transform: translate(40%, -50%) scale(0.93);
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__pips {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 18 * var(--s));
    transform: translateX(-50%);
    z-index: 5;
}
/* the dock: the chosen sigil (replays its sound) + "again" (starts the
   review), on the plate's bottom edge, never over other hexes */
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__dock {
    position: absolute;
    left: 50%;
    bottom: calc(-26 * var(--s));
    transform: translateX(-50%);
    z-index: 6;
    display: flex;
    align-items: center;
    gap: calc(22 * var(--s));
    padding: calc(10 * var(--s)) calc(20 * var(--s)) calc(10 * var(--s)) calc(16 * var(--s));
    border-radius: 999px;
    background: rgba(5, 11, 18, 0.96);
    box-shadow:
        inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-live-rgb), 0.4),
        0 calc(10 * var(--s)) calc(30 * var(--s)) rgba(0, 0, 0, 0.6);
    animation: cx-go-in 240ms var(--cx-ease) 1 both;
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__docksigil {
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
    display: grid;
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__dock .cx-sigil.is-speaking {
    animation: none;
}
html.cx-v2-codex #codex-sidebar.cx-codex .cx-codex__again {
    padding: 0;
    border: 0;
    cursor: pointer;
}

/* ----------------------------------------------------------- settings -- */
/* For parents: a plain slate, text allowed, deliberately not a game screen */
html.cx-v2-settings #settings-modal.cx-settings {
    background: rgba(2, 5, 9, 0.8);
    font-family: var(--cx-font);
}
html.cx-v2-settings #settings-modal.cx-settings .settings-content {
    width: calc(470 * var(--s));
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 32px);
    overflow: auto;
    padding: calc(28 * var(--s)) calc(32 * var(--s));
    border: 0;
    border-radius: calc(14 * var(--s));
    background: #111a24;
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.08),
        0 30px 80px rgba(0, 0, 0, 0.7);
    color: #d4dee8;
    -webkit-font-smoothing: antialiased;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-close {
    top: calc(12 * var(--s));
    right: calc(12 * var(--s));
    width: calc(44 * var(--s));
    height: calc(44 * var(--s));
    padding: 0;
    border: 0;
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: #b8c6d3;
    background: rgba(255, 255, 255, 0.06);
    font-size: 0;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-close svg,
html.cx-v2-settings #settings-modal.cx-settings .settings-copy-btn svg {
    width: 50%;
    height: 50%;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-section {
    margin-bottom: 0;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-label {
    margin: 0 0 calc(6 * var(--s));
    font-size: calc(13 * var(--s));
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #8fa2b4;
}
html.cx-v2-settings #settings-modal.cx-settings .cx-settings__hint {
    margin: 0 0 calc(12 * var(--s));
    font-size: calc(15 * var(--s));
    line-height: 1.45;
    color: #b8c6d3;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-code-display {
    justify-content: space-between;
    gap: calc(12 * var(--s));
    margin-bottom: calc(24 * var(--s));
    padding: calc(12 * var(--s)) calc(12 * var(--s)) calc(12 * var(--s)) calc(18 * var(--s));
    border-radius: calc(10 * var(--s));
    background: #0a1119;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
html.cx-v2-settings #settings-modal.cx-settings #current-save-code {
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 600;
    font-size: calc(30 * var(--s));
    letter-spacing: 0.22em;
    color: #7ff3c0;
    text-shadow: none;
    user-select: text;
    -webkit-user-select: text;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-copy-btn {
    flex: none;
    width: calc(44 * var(--s));
    height: calc(44 * var(--s));
    padding: 0;
    border: 0;
    border-radius: calc(10 * var(--s));
    display: grid;
    place-items: center;
    color: #b8c6d3;
    background: rgba(255, 255, 255, 0.07);
    font-size: 0;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-copy-btn.copied {
    color: #7ff3c0;
    background: rgba(127, 243, 192, 0.18);
}
html.cx-v2-settings #settings-modal.cx-settings .settings-code-input {
    gap: calc(10 * var(--s));
}
html.cx-v2-settings #settings-modal.cx-settings #save-code-input {
    flex: 1 1 0;
    min-width: 0;
    height: calc(48 * var(--s));
    padding: 0 calc(14 * var(--s));
    border-radius: calc(10 * var(--s));
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: #0a1119;
    color: #e8f0f7;
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 500;
    font-size: calc(20 * var(--s));
    letter-spacing: 0.2em;
}
html.cx-v2-settings #settings-modal.cx-settings #save-code-input:focus {
    border-color: rgba(var(--cx-live-rgb), 0.7);
}
html.cx-v2-settings #settings-modal.cx-settings .settings-apply-btn {
    flex: none;
    width: calc(96 * var(--s));
    height: calc(48 * var(--s));
    padding: 0;
    border: 0;
    border-radius: calc(10 * var(--s));
    background: #2a7fa0;
    color: #fff;
    font-family: var(--cx-font);
    font-weight: 600;
    font-size: calc(15 * var(--s));
    cursor: pointer;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-error {
    margin-top: calc(8 * var(--s));
    font-size: calc(13 * var(--s));
    color: #ff8fa6;
}
html.cx-v2-settings #settings-modal.cx-settings .settings-divider {
    height: 1px;
    margin: calc(20 * var(--s)) 0 calc(16 * var(--s));
    background: rgba(255, 255, 255, 0.08);
}
html.cx-v2-settings #settings-modal.cx-settings .settings-info-text {
    font-size: calc(12.5 * var(--s));
    line-height: 1.55;
    color: #7a8c9c;
}
@media (hover: hover) {
    html.cx-v2-settings #settings-modal.cx-settings .settings-close:hover,
    html.cx-v2-settings #settings-modal.cx-settings .settings-copy-btn:hover {
        background: rgba(255, 255, 255, 0.12);
        color: #e8f0f7;
        border-color: transparent;
    }
    html.cx-v2-settings #settings-modal.cx-settings .settings-apply-btn:hover {
        background: #3391b5;
    }
}

/* ======================================================================
   v2 blending and words (UI step 5, DESIGN.md §5.4 + §5.1 rule briefings):
   the blend machine (html.cx-v2-blend), comprehension (cx-v2-comprehension),
   try saying it (cx-v2-trysaying) and the rule briefings (cx-v2-rules).
   ----------------------------------------------------------------------
   Markup: src/ui/blendMachine.js, src/ui/wordStrip.js, src/ui/wordScene.js,
   src/ui/trySaying.js, src/ui/ruleBriefings.js. Tappable letters keep their
   legacy class on an outer <button class="winch-letter|blend-letter cx-hit">
   (ids, data-index, data-letter), so the first rules strip what styles.css
   gives those classes and only the inner .cx-tile is styled.
   ====================================================================== */
@keyframes cx-crank-turn {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}
@keyframes cx-crank-bob {
    0%, 100% { transform: translate(-50%, 0); }
    50% { transform: translate(-50%, calc(-6 * var(--s))); }
}
@keyframes cx-say-drain {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: 100; }
}
@keyframes cx-sneak-in {
    from { opacity: 0; transform: translateX(calc(180 * var(--s))); }
    to { opacity: 1; transform: none; }
}
@keyframes cx-mark-in {
    from { opacity: 0; transform: scale(0.4); }
    to { opacity: 1; transform: none; }
}
@keyframes cx-outline-beacon {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

html.cx-v2-blend .cx-winch .winch-letter.cx-hit,
html.cx-v2-comprehension .cx-comp .blend-letter.cx-hit {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: inline-flex;
    flex: none;
    width: auto;
    height: auto;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    font-style: normal;
    line-height: 1;
    text-transform: none;
    text-shadow: none;
    transform: none;
    animation: none;
    transition: none;
    opacity: 1;
    filter: none;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
}
html.cx-v2-blend .cx-winch .winch-letter.cx-hit:active > .cx-tile:not(.is-sounding),
html.cx-v2-comprehension .cx-comp .blend-letter.cx-hit:active > .cx-tile:not(.is-sounding) {
    transform: scale(0.94);
}
html.cx-v2-blend .cx-winch .winch-letter.cx-hit:focus-visible,
html.cx-v2-comprehension .cx-comp .blend-letter.cx-hit:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}
/* a silent e can still be the next letter: its dashed frame turns amber */
.cx-tile.is-silent.is-next::after,
.cx-tile.is-silent.is-hint::after {
    border-color: var(--cx-look);
}

/* the squish for tiles wrapped in hit buttons: one capsule, one word
   (the component's :first-child / :last-child rules would match every
   wrapped tile, so they are restated per button) */
.cx-word.is-blended > .cx-hit > .cx-tile {
    border-radius: 0;
    padding: 0 calc(var(--t) * 0.05);
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct);
}
.cx-word.is-blended > .cx-hit:first-of-type > .cx-tile {
    padding-left: calc(var(--t) * 0.32);
    border-radius: calc(var(--t) * 0.17) 0 0 calc(var(--t) * 0.17);
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct),
        inset calc(2.5 * var(--s)) 0 0 var(--cx-correct);
}
.cx-word.is-blended > .cx-hit:last-of-type > .cx-tile {
    padding-right: calc(var(--t) * 0.32);
    border-radius: 0 calc(var(--t) * 0.17) calc(var(--t) * 0.17) 0;
    box-shadow:
        inset 0 calc(2.5 * var(--s)) 0 var(--cx-correct),
        inset 0 calc(-2.5 * var(--s)) 0 var(--cx-correct),
        inset calc(-2.5 * var(--s)) 0 0 var(--cx-correct);
}
.cx-word.is-blended > .cx-hit:first-of-type:last-of-type > .cx-tile {
    border-radius: calc(var(--t) * 0.17);
    box-shadow: inset 0 0 0 calc(2.5 * var(--s)) var(--cx-correct);
}
.cx-word.is-blended .cx-tile.is-silent::after {
    display: none;
}
.cx-word.is-blended > .cx-hit {
    cursor: default;
}

/* magic-e link: dotted arc under the tiles from the silent e to the vowel
   (wordStrip.drawMagicLink lays the path out in real pixels) */
.cx-word > .cx-link {
    position: absolute;
    left: 0;
    top: calc(100% - 2 * var(--s));
    width: 100%;
    height: calc(40 * var(--s));
    overflow: visible;
    pointer-events: none;
    transition: opacity var(--cx-t-state) var(--cx-ease);
}
.cx-word > .cx-link path {
    fill: none;
    stroke: var(--cx-ink-magic);
    stroke-width: calc(2.5 * var(--s));
    stroke-dasharray: calc(2 * var(--s)) calc(6 * var(--s));
    stroke-linecap: round;
    filter: drop-shadow(0 0 calc(4 * var(--s)) rgba(255, 138, 61, 0.7));
}
.cx-word.is-blended > .cx-link {
    opacity: 0;
}

/* ---------------------------------------------------------- the winch -- */
html.cx-v2-blend .cx-stage--winch .cx-winch-panel {
    padding-left: calc(44 * var(--s));
    padding-right: calc(44 * var(--s));
}
html.cx-v2-blend .cx-winch.winch-machine {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(22 * var(--s));
    margin: 0;
    padding-top: calc(40 * var(--s)); /* room for the next-letter chevron */
}
.cx-winch__rail {
    position: absolute;
    left: calc(14 * var(--s));
    right: calc(40 * var(--s));
    top: calc(50% + 20 * var(--s));
    height: calc(8 * var(--s));
    transform: translateY(-50%);
    border-radius: calc(4 * var(--s));
    background: linear-gradient(
        90deg,
        rgba(var(--cx-live-rgb), 0.15),
        rgba(var(--cx-live-rgb), 0.45),
        rgba(var(--cx-live-rgb), 0.15)
    );
    box-shadow: 0 0 calc(10 * var(--s)) rgba(var(--cx-live-rgb), 0.3);
    pointer-events: none;
}
.cx-winch__cap {
    position: relative;
    flex: none;
    width: calc(28 * var(--s));
    height: calc(72 * var(--s));
    border-radius: calc(8 * var(--s));
    background: linear-gradient(180deg, #1b3550, #0a1622);
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.45);
}
/* rail ends and the legacy arrow: kept for their ids, drawn by the rail
   and by the chevron on the next tile */
.cx-winch__arm,
.cx-winch__arrow {
    display: none;
}
html.cx-v2-blend .cx-winch .cx-word.winch-letters {
    position: relative;
    display: flex;
    gap: calc(12 * var(--s));
    transition: gap var(--cx-t-enter) var(--cx-ease);
}
html.cx-v2-blend .cx-winch .cx-word.winch-letters.is-blended {
    gap: 0;
}
.cx-crank {
    --b: calc(84 * var(--s));
    cursor: pointer;
}
.cx-crank svg {
    width: 64%;
    height: 64%;
}
.cx-crank.is-idle {
    opacity: 0.45;
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.35);
}
.cx-crank.is-next {
    --rgb: var(--cx-look-rgb);
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) var(--cx-look),
        0 0 calc(26 * var(--s)) rgba(var(--cx-look-rgb), 0.6);
}
.cx-crank.is-next::before {
    content: '';
    position: absolute;
    left: 50%;
    bottom: calc(100% + 10 * var(--s));
    width: calc(30 * var(--s));
    height: calc(22 * var(--s));
    background: var(--cx-look);
    clip-path: polygon(0 0, 100% 0, 50% 100%);
    filter: drop-shadow(0 0 calc(6 * var(--s)) rgba(var(--cx-look-rgb), 0.8));
    transform: translate(-50%, 0);
    animation: cx-crank-bob 1.2s var(--cx-ease) infinite;
}
.cx-crank.is-turning {
    cursor: default;
}
.cx-crank.is-turning svg {
    animation: cx-crank-turn 700ms var(--cx-ease) 1 both;
}
.cx-winch__dock:empty {
    display: none;
}
/* sight word (Passover): one tile, no crank */
.cx-winch--sight {
    padding-top: 0;
}

/* -------------------------------------------------------- comprehension -- */
html.cx-v2-comprehension #training-content > .cx-comp.comprehension-test {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    min-height: 0;
    width: 100%;
    height: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(16 * var(--s));
    padding: calc(4 * var(--s)) calc(32 * var(--s)) calc(24 * var(--s));
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
.cx-comp__top {
    flex: none;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: end;
    padding-top: calc(38 * var(--s)); /* room for the next-letter chevron */
}
.cx-comp__top > .cx-mode {
    justify-self: end;
}
html.cx-v2-comprehension .cx-comp .blend-word-container.comprehension-word {
    display: block;
    margin: 0;
}
html.cx-v2-comprehension .cx-comp .cx-word.blend-word {
    position: relative;
    display: flex;
    gap: calc(12 * var(--s));
    z-index: auto;
}
.cx-comp__slot {
    flex: 1 1 auto;
    align-self: stretch;
    min-height: 0;
    display: grid;
    place-items: center;
}
html.cx-v2-comprehension .cx-comp .cx-scene.scene-container {
    max-width: none;
    border: 0;
    cursor: crosshair;
    transition: opacity var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-comprehension .cx-comp .cx-scene.scene-container.is-explore {
    cursor: zoom-in;
}
html.cx-v2-comprehension .cx-comp .cx-scene > img.scene-image {
    display: block;
    width: 100%;
    height: 100%;
    filter: none;
}
/* the image is fitted (fitScene) before it shows: no flash at the default size */
.cx-scene.is-fitting {
    opacity: 0;
}
/* read-through: taps are not answers yet ("not yet", not silently ignored) */
.cx-scene.is-standby::after {
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.25);
}
.cx-scene.is-standby {
    cursor: default;
}
/* overlay shapes, in image percent (viewBox 0 0 100 100, no aspect lock) */
.cx-scene__overlay .cx-hot {
    fill: rgba(0, 200, 255, 0.18);
    stroke: rgba(var(--cx-live-rgb), 0.85);
    stroke-width: calc(2.5 * var(--s));
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
.cx-scene__overlay .cx-spot-dim {
    fill: rgba(2, 6, 10, 0.66);
}
.cx-scene__overlay .cx-spot-edge {
    fill: none;
    stroke: var(--cx-look);
    stroke-width: calc(3 * var(--s));
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    filter: drop-shadow(0 0 calc(4 * var(--s)) var(--cx-look));
    animation: cx-outline-beacon 1.1s ease-in-out 3;
}
.cx-scene__overlay .cx-found-edge {
    fill: rgba(var(--cx-correct-rgb), 0.12);
    stroke: var(--cx-correct);
    stroke-width: calc(3 * var(--s));
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    filter: drop-shadow(0 0 calc(4 * var(--s)) var(--cx-correct));
}
/* markers: the component .cx-mark, clamped inside the frame (never cut) */
html.cx-v2-comprehension .cx-scene .click-marker.cx-mark {
    width: calc(56 * var(--s));
    height: calc(56 * var(--s));
    margin: calc(-28 * var(--s)) 0 0 calc(-28 * var(--s));
    font-size: 0;
    transform: none;
    animation: cx-mark-in 220ms var(--cx-ease-spring) both;
    pointer-events: none;
}
html.cx-v2-comprehension .cx-scene .cx-mark svg {
    width: calc(26 * var(--s));
    height: calc(26 * var(--s));
}
html.cx-v2-comprehension .cx-scene .click-marker.cx-mark.correct {
    color: var(--cx-correct);
    background: rgba(0, 60, 40, 0.6);
    box-shadow: 0 0 0 calc(3 * var(--s)) currentColor, 0 0 calc(18 * var(--s)) currentColor;
}
html.cx-v2-comprehension .cx-scene .click-marker.cx-mark.incorrect {
    color: var(--cx-wrong);
    background: rgba(70, 0, 20, 0.6);
    box-shadow: 0 0 0 calc(3 * var(--s)) currentColor, 0 0 calc(18 * var(--s)) currentColor;
}
.cx-mark.is-probe {
    pointer-events: none;
    animation: cx-mark-in 220ms var(--cx-ease-spring) both;
}
html.cx-v2-comprehension .cx-comp .cx-mode > button {
    padding: 0;
    background: none;
    transition: background var(--cx-t-state) var(--cx-ease), color var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-comprehension .cx-comp .cx-mode > button.is-on {
    color: #04151d;
    background: var(--cx-live);
}
html.cx-v2-comprehension .cx-comp .cx-mode.is-disabled {
    opacity: 0.35;
    pointer-events: none;
}
@media (hover: hover) {
    html.cx-v2-comprehension .cx-comp .cx-mode > button:not(.is-on):hover {
        background: rgba(var(--cx-live-rgb), 0.14);
    }
}

/* -------------------------------------------------------- try saying it -- */
/* The HUD stays (one policy, DESIGN.md §3.13): the per-sound art fills the
   screen behind it, the "your turn" glyph and the voice panel sit on the
   right, beside the sigil the agent holds, never on the agent. */
html.cx-v2-trysaying #training-content > .cx-say.try-saying-screen {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    display: block;
    padding: 0;
    background: none;
    z-index: auto;
    font-family: var(--cx-font);
    color: var(--cx-ink);
}
/* full-bleed, behind the HUD band too (position: fixed; no transformed ancestor) */
.cx-say__bg,
.cx-rule__bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
}
.cx-say__side {
    position: absolute;
    top: 0;
    bottom: 0;
    right: calc(28 * var(--s));
    width: calc(230 * var(--s));
    display: grid;
    place-items: center;
    z-index: 1;
}
.cx-say__side > * {
    grid-area: 1 / 1;
}
.cx-say__speak {
    position: relative;
    width: calc(150 * var(--s));
    height: calc(150 * var(--s));
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: rgba(5, 11, 18, 0.85);
    transition: opacity var(--cx-t-enter) var(--cx-ease);
}
.cx-say__speak.is-hidden {
    opacity: 0;
}
.cx-say__speak > svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
}
.cx-say__speak circle {
    fill: none;
    stroke-width: 5;
}
.cx-say__speak .t {
    stroke: rgba(var(--cx-line-rgb), 0.18);
}
.cx-say__speak .r {
    stroke: var(--cx-live);
    stroke-dasharray: 100 100;
    stroke-dashoffset: 0;
    stroke-linecap: round;
    filter: drop-shadow(0 0 4px var(--cx-live));
}
.cx-say__speak.is-draining .r {
    animation: cx-say-drain var(--cx-say-ms, 3000ms) linear 1 forwards;
}
.cx-say__speak > span {
    display: grid;
    color: #fff;
}
.cx-say__speak > span svg {
    width: calc(70 * var(--s));
    height: calc(70 * var(--s));
}
html.cx-v2-trysaying .cx-say .pitch-options.cx-say__voices {
    display: flex;
    flex-direction: column-reverse; /* DOM order low, normal, high; high shows on top (pitch = height) */
    align-items: center;
    gap: calc(18 * var(--s));
    padding: calc(28 * var(--s)) calc(26 * var(--s)) calc(62 * var(--s));
    opacity: 0;
    visibility: hidden;
    transform: translateY(calc(12 * var(--s)));
    transition:
        opacity var(--cx-t-enter) var(--cx-ease),
        transform var(--cx-t-enter) var(--cx-ease),
        visibility 0s linear var(--cx-t-enter);
}
html.cx-v2-trysaying .cx-say .pitch-options.cx-say__voices.visible {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition:
        opacity var(--cx-t-enter) var(--cx-ease),
        transform var(--cx-t-enter) var(--cx-ease),
        visibility 0s;
}
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice {
    /* restated so the legacy .pitch-btn.playing / .selected fills can't win */
    background: linear-gradient(180deg, rgba(21, 41, 61, 0.97), rgba(8, 17, 27, 0.97));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--rgb), var(--edge)),
        0 0 calc(22 * var(--s)) rgba(var(--rgb), var(--halo)),
        0 calc(10 * var(--s)) calc(26 * var(--s)) rgba(0, 0, 0, 0.5);
    min-width: 0;
    width: calc(180 * var(--s));
    height: calc(92 * var(--s));
    padding: 0;
    border: 0;
    border-radius: var(--cx-r-lg);
    font-size: 0;
    animation: none;
    transform: none;
    transition:
        transform var(--cx-t-state) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice svg {
    width: calc(120 * var(--s));
    height: calc(64 * var(--s));
}
/* listen: the demo and the question play; the voices are not live yet */
html.cx-v2-trysaying .cx-say .pitch-options.is-standby .pitch-btn.cx-choice:not(.playing) {
    --edge: 0.42;
    --halo: 0;
}
html.cx-v2-trysaying .cx-say .pitch-options.is-standby .pitch-btn.cx-choice {
    cursor: default;
}
/* the demo names each voice in turn */
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice.playing {
    --edge: 1;
    --halo: 0.6;
    transform: translateY(calc(-4 * var(--s)));
}
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice.selected {
    --rgb: var(--cx-pick-rgb);
    --edge: 1;
    --halo: 0.35;
}
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice.selected svg {
    color: #fff;
}
html.cx-v2-trysaying .cx-say .pitch-btn.cx-choice:active {
    transform: scale(0.96);
}
/* go: rendered only after a pick (never invisible-but-live); no hover change */
html.cx-v2-trysaying .cx-say .try-saying-continue.cx-go {
    position: relative;
    inset: auto;
    width: var(--b);
    height: var(--b);
    border: 0;
    font-size: 0;
    color: #04151d;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: none;
    transition: opacity var(--cx-t-state) var(--cx-ease);
    z-index: auto;
}
html.cx-v2-trysaying .cx-say .try-saying-continue.cx-go.visible {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
}
html.cx-v2-trysaying .cx-say .try-saying-continue.cx-go.visible:hover {
    background: radial-gradient(circle at 50% 30%, #7ff1ff, var(--cx-live) 60%, #00a9c9);
    transform: none;
}
html.cx-v2-trysaying .cx-say .try-saying-continue.cx-go.visible:active {
    transform: scale(0.93);
}

/* ------------------------------------------------------- rule briefings -- */
html.cx-v2-rules #training-content > .cx-rule.rule-interstitial {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    width: 100%;
    height: auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    font-family: var(--cx-font);
    color: var(--cx-ink);
}
/* the backdrop is mood, the tiles are the teaching. The briefing art is
   already dark with its figures in the outer thirds (ART-INVENTORY.md §4),
   so the scrim is flat (no edge vignette that would bury them) plus the
   HUD's top band. */
.cx-rule__bg::after {
    background:
        linear-gradient(to bottom, rgba(4, 7, 12, 0.78) 0, rgba(4, 7, 12, 0) calc(120 * var(--s))),
        rgba(4, 7, 12, 0.22);
}
.cx-rule > .cx-stage {
    position: relative;
    z-index: 1;
}
.cx-rule-panel {
    min-width: calc(640 * var(--s));
    min-height: calc(420 * var(--s));
    display: grid;
    place-items: center;
}
.cx-rule__stack {
    display: grid;
    place-items: center;
    gap: calc(52 * var(--s));
}
.cx-rule__row {
    position: relative;
    transition: opacity var(--cx-t-enter) var(--cx-ease);
}
.cx-rule__row.is-before {
    opacity: 0.45;
}
/* backwards fill: once in, the row's own opacity applies (is-before dims it) */
.cx-rule__row.is-entering {
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) backwards;
}
.cx-tile.is-sneaking {
    animation: cx-sneak-in 900ms var(--cx-ease) both;
}

/* ======================================================================
   v2 agent phases (UI step 6, DESIGN.md §5.3): badge check, secret
   operatives, sound match, chase.
   ----------------------------------------------------------------------
   Markup: src/ui/agentPhases.js (the agent card, chips, gate). The legacy
   classes the tests and recipes use (.badge-check-area, .agent-card,
   .agent-badge, .gate-btn, .operative, .sound-agent, #target-drop-zone,
   .chase-*) stay on the outer elements; the rules below neutralise their
   old styling. Each part is scoped to its html.cx-v2-<part> class.
   ====================================================================== */
/* The full-scene rule (DESIGN.md §3.13): the room fills the whole screen,
   behind the HUD band too. position: fixed, so it must never sit inside a
   transformed element (the v2 panel rise transforms .cx-stage > .cx-panel
   only). */
.cx-fullbg {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
}

/* ------------------------------------------------------------- the gate -- */
html.cx-v2-badgecheck #training-content > .badge-check-area.cx-gate {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(22 * var(--s));
    padding: calc(20 * var(--s)) calc(32 * var(--s)) calc(24 * var(--s));
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
html.cx-v2-badgecheck .cx-gate__card {
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-left: calc(56 * var(--s));
    padding-right: calc(56 * var(--s));
    transition:
        transform var(--cx-t-enter) var(--cx-ease),
        opacity var(--cx-t-enter) var(--cx-ease);
}
/* the right call: admitted agents go on through the doorway (right, the art
   is mirrored so the doorway is on the admit side); rejected ones back out
   to the left */
/* the card slides first (green, readable), then fades in the last part of
   the 400 ms before the next agent arrives */
html.cx-v2-badgecheck .cx-gate__card.is-admit,
html.cx-v2-badgecheck .cx-gate__card.is-reject {
    transition:
        transform 380ms cubic-bezier(0.45, 0, 0.55, 1),
        opacity 170ms ease-in 200ms;
    opacity: 0;
}
html.cx-v2-badgecheck .cx-gate__card.is-admit {
    transform: translateX(calc(170 * var(--s))) scale(0.92);
}
html.cx-v2-badgecheck .cx-gate__card.is-reject {
    transform: translateX(calc(-150 * var(--s)));
}
@keyframes cx-gate-in {
    from {
        opacity: 0;
        transform: translateX(calc(-60 * var(--s)));
    }
}
html.cx-v2-badgecheck .cx-gate__card.is-entering {
    animation: cx-gate-in var(--cx-t-enter) var(--cx-ease) backwards;
}
html.cx-v2-badgecheck .cx-gate__card.is-shake {
    animation: cx-shake var(--cx-t-shake) var(--cx-ease);
}
html.cx-v2-badgecheck .cx-gate .cx-replay {
    cursor: pointer;
}
/* word-ending trials: the ending beside the replay node, 4 s */
html.cx-v2-badgecheck .cx-gate__targetslot {
    position: absolute;
    left: calc(100% + 18 * var(--s));
    top: 50%;
    transform: translateY(-50%);
}
html.cx-v2-badgecheck .cx-gate__target {
    display: flex;
    gap: calc(6 * var(--s));
    opacity: 0;
    transition: opacity 300ms var(--cx-ease);
}
html.cx-v2-badgecheck .cx-gate__target.is-on {
    opacity: 1;
}
/* legacy classes kept for tests and recipes, their old looks removed */
html.cx-v2-badgecheck .cx-gate .agent-queue {
    flex: none;
    width: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: auto;
}
html.cx-v2-badgecheck .cx-gate .agent-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(26 * var(--s));
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    transition: none;
    transform: none;
    opacity: 1;
    animation: none;
}
html.cx-v2-badgecheck .cx-gate .agent-badge {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    font-size: inherit;
    font-weight: inherit;
    color: inherit;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    text-transform: none;
    letter-spacing: normal;
}
html.cx-v2-badgecheck .cx-gate .cx-yourturn .cx-tile {
    overflow: hidden;
}
html.cx-v2-badgecheck .cx-gate .gate-controls {
    position: relative;
    z-index: auto;
    gap: calc(40 * var(--s));
}
html.cx-v2-badgecheck .cx-gate .gate-btn {
    padding: 0;
    border: 0;
    font-size: inherit;
    font-weight: inherit;
    font-family: var(--cx-font);
    color: rgb(var(--rgb));
    transition: transform var(--cx-t-press) var(--cx-ease);
}
html.cx-v2-badgecheck .cx-gate .gate-btn.reject {
    background: linear-gradient(180deg, rgba(21, 41, 61, 0.97), rgba(8, 17, 27, 0.97));
}
html.cx-v2-badgecheck .cx-gate .gate-btn.admit {
    background: linear-gradient(180deg, rgba(0, 70, 46, 0.95), rgba(4, 28, 22, 0.97));
}
html.cx-v2-badgecheck .cx-gate .gate-btn:active {
    transform: scale(0.95);
}
@media (hover: hover) {
    html.cx-v2-badgecheck .cx-gate .gate-btn:hover {
        filter: brightness(1.15);
    }
}
html.cx-v2-badgecheck .cx-gate .gate-btn:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}
/* the wrong call: the button pressed turns red for a moment */
html.cx-v2-badgecheck .cx-gate .gate-btn.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    animation: cx-shake var(--cx-t-shake) var(--cx-ease);
}

/* ---------------------------------------------------------- operatives -- */
html.cx-v2-operatives #training-content > .operative-grid.cx-ops {
    flex: 1 1 auto;
    align-self: stretch;
    width: 100%;
    height: 100%;
    min-height: 0;
    padding: 0;
    gap: 0;
    display: flex;
}
html.cx-v2-operatives .cx-ops__panel {
    padding: calc(40 * var(--s)) calc(56 * var(--s)) calc(84 * var(--s));
}
html.cx-v2-operatives .cx-ops .operatives-container {
    display: grid;
    grid-template-columns: repeat(3, auto);
    column-gap: calc(58 * var(--s));
    row-gap: calc(52 * var(--s));
    max-width: none;
}
/* the legacy .operative card (and its red "?" marked state) is only a hit
   area now: the agent card inside carries every state */
html.cx-v2-operatives .cx-ops .operative,
html.cx-v2-operatives .cx-ops .operative:hover,
html.cx-v2-operatives .cx-ops .operative.marked-spy {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: block;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    box-shadow: none;
    transform: none;
    transition: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
html.cx-v2-operatives .cx-ops .operative.marked-spy::after {
    content: none;
    display: none;
}
html.cx-v2-operatives .cx-ops .operative:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(6 * var(--s));
}
html.cx-v2-operatives .cx-ops .operative:active .cx-agent {
    transform: scale(0.96);
}
html.cx-v2-operatives .cx-ops .operative-submit.cx-go {
    padding: 0;
    border-radius: 50%;
    font-size: 0;
    background: radial-gradient(circle at 50% 30%, #7ff1ff, var(--cx-live) 60%, #00a9c9);
    cursor: pointer;
}
html.cx-v2-operatives .cx-ops .operative-submit.cx-go.is-disabled {
    background: rgba(var(--cx-live-rgb), 0.12);
    cursor: default;
}
html.cx-v2-operatives .cx-ops .operative-submit.cx-go:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(4 * var(--s));
}
@media (hover: hover) {
    html.cx-v2-operatives .cx-ops .operative-submit.cx-go:not(.is-disabled):hover {
        filter: brightness(1.1);
    }
}

/* --------------------------------------------------------- sound match -- */
html.cx-v2-soundmatch #training-content > .sound-match.cx-match {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    width: 100%;
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(74 * var(--s));
    padding: calc(20 * var(--s)) calc(32 * var(--s)) calc(28 * var(--s));
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
/* the destination: dashed ring, static; solid white only while an agent is
   being dragged (the one animation at a time) */
html.cx-v2-soundmatch .cx-match .target-drop-zone.cx-match__dock {
    position: relative;
    padding: calc(26 * var(--s));
    border: 0;
    border-radius: calc(40 * var(--s));
    background: rgba(5, 11, 18, 0.86);
    animation: none;
    transform: none;
    transition: none;
    cursor: default;
}
html.cx-v2-soundmatch .cx-match .target-drop-zone.cx-match__dock::after {
    content: none;
    display: none;
}
html.cx-v2-soundmatch .cx-match__dock::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: calc(3 * var(--s)) dashed rgba(var(--cx-line-rgb), 0.45);
    pointer-events: none;
    transition:
        border-color var(--cx-t-state) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-soundmatch .cx-match__dock.is-armed::before {
    border-color: rgba(255, 255, 255, 0.9);
    box-shadow: 0 0 calc(24 * var(--s)) rgba(255, 255, 255, 0.25);
}
html.cx-v2-soundmatch .cx-match__dock.is-over::before {
    border-style: solid;
    box-shadow: 0 0 calc(34 * var(--s)) rgba(255, 255, 255, 0.4);
}
html.cx-v2-soundmatch .cx-match__dock.is-correct::before {
    border-style: solid;
    border-color: var(--cx-correct);
    box-shadow: 0 0 calc(28 * var(--s)) rgba(var(--cx-correct-rgb), 0.45);
}
html.cx-v2-soundmatch .cx-match__dock.is-wrong::before {
    border-style: solid;
    border-color: var(--cx-wrong);
}
/* the silent letter: a plain tile (no legacy text-shadow, padding, border) */
html.cx-v2-soundmatch .cx-match .cx-tile.target-sigil-display {
    padding: 0;
    border: 0;
    text-shadow: none;
    text-transform: none;
    font-size: calc(var(--t) * 0.56);
}
html.cx-v2-soundmatch .cx-match .sound-agents.cx-match__row {
    display: flex;
    flex-wrap: nowrap;
    gap: calc(46 * var(--s));
    padding: calc(24 * var(--s)) calc(36 * var(--s));
    border-radius: calc(90 * var(--s));
    background: rgba(5, 11, 18, 0.7);
}
/* the legacy .sound-agent card is only the draggable hit area now */
html.cx-v2-soundmatch .cx-match .sound-agent,
html.cx-v2-soundmatch .cx-match .sound-agent:hover {
    position: relative;
    display: block;
    padding: 0;
    gap: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    box-shadow: none;
    cursor: grab;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    transition: none;
    animation: none;
}
html.cx-v2-soundmatch .cx-match .sound-agent:has(.is-dragging) {
    cursor: grabbing;
}
html.cx-v2-soundmatch .cx-match .sound-agent .cx-agent__portrait img {
    pointer-events: none;
    -webkit-user-drag: none;
}
html.cx-v2-soundmatch .cx-match .sound-agent.is-leaving {
    opacity: 0;
    transition: opacity 280ms var(--cx-ease);
}
/* the click path: a picked agent docks a check under its chin */
html.cx-v2-soundmatch .cx-match__commit {
    position: absolute;
    left: 50%;
    top: calc(100% + 16 * var(--s));
    transform: translateX(-50%);
    z-index: 3;
    cursor: pointer;
}
html.cx-v2-soundmatch .cx-match__commit[hidden] {
    display: none;
}
html.cx-v2-soundmatch .cx-match__commit:active {
    transform: translateX(-50%) scale(0.93);
}

/* --------------------------------------------------------------- chase -- */
/* The map sits in an aspect-locked frame (paths, cover and perspective data
   are % of the image) inside the lesson area, never under the HUD band; the
   same map, blurred, fills the whole screen behind it (DESIGN.md §3.13). */
html.cx-v2-chase #training-content > .chase-game.cx-chase {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    display: block;
    overflow: visible;
}
html.cx-v2-chase .cx-chase__slot {
    position: absolute;
    top: calc(4 * var(--s));
    left: calc(8 * var(--s));
    right: calc(8 * var(--s));
    bottom: calc(10 * var(--s));
    z-index: 1;
    display: grid;
    place-items: center;
}
html.cx-v2-chase .cx-chase .chase-scene.cx-scene {
    width: var(--scene-w, 960px);
    height: var(--scene-h, 523px);
    max-width: none;
    aspect-ratio: auto;
    background: #000;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}
html.cx-v2-chase .cx-chase .chase-scene.cx-scene.is-fitting {
    visibility: hidden;
}
html.cx-v2-chase .cx-chase .chase-scene > img {
    pointer-events: none;
    -webkit-user-drag: none;
}
html.cx-v2-chase .cx-chase .cx-dock-top.cx-chase__top {
    top: calc(26 * var(--s));
    transform: translate(-50%, 0);
    z-index: 20;
}
html.cx-v2-chase .cx-chase .cx-dock-bottom.cx-chase__bottom {
    bottom: calc(14 * var(--s));
    transform: translate(-50%, 0);
    z-index: 20;
}
html.cx-v2-chase .cx-chase .cx-replay {
    cursor: pointer;
}
/* runners: the one scene-level resolver takes the taps (48 px minimum, the
   nearer centre between overlapping runners), so the old invisible 15 px
   halo goes */
html.cx-v2-chase .cx-chase .chase-agent::after {
    content: none;
}
html.cx-v2-chase .cx-chase .chase-agent-badge.cx-chip {
    --c: max(calc(44 * var(--s)), calc(34 * var(--s) * var(--sc, 1)));
    position: absolute;
    left: 50%;
    top: -6%;
    bottom: auto;
    transform: translate(-50%, -50%);
    padding: 0 calc(6 * var(--s));
    font-size: calc(var(--c) * 0.62);
    font-weight: 700;
    color: #fff;
    background: #050b12;
    border-radius: calc(var(--c) * 0.28);
    text-transform: none;
    pointer-events: none;
    z-index: 2;
    transition:
        box-shadow var(--cx-t-state) var(--cx-ease),
        background var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-chase .cx-chase .chase-agent-badge.cx-chip[data-len='2'] {
    font-size: calc(var(--c) * 0.5);
}
html.cx-v2-chase .cx-chase .chase-agent-badge.cx-chip.is-correct {
    --rgb: var(--cx-correct-rgb);
    background: #053d27;
}
html.cx-v2-chase .cx-chase .chase-agent-badge.cx-chip.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    background: #3d0615;
}

/* ======================================================================
   v2 text phases (UI step 7, DESIGN.md §5.2, §5.4): message scan,
   transcribe, word chain.
   ----------------------------------------------------------------------
   Markup: src/ui/textPhases.js. The legacy classes the tests and recipes
   use (.scan-message, .scan-letter, .transcribe-letter, .transcribe-options,
   .word-chain-area, #chain-options, .frequency-btn, .transmission-letter)
   stay on the outer elements; the rules below neutralise their old
   styling. Each part is scoped to its html.cx-v2-<part> class.
   ====================================================================== */
html.cx-v2-transcribe .cx-hit,
html.cx-v2-wordchain .cx-hit {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: inline-flex;
    width: auto;
    height: auto;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    line-height: 1;
    text-transform: none;
    text-shadow: none;
    transform: none;
    animation: none;
    transition: none;
    opacity: 1;
    filter: none;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
}
html.cx-v2-transcribe .cx-hit:disabled,
html.cx-v2-wordchain .cx-hit:disabled {
    cursor: default;
}
html.cx-v2-transcribe .cx-hit:active > .cx-tile:not(.is-standby),
html.cx-v2-wordchain .cx-hit:active > .cx-tile:not(.is-standby):not(.is-correct) {
    transform: scale(0.94);
}
html.cx-v2-transcribe .cx-hit:focus,
html.cx-v2-wordchain .cx-hit:focus,
html.cx-v2-messagescan .scan-letter:focus {
    outline: none;
}
html.cx-v2-transcribe .cx-hit:focus-visible,
html.cx-v2-wordchain .cx-hit:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(3 * var(--s));
}

/* a tap while the prompt plays is held, and shows it: the key dips once
   (no colour: it is not wrong), the replay node bumps ("listen first") */
@keyframes cx-held {
    0% { transform: scale(1); opacity: 1; }
    20% { transform: scale(0.92); opacity: 0.6; }
    70% { transform: scale(0.96); opacity: 0.75; }
    100% { transform: scale(1); opacity: 1; }
}
@keyframes cx-listen {
    0%, 100% {
        transform: scale(1);
    }
    30% {
        transform: scale(1.16);
        box-shadow:
            inset 0 0 0 calc(2.5 * var(--s)) #fff,
            0 0 0 calc(5 * var(--s)) rgba(var(--cx-live-rgb), 0.55),
            0 0 calc(30 * var(--s)) rgba(var(--cx-live-rgb), 0.8);
    }
}
.cx-tile.is-held {
    animation: cx-held 600ms var(--cx-ease);
}
.cx-replay.is-listen {
    animation: cx-listen 800ms var(--cx-ease) 1;
}

/* ------------------------------------------------------------ message scan -- */
html.cx-v2-messagescan .cx-panel.cx-scan-panel {
    width: min(calc(860 * var(--s)), 100%);
    max-height: 100%;
    padding-left: calc(48 * var(--s));
    padding-right: calc(48 * var(--s));
}
html.cx-v2-messagescan .scan-message.cx-scan {
    display: flex;
    flex-direction: column;
    gap: calc(14 * var(--s));
    max-width: none;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}
html.cx-v2-messagescan .cx-scan__phrase {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    column-gap: calc(30 * var(--s));
    row-gap: calc(6 * var(--s));
    font-family: var(--cx-font);
    font-weight: 500;
    font-size: calc(50 * var(--s));
    line-height: 1.25;
    color: var(--cx-ink);
    transition:
        font-size var(--cx-t-enter) var(--cx-ease),
        color var(--cx-t-enter) var(--cx-ease);
}
html.cx-v2-messagescan .cx-scan__phrase.is-done {
    font-size: calc(30 * var(--s));
    color: var(--cx-ink-2);
    column-gap: calc(18 * var(--s));
}
html.cx-v2-messagescan .cx-scan__phrase:not(.is-done) {
    margin-top: calc(10 * var(--s));
    padding-top: calc(18 * var(--s));
    border-top: calc(1.5 * var(--s)) solid rgba(var(--cx-line-rgb), 0.14);
}
html.cx-v2-messagescan .cx-scan__phrase:first-child:not(.is-done) {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}
html.cx-v2-messagescan .cx-scan__phrase.is-entering {
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) both;
}
html.cx-v2-messagescan .cx-scan__word {
    display: inline-flex;
    white-space: nowrap;
}
/* a letter: plain text, no box; the hit area is wider than the glyph
   (::before), the underline carries the state (::after) */
html.cx-v2-messagescan .scan-letter.cx-scan__l {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: inline-block;
    width: auto;
    min-width: 0;
    height: auto;
    margin: 0;
    padding: 0 calc(1.5 * var(--s));
    border: 0;
    border-radius: calc(6 * var(--s));
    background: transparent;
    box-shadow: none;
    font: inherit;
    line-height: inherit;
    color: inherit !important;
    text-shadow: none;
    text-transform: none;
    cursor: pointer;
    transform: none;
    animation: none;
    transition: background var(--cx-t-state) var(--cx-ease);
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
}
html.cx-v2-messagescan .scan-letter.cx-scan__l:disabled {
    cursor: default;
}
html.cx-v2-messagescan .scan-letter.cx-scan__l::before {
    content: '';
    position: absolute;
    inset: calc(-6 * var(--s)) calc(-3 * var(--s));
}
html.cx-v2-messagescan .scan-letter.cx-scan__l::after {
    content: '';
    position: absolute;
    left: 8%;
    right: 8%;
    bottom: calc(-2 * var(--s));
    height: calc(5 * var(--s));
    border-radius: calc(3 * var(--s));
    background: transparent;
    pointer-events: none;
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-correct {
    color: #bfffe0 !important;
    background: rgba(var(--cx-correct-rgb), 0.14);
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-correct::after {
    background: var(--cx-correct);
    box-shadow: 0 0 calc(8 * var(--s)) var(--cx-correct);
}
html.cx-v2-messagescan .cx-scan__phrase.is-done .scan-letter.is-correct::after {
    height: calc(3 * var(--s));
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-wrong {
    color: #ffc2d1 !important;
    background: rgba(var(--cx-wrong-rgb), 0.16);
    animation: cx-shake var(--cx-t-shake) var(--cx-ease);
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-wrong::after {
    background: var(--cx-wrong);
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-hint {
    background: rgba(var(--cx-look-rgb), 0.14);
}
html.cx-v2-messagescan .scan-letter.cx-scan__l.is-hint::after {
    background: var(--cx-look);
    box-shadow: 0 0 calc(10 * var(--s)) var(--cx-look);
}

/* -------------------------------------------------------------- transcribe -- */
/* one centred column: slots, a divider, the pad */
html.cx-v2-transcribe .transcribe-phase.cx-spell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: calc(34 * var(--s));
    height: auto;
    padding: calc(26 * var(--s)) 0 0;
}
html.cx-v2-transcribe .cx-spell.is-entering {
    animation: cx-round-in 220ms var(--cx-ease) both;
}
html.cx-v2-transcribe .cx-spell__slots {
    padding-bottom: calc(30 * var(--s));
    border-bottom: calc(1.5 * var(--s)) solid rgba(var(--cx-line-rgb), 0.14);
    min-width: calc(3 * 96 * var(--s) + 2 * 12 * var(--s));
}
html.cx-v2-transcribe .cx-spell__slots.is-blended {
    padding-bottom: calc(30 * var(--s));
}
html.cx-v2-transcribe .transcribe-options.cx-spell__pad {
    display: grid;
    grid-template-columns: repeat(3, auto);
    gap: calc(18 * var(--s));
    max-width: none;
}
html.cx-v2-transcribe .cx-stage--spell .cx-replay.is-blend {
    --rgb: var(--cx-live-rgb);
}

/* -------------------------------------------------------------- word chain -- */
/* the radio room fills the screen behind the HUD (full-scene rule) */
html.cx-v2-wordchain #training-content > .word-chain-area.cx-chain {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: auto;
    height: auto;
    overflow: visible;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: calc(34 * var(--s));
    padding: calc(20 * var(--s)) calc(32 * var(--s)) calc(26 * var(--s));
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
/* the CRT: faint green scanlines */
html.cx-v2-wordchain .cx-panel.cx-crt {
    flex: none;
    overflow: visible;
    padding-left: calc(56 * var(--s));
    padding-right: calc(56 * var(--s));
    background:
        repeating-linear-gradient(to bottom, rgba(0, 255, 136, 0.035) 0 calc(2 * var(--s)), transparent calc(2 * var(--s)) calc(4 * var(--s))),
        linear-gradient(180deg, rgba(6, 22, 20, 0.94), rgba(3, 10, 12, 0.96));
}
html.cx-v2-wordchain .cx-crt .cx-replay {
    cursor: pointer;
}
html.cx-v2-wordchain .cx-chain__word {
    position: relative;
    gap: calc(16 * var(--s));
}
html.cx-v2-wordchain .transmission-letter.cx-chain__slot {
    position: relative;
    display: inline-flex;
    width: auto;
    height: auto;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    text-shadow: none;
    text-transform: none;
    animation: none;
    transition: none;
    filter: none;
}
/* scaffolded: the letter to change, amber with signal static (never red) */
html.cx-v2-wordchain .cx-tile.cx-static {
    background:
        repeating-linear-gradient(to bottom, rgba(255, 206, 58, 0.14) 0 calc(3 * var(--s)), transparent calc(3 * var(--s)) calc(7 * var(--s))),
        linear-gradient(180deg, rgba(60, 46, 8, 0.9), rgba(20, 16, 4, 0.96));
    text-shadow:
        calc(3 * var(--s)) 0 rgba(var(--cx-look-rgb), 0.55),
        calc(-3 * var(--s)) 0 rgba(var(--cx-live-rgb), 0.45);
}
/* the swap: the old glyph drops out of its slot and fades */
@keyframes cx-chain-drop {
    0% { transform: none; opacity: 0.9; }
    55% { transform: translateY(calc(62 * var(--s))) rotate(7deg); opacity: 0.45; }
    100% { transform: translateY(calc(80 * var(--s))) rotate(9deg); opacity: 0; }
}
html.cx-v2-wordchain .cx-tile.cx-chain__old {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    animation: cx-chain-drop 800ms cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
}
/* unscaffolded: one neutral sweep of static across the word after the
   instruction ("one sound changes"), without saying which */
html.cx-v2-wordchain .cx-crt__sweep {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    overflow: hidden;
    pointer-events: none;
    z-index: 1;
}
html.cx-v2-wordchain .cx-crt__sweep::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 32%;
    opacity: 0;
    background:
        repeating-linear-gradient(to bottom, rgba(238, 247, 255, 0.22) 0 calc(2 * var(--s)), transparent calc(2 * var(--s)) calc(5 * var(--s))),
        linear-gradient(90deg, transparent, rgba(238, 247, 255, 0.12) 40%, rgba(238, 247, 255, 0.12) 60%, transparent);
    transform: translateX(-110%);
}
@keyframes cx-static-sweep {
    0% { transform: translateX(-110%); opacity: 1; }
    100% { transform: translateX(320%); opacity: 1; }
}
html.cx-v2-wordchain .cx-crt.is-sweeping .cx-crt__sweep::before {
    animation: cx-static-sweep 760ms linear 1 forwards;
}
/* the keypad */
html.cx-v2-wordchain .cx-panel.cx-keys {
    flex: none;
    padding: calc(22 * var(--s)) calc(30 * var(--s));
}
html.cx-v2-wordchain .cx-keys__row {
    display: flex;
    gap: calc(18 * var(--s));
    max-width: none;
}
/* hover (desktop) lights an idle live key; never one that appeared under a
   resting pointer (is-fresh until the pointer moves), never a resolved key */
@media (hover: hover) {
    html.cx-v2-wordchain .cx-keys__row:not(.is-fresh) .frequency-btn:hover > .cx-tile:not(.is-standby):not(.is-dim):not(.is-wrong):not(.is-correct) {
        --edge: 1;
        --halo: 0.5;
    }
}

/* ======================================================================
   v2 reading (UI step 8, DESIGN.md §5.5): sentence pyramid, story
   missions, Passover.
   ----------------------------------------------------------------------
   Markup: src/ui/readingPhases.js. Everything here sits under .cx-reading
   (or .cx-passover-beat), which only the v2 code paths emit (parts
   pyramid, story, passover in src/ui/uiVersion.js), so with those parts
   off none of it applies. The legacy classes kept as hooks (.pyramid-*,
   .story-*, .spy-option, .ready-btn) are styled by styles.css; every rule
   below that shares an element with one of them declares each property the
   legacy rule sets, at a higher specificity (0,2,0 against 0,1,0), so the
   old look never leaks in (monospace, lowercase, the stray space).
   One reading type: --cx-font (CX Lexend) 500; case as the data has it.
   ====================================================================== */
.cx-reading,
.cx-passover-beat {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    width: 100%;
    height: auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    padding: 0;
    font-family: var(--cx-font);
    color: var(--cx-ink);
    text-transform: none;
}

/* ------------------------------------------------------------ a word -- */
/* A word the child can tap: its own box (a real target, >= 44 wide even
   for "a"), letters in their phonics ink, punctuation inside the box so
   nothing ever sits before it. */
.cx-reading .cx-rw {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    min-height: calc(60 * var(--s));
    margin: 0;
    padding: 0 calc(6 * var(--s));
    border: 0;
    border-radius: calc(12 * var(--s));
    background: transparent;
    box-shadow: none;
    color: inherit;
    font: inherit;
    line-height: 1;
    letter-spacing: 0.01em;
    text-transform: none;
    text-shadow: none;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition:
        background var(--cx-t-state) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
    animation: none;
}
/* hit area: at least 44 design px wide, without widening the gaps */
.cx-reading .cx-rw::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 100%;
    min-width: calc(44 * var(--s));
    transform: translateX(-50%);
}
.cx-reading span.cx-rw {
    cursor: default;
}
.cx-reading .cx-rw:focus {
    outline: none;
}
.cx-reading .cx-rw:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(2 * var(--s));
}
.cx-reading .cx-rw__l,
.cx-reading .cx-rw__p {
    transition:
        color var(--cx-t-state) var(--cx-ease),
        text-shadow var(--cx-t-state) var(--cx-ease);
}
.cx-reading .cx-rw__p {
    color: inherit;
}
/* the newest word: framed and brighter than the go ("unread") */
.cx-reading .cx-rw.is-new {
    color: #fff;
    background: rgba(var(--cx-live-rgb), 0.1);
    box-shadow:
        inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.55),
        0 0 calc(18 * var(--s)) rgba(var(--cx-live-rgb), 0.3);
    text-shadow: 0 0 calc(12 * var(--s)) rgba(var(--cx-live-rgb), 0.6);
}
/* being sounded out: the frame bright, the letters light in order */
.cx-reading .cx-rw.is-decoding {
    background: rgba(var(--cx-live-rgb), 0.12);
    box-shadow: inset 0 0 0 calc(2 * var(--s)) var(--cx-live);
}
.cx-reading .cx-rw.is-decoding .cx-rw__l {
    color: var(--cx-ink-2);
}
.cx-reading .cx-rw.is-decoding .cx-rw__l.is-sounded {
    color: var(--cx-correct);
}
.cx-reading .cx-rw.is-decoding .cx-rw__l.is-sounding {
    color: #fff;
    text-shadow:
        0 0 calc(10 * var(--s)) var(--cx-live),
        0 0 calc(20 * var(--s)) var(--cx-live);
}
.cx-reading .cx-rw.is-decoding.is-sight .cx-rw__l {
    color: #fff;
    text-shadow: 0 0 calc(10 * var(--s)) rgba(var(--cx-live-rgb), 0.8);
}
@media (hover: hover) {
    .cx-reading button.cx-rw:not(.is-decoding):hover {
        background: rgba(var(--cx-live-rgb), 0.1);
        box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-live-rgb), 0.4);
    }
    .cx-reading button.cx-rw.is-new:not(.is-decoding):hover {
        background: rgba(var(--cx-live-rgb), 0.16);
        box-shadow:
            inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.85),
            0 0 calc(18 * var(--s)) rgba(var(--cx-live-rgb), 0.4);
    }
}
/* "fur-lined": two words, one hyphen, no gap */
.cx-reading .cx-rw-glue {
    display: inline-flex;
    flex: none;
    white-space: nowrap;
}
.cx-reading .cx-rw-glue > .cx-rw:not(:last-child) {
    padding-right: 0;
}
.cx-reading .cx-rw-glue > .cx-rw + .cx-rw {
    padding-left: 0;
}
/* a spaced dash between words */
.cx-reading .cx-rw-punct {
    display: inline-flex;
    align-items: center;
    flex: none;
    color: var(--cx-ink-2);
}

/* ------------------------------------------------------------- pyramid -- */
.cx-reading .cx-panel.cx-pyr-panel {
    min-width: calc(560 * var(--s));
    max-width: min(calc(920 * var(--s)), 100%);
    max-height: 100%;
    padding: calc(34 * var(--s)) calc(36 * var(--s)) calc(56 * var(--s));
}
.cx-reading .cx-panel.cx-pyr-panel--wide {
    width: min(calc(880 * var(--s)), 100%);
    max-width: none;
    padding-left: calc(28 * var(--s));
    padding-right: calc(28 * var(--s));
}
/* The lines sit at the bottom of a box sized for the lines that will show,
   so a new line appears at the bottom and the panel never changes size. */
.cx-reading .pyramid-container.cx-pyr {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: calc(10 * var(--s));
    width: 100%;
    max-width: none;
    height: calc((60 + (var(--rows) - 1) * 60) * var(--s));
    overflow: hidden;
}
.cx-reading .cx-pyr-panel--wide .pyramid-container.cx-pyr {
    height: calc(430 * var(--s));
    /* older lines leave upward under a fade (they wrap, so they are taller) */
    -webkit-mask: linear-gradient(to bottom, transparent 0, #000 calc(56 * var(--s)));
    mask: linear-gradient(to bottom, transparent 0, #000 calc(56 * var(--s)));
}
.cx-reading .pyramid-line.cx-pyr__line {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    column-gap: calc(4 * var(--s));
    row-gap: 0;
    max-width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    font-family: var(--cx-font);
    font-weight: 500;
    font-size: var(--cur);
    line-height: 1;
    color: var(--cx-ink);
    text-align: center;
    white-space: normal;
    animation: none;
    transition:
        font-size var(--cx-t-enter) var(--cx-ease),
        color var(--cx-t-enter) var(--cx-ease),
        opacity var(--cx-t-enter) var(--cx-ease);
}
.cx-reading .pyramid-line.cx-pyr__line.is-old {
    font-size: var(--old);
    color: var(--cx-ink-2);
}
.cx-reading .pyramid-line.cx-pyr__line.is-old .cx-rw {
    min-height: calc(50 * var(--s));
}
.cx-reading .pyramid-line.cx-pyr__line.is-fading {
    opacity: 0.35;
}
.cx-reading .pyramid-line.cx-pyr__line.is-hidden {
    display: none;
}
.cx-reading .pyramid-line.cx-pyr__line.is-entering {
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) backwards;
}
.cx-reading .pyramid-continue.cx-go {
    margin: 0;
    font-size: inherit;
    animation: none;
}

/* ----------------------------------------------------------- which spy -- */
.cx-reading .cx-stage--spy {
    flex-direction: column;
    gap: calc(56 * var(--s));
}
.cx-reading .cx-panel.cx-spy__sentence {
    max-width: min(calc(900 * var(--s)), 100%);
    padding: calc(20 * var(--s)) calc(40 * var(--s));
}
.cx-reading .cx-panel.cx-spy__sentence.has-bottom {
    padding-bottom: calc(34 * var(--s));
}
.cx-reading .cx-spy__text {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    column-gap: calc(4 * var(--s));
    font-family: var(--cx-font);
    font-weight: 500;
    font-size: calc(44 * var(--s));
    line-height: 1;
    color: var(--cx-ink);
}
.cx-reading .cx-spy.is-long .cx-spy__text {
    font-size: calc(36 * var(--s));
}
.cx-reading .cx-spy.is-xlong .cx-spy__text {
    font-size: calc(33 * var(--s));
}
.cx-reading .cx-spy.is-long .cx-spy__text .cx-rw,
.cx-reading .cx-spy.is-xlong .cx-spy__text .cx-rw {
    min-height: calc(48 * var(--s));
}
.cx-reading .cx-spy__text .cx-rw {
    min-height: calc(56 * var(--s));
}
.cx-reading .cx-spy__agents {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;
    gap: calc(74 * var(--s));
    margin: 0;
    padding: 0 0 calc(82 * var(--s));
    animation: none;
}
.cx-reading .cx-spy__opt {
    position: relative;
    display: block;
    flex: none;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    text-align: center;
    transform: none;
    transition: none;
    animation: none;
    cursor: default;
}
.cx-reading .cx-spy__agent {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.cx-reading .cx-spy__agent:focus {
    outline: none;
}
.cx-reading .cx-spy__agent:focus-visible {
    outline: calc(2 * var(--s)) solid rgba(var(--cx-live-rgb), 0.8);
    outline-offset: calc(6 * var(--s));
}
@media (hover: hover) {
    .cx-reading .cx-spy__agent:hover .cx-agent:not(.is-selected):not(.is-correct):not(.is-wrong):not(.is-dim) {
        --ring: 0.85;
        --rgb: var(--cx-live-rgb);
    }
}
.cx-reading .cx-agent.is-dim {
    opacity: 0.45;
}
/* the confirm: docked under the agent you just heard */
.cx-reading .cx-spy__confirm {
    position: absolute;
    left: 50%;
    top: calc(100% + 18 * var(--s));
    transform: translateX(-50%);
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
}
.cx-reading .cx-spy__opt.is-confirming .cx-spy__confirm {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    animation: cx-spy-confirm-in var(--cx-t-enter) var(--cx-ease-spring) 1 both;
}
.cx-reading .cx-spy__opt.is-confirming .cx-spy__confirm.is-pressed,
.cx-reading .cx-spy__opt.is-confirming .cx-spy__confirm:active {
    transform: translateX(-50%) scale(0.93);
}
@keyframes cx-spy-confirm-in {
    from {
        opacity: 0;
        transform: translate(-50%, calc(-14 * var(--s))) scale(0.8);
    }
    to {
        opacity: 1;
        transform: translateX(-50%);
    }
}

/* --------------------------------------------------------------- story -- */
.cx-reading .cx-panel.cx-story-panel {
    max-height: 100%;
    padding: calc(22 * var(--s)) calc(22 * var(--s)) calc(52 * var(--s));
}
.cx-reading .story-book.cx-story {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(18 * var(--s));
    width: calc(700 * var(--s));
    max-width: none;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}
.cx-reading .story-image-container.cx-story__img {
    flex: none;
    display: block;
    width: calc(560 * var(--s));
    height: calc(305 * var(--s));
    aspect-ratio: auto;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: calc(10 * var(--s));
    overflow: hidden;
    background: rgba(8, 17, 27, 0.9);
    box-shadow: 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.25);
}
.cx-reading .cx-story-panel.is-long .cx-story__img {
    width: calc(420 * var(--s));
    height: calc(229 * var(--s));
}
/* safety net (training.fitStoryPageV2): a page that would overflow */
.cx-reading .cx-story-panel.is-tight .cx-story__img {
    width: calc(320 * var(--s));
    height: calc(174 * var(--s));
}
.cx-reading .cx-story__img .story-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.cx-reading .story-words.cx-story__text {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    column-gap: calc(2 * var(--s));
    row-gap: 0;
    max-width: calc(660 * var(--s));
    margin: 0;
    padding: 0 0 calc(8 * var(--s));
    font-family: var(--cx-font);
    font-weight: 500;
    font-size: calc(46 * var(--s));
    line-height: 1;
    color: #fff;
}
.cx-reading .cx-story-panel.is-long .cx-story__text {
    font-size: calc(40 * var(--s));
}
.cx-reading .cx-story__text .cx-rw {
    min-height: calc(58 * var(--s));
    padding: 0 calc(5 * var(--s));
}
.cx-reading .cx-story-panel.is-long .cx-story__text .cx-rw {
    min-height: calc(52 * var(--s));
}
.cx-reading .ready-btn.cx-go {
    margin: 0;
    padding: 0;
    font-size: inherit;
    font-weight: inherit;
    align-self: auto;
}

/* ------------------------------------------------------------ Passover -- */
.cx-passover-beat .cx-passover-panel {
    min-width: calc(560 * var(--s));
    min-height: calc(300 * var(--s));
    display: grid;
    place-items: center;
    padding-bottom: calc(60 * var(--s));
}
.cx-passover__words {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: calc(28 * var(--s));
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) backwards;
}
.cx-passover-beat .cx-pips {
    animation: none;
}
@media (prefers-reduced-motion: reduce) {
    .cx-reading .pyramid-line.cx-pyr__line.is-entering,
    .cx-reading .cx-spy__opt.is-confirming .cx-spy__confirm,
    .cx-passover__words {
        animation: none;
    }
}

/* ======================================================================
   v2 step 9 (DESIGN.md §5.3 lineups, §5.6 first review level, §5.1
   placement): html.cx-v2-lineup, html.cx-v2-review, html.cx-v2-placement.
   ====================================================================== */

/* ------------------------------------------------------------- lineups -- */
/* The photo in an aspect-locked frame above, the word + the ask / accuse
   switch below (mockups lineup, lineup-accuse). The strips are placed in
   image percent inside the frame (src/ui/lineupPhases.js). */
html.cx-v2-lineup #training-content > .cx-lineup.lineup-tutorial {
    position: relative;
    flex: 1 1 auto;
    align-self: stretch;
    min-height: 0;
    width: 100%;
    height: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: calc(18 * var(--s));
    padding: calc(24 * var(--s)) calc(32 * var(--s)) calc(22 * var(--s));
    box-sizing: border-box;
    font-family: var(--cx-font);
    color: var(--cx-ink);
    -webkit-font-smoothing: antialiased;
}
.cx-lineup__slot {
    flex: 1 1 auto;
    align-self: stretch;
    min-height: 0;
    display: grid;
    place-items: center;
}
html.cx-v2-lineup .cx-lineup .cx-scene.lineup-scene {
    max-width: none;
    aspect-ratio: auto;
    user-select: none;
    -webkit-user-select: none;
    transition: opacity var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-lineup .cx-lineup .cx-scene.lineup-scene > img.lineup-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: fill;
    border-radius: 0;
    pointer-events: none;
    -webkit-user-drag: none;
}
html.cx-v2-lineup .cx-lineup .lineup-clickzones {
    position: absolute;
    inset: 0;
    z-index: 4;
}
/* accuse: the whole frame turns white (the mode is visible without a cursor) */
html.cx-v2-lineup .cx-lineup .cx-scene.is-accuse::after {
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) #fff,
        inset 0 0 calc(40 * var(--s)) rgba(255, 255, 255, 0.18);
}
html.cx-v2-lineup .cx-lineup .cx-scene.is-standby .lineup-person {
    cursor: default;
}

/* a suspect strip */
html.cx-v2-lineup .cx-lineup .lineup-person.cx-suspect {
    --bk: calc(22 * var(--s));
    --bw: calc(3 * var(--s));
    --c: rgba(255, 255, 255, 0.95);
    position: absolute;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: calc(10 * var(--s));
    background: transparent;
    box-shadow: none;
    cursor: pointer;
    animation: none;
    transition:
        box-shadow var(--cx-t-state) var(--cx-ease),
        background var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-lineup .cx-lineup .lineup-person.cx-suspect:focus-visible {
    outline: none;
}
@media (hover: hover) {
    html.cx-v2-lineup .cx-lineup .cx-scene:not(.is-standby) .lineup-person.cx-suspect:hover {
        box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.55);
        background: rgba(var(--cx-live-rgb), 0.06);
    }
    html.cx-v2-lineup .cx-lineup .cx-scene.is-accuse:not(.is-standby) .lineup-person.cx-suspect:hover {
        box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(255, 255, 255, 0.75);
        background: rgba(255, 255, 255, 0.06);
    }
}
/* accuse: the same reticle brackets on every suspect (none singled out) */
html.cx-v2-lineup .cx-lineup .cx-scene.is-accuse .lineup-person.cx-suspect::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        linear-gradient(var(--c), var(--c)) left top / var(--bk) var(--bw),
        linear-gradient(var(--c), var(--c)) left top / var(--bw) var(--bk),
        linear-gradient(var(--c), var(--c)) right top / var(--bk) var(--bw),
        linear-gradient(var(--c), var(--c)) right top / var(--bw) var(--bk),
        linear-gradient(var(--c), var(--c)) left bottom / var(--bk) var(--bw),
        linear-gradient(var(--c), var(--c)) left bottom / var(--bw) var(--bk),
        linear-gradient(var(--c), var(--c)) right bottom / var(--bk) var(--bw),
        linear-gradient(var(--c), var(--c)) right bottom / var(--bw) var(--bk);
    background-repeat: no-repeat;
    filter: drop-shadow(0 0 calc(3 * var(--s)) rgba(0, 0, 0, 0.8));
}
html.cx-v2-lineup .cx-lineup .cx-scene.is-standby .lineup-person.cx-suspect::before {
    opacity: 0.45;
}
/* ask: the suspect talking gets a soft outline and a live notch */
html.cx-v2-lineup .cx-lineup .lineup-person.cx-suspect.is-speaking {
    box-shadow:
        inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.85),
        0 0 calc(18 * var(--s)) rgba(var(--cx-live-rgb), 0.35);
    background: rgba(var(--cx-live-rgb), 0.08);
}
.cx-suspect__notch {
    position: absolute;
    left: 50%;
    top: calc(-24 * var(--s));
    width: calc(38 * var(--s));
    height: calc(38 * var(--s));
    margin-left: calc(-19 * var(--s));
    border-radius: 50%;
    display: none;
    place-items: center;
    color: var(--cx-live);
    background: rgba(5, 11, 18, 0.9);
    box-shadow:
        inset 0 0 0 calc(2 * var(--s)) rgba(var(--cx-live-rgb), 0.85),
        0 0 calc(10 * var(--s)) rgba(var(--cx-live-rgb), 0.45);
    pointer-events: none;
}
.cx-suspect__notch svg {
    width: calc(20 * var(--s));
    height: calc(20 * var(--s));
}
.cx-suspect__notch .cx-waves {
    display: none;
}
.cx-suspect__asked {
    display: grid;
    place-items: center;
}
/* answered suspects keep a speech notch; the one talking shows live bars */
.cx-suspect.is-asked > .cx-suspect__notch,
.cx-suspect.is-speaking > .cx-suspect__notch {
    display: grid;
}
.cx-suspect.is-speaking > .cx-suspect__notch {
    color: #04151d;
    background: #fff;
    box-shadow: 0 0 calc(14 * var(--s)) rgba(255, 255, 255, 0.6);
}
.cx-suspect.is-speaking > .cx-suspect__notch .cx-suspect__asked {
    display: none;
}
.cx-suspect.is-speaking > .cx-suspect__notch .cx-waves {
    display: flex;
    color: #04151d;
}
/* accusation feedback */
html.cx-v2-lineup .cx-lineup .lineup-person.cx-suspect.is-wrong {
    --c: var(--cx-wrong);
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) var(--cx-wrong),
        0 0 calc(20 * var(--s)) rgba(var(--cx-wrong-rgb), 0.55);
    background: rgba(var(--cx-wrong-rgb), 0.18);
    animation: cx-shake var(--cx-t-shake) var(--cx-ease) 1;
}
html.cx-v2-lineup .cx-lineup .lineup-person.cx-suspect.is-correct {
    --c: var(--cx-correct);
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) var(--cx-correct),
        0 0 calc(24 * var(--s)) rgba(var(--cx-correct-rgb), 0.55);
    background: rgba(var(--cx-correct-rgb), 0.16);
    animation: cx-lock 260ms var(--cx-ease-spring) 1;
}

/* the word + the switch under the photo */
.cx-lineup__bar {
    flex: none;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    min-height: calc(84 * var(--s));
}
.cx-lineup__bar > .cx-mode {
    justify-self: end;
}
html.cx-v2-lineup .cx-lineup .lineup-note.cx-lineup__note {
    position: static;
    display: flex;
    flex-direction: row;
    align-items: center;
    border-radius: 0;
    gap: calc(14 * var(--s));
    margin: 0;
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
    transform: none;
    font: inherit;
    color: inherit;
}
html.cx-v2-lineup .cx-lineup .lineup-note .note-letter.cx-hit {
    border-radius: 0;
    cursor: pointer;
    transition: none;
    min-width: 0;
    height: auto;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    box-shadow: none;
    font: inherit;
    color: inherit;
    transform: none;
}
html.cx-v2-lineup .cx-lineup .lineup-mode-toggle.cx-mode {
    position: static;
    top: auto;
    right: auto;
}
html.cx-v2-lineup .cx-lineup .lineup-mode-toggle.cx-mode > button {
    padding: 0;
    border: 0;
    background: none;
    font-size: 0;
    cursor: pointer;
    transition:
        background var(--cx-t-state) var(--cx-ease),
        color var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-lineup .cx-lineup .lineup-mode-toggle.cx-mode > button.is-on {
    color: #04151d;
    background: var(--cx-live);
    box-shadow: 0 0 calc(16 * var(--s)) rgba(var(--cx-live-rgb), 0.6);
}
html.cx-v2-lineup .cx-lineup .lineup-mode-toggle.cx-mode.is-disabled {
    opacity: 0.35;
    pointer-events: none;
}
@media (hover: hover) {
    html.cx-v2-lineup .cx-lineup .lineup-mode-toggle.cx-mode > button:not(.is-on):hover {
        background: rgba(var(--cx-live-rgb), 0.14);
    }
}

/* ------------------------------------------------ review + placement base -- */
/* Buttons that wrap a tile / agent / scrap: no UA chrome, no legacy rule. */
html.cx-v2-review .cx-lost .cx-hit,
html.cx-v2-review .cx-lost__agent,
html.cx-v2-review .cx-disguises .cx-hit,
html.cx-v2-review .cx-folder,
html.cx-v2-review .cx-duel .cx-hit,
html.cx-v2-review .cx-team,
html.cx-v2-placement .cx-place .cx-hit {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    line-height: 1;
    text-transform: none;
    text-shadow: none;
    animation: none;
    opacity: 1;
    filter: none;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: none;
}
html.cx-v2-review .cx-lost .cx-hit,
html.cx-v2-review .cx-disguises .cx-hit,
html.cx-v2-review .cx-duel .cx-hit,
html.cx-v2-placement .cx-place .cx-hit {
    display: inline-flex;
    width: auto;
    height: auto;
    min-width: 0;
    border-radius: 0;
    transition: none;
}
/* picked up (tap-to-pick): white edge, lifted (DESIGN.md §3.1 is-selected) */
.cx-hit.is-picked > .cx-tile {
    --rgb: var(--cx-pick-rgb);
    --edge: 1;
    --halo: 0.4;
    transform: translateY(calc(-6 * var(--s)));
}
/* being dragged: above everything, no transition lag */
.cx-hit.is-dragging,
.cx-scrap.is-dragging {
    transition: none !important;
    pointer-events: none;
}
.cx-hit.is-dragging > .cx-tile {
    --rgb: var(--cx-pick-rgb);
    --edge: 1;
    transform: scale(1.08);
    box-shadow:
        inset 0 0 0 calc(2.5 * var(--s)) #fff,
        0 calc(14 * var(--s)) calc(28 * var(--s)) rgba(0, 0, 0, 0.6);
}
@media (hover: hover) {
    html.cx-v2-review :is(.cx-lost, .cx-disguises, .cx-duel) .cx-hit:not(.is-picked):hover > .cx-tile:not(.is-correct):not(.is-wrong):not(.is-dim):not(.is-hint),
    html.cx-v2-placement .cx-place .cx-hit:not(:disabled):hover > .cx-tile:not(.is-correct):not(.is-wrong):not(.is-dim):not(.is-hint) {
        --edge: 1;
        --halo: 0.45;
    }
}

/* ------------------------------------------------------------ capitals -- */
/* Kelsey's paper scraps (a deliberate contrast), pinned to an evidence
   board: cream paper, dark ink, a slight tilt each, never overlapping. */
html.cx-v2-review #training-content > .cx-caps {
    flex: 1 1 auto;
    align-self: stretch;
    display: grid;
    place-items: center;
    padding: calc(24 * var(--s)) calc(32 * var(--s));
    font-family: var(--cx-font);
}
.cx-caps__board {
    --cx-paper: #efe7d4;
    --cx-paper-ink: #17202b;
    width: calc(880 * var(--s));
    padding: calc(34 * var(--s)) calc(36 * var(--s)) calc(38 * var(--s));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(46 * var(--s));
}
.cx-caps__row {
    display: flex;
    justify-content: center;
    gap: calc(18 * var(--s));
}
.cx-caps__examples {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: calc(18 * var(--s)) calc(22 * var(--s));
    max-width: calc(760 * var(--s));
}
.cx-caps__lowers {
    display: flex;
    justify-content: center;
    gap: calc(26 * var(--s));
    min-height: calc(92 * var(--s));
}
:where(html.cx-v2-review .cx-caps) .cx-scrap {
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
    padding: 0;
    border: 0;
    line-height: 1;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: none;
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: calc(4 * var(--s));
    color: var(--cx-paper-ink);
    background: var(--cx-paper);
    border-radius: calc(3 * var(--s)) calc(5 * var(--s)) calc(2 * var(--s)) calc(6 * var(--s));
    box-shadow:
        0 calc(8 * var(--s)) calc(18 * var(--s)) rgba(0, 0, 0, 0.55),
        inset 0 0 0 calc(1 * var(--s)) rgba(0, 0, 0, 0.08);
    font-family: var(--cx-font);
    font-weight: 700;
    transform: rotate(var(--tilt, 0deg));
    transition:
        transform var(--cx-t-state) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
}
.cx-scrap--pair {
    width: calc(96 * var(--s));
    height: calc(96 * var(--s));
    font-size: calc(46 * var(--s));
}
.cx-scrap--pair .cx-scrap__low {
    font-weight: 600;
}
.cx-scrap--example {
    gap: calc(10 * var(--s));
    height: calc(64 * var(--s));
    padding: 0 calc(20 * var(--s));
    font-size: calc(30 * var(--s));
    font-weight: 600;
}
.cx-scrap__cap {
    color: #0a8a4e;
    font-weight: 800;
}
/* the example (and its letter pair) being read: lifted, lit */
.cx-scrap.reading {
    transform: rotate(0deg) translateY(calc(-6 * var(--s))) scale(1.06);
    box-shadow:
        0 0 0 calc(3 * var(--s)) var(--cx-live),
        0 0 calc(24 * var(--s)) rgba(var(--cx-live-rgb), 0.55),
        0 calc(10 * var(--s)) calc(20 * var(--s)) rgba(0, 0, 0, 0.55);
}
.cx-scrap--upper {
    width: calc(100 * var(--s));
    height: calc(150 * var(--s));
    flex-direction: column;
    justify-content: flex-start;
    padding-top: calc(18 * var(--s));
    gap: calc(12 * var(--s));
    font-size: calc(52 * var(--s));
}
.cx-scrap__slot {
    width: calc(64 * var(--s));
    height: calc(64 * var(--s));
    border-radius: calc(8 * var(--s));
    border: calc(2 * var(--s)) dashed rgba(23, 32, 43, 0.35);
    display: grid;
    place-items: center;
    font-size: calc(40 * var(--s));
    font-weight: 600;
    line-height: 1;
}
.cx-scrap--upper.is-armed .cx-scrap__slot {
    border-color: rgba(23, 32, 43, 0.6);
}
.cx-scrap--upper.is-over {
    box-shadow:
        0 0 0 calc(3 * var(--s)) #fff,
        0 calc(8 * var(--s)) calc(18 * var(--s)) rgba(0, 0, 0, 0.55);
}
.cx-scrap--upper.is-matched .cx-scrap__slot {
    border: calc(2.5 * var(--s)) solid #0a8a4e;
    background: rgba(0, 200, 110, 0.22);
    color: #0a6e3e;
}
.cx-scrap--upper.is-matched {
    box-shadow:
        0 0 0 calc(3 * var(--s)) var(--cx-correct),
        0 0 calc(18 * var(--s)) rgba(var(--cx-correct-rgb), 0.45),
        0 calc(8 * var(--s)) calc(18 * var(--s)) rgba(0, 0, 0, 0.55);
    cursor: default;
}
.cx-scrap--upper.is-wrong {
    box-shadow:
        0 0 0 calc(3 * var(--s)) var(--cx-wrong),
        0 0 calc(18 * var(--s)) rgba(var(--cx-wrong-rgb), 0.5);
    animation: cx-shake var(--cx-t-shake) var(--cx-ease) 1;
}
.cx-scrap--lower {
    width: calc(76 * var(--s));
    height: calc(84 * var(--s));
    font-size: calc(48 * var(--s));
    font-weight: 600;
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) backwards;
}
.cx-scrap--lower.is-picked {
    transform: rotate(0deg) translateY(calc(-8 * var(--s)));
    box-shadow:
        0 0 0 calc(3 * var(--s)) #fff,
        0 0 calc(26 * var(--s)) rgba(255, 255, 255, 0.55),
        0 calc(12 * var(--s)) calc(24 * var(--s)) rgba(0, 0, 0, 0.6);
}
.cx-scrap--lower.is-dragging {
    box-shadow:
        0 0 0 calc(3 * var(--s)) #fff,
        0 calc(16 * var(--s)) calc(30 * var(--s)) rgba(0, 0, 0, 0.65);
}

/* --------------------------------------------------------- lost badges -- */
html.cx-v2-review #training-content > .cx-lost {
    flex: 1 1 auto;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(56 * var(--s));
    padding: calc(24 * var(--s)) calc(32 * var(--s));
    font-family: var(--cx-font);
}
.cx-lost__agents {
    display: flex;
    gap: calc(22 * var(--s));
    padding: calc(26 * var(--s)) calc(28 * var(--s)) calc(28 * var(--s));
}
.cx-lost__col {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(22 * var(--s));
}
html.cx-v2-review .cx-lost__agent {
    display: block;
    border-radius: 50%;
}
@media (hover: hover) {
    html.cx-v2-review .cx-lost__agent:hover .cx-agent:not(.is-correct) {
        --ring: 0.75;
    }
}
.cx-lost__slot {
    width: calc(60 * var(--s));
    height: calc(60 * var(--s));
    border-radius: calc(10 * var(--s));
    border: calc(2 * var(--s)) dashed rgba(var(--cx-line-rgb), 0.45);
    display: grid;
    place-items: center;
    cursor: pointer;
    transition: border-color var(--cx-t-state) var(--cx-ease);
}
.cx-lost__slot.is-armed:not(.filled) {
    border-color: rgba(255, 255, 255, 0.55);
}
.cx-lost__slot.is-next:not(.filled) {
    border-color: var(--cx-look);
    box-shadow: 0 0 calc(12 * var(--s)) rgba(var(--cx-look-rgb), 0.45);
}
.cx-lost__slot.is-over:not(.filled) {
    border-style: solid;
    border-color: #fff;
}
.cx-lost__slot.filled {
    border: 0;
    cursor: default;
}
.cx-lost__slot.is-wrong {
    border-style: solid;
    border-color: var(--cx-wrong);
    box-shadow: 0 0 calc(14 * var(--s)) rgba(var(--cx-wrong-rgb), 0.55);
    animation: cx-shake var(--cx-t-shake) var(--cx-ease) 1;
}
.cx-lost__tray {
    display: flex;
    gap: calc(22 * var(--s));
    padding: calc(26 * var(--s)) calc(34 * var(--s));
    min-height: calc(128 * var(--s));
    align-items: center;
}

/* ------------------------------------------------------- minimal pairs -- */
.cx-panel.cx-pairs {
    padding-left: calc(40 * var(--s));
    padding-right: calc(40 * var(--s));
}
.cx-pairs__row {
    display: flex;
    gap: calc(48 * var(--s));
}
html.cx-v2-review .cx-pairs .cx-choice.cx-pair {
    width: calc(270 * var(--s));
    height: calc(170 * var(--s));
    min-width: 0;
    padding: 0;
    font-size: calc(78 * var(--s));
    font-weight: 600;
    letter-spacing: 0.01em;
    transition:
        transform var(--cx-t-press) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease),
        opacity var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-review .cx-pairs .cx-choice.cx-pair:active {
    transform: scale(0.96);
}
@media (hover: hover) {
    html.cx-v2-review .cx-pairs .cx-choice.cx-pair:not(.is-correct):not(.is-wrong):not(.is-hint):not(.is-dim):hover {
        --edge: 1;
        --halo: 0.45;
    }
}
.cx-choice.is-hint::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: 0 0 0 calc(4 * var(--s)) rgba(var(--cx-look-rgb), 0.8);
    animation: cx-beacon 1.1s ease-out 3;
    pointer-events: none;
}

/* ------------------------------------------------------------ disguises -- */
html.cx-v2-review #training-content > .cx-disguises {
    flex: 1 1 auto;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(34 * var(--s));
    padding: calc(20 * var(--s)) calc(32 * var(--s)) calc(16 * var(--s));
    font-family: var(--cx-font);
}
.cx-disguises > .cx-stage {
    flex: none;
    padding: 0 0 calc(28 * var(--s));
}
.cx-disguises__folders {
    display: flex;
    gap: calc(26 * var(--s));
}
html.cx-v2-review .cx-folder {
    --rgb: var(--cx-live-rgb);
    --edge: 0.8;
    width: calc(96 * var(--s));
    height: calc(76 * var(--s));
    margin-top: calc(12 * var(--s));
    border-radius: calc(4 * var(--s)) calc(12 * var(--s)) calc(12 * var(--s)) calc(12 * var(--s));
    display: grid;
    place-items: center;
    background: linear-gradient(180deg, rgba(14, 32, 48, 0.97), rgba(6, 14, 22, 0.97));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--rgb), var(--edge)),
        0 0 calc(18 * var(--s)) rgba(var(--rgb), 0.3);
    transition: box-shadow var(--cx-t-state) var(--cx-ease), transform var(--cx-t-press) var(--cx-ease);
}
.cx-folder__tab {
    position: absolute;
    left: 0;
    top: calc(-12 * var(--s));
    width: calc(40 * var(--s));
    height: calc(16 * var(--s));
    border-radius: calc(6 * var(--s)) calc(10 * var(--s)) 0 0;
    background: rgba(6, 14, 22, 0.97);
    box-shadow: inset 0 0 0 calc(3 * var(--s)) rgba(var(--rgb), var(--edge));
    clip-path: inset(0 0 calc(3 * var(--s)) 0);
}
.cx-folder .cx-tile {
    --edge: 0.55;
    --halo: 0;
    pointer-events: none;
}
.cx-folder.is-armed {
    --edge: 1;
}
.cx-folder.is-over {
    --rgb: var(--cx-pick-rgb);
    --edge: 1;
    transform: translateY(calc(-4 * var(--s)));
}
.cx-folder.is-correct {
    --rgb: var(--cx-correct-rgb);
    --edge: 1;
    animation: cx-lock 260ms var(--cx-ease-spring) 1;
}
.cx-folder.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    --edge: 1;
    animation: cx-shake var(--cx-t-shake) var(--cx-ease) 1;
}
@media (hover: hover) {
    html.cx-v2-review .cx-folder:hover {
        --edge: 1;
    }
}
.cx-panel.cx-disguises__tray {
    padding: calc(28 * var(--s)) calc(34 * var(--s)) calc(46 * var(--s));
}
.cx-disguises__letters {
    display: flex;
    gap: calc(18 * var(--s));
    min-height: calc(82 * var(--s));
    min-width: calc(8 * 82 * var(--s) + 7 * 18 * var(--s));
    justify-content: center;
}
.cx-disguises__letters.is-entering {
    animation: cx-round-in 260ms var(--cx-ease) both;
}
/* a letter in a real face; --fx lifts a small x-height to Lexend's */
.cx-tile.cx-disguise {
    font-weight: 400;
    font-size: calc(var(--t) * 0.56 * var(--fx, 1));
    letter-spacing: 0;
    text-shadow: none;
    overflow: hidden;
}

/* ----------------------------------------------------------------- race -- */
/* Team colours (DESIGN.md §2.2): race only, never a tile state. */
.cx-race {
    --cx-blue-rgb: 63, 140, 255;
    --cx-red-rgb: 255, 94, 58;
}
.cx-team--blue,
.cx-emblem--blue,
.cx-race__side--blue,
.cx-race__pips--blue,
.cx-race__burst--blue {
    --team: var(--cx-blue-rgb);
}
.cx-team--red,
.cx-emblem--red,
.cx-race__side--red,
.cx-race__pips--red,
.cx-race__burst--red {
    --team: var(--cx-red-rgb);
}
.cx-emblem {
    display: inline-grid;
    place-items: center;
    width: var(--e);
    height: var(--e);
    color: rgb(var(--team));
    filter: drop-shadow(0 0 calc(10 * var(--s)) rgba(var(--team), 0.7));
}
.cx-emblem svg {
    width: 100%;
    height: 100%;
}
html.cx-v2-review #training-content > .cx-race--pick,
html.cx-v2-review #training-content > .cx-race--score {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-family: var(--cx-font);
}
.cx-race__bg {
    filter: brightness(0.5) saturate(0.9);
}
.cx-race__teams {
    position: relative;
    z-index: 1;
    display: flex;
    gap: calc(76 * var(--s));
}
html.cx-v2-review .cx-team {
    width: calc(270 * var(--s));
    height: calc(300 * var(--s));
    border-radius: calc(26 * var(--s));
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(36 * var(--s));
    background: linear-gradient(180deg, rgba(8, 17, 27, 0.9), rgba(4, 9, 15, 0.95));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--team), 0.85),
        0 0 calc(30 * var(--s)) rgba(var(--team), 0.35);
    transition: transform var(--cx-t-press) var(--cx-ease), box-shadow var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-review .cx-team:active {
    transform: scale(0.96);
}
@media (hover: hover) {
    html.cx-v2-review .cx-team:hover {
        box-shadow:
            inset 0 0 0 calc(3 * var(--s)) rgb(var(--team)),
            0 0 calc(44 * var(--s)) rgba(var(--team), 0.6);
        transform: translateY(calc(-4 * var(--s)));
    }
}
.cx-team__crew {
    display: flex;
}
.cx-team__crew .cx-agent {
    --rgb: var(--team);
    --ring: 0.95;
    margin: 0 calc(-7 * var(--s));
}
/* the round: the chase frame and a scoreboard docked under it */
html.cx-v2-review #training-content > .chase-game.cx-race {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    display: block;
    overflow: visible;
}
html.cx-v2-review .cx-race .cx-chase__slot.cx-race__slot {
    position: absolute;
    top: calc(4 * var(--s));
    left: calc(8 * var(--s));
    right: calc(8 * var(--s));
    bottom: calc(92 * var(--s));
    z-index: 1;
    display: grid;
    place-items: center;
}
html.cx-v2-review .cx-race .chase-scene.cx-scene {
    width: var(--scene-w, 900px);
    height: var(--scene-h, 490px);
    max-width: none;
    aspect-ratio: auto;
    background: #000;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}
html.cx-v2-review .cx-race .chase-scene.cx-scene.is-fitting {
    visibility: hidden;
}
html.cx-v2-review .cx-race .chase-scene > img {
    pointer-events: none;
    -webkit-user-drag: none;
}
html.cx-v2-review .cx-race .cx-dock-top.cx-chase__top {
    top: calc(26 * var(--s));
    transform: translate(-50%, 0);
    z-index: 20;
}
html.cx-v2-review .cx-race .cx-replay {
    cursor: pointer;
}
html.cx-v2-review .cx-race .chase-agent::after {
    content: none;
}
html.cx-v2-review .cx-race .chase-agent-badge.cx-chip {
    --c: max(calc(44 * var(--s)), calc(34 * var(--s) * var(--sc, 1)));
    position: absolute;
    left: 50%;
    top: -6%;
    bottom: auto;
    transform: translate(-50%, -50%);
    padding: 0 calc(6 * var(--s));
    font-size: calc(var(--c) * 0.62);
    font-weight: 700;
    color: #fff;
    background: #050b12;
    border-radius: calc(var(--c) * 0.28);
    text-transform: none;
    pointer-events: none;
    z-index: 2;
    transition:
        box-shadow var(--cx-t-state) var(--cx-ease),
        background var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-review .cx-race .chase-agent-badge.cx-chip.is-correct {
    --rgb: var(--cx-correct-rgb);
    background: #053d27;
}
html.cx-v2-review .cx-race .chase-agent-badge.cx-chip.is-wrong {
    --rgb: var(--cx-wrong-rgb);
    background: #3d0615;
}
/* the computer's catch is not the child's doing: its runner just goes,
   and its emblem bursts there in its own colour */
html.cx-v2-review .cx-race .chase-agent.ai-caught {
    opacity: 0;
    transition: opacity 200ms var(--cx-ease);
}
.cx-race__burst {
    position: absolute;
    z-index: 6;
    width: calc(76 * var(--s));
    height: calc(76 * var(--s));
    margin: calc(-38 * var(--s)) 0 0 calc(-38 * var(--s));
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: radial-gradient(circle, rgba(var(--team), 0.35), rgba(var(--team), 0) 70%);
    box-shadow: 0 0 0 calc(3 * var(--s)) rgba(var(--team), 0.8);
    pointer-events: none;
    animation: cx-race-burst 700ms var(--cx-ease) both;
}
@keyframes cx-race-burst {
    0% { transform: scale(0.4); opacity: 0; }
    25% { transform: scale(1.05); opacity: 1; }
    100% { transform: scale(1.35); opacity: 0; }
}
.cx-race__board {
    position: absolute;
    left: 50%;
    bottom: calc(12 * var(--s));
    transform: translateX(-50%);
    z-index: 2;
    display: flex;
    align-items: center;
    gap: calc(22 * var(--s));
    padding: calc(8 * var(--s)) calc(12 * var(--s));
    border-radius: calc(16 * var(--s));
    background: rgba(5, 11, 18, 0.94);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.22);
}
.cx-race__side {
    display: flex;
    align-items: center;
    gap: calc(12 * var(--s));
    padding: calc(6 * var(--s)) calc(14 * var(--s));
    border-radius: calc(12 * var(--s));
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--team), 0.75);
    background: rgba(var(--team), 0.08);
}
.cx-race__side .cx-tile {
    --rgb: var(--team);
    --edge: 0.9;
    --halo: 0.2;
    cursor: default;
}
.cx-race__pips {
    display: inline-flex;
    gap: calc(7 * var(--s));
}
.cx-race__pips > i,
.cx-race__rounds > i {
    width: calc(11 * var(--s));
    height: calc(11 * var(--s));
    border-radius: calc(2 * var(--s));
    transform: rotate(45deg);
    background: rgba(var(--cx-line-rgb), 0.1);
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--team, var(--cx-line-rgb)), 0.5);
}
/* a filled pip is the side's own colour (UIA-7: the dead legacy
   .score-dot rule used to paint them white) */
.cx-race__pips > i.filled {
    background: rgb(var(--team));
    box-shadow: 0 0 calc(8 * var(--s)) rgba(var(--team), 0.75);
}
.cx-race__rounds {
    display: inline-flex;
    gap: calc(10 * var(--s));
}
.cx-race__rounds > i {
    width: calc(15 * var(--s));
    height: calc(15 * var(--s));
    box-shadow: inset 0 0 0 calc(1.5 * var(--s)) rgba(var(--cx-line-rgb), 0.45);
}
.cx-race__rounds > i.is-blue {
    background: rgb(var(--cx-blue-rgb));
    box-shadow: 0 0 calc(8 * var(--s)) rgba(var(--cx-blue-rgb), 0.7);
}
.cx-race__rounds > i.is-red {
    background: rgb(var(--cx-red-rgb));
    box-shadow: 0 0 calc(8 * var(--s)) rgba(var(--cx-red-rgb), 0.7);
}
/* between rounds / the result: the leader large with its stars */
.cx-race__result {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: calc(56 * var(--s));
    margin-bottom: calc(40 * var(--s));
}
.cx-race__insignia {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(28 * var(--s));
    border-radius: calc(26 * var(--s));
    background: linear-gradient(180deg, rgba(8, 17, 27, 0.9), rgba(4, 9, 15, 0.95));
    box-shadow:
        inset 0 0 0 calc(3 * var(--s)) rgba(var(--team), 0.85),
        0 0 calc(34 * var(--s)) rgba(var(--team), 0.4);
    animation: cx-round-in var(--cx-t-enter) var(--cx-ease) backwards;
}
.cx-race__insignia.is-lead {
    width: calc(300 * var(--s));
    height: calc(340 * var(--s));
}
.cx-race__insignia.is-behind {
    width: calc(200 * var(--s));
    height: calc(220 * var(--s));
    opacity: 0.55;
    box-shadow: inset 0 0 0 calc(2 * var(--s)) rgba(var(--team), 0.6);
}
.cx-race__stars {
    display: flex;
    gap: calc(10 * var(--s));
    min-height: calc(34 * var(--s));
}
.cx-race__star {
    display: inline-grid;
    width: calc(34 * var(--s));
    height: calc(34 * var(--s));
    color: var(--cx-look);
    filter: drop-shadow(0 0 calc(6 * var(--s)) rgba(var(--cx-look-rgb), 0.6));
}
.cx-race__insignia.is-behind .cx-race__star {
    width: calc(24 * var(--s));
    height: calc(24 * var(--s));
}
.cx-race__star svg {
    width: 100%;
    height: 100%;
}
.cx-race__go {
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: calc(60 * var(--s));
    transform: translateX(-50%);
}
html.cx-v2-review .cx-race__go .cx-go {
    padding: 0;
    cursor: pointer;
}

/* ----------------------------------------------------------- quick draw -- */
.cx-panel.cx-duel {
    padding-left: calc(60 * var(--s));
    padding-right: calc(60 * var(--s));
}
.cx-grid--duel {
    grid-template-columns: repeat(2, auto);
    gap: calc(52 * var(--s));
}
.cx-duel__pair {
    display: flex;
    gap: calc(64 * var(--s));
    padding: calc(10 * var(--s)) calc(20 * var(--s));
}
.cx-duel__intro {
    cursor: default;
    --edge: 0.5;
    --halo: 0.15;
}
.cx-duel__intro.is-named {
    --edge: 1;
    --halo: 0.6;
    transform: translateY(calc(-8 * var(--s))) scale(1.04);
}
.cx-duel__intro.cx-duel__best.is-named {
    --rgb: var(--cx-correct-rgb);
}
.cx-duel__intro.cx-duel__tricky.is-named {
    --rgb: var(--cx-look-rgb);
}

/* ------------------------------------------------- skip-review choice -- */
html.cx-v2-review #training-content > .cx-choose {
    flex: 1 1 auto;
    align-self: stretch;
    display: grid;
    place-items: center;
}
.cx-choose__row {
    display: flex;
    gap: calc(76 * var(--s));
}
html.cx-v2-review .cx-choice.cx-choose__card {
    width: calc(230 * var(--s));
    height: calc(230 * var(--s));
    min-width: 0;
    padding: 0;
    transition:
        transform var(--cx-t-state) var(--cx-ease),
        opacity var(--cx-t-enter) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-review .cx-choice.cx-choose__card svg {
    width: calc(96 * var(--s));
    height: calc(96 * var(--s));
}
/* hidden until the narration names it; the slot is kept (nothing jumps) */
.cx-choice.is-hidden {
    opacity: 0;
    pointer-events: none;
}
@media (hover: hover) {
    html.cx-v2-review .cx-choice.cx-choose__card:hover {
        --edge: 1;
        --halo: 0.55;
    }
}

/* ------------------------------------------------------------ placement -- */
/* The first-run offer over the platformer (mockup placement-choice): two
   choice cards, the first sigil's hex and a radar, over a light scrim. */
html.cx-v2-placement #placement-choice.cx-place-choice {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(76 * var(--s));
    background: rgba(4, 7, 12, 0.5);
    font-family: var(--cx-font);
}
html.cx-v2-placement .cx-choice.cx-place-card {
    width: calc(230 * var(--s));
    height: calc(230 * var(--s));
    min-width: 0;
    padding: 0;
    cursor: pointer;
    transition:
        transform var(--cx-t-state) var(--cx-ease),
        opacity var(--cx-t-enter) var(--cx-ease),
        box-shadow var(--cx-t-state) var(--cx-ease);
}
html.cx-v2-placement .cx-choice.cx-place-card svg {
    width: calc(96 * var(--s));
    height: calc(96 * var(--s));
}
html.cx-v2-placement .cx-choice.cx-place-card.is-hidden {
    opacity: 0;
    pointer-events: none;
}
@media (hover: hover) {
    html.cx-v2-placement .cx-choice.cx-place-card:hover {
        --edge: 1;
        --halo: 0.55;
    }
}
/* the test (mockup placement-test): the symbol-pick panel with the dashed
   "I don't know" tile set apart by a divider */
.cx-panel.cx-place {
    padding-left: calc(40 * var(--s));
    padding-right: calc(40 * var(--s));
}
.cx-place__body {
    display: flex;
    align-items: center;
    gap: calc(44 * var(--s));
}
.cx-place__divider {
    align-self: stretch;
    width: calc(1.5 * var(--s));
    margin: calc(40 * var(--s)) 0;
    background: rgba(var(--cx-line-rgb), 0.25);
}
.cx-tile.cx-tile--idk {
    --edge: 0;
    --halo: 0;
    color: var(--cx-ink-2);
    background: rgba(8, 17, 27, 0.7);
}
.cx-tile.cx-tile--idk::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: calc(2.5 * var(--s)) dashed rgba(var(--cx-live-rgb), 0.55);
    pointer-events: none;
}
html.cx-v2-placement .cx-place .cx-hit:disabled {
    cursor: default;
}
