/* ═══════════════════════════════════════════════════════════
   CogSmith — Cognitive Training Puzzles
   Complete stylesheet with animations, responsive design,
   and modern UI components. No external dependencies.
   ═══════════════════════════════════════════════════════════ */

/* ──────────────── DESIGN TOKENS ────────────────
   One source of truth for colour, space, type, depth and motion.
   Component rules below consume these and never hard-code a raw
   value, so a change here propagates through the whole app.
   ─────────────────────────────────────────────── */
:root {
    /* ── Colour: palette ── */
    --clr-bg: #0f0e17;
    --clr-surface: #1a1932;
    --clr-surface-alt: #232046;
    --clr-primary: #7f5af0;
    --clr-primary-hover: #6b46d6;
    --clr-secondary: #2cb67d;
    --clr-accent: #ff8906;
    --clr-danger: #e53170;

    /* ── Colour: text ── */
    --clr-text: #fffffe;
    --clr-text-muted: #94a1b2;
    --clr-text-dim: #72757e;

    /* ── Colour: cards & badges ── */
    --clr-card: #1e1c36;
    --clr-card-hover: #2a2750;
    --clr-badge-memory: #7f5af0;
    --clr-badge-focus: #2cb67d;
    --clr-badge-logic: #ff8906;
    --clr-badge-reasoning: #e53170;
    --clr-badge-spatial: #00b4d8;
    --clr-badge-strategy: #f72585;

    /* ── Colour: hairlines & scrims ──
       Named by role, so borders stay consistent instead of each
       component inventing its own rgba(255,255,255,0.0x). */
    --clr-line: rgba(255, 255, 255, 0.08);
    --clr-line-strong: rgba(255, 255, 255, 0.16);
    --clr-line-primary: rgba(127, 90, 240, 0.28);
    --clr-scrim: rgba(8, 7, 16, 0.72);

    /* ── Colour: signature gradients ──
       Previously re-declared inline in a dozen rules. */
    --grad-brand: linear-gradient(135deg, var(--clr-primary), var(--clr-secondary));
    --grad-primary: linear-gradient(135deg, var(--clr-primary), #5a3cc4);
    --grad-success: linear-gradient(135deg, var(--clr-secondary), #1a8c5e);
    --grad-hot: linear-gradient(120deg, var(--clr-primary), var(--clr-danger));

    /* ── Space scale (4px base) ──
       Replaces the previous ad-hoc 0.4/0.55/0.6/0.7/0.9rem values. */
    --sp-1: 0.25rem;   /*  4px */
    --sp-2: 0.5rem;    /*  8px */
    --sp-3: 0.75rem;   /* 12px */
    --sp-4: 1rem;      /* 16px */
    --sp-5: 1.5rem;    /* 24px */
    --sp-6: 2rem;      /* 32px */
    --sp-7: 3rem;      /* 48px */

    /* ── Type scale ── */
    --fs-xs: 0.75rem;
    --fs-sm: 0.85rem;
    --fs-base: 0.95rem;
    --fs-md: 1.05rem;
    --fs-lg: 1.3rem;
    --fs-xl: 1.75rem;
    --fs-2xl: 2.5rem;
    --ff-base: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
    /* Tabular figures for every live-updating number (timer, score,
       counters) so digits never jitter as the value changes. */
    --ff-num: ui-monospace, "SF Mono", "Segoe UI Mono", "Roboto Mono", monospace;

    /* ── Elevation ──
       A real ramp instead of one shared --shadow. Each level pairs a
       tight contact shadow with a wide ambient one. */
    --elev-1: 0 1px 2px rgba(0, 0, 0, 0.30), 0 2px 8px rgba(0, 0, 0, 0.18);
    --elev-2: 0 2px 4px rgba(0, 0, 0, 0.32), 0 8px 24px rgba(0, 0, 0, 0.28);
    --elev-3: 0 4px 8px rgba(0, 0, 0, 0.36), 0 16px 48px rgba(0, 0, 0, 0.40);
    --glow-primary: 0 0 0 1px var(--clr-line-primary), 0 8px 28px rgba(127, 90, 240, 0.28);
    --shadow: var(--elev-2);   /* legacy alias — kept so old rules stay valid */

    /* ── Radii ── */
    --radius: 12px;
    --radius-sm: 8px;
    --radius-lg: 20px;
    --radius-pill: 999px;

    /* ── Motion ──
       Named easings: `out` for things entering/settling, `spring` for
       affordances that should feel physical (press, lift, pop). */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --dur-fast: 0.12s;
    --dur-base: 0.22s;
    --dur-slow: 0.36s;
    --transition: var(--dur-base) cubic-bezier(0.4, 0, 0.2, 1); /* legacy alias */

    /* ── Focus ring ──
       One token drives every focusable element; see the global
       :focus-visible rule in the ACCESSIBILITY section. */
    --focus-ring: 0 0 0 2px var(--clr-bg), 0 0 0 4px var(--clr-primary);

    /* ── Touch ──
       WCAG 2.5.8 / Apple HIG minimum hit area. Interactive controls
       claim at least this much, padding out invisibly where needed. */
    --tap-min: 44px;

    /* ── Safe area (notch / home indicator on iOS, gesture bar on
       Android). This app ships as a Capacitor + Expo native shell, so
       these are load-bearing, not decorative. ── */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);

    /* Memory card flip */
    --card-flip-speed: 0.5s;
}

/* ──────────────── RESET & BASE ──────────────── */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: 16px;
    scroll-behavior: smooth;
    /* Stop iOS from inflating type when the device is rotated. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    font-family: var(--ff-base);
    background: var(--clr-bg);
    color: var(--clr-text);
    /* dvh tracks the *visible* viewport, so the layout no longer sits
       under mobile browser chrome; the vh line is the fallback for
       engines without dvh and must stay first. */
    min-height: 100vh;
    min-height: 100dvh;
    overflow-x: hidden;
    line-height: 1.6;
    /* Kill the grey flash Safari/Chrome paint over every tapped
       element — this app taps constantly and it read as lag. */
    -webkit-tap-highlight-color: transparent;
    /* No rubber-band / pull-to-refresh inside the native shell. */
    overscroll-behavior-y: none;
}

/* ──────────────── SCREEN MANAGEMENT ──────────────── */
.screen {
    display: none;
    width: 100%;
    min-height: 100vh;
    min-height: 100dvh;
    /* Base padding plus whatever the notch / home indicator claims. */
    padding: calc(var(--sp-5) + var(--safe-top)) calc(var(--sp-5) + var(--safe-right))
             calc(var(--sp-5) + var(--safe-bottom)) calc(var(--sp-5) + var(--safe-left));
    animation: fadeIn var(--dur-slow) var(--ease-out);
}

.screen.active {
    display: flex;
    flex-direction: column;
    align-items: center;
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ──────────────── TOUCH BEHAVIOUR ────────────────
   Every interactive surface opts out of the browser's double-tap-to-zoom
   gesture. That gesture costs a ~300ms delay before the click lands —
   fatal in Chroma Veto (1.6s per trial) and Rule Shift, where the delay
   was being scored as the player's reaction time.
   ─────────────────────────────────────────────── */
button,
[role="button"],
.memory-card,
.sliding-tile,
.pattern-option,
.fusion-option,
.mastermind-slot,
.mastermind-color-btn {
    touch-action: manipulation;
}

/* Game surfaces are for tapping, not for selecting. Long-press on a
   tile used to raise the iOS selection/callout UI mid-round. */
.memory-card,
.sliding-tile,
.pattern-item,
.pattern-option,
.fusion-cell,
.fusion-option,
.mastermind-peg,
.forge-tile,
.forge-slot,
.shift-frame,
.flux-display,
.game-card-icon,
.stat-value,
.info-value {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

/* ──────────────── ACCESSIBILITY ────────────────
   The stylesheet previously defined no focus styles at all, so keyboard
   and switch users navigated ~200 controls against whatever default ring
   the engine drew — largely invisible on this near-black background.

   :focus-visible (not :focus) means pointer users never see a ring after
   a click, while keyboard users always do.
   ─────────────────────────────────────────────── */
:where(a, button, input, select, textarea, [tabindex]):focus-visible,
.memory-card:focus-visible,
.pattern-option:focus-visible,
.fusion-option:focus-visible,
.mastermind-slot:focus-visible,
.sliding-tile:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    /* Lift the ring above neighbouring tiles in a tight grid. */
    position: relative;
    z-index: 3;
}

/* Round elements get a ring that follows their shape. */
.mastermind-peg:focus-visible,
.mastermind-color-btn:focus-visible,
.badge:focus-visible {
    border-radius: 50%;
}

/* Visually hidden, but announced by screen readers. Used for the live
   regions that voice score and timer changes. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Skip link — first tab stop, so keyboard users can jump the header
   and land directly on the game grid. */
.skip-link {
    position: absolute;
    top: var(--sp-2);
    left: var(--sp-2);
    z-index: 3000;
    padding: var(--sp-2) var(--sp-4);
    background: var(--clr-primary);
    color: #fff;
    border-radius: var(--radius-sm);
    font-weight: 600;
    text-decoration: none;
    transform: translateY(-150%);
    transition: transform var(--dur-base) var(--ease-out);
}

.skip-link:focus-visible {
    transform: translateY(0);
}

/* Minimum hit area for small controls, applied without changing their
   visual size: the pseudo-element grows the touch target outward. */
.btn-diff,
.btn-tab,
.modal-close {
    position: relative;
}

.btn-diff::after,
.btn-tab::after,
.modal-close::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: var(--tap-min);
    min-height: var(--tap-min);
    transform: translate(-50%, -50%);
}

/* ──────────────── MAIN MENU ──────────────── */

/* Ambient depth. Two fixed, non-interactive colour washes keep the
   near-black background from reading as flat grey on OLED panels.
   Painted on a pseudo-element so nothing inherits or intercepts. */
#menu-screen::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(60rem 30rem at 50% -10%, rgba(127, 90, 240, 0.16), transparent 70%),
        radial-gradient(40rem 24rem at 90% 10%, rgba(44, 182, 125, 0.10), transparent 70%);
}

/* ──────────────── APP BAR ────────────────
   The menu header is a real, full-width bar: brand on the left, identity
   on the right, both in normal flow.

   It used to be a centred block with the profile chip absolutely
   positioned at `right: 0`. #menu-screen is a column flex container with
   align-items:center, so this header shrink-wrapped to the width of its
   own widest child — the ~34ch tagline. `right: 0` therefore resolved to
   the right edge of that narrow box, roughly the middle of the viewport,
   and the chip sat squarely on top of the centred wordmark at every width
   above the 720px breakpoint that reset it to position:static.

   Two elements in the same flex row cannot overlap, so the class of bug
   is gone rather than nudged out of view.
   ─────────────────────────────────────── */
.menu-header {
    width: 100%;
    /* Matches .game-cards, so the brand lines up with the grid edge. */
    max-width: 960px;
    margin-bottom: var(--sp-5);
}

.app-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    width: 100%;
    min-height: var(--tap-min);
}

.app-bar-brand {
    display: flex;
    align-items: center;
    /* Lets the wordmark shrink before the chip does, so a long display
       name never pushes the chip off the right edge. */
    min-width: 0;
}

.logo {
    font-size: var(--fs-2xl);
    font-weight: 800;
    letter-spacing: -0.02em;
    background: var(--grad-brand);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    /* Fallback: if background-clip:text is unsupported the transparent
       fill would render the wordmark invisible. Painting a real colour
       first means the worst case is a solid-purple logo, not a gone one. */
    color: var(--clr-primary);
    margin-bottom: var(--sp-1);
}

.app-bar .logo {
    margin-bottom: 0;
    /* The wordmark is two words; wrapping it mid-brand looks broken. */
    white-space: nowrap;
}

.tagline {
    color: var(--clr-text-muted);
    font-size: var(--fs-md);
    max-width: 34ch;
    margin: 0 auto;
    text-wrap: balance;
}

/* In the bar the tagline is a subtitle under the brand, so it aligns
   left with it rather than centring against the whole 960px row. */
.menu-header .tagline {
    max-width: 48ch;
    margin: var(--sp-2) 0 0;
}

/* Stats bar */
.stats-bar {
    display: flex;
    gap: var(--sp-3);
    margin-bottom: var(--sp-6);
    flex-wrap: wrap;
    justify-content: center;
}

.stat-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    background: linear-gradient(180deg, var(--clr-surface-alt), var(--clr-surface));
    border: 1px solid var(--clr-line);
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--radius);
    min-width: 120px;
    box-shadow: var(--elev-1);
}

.stat-label {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--clr-text-muted);
    order: 2;            /* value reads first, label captions it */
}

.stat-value {
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--clr-secondary);
    font-family: var(--ff-num);
    font-variant-numeric: tabular-nums;
    order: 1;
}

/* Game selection cards */
.game-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--sp-4);
    width: 100%;
    max-width: 960px;
    margin-bottom: var(--sp-6);
}

.game-card {
    position: relative;
    isolation: isolate;
    background: var(--clr-card);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-lg);
    padding: var(--sp-6) var(--sp-5);
    text-align: center;
    cursor: pointer;
    overflow: hidden;
    box-shadow: var(--elev-1);
    transition:
        transform var(--dur-base) var(--ease-out),
        border-color var(--dur-base) var(--ease-out),
        box-shadow var(--dur-base) var(--ease-out),
        background var(--dur-base) var(--ease-out);
}

/* A soft brand wash that fades in on hover/focus, so the lift reads as
   illumination rather than a plain colour swap. */
.game-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: 0;
    background: radial-gradient(24rem 12rem at 50% 0%, rgba(127, 90, 240, 0.22), transparent 70%);
    transition: opacity var(--dur-base) var(--ease-out);
}

/* Hover lift is gated behind a real pointer. On touch, :hover sticks
   after a tap and left cards stuck in the raised state. */
@media (hover: hover) {
    .game-card:hover {
        background: var(--clr-card-hover);
        border-color: var(--clr-primary);
        transform: translateY(-4px);
        box-shadow: var(--elev-3);
    }

    .game-card:hover::before { opacity: 1; }
}

.game-card:focus-within {
    border-color: var(--clr-primary);
    box-shadow: var(--glow-primary);
}

.game-card:focus-within::before { opacity: 1; }

/* Press feedback — the one state touch users actually get. */
.game-card:active {
    transform: translateY(-1px) scale(0.99);
    box-shadow: var(--elev-1);
}

.game-card-icon {
    font-size: 3.5rem;
    line-height: 1;
    margin-bottom: var(--sp-3);
    /* Grounds the emoji against the card so it reads as an object. */
    filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
    transition: transform var(--dur-slow) var(--ease-spring);
}

@media (hover: hover) {
    .game-card:hover .game-card-icon {
        transform: translateY(-2px) scale(1.08);
    }
}

.game-card h2 {
    font-size: var(--fs-lg);
    letter-spacing: -0.01em;
    margin-bottom: var(--sp-2);
}

.game-card p {
    color: var(--clr-text-muted);
    font-size: 0.9rem;
    margin-bottom: var(--sp-4);
    line-height: 1.5;
    text-wrap: pretty;
}

.game-card-meta {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-bottom: var(--sp-3);
    flex-wrap: wrap;
}

/* Your own record on the card, written by _renderCardStats(). This is what
   turns the grid from a brochure — cards that read identically to a
   first-time visitor and to someone 200 plays in — into a dashboard. */
/* Scoped through .game-card on purpose: the stat line is a <p>, and the
   generic `.game-card p` rule above is (0,1,1) — it outranks a bare
   `.game-card-stat` (0,1,0) and would silently win on colour, size and
   spacing, rendering the metric as ordinary body copy. */
.game-card .game-card-stat {
    font-size: var(--fs-xs);
    color: var(--clr-text-muted);
    font-family: var(--ff-num);
    font-variant-numeric: tabular-nums;
    margin-bottom: var(--sp-3);
    line-height: 1.4;
    /* Hold the line's height before the text is written, so cards do not
       jump as the menu paints. */
    min-height: 1.25em;
}

/* An unplayed game reads as an invitation, not as a score of zero. */
.game-card .game-card-stat.is-new {
    font-family: var(--ff-base);
    font-style: italic;
    color: var(--clr-text-dim);
}

/* Improvement is celebrated; a dip is left neutral on purpose. Colouring a
   downward trend red would punish the exact user this app most needs to
   keep — the one having an off day. */
.game-card .game-card-stat[data-trend="up"] {
    color: var(--clr-secondary);
}

/* ── The whole card is the hit target ──
   The card already advertised itself as pressable (cursor: pointer, a
   :active press animation, a hover lift) but carried no handler: only the
   ~90px Play button did anything, so most taps on a card landed on dead
   space. Stretching the button's own ::after over the card makes the
   advertised target real without adding a second tab stop or nesting one
   control inside another. */
.game-card-play::after {
    content: "";
    position: absolute;
    inset: 0;
}

/* Badges */
.badge {
    font-size: 0.7rem;
    padding: 0.25rem 0.7rem;
    border-radius: 50px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.badge-memory   { background: rgba(127, 90, 240, 0.2); color: var(--clr-badge-memory); }
.badge-focus    { background: rgba(44, 182, 125, 0.2); color: var(--clr-badge-focus); }
.badge-logic    { background: rgba(255, 137, 6, 0.2);  color: var(--clr-badge-logic); }
.badge-reasoning{ background: rgba(229, 49, 112, 0.2); color: var(--clr-badge-reasoning); }
.badge-spatial  { background: rgba(0, 180, 216, 0.2);  color: var(--clr-badge-spatial); }
.badge-strategy { background: rgba(247, 37, 133, 0.2); color: var(--clr-badge-strategy); }

/* ──────────────── BUTTONS ──────────────── */
.btn {
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-weight: 600;
    border-radius: var(--radius-sm);
    font-size: var(--fs-base);
    /* Every button clears the minimum hit area, regardless of its label
       length — this alone fixed a set of controls that were ~30px tall. */
    min-height: var(--tap-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    transition:
        background var(--dur-fast) var(--ease-out),
        transform var(--dur-fast) var(--ease-out),
        box-shadow var(--dur-fast) var(--ease-out),
        color var(--dur-fast) var(--ease-out);
}

/* A disabled control must look inert and stop accepting pointer events;
   several call sites set [disabled] but nothing styled it globally. */
.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.45;
    cursor: not-allowed;
    transform: none !important;
    pointer-events: none;
}

.btn-primary {
    background: var(--clr-primary);
    color: #fff;
    padding: var(--sp-3) var(--sp-6);
    box-shadow: var(--elev-1);
}

@media (hover: hover) {
    .btn-primary:hover {
        background: var(--clr-primary-hover);
        transform: translateY(-1px);
        box-shadow: var(--elev-2);
    }
}

/* Press states give touch users the feedback hover never provides. */
.btn-primary:active {
    transform: translateY(1px) scale(0.98);
    box-shadow: none;
}

.btn-secondary {
    background: var(--clr-surface);
    color: var(--clr-text);
    padding: var(--sp-3) var(--sp-6);
    border: 1px solid var(--clr-line);
}

@media (hover: hover) {
    .btn-secondary:hover {
        background: var(--clr-surface-alt);
        border-color: var(--clr-line-strong);
    }
}

.btn-secondary:active {
    transform: translateY(1px) scale(0.98);
}

.btn-danger {
    background: rgba(229, 49, 112, 0.15);
    color: var(--clr-danger);
    padding: 0.5rem 1.2rem;
    font-size: 0.8rem;
}

@media (hover: hover) {
    .btn-danger:hover {
        background: rgba(229, 49, 112, 0.3);
    }
}

.btn-danger:active {
    transform: scale(0.97);
}

.btn-icon {
    background: var(--clr-surface);
    color: var(--clr-text);
    padding: var(--sp-2) var(--sp-4);
    min-width: var(--tap-min);
    font-size: 0.9rem;
    border: 1px solid var(--clr-line);
}

@media (hover: hover) {
    .btn-icon:hover {
        background: var(--clr-surface-alt);
        border-color: var(--clr-line-strong);
    }
}

.btn-icon:active {
    transform: scale(0.94);
}

.btn-leaderboard {
    margin-top: 0.5rem;
    font-size: 1rem;
}

.btn-diff {
    background: transparent;
    color: var(--clr-text-muted);
    padding: 0.4rem 1.2rem;
    border: 1px solid rgba(255, 255, 255, 0.1);
    font-size: 0.85rem;
}

.btn-diff.active {
    background: var(--clr-primary);
    color: #fff;
    border-color: var(--clr-primary);
}

@media (hover: hover) {
    .btn-diff:hover:not(.active) {
        background: var(--clr-surface-alt);
    }
}

.btn-diff:active {
    transform: scale(0.96);
}

.btn-tab {
    background: transparent;
    color: var(--clr-text-muted);
    padding: 0.5rem 1.2rem;
    font-size: 0.85rem;
    border-bottom: 2px solid transparent;
    border-radius: 0;
}

.btn-tab.active {
    color: var(--clr-primary);
    border-bottom-color: var(--clr-primary);
}

.btn-small {
    margin-top: 1rem;
}

/* ──────────────── GAME TOPBAR ──────────────── */
.game-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 900px;
    margin-bottom: var(--sp-3);
    gap: var(--sp-4);
    flex-wrap: wrap;
}

.game-info {
    display: flex;
    gap: var(--sp-2);
    background: var(--clr-surface);
    border: 1px solid var(--clr-line);
    border-radius: var(--radius);
    padding: var(--sp-2) var(--sp-3);
    box-shadow: var(--elev-1);
}

.info-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 4.5rem;
    padding: 0 var(--sp-2);
}

/* Hairline separators between the readouts, drawn only between
   siblings so the group never ends on a stray rule. */
.info-group + .info-group {
    border-left: 1px solid var(--clr-line);
}

.info-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--clr-text-muted);
    line-height: 1.4;
}

.info-value {
    font-size: var(--fs-lg);
    font-weight: 700;
    line-height: 1.2;
    color: var(--clr-accent);
    /* Monospaced + tabular so a rising score or ticking clock never
       shifts the layout of the elements beside it. */
    font-family: var(--ff-num);
    font-variant-numeric: tabular-nums;
}

.topbar-actions {
    display: flex;
    gap: 0.5rem;
}

/* Difficulty bar */
.difficulty-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.difficulty-label {
    color: var(--clr-text-muted);
    font-size: 0.85rem;
    margin-right: 0.3rem;
}

/* ──────────────── GAME AREAS ──────────────── */
.game-area {
    display: none;
    width: 100%;
    max-width: 700px;
    justify-content: center;
}

.game-area.active {
    display: flex;
}

/* ──────────────── MEMORY MATCH ──────────────── */
.memory-grid {
    display: grid;
    gap: 10px;
    width: 100%;
    max-width: 560px;
    margin: 0 auto;
}

/* Grid columns set dynamically via JS: grid-template-columns */

.memory-card {
    aspect-ratio: 1;
    perspective: 800px;
    cursor: pointer;
}

.memory-card-inner {
    position: relative;
    width: 100%;
    height: 100%;
    transition: transform var(--card-flip-speed) ease;
    transform-style: preserve-3d;
    border-radius: var(--radius);
}

.memory-card.flipped .memory-card-inner {
    transform: rotateY(180deg);
}

.memory-card-face {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius);
    backface-visibility: hidden;
    font-size: 2rem;
    font-weight: 700;
    user-select: none;
}

.memory-card-front {
    background: linear-gradient(135deg, var(--clr-primary), #5a3cc4);
    color: rgba(255, 255, 255, 0.3);
    font-size: 1.5rem;
}

.memory-card-back {
    background: var(--clr-surface);
    border: 2px solid rgba(127, 90, 240, 0.3);
    transform: rotateY(180deg);
}

.memory-card.matched .memory-card-inner {
    transform: rotateY(180deg);
}

.memory-card.matched .memory-card-back {
    border-color: var(--clr-secondary);
    background: rgba(44, 182, 125, 0.15);
    animation: matchPulse 0.5s ease;
}

@keyframes matchPulse {
    0%, 100% { transform: rotateY(180deg) scale(1); }
    50%      { transform: rotateY(180deg) scale(1.08); }
}

.memory-card.wrong .memory-card-back {
    border-color: var(--clr-danger);
    animation: shake 0.4s ease;
}

@keyframes shake {
    0%, 100% { transform: rotateY(180deg) translateX(0); }
    25%      { transform: rotateY(180deg) translateX(-6px); }
    75%      { transform: rotateY(180deg) translateX(6px); }
}

/* ──────────────── PATTERN SEQUENCE ──────────────── */
.pattern-container {
    width: 100%;
    max-width: 600px;
    text-align: center;
}

.pattern-type-label {
    font-size: 0.85rem;
    color: var(--clr-text-muted);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-bottom: 0.5rem;
}

.pattern-sequence {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
    min-height: 80px;
}

.pattern-item {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    font-size: 1.4rem;
    font-weight: 700;
    background: var(--clr-surface);
    border: 2px solid rgba(255, 255, 255, 0.1);
    transition: var(--transition);
}

.pattern-item.blank {
    border-style: dashed;
    border-color: var(--clr-primary);
    color: var(--clr-primary);
    font-size: 1.8rem;
}

.pattern-item.color-swatch {
    border: 3px solid rgba(255, 255, 255, 0.15);
}

.pattern-options {
    display: flex;
    gap: 0.8rem;
    justify-content: center;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.pattern-option {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    font-size: 1.3rem;
    font-weight: 700;
    background: var(--clr-card);
    border: 2px solid rgba(255, 255, 255, 0.1);
    cursor: pointer;
    transition: var(--transition);
}

/* Gated: an ungated :hover sticks after a tap on touch, so the answer the
   player just chose stayed scaled up while the .correct/.wrong feedback
   painted over it — and was still scaled on the next round. */
@media (hover: hover) {
    .pattern-option:hover {
        border-color: var(--clr-primary);
        transform: scale(1.08);
    }
}

.pattern-option:active {
    transform: scale(0.94);
}

.pattern-option.correct {
    border-color: var(--clr-secondary);
    background: rgba(44, 182, 125, 0.2);
    animation: matchPulse 0.4s ease;
}

.pattern-option.incorrect {
    border-color: var(--clr-danger);
    animation: shake 0.4s ease;
}

.pattern-feedback {
    min-height: 2rem;
    font-size: 1.1rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.pattern-feedback.correct { color: var(--clr-secondary); }
.pattern-feedback.incorrect { color: var(--clr-danger); }

.pattern-progress {
    color: var(--clr-text-muted);
    font-size: 0.9rem;
}

/* ──────────────── SLIDING TILE PUZZLE ──────────────── */
.sliding-grid {
    display: grid;
    gap: 4px;
    width: 100%;
    max-width: 400px;
    margin: 0 auto;
    background: var(--clr-surface);
    padding: 6px;
    border-radius: var(--radius);
}

.sliding-tile {
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    font-weight: 700;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: transform 0.15s ease, background 0.2s ease;
    user-select: none;
}

.sliding-tile.numbered {
    background: linear-gradient(135deg, var(--clr-primary), #5a3cc4);
    color: #fff;
}

@media (hover: hover) {
    .sliding-tile.numbered:hover {
        transform: scale(0.96);
    }
}

.sliding-tile.numbered:active {
    transform: scale(0.92);
}

.sliding-tile.empty {
    background: rgba(255, 255, 255, 0.03);
    cursor: default;
}

.sliding-tile.correct-pos {
    background: linear-gradient(135deg, var(--clr-secondary), #1a8c5e);
}

.sliding-target {
    text-align: center;
    margin-top: 1rem;
}

.target-label {
    color: var(--clr-text-muted);
    font-size: 0.85rem;
}

/* Tile slide animation */
@keyframes tileSlide {
    0%   { transform: scale(0.92); }
    100% { transform: scale(1); }
}

.sliding-tile.just-moved {
    animation: tileSlide 0.15s ease;
}

/* ──────────────── MASTERMIND CODE BREAKER ──────────────── */
.mastermind-container {
    width: 100%;
    max-width: 560px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.mastermind-status {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.6rem 1rem;
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
    color: var(--clr-text-muted);
    flex-wrap: wrap;
    gap: 0.5rem;
}

.mastermind-status strong {
    color: var(--clr-accent);
}

.mastermind-history {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-height: 380px;
    overflow-y: auto;
    padding-right: 0.25rem;
}

.mastermind-guess-row,
.mastermind-input-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    border: 1px solid rgba(255, 255, 255, 0.05);
}

.mastermind-guess-row {
    animation: fadeIn 0.3s ease;
}

.mastermind-input-row {
    border-color: var(--clr-primary);
    border-style: dashed;
    background: var(--clr-surface-alt);
}

.mastermind-guess-num {
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--clr-text-dim);
    min-width: 1.5rem;
    text-align: center;
}

.mastermind-pegs {
    display: flex;
    gap: 0.4rem;
}

.mastermind-peg {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.15);
    transition: var(--transition);
}

.mastermind-slot {
    background: rgba(255, 255, 255, 0.05);
    cursor: pointer;
    border-style: dashed;
    border-color: rgba(255, 255, 255, 0.2);
}

.mastermind-slot.filled {
    border-style: solid;
}

@media (hover: hover) {
    .mastermind-slot:hover {
        border-color: var(--clr-primary);
        transform: scale(1.1);
    }
}

.mastermind-slot:active {
    transform: scale(0.94);
}

.mastermind-feedback-dots {
    display: flex;
    gap: 0.25rem;
    flex-wrap: wrap;
    min-width: 80px;
    justify-content: center;
    margin-left: auto;
}

.mastermind-dot {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.1);
    animation: dotAppear 0.3s ease;
}

@keyframes dotAppear {
    from { transform: scale(0); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

.dot-black {
    background: #ff4444;
    border-color: #ff4444;
    box-shadow: 0 0 6px rgba(255, 68, 68, 0.5);
}

.dot-white {
    background: #ffffff;
    border-color: #ffffff;
    box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
}

.dot-empty {
    background: rgba(255, 255, 255, 0.08);
}

.dot-hidden {
    background: rgba(255, 255, 255, 0.08);
    position: relative;
}

.dot-hidden::after {
    content: "?";
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.55rem;
    color: var(--clr-text-dim);
}

.mastermind-feedback-text {
    font-size: 0.8rem;
    white-space: nowrap;
    color: var(--clr-text-muted);
}

.mastermind-feedback-hidden {
    font-size: 0.75rem;
    color: var(--clr-text-dim);
    font-style: italic;
}

.mastermind-input-actions {
    display: flex;
    gap: 0.4rem;
    margin-left: auto;
}

.mastermind-submit,
.mastermind-clear {
    padding: 0.4rem 0.8rem !important;
    font-size: 0.85rem !important;
    min-width: 2.2rem;
}

.mastermind-submit:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.mastermind-picker {
    text-align: center;
    padding: 1rem;
    background: var(--clr-surface);
    border-radius: var(--radius);
    border: 1px solid rgba(255, 255, 255, 0.05);
}

.mastermind-picker-label {
    font-size: 0.8rem;
    color: var(--clr-text-muted);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-bottom: 0.6rem;
}

.mastermind-picker-colors {
    display: flex;
    gap: 0.6rem;
    justify-content: center;
    flex-wrap: wrap;
}

.mastermind-color-btn {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    cursor: pointer;
    border: 3px solid rgba(255, 255, 255, 0.15);
    transition: var(--transition);
}

@media (hover: hover) {
    .mastermind-color-btn:hover {
        transform: scale(1.15);
        border-color: #fff;
        box-shadow: 0 0 12px rgba(255, 255, 255, 0.3);
    }
}

.mastermind-color-btn:active {
    transform: scale(0.92);
}

.mastermind-reveal {
    text-align: center;
    padding: 1rem;
    background: rgba(127, 90, 240, 0.1);
    border-radius: var(--radius);
    border: 1px solid rgba(127, 90, 240, 0.3);
}

.mastermind-reveal-label {
    font-size: 0.85rem;
    color: var(--clr-text-muted);
    text-transform: uppercase;
    letter-spacing: 1px;
    display: block;
    margin-bottom: 0.5rem;
}

.mastermind-code-row {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
}

/* ──────────────── COGNITIVE FUSION ──────────────── */
.fusion-container {
    width: 100%;
    max-width: 700px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.fusion-status {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    padding: 0.7rem 1rem;
    color: var(--clr-text-muted);
    font-size: 0.85rem;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.fusion-status strong {
    color: var(--clr-accent);
}

.fusion-stage {
    text-align: center;
    padding: 0.7rem 1rem;
    border-radius: var(--radius-sm);
    background: rgba(127, 90, 240, 0.12);
    border: 1px solid rgba(127, 90, 240, 0.3);
    color: var(--clr-text);
}

.fusion-stage-title {
    font-weight: 700;
    margin-bottom: 0.2rem;
}

.fusion-stage-sub {
    color: var(--clr-text-muted);
    font-size: 0.85rem;
}

.fusion-sequence {
    display: grid;
    grid-template-columns: repeat(2, minmax(160px, 1fr));
    gap: 0.9rem;
}

.fusion-frame {
    background: var(--clr-surface);
    border-radius: var(--radius);
    border: 1px solid rgba(255, 255, 255, 0.06);
    padding: 0.7rem;
}

.fusion-frame-label {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--clr-text-muted);
    margin-bottom: 0.5rem;
    text-align: center;
}

.fusion-board {
    display: grid;
    gap: 5px;
    background: rgba(255, 255, 255, 0.03);
    border-radius: var(--radius-sm);
    padding: 6px;
}

.fusion-cell {
    aspect-ratio: 1;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.06);
}

.fusion-cell.on {
    background: linear-gradient(135deg, var(--clr-secondary), #1a8c5e);
    box-shadow: 0 0 10px rgba(44, 182, 125, 0.4);
}

.fusion-cell.masked {
    background: rgba(255, 255, 255, 0.05);
    border-style: dashed;
}

.fusion-cell.masked::after {
    content: "?";
    display: flex;
    width: 100%;
    height: 100%;
    align-items: center;
    justify-content: center;
    color: var(--clr-text-dim);
    font-size: 0.85rem;
}

.fusion-options {
    display: grid;
    grid-template-columns: repeat(2, minmax(160px, 1fr));
    gap: 0.9rem;
}

.fusion-option {
    background: var(--clr-card);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--radius);
    padding: 0.7rem;
    transition: var(--transition);
    cursor: pointer;
}

@media (hover: hover) {
    .fusion-option:hover {
        border-color: var(--clr-primary);
        transform: translateY(-3px);
    }
}

.fusion-option:active {
    transform: translateY(0) scale(0.97);
}

.fusion-option.correct {
    border-color: var(--clr-secondary);
    box-shadow: 0 0 0 2px rgba(44, 182, 125, 0.25);
}

.fusion-option.incorrect {
    border-color: var(--clr-danger);
    animation: shake 0.4s ease;
}

.fusion-option.disabled {
    pointer-events: none;
    opacity: 0.65;
}

.fusion-option-label {
    font-size: 0.8rem;
    color: var(--clr-text-muted);
    text-align: center;
    margin-top: 0.4rem;
}

.fusion-feedback {
    min-height: 1.6rem;
    text-align: center;
    font-weight: 700;
}

.fusion-feedback.correct {
    color: var(--clr-secondary);
}

.fusion-feedback.incorrect {
    color: var(--clr-danger);
}

/* ──────────────── NEURO WEAVE ──────────────── */
.neuro-container {
    width: 100%;
    max-width: 760px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.neuro-status {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    padding: 0.7rem 1rem;
    color: var(--clr-text-muted);
    font-size: 0.85rem;
}

.neuro-status strong {
    color: var(--clr-accent);
}

.neuro-phase {
    background: rgba(44, 182, 125, 0.1);
    border: 1px solid rgba(44, 182, 125, 0.25);
    border-radius: var(--radius-sm);
    padding: 0.7rem 1rem;
    text-align: center;
}

.neuro-phase-title {
    font-weight: 700;
}

.neuro-phase-sub {
    color: var(--clr-text-muted);
    font-size: 0.84rem;
}

.neuro-cipher-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 0.7rem;
}

.neuro-cipher-item {
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    border: 1px solid rgba(255, 255, 255, 0.06);
    padding: 0.8rem;
    text-align: center;
}

.neuro-symbol {
    font-size: 1.7rem;
    margin-bottom: 0.2rem;
}

.neuro-digit {
    color: var(--clr-secondary);
    font-weight: 700;
}

.neuro-sequence-box {
    background: var(--clr-surface);
    border-radius: var(--radius);
    padding: 0.9rem;
    border: 1px solid rgba(255, 255, 255, 0.08);
}

.neuro-sequence-label {
    color: var(--clr-text-muted);
    text-transform: uppercase;
    font-size: 0.8rem;
    margin-bottom: 0.5rem;
    letter-spacing: 1px;
}

.neuro-sequence-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    justify-content: center;
}

.neuro-token {
    width: 54px;
    height: 54px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--clr-card);
    border: 1px solid rgba(255, 255, 255, 0.1);
    font-size: 1.35rem;
}

.neuro-token.blank {
    border-style: dashed;
    border-color: var(--clr-primary);
    color: var(--clr-primary);
    font-size: 1.2rem;
}

.neuro-options {
    display: grid;
    grid-template-columns: repeat(4, minmax(90px, 1fr));
    gap: 0.7rem;
}

.neuro-option {
    background: var(--clr-card);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--radius-sm);
    padding: 0.8rem 0.4rem;
    text-align: center;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .neuro-option:hover {
        border-color: var(--clr-primary);
        transform: translateY(-2px);
    }
}

.neuro-option:active {
    transform: translateY(0) scale(0.97);
}

.neuro-option.correct {
    border-color: var(--clr-secondary);
    box-shadow: 0 0 0 2px rgba(44, 182, 125, 0.22);
}

.neuro-option.incorrect {
    border-color: var(--clr-danger);
    animation: shake 0.35s ease;
}

.neuro-option.disabled {
    pointer-events: none;
    opacity: 0.65;
}

.neuro-option-value {
    margin-top: 0.3rem;
    font-size: 0.72rem;
    color: var(--clr-text-dim);
}

.neuro-feedback {
    min-height: 1.4rem;
    text-align: center;
    font-weight: 700;
}

.neuro-feedback.correct { color: var(--clr-secondary); }
.neuro-feedback.incorrect { color: var(--clr-danger); }

/* ──────────────── MODALS ──────────────── */
.modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: var(--clr-scrim);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    align-items: center;
    justify-content: center;
    /* Respect the notch: a full-screen overlay is exactly where a modal
       would otherwise slide under the status bar / home indicator. */
    padding: calc(var(--sp-4) + var(--safe-top)) calc(var(--sp-4) + var(--safe-right))
             calc(var(--sp-4) + var(--safe-bottom)) calc(var(--sp-4) + var(--safe-left));
    animation: fadeIn var(--dur-base) var(--ease-out);
}

.modal.active {
    display: flex;
}

.modal-content {
    background: var(--clr-surface);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-lg);
    padding: var(--sp-6);
    max-width: 520px;
    width: 100%;
    /* dvh so a tall modal is not clipped by mobile browser chrome. */
    max-height: 80vh;
    max-height: min(80dvh, 100% - var(--sp-4));
    overflow-y: auto;
    /* Keep momentum scrolling inside the card instead of chaining out
       to the page behind it. */
    overscroll-behavior: contain;
    position: relative;
    box-shadow: var(--elev-3);
    /* The card animates in on its own curve rather than just riding the
       backdrop fade — it now reads as arriving, not blinking. */
    animation: modalIn var(--dur-slow) var(--ease-spring) both;
}

@keyframes modalIn {
    from { opacity: 0; transform: translateY(16px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-close {
    position: absolute;
    top: var(--sp-2);
    right: var(--sp-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap-min);
    height: var(--tap-min);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--clr-text-muted);
    font-size: 1.2rem;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out);
    /* The modal card scrolls; the close affordance must not scroll away. */
    z-index: 2;
}

@media (hover: hover) {
    .modal-close:hover {
        color: var(--clr-text);
        background: var(--clr-surface-alt);
    }
}

.modal-close:active {
    transform: scale(0.92);
}

/* Instructions */
.instructions-body {
    color: var(--clr-text-muted);
    line-height: 1.8;
}

.instructions-body h3 {
    color: var(--clr-text);
    margin-top: 1rem;
    margin-bottom: 0.3rem;
}

.instructions-body ul {
    padding-left: 1.2rem;
}

/* Results */
.results-content {
    text-align: center;
}

.results-body {
    margin: 1.5rem 0;
}

.results-body .result-row {
    display: flex;
    justify-content: space-between;
    padding: 0.4rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    color: var(--clr-text-muted);
}

.results-body .result-row span:last-child {
    color: var(--clr-text);
    font-weight: 600;
}

.results-actions {
    display: flex;
    gap: 0.8rem;
    justify-content: center;
}

/* New high score flash */
.new-high-score {
    color: var(--clr-accent);
    font-weight: 700;
    animation: pulse 1s ease infinite;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.5; }
}

/* ──────────────── CONFIRM DIALOG ────────────────
   The in-app stand-in for window.confirm(). Deliberately narrow and
   centred so it reads as a decision, not as a panel of content.
   ─────────────────────────────────────────────── */
.confirm-content {
    max-width: 400px;
    text-align: center;
}

.confirm-message {
    color: var(--clr-text-muted);
    margin: var(--sp-3) 0 var(--sp-5);
    text-wrap: pretty;
}

/* A filled destructive action, distinct from the existing subtle
   .btn-danger used for the low-stakes inline "Clear Scores" trigger.
   Destructive confirmation deserves to look destructive. */
.btn-danger-solid {
    background: var(--clr-danger);
    color: #fff;
}

@media (hover: hover) {
    .btn-danger-solid:hover {
        background: #c4285e;
    }
}

/* Leaderboard */
.leaderboard-content {
    max-width: 480px;
}

/* One tab per game wrapped onto three rows inside a 480px dialog, pushing
   the scores themselves below the fold on a phone — the tab chrome was
   taller than the content it filtered. One scrolling row instead, the
   standard segmented-scroller pattern; the partially-visible next tab is
   what signals that it scrolls. Because the strip never wraps, it absorbs
   new games without re-growing (it is fifteen tabs wide as of the
   Planning/Sequencing/Speed/Prospective additions); showLeaderboardTab()
   scrolls the selected one into view so it is never stranded off-screen. */
.leaderboard-tabs {
    display: flex;
    flex-wrap: nowrap;
    gap: 0;
    margin-bottom: 1rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* The strip is only 40px tall; a scrollbar across it is pure noise. */
    scrollbar-width: none;
}

.leaderboard-tabs::-webkit-scrollbar {
    display: none;
}

.leaderboard-tabs .btn-tab {
    flex: 0 0 auto;
    scroll-snap-align: start;
    white-space: nowrap;
}

.leaderboard-body {
    min-height: 200px;
}

.lb-entry {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.lb-rank {
    font-size: 1.1rem;
    font-weight: 700;
    width: 2rem;
    text-align: center;
    color: var(--clr-text-muted);
}

.lb-rank.gold   { color: #ffd700; }
.lb-rank.silver { color: #c0c0c0; }
.lb-rank.bronze { color: #cd7f32; }

.lb-details {
    flex: 1;
}

.lb-score {
    font-weight: 700;
    color: var(--clr-secondary);
}

.lb-meta {
    font-size: 0.75rem;
    color: var(--clr-text-dim);
}

.lb-empty {
    text-align: center;
    color: var(--clr-text-dim);
    padding: 2rem 0;
}

/* ──────────────── TOAST NOTIFICATIONS ──────────────── */
.toast {
    position: fixed;
    /* Clear the home indicator / gesture bar — a fixed 2rem put the toast
       underneath it on every notched phone, and this app ships as a
       native shell. */
    bottom: calc(2rem + var(--safe-bottom));
    left: 50%;
    transform: translateX(-50%) translateY(100px);
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    padding: 0.8rem 1.5rem;
    border-radius: var(--radius);
    font-weight: 600;
    z-index: 2000;
    opacity: 0;
    transition: transform 0.4s ease, opacity 0.4s ease;
    pointer-events: none;
    border: 1px solid rgba(127, 90, 240, 0.2);
}

.toast.show {
    transform: translateX(-50%) translateY(0);
    opacity: 1;
}

/* ──────────────── RESPONSIVE ──────────────── */
@media (max-width: 768px) {
    .logo { font-size: 2rem; }
    .tagline { font-size: 0.9rem; }

    .game-cards {
        grid-template-columns: 1fr;
        max-width: 400px;
    }

    .game-topbar {
        justify-content: center;
    }

    .game-info {
        gap: 1rem;
    }

    .info-value { font-size: 1.1rem; }

    .pattern-item,
    .pattern-option {
        width: 52px;
        height: 52px;
        font-size: 1.1rem;
    }

    .sliding-grid {
        max-width: 320px;
    }

    .sliding-tile {
        font-size: 1.2rem;
    }

    .memory-card-face {
        font-size: 1.5rem;
    }

    .mastermind-peg {
        width: 32px;
        height: 32px;
    }

    .mastermind-color-btn {
        width: 40px;
        height: 40px;
    }

    .mastermind-dot {
        width: 14px;
        height: 14px;
    }

    .stats-bar {
        gap: 0.8rem;
    }

    .stat-item {
        padding: 0.5rem 1rem;
        min-width: 90px;
    }

    .fusion-sequence,
    .fusion-options {
        grid-template-columns: 1fr;
    }

    .neuro-options {
        grid-template-columns: repeat(2, minmax(90px, 1fr));
    }
}

@media (max-width: 480px) {
    .screen {
        /* Keep the safe-area insets — dropping to a flat 1rem here was
           re-introducing the notch overlap on small iPhones. */
        padding: calc(var(--sp-4) + var(--safe-top)) calc(var(--sp-4) + var(--safe-right))
                 calc(var(--sp-4) + var(--safe-bottom)) calc(var(--sp-4) + var(--safe-left));
    }

    .game-topbar {
        flex-direction: column;
        align-items: stretch;
    }

    .game-info {
        justify-content: center;
    }

    .topbar-actions {
        justify-content: center;
    }

    .difficulty-bar {
        flex-wrap: wrap;
        justify-content: center;
    }

    .modal-content {
        padding: var(--sp-5);
    }

    /* 44px is the minimum comfortable target — these must not shrink
       further, so the shared sizing stops here. */
    .pattern-item,
    .pattern-option {
        width: var(--tap-min);
        height: var(--tap-min);
        font-size: 1rem;
    }

    /* The menu action row stacks rather than wrapping into ragged
       half-width buttons. */
    .menu-actions {
        flex-direction: column;
        width: 100%;
        max-width: 320px;
    }

    .menu-actions .btn {
        width: 100%;
    }

    /* Three stat tiles across a 320px screen leaves ~90px each; letting
       them share the row keeps the hero metrics above the fold. */
    .stats-bar {
        gap: var(--sp-2);
        width: 100%;
    }

    .stat-item {
        flex: 1;
        min-width: 0;
        padding: var(--sp-2) var(--sp-3);
    }

    .stat-label { font-size: 0.65rem; }
    .stat-value { font-size: 1.25rem; }

    /* Results and confirm dialogs: full-width stacked actions so the
       primary control is never a cramped half-button. */
    .results-actions {
        flex-direction: column-reverse;
    }

    .results-actions .btn {
        width: 100%;
    }
}

/* ──────────────── LANDSCAPE PHONES ────────────────
   A short viewport (phone held sideways) has no room for the full menu
   header; the modals in particular need to stop reserving 80% of a
   400px-tall screen for padding.
   ─────────────────────────────────────────────── */
@media (max-height: 480px) and (orientation: landscape) {
    .logo { font-size: var(--fs-xl); }
    .tagline { display: none; }
    .menu-header { margin-bottom: var(--sp-3); }
    .stats-bar { margin-bottom: var(--sp-4); }

    .modal-content {
        max-height: calc(100dvh - var(--sp-6));
        padding: var(--sp-4);
    }
}

/* ──────────────── SCROLLBAR ──────────────── */
::-webkit-scrollbar {
    width: 6px;
}

::-webkit-scrollbar-track {
    background: var(--clr-bg);
}

::-webkit-scrollbar-thumb {
    background: var(--clr-surface-alt);
    border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--clr-primary);
}

/* ═══════════════════════════════════════════════════════════
   SYNAPSE STORM — Expert-level multi-track challenge
   ═══════════════════════════════════════════════════════════ */

.game-card-expert {
    border: 1px solid rgba(255, 137, 6, 0.45);
    background: linear-gradient(155deg, var(--clr-card) 65%, rgba(229, 49, 112, 0.12));
}

/* Gated like the base card: an unguarded :hover sticks after a tap on
   touch devices, leaving the card stuck in its raised state. */
@media (hover: hover) {
    .game-card-expert:hover {
        border-color: var(--clr-accent);
        box-shadow: 0 12px 40px rgba(255, 137, 6, 0.22);
    }
}

.game-card-expert:focus-within {
    border-color: var(--clr-accent);
    box-shadow: 0 0 0 1px rgba(255, 137, 6, 0.45), 0 8px 28px rgba(255, 137, 6, 0.22);
}

/* The expert cards get a warm wash instead of the default purple one. */
.game-card-expert::before {
    background: radial-gradient(24rem 12rem at 50% 0%, rgba(255, 137, 6, 0.20), transparent 70%);
}

.expert-tag {
    display: inline-block;
    font-size: 0.65rem;
    padding: 0.15rem 0.5rem;
    margin-left: 0.4rem;
    border-radius: 50px;
    background: linear-gradient(135deg, var(--clr-accent), var(--clr-danger));
    color: #fff;
    font-weight: 700;
    letter-spacing: 1px;
    vertical-align: middle;
}

#synapse-area {
    width: 100%;
    max-width: 540px;
    margin: 0 auto;
}

.synapse-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}

.synapse-round-label,
.synapse-counter {
    font-size: 0.85rem;
    color: var(--clr-text-muted);
    background: var(--clr-surface);
    padding: 0.3rem 0.7rem;
    border-radius: var(--radius-sm);
}

.synapse-phase-label {
    font-weight: 700;
    color: var(--clr-accent);
    text-align: center;
    flex: 1;
}

.synapse-grid {
    display: grid;
    gap: 0.6rem;
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--clr-surface);
    border-radius: var(--radius-lg);
    padding: 0.8rem;
    margin-bottom: 0.8rem;
}

.synapse-cell {
    background: var(--clr-surface-alt);
    border-radius: var(--radius);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s ease, transform 0.12s ease;
    min-height: 70px;
}

.synapse-cell.flash {
    animation: synapsePulse 0.18s ease;
    box-shadow: 0 0 24px rgba(255, 255, 255, 0.25);
}

@keyframes synapsePulse {
    0%   { transform: scale(0.92); }
    60%  { transform: scale(1.06); }
    100% { transform: scale(1); }
}

.synapse-digit {
    font-size: 2.2rem;
    font-weight: 800;
    color: #fff;
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.synapse-hint {
    text-align: center;
    color: var(--clr-text-dim);
    font-size: 0.85rem;
    font-style: italic;
}

/* Shares Echo Path's treatment on purpose: the calibrated-level badge means
   the same thing in both games, so it should not have to be re-learned. */
.synapse-calib {
    align-self: center;
    padding: var(--sp-1) var(--sp-3);
    margin-bottom: var(--sp-2);
    border-radius: var(--radius-pill);
    background: rgba(255, 137, 6, 0.14);
    border: 1px solid rgba(255, 137, 6, 0.34);
    color: var(--clr-accent);
    font-size: var(--fs-xs);
    font-weight: 700;
    white-space: nowrap;
}

.synapse-calib.is-fresh {
    background: rgba(255, 255, 255, 0.05);
    border-color: var(--clr-line-strong);
    color: var(--clr-text-muted);
}

.synapse-question {
    font-size: 1.15rem;
    text-align: center;
    margin: 1rem 0 1.2rem;
    color: var(--clr-text);
}

.synapse-question em {
    color: var(--clr-accent);
    font-style: normal;
    font-weight: 700;
}

.synapse-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: 0.6rem;
    margin-bottom: 1rem;
}

.synapse-option {
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 0.9rem 0.6rem;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .synapse-option:hover {
        background: var(--clr-card-hover);
        border-color: var(--clr-primary);
        transform: translateY(-2px);
    }
}

.synapse-option:active {
    transform: translateY(0) scale(0.97);
}

.synapse-option.correct {
    background: var(--clr-secondary);
    color: #0f0e17;
    border-color: var(--clr-secondary);
}

.synapse-option.wrong {
    background: var(--clr-danger);
    color: #fff;
    border-color: var(--clr-danger);
}

.synapse-color-option {
    color: #0f0e17;
    text-shadow: 0 1px 2px rgba(255, 255, 255, 0.4);
}

.synapse-feedback {
    text-align: center;
    min-height: 1.4rem;
    font-weight: 700;
}

.synapse-feedback .ok  { color: var(--clr-secondary); }
.synapse-feedback .bad { color: var(--clr-danger); }

/* ═══════════════════════════════════════════════════════════
   EXPERT PACK — Chroma Veto · Rule Shift · Number Flux · Word Forge
                 Tower Ascent · Echo Path · Signal Sweep · Time Vault

   The header, round chip, phase caption and feedback line are shared
   vocabulary across every expert game. New games join these selector
   lists rather than re-declaring the same rules, so the pack keeps one
   voice as it grows.
   ═══════════════════════════════════════════════════════════ */

#veto-area, #shift-area, #flux-area, #forge-area,
#echo-area, #vault-area {
    width: 100%;
    max-width: 540px;
    margin: 0 auto;
}

/* Tower needs room for the target and the board side by side; Sweep
   needs it for a six-column field. */
#tower-area, #sweep-area {
    width: 100%;
    max-width: 640px;
    margin: 0 auto;
}

/* `.game-area.active` is `display: flex` with the default row direction,
   so a game rendering several top-level children (header, stage,
   controls, feedback) would lay them out SIDE BY SIDE. The four games
   below render exactly that shape, so they stack explicitly.

   Deliberately scoped to these four rather than fixed on the shared
   `.game-area.active` rule: the older areas each hand their area a
   single wrapper child, for which row and column are equivalent, and
   flipping the shared default would change how every one of them sizes
   on the cross axis. */
#tower-area, #echo-area, #sweep-area, #vault-area {
    flex-direction: column;
}

/* Shared header layout (mirrors synapse-header) */
.veto-header, .shift-header, .flux-header, .forge-header,
.tower-header, .echo-header, .sweep-header, .vault-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}

.veto-round-label, .shift-round-label, .flux-round-label, .forge-round-label,
.tower-round-label, .echo-round-label, .sweep-round-label, .vault-round-label {
    font-size: 0.85rem;
    color: var(--clr-text-muted);
    background: var(--clr-surface);
    padding: 0.3rem 0.7rem;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}

.veto-phase-label, .shift-phase-label, .flux-phase-label, .forge-phase-label,
.tower-phase-label, .echo-phase-label, .sweep-phase-label, .vault-phase-label {
    font-weight: 700;
    color: var(--clr-accent);
    text-align: center;
    flex: 1;
    font-size: 0.9rem;
}

/* Shared feedback line */
.veto-feedback, .shift-feedback, .flux-feedback, .forge-feedback,
.tower-feedback, .echo-feedback, .sweep-feedback, .vault-feedback {
    text-align: center;
    min-height: 1.6rem;
    font-weight: 700;
    margin-top: 0.6rem;
}

.veto-feedback .ok,  .shift-feedback .ok,  .flux-feedback .ok,  .forge-feedback .ok,
.tower-feedback .ok, .echo-feedback .ok,   .sweep-feedback .ok, .vault-feedback .ok   { color: var(--clr-secondary); }
.veto-feedback .bad, .shift-feedback .bad, .flux-feedback .bad, .forge-feedback .bad,
.tower-feedback .bad, .echo-feedback .bad, .sweep-feedback .bad, .vault-feedback .bad { color: var(--clr-danger); }
.tower-feedback .warn, .echo-feedback .warn, .sweep-feedback .warn, .vault-feedback .warn { color: var(--clr-accent); }

/* Shared per-trial deadline bar */
.veto-deadline {
    width: 100%;
    height: 6px;
    background: var(--clr-surface);
    border-radius: 3px;
    overflow: hidden;
    margin-top: 0.9rem;
}

.veto-deadline-fill {
    height: 100%;
    width: 100%;
    background: linear-gradient(90deg, var(--clr-secondary), var(--clr-accent), var(--clr-danger));
    /* Duration comes from a per-trial CSS variable (set inline by the game),
       so the reduced-motion blanket rule can preserve THIS animation while
       neutralizing decorative ones. */
    animation: vetoDrain linear forwards;
    animation-duration: var(--deadline-ms, 2000ms);
    transform-origin: left;
}

@keyframes vetoDrain {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

/* ── Chroma Veto ── */
.veto-stage {
    background: var(--clr-surface);
    border-radius: var(--radius-lg);
    padding: 1.6rem 1rem;
    margin-bottom: 1rem;
    text-align: center;
}

.veto-word {
    font-size: 2.6rem;
    font-weight: 800;
    letter-spacing: 2px;
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}

.veto-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 0.6rem;
    margin-bottom: 0.7rem;
}

.veto-color-btn {
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 0.85rem 0.5rem;
    font-size: 0.95rem;
    font-weight: 700;
    color: #0f0e17;
    text-shadow: 0 1px 2px rgba(255, 255, 255, 0.4);
    cursor: pointer;
    transition: var(--transition);
}

/* Chroma Veto is scored on reaction time, so a stuck :hover on the button
   the player just pressed is not cosmetic — it misreads as the app still
   awaiting that tap. */
@media (hover: hover) {
    .veto-color-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
    }
}

.veto-color-btn:active {
    transform: translateY(0) scale(0.96);
}

.veto-veto-btn {
    display: block;
    width: 100%;
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 2px dashed var(--clr-danger);
    border-radius: var(--radius);
    padding: 0.9rem;
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: 1px;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .veto-veto-btn:hover {
        background: rgba(229, 49, 112, 0.15);
        transform: translateY(-2px);
    }
}

.veto-veto-btn:active {
    transform: translateY(0) scale(0.98);
}

/* ── Rule Shift ── */
.shift-legend {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.9rem;
}

.shift-legend-item {
    font-size: 0.75rem;
    color: var(--clr-text-muted);
    background: var(--clr-surface);
    border: 1px solid transparent;
    padding: 0.25rem 0.6rem;
    border-radius: 50px;
}

.shift-stage {
    text-align: center;
    margin-bottom: 1rem;
}

.shift-frame {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-width: 130px;
    min-height: 130px;
    background: var(--clr-surface);
    border: 4px solid;
    border-radius: var(--radius-lg);
    padding: 1rem;
    transition: border-color 0.15s ease;
}

.shift-rule-icon {
    font-size: 1.3rem;
}

.shift-digit {
    font-size: 3.2rem;
    font-weight: 800;
    color: var(--clr-text);
}

.shift-rule-label {
    margin-top: 0.5rem;
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: 0.5px;
}

.shift-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 0.6rem;
}

.shift-option {
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 0.9rem 0.6rem;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .shift-option:hover {
        background: var(--clr-card-hover);
        border-color: var(--clr-primary);
        transform: translateY(-2px);
    }
}

.shift-option:active {
    transform: translateY(0) scale(0.97);
}

/* ── Number Flux ── */
.flux-stage {
    background: var(--clr-surface);
    border-radius: var(--radius-lg);
    padding: 1.6rem 1rem;
    margin-bottom: 0.8rem;
    text-align: center;
}

.flux-display {
    font-size: 3rem;
    font-weight: 800;
    color: var(--clr-text);
    min-height: 4rem;
}

.flux-display.flux-pop {
    animation: fluxPop 0.25s ease;
}

@keyframes fluxPop {
    0%   { transform: scale(0.7); opacity: 0.3; }
    100% { transform: scale(1);   opacity: 1; }
}

.flux-progress {
    color: var(--clr-text-muted);
    font-size: 0.85rem;
}

.flux-hint {
    text-align: center;
    color: var(--clr-text-dim);
    font-size: 0.85rem;
    font-style: italic;
}

.flux-question {
    font-size: 1.1rem;
    text-align: center;
    margin: 0.8rem 0 1.1rem;
}

.flux-question strong {
    color: var(--clr-accent);
}

.flux-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
    gap: 0.6rem;
}

.flux-option {
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 0.9rem 0.6rem;
    font-size: 1.1rem;
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .flux-option:hover {
        background: var(--clr-card-hover);
        border-color: var(--clr-primary);
        transform: translateY(-2px);
    }
}

.flux-option:active {
    transform: translateY(0) scale(0.97);
}

.flux-option.correct {
    background: var(--clr-secondary);
    color: #0f0e17;
    border-color: var(--clr-secondary);
}

.flux-option.wrong {
    background: var(--clr-danger);
    color: #fff;
    border-color: var(--clr-danger);
}

/* ── Word Forge ── */
.forge-strikes {
    min-width: 3rem;
    text-align: right;
    color: var(--clr-danger);
    font-weight: 700;
    letter-spacing: 2px;
}

.forge-anvil {
    display: flex;
    justify-content: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    background: var(--clr-surface);
    border-radius: var(--radius-lg);
    padding: 1.2rem 0.8rem;
    margin-bottom: 1rem;
    min-height: 4.6rem;
}

.forge-slot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.9rem;
    background: var(--clr-surface-alt);
    border-bottom: 3px solid var(--clr-text-dim);
    border-radius: var(--radius-sm);
    font-size: 1.3rem;
    font-weight: 800;
    color: var(--clr-text);
}

.forge-slot.filled {
    background: rgba(44, 182, 125, 0.18);
    border-bottom-color: var(--clr-secondary);
    color: var(--clr-secondary);
}

.forge-slot.forged {
    animation: forgeGlow 0.6s ease;
}

@keyframes forgeGlow {
    0%   { box-shadow: 0 0 0 rgba(255, 137, 6, 0); }
    50%  { box-shadow: 0 0 22px rgba(255, 137, 6, 0.7); }
    100% { box-shadow: 0 0 0 rgba(255, 137, 6, 0); }
}

.forge-hint-letter {
    opacity: 0.35;
}

.forge-tiles {
    display: flex;
    justify-content: center;
    gap: 0.45rem;
    flex-wrap: wrap;
}

.forge-tile {
    width: 2.7rem;
    height: 2.7rem;
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font-size: 1.15rem;
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .forge-tile:hover:not(:disabled) {
        background: var(--clr-card-hover);
        border-color: var(--clr-accent);
        transform: translateY(-2px);
    }
}

.forge-tile:active:not(:disabled) {
    transform: translateY(0) scale(0.95);
}

.forge-tile:disabled {
    opacity: 0.25;
    cursor: default;
}

.forge-tile.shake {
    animation: forgeShake 0.4s ease;
    border-color: var(--clr-danger);
}

@keyframes forgeShake {
    0%, 100% { transform: translateX(0); }
    25%      { transform: translateX(-5px); }
    75%      { transform: translateX(5px); }
}

/* ── Tower Ascent ──
   Two peg-sets: an inert target above, the live board below. Peg height
   is driven by --cap so a peg's capacity is legible at a glance rather
   than something you have to discover by failing to place an orb. */
.tower-stage {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-3);
    align-items: end;
}

.tower-panel {
    background: var(--clr-surface);
    border: 1px solid var(--clr-line);
    border-radius: var(--radius);
    padding: var(--sp-3) var(--sp-2) var(--sp-2);
}

.tower-panel-board {
    border-color: var(--clr-line-primary);
    background: var(--clr-surface-alt);
}

.tower-panel-title {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--clr-text-muted);
    text-align: center;
    margin-bottom: var(--sp-2);
    font-weight: 700;
}

.tower-pegs {
    display: flex;
    justify-content: center;
    align-items: flex-end;
    gap: var(--sp-2);
}

.tower-peg {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    flex: 1 1 0;
    min-width: 0;
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
}

.tower-peg-btn {
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
    .tower-peg-btn:hover { background: rgba(127, 90, 240, 0.12); }
}

.tower-peg-btn:active { background: rgba(127, 90, 240, 0.2); }

/* The stack grows upward, so index 0 (the bottom ball) must paint at the
   bottom. column-reverse gives that without reversing the model. */
.tower-peg-stack {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    justify-content: flex-start;
    gap: 3px;
    width: 100%;
    /* Reserve the peg's full capacity so pegs do not change height as
       orbs move — a shifting baseline makes the board hard to read. */
    min-height: calc(var(--cap) * 1.85rem + var(--sp-2));
    padding-bottom: 3px;
}

.tower-orb {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: var(--orb);
    box-shadow: inset 0 -3px 6px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.4);
    flex: 0 0 auto;
    transition: transform var(--dur-base) var(--ease-spring);
}

.tower-orb.is-lifted {
    transform: translateY(-0.5rem) scale(1.12);
    box-shadow: inset 0 -3px 6px rgba(0, 0, 0, 0.35), 0 0 0 3px rgba(255, 255, 255, 0.85);
}

.tower-peg-base {
    width: 100%;
    height: 6px;
    border-radius: 3px;
    background: var(--clr-text-dim);
    opacity: 0.5;
}

.tower-pegs-goal .tower-orb {
    width: 1.15rem;
    height: 1.15rem;
}

.tower-pegs-goal .tower-peg-stack {
    min-height: calc(var(--cap) * 1.35rem + var(--sp-1));
}

.tower-controls {
    margin-top: var(--sp-4);
    text-align: center;
}

.tower-declare-prompt {
    font-weight: 700;
    margin-bottom: var(--sp-2);
}

.tower-declare-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
}

.tower-declare {
    min-width: var(--tap-min);
    min-height: var(--tap-min);
    padding: 0 var(--sp-3);
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-pill);
    font-family: var(--ff-num);
    font-size: var(--fs-md);
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
}

@media (hover: hover) {
    .tower-declare:hover {
        background: var(--clr-primary);
        transform: translateY(-2px);
    }
}

.tower-declare:active { transform: translateY(0) scale(0.95); }

.tower-declare-note {
    margin-top: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--clr-text-muted);
}

.tower-budget {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-1);
    background: var(--clr-surface);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-pill);
    padding: var(--sp-2) var(--sp-4);
    font-family: var(--ff-num);
}

.tower-budget-used   { font-size: var(--fs-xl); font-weight: 800; color: var(--clr-primary); }
.tower-budget-sep    { color: var(--clr-text-dim); }
.tower-budget-total  { font-size: var(--fs-md); font-weight: 700; }
.tower-budget-label  {
    font-family: var(--ff-base);
    font-size: var(--fs-xs);
    color: var(--clr-text-muted);
    margin-left: var(--sp-1);
}

/* The last move before the declared budget runs out. */
.tower-budget.is-low {
    border-color: var(--clr-danger);
}

.tower-budget.is-low .tower-budget-used { color: var(--clr-danger); }

/* ── Echo Path ──
   One grid serves both playback and recall; the cells are only buttons
   during recall (see _gridMarkup), so the styling has to read the same
   either way. */
.echo-grid {
    display: grid;
    grid-template-columns: repeat(var(--echo-n), 1fr);
    gap: var(--sp-2);
    width: 100%;
    max-width: 22rem;
    margin: 0 auto;
}

.echo-cell {
    aspect-ratio: 1;
    background: var(--clr-surface-alt);
    border: 1px solid var(--clr-line);
    border-radius: var(--radius);
    font-family: var(--ff-num);
    font-size: var(--fs-lg);
    font-weight: 800;
    color: var(--clr-text);
    padding: 0;
    transition: background var(--dur-base) var(--ease-out),
                transform var(--dur-fast) var(--ease-spring),
                border-color var(--dur-base) var(--ease-out);
}

.echo-cell-btn { cursor: pointer; }

@media (hover: hover) {
    .echo-cell-btn:hover {
        border-color: var(--clr-primary);
        background: var(--clr-card-hover);
    }
}

.echo-cell-btn:active { transform: scale(0.94); }

.echo-cell.is-lit {
    background: var(--grad-brand);
    border-color: var(--clr-primary);
    transform: scale(1.06);
}

.echo-cell.is-correct {
    background: rgba(44, 182, 125, 0.28);
    border-color: var(--clr-secondary);
}

.echo-cell.is-wrong {
    background: rgba(229, 49, 112, 0.28);
    border-color: var(--clr-danger);
}

/* The replay of the correct order after a miss — numbered, so the
   sequence is readable rather than just a re-flash. */
.echo-cell.is-answer {
    background: rgba(255, 137, 6, 0.25);
    border-color: var(--clr-accent);
    color: var(--clr-accent);
}

.echo-rule-slot {
    display: flex;
    justify-content: center;
    margin-top: var(--sp-4);
    min-height: 4.5rem;
}

.echo-rule-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    width: 100%;
    max-width: 22rem;
    padding: var(--sp-3);
    border-radius: var(--radius);
    border: 1px solid var(--clr-line-primary);
    background: var(--clr-surface);
    text-align: center;
}

.echo-rule-card.is-face-down {
    background: var(--clr-surface-alt);
    border-style: dashed;
}

.echo-rule-back {
    font-size: var(--fs-xl);
    font-weight: 800;
    color: var(--clr-text-dim);
}

.echo-rule-card.is-face-up {
    background: linear-gradient(135deg, rgba(127, 90, 240, 0.22), rgba(44, 182, 125, 0.12));
    animation: echoFlip var(--dur-slow) var(--ease-spring);
}

.echo-rule-card.is-compact { padding: var(--sp-2) var(--sp-3); }

@keyframes echoFlip {
    from { transform: rotateX(90deg); opacity: 0; }
    to   { transform: rotateX(0); opacity: 1; }
}

.echo-rule-label {
    font-size: var(--fs-md);
    font-weight: 800;
    letter-spacing: 0.04em;
    color: var(--clr-text);
}

.echo-rule-hint {
    font-size: var(--fs-xs);
    color: var(--clr-text-muted);
}

.echo-hint {
    text-align: center;
    margin-top: var(--sp-3);
    font-size: var(--fs-xs);
    color: var(--clr-text-muted);
}

/* ── Echo Path: session chrome ──
   The calibrated starting span and the cell-tone switch. Both belong
   above the grid: the span explains why round one is not where it was
   last time, and the switch has to be reachable before playback starts,
   not after it has already run silently. */
.echo-toolbar {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
}

.echo-calib {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--radius-pill);
    background: rgba(255, 137, 6, 0.14);
    border: 1px solid rgba(255, 137, 6, 0.34);
    color: var(--clr-accent);
    font-size: var(--fs-xs);
    font-weight: 700;
    white-space: nowrap;
}

.echo-calib.is-fresh {
    background: rgba(255, 255, 255, 0.05);
    border-color: var(--clr-line-strong);
    color: var(--clr-text-muted);
}

.echo-audio-toggle {
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--radius-pill);
    border: 1px solid var(--clr-line-strong);
    background: transparent;
    color: var(--clr-text-muted);
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out);
}

/* aria-pressed is the source of truth for the toggle's look, so the
   visual state can never disagree with what a screen reader is told. */
.echo-audio-toggle[aria-pressed="true"] {
    color: var(--clr-secondary);
    border-color: rgba(44, 182, 125, 0.45);
    background: rgba(44, 182, 125, 0.12);
}

.echo-audio-toggle:focus-visible {
    outline: 2px solid var(--clr-primary);
    outline-offset: 2px;
}

.echo-progress {
    display: flex;
    justify-content: center;
    gap: var(--sp-2);
    margin-top: var(--sp-3);
    min-height: 0.75rem;
}

.echo-pip {
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--clr-surface-alt);
    border: 1px solid var(--clr-line-strong);
    transition: var(--transition);
}

.echo-pip.is-filled {
    background: var(--clr-secondary);
    border-color: var(--clr-secondary);
}

/* ── Signal Sweep ── */
.sweep-brief {
    text-align: center;
    padding: var(--sp-6) var(--sp-4);
}

.sweep-brief-caption {
    color: var(--clr-text-muted);
    font-size: var(--fs-sm);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.sweep-brief-target {
    margin: var(--sp-4) 0;
}

.sweep-brief-glyph {
    font-size: 5rem;
    line-height: 1;
    color: var(--sig);
    filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.5));
}

.sweep-brief-count {
    color: var(--clr-text-muted);
    font-size: var(--fs-base);
}

.sweep-target-chip {
    display: inline-block;
    font-size: var(--fs-lg);
    line-height: 1;
    color: var(--sig);
    vertical-align: middle;
    margin: 0 var(--sp-1);
}

.sweep-counter {
    font-family: var(--ff-num);
    font-size: var(--fs-sm);
    color: var(--clr-text-muted);
    margin-left: var(--sp-1);
}

.sweep-clock {
    width: 100%;
    height: 6px;
    background: var(--clr-surface);
    border-radius: 3px;
    overflow: hidden;
    margin-bottom: var(--sp-3);
}

.sweep-clock-bar {
    height: 100%;
    width: 100%;
    background: linear-gradient(90deg, var(--clr-secondary), var(--clr-accent), var(--clr-danger));
    transform-origin: left;
    /* Duration is set per round via --sweep-ms (inline, from the game).
       See the REDUCED MOTION block: this is essential motion and is
       preserved there by name. */
    animation: sweepDrain linear forwards;
    animation-duration: var(--sweep-ms, 12000ms);
}

@keyframes sweepDrain {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

.sweep-grid {
    display: grid;
    grid-template-columns: repeat(var(--sweep-cols), 1fr);
    gap: var(--sp-2);
    width: 100%;
}

.sweep-item {
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--clr-surface);
    border: 1px solid var(--clr-line);
    border-radius: var(--radius-sm);
    color: var(--sig);
    /* Scales with the column count so a 6-wide field stays legible on a
       phone without the glyphs colliding. */
    font-size: clamp(1.1rem, 6vw, 1.9rem);
    line-height: 1;
    padding: 0;
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease-spring),
                background var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
    .sweep-item:hover { background: var(--clr-card-hover); }
}

.sweep-item:active { transform: scale(0.9); }

.sweep-item.is-hit {
    background: rgba(44, 182, 125, 0.3);
    border-color: var(--clr-secondary);
    cursor: default;
}

.sweep-item.is-miss {
    background: rgba(229, 49, 112, 0.25);
    border-color: var(--clr-danger);
    opacity: 0.55;
    cursor: default;
}

/* A target that was never tapped, revealed once the round closes. */
.sweep-item.is-missed {
    border-color: var(--clr-accent);
    box-shadow: 0 0 0 2px var(--clr-accent) inset;
    animation: sweepReveal 0.5s var(--ease-spring);
}

@keyframes sweepReveal {
    0%   { transform: scale(1); }
    50%  { transform: scale(1.15); }
    100% { transform: scale(1); }
}

/* ── Time Vault ── */
.vault-brief {
    background: var(--clr-surface);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-4);
    text-align: center;
}

.vault-intent-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
}

.vault-intent {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    background: var(--clr-surface-alt);
    border-left: 3px solid var(--clr-accent);
    border-radius: var(--radius-sm);
    padding: var(--sp-3);
    text-align: left;
}

.vault-intent-label {
    font-weight: 800;
    font-size: var(--fs-md);
    color: var(--clr-accent);
}

.vault-intent-brief {
    font-size: var(--fs-sm);
    color: var(--clr-text-muted);
    line-height: 1.5;
}

.vault-brief-warning {
    font-size: var(--fs-sm);
    color: var(--clr-text-muted);
    margin-bottom: var(--sp-4);
    text-wrap: pretty;
}

.vault-begin { width: 100%; }

.vault-sealing {
    text-align: center;
    padding: var(--sp-7) var(--sp-4);
}

.vault-sealing-icon {
    font-size: 4rem;
    line-height: 1;
    margin-bottom: var(--sp-3);
    animation: vaultSeal 1.2s var(--ease-out);
}

@keyframes vaultSeal {
    0%   { transform: scale(1.4) rotate(-12deg); opacity: 0; }
    60%  { transform: scale(1) rotate(0); opacity: 1; }
    100% { transform: scale(1); opacity: 1; }
}

.vault-sealing-text {
    color: var(--clr-text-muted);
    font-weight: 600;
}

.vault-progress {
    width: 100%;
    height: 4px;
    background: var(--clr-surface);
    border-radius: 2px;
    overflow: hidden;
    margin-bottom: var(--sp-4);
}

.vault-progress-fill {
    height: 100%;
    background: var(--grad-brand);
    transition: width var(--dur-base) var(--ease-out);
}

.vault-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    min-height: 12rem;
    background: var(--clr-surface-alt);
    border: 2px solid var(--clr-line);
    border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-4);
    margin-bottom: var(--sp-4);
}

/* The VAULT cue. It has to be unmistakable when you are looking for it
   and easy to sail past when you are absorbed in the sorting — which is
   exactly the tension the game measures. */
.vault-card.is-golden {
    background: linear-gradient(140deg, #4a3a12, #6d5416);
    border-color: #d9a520;
    box-shadow: 0 0 28px rgba(217, 165, 32, 0.28);
}

.vault-card-mark {
    position: absolute;
    top: var(--sp-3);
    right: var(--sp-4);
    font-size: var(--fs-xl);
    line-height: 1;
}

.vault-card-numeral {
    font-family: var(--ff-num);
    font-size: 4rem;
    font-weight: 800;
    line-height: 1;
}

.vault-card-dots {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
    max-width: 9rem;
}

.vault-dot {
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    background: var(--clr-text);
    opacity: 0.85;
}

/* Time Vault borrows .veto-deadline for its per-trial clock; it just
   needs room beneath the card rather than above the buttons. */
#vault-area .veto-deadline {
    margin-top: 0;
    margin-bottom: var(--sp-3);
}

.vault-responses {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
}

.vault-response {
    flex: 1 1 8rem;
    min-height: var(--tap-min);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
}

.vault-ongoing {
    background: var(--clr-surface-alt);
    color: var(--clr-text);
    border: 1px solid var(--clr-line-strong);
}

/* The prospective actions are visually separated from the ongoing
   answers, so a mis-tap is a memory failure rather than a targeting one. */
.vault-pm {
    background: rgba(255, 137, 6, 0.14);
    color: var(--clr-accent);
    border: 1px dashed var(--clr-accent);
}

@media (hover: hover) {
    .vault-ongoing:hover { background: var(--clr-card-hover); }
    .vault-pm:hover      { background: rgba(255, 137, 6, 0.26); }
}

.vault-response:active { transform: scale(0.96); }

/* ── Expert-pack responsive tightening ── */
@media (max-width: 520px) {
    .tower-stage {
        grid-template-columns: 1fr;
        gap: var(--sp-2);
    }

    .tower-orb { width: 1.35rem; height: 1.35rem; }
    .tower-peg-stack { min-height: calc(var(--cap) * 1.6rem + var(--sp-2)); }

    .tower-declare {
        min-width: 2.75rem;
        padding: 0 var(--sp-2);
    }

    .vault-card { min-height: 10rem; }
    .vault-card-numeral { font-size: 3.2rem; }
    .vault-response { flex: 1 1 6.5rem; }
}

/* ═══════════════════════════════════════════════════════════
   MENU ACTIONS — Progress · Leaderboard

   Daily Workout used to live here, at the bottom of the menu below
   eleven cards. It is now the Today panel above the grid; what remains
   here is genuinely secondary, and is styled to say so.
   ═══════════════════════════════════════════════════════════ */
.menu-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: center;
    margin-top: 0.75rem;
}

.btn-progress,
.btn-leaderboard {
    font-size: 1rem;
    margin-top: 0;
}

/* ═══════════════════════════════════════════════════════════
   TODAY PANEL — the menu's primary action.

   A returning trainer opens this app to do one thing. That thing now
   leads the screen and names the streak it is asking them to protect,
   instead of sitting below every game card.
   ═══════════════════════════════════════════════════════════ */
.today-panel {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    width: 100%;
    max-width: 960px;
    margin-bottom: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border: 1px solid var(--clr-line-primary);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(32rem 14rem at 0% 0%, rgba(127, 90, 240, 0.20), transparent 70%),
        linear-gradient(180deg, var(--clr-surface-alt), var(--clr-card));
    box-shadow: var(--elev-2);
}

.today-copy {
    flex: 1;
    min-width: 0;   /* let long detail text wrap instead of stretching the row */
    text-align: left;
}

.today-eyebrow {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--clr-text-muted);
    margin-bottom: var(--sp-1);
}

.today-headline {
    font-size: var(--fs-lg);
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
}

.today-detail {
    color: var(--clr-text-muted);
    font-size: var(--fs-sm);
    text-wrap: pretty;
    margin-top: var(--sp-1);
    /* Reserve the line so the panel does not resize when the copy is
       written in after ScoreManager loads. */
    min-height: 1.4em;
}

.today-start {
    flex: 0 0 auto;
    min-width: 7.5rem;
    font-weight: 700;
}

/* Already worked out today: the panel stops selling. The border cools to
   the success colour and the call to action steps down to a secondary
   treatment — replaying is allowed, but it is no longer being urged. */
.today-panel[data-state="done"] {
    border-color: rgba(44, 182, 125, 0.35);
    background:
        radial-gradient(32rem 14rem at 0% 0%, rgba(44, 182, 125, 0.16), transparent 70%),
        linear-gradient(180deg, var(--clr-surface-alt), var(--clr-card));
}

.today-panel[data-state="done"] .today-start {
    background: var(--clr-surface);
    color: var(--clr-text);
    border: 1px solid var(--clr-line-strong);
    box-shadow: none;
}

@media (hover: hover) {
    .today-panel[data-state="done"] .today-start:hover {
        background: var(--clr-surface-alt);
    }
}

@media (max-width: 560px) {
    .today-panel {
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-4);
        text-align: center;
    }

    .today-copy { text-align: center; }
    .today-start { width: 100%; }
}

/* Workout banner replaces the difficulty bar during a workout */
.workout-banner {
    text-align: center;
    padding: 0.55rem 1rem;
    margin: 0 auto 0.5rem;
    max-width: 640px;
    border-radius: var(--radius);
    background: linear-gradient(120deg, rgba(127, 90, 240, 0.18), rgba(229, 49, 112, 0.18));
    border: 1px solid rgba(127, 90, 240, 0.4);
    color: var(--clr-text);
    font-size: 0.95rem;
}

/* ═══════════════════════════════════════════════════════════
   PROGRESS MODAL — per-domain stat trends & sparklines
   ═══════════════════════════════════════════════════════════ */
.stats-content {
    max-width: 560px;
    max-height: 86vh;
    overflow-y: auto;
    text-align: left;
}

.stats-subtitle {
    color: var(--clr-text-muted);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.stats-empty {
    color: var(--clr-text-muted);
    text-align: center;
    padding: 2rem 0;
}

/* ── Cognitive map: the radar ──────────────────────────────
   The chart itself is aria-hidden; the domain list below it is the
   accessible, operable copy of the same twelve numbers. */
.stats-radar {
    display: flex;
    justify-content: center;
    margin: 0 auto var(--sp-3);
    max-width: 340px;
}

.stats-radar .radar {
    width: 100%;
    height: auto;
    display: block;
    overflow: visible;
}

.radar-ring {
    fill: none;
    stroke: var(--clr-line);
    stroke-width: 1;
}

.radar-spoke {
    stroke: var(--clr-line);
    stroke-width: 1;
}

.radar-area {
    fill: rgba(127, 90, 240, 0.28);
    stroke: var(--clr-primary);
    stroke-width: 2;
    stroke-linejoin: round;
}

/* Where the map stood a month ago: an outline, never a fill, so the two
   shapes read as "then" and "now" rather than as one ambiguous overlap. */
.radar-past {
    fill: none;
    stroke: var(--clr-text-dim);
    stroke-width: 1.5;
    stroke-dasharray: 4 3;
    stroke-linejoin: round;
}

.radar-dot {
    fill: var(--clr-primary);
}

/* Radius is set on the element in radar.js, not here: the CSS `r`
   geometry property is unsupported in older WebViews and the focus dot
   would silently stay the same size as every other one. */
.radar-dot.is-focus {
    fill: var(--clr-accent);
}

.radar-label {
    fill: var(--clr-text-muted);
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: 0.2px;
}

.radar-label.is-untrained { fill: var(--clr-text-dim); }
.radar-label.is-stale     { fill: var(--clr-accent); }
.radar-label.is-focus     { fill: var(--clr-accent); font-weight: 800; }

/* ── Cognitive map: headline numbers ─────────────────────── */
.stats-metrics {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
}

.stats-metric {
    background: var(--clr-surface);
    border: 1px solid var(--clr-line);
    border-radius: var(--radius);
    padding: var(--sp-2) var(--sp-1);
    text-align: center;
}

.stats-metric b {
    display: block;
    font-size: 1.35rem;
    font-weight: 800;
    line-height: 1.1;
}

.stats-metric b i {
    font-style: normal;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--clr-text-muted);
}

.stats-metric span {
    display: block;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.7px;
    color: var(--clr-text-muted);
    margin-top: 2px;
}

/* ── Cognitive map: the single recommended next action ───── */
.stats-focus {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-2);
    padding: var(--sp-3);
    margin-bottom: var(--sp-3);
    border-radius: var(--radius);
    background: linear-gradient(120deg, rgba(127, 90, 240, 0.2), rgba(44, 182, 125, 0.16));
    border: 1px solid var(--clr-line-primary);
}

/* An untouched domain is the strongest recommendation the app can make,
   so it gets the warm treatment rather than the neutral one. */
.stats-focus[data-tier="untrained"] {
    background: linear-gradient(120deg, rgba(255, 137, 6, 0.22), rgba(229, 49, 112, 0.16));
    border-color: rgba(255, 137, 6, 0.4);
}

.stats-focus-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.stats-focus-kicker {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--clr-text-muted);
}

.stats-focus-domain {
    font-size: 1.1rem;
    font-weight: 800;
}

.stats-focus-reason {
    font-size: 0.78rem;
    color: var(--clr-text-muted);
}

.stats-focus-btn {
    padding: var(--sp-2) var(--sp-3);
    font-size: 0.85rem;
    white-space: nowrap;
}

.stats-trend-line {
    font-size: 0.8rem;
    color: var(--clr-text-muted);
    text-align: center;
    margin-bottom: var(--sp-3);
}

.stats-trend-line b        { font-weight: 800; }
.stats-trend-line b.is-up  { color: var(--clr-secondary); }
.stats-trend-line b.is-down{ color: var(--clr-danger); }

.stats-row-delta {
    margin-left: 0.3em;
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: -0.2px;
}

.stats-row-delta.is-up   { color: var(--clr-secondary); }
.stats-row-delta.is-down { color: var(--clr-danger); }

.stats-scale-note {
    font-size: 0.75rem;
    line-height: 1.45;
    color: var(--clr-text-dim);
    margin-bottom: var(--sp-3);
}

/* ── Cognitive map: the twelve rows ───────────────────────
   One line per domain, so the whole map fits a phone screen where the
   previous per-game layout ran to twelve full sections. */
.stats-domain-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 2px;
}

.stats-domain-row {
    display: grid;
    /* The value column sizes to content: it holds the level and, when
       there is one, a delta chip beside it. A fixed width clipped the
       chip the moment a domain moved. */
    grid-template-columns: 5.6rem 1fr auto auto 0.8rem;
    align-items: center;
    gap: var(--sp-2);
    width: 100%;
    padding: var(--sp-2) var(--sp-2);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--clr-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background var(--dur-fast) ease;
}

@media (hover: hover) {
    .stats-domain-row:hover { background: var(--clr-card-hover); }
}

.stats-domain-row:focus-visible {
    outline: 2px solid var(--clr-primary);
    outline-offset: -2px;
}

.stats-domain-row[data-state="untrained"] { color: var(--clr-text-muted); }

.stats-row-name {
    font-size: 0.84rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stats-row-track {
    height: 7px;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}

.stats-row-fill {
    display: block;
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--grad-brand);
}

.stats-domain-row[data-state="stale"] .stats-row-fill {
    background: linear-gradient(135deg, var(--clr-accent), var(--clr-danger));
}

.stats-row-val {
    font-size: 0.82rem;
    font-weight: 800;
    text-align: right;
    white-space: nowrap;
    min-width: 1.9rem;
    font-variant-numeric: tabular-nums;
}

.stats-row-meta {
    font-size: 0.7rem;
    color: var(--clr-text-dim);
    white-space: nowrap;
}

.stats-row-caret {
    color: var(--clr-text-dim);
    text-align: right;
}

/* ── Drill-down: one domain's charts ─────────────────────── */
.stats-back {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    background: transparent;
    border: 0;
    padding: var(--sp-1) 0;
    margin-bottom: var(--sp-2);
    color: var(--clr-primary);
    font: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
}

.stats-back:focus-visible {
    outline: 2px solid var(--clr-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

.stats-detail-head {
    padding-bottom: var(--sp-2);
    margin-bottom: var(--sp-3);
    border-bottom: 1px solid var(--clr-line);
}

.stats-detail-title {
    font-size: 1.25rem;
    font-weight: 800;
}

/* The heading takes focus on drill-down, so it must not look focused
   like a control would. */
.stats-detail-title:focus { outline: none; }
.stats-detail-title:focus-visible {
    outline: 2px solid var(--clr-primary);
    outline-offset: 3px;
    border-radius: 4px;
}

.stats-detail-meta {
    font-size: 0.8rem;
    color: var(--clr-text-muted);
    margin-top: 2px;
}

.stats-game.is-unplayed { opacity: 0.78; }

.stats-game-empty {
    font-size: 0.8rem;
    color: var(--clr-text-muted);
    margin: var(--sp-2) 0;
}

.stats-calibration {
    font-size: 0.75rem;
    color: var(--clr-text-muted);
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px dashed var(--clr-line);
}

.stats-calibration b {
    color: var(--clr-accent);
    font-weight: 800;
}

.stats-calibration-meta { color: var(--clr-text-dim); }

.stats-game-play {
    margin-top: var(--sp-2);
    padding: var(--sp-1) var(--sp-3);
    background: transparent;
    border: 1px solid var(--clr-line-strong);
    border-radius: var(--radius-pill);
    color: var(--clr-text);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
}

@media (hover: hover) {
    .stats-game-play:hover {
        background: var(--clr-primary);
        border-color: var(--clr-primary);
    }
}

.stats-game-play:focus-visible {
    outline: 2px solid var(--clr-primary);
    outline-offset: 2px;
}

@media (max-width: 420px) {
    .stats-domain-row {
        grid-template-columns: 4.9rem 1fr auto 0.7rem;
    }

    /* The play count is the first thing to go: the bar and the number
       already carry the row's meaning, and the accessible name still
       states it in full. */
    .stats-row-meta { display: none; }

    .stats-focus { flex-direction: column; align-items: stretch; }
    .stats-focus-btn { width: 100%; }
}

.stats-domain {
    margin-bottom: 1.4rem;
}

.stats-domain-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    padding-bottom: 0.4rem;
    margin-bottom: 0.6rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.stats-domain-name {
    font-weight: 700;
    font-size: 1.05rem;
}

.stats-domain-meta {
    color: var(--clr-text-muted);
    font-size: 0.82rem;
}

.stats-games {
    display: grid;
    gap: 0.7rem;
}

.stats-game {
    background: var(--clr-surface);
    border-radius: var(--radius);
    padding: 0.7rem 0.8rem;
    border: 1px solid rgba(255, 255, 255, 0.05);
}

.stats-game-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.35rem;
}

.stats-game-label {
    font-weight: 600;
    font-size: 0.92rem;
}

.stats-trend {
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    white-space: nowrap;
}

.stats-trend.up   { color: #baf7d0; background: rgba(44, 182, 125, 0.2); }
.stats-trend.down { color: #ffc2d4; background: rgba(229, 49, 112, 0.2); }
.stats-trend.flat { color: var(--clr-text-muted); background: rgba(255, 255, 255, 0.06); }
.stats-trend.none { color: var(--clr-text-dim); background: rgba(255, 255, 255, 0.04); }

.stats-spark {
    width: 100%;
    height: 44px;
    margin: 0.2rem 0 0.4rem;
}

.stats-spark .spark {
    width: 100%;
    height: 100%;
    display: block;
}

.stats-game-metrics {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    font-size: 0.78rem;
    color: var(--clr-text-muted);
}

.stats-game-metrics b {
    color: var(--clr-text);
    font-weight: 700;
}

/* ═══════════════════════════════════════════════════════════
   DAILY WORKOUT — interstitial & summary modals
   ═══════════════════════════════════════════════════════════ */
.workout-inter-body {
    text-align: center;
    margin: 0.6rem 0 1rem;
}

.workout-inter-progress {
    color: var(--clr-text-muted);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.workout-inter-next {
    font-size: 1.3rem;
    font-weight: 700;
    margin: 0.4rem 0;
}

.workout-inter-score {
    color: var(--clr-text-muted);
}

.workout-summary-body {
    text-align: center;
    margin: 0.5rem 0 1rem;
}

.workout-summary-score {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: 0.6rem;
}

.workout-summary-score-val {
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1;
    color: var(--clr-primary);
}

.workout-summary-score-label {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--clr-text-muted);
}

.workout-summary-streak {
    font-size: 1.1rem;
    font-weight: 700;
    margin-bottom: 0.3rem;
}

.workout-summary-note {
    color: var(--clr-text-muted);
    font-size: 0.82rem;
    margin-bottom: 0.5rem;
}

.workout-summary-caption {
    color: var(--clr-text-muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-top: 0.5rem;
}

.workout-summary-games {
    list-style: none;
    padding: 0;
    margin: 0.4rem 0 0;
    display: grid;
    gap: 0.25rem;
}

.workout-summary-games li {
    background: var(--clr-surface);
    border-radius: var(--radius-sm);
    padding: 0.4rem 0.6rem;
    font-size: 0.9rem;
}

/* ═══════════════════════════════════════════════════════════
   REDUCED MOTION — respect prefers-reduced-motion.
   Neutralizes decorative animation/transition app-wide (the flash,
   shake, pulse, pop and slide effects), while deliberately preserving
   motion that is ESSENTIAL to gameplay: the Chroma Veto / Rule Shift
   countdown bar conveys remaining time and has no static substitute
   (WCAG 2.3.3 essential-motion exception).
   ═══════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }

    /* Essential: the deadline countdown keeps its real, per-trial duration. */
    .veto-deadline-fill {
        animation-duration: var(--deadline-ms, 2000ms) !important;
    }

    /* Same exception, same reason: Signal Sweep's draining bar is the only
       indication of how much of the round is left. Flattening it to 0.001ms
       would empty the bar instantly and leave the player searching a field
       with no idea how long they have. */
    .sweep-clock-bar {
        animation-duration: var(--sweep-ms, 12000ms) !important;
    }

    /* Echo Path's playback is the stimulus itself — the path is only ever
       shown by lighting cells in sequence, so removing the lit state's
       fade is fine (JS still holds each cell for its full flash duration)
       but the cell must still visibly change. Kept as an instant swap. */
    .echo-cell.is-lit {
        transition-duration: 0.001ms !important;
    }
}
