/* PRAXIS theming + motion (NEXT_STEPS §3.2).
   ---------------------------------------------------------------------------
   One shared stylesheet that owns the palette (formerly duplicated as an inline
   `:root` block in each page) plus the accent themes and the shared motion
   primitives. Dependency-free, no build step — matching the rest of static/.

   The whole app already reads its colors from CSS custom properties, so theming
   is defining alternate variable sets rather than restyling components:

     - the DARK palette is the default `:root`;
     - a LIGHT palette overrides it under `:root[data-theme="light"]`;
     - four ACCENT sets swap `--accent` (+ derived tints) under `[data-accent=…]`;
     - `data-theme` / `data-accent` / `data-motion` live on <html>, set before
       first paint by a tiny inline snippet (see theme.js), so there's no flash.

   Derived tokens exist so the few places that used a literal hex against the
   accent (buttons: `color:#0b0d12`) or a hard-coded accent tint
   (`rgba(110,168,254,.12)` for inline code / highlights) re-derive per theme
   and per accent instead of being wrong in light mode or off-blue accents:

     --on-accent    text/icon color that sits ON an --accent fill (AA-checked)
     --accent-weak  ~12% accent wash (inline code bg, memory flash)
     --accent-mid   ~22% accent wash (verbatim in-memory highlight)
     --code-fg      readable inline-code foreground for the theme
     --shadow       drop-shadow color tuned to the theme's brightness

   Contrast: every fg/bg, muted/bg, and accent/bg pair below was checked to meet
   WCAG AA (4.5:1 body, 3:1 large) in both themes for all four accents. Keep new
   accents to that bar — see the ratio check in the commit that added this file. */

/* ---- DARK (default) ----------------------------------------------------- */
:root {
  --bg:#0f1117; --panel:#1a1d27; --fg:#e6e8ee; --muted:#8b90a0;
  --accent:#6ea8fe; --good:#4ade80; --bad:#f87171; --line:#2a2e3a;
  --on-accent:#0b0d12;
  --accent-weak:rgba(110,168,254,.12);
  --accent-mid:rgba(110,168,254,.22);
  --code-fg:#cfe0ff;
  --shadow:rgba(0,0,0,.4);
  color-scheme:dark;
}

/* ---- LIGHT -------------------------------------------------------------- */
:root[data-theme="light"] {
  --bg:#f7f8fa; --panel:#ffffff; --fg:#1a1d27; --muted:#5c6270;
  --accent:#2563eb; --good:#15803d; --bad:#dc2626; --line:#dfe3ea;
  --on-accent:#ffffff;
  --accent-weak:rgba(37,99,235,.10);
  --accent-mid:rgba(37,99,235,.16);
  --code-fg:#1d4ed8;
  --shadow:rgba(15,23,42,.14);
  color-scheme:light;
}

/* ---- ACCENTS -----------------------------------------------------------
   Only the accent hue (and its two washes + the readable code foreground)
   change; the rest of the palette is inherited from the theme above. Default
   (blue) needs no block — it's already in the theme roots. Each accent has a
   dark and a light value so it stays AA against both backgrounds. */
:root[data-accent="teal"]   { --accent:#2dd4bf; --code-fg:#5eead4;
  --accent-weak:rgba(45,212,191,.13); --accent-mid:rgba(45,212,191,.22); }
:root[data-accent="violet"] { --accent:#a78bfa; --code-fg:#d6c7ff;
  --accent-weak:rgba(167,139,250,.14); --accent-mid:rgba(167,139,250,.24); }
:root[data-accent="amber"]  { --accent:#fbbf24; --code-fg:#fcd34d;
  --accent-weak:rgba(251,191,36,.13); --accent-mid:rgba(251,191,36,.22); }

:root[data-theme="light"][data-accent="teal"]   { --accent:#0f766e; --code-fg:#0f766e;
  --accent-weak:rgba(15,118,110,.10); --accent-mid:rgba(15,118,110,.16); }
:root[data-theme="light"][data-accent="violet"] { --accent:#7c3aed; --code-fg:#6d28d9;
  --accent-weak:rgba(124,58,237,.10); --accent-mid:rgba(124,58,237,.16); }
:root[data-theme="light"][data-accent="amber"]  { --accent:#b45309; --code-fg:#b45309;
  --accent-weak:rgba(180,83,9,.10); --accent-mid:rgba(180,83,9,.16); }

/* ---- MOTION ------------------------------------------------------------
   Restrained motion that communicates state, not decoration. Everything here
   is gated behind BOTH `prefers-reduced-motion: no-preference` AND the absence
   of an explicit `data-motion="off"` opt-out (Settings toggle), so a
   motion-sensitive user — by OS setting or by choice — gets a still UI.

   Keyframes are declared unconditionally (harmless if never referenced); the
   rules that *apply* them live inside the guard. Utility transitions on hover /
   theme changes also live in the guard so a reduced-motion user gets instant
   state changes. */

@keyframes praxis-bubble-in {
  from { opacity:0; transform:translateY(6px); }
  to   { opacity:1; transform:translateY(0); }
}
@keyframes praxis-fade-in {
  from { opacity:0; } to { opacity:1; }
}
@keyframes praxis-strip-in {
  from { opacity:0; transform:translateY(-4px); }
  to   { opacity:1; transform:translateY(0); }
}
/* The three-dot "thinking" affordance shown on the student's side while the
   first clause of the reply is still in flight (masks the LLM TTFT). */
@keyframes praxis-dot {
  0%, 80%, 100% { opacity:.25; transform:translateY(0); }
  40%           { opacity:1;   transform:translateY(-3px); }
}
/* Page entrance: the arriving page fades + rises in on load (see the body rule
   inside the guard below for why this replaced the cross-document transition). */
@keyframes praxis-page-enter {
  from { opacity:0; transform:translateY(8px); }
  to   { opacity:1; transform:translateY(0); }
}

@media (prefers-reduced-motion: no-preference) {
  :root:not([data-motion="off"]) {
    /* Hover / focus / theme-switch easing on the common interactive chrome, so
       state changes glide rather than snap (~140 ms). Scoped to properties that
       actually change — never `all` — to keep it cheap. */
    button, .gear, .chip a, a, .move-tag, .coach-move, input, select,
    details.panel > summary::after {
      transition: background-color .14s ease, color .14s ease,
                  border-color .14s ease, box-shadow .14s ease, opacity .14s ease,
                  filter .14s ease;
    }
    /* Theme swap itself: ease the big surfaces so light/dark doesn't hard-cut. */
    body { transition: background-color .18s ease, color .18s ease; }

    /* Page entrance (§3.2). Navigating between pages fades + rises the ARRIVING
       page in — an entrance-only animation on load, NOT a cross-document View
       Transition. The old `@view-transition { navigation:auto }` approach held a
       frozen snapshot of the outgoing page on screen for the whole network load
       of the next one, then cross-faded — so it *added* the navigation latency
       to the perceived click→next-page delay and, when the browser skipped it
       (slow load, interrupted nav, bfcache), rejected with an unhandled
       `AbortError: Transition was skipped`. This runs entirely on the incoming
       page instead: the click navigates immediately (no held snapshot, no exit
       fade), and this fires once the new page first paints — so time-to-next-page
       is just the raw navigation, and there's no API promise to reject.

       Fill mode is `backwards`, NOT `both`: `backwards` applies the `from` state
       before the first frame (no flash of the un-faded page) but reverts to the
       element's own styles once done, so `body` is left with NO transform. `both`
       would retain `transform:translateY(0)`, and any non-`none` transform on
       `body` makes it the containing block for `position:fixed` descendants —
       which would break the fixed `.theme-switch` and the tour spotlight (they
       must stay viewport-fixed). */
    body { animation: praxis-page-enter .26s ease-out backwards; }

    /* A freshly appended conversation message arrives instead of popping. The
       class is added by app.js only to genuinely new bubbles (not on the
       memory/feedback repaint frames), so it fires once per turn. */
    .turn > div.bubble-in { animation: praxis-bubble-in .28s ease-out both; }

    /* Coaching strip + debrief reveal: the moments the eye should be drawn to. */
    .coach:not([hidden]).strip-in { animation: praxis-strip-in .3s ease-out both; }
    details.debrief[open] > *:not(summary) { animation: praxis-fade-in .32s ease-out both; }

    /* Memory knowledge-base flash already exists in index.html; nothing to add. */

    /* Collapsible panels expand/collapse smoothly instead of snapping. Uses the
       modern `::details-content` pseudo + `interpolate-size` (so `height` can
       animate to/from the `auto` keyword); browsers without it keep the native
       instant toggle. The chevron already eases via its own transform transition
       (index.html). `content-visibility` transitions with `allow-discrete` so the
       content stays visible through a collapse instead of vanishing at frame 0.
       The `toggle` event still fires immediately, so panel-state persistence
       (app.js wirePersistence) is unaffected — this is purely visual. */
    details.panel, details.code-block { interpolate-size: allow-keywords; }
    details.panel::details-content, details.code-block::details-content {
      height:0; overflow:clip;
      transition: height .28s ease, content-visibility .28s allow-discrete;
    }
    details.panel[open]::details-content,
    details.code-block[open]::details-content { height:auto; }

    .thinking-dots i { animation: praxis-dot 1.1s ease-in-out infinite; }
    .thinking-dots i:nth-child(2) { animation-delay:.15s; }
    .thinking-dots i:nth-child(3) { animation-delay:.3s; }

    /* Account dropdown open/close (same idiom as index.html's ⚙ Settings menu):
       fade + drop from under its toggle via a discrete `display` transition +
       `@starting-style`. Lives here (not index.html) because the account bar is
       shared across pages. Reduced-motion users keep the instant snap. */
    .acct-menu { transform-origin:top right;
      transition:opacity .16s ease, transform .16s ease, display .16s allow-discrete; }
    @starting-style {
      .acct-menu { opacity:0; transform:translateY(-6px) scale(.98); } }
  }
}

/* ---- Skip link (a11y) ---------------------------------------------------
   Shared by every page (auth + in-app) — keyboard/screen-reader users can jump
   past the header controls straight to `<main id="main">`. Visually hidden by
   parking off-screen (never `display:none`/`[hidden]`, which would also drop
   it from the tab order) and revealed as an accent pill on focus. The focus
   visibility itself is unconditional — motion is optional polish only, so the
   `top` transition isn't gated behind the reduced-motion/data-motion guard. */
.skip-link {
  position:absolute; left:8px; top:-48px; z-index:1000;
  background:var(--accent); color:var(--on-accent);
  padding:8px 14px; border-radius:8px; font-weight:600; text-decoration:none;
}
.skip-link:focus { top:8px; }

/* ---- Visually-hidden (a11y) ---------------------------------------------
   Off-screen but still in the accessibility tree — for screen-reader-only
   labels and polite live regions (e.g. the conversation announcer in the
   practice room, which speaks each new reply without the visible transcript
   itself carrying aria-live, since that panel is rebuilt wholesale per turn
   and would otherwise re-announce the entire history). Shared by every page.
   `account.html` keeps its own scoped copy for a field that predates this. */
.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;
}

/* The auth pages (login/register/verify) center `.card` via `body{display:flex}`;
   wrapping it in `<main>` would make `<main>` the flex item instead and break
   centering. Scoped to `main:has(> .card)` so it only applies there — index.html
   / dashboard.html wrap `.app` / `#content` (not a bare `.card`), so their
   `<main>` keeps its normal block layout. */
main:has(> .card) { display:contents; }

/* ---- "Thinking" indicator (structure; animation is gated above) --------- */
.thinking { display:flex; align-items:center; gap:8px; color:var(--muted);
            font-size:13px; padding:2px 0; }
.thinking-dots { display:inline-flex; gap:4px; }
.thinking-dots i { width:6px; height:6px; border-radius:50%;
                   background:var(--muted); display:inline-block; }
.thinking[hidden] { display:none; }

/* ---- Appearance control (theme + accent) -------------------------------
   The markup emitted by PraxisTheme.mountControls (theme.js): a segmented
   dark/light toggle + accent swatches. Shared by the login screen and the main
   app's floating control, so it's styled once here. The host page positions the
   wrapper (`.theme-switch` — see auth.css / index.html). */
.praxis-theme-seg { display:inline-flex; border:1px solid var(--line);
  border-radius:999px; overflow:hidden; }
.praxis-theme-seg button {
  width:auto; border:none; background:var(--bg); color:var(--muted);
  padding:4px 9px; font:inherit; font-size:13px; line-height:1;
  border-radius:0; cursor:pointer; }
.praxis-theme-seg button[aria-pressed="true"] {
  background:var(--accent); color:var(--on-accent); }
.praxis-theme-swatches { display:inline-flex; gap:7px; }
.praxis-theme-swatches button {
  width:20px; height:20px; padding:0; border-radius:50%;
  border:2px solid transparent; cursor:pointer; }
.praxis-theme-swatches button[aria-pressed="true"] { border-color:var(--fg); }

/* Floating wrapper: a pill in the top-right corner, shared shape for login and
   the app. The app's own top bar offsets its copy (see index.html) so it never
   collides with the account chip / Settings gear. */
.theme-switch {
  display:flex; align-items:center; gap:12px;
  background:var(--panel); border:1px solid var(--line);
  border-radius:999px; padding:6px 10px; box-shadow:0 8px 24px var(--shadow); }

/* ---- App navigation bar (shared: Practice + Dashboard) -----------------
   Built by loadAppNav() in util.js and styled once HERE so the two in-app
   pages render identical chrome (no per-page drift). Three pieces: the primary
   `.tabs` (Practice / Dashboard), the `.acct` account dropdown (name ▾ → menu),
   and the `.demo-strip` mode banner. Class-based, so these win over index.html's
   element-level `button {}` base regardless of stylesheet order.

   Tabs: rounded pills, the active one washed in the accent. Real <a> links
   (primary navigation), so text-decoration is forced off in both states
   (dashboard.html has a global `a:hover{text-decoration:underline}` that would
   otherwise win on specificity). */

/* Brand wordmark: a small accent "PRAXIS" link atop each in-app header that
   returns to the public landing page at `/`. Identical on Practice + Dashboard
   so the back-to-home affordance never drifts. text-decoration forced off in
   both states (same reason as .tab, dashboard's global a:hover underline). */
.brand { display:inline-block; font-size:13px; font-weight:700; letter-spacing:.14em;
  color:var(--accent); text-transform:uppercase; margin:0 0 6px; }
.brand, .brand:hover { text-decoration:none; }
.brand:hover { filter:brightness(1.12); }

.tabs { display:inline-flex; gap:4px; margin-top:10px; flex-wrap:wrap; }
.tab { display:inline-flex; align-items:center; padding:6px 14px; border-radius:999px;
  font-size:13px; font-weight:600; color:var(--muted); background:transparent;
  border:1px solid transparent; cursor:pointer; }
.tab, .tab:hover { text-decoration:none; }
.tab:hover { color:var(--fg); }
.tab[aria-current="page"] { color:var(--accent); background:var(--accent-weak);
  border-color:var(--accent); }

/* Account dropdown: a compact pill that opens a right-aligned popover — same
   shape + open/close animation as index.html's ⚙ Settings menu. */
.acct-slot[hidden] { display:none; }
.acct { position:relative; display:inline-block; }
.acct-toggle { display:inline-flex; align-items:center; gap:7px; background:var(--panel);
  border:1px solid var(--line); color:var(--fg); border-radius:999px;
  padding:5px 12px 5px 14px; font:inherit; font-size:13px; font-weight:600; cursor:pointer; }
.acct-toggle:hover { border-color:var(--accent); }
.acct-caret { color:var(--muted); font-size:10px; line-height:1; }
.acct-menu { position:absolute; right:0; top:calc(100% + 8px); z-index:20; width:230px;
  background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:8px;
  box-shadow:0 12px 30px var(--shadow); opacity:1; transform:none; }
.acct-menu[hidden] { display:none; opacity:0; transform:translateY(-6px) scale(.98);
  pointer-events:none; }
.acct-id { display:flex; flex-direction:column; gap:2px; padding:6px 10px 10px;
  margin-bottom:6px; border-bottom:1px solid var(--line); }
.acct-id b { color:var(--fg); font-size:14px; }
.acct-id span { color:var(--muted); font-size:12px; word-break:break-all; }
.acct-menu .menu-item { display:block; width:100%; text-align:left; background:transparent;
  border:none; color:var(--fg); border-radius:8px; padding:8px 10px; font:inherit;
  font-size:13px; cursor:pointer; }
.acct-menu .menu-item:hover { background:var(--accent-weak); color:var(--accent); }
.acct-menu .menu-item.danger { color:var(--bad); }
.acct-menu .menu-item.danger:hover { background:transparent; color:var(--bad); }
/* Anonymous state: a single accent-filled Sign-in CTA (no dropdown). */
.signin-btn { background:var(--accent); color:var(--on-accent); border:none;
  border-radius:999px; padding:7px 16px; font:inherit; font-size:13px; font-weight:600;
  cursor:pointer; }
.signin-btn:hover { filter:brightness(1.12); }

/* Demo-mode banner: a thin full-width strip below the header (shown only in
   demo mode) so "nothing is saved" reads as a global state, not a button
   competing in the action row. Carries the Exit-demo / Sign-in-to-save action. */
.demo-strip { display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; background:var(--accent-weak); border:1px solid var(--accent);
  border-radius:10px; padding:10px 16px; margin-bottom:18px; font-size:13px; color:var(--fg); }
.demo-strip[hidden] { display:none; }
.demo-strip b { color:var(--accent); }
/* The strip's action(s) group at the right — the optional "🔊 Live walkthrough"
   button plus Exit-demo / Sign-in. */
.demo-strip .demo-actions { display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; }
.demo-strip button { background:transparent; border:1px solid var(--accent); color:var(--accent);
  border-radius:8px; padding:5px 12px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; }
.demo-strip button:hover { background:var(--accent); color:var(--on-accent); }
/* The Live-walkthrough button is the primary CTA in the strip — accent-filled
   so it reads as the "watch it work" invitation, distinct from the outlined
   Exit/Sign-in action beside it. */
.demo-strip button[data-walkthrough] { background:var(--accent); color:var(--on-accent);
  border-color:transparent; }
.demo-strip button[data-walkthrough]:hover { filter:brightness(1.12); }

/* ---- Microphone-permission warning strip -------------------------------
   Same full-width shape as the demo strip, but RED (--bad) — shown only when
   push-to-talk can't work because the mic is blocked/absent, so the visitor
   isn't left wondering why holding the button does nothing (the typed chat bar
   still works). Toggled by app.js. Like the demo strip it's `display:flex`, so
   the `[hidden]` attribute needs an explicit rule to win over that. The --bad
   token is AA-checked in both themes and independent of accent, so it stays
   legible on every theme×accent pair. */
.mic-warning { display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; background:color-mix(in srgb, var(--bad) 12%, transparent);
  border:1px solid var(--bad); border-radius:10px; padding:10px 16px;
  margin-bottom:18px; font-size:13px; color:var(--fg); }
.mic-warning[hidden] { display:none; }
.mic-warning b { color:var(--bad); }
.mic-warning button { background:transparent; border:1px solid var(--bad); color:var(--bad);
  border-radius:8px; padding:5px 12px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; }
.mic-warning button:hover { background:var(--bad); color:#fff; }

/* ---- Daily session-quota strip -----------------------------------------
   Same full-width shape as the demo strip. Neutral panel tint while there's
   quota left (it's an informational counter, not a warning); flips to the RED
   (--bad) treatment via `.maxed` once the daily cap is hit, matching the mic
   strip. Like the strips above it's `display:flex`, so it needs its own
   `[hidden]` rule to beat the UA default. */
.quota-strip { display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; background:var(--panel); border:1px solid var(--line);
  border-radius:10px; padding:10px 16px; margin-bottom:18px; font-size:13px; color:var(--muted); }
.quota-strip[hidden] { display:none; }
.quota-strip b { color:var(--fg); }
.quota-strip.maxed { background:color-mix(in srgb, var(--bad) 12%, transparent);
  border-color:var(--bad); color:var(--fg); }
.quota-strip.maxed b { color:var(--bad); }
/* An optional inline "Sign in" link on the demo strip when the cap is hit. */
.quota-strip a { color:var(--accent); font-weight:600; text-decoration:none; }
.quota-strip a:hover { text-decoration:underline; }

/* ---- Copyright notice --------------------------------------------------
   The "All Rights Reserved" line shown at the bottom of every page. Styled
   once here (both the auth pages via @import and the in-app pages via <link>
   pull this in); each page places its own <footer class="copyright"> — the
   auth pages pin it to the bottom (see auth.css) since their body is a
   centered flexbox. */
.copyright { color:var(--muted); font-size:12px; text-align:center;
  margin-top:32px; }
.copyright a { color:var(--accent); text-decoration:none; }
.copyright a:hover { text-decoration:underline; }
