/* ISAAC LSAT dashboard — vanilla CSS, no preprocessor/build step (U6).
 * Styling matches the barbri-lms design system (../barbri-lms/src/styles):
 * matisse-blue primary, Goldplay headings, cod-gray text, and the same
 * success/warning/danger status colors. The luxury navy+gold palette this
 * file used to carry (ported from Apex) has been fully replaced — barbri-lms
 * has no gold/luxury accent, so the former "gold" token now maps onto
 * barbri-lms's single amber $warning token.
 *
 * Body text names "Proxima Nova" in its font stack but does NOT self-host it
 * (unlike Goldplay, which IS bundled under static/fonts/ — same licensed
 * files barbri-lms itself uses, no Google Fonts / CDN dependency). Embedded in
 * barbri-lms, that name resolves to the @font-face the host page already
 * declared (barbri-lms/src/styles/custom-styles/_variables.scss's `$proxima:
 * 'Proxima Nova', ...` — NOT "Proxima Nova Old", which this file named until
 * 2026-08-25 and which matches no real font-face anywhere), so text matches
 * for free with no duplicate font download; the standalone pages (which have
 * no host page to borrow from) fall through the stack to a system sans-serif
 * instead of also bundling that font. */

@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_regular-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_regular-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_regular_it-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_regular_it-webfont.woff") format("woff");
  font-weight: normal;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_medium-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_medium-webfont.woff") format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_medium_it-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_medium_it-webfont.woff") format("woff");
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_semibold-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_semibold-webfont.woff") format("woff");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Goldplay";
  src: url("fonts/goldplay/latinotype_-_goldplay_semibold_it-webfont.woff2") format("woff2"),
       url("fonts/goldplay/latinotype_-_goldplay_semibold_it-webfont.woff") format("woff");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
}

/* Self-hosted, matching the Goldplay policy above: @barbri/oneux's `bb-icon` requires
 * "Material Symbols Outlined" to be loaded by the consuming app (its own README says
 * so), and the real design system loads it from Google Fonts — which our CSP's
 * `font-src 'self'` (app/middleware/security_headers.py) blocks outright. Sourced from
 * the `material-symbols` npm devDependency's variable-font build (weight 100–700, plus
 * the FILL/GRAD/opsz axes bb-icon's own CSS varies) rather than a CDN link. */
@font-face {
  font-family: "Material Symbols Outlined";
  src: url("fonts/material-symbols/material-symbols-outlined.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

/* Self-hosted ROUNDED variant, same origin/CSP rationale as the Outlined block above —
 * but a DISTINCT font-family, not a duplicate. @barbri/oneux's <bb-icon> is hardwired
 * internally to "Material Symbols Outlined" (its own compiled CSS references that exact
 * name), so it can't be repointed at Rounded; the tutor chat's thinking-trace card (U4,
 * 2026-08-23 tutor-chat-visual-pass plan) instead renders its own plain
 * <span class="msr-icon"> glyphs (app/mfe/views/chat.js's materialIcon()) against this
 * font-family directly, sourced from the same `material-symbols` npm devDependency's
 * Rounded build (node_modules/material-symbols/material-symbols-rounded.woff2). */
@font-face {
  font-family: "Material Symbols Rounded";
  src: url("fonts/material-symbols/material-symbols-rounded.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}
:root {
  /* ── @barbri/oneux token bridge ──────────────────────────────────────────
   * Real values pulled from @barbri/oneux@0.1.42's dist/global-styles.css (the
   * package pe-vue-net/barbri-lms actually ships), so this file's visual
   * language matches the live design system even though the package itself
   * isn't installed yet (blocked on an expired Azure Artifacts token as of
   * this pass — see package.json/.npmrc). Once it installs and its
   * global-styles.css loads, these become harmless duplicate declarations of
   * the same values; deleting this block then is the whole migration.
   * Only the handful of primitives/aliases this file actually uses are
   * ported — not oneux's full token set. */
  --bb-color-blue-50: #ebf2f9;
  --bb-color-blue-100: #c8d9ee;
  --bb-color-blue-500: #3c79c3;
  --bb-color-blue-600: #3163a0;
  --bb-color-blue-700: #2a5488;
  --bb-color-blue-900: #112237;
  --bb-color-blue-950: #001722;
  --bb-color-neutral-50: #ffffff;
  --bb-color-neutral-100: #efefef;
  --bb-color-neutral-300: #bdbdbd;
  --bb-color-neutral-500: #7c7c7c;
  --bb-color-neutral-700: #525252;
  --bb-color-neutral-950: #292929;
  --bb-color-red-50: #fef2f2;
  --bb-color-red-300: #f9a8aa;
  --bb-color-red-600: #d9253b;
  --bb-color-red-900: #83182e;
  --bb-color-orange-50: #fff0e6;
  --bb-color-orange-300: #ffb98a;
  --bb-color-orange-800: #752f00;
  --bb-color-orange-900: #471d00;
  --bb-color-green-50: #f0f7ee;
  --bb-color-green-700: #457336;
  --bb-color-green-900: #1d3117;
  --bb-color-purple-50: #f2e8fc;
  --bb-color-purple-300: #ad6eed;
  --bb-color-purple-600: #6917ba;

  /* Semantic aliases oneux's OWN component CSS reads directly (bb-card, bb-sidebar,
   * bb-sidebar-item, bb-chip — grepped from their .scss sources) — these have to be
   * real --bb-* names, not just isaac-3.0's own var(--color-*) aliases, since the
   * components' internal styles reference them by that name regardless of whether
   * oneux's own global-styles.css is loaded (see app/mfe/oneux.js's module docstring
   * on why that stylesheet isn't wired in this pass). */
  --bb-color-bg-surface: var(--bb-color-neutral-50);
  --bb-color-bg-surface-hover: var(--bb-color-blue-50);
  --bb-color-border-secondary: var(--bb-color-neutral-300);
  --bb-color-text-secondary: var(--bb-color-neutral-700);
  --bb-color-text-invert: var(--bb-color-neutral-50);
  --bb-body-font-size: 16px;
  --bb-body-font-weight: 400;
  --bb-body-font-weight-strong: 600;
  --bb-body-line-height: 1.5;
  --bb-icon-font-weight: 300;

  --bb-spacing-half: 4px;
  --bb-spacing-1u: 8px;
  --bb-spacing-1-5u: 12px;
  --bb-spacing-2u: 16px;
  --bb-spacing-3u: 24px;
  --bb-spacing-4u: 32px;
  --bb-spacing-6u: 48px;

  --bb-border-width-1: 1px;
  --bb-border-radius-xs: 2px;
  --bb-border-radius-s: 4px;
  --bb-border-radius-md: 6px;
  --bb-border-radius-lg: 8px;
  --bb-border-radius-xl: 12px;
  --bb-border-radius-3xl: 24px;

  --bb-shadow-200: 0px 3px 1px -1px rgb(26 26 26 / 7%);
  --bb-shadow-300: 0px 4px 6px -2px rgb(26 26 26 / 20%);
  --bb-shadow-400: 0px 8px 16px -4px rgb(26 26 26 / 22%);

  /* ── isaac-3.0's own names, re-expressed against the bridge above ──
   * Kept so every existing rule below (and any other file) needs no rename;
   * only the values move to track oneux's real palette. Typography stays on
   * Goldplay/Proxima Nova rather than oneux's Bitter/Nunito — those
   * aren't self-hosted anywhere in this repo yet and pulling them from a CDN
   * would be a new external dependency + CSP hole, which is a decision to
   * take deliberately rather than as a side effect of a visual refresh. */
  --color-bg: var(--bb-color-neutral-50);
  --color-surface: var(--bb-color-blue-50);
  --color-subtle: var(--bb-color-blue-100);
  --color-panel: var(--bb-color-neutral-50);
  --color-border: var(--bb-color-blue-100);
  --color-border-light: var(--bb-color-blue-50);

  --color-text: var(--bb-color-neutral-950);
  --color-text-muted: var(--bb-color-neutral-700);

  --color-primary: var(--bb-color-blue-700);
  --color-primary-hover: var(--bb-color-blue-600);
  --color-primary-active: var(--bb-color-blue-950);
  --color-primary-tint: var(--bb-color-blue-100);

  --color-danger: var(--bb-color-red-600);
  --color-danger-tint: var(--bb-color-red-50);
  --color-danger-border: var(--bb-color-red-300);
  --color-warning: var(--bb-color-orange-800);
  --color-warning-tint: var(--bb-color-orange-50);
  --color-warning-border: var(--bb-color-orange-300);
  --color-warning-text-dark: var(--bb-color-orange-900);
  --color-success: var(--bb-color-green-700);
  --color-success-tint: var(--bb-color-green-50);

  /* ISAAC's own accent, reserved for what ISAAC itself is doing (the rail's
   * thinking/trace/AI-generated affordances) rather than the app's neutral
   * chrome — mirrors the prototype's own purple-for-ISAAC convention. */
  --color-isaac: var(--bb-color-purple-600);
  --color-isaac-tint: var(--bb-color-purple-50);
  --color-isaac-border: var(--bb-color-purple-300);

  /* Typography — same webfonts as barbri-lms */
  --font-heading: "Goldplay", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-body: "Proxima Nova", Avenir, Helvetica, Arial, sans-serif;

  /* Shape & elevation */
  --radius-sm: var(--bb-border-radius-xs);
  --radius: var(--bb-border-radius-s);
  --radius-md: var(--bb-border-radius-md);
  --radius-lg: var(--bb-border-radius-lg);
  --radius-xl: var(--bb-border-radius-xl);
  --radius-full: 9999px;
  --shadow-sm: var(--bb-shadow-200);
  --shadow-md: var(--bb-shadow-300);
  --shadow-lg: var(--bb-shadow-400);

  --transition: 150ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--color-bg);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
}

:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* ── Shell: rail + content ────────────────────────────────────────────────
 * Ported from BARBRI-Applications/ProtoLab's isaac-tutor prototype
 * (prototypes/epp/src/pages/isaac-tutor/PowerscoreIsaacTutorPage.{vue,css}) —
 * same rail IA (title/BETA, "+ New study session", Analytics/Tutor nav,
 * collapse toggle). The two nav items are real @barbri/oneux <bb-sidebar-item>
 * elements (see shell.js) and style themselves — only the rail shell around
 * them (this file) and their collapsed-state layout overrides (further down)
 * are ours. */
.isaac-shell-body {
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}

.isaac-rail {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-2u);
  flex-shrink: 0;
  width: 350px;
  box-sizing: border-box;
  padding: var(--bb-spacing-1-5u);
  background: var(--color-panel);
  border-right: 1px solid var(--color-border);
  transition: width 0.2s ease;
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 0;
}

.isaac-rail.is-collapsed {
  width: 72px;
}

/* The rail's own scroll region — everything below the header/new-session
 * button. Deliberately NOT on `.isaac-rail` itself: giving the rail its own
 * `overflow-y: auto` also forces `overflow-x` non-visible (CSS's one-axis-
 * non-visible-forces-the-other rule), which clipped the account menu's
 * `position: absolute` popover (in `.isaac-rail-foot`, below this element) at
 * the rail's edge whenever it was collapsed.
 *
 * The overflow itself lives on `.isaac-rail-conversations` below, not here —
 * the nav group (Analytics/Tutor) has a fixed content height and should stay
 * fully visible, with only the (potentially long) conversation list scrolling
 * underneath it, rather than a long conversation list scrolling the nav out
 * of view too. */
.isaac-rail-scroll {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-2u);
  flex: 1;
  min-height: 0;
}

.isaac-rail-nav-group {
  flex-shrink: 0;
}

#app-header.isaac-rail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bb-spacing-2u);
  padding: var(--bb-spacing-1-5u);
}

/* `#app-header.isaac-rail-header` above is an ID selector (specificity 1,1,0)
 * — a class-only override here can never beat it, so this needs to repeat the
 * `#app-header` id to actually win (1,3,0). Without it, the collapsed toggle
 * stayed pinned to `justify-content: space-between`'s start edge instead of
 * centering, leaving it misaligned with the nav items/new-session button
 * below it (which center via their own `margin: 0 auto`, unaffected by the
 * header's justify-content). */
.isaac-rail.is-collapsed #app-header.isaac-rail-header {
  justify-content: center;
}

.isaac-rail.is-collapsed .isaac-rail-title-row {
  display: none;
}

.isaac-rail-title-row {
  display: flex;
  align-items: center;
  gap: var(--bb-spacing-1u);
  min-width: 0;
}

/* Matches ProtoLab's isaac-tutor prototype rail title (PowerscoreIsaacTutorPage.
 * css's `.isaac-tutor-rail-title`, its --font-family/size/weight/line-height-h3-l
 * tokens: Goldplay 24px/32px, weight 500 — not the smaller 20px/600 this used to
 * carry). */
.isaac-rail-title {
  font-family: var(--font-heading);
  font-size: 24px;
  font-weight: 500;
  line-height: 32px;
  color: var(--color-text);
}

/* Matches ProtoLab's shared BETA tag (design-system/components/BetaTag.css's
 * `.beta-tag`, its body-sm-s-semibold tokens): 12px/19px semibold, 6px-radius
 * chip rather than the 10px/700/uppercase pill this used to be — the tint/text
 * colors stay on this file's own --color-isaac tokens (bb-color-purple-600/50)
 * since the proto's exact purple-700/100 shades aren't in the bb bridge above. */
.isaac-rail-beta {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  line-height: 19px;
  letter-spacing: normal;
  padding: 2px 4px;
  border-radius: var(--radius-md);
  background: var(--color-isaac-tint);
  color: var(--color-isaac);
}

/* Matches ProtoLab's isaac-tutor prototype rail toggle (PowerscoreIsaacTutor
 * Page.css's `.isaac-tutor-rail-toggle { width: 40px; height: 40px; }`,
 * unconditional — not just while collapsed) so its hover/focus box is the
 * same size as the nav items and "+ New study session" button below it,
 * rather than the smaller 32px this used to be. */
.isaac-rail-toggle {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.isaac-rail-toggle:hover {
  background: var(--color-subtle);
  color: var(--color-primary);
}

/* Same outward-outline-in-a-crowded-72px-rail issue as the collapsed nav items
 * and new-session button — inset the ring so it fits inside this 40px box too. */
.isaac-rail.is-collapsed .isaac-rail-toggle:focus-visible {
  outline-offset: -2px;
}

/* Matches ProtoLab's isaac-tutor prototype `Tooltip` component (design-system/
 * components/Tooltip.css's `.tooltip`, its body-l tokens and --radius-s/
 * --spacing-md/lg values) — dark inverted chip, fading in on hover/focus.
 * Positioned via shell.js's attachTooltip() (`position: fixed`, left/top set
 * from the trigger's own getBoundingClientRect() — see that function's
 * comment for why this isn't plain CSS `position: absolute`). */
.isaac-tooltip {
  position: fixed;
  z-index: 1100;
  transform: translateY(-50%);
  padding: 8px 12px;
  background: var(--bb-color-neutral-950);
  color: #fff;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
  pointer-events: none;
  animation: isaac-tooltip-fade-in 0.18s ease-in-out;
}

@keyframes isaac-tooltip-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Matches ProtoLab's isaac-tutor prototype new-chat button (a DS `<Button
 * variant="secondary" size="small">`, whose `.btn`/`.btn-small` carry body-l-
 * semibold family/weight/line-height at a 16px size) — was 13px with no
 * line-height set. */
.isaac-rail-new-session {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--bb-spacing-1u);
  width: 100%;
  background: none;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  border-radius: var(--radius);
  padding: 9px 12px;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 26px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.isaac-rail-new-session:hover {
  background: var(--color-primary);
  color: #fff;
}

.isaac-rail-new-session-icon {
  flex-shrink: 0;
  font-size: 18px;
  line-height: 1;
}

.isaac-rail.is-collapsed .isaac-rail-new-session {
  width: 40px;
  height: 40px;
  padding: 0;
  margin: 0 auto;
}

.isaac-rail.is-collapsed .isaac-rail-new-session-icon {
  font-size: 20px;
}

.isaac-rail.is-collapsed .isaac-rail-new-session-label {
  display: none;
}

/* Same outward-outline clipping issue as the collapsed nav items above — draw
 * the focus ring inset so it fits inside the 40px icon-only button. */
.isaac-rail.is-collapsed .isaac-rail-new-session:focus-visible {
  outline-offset: -2px;
}

.isaac-rail-nav-group {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-half);
}

#app-nav {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-half);
}

/* bb-sidebar-item styles itself (its own connectedCallback-injected <style>, see
 * @barbri/oneux's bb-sidebar-item.scss) — the only thing left to us is the
 * collapsed-rail layout, which is rail-specific and the component knows nothing
 * about. Selectors reach through the light-DOM host into its rendered markup. */
#app-nav bb-sidebar-item .bb-sidebar-item__icon {
  font-size: 20px;
}

/* bb-sidebar-item's own scss reads `--bb-body-font-size` for its label text
 * (var(--bb-body-font-size), bridged above to 16px) — ProtoLab's isaac-tutor
 * prototype nav item (.isaac-tutor-nav-item) instead sizes its label at
 * body-sm-l (14px). Overriding the variable at this scope matches that
 * without touching the global 16px default other bb-* components rely on. */
#app-nav bb-sidebar-item {
  display: block;
  width: 100%;
  --bb-body-font-size: 14px;
}

.isaac-rail.is-collapsed #app-nav bb-sidebar-item {
  width: 40px;
  height: 40px;
  margin: 0 auto;
}

/* .bb-sidebar-item is nested inside oneux's own .bb-sidebar-item-container div
 * (see bb-sidebar-item.ts's render()), which has no height of its own — so
 * without this, the `.bb-sidebar-item { height: 100% }` below resolves against
 * an auto-height ancestor and collapses to content height, leaving the
 * focus/hover box short and wide instead of filling the 40px square. */
.isaac-rail.is-collapsed #app-nav bb-sidebar-item .bb-sidebar-item-container {
  height: 100%;
}

.isaac-rail.is-collapsed #app-nav bb-sidebar-item .bb-sidebar-item {
  width: 100%;
  height: 100%;
  padding: 0;
}

.isaac-rail.is-collapsed #app-nav bb-sidebar-item .bb-sidebar-item__text {
  display: none;
}

/* Collapsed nav items shrink to a 40px icon-only box (above) — oneux's own
 * :focus-visible outline (2px, offset OUTWARD by 2px) then has almost no room
 * in the 72px rail before it's cramped against the rail edge or the next item.
 * Draw it inset instead so it always fits cleanly inside the icon box. */
.isaac-rail.is-collapsed #app-nav bb-sidebar-item .bb-sidebar-item:focus-visible {
  outline-offset: -2px;
}

/* Active-state override (2026-08-24 proto-parity fix): oneux's own bb-sidebar-item.
 * scss renders `.-active` as a filled dark-navy pill (`background:
 * var(--bb-color-blue-900)`), but ProtoLab's isaac-tutor prototype
 * (PowerscoreIsaacTutorPage.css's `.isaac-tutor-nav-item.is-active`) uses a subtle
 * surface tint plus brand-colored bold text — the same treatment its own conversation
 * rows get (see `.conversation-item.active` below), so both read as one consistent
 * rail language. `#app-nav` in the selector is what wins the specificity fight
 * against oneux's own injected <style> (0,2,0) without needing !important. */
#app-nav bb-sidebar-item .bb-sidebar-item.-active,
#app-nav bb-sidebar-item .bb-sidebar-item.-active:hover {
  background: var(--bb-color-bg-surface-hover);
  color: var(--color-primary);
  font-weight: var(--bb-body-font-weight-strong);
}

.isaac-rail-foot {
  margin-top: auto;
  padding-top: var(--bb-spacing-1-5u);
  border-top: 1px solid var(--color-border-light);
}

/* ── Account widget (initials avatar + logout menu), pinned to the rail's foot ── */
.account {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--bb-spacing-1u);
  padding: var(--bb-spacing-half);
}

.isaac-rail.is-collapsed .account-email-preview {
  display: none;
}

.isaac-rail.is-collapsed .isaac-rail-conversations {
  display: none;
}

.account-email-preview {
  min-width: 0;
  font-size: 12px;
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: var(--radius-full, 9999px);
  border: 1px solid var(--color-border);
  background: var(--color-primary);
  color: #fff;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), transform var(--transition);
}

.account-btn:hover {
  box-shadow: 0 0 0 3px var(--color-warning-tint), var(--shadow-md);
}

.account-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-primary-tint), var(--shadow-md);
}

.account-initials {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.account-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  min-width: 200px;
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 6px;
  z-index: 300;
}

.account-email {
  padding: 8px 10px 6px;
  font-size: 12px;
  color: var(--color-text-muted);
  word-break: break-all;
  border-bottom: 1px solid var(--color-border-light);
  margin-bottom: 4px;
}

.account-logout {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 8px 10px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--color-danger);
  cursor: pointer;
  transition: background var(--transition);
}

.account-logout:hover {
  background: var(--color-danger-tint);
}

.account-logout:disabled {
  opacity: 0.6;
  cursor: default;
}

.isaac-view-root {
  flex: 1;
  min-width: 0;
}

#dashboard-root {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-4u);
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 32px 64px;
}

@media (max-width: 900px) {
  .isaac-shell-body {
    flex-direction: column;
  }
  /* Below this breakpoint the rail is a collapsible drawer, not a permanent
   * side column: shell.js starts it `.is-collapsed` by default on mobile
   * (see mountShell()'s `mobileQuery` check) so only this header row (title +
   * expand toggle) shows as a slim top bar and the chat gets the full screen.
   * Pressing the toggle covers the ENTIRE screen with the rail (a full-screen
   * nav overlay, `position: fixed` + `inset: 0`), stacked full-width (header,
   * new-session, nav, conversations, account) instead of wrapped into a
   * cramped shared row — a row/wrap layout there let conversation titles and
   * the Analytics/Tutor nav fight for the same line and get crushed. */
  .isaac-rail,
  .isaac-rail.is-collapsed {
    width: 100%;
    flex-shrink: 0;
    flex-direction: column;
    border-right: none;
    border-bottom: 1px solid var(--color-border);
    position: static;
    height: auto;
    overflow: hidden;
  }
  .isaac-rail:not(.is-collapsed) {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100vh;
    z-index: 50;
    border-bottom: none;
  }
  /* Desktop's collapsed state is a narrow 72px icon-only column, so it hides
   * the title and centers the lone toggle in it (see the unscoped
   * `.isaac-rail.is-collapsed` rules above this media query). Mobile's
   * collapsed state is a full-width header bar instead — undo both so the
   * "ISAAC" title/badge stays visible and the toggle sits at the row's end. */
  .isaac-rail.is-collapsed .isaac-rail-title-row {
    display: flex;
  }
  .isaac-rail.is-collapsed #app-header.isaac-rail-header {
    justify-content: space-between;
  }
  /* Everything but the header row is the part the toggle shows/hides. */
  .isaac-rail.is-collapsed .isaac-rail-new-session,
  .isaac-rail.is-collapsed .isaac-rail-scroll,
  .isaac-rail.is-collapsed .isaac-rail-foot {
    display: none;
  }
  .isaac-rail-scroll {
    flex-direction: column;
    min-height: 0;
    /* NOT the scroll container here (unlike desktop) — a flex item's
     * overflowing content only bubbles up as scrollable overflow to an
     * ancestor when that item's own height is genuinely auto/content-sized;
     * `.isaac-rail-conversations` below gets a definite flex-computed height
     * instead (flex: 1 against this row's own capped height), so its
     * overflow never reaches this far up. The conversations list scrolls
     * itself below instead — same pattern as the desktop rule. */
    overflow: visible;
  }
  .isaac-rail-nav-group,
  #app-nav {
    flex-direction: row;
    flex-shrink: 0;
  }
  .isaac-rail-nav-item {
    justify-content: center;
  }
  .isaac-rail-foot {
    flex-shrink: 0;
    margin-top: 0;
    border-top: 1px solid var(--color-border-light);
  }
  .isaac-rail-conversations {
    border-top: none;
    overflow-y: auto;
  }
  /* The "ISAAC can make mistakes"/AI-generated-content fine print under the
   * composer is the first thing to go on a cramped mobile screen — it's
   * non-essential legal boilerplate, not something the reply itself depends
   * on, and it competes with the composer/quick-actions for the limited
   * space right above the keyboard. */
  .composer-disclaimer {
    display: none;
  }
}

.hidden {
  display: none !important;
}

/* Matches ProtoLab's isaac-tutor prototype h1 (PowerscoreIsaacTutorPage.css's
 * `.isaac-tutor-title`, its --font-family/size/weight/line-height-h1-l tokens:
 * Goldplay 32px/44px, weight 500, normal text color — not the bolder 28px/600
 * primary-blue this used to carry). */
.welcome {
  font-family: var(--font-heading);
  font-size: 32px;
  font-weight: 500;
  line-height: 44px;
  color: var(--color-text);
  margin: 0 0 20px;
}

#banner {
  padding: 12px 16px;
  border-radius: 6px;
  margin-bottom: 16px;
  font-size: 14px;
}

.banner-unavailable {
  background: var(--color-danger-tint);
  color: var(--color-danger);
  border: 1px solid var(--color-danger-border);
}

.banner-empty {
  background: var(--color-primary-tint);
  color: var(--color-primary-active);
  border: 1px solid #acceed; /* $link-water-200 */
}

/* Each Analytics panel is a plain div now (dashboard.js) — NOT a real @barbri/
 * oneux <bb-card> as it used to be (2026-08-25): <bb-card>'s BBLightElement
 * base class has an unconditional bug in its light-DOM slot management that
 * throws a HierarchyRequestError on literally every card's first render (see
 * dashboard.js's ctaPanel comment for the full mechanism), independent of
 * content. The radius/shadow/padding/background bb-card.scss used to supply
 * are hand-rolled below instead. Only the heading typography beyond this is
 * still ours, as before.
 *
 * Border matches ProtoLab's isaac-tutor prototype card border (design-system/
 * components/Card.css's base `.card { border: var(--border-width-1) solid
 * var(--color-border); }`, where --color-border is --color-matisse-50,
 * #EBF2F9) — bb-card.scss's own border instead used --bb-color-neutral-300
 * (#bdbdbd), a much more visible gray that read as a heavy outline next to
 * the card's own shadow. --color-border-light is already bridged to the same
 * #ebf2f9 blue-50 tint the proto uses, so this reuses it rather than
 * introducing a new token. Radius/shadow/padding/background are bb-card.scss's
 * own former values (--bb-border-radius-md, --bb-shadow-300, --bb-spacing-6u,
 * --bb-color-bg-surface), kept as-is since only the border was ever meant to
 * change from bb-card's default look. */
.isaac-panel {
  display: block;
  border-radius: var(--bb-border-radius-md);
  border: 1px solid var(--color-border-light);
  background-color: var(--bb-color-bg-surface);
  box-shadow: var(--bb-shadow-300);
  padding: var(--bb-spacing-6u);
}
/* Matches ProtoLab's isaac-tutor prototype card title (design-system/components/
 * Card.css's `.card-container-title`, its --font-family/size/weight/line-height-h4-l
 * tokens: Goldplay 22px/30px, weight 500 — not the smaller 20px/600 this used to
 * carry). */
.isaac-panel-title {
  margin: 0 0 14px;
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 500;
  line-height: 30px;
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ── Helptext ("?") disclosure — small, unobtrusive, click-to-toggle flyout ── */
.helptext {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
}

.helptext-trigger {
  width: 16px;
  height: 16px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-subtle);
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.helptext-trigger:hover {
  background: var(--color-primary-tint);
  color: var(--color-primary-active);
}

.helptext-flyout {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 200;
  width: 240px;
  max-width: min(240px, 80vw);
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 10px 12px;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--color-text);
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}

.stat-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  padding: 14px;
}

.stat-label-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Matches ProtoLab's isaac-tutor prototype stat card (design-system/components/
 * Card.css's `.card-global-label` / `.card-global-value`, its body-sm-l-semibold
 * and h4-l tokens) — was an 11px uppercase-tracked label and a 30px/600 value,
 * neither of which the proto card actually uses. */
.stat-label {
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  line-height: 21px;
  color: var(--color-text-muted);
}

.stat-value {
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 500;
  line-height: 30px;
  margin-top: 4px;
}

.gain-up {
  color: var(--color-success);
}

.gain-down {
  color: var(--color-danger);
}

.gain-flat {
  color: var(--color-text-muted);
}

.list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Matches ProtoLab's isaac-tutor prototype empty-state card text (Powerscore
 * IsaacTutorPage.css's `.isaac-tutor-goal-empty`, its body-l tokens) — was
 * 13px with no family/weight/line-height set. */
.empty-note {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text-muted);
}

.section-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Matches ProtoLab's isaac-tutor prototype per-section accuracy row (Powerscore
 * IsaacTutorPage.css's `.isaac-tutor-section-name`/`-meta`, their h4-l/body-l
 * tokens) — was an unsized 600-weight name and a 12px muted meta line. */
.section-name {
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 500;
  line-height: 30px;
}

.section-meta {
  display: flex;
  gap: 12px;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text-muted);
}

.bar-wrap {
  height: 12px;
  border-radius: var(--radius-full);
  background: var(--color-subtle);
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  background: var(--color-primary);
}

/* Course coverage (U7) — deliberately NOT `.bar-fill`'s blue: coverage is LMS
 * catalog completion, a different axis from the accuracy bars above (which
 * reuse `.bar-wrap`/`.bar-fill`) and must never read as the same measurement.
 * Reuses `--color-isaac` (purple), already established elsewhere as ISAAC's own
 * accent (see `.isaac-rail-beta`) rather than raw performance data. */
.coverage-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Course Coverage rows have no direct ProtoLab counterpart (that card isn't in
 * the isaac-tutor prototype), but they're the same "row name + meta line"
 * shape as Per-Section Accuracy just above — kept on the same h4-l/body-l
 * treatment as `.section-name`/`.section-meta` for internal consistency
 * rather than leaving a same-shaped row at a different size/weight. */
.coverage-section-name {
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 500;
  line-height: 30px;
}

.coverage-meta {
  display: flex;
  gap: 12px;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text-muted);
}

.coverage-bar-fill {
  background: var(--color-isaac);
}

.coverage-unavailable-note {
  color: var(--color-danger);
}

/* Matches ProtoLab's isaac-tutor prototype `Message` component (design-system/
 * components/Message.css's `.message`/`.message-info`/`.message-text`) — an
 * info-tinted bordered box with an icon, reusing this file's own existing
 * `--color-primary-tint`/`#acceed` info-tone pair (see `.banner-empty` above)
 * rather than introducing new tokens the proto's own `--color-bg-surface-info`
 * doesn't have a bridge for here. Was a bare 13px muted line with no icon,
 * background, or border. */
/* Proto's actual info tone: --color-bg-surface-info = --color-matisse-50
 * (#EBF2F9, = --bb-color-blue-50 here) and --color-border-info =
 * --color-matisse-300 (#82A9D9) — the bb bridge above has no blue-300 shade,
 * so that one border color is a plain hex rather than a token. The earlier
 * pass reused `--color-primary-tint` (blue-100, #c8d9ee — visibly darker) and
 * `.banner-empty`'s unrelated `#acceed` border by analogy instead of the
 * proto's real values. */
.dashboard-freshness-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 12px;
  border-radius: var(--radius-lg);
  border: 1px solid #82a9d9;
  background: var(--bb-color-blue-50);
  margin: 0 0 16px;
}

.dashboard-freshness-note-icon {
  margin-top: 2px;
  flex-shrink: 0;
  /* --color-primary-active (bb-color-blue-950, #001722) reads as near-black,
   * not blue — fine as the .banner-empty text color it was chosen for, but
   * wrong for a lone icon that's supposed to read as an info-blue accent. */
  color: var(--color-primary);
  font-variation-settings: 'FILL' 1;
}

.dashboard-freshness-note-text {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text);
  margin: 0;
}

.spinner-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.loading-row {
  padding: 4px 0 20px;
  color: var(--color-text-muted);
  font-size: 14px;
}

.spinner {
  width: 15px;
  height: 15px;
  flex: none;
  border: 2px solid var(--color-border-light);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Text on the left, status on the right, centered against each other — matches the
 * ux/isaac-tutor prototype's "Areas to Review" row. A divider between rows rather than a
 * rule around the list (the card's own edges already close it), and no left border-color
 * accent: that marked every row as a warning, including ones that are simply next in the
 * ranking — the Stagnant chip is what actually says which ones are stuck. */
.weak-area-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bb-spacing-2u);
  padding: var(--bb-spacing-1-5u) 0;
}

.weak-area-row + .weak-area-row {
  border-top: 1px solid var(--color-border-light);
}

.weak-area-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/* Matches ProtoLab's isaac-tutor prototype "Areas to Review" row (Powerscore
 * IsaacTutorPage.css's `.isaac-tutor-area-title`/`-summary`, their body-l-
 * semibold/body-l tokens) — the 600 weight already matched, but neither had an
 * explicit size (title) or was smaller than spec (12px vs 16px, summary). */
.weak-area-title {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 26px;
  overflow-wrap: anywhere;
}

.weak-area-rationale {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.weak-area-stagnant-note {
  font-size: 12px;
  color: var(--color-danger);
  margin-bottom: 6px;
}

/* Plain badge styling, since this is now a plain span rather than a real
 * <bb-chip variant="danger" size="sm"> — see dashboard.js's comment on why —
 * so bb-chip.scss's own `.-danger`/`.-size-sm` recipe (uppercase, medium
 * weight, 14px, 4px/8px padding, md radius) has to be hand-rolled here
 * instead, on this file's own --color-danger-* tokens rather than bb-chip's
 * exact red-50/red-100/red-700 (not all bridged above). */
.weak-area-stagnant-chip {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: var(--radius);
  background: var(--color-danger-tint);
  border: 1px solid var(--color-danger-border);
  color: var(--color-danger);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.1;
  text-transform: uppercase;
}

.goal-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Matches ProtoLab's isaac-tutor prototype card body text — `.goal-target` as
 * the bold fact line takes body-l-semibold (same 16px/600/26px "Areas to
 * Review" title uses via `.weak-area-title`); `.goal-hours` as the
 * secondary line takes body-l, same as `.isaac-tutor-goal-empty`. Was
 * 14px/600 and 12px muted respectively, neither with an explicit family. */
.goal-target {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 26px;
}

.goal-hours {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--color-text-muted);
}

.goal-cta-button {
  align-self: flex-start;
  background: var(--color-primary);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition);
}

.goal-cta-button:hover {
  background: var(--color-primary-hover);
}

.pace-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 10px;
  border-radius: 12px;
  margin-top: 4px;
}

.pace-badge-ahead {
  background: var(--color-success-tint);
  color: var(--color-success);
}

.pace-badge-behind {
  background: var(--color-danger-tint);
  color: var(--color-danger);
}

.pace-badge-on-pace {
  background: var(--color-primary-tint);
  color: var(--color-primary-active);
}

.pace-badge-new {
  background: var(--color-primary-tint);
  color: var(--color-primary-active);
}

.pace-projection {
  font-size: 12px;
  color: var(--color-text-muted);
  margin-top: 2px;
}

.phase-chip {
  display: inline-block;
  font-size: 11px;
  color: var(--color-text-muted);
  margin-top: 2px;
}

.trap-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--bb-spacing-1u) 0;
}

.trap-row + .trap-row {
  border-top: 1px solid var(--color-border-light);
}

.trap-label-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Matches ProtoLab's isaac-tutor prototype trap-susceptibility row (Powerscore
 * IsaacTutorPage.css's `.isaac-tutor-trap-label`, its body-l tokens) — was
 * 14px with no explicit weight/line-height/family. */
.trap-label {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
}

.trap-badge {
  font-size: 12px;
  font-weight: 600;
  padding: 2px 10px;
  border-radius: var(--radius-lg);
  background: var(--color-subtle);
  color: var(--color-text-muted);
}

.trap-susceptible {
  background: var(--color-danger-tint);
  color: var(--color-danger);
}

/* The daily invitation into ISAAC — the one panel on the page that's a
 * greeting rather than a report, so it gets its own quiet identity: ISAAC's
 * own purple (already established for the BETA tag and thinking-trace card)
 * instead of the neutral white/blue-border every other panel here uses. */
#cta-panel {
  border-color: var(--color-isaac-border);
  background: linear-gradient(160deg, var(--color-isaac-tint) 0%, #fff 40%);
}

.cta-eyebrow {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-isaac);
  margin: 0 0 6px;
}

.cta-greeting {
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 500;
  line-height: 30px;
  color: var(--color-text);
  margin: 0 0 20px;
}

/* Shaped like the real tutor-chat composer (`.composer-input`/`.composer-send`
 * below) — a rounded field with a circular arrow at its trailing end — so
 * starting a session reads as opening the same conversation the pill is
 * already previewing, not submitting a form. The arrow lives in `::after`
 * (not a child element) so `wireCta()`'s `ctaButton.textContent = label`
 * keeps working unchanged — textContent replacement doesn't touch generated
 * content. */
.cta-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  box-sizing: border-box;
  background: #fff;
  color: var(--color-text);
  text-align: left;
  border: 1px solid var(--color-isaac-border);
  border-radius: var(--radius-full);
  padding: 14px 14px 14px 22px;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color var(--transition), box-shadow var(--transition),
    transform var(--transition);
}

.cta-button::after {
  content: 'arrow_upward';
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-isaac);
  color: #fff;
  font-family: 'Material Symbols Rounded';
  font-size: 18px;
  font-weight: 400;
  line-height: 1;
  font-feature-settings: 'liga';
  transition: transform var(--transition);
}

.cta-button:hover:not(:disabled) {
  border-color: var(--color-isaac);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.cta-button:hover:not(:disabled)::after {
  transform: translateX(2px);
}

.cta-button:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

#cta-status {
  margin-top: 12px;
  font-size: 13px;
  color: var(--color-text-muted);
}

/* ── Tutor chat (chat.html / chat.js) ─────────────────────────────────────── */

.chat-body {
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

#chat-root {
  flex: 1;
  min-height: 0;
  width: 100%;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Conversation history (persisted conversations, 2026-07-24 plan) now renders inside
   the SHARED rail (shell.js's .isaac-rail-conversations slot), below the Analytics/
   Tutor nav, instead of its own side-by-side column — see chat.js's module docstring
   (2026-08-23 sidebar-merge follow-up to the tutor-chat visual pass). The rail widens
   on the tutor route (below, near .isaac-rail) to give conversation titles room; the
   .conversation-* rules here style the list's CONTENTS regardless of which container
   they render into. */
.isaac-rail-conversations {
  display: flex;
  flex-direction: column;
  gap: var(--bb-spacing-half);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-top: var(--bb-spacing-1u);
  border-top: 1px solid var(--color-border-light);
}

.isaac-rail-conversations-label {
  padding: 0 var(--bb-spacing-1-5u);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.conversation-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--bb-spacing-half);
}

.conversation-item {
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 13px;
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background var(--transition), color var(--transition);
  /* `.isaac-rail-conversations` is a flex column, and this item's own
   * `overflow: hidden` (needed for the ellipsis above) strips its flexbox
   * "automatic minimum size" — without flex-shrink: 0, a long list gets
   * squashed to fit the available space (clipping every row's text) instead
   * of overflowing the container and letting it scroll. */
  flex-shrink: 0;
}

.conversation-item:hover:not(:disabled) {
  background: var(--color-subtle);
}

/* Same subtle surface-tint + brand-text treatment the nav items get (see
 * `#app-nav bb-sidebar-item .bb-sidebar-item.-active` above) — ProtoLab's
 * PowerscoreIsaacTutorPage.css shares ONE rule across `.isaac-tutor-nav-item.
 * is-active` and `.isaac-tutor-conversation.is-active`, so both read as the same
 * kind of "this is where you are" state rather than two different treatments
 * (2026-08-24 proto-parity fix, supersedes the border-accent version from the
 * 2026-08-23 sidebar-merge). */
.conversation-item.active {
  background: var(--bb-color-bg-surface-hover);
  color: var(--color-primary);
  font-weight: 600;
}

.conversation-item:disabled {
  opacity: 0.5;
  cursor: default;
}

.conversation-list-status {
  font-size: 12px;
  color: var(--color-text-muted);
  padding: 8px 10px;
}

.conversation-list-error {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--color-danger);
  padding: 8px 10px;
}

.conversation-retry-btn {
  align-self: flex-start;
  background: none;
  border: 1px solid var(--color-danger);
  color: var(--color-danger);
  border-radius: var(--radius);
  padding: 4px 10px;
  font-size: 12px;
  cursor: pointer;
}

.chat-main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ── Blank-chat hero state (2026-08-23 tutor-chat-visual-pass plan, U2; copy/order
   verified against ProtoLab's IsaacTutorChat.vue/.css, 2026-08-24) ──
   For a genuinely empty, non-seeded conversation, chat.js's enterHero() hides
   .transcript-wrap and reparents the SAME .composer/.composer-disclaimer/.quick-actions
   elements into .chat-hero, IN THAT ORDER (greeting, composer, disclaimer, chips) — no
   duplicate markup, just a different parent + a centering modifier on .chat-main.
   Quick-action chip styling (bb-chip) lives with .quick-action/.follow-up-chip further
   below (U3). */
.chat-main.is-hero {
  justify-content: center;
  align-items: center;
}

.chat-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bb-spacing-2u);
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
  padding: var(--bb-spacing-4u) var(--bb-spacing-2u);
  text-align: center;
}

.chat-hero-greeting {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 26px;
  font-weight: 600;
  color: var(--color-text);
}

.chat-hero .quick-actions {
  justify-content: center;
}

.chat-hero .composer {
  width: 100%;
}

.transcript-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.transcript {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 4px 2px;
}

.jump-to-bottom {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  z-index: 20;
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 6px 14px;
  font-size: 12px;
  color: var(--color-text);
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: border-color var(--transition), color var(--transition), background var(--transition);
}

.jump-to-bottom:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
  background: var(--color-primary-tint);
}

.msg {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-width: 88%;
}

.msg-user {
  align-self: flex-end;
  align-items: flex-end;
}

.msg-assistant {
  align-self: flex-start;
}

.msg-who {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

/* Matches ProtoLab's isaac-tutor prototype chat bubble (IsaacTutorChat.css's
 * `.isaac-chat-bubble`, its --font-family/size/weight/line-height-body-l tokens:
 * Proxima Nova 16px/24px, weight 400 — not the smaller 14px/1.55 this used to
 * carry). */
.msg-body {
  padding: 11px 15px;
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.msg-user .msg-body {
  background: var(--color-primary);
  color: #fff;
  border-bottom-right-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.msg-assistant .msg-body {
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-bottom-left-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

/* Block elements the Markdown renderer (chat.js's mdFeed()/renderMarkdownStatic()) builds
 * for assistant text — kept compact so paragraphs/lists/headers read as chat prose, not
 * a full document, inside the narrow bubble. */
.msg-body .msg-p {
  margin: 0 0 8px;
}

.msg-body .msg-p:last-child {
  margin-bottom: 0;
}

.msg-body ul,
.msg-body ol {
  margin: 0 0 8px;
  padding-left: 22px;
}

.msg-body ul:last-child,
.msg-body ol:last-child {
  margin-bottom: 0;
}

.msg-body li {
  margin-bottom: 2px;
}

.msg-body .msg-h1,
.msg-body .msg-h2,
.msg-body .msg-h3,
.msg-body .msg-h4,
.msg-body .msg-h5,
.msg-body .msg-h6 {
  margin: 0 0 6px;
  font-weight: 700;
  line-height: 1.3;
}

.msg-body .msg-h1 { font-size: 19px; }
.msg-body .msg-h2 { font-size: 18px; }
.msg-body .msg-h3,
.msg-body .msg-h4,
.msg-body .msg-h5,
.msg-body .msg-h6 { font-size: 16px; }

/* ── Word-by-word reveal-text animation (2026-08-23 tutor-chat-visual-pass plan, U5) ──
 * Replaces the old character-count typewriter (chat.js's pumpTypewriter, now pumpReveal())
 * with a per-word opacity fade-in: each streamed word is its own <span class="reveal-word">
 * (chat.js's revealWord()), so this is genuinely the first word-reveal-style keyframe in
 * this file (the only prior one, @keyframes spin above, is the loading-spinner/trace-step
 * rotation). .qcard (above) reuses the SAME keyframe for its own, independently-timed
 * per-card fade-in. prefers-reduced-motion turns both off entirely — the reveal loop
 * itself is also skipped in JS for the word case (chat.js's prefersReducedMotion()), so
 * this media query is a belt-and-suspenders backstop, not the only guard. */
@keyframes reveal-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.reveal-word {
  animation: reveal-fade-in 160ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .reveal-word,
  .qcard {
    animation: none;
  }
}

.msg-note {
  align-self: center;
  font-size: 12px;
  color: var(--color-text-muted);
  background: var(--color-subtle);
  border-radius: var(--radius-lg);
  padding: 4px 12px;
}

.note-warn {
  background: var(--color-warning-tint);
  color: var(--color-warning);
}

.note-grounded {
  background: var(--color-primary-tint);
  color: var(--color-primary-active);
  font-style: normal;
}

.note-error {
  background: var(--color-danger-tint);
  color: var(--color-danger);
}

/* AI practice-question card */
.qcard {
  align-self: stretch;
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Each card gets its OWN fade-in (2026-08-23 tutor-chat-visual-pass plan, U5) — a
     multi-item get_questions result renders one .qcard per item (chat.js's
     renderQuestionCard(), appended directly to .transcript with no shared wrapper), so
     this is already independently-timed per card, never one combined fade for the whole
     result. See the reveal-fade-in keyframe below, shared with .reveal-word. */
  animation: reveal-fade-in 220ms ease-out;
}

.qcard-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.qtag {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: var(--radius-lg);
  background: var(--color-subtle);
  color: var(--color-text-muted);
}

.qtag-ai {
  background: var(--color-warning-tint);
  color: var(--color-warning-text-dark);
}

.qcard-stimulus {
  margin: 0;
  font-size: 14px;
  line-height: 1.55;
}

.qcard-stem {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
}

.qcard-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.qoption {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  text-align: left;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.45;
  cursor: pointer;
  color: var(--color-text);
  transition: border-color var(--transition), background var(--transition);
}

.qoption:hover:not(.locked) {
  border-color: var(--color-primary);
  background: var(--color-panel);
}

.qoption.selected {
  border-color: var(--color-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

.qoption.locked {
  cursor: default;
}

.qoption.correct {
  background: var(--color-success-tint);
  border-color: var(--color-success);
}

.qoption.wrong {
  background: var(--color-danger-tint);
  border-color: var(--color-danger);
}

.qoption-letter {
  font-weight: 700;
  color: var(--color-primary);
}

.qcard-reveal {
  align-self: flex-start;
  background: none;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  border-radius: var(--radius);
  padding: 7px 16px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.qcard-reveal:hover:not(:disabled) {
  background: var(--color-primary);
  color: #fff;
}

.qcard-reveal:disabled {
  opacity: 0.5;
  cursor: default;
}

.qcard-verdict {
  font-size: 13px;
  color: var(--color-text-muted);
}

.verdict-correct {
  color: var(--color-success);
  font-weight: 600;
}

.verdict-wrong {
  color: var(--color-danger);
  font-weight: 600;
}

/* Composer */
/* Quick-action + follow-up chips (2026-08-23 tutor-chat-visual-pass plan, U3; restyled
   2026-08-25 off @barbri/oneux's <bb-chip> — see chat.js's makeChip() for why) — plain
   <button class="chip"> elements matching the real prototype's Chip.vue/Chip.css
   exactly: a sentence-case pill, not bb-chip's own shared tag/chip base, which hardcodes
   `text-transform: uppercase` and a small tag-sized footprint on every variant. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 40px;
  padding: 0 16px;
  background-color: var(--bb-color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--color-text);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition);
}

.chip:hover:not(:disabled) {
  background-color: var(--bb-color-bg-surface-hover);
  border-color: var(--color-primary);
}

.chip:disabled {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}

.quick-actions,
.follow-ups {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Screen-reader-only content — no visual presence, still reachable by assistive tech.
 * Used by chat.js's traceAnnouncer (U4's aria-live settle announcement). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Material Symbols ROUNDED glyphs (see the self-hosted @font-face above) — used ONLY by
 * the thinking-trace card below, via chat.js's materialIcon(). Ligature substitution
 * (`font-feature-settings: "liga"`) turns the span's plain-text glyph NAME (e.g.
 * "check_circle") into the icon; mirrors the `material-symbols` npm package's own
 * documented .material-symbols-rounded utility class shape. */
.msr-icon {
  font-family: "Material Symbols Rounded";
  font-weight: 400;
  font-style: normal;
  font-size: 18px;
  line-height: 1;
  display: inline-flex;
  flex-shrink: 0;
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "liga";
  font-variation-settings: "FILL" 0;
}

/* Material Symbols OUTLINED glyphs — the rail's own icon-only chrome (collapse
 * toggle), matching the Outlined family @app-nav's bb-sidebar-item icons already
 * use (see the self-hosted @font-face above), rather than the Rounded family
 * `.msr-icon` reserves for the tutor-chat thinking-trace card. Same ligature-
 * substitution shape as `.msr-icon` above. */
.mso-icon {
  font-family: "Material Symbols Outlined";
  font-weight: 400;
  font-style: normal;
  font-size: 20px;
  line-height: 1;
  display: inline-flex;
  flex-shrink: 0;
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "liga";
  font-variation-settings: "FILL" 0;
}

/* ── Thinking-trace card (2026-08-23 tutor-chat-visual-pass plan, U4) ──
 * Replaces the old single ephemeral "ISAAC is ..." status note (chat.js's dispatchChunk()
 * tool_start/tool_end/tool_result/tool_error handling) with a persistent, collapsible
 * card of one step per tool call. Plain DOM — no @barbri/oneux component dependency,
 * unlike the quick-action/follow-up chips above. */
.trace-card {
  align-self: flex-start;
  max-width: 88%;
  background: var(--color-isaac-tint);
  border: 1px solid var(--color-isaac-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.trace-card-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  padding: 8px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-isaac);
  cursor: pointer;
  text-align: left;
}

.trace-card-title {
  flex: 1;
}

.trace-card-chevron {
  transition: transform var(--transition);
}

.trace-card.is-collapsed .trace-card-chevron {
  transform: rotate(-90deg);
}

.trace-steps {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0 12px 10px;
  list-style: none;
}

.trace-step {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.trace-step-icon {
  margin-top: 1px;
  color: var(--color-text-muted);
}

.trace-step.is-running .trace-step-icon {
  color: var(--color-isaac);
  animation: spin 1s linear infinite;
}

.trace-step.is-done .trace-step-icon {
  color: var(--color-success);
}

.trace-step.is-error .trace-step-icon {
  color: var(--color-warning);
}

.trace-step.is-interrupted .trace-step-icon {
  color: var(--color-text-muted);
}

.trace-step-body {
  min-width: 0;
}

.trace-step-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
}

/* The source line is what makes the answer checkable (2026-08-25 lineage addition,
   verified against IsaacThinkingTrace.vue's .isaac-trace-step-source) — only rendered
   for a step that actually read something (see chat.js's toolSource()/addTraceStep()),
   so a write-only step (record_focus, set_study_goals) has no line here. */
.trace-step-source {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--color-isaac);
}

.trace-step-source-icon {
  font-size: 14px;
}

.trace-step-detail {
  font-size: 12px;
  color: var(--color-text-muted);
}

/* A growing multi-line field with a round send/stop icon button embedded over its
   right end (2026-08-28 fix — reverses the 2026-08-24 proto-parity choice of a
   plain single-line field, verified against IsaacTutorChat.css's .isaac-chat-
   composer/.isaac-chat-field/.isaac-chat-send, in favor of growing with a draft's
   content). */
.composer {
  position: relative;
  display: flex;
  align-items: center;
}

.composer-input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  max-height: 160px;
  /* chat.js's autoGrowComposer() sets an explicit inline height on every keystroke —
     this is only the height BEFORE any input, and the floor autoGrowComposer's
     `Math.min` growth never shrinks below. overflow-y starts hidden and is flipped
     to auto by that same function, but ONLY once content has genuinely grown past
     max-height — a plain `overflow-y: auto` here showed a sliver of scrollbar even
     on an empty field (a textarea's scrollHeight commonly rounds a couple pixels
     taller than its clientHeight with zero content, which is not real overflow). */
  resize: none;
  overflow-y: hidden;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4;
  /* Right inset clears the 32px round button plus a gap, so a long draft never
     runs under it. */
  padding: 12px 46px 12px 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-panel);
  color: var(--color-text);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--transition), box-shadow var(--transition);
}

.composer-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-tint);
}

/* Anchored to the field's bottom-right corner (not vertically centered — see
   .composer's own comment above) so it stays put as the field grows with content,
   rather than drifting toward the vertical middle of an increasingly tall field. */
.composer-send {
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background var(--transition), color var(--transition), right var(--transition),
    border-color var(--transition);
}

/* chat.js's autoGrowComposer() adds this class only once a draft has genuinely
   grown past the field's max-height and its scrollbar is showing (see that
   function's own comment — it deliberately does NOT show a scrollbar just because
   overflow-y is `auto`). A classic (non-overlay, ~15-17px) scrollbar renders flush
   against the field's own right edge, which otherwise sits directly under the send
   button — shifting both the button and the field's own right-side text clearance
   further left here keeps them clear of it, without permanently reserving that
   space for the common case where no scrollbar is showing at all. */
.composer.is-scrollable .composer-input {
  padding-right: 62px;
}

.composer.is-scrollable .composer-send {
  right: 22px;
}

.composer-send:hover:not(:disabled) {
  background: var(--color-subtle);
  color: var(--color-primary);
  border-color: var(--color-primary);
}

.composer-send:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Send button swaps to Stop while a turn streams (chat.js's setBusy()) — the composer
   input itself stays enabled/typable throughout, only this action gates. Filled/
   primary rather than tertiary while stopping, matching IsaacTutorChat.css's
   .isaac-chat-send-stop (a danger fill there; primary here since this token bridge
   has no danger-button precedent to reuse for a fill this prominent). */
.composer-send.is-stop {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}

.composer-send.is-stop:hover:not(:disabled) {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

.composer-disclaimer {
  margin: 0;
  padding-top: 8px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--color-text-muted);
  text-align: center;
}

/* Second fine-print line under the composer (playtest-feedback plan U2) — a separate
   .composer-disclaimer node for the copyright/AI-generated-content disclosure, added
   alongside (not merged into) the 2026-08-24 proto-parity disclaimer above. Tighter
   top padding than the base rule so the two lines read as a pair rather than each
   getting the full composer-to-disclaimer gap. */
.composer-copyright {
  padding-top: 2px;
}

/* ── Composer wake animation (2026-08-23 tutor-chat-visual-pass plan, U6) ──
 * A one-time attention pulse on .composer, applied by chat.js's maybeWakeComposer() —
 * fired only on the FIRST-EVER hero render in a browser session (a sessionStorage flag
 * gates re-triggering on later "New chat" clicks, not this class/keyframe). Reuses the
 * same opacity-only-safe posture as reveal-fade-in above (box-shadow here, not layout-
 * affecting transforms), and prefers-reduced-motion drops it entirely, matching every
 * other animation in this file. The class is removed on animationend (chat.js) so a
 * later reparenting of the same composer node never risks a stray replay. */
@keyframes composer-wake {
  0%, 100% {
    box-shadow: var(--shadow-sm);
  }
  50% {
    box-shadow: 0 0 0 6px var(--color-primary-tint), var(--shadow-sm);
  }
}

.composer-wake {
  animation: composer-wake 1400ms ease-in-out;
  border-radius: var(--radius-md);
}

@media (prefers-reduced-motion: reduce) {
  .composer-wake {
    animation: none;
  }
}

/* ── Interrupted-response banner (2026-08-23 tutor-chat-visual-pass plan, U7) ──
 * Replaces the old plain ".note-stopped" ephemeral note after a user-initiated Stop
 * (chat.js's recordInterruptedTurn()/renderInterruptedBanner()) with a recoverable
 * "Edit prompt"/"Carry on" affordance. Both actions are real <button>s (not clickable
 * divs), matching the trace-card-toggle's own accessibility posture above. */
.interrupted-banner {
  align-self: flex-start;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  max-width: 88%;
  background: var(--color-subtle);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 8px 12px;
  font-size: 13px;
}

.interrupted-banner-text {
  font-style: italic;
  color: var(--color-text-muted);
}

.interrupted-banner-actions {
  display: flex;
  gap: 8px;
}

.interrupted-banner-btn {
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 5px 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  color: var(--color-text);
  transition: border-color var(--transition), background var(--transition);
}

.interrupted-banner-btn:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

.interrupted-carry-btn {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* ── Thumbs feedback row (chat.js, below a completed assistant turn) ──────── */

.feedback-row {
  align-self: flex-start;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: -6px 0 0 2px;
  font-size: 12px;
  color: var(--color-text-muted);
}

.feedback-label {
  letter-spacing: 0.01em;
}

.feedback-btn {
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 3px 10px;
  font-size: 13px;
  line-height: 1.4;
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}

.feedback-btn:hover:not(:disabled) {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

.feedback-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Thumbs (2026-08-25 proto-parity fix, verified against IsaacTutorChat.css's
   .isaac-chat-rate .icon-button — 28px, transparent, brand fill only once rated) —
   .feedback-send below keeps the old rectangular text-button look; it has no proto
   equivalent to match (the prototype's category/comment step is a modal, not inline). */
.feedback-up,
.feedback-down {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  color: var(--color-text-muted);
}

.feedback-up .msr-icon,
.feedback-down .msr-icon {
  font-size: 18px;
}

.feedback-up:hover:not(:disabled),
.feedback-down:hover:not(:disabled) {
  background: var(--bb-color-bg-surface-hover);
  border-color: transparent;
}

.feedback-up.is-active,
.feedback-down.is-active {
  background: var(--bb-color-bg-surface-hover);
  color: var(--color-primary);
}

.feedback-up.is-active .msr-icon,
.feedback-down.is-active .msr-icon {
  font-variation-settings: "FILL" 1;
}

.feedback-send {
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 3px 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-primary);
  cursor: pointer;
}

.feedback-send:hover:not(:disabled) {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

.feedback-send:disabled {
  opacity: 0.5;
  cursor: default;
}

.feedback-comment-box {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  width: 100%;
  margin-top: 4px;
}

/* Category selector (2026-08-23 tutor-chat-visual-pass plan, U8) — required for a
 * thumbs-down submission (Send stays disabled until a value is chosen), unlike the
 * always-optional free-text comment below it. */
.feedback-issue-field {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.feedback-issue-label {
  letter-spacing: 0.01em;
}

.feedback-issue-select {
  font-family: inherit;
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-panel);
  color: var(--color-text);
}

.feedback-issue-select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-tint);
}

.feedback-comment-inputs {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}

.feedback-comment {
  flex: 1;
  min-width: 0;
  font-family: inherit;
  font-size: 12px;
  padding: 6px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-panel);
  color: var(--color-text);
}

.feedback-comment:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-tint);
}

/* Describes the conversation LINK (U1's conversation_id, by reference) — deliberately
 * NOT the prototype's "sends the entire conversation" framing (see the plan's Key
 * Technical Decisions). */
.feedback-caption {
  font-size: 11px;
  color: var(--color-text-muted);
  margin: 0;
}

.feedback-done {
  font-size: 12px;
  color: var(--color-text-muted);
}

.feedback-error {
  color: var(--color-danger);
}

/* ── Experiments readout (experiments.html / experiments.js) ─────────────── */

.exp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

.exp-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.exp-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.exp-card-variant {
  font-family: var(--font-heading);
  font-size: 18px;
  font-weight: 600;
  color: var(--color-primary);
}

.exp-card-desc {
  margin: 0 0 4px;
  font-size: 13px;
  color: var(--color-text-muted);
  line-height: 1.45;
}

.exp-stat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  padding: 4px 0;
  border-top: 1px solid var(--color-border-light);
}

.exp-stat-row:first-of-type {
  border-top: none;
}

.exp-stat-label {
  color: var(--color-text-muted);
}

.exp-stat-value {
  font-weight: 600;
}

.exp-stat-highlight {
  margin-top: 4px;
  border-top: 1px solid var(--color-border);
  padding-top: 10px;
}

.exp-stat-highlight .exp-stat-label {
  font-weight: 600;
  color: var(--color-text);
}

.exp-stat-highlight .exp-stat-value {
  font-family: var(--font-heading);
  font-size: 20px;
  color: var(--color-primary);
}

.exp-score {
  margin-top: 4px;
  border-top: 1px solid var(--color-border);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.exp-score-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
}

.exp-score-gain {
  font-family: var(--font-heading);
  font-size: 26px;
  font-weight: 700;
  color: var(--color-primary);
}

.exp-score-latest {
  font-size: 12px;
  color: var(--color-text-muted);
}

.exp-score-n {
  font-size: 12px;
  color: var(--color-text-muted);
}

.exp-score-caveat {
  font-size: 12px;
  font-style: italic;
  color: var(--color-text-muted);
}

.exp-empty {
  color: var(--color-text-muted);
  font-size: 13px;
}

.exp-note {
  margin-top: 16px;
  font-size: 12px;
  color: var(--color-text-muted);
}

.exp-prompt-link {
  align-self: flex-start;
  margin-top: 4px;
  background: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 3px 10px;
  font-size: 12px;
  color: var(--color-primary);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}

.exp-prompt-link:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

.exp-prompt-panel {
  margin-top: 6px;
  border-top: 1px solid var(--color-border-light);
  padding-top: 10px;
}

.exp-prompt-note {
  margin: 0 0 8px;
  font-size: 12px;
  color: var(--color-text-muted);
  line-height: 1.45;
}

.exp-prompt-text {
  margin: 0;
  max-height: 320px;
  overflow: auto;
  padding: 10px 12px;
  background: var(--color-subtle);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-sm);
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.exp-prompt-error {
  font-size: 12px;
  color: var(--color-danger);
}

/* ── MFE SPA shell (plan U5) ───────────────────────────────────────────────────
   The dashboard + tutor views now run as ONE mounted app (shared by the standalone
   bundle and the embedded federated remote) instead of separate HTML pages. The header
   and view render inside `.isaac-app`; the ported views reuse the existing #dashboard-root
   / #chat-root / #banner / #app-* hooks, so the bulk of the styling above applies
   unchanged. These rules only add what the old MULTI-PAGE layout got from the document
   itself: the tutor route's full-height, non-scrolling frame (previously `body.chat-body`).
   Full embedded style isolation against the host's PrimeVue/Bootstrap is plan U7. */
.isaac-app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
/* NOTE: .isaac-view-root is a GRANDCHILD of .isaac-app (root > .isaac-shell-body >
   .isaac-view-root — see shell.js's mountShell()), not a direct child, so every rule
   below uses a descendant combinator, not `>`. The `>` version here previously never
   matched anything, silently no-opping the whole "let the transcript scroll" contract:
   .isaac-view-root stayed a plain block (no flex context for #chat-root's flex:1/
   min-height:0 to engage), so on the tutor route the .isaac-app's overflow:hidden just
   clipped content at the viewport edge instead of confining the scroll to .transcript. */
.isaac-app .isaac-view-root {
  display: block;
}
/* Tutor route: fill the viewport and let the transcript (not the page) scroll. */
.isaac-app.route-tutor {
  height: 100vh;
  min-height: 0;
  overflow: hidden;
}
.isaac-app.route-tutor .isaac-shell-body {
  height: 100%;
  min-height: 0;
}
.isaac-app.route-tutor .isaac-view-root {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Analytics/dashboard route: same "fill the box, one region scrolls" contract as the
   tutor route above (2026-08-27 fix) — this app is embedded into barbri-lms's
   IsaacTutorView.vue, whose `.isaac-tutor-view__container` pins OUR root to
   `position: absolute; inset: 0; height: 100%` inside its own `overflow: hidden` box
   (see that file's comment: it's built for exactly the tutor route's fixed-viewport-
   with-internal-scroll model). Without a matching rule here, the dashboard route —
   which otherwise just grows naturally and lets the PAGE scroll — gets silently
   clipped at the host's slot edge with no scrollbar at all once its content overflows
   that slot; `.isaac-view-root` becomes the scrollable region instead, exactly like
   `.transcript` is for the tutor route (both routes' rules are otherwise identical:
   only the flex-direction/inner-scroll target differs, since #dashboard-root has no
   dedicated scrolling child of its own the way #chat-root's .transcript is). */
.isaac-app.route-dashboard {
  height: 100vh;
  min-height: 0;
  overflow: hidden;
}
.isaac-app.route-dashboard .isaac-shell-body {
  height: 100%;
  min-height: 0;
}
.isaac-app.route-dashboard .isaac-view-root {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Rail width (2026-08-24 fix): the same width on every route — was previously
   350px only on the tutor route (350px there gave the conversation list room for
   readable titles) vs. 250px on Analytics, which made the rail visibly resize on
   every nav switch. `.isaac-rail`'s base width above is now 350px everywhere so
   the rail no longer shifts width when switching between Analytics and Tutor. */
