/* ==========================================================================
   VR Classroom LLM Tool — design system. Two themes, one token vocabulary.

     LIGHT  = Melbourne Polytechnic brand. White ground, deep-indigo ink
              (#200b4e), violet accent for selection/focus, and the vivid
              brand orange (#ff4e00) held back for ONE primary action per
              view. Semantic colours borrow the icon-set hues (lime, teal,
              coral/rose, yellow) darkened until they pass AA on white.
     DARK   = the original periwinkle-indigo theme, value for value.

   Token NAMES are frozen by API-CONTRACT.md §8; values live here. The LIGHT
   values sit on bare :root so no colour is ever defined only inside a media
   query; the DARK values are re-declared under BOTH :root[data-theme="dark"]
   and the prefers-color-scheme block, so the OS preference is honoured and an
   explicit choice always wins in either direction.

   4/8px rhythm · system font stacks · WCAG AA on every text/surface pair in
   BOTH themes (checked numerically, worst pair ≈ 4.6:1).

   Type scale — five steps, no half pixels. Hierarchy comes from weight and
   colour, not from 0.5px size differences:
     10px  uppercase micro-labels, chips' siblings   (600, tracked)
     11px  mono meta (counters, wire, char counts)
     12px  secondary copy, dense controls
     13px  body / editor / bubbles / inputs
     15px  dialog + card titles
   Dashed borders mean exactly one thing: "nothing here yet / provisional"
   (empty states, the add button, a defaulted verdict). Everything else solid.
   ========================================================================== */

/* -------------------------------------------- LIGHT — Melbourne Polytechnic */

:root {
  color-scheme: light;

  /* elevation ramp: canvas → sheet → raised control → floating.
     In light the ramp runs the other way round: the page is a faint tint and
     surfaces rise towards white, while CONTROLS sit a step below both so a
     button still reads as a button on either ground. */
  --bg-0: #f6f5f9;
  --bg-1: #ffffff;
  --bg-2: #eae8ef;
  --panel: #ffffff;

  --border: #d9d4e6;
  --border-strong: #948aae;

  --text-1: #00072e;           /* the brand ink — sampled from logo.png */
  --text-2: #4e4463;
  --text-3: #6a6180;

  /* accent = selection / focus / "this one is active". Brand violet, NOT the
     orange — orange is the call to action and nothing else. */
  --accent: #3a1d7a;
  --accent-hover: #4b2a95;
  --accent-soft: rgba(58, 29, 122, 0.08);

  --success: #3a6e12;          /* lime, darkened */
  --success-soft: rgba(124, 191, 62, 0.18);
  --failure: #b32d3a;          /* coral/rose, darkened */
  --failure-soft: rgba(217, 105, 122, 0.16);
  --progress: #15707f;         /* teal, darkened */
  --progress-soft: rgba(124, 197, 214, 0.24);

  --warning: #7e5600;          /* brand yellow, darkened to a text weight */

  /* Indicator colours. Deliberately NOT the text-weight tokens above: a 9px
     dot or an 11px star needs SATURATION, not darkness — darkening a hue for
     text contrast is what turned these grey. Both clear the 3:1 non-text
     contrast bar (WCAG 1.4.11) on --bg-1: orange 3.31:1, green 3.51:1. */
  --star: #ff4e00;             /* brand orange — marks the default version */
  --status-ok: #0f9d58;        /* "API connected" — a true green, not olive */
  --danger: #b02532;

  --shadow-1: 0 1px 2px rgba(32, 11, 78, 0.06), 0 2px 8px rgba(32, 11, 78, 0.07);
  --shadow-2: 0 6px 16px rgba(32, 11, 78, 0.1), 0 20px 44px rgba(32, 11, 78, 0.13);

  /* private helpers (not part of the frozen token list) */
  --accent-text: #33176e;      /* accent-family text on soft/tinted surfaces */
  --accent-ink: #ffffff;       /* text on solid accent */
  --accent-edge: rgba(58, 29, 122, 0.38);
  --warning-soft: rgba(242, 206, 91, 0.26);
  --danger-hover: #8f1c28;
  --bg-0-rgb: 246, 245, 249;   /* --bg-0 as channels, for fade gradients */
  --scroll-thumb: #c6c0d6;
  --scroll-thumb-hover: #a49abd;

  /* the call to action — brand orange, deep-indigo label (5.2:1). One per
     view: Send, Save, Sign in. */
  --cta: #ff4e00;
  --cta-hover: #ff6524;
  --cta-ink: #ffffff;

  --btn-hover: #e1dded;
  --bubble-user: #e3d9f8;      /* the teacher's own turn, brand violet */
  --selection-bg: rgba(255, 78, 0, 0.22);
  --selection-fg: #200b4e;
  --scrim: rgba(32, 11, 78, 0.34);
  --scrim-strong: rgba(32, 11, 78, 0.42);
  --shimmer: rgba(255, 255, 255, 0.9);
  --spinner-track: rgba(32, 11, 78, 0.28);
  --wire-shade-1: rgba(32, 11, 78, 0.1);
  --wire-shade-2: rgba(32, 11, 78, 0.16);
  --glyph-spark: #ff4e00;      /* the mark's spark keeps the brand orange */
  --login-glow: rgba(58, 29, 122, 0.08);
  --login-glow-2: rgba(255, 78, 0, 0.07);

  /* theme-independent */
  --radius-s: 6px;
  --radius-m: 9px;
  --radius-l: 14px;

  --font-ui: "Segoe UI Variable Text", "Segoe UI Variable", "Segoe UI",
    system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "Cascadia Code", Consolas,
    "Courier New", monospace;

  --wire-line: 18px;           /* wire/pre line box — clips land on it */
  --speed: 170ms;
  --speed-slow: 240ms;
}

/* ---------------------------------------------- DARK — the original theme */

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg-0: #0b0e14;
  --bg-1: #101520;
  --bg-2: #151b28;
  --panel: #182030;

  --border: #232c3d;
  --border-strong: #32405a;

  --text-1: #e9edf5;
  --text-2: #aeb9cc;
  --text-3: #7d8aa3;

  /* Indicators: on a dark ground these can be light and vivid. */
  --star: #f0b429;
  --status-ok: #5fd49b;

  --accent: #8fa4ff;
  --accent-hover: #a6b6ff;
  --accent-soft: rgba(143, 164, 255, 0.13);

  --success: #5fd49b;
  --success-soft: rgba(95, 212, 155, 0.11);
  --failure: #ffa09b;
  --failure-soft: rgba(255, 160, 155, 0.11);
  --progress: #6ec4e8;
  --progress-soft: rgba(110, 196, 232, 0.11);

  --warning: #e5c07b;
  --danger: #c33d38;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.24);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.45), 0 14px 36px rgba(0, 0, 0, 0.34);

  --accent-text: #c9d3ff;
  --accent-ink: #0d1120;
  --accent-edge: rgba(143, 164, 255, 0.45);
  --warning-soft: rgba(229, 192, 123, 0.1);
  --danger-hover: #d24a44;
  --bg-0-rgb: 11, 14, 20;
  --scroll-thumb: #3c4a68;
  --scroll-thumb-hover: #55658c;

  /* dark has no separate brand CTA — the accent IS the primary action */
  --cta: var(--accent);
  --cta-hover: var(--accent-hover);
  --cta-ink: var(--accent-ink);

  --btn-hover: #1b2334;
  --bubble-user: #1d2742;
  --selection-bg: rgba(143, 164, 255, 0.32);
  --selection-fg: #eef1ff;
  --scrim: rgba(5, 7, 12, 0.62);
  --scrim-strong: rgba(5, 7, 12, 0.7);
  --shimmer: rgba(255, 255, 255, 0.05);
  --spinner-track: rgba(13, 17, 32, 0.3);
  --wire-shade-1: rgba(0, 0, 0, 0.55);
  --wire-shade-2: rgba(0, 0, 0, 0.8);
  --glyph-spark: var(--warning);
  --login-glow: rgba(143, 164, 255, 0.09);
  --login-glow-2: transparent;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg-0: #0b0e14;
    --bg-1: #101520;
    --bg-2: #151b28;
    --panel: #182030;

    --border: #232c3d;
    --border-strong: #32405a;

    --text-1: #e9edf5;
    --text-2: #aeb9cc;
    --text-3: #7d8aa3;

  /* Indicators: on a dark ground these can be light and vivid. */
  --star: #f0b429;
  --status-ok: #5fd49b;

    --accent: #8fa4ff;
    --accent-hover: #a6b6ff;
    --accent-soft: rgba(143, 164, 255, 0.13);

    --success: #5fd49b;
    --success-soft: rgba(95, 212, 155, 0.11);
    --failure: #ffa09b;
    --failure-soft: rgba(255, 160, 155, 0.11);
    --progress: #6ec4e8;
    --progress-soft: rgba(110, 196, 232, 0.11);

    --warning: #e5c07b;
    --danger: #c33d38;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.24);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.45), 0 14px 36px rgba(0, 0, 0, 0.34);

    --accent-text: #c9d3ff;
    --accent-ink: #0d1120;
    --accent-edge: rgba(143, 164, 255, 0.45);
    --warning-soft: rgba(229, 192, 123, 0.1);
    --danger-hover: #d24a44;
    --bg-0-rgb: 11, 14, 20;
    --scroll-thumb: #3c4a68;
    --scroll-thumb-hover: #55658c;

    --cta: var(--accent);
    --cta-hover: var(--accent-hover);
    --cta-ink: var(--accent-ink);

    --btn-hover: #1b2334;
    --bubble-user: #1d2742;
    --selection-bg: rgba(143, 164, 255, 0.32);
    --selection-fg: #eef1ff;
    --scrim: rgba(5, 7, 12, 0.62);
    --scrim-strong: rgba(5, 7, 12, 0.7);
    --shimmer: rgba(255, 255, 255, 0.05);
    --spinner-track: rgba(13, 17, 32, 0.3);
    --wire-shade-1: rgba(0, 0, 0, 0.55);
    --wire-shade-2: rgba(0, 0, 0, 0.8);
    --glyph-spark: var(--warning);
    --login-glow: rgba(143, 164, 255, 0.09);
    --login-glow-2: transparent;
  }
}

/* ------------------------------------------------------------------ base */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* color-scheme is set per theme with the tokens above */
  height: 100%;
}

body {
  margin: 0;
  height: 100%;
  overflow: hidden; /* the page never scrolls — panels scroll internally */
  background: var(--bg-0);
  color: var(--text-1);
  font: 400 13px/1.5 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p {
  margin: 0;
}

::selection {
  background: var(--selection-bg);
  color: var(--selection-fg);
}

::placeholder {
  color: var(--text-3); /* 4.5:1+ on every input surface */
  opacity: 1;
}

code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  color: var(--text-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 4px;
  white-space: nowrap; /* a chip never breaks across two lines */
}

/* focus — a single confident ring everywhere */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.input:focus-visible,
.textarea:focus-visible {
  outline: none;
  border-color: var(--accent-edge);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* custom scrollbars */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-thumb) transparent;
}
*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
*::-webkit-scrollbar-track {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: 8px;
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--scroll-thumb-hover);
  border-width: 2px;
  background-clip: padding-box;
}
*::-webkit-scrollbar-corner {
  background: transparent;
}

/* Overflow that reads as overflow. The tail of a scrollable pane fades out
   and the fade lifts as you reach the end — driven by the pane's OWN scroll
   timeline, so a pane with nothing to scroll is never faded (an inactive
   timeline animates nothing and --scroll-fade stays at its initial 0). */
@property --scroll-fade {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
@keyframes scroll-fade-out {
  0%,
  85% {
    --scroll-fade: 1;
  }
  100% {
    --scroll-fade: 0;
  }
}
.wire-pre,
.accordion-body,
.workspace-foot {
  mask-image: linear-gradient(
    to bottom,
    #000 calc(100% - 22px * var(--scroll-fade)),
    rgba(0, 0, 0, 0.12) calc(100% - 2px * var(--scroll-fade))
  );
  animation: scroll-fade-out linear both;
  animation-timeline: scroll(self block);
}

/* panes whose content is meant to be scrolled get a visible track too */
.wire-pre,
.accordion-body {
  scrollbar-color: var(--scroll-thumb-hover) var(--bg-2);
}
.wire-pre::-webkit-scrollbar-track,
.accordion-body::-webkit-scrollbar-track {
  background: var(--bg-2);
  border-radius: 8px;
}
.wire-pre::-webkit-scrollbar-thumb,
.accordion-body::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb-hover);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------- controls */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: 500 12px/1 var(--font-ui);
  color: var(--text-1);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 7px 12px;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition:
    background var(--speed) ease,
    border-color var(--speed) ease,
    color var(--speed) ease,
    box-shadow var(--speed) ease,
    transform var(--speed) ease;
}
.btn:hover:not(:disabled) {
  background: var(--btn-hover);
  border-color: var(--border-strong);
}
.btn:active:not(:disabled) {
  transform: translateY(1px);
}

/* the one loud control per view. In dark that is the accent; in light it is
   the Melbourne Polytechnic orange with the brand ink as its label. */
.btn--primary {
  background: var(--cta);
  border-color: transparent;
  color: var(--cta-ink);
  font-weight: 600;
}
.btn--primary:hover:not(:disabled) {
  background: var(--cta-hover);
  border-color: transparent;
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-2);
}
.btn--ghost:hover:not(:disabled) {
  background: var(--bg-2);
  border-color: var(--border);
  color: var(--text-1);
}

.btn--danger {
  background: var(--danger);
  border-color: transparent;
  color: #fff;
  font-weight: 600;
}
.btn--danger:hover:not(:disabled) {
  background: var(--danger-hover);
  border-color: transparent;
}

.btn--icon {
  padding: 0;
  width: 28px;
  height: 28px;
  flex: none;
}
.btn--icon svg {
  width: 15px;
  height: 15px;
  display: block;
}
/* all three workspace icons rest in the same quiet grey and only say what
   they mean on hover — including the destructive one */
.btn--icon.btn--ghost,
.btn--icon.btn--danger {
  color: var(--text-3);
  background: transparent;
  border-color: transparent;
}
.btn--icon.btn--ghost:hover:not(:disabled) {
  color: var(--text-1);
  background: var(--bg-2);
  border-color: var(--border);
}
.btn--icon.btn--danger:hover:not(:disabled) {
  background: var(--danger);
  border-color: transparent;
  color: #fff;
}
/* the star is drawn filled (fill="currentColor") only when this version IS
   the default. At that point the button is also disabled — it has stopped
   being an action and become a status marker, so it must NOT inherit the
   muted disabled colour. --star (not the text-weight --warning) keeps it
   vivid; opacity is pinned so the disabled rule below cannot fade it. */
.btn--icon:has(svg[fill="currentColor"]),
.btn--icon:has(svg[fill="currentColor"]):disabled {
  color: var(--star);
  opacity: 1;
}

/* disabled — explicit, never a translucent ghost. A dead control keeps a
   readable label and a visible body, and any accent fill is dropped so it
   can't be the loudest thing in the row. */
.btn:disabled {
  cursor: not-allowed;
  color: var(--text-3);
  background: var(--bg-1);
  border-color: var(--border);
  box-shadow: none;
}
.btn--primary:disabled,
.btn--danger:disabled {
  background: var(--bg-1);
  border-color: var(--border);
  color: var(--text-3);
  font-weight: 500;
}
.btn--ghost:disabled,
.btn--icon:disabled {
  background: transparent;
  border-color: transparent;
  color: var(--text-3);
}

.input,
.textarea {
  font: 400 13px/1.45 var(--font-ui);
  color: var(--text-1);
  background: var(--bg-0);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 7px 10px;
  width: 100%;
  min-width: 0;
  transition: border-color var(--speed) ease, box-shadow var(--speed) ease,
    color var(--speed) ease, background var(--speed) ease;
}
.textarea {
  resize: vertical;
  font-family: inherit;
}
.input:disabled,
.textarea:disabled {
  cursor: not-allowed;
  color: var(--text-3);
  background: var(--bg-1);
  border-color: var(--border);
}
input[type="checkbox"] {
  accent-color: var(--accent);
}
input[type="number"] {
  font-variant-numeric: tabular-nums;
}

/* skeleton loading state */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--bg-2);
  border-radius: var(--radius-s);
  min-height: 12px;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    100deg,
    transparent 30%,
    var(--shimmer) 50%,
    transparent 70%
  );
  animation: shimmer 1.6s infinite;
}
@keyframes shimmer {
  to {
    transform: translateX(100%);
  }
}

.boot-pad {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  min-height: 0;
}

/* ------------------------------------------------------------------ shell */

.app {
  height: 100dvh;
  display: grid;
  grid-template-rows: 44px minmax(0, 1fr);
}

.columns {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr) 420px;
  min-height: 0;
}
@media (max-width: 1439px) {
  .columns {
    grid-template-columns: 240px minmax(0, 1fr) 384px;
  }
}
/* Surplus width past ~1600 goes to the transcript, not into ever-longer
   prompt lines — the editor stops at --measure (see .workspace). */
@media (min-width: 1600px) {
  .columns {
    grid-template-columns: 260px minmax(0, 1fr) 480px;
  }
}
@media (min-width: 1840px) {
  .columns {
    grid-template-columns: 260px minmax(0, 1fr) 540px;
  }
}
.columns > * {
  min-width: 0;
  min-height: 0;
}

/* ----------------------------------------------------------------- topbar */

.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 10px 0 14px;
  background: var(--bg-1);
  border-bottom: 1px solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.brand .glyph {
  width: 22px;
  height: 22px;
  flex: none;
}
/* the mark's spark keeps the brand orange in light, the warm gold in dark —
   the rest of it rides on --accent, so it follows the theme for free */
.brand .glyph,
.login-brand .glyph {
  --warning: var(--glyph-spark);
}

/* Lockup: "VR Classroom" is the wordmark, "LLM Tool" the qualifier. A hairline
   rule between them makes the second half read as deliberate, not clipped. */
/* ---- Melbourne Polytechnic lockup -------------------------------------- */
/* Their official artwork, two supplied variants: navy on light grounds,
   white on dark. Swapped in CSS so it follows BOTH the explicit data-theme
   attribute and the prefers-color-scheme fallback. The logo is never
   recoloured — brand marks are used as issued. */
.brand-logo {
  display: block;
  flex: none;
  line-height: 0;
}
/* Both variants start hidden and exactly one is switched on. Hiding by
   default matters: a bare `.brand-logo-dark { display:none }` would lose to
   `.brand-logo img` on specificity (0,1,0 vs 0,1,1) and BOTH would render
   stacked. Every show/hide rule below is class-on-class so they compare
   like for like. */
.brand-logo img {
  display: none;
  height: 24px;
  width: auto;
}
.login-brand .brand-logo img {
  height: 34px;
}
.brand-logo .brand-logo-light { display: block; }
:root[data-theme="dark"] .brand-logo .brand-logo-light { display: none; }
:root[data-theme="dark"] .brand-logo .brand-logo-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo .brand-logo-light { display: none; }
  :root:not([data-theme="light"]) .brand-logo .brand-logo-dark { display: block; }
}
/* the wordmark sits beside their logo, so it must not compete with it */
.brand-name-main {
  font-weight: 600;
}

.brand-name {
  display: flex;
  align-items: baseline;
  gap: 7px;
  min-width: 0;
}
.brand-name-main {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-1);
  white-space: nowrap;
}
.brand-name-sub {
  position: relative;
  padding-left: 8px;
  font: 600 10px/1 var(--font-ui);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
.brand-name-sub::before {
  content: "";
  position: absolute;
  left: 0;
  top: -1px;
  height: 11px;
  border-left: 1px solid var(--border-strong);
}
@media (max-width: 1099px) {
  .brand-name-sub {
    display: none; /* the wordmark alone still names the app */
  }
}

.topbar-status {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.model-readout {
  font: 500 12px var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

.badge--mock {
  font: 700 10px/1 var(--font-ui);
  letter-spacing: 0.09em;
  color: var(--warning);
  background: var(--warning-soft);
  border: 1px solid color-mix(in srgb, var(--warning) 35%, transparent);
  border-radius: 999px;
  padding: 4px 8px;
}

.llm-dot {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background var(--speed) ease;
}
.llm-dot:hover {
  background: var(--bg-2);
}
.llm-dot::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--text-3);
  transition: background var(--speed) ease, box-shadow var(--speed) ease;
}
.llm-dot--ok::before {
  background: var(--status-ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-ok) 22%, transparent);
}
.llm-dot--bad::before {
  background: var(--failure);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--failure) 18%, transparent);
}
.llm-dot--unknown::before {
  background: transparent;
  border: 1.5px solid var(--text-3);
}
.llm-dot.is-checking::before {
  animation: dot-pulse 0.9s ease-in-out infinite;
}
@keyframes dot-pulse {
  50% {
    opacity: 0.35;
  }
}

/* ----------------------------------------------------------- theme toggle */

/* Same 28px icon button as the workspace row — quiet by default, only says
   what it means on hover. The icon shows the theme you are switching TO. */
.theme-toggle svg {
  width: 16px;
  height: 16px;
}
/* on the sign-in page there is no topbar to live in */
.theme-toggle--floating {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 10;
}

/* ----------------------------------------------------------- scenario rail */

.rail {
  display: flex;
  flex-direction: column;
  background: var(--bg-1);
  border-right: 1px solid var(--border);
}

/* header, cards and the add button all share one 12px left edge */
.rail-head {
  font: 600 10px var(--font-ui);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 16px 12px 8px;
  flex: none;
}

.rail-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 2px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rail-empty {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-m);
  color: var(--text-2);
  font-size: 12px;
  line-height: 1.5;
  text-align: center;
  padding: 16px 12px;
}

.rail-add {
  margin: 0 12px 12px;
  flex: none;
  border: 1px dashed var(--border-strong);
  background: transparent;
  color: var(--text-2);
}
.rail-add:hover:not(:disabled) {
  border-color: var(--accent-edge);
  border-style: dashed;
  color: var(--accent-text);
  background: var(--accent-soft);
}

.scenario-card {
  position: relative;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  transition: border-color var(--speed) ease, background var(--speed) ease;
}
.scenario-card:hover {
  border-color: var(--border-strong);
}
.scenario-card.is-active {
  border-color: var(--accent-edge);
  background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--accent) 9%, transparent),
      color-mix(in srgb, var(--accent) 3%, transparent)
    ),
    var(--bg-2);
}
.scenario-card.is-active::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}

.scenario-card-main {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  padding: 10px 12px;
  cursor: pointer;
  border-radius: inherit;
}
.scenario-card-main:focus-visible {
  outline-offset: -2px;
}

.scenario-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-right: 28px; /* room for the edit button */
}
.scenario-num {
  font: 600 10px var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  letter-spacing: 0.06em;
  flex: none;
}
.scenario-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-1);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.scenario-desc {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; /* clamped to one line by design */
}
.scenario-desc.is-placeholder {
  color: var(--text-3);
  font-style: italic;
}
.scenario-limits {
  margin-top: 4px;
  font: 500 10px var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}

.scenario-edit {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 24px;
  height: 24px;
  color: var(--text-3);
  opacity: 0;
  transition: opacity var(--speed) ease, background var(--speed) ease,
    color var(--speed) ease, border-color var(--speed) ease;
}
.scenario-card:hover .scenario-edit,
.scenario-card.is-active .scenario-edit,
.scenario-card.is-editing .scenario-edit,
.scenario-edit:focus-visible {
  opacity: 1;
}
.scenario-edit:hover:not(:disabled) {
  color: var(--text-1);
}
.scenario-card.is-editing .scenario-edit {
  color: var(--accent-text);
  background: var(--accent-soft);
  border-color: transparent;
}

/* inline meta editor */
.scenario-form {
  border-top: 1px solid var(--border);
  padding: 10px 12px 12px;
  display: grid;
  gap: 8px;
}
.form-row2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.field-label {
  font: 600 10px var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.field-hint {
  font-size: 10px;
  color: var(--text-3);
}
.scenario-form .input,
.scenario-form .textarea {
  font-size: 12px;
  padding: 6px 8px;
}
.scenario-form .textarea {
  resize: vertical;
  min-height: 52px;
}
.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 2px;
}

/* -------------------------------------------------------------- workspace */

.workspace {
  display: flex;
  flex-direction: column;
  background: var(--bg-0);
}

/* One measure for the whole column — strip, actions, editor and accordions
   line up, and past 1600px they centre instead of stretching to 150+ chars. */
.workspace > * {
  width: 100%;
  max-width: var(--measure, none);
  margin-inline: auto;
}
@media (min-width: 1600px) {
  .workspace {
    --measure: 900px; /* ≈ 108ch of 13px mono plus the panel padding */
  }
}

.workspace-head {
  flex: none;
  padding: 10px 14px 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-0);
}

.version-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.version-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 500 12px/1 var(--font-ui);
  color: var(--text-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
  max-width: 220px;
  transition: color var(--speed) ease, border-color var(--speed) ease,
    background var(--speed) ease;
}
.version-chip:hover {
  color: var(--text-1);
  border-color: var(--border-strong);
}
.version-chip.is-active {
  color: var(--accent-text);
  background: var(--accent-soft);
  border-color: var(--accent-edge);
  font-weight: 600;
}
.version-chip-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.version-star {
  color: var(--star);
  font-size: 11px;
  line-height: 1;
  flex: none;
}
.dirty-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  animation: dot-pulse 1.6s ease-in-out infinite;
}
.version-empty {
  font-size: 12px;
  color: var(--text-3);
  font-style: italic;
}

.version-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.actions-sep {
  width: 1px;
  height: 16px;
  background: var(--border);
  margin: 0 3px;
  flex: none;
}
.actions-spring {
  flex: 1;
}

.editor-meta {
  flex: none;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 14px 6px;
}
.editor-label {
  font: 600 10px var(--font-ui);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}
.editor-count {
  font: 500 11px var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}

.editor-wrap {
  /* grows into the free space but never gives all of it back to an open
     accordion — the editor is the work surface */
  flex: 1 1 200px;
  min-height: 160px;
  display: flex;
  padding: 0 14px;
}
.editor-input {
  flex: 1;
  height: 100%;
  resize: none;
  background: var(--bg-1);
  border-radius: var(--radius-m);
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.6;
  padding: 12px 14px;
  tab-size: 2;
}

.workspace-foot {
  /* may shrink (and scroll) when both accordions are open on a short screen */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 14px 12px;
}

.accordion {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  flex: none;
}
.accordion-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font: 600 12px var(--font-ui);
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
  list-style: none;
  border-radius: inherit;
  transition: color var(--speed) ease;
}
.accordion-head::-webkit-details-marker {
  display: none;
}
.accordion-head:hover {
  color: var(--text-1);
}
.accordion-chevron {
  width: 10px;
  height: 10px;
  flex: none;
  color: var(--text-3);
  transition: transform var(--speed) ease;
}
.accordion[open] > .accordion-head {
  color: var(--text-1);
}
.accordion[open] > .accordion-head .accordion-chevron {
  transform: rotate(90deg);
}
.accordion-tag {
  margin-left: auto;
  font: 500 10px var(--font-mono);
  color: var(--text-3);
  letter-spacing: 0.04em;
}
.accordion-body {
  border-top: 1px solid var(--border);
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* deep enough for a note + two ten-line panes on a 1080 screen; on an 800
     screen it caps and scrolls instead of pushing the editor off the page */
  max-height: min(46vh, 520px);
  overflow-y: auto;
}
/* children keep their natural height and scroll the BODY — without this the
   <pre>s (overflow:auto) get flex-shrunk to a one-line peephole */
.accordion-body > * {
  flex: 0 0 auto;
}
/* (a scrolled body fades its tail — see --scroll-fade above) */
.accordion-note {
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-2);
}
.accordion-sub {
  font: 600 10px var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-top: 2px;
}

/* --------------------------------------------------------------- chat panel */

.chat-col {
  display: flex;
  background: var(--bg-1);
  border-left: 1px solid var(--border);
}

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

.chat-status {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--border);
}

.chat-status-pill {
  flex: none;
  font: 700 10px/1 var(--font-ui);
  letter-spacing: 0.1em;
  padding: 4px 8px;
  border-radius: 999px;
  color: var(--text-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  transition: color var(--speed) ease, background var(--speed) ease,
    border-color var(--speed) ease;
}
.chat-status-pill--passed {
  color: var(--success);
  background: var(--success-soft);
  border-color: color-mix(in srgb, var(--success) 32%, transparent);
}
.chat-status-pill--failed {
  color: var(--failure);
  background: var(--failure-soft);
  border-color: color-mix(in srgb, var(--failure) 32%, transparent);
}

.chat-counters {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.counter {
  font: 600 11px var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--text-3);
  white-space: nowrap;
  transition: color var(--speed) ease;
}
/* budget pressure: half spent, then spent */
.counter--warn {
  color: var(--warning);
}
.counter--danger {
  color: var(--failure);
  font-weight: 700;
}

.mood-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: color var(--speed) ease;
}
.mood-toggle:hover {
  color: var(--text-1);
}
.mood-toggle input {
  width: 13px;
  height: 13px;
  margin: 0;
  flex: none;
  cursor: pointer;
}

.chat-using {
  flex-basis: 100%;
  min-width: 0;
  font-size: 11px;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 20px; /* turn boundary — must beat the 4px inside a turn */
}
/* a message and its reply are one exchange: pull them together so the 20px
   above reads as the boundary between exchanges */
.turn--user + .turn--student {
  margin-top: -8px;
}
/* the transcript hangs off the composer and grows upward; the auto margin
   collapses to 0 as soon as the turns fill the pane */
.chat-scroll > :first-child {
  margin-top: auto;
}

.chat-empty {
  margin: auto;
  max-width: 320px;
  text-align: left;
  padding: 20px 16px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-l);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.chat-empty p {
  font-size: 12px;
  line-height: 1.7;
  color: var(--text-2);
}
.chat-empty p:first-child {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-1);
  line-height: 1.5;
}
/* trigger tokens read as tokens, not as links */
.chat-empty code {
  color: var(--accent-text);
  background: var(--accent-soft);
  border-color: var(--accent-edge);
}

/* turns */
.turn {
  display: flex;
  flex-direction: column;
  max-width: 88%;
}
.turn--user {
  align-self: flex-end;
  align-items: flex-end;
}
.turn--student {
  align-self: flex-start;
  align-items: flex-start;
}
.turn--system {
  align-self: center;
  align-items: center;
  max-width: 96%;
}

.bubble {
  font-size: 13px;
  line-height: 1.5;
  padding: 8px 12px;
  border-radius: 12px;
  border: 1px solid transparent;
  overflow-wrap: break-word;
  max-width: 100%;
}
.turn--user .bubble {
  background: var(--bubble-user);
  border-color: color-mix(in srgb, var(--accent) 25%, transparent);
  border-bottom-right-radius: 4px;
}
.turn--student .bubble {
  background: var(--bg-2);
  border-color: var(--border);
  border-bottom-left-radius: 4px;
}
.turn--system .bubble {
  background: var(--bg-1);
  border-color: var(--border);
  color: var(--text-2);
  font-size: 12px;
}

.bubble--streaming {
  border-color: var(--accent-edge);
}
.caret {
  display: inline-block;
  width: 6px;
  height: 14px;
  margin-left: 3px;
  vertical-align: -2px;
  border-radius: 1.5px;
  background: var(--accent);
  animation: caret-blink 1s steps(2, start) infinite;
}
@keyframes caret-blink {
  50% {
    opacity: 0;
  }
}

.bubble--failed {
  background: var(--failure-soft);
  border-color: color-mix(in srgb, var(--failure) 35%, transparent);
  color: var(--text-2);
}
.bubble--failed > div:last-child {
  color: var(--failure); /* the reason line chat.js appends */
}

.turn-meta {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
}
.turn--user .turn-meta {
  justify-content: flex-end;
}

/* chips */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 600 11px/1 var(--font-ui);
  letter-spacing: 0.03em;
  padding: 4px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.chip--success {
  color: var(--success);
  background: var(--success-soft);
  border-color: color-mix(in srgb, var(--success) 32%, transparent);
}
.chip--failure {
  color: var(--failure);
  background: var(--failure-soft);
  border-color: color-mix(in srgb, var(--failure) 32%, transparent);
}
.chip--progress {
  color: var(--progress);
  background: var(--progress-soft);
  border-color: color-mix(in srgb, var(--progress) 32%, transparent);
}
.chip--mood {
  color: var(--warning);
  background: var(--warning-soft);
  border-color: color-mix(in srgb, var(--warning) 32%, transparent);
}
.chip--flag {
  color: var(--text-2);
  background: color-mix(in srgb, var(--text-3) 12%, transparent);
  border-color: var(--border-strong);
}
/* the verdict was defaulted, not returned by the model: same family colour,
   no fill, dashed edge — the one dashed meaning, "not really there" */
.chip--forced {
  background: transparent;
  border-style: dashed;
  font-weight: 500;
  font-style: italic;
}

/* wire drawer */
.wire-panel {
  min-width: 0;
  max-width: 100%;
}
/* closed it is just another chip in the row; open it takes the full row */
.wire-panel[open] {
  width: 100%;
}
.wire-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 600 11px/1 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 4px 8px; /* same box as .chip, so the row lines up */
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  cursor: pointer;
  user-select: none;
  list-style: none;
  width: fit-content;
  transition: color var(--speed) ease, border-color var(--speed) ease,
    background var(--speed) ease;
}
.wire-toggle::-webkit-details-marker {
  display: none;
}
.wire-toggle::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
}
.wire-toggle:hover {
  color: var(--text-2);
  background: var(--bg-2);
}
.wire-panel[open] > .wire-toggle {
  color: var(--accent-text);
  border-color: var(--accent-edge);
  background: var(--accent-soft);
}

/* Sections inside the drawer (request body, raw reply, assembled system
   string). chat.js may still inline these — the classes are here so it
   doesn't have to. */
.wire-section {
  margin-top: 8px;
}
.wire-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 4px;
}
.wire-label {
  font-size: 11px;
  color: var(--text-3);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wire-copy {
  font-size: 11px;
  padding: 2px 8px;
  flex: none;
}

.wire-pre {
  margin: 0;
  font: 400 11px/var(--wire-line) var(--font-mono);
  color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 8px 10px;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: auto;
  /* Exactly ten line boxes, so a clip lands between two lines instead of
     slicing one in half. Only the TOP padding counts here: overflowing text
     is visible through a scroll container's bottom padding, so the visible
     strip is padding-top + N lines (+ the two borders). */
  max-height: calc(var(--wire-line) * 10 + 10px);
  resize: vertical; /* drag it open when 10 lines aren't enough */
  user-select: text;
  cursor: text;
  /* scroll shadows: the two `local` layers travel with the content and hide
     the fixed `scroll` layers once you reach that end, so the shading is
     only ever there when there IS more text in that direction */
  background:
    linear-gradient(
        var(--bg-0) 40%,
        rgba(var(--bg-0-rgb), 0)
      )
      center top / 100% 16px local no-repeat,
    linear-gradient(
        rgba(var(--bg-0-rgb), 0),
        var(--bg-0) 70%
      )
      center bottom / 100% 22px local no-repeat,
    linear-gradient(
        to bottom,
        var(--wire-shade-1),
        transparent
      )
      center top / 100% 14px scroll no-repeat,
    linear-gradient(
        to top,
        var(--wire-shade-2),
        transparent
      )
      center bottom / 100% 24px scroll no-repeat,
    var(--bg-0);
  /* (the text itself fades into that band — see --scroll-fade above) */
}

/* banner */
.chat-banner {
  flex: none;
  padding: 10px 14px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-top: 1px solid var(--border);
}
.chat-banner--passed {
  color: var(--success);
  background: var(--success-soft);
  border-top-color: color-mix(in srgb, var(--success) 32%, transparent);
}
.chat-banner--failed {
  color: var(--failure);
  background: var(--failure-soft);
  border-top-color: color-mix(in srgb, var(--failure) 32%, transparent);
}

/* mock-mode trigger chips — a row of clickable tokens above the composer.
   Everything is scoped to :not([hidden]) so the row (and the composer's
   border) behave when the server is not in mock mode. */
.mock-triggers:not([hidden]) {
  flex: none;
  grid-column: 1 / -1; /* harmless if it is placed inside .composer instead */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  padding: 8px 12px 0;
  border-top: 1px solid var(--border);
}
.mock-triggers:not([hidden]) + .composer {
  border-top: none;
  padding-top: 8px;
}
.mock-triggers > span {
  font: 600 10px var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-right: 2px;
}
.mock-triggers button,
.mock-triggers .mock-trigger,
.mock-triggers code {
  font: 500 11px/1 var(--font-mono);
  color: var(--text-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--speed) ease, border-color var(--speed) ease,
    background var(--speed) ease, transform var(--speed) ease;
}
.mock-triggers button:hover,
.mock-triggers .mock-trigger:hover,
.mock-triggers code:hover {
  color: var(--accent-text);
  background: var(--accent-soft);
  border-color: var(--accent-edge);
}
.mock-triggers button:active,
.mock-triggers .mock-trigger:active {
  transform: translateY(1px);
}

/* composer */
.composer {
  flex: none;
  display: grid;
  grid-template: "input send" auto "input reset" auto / minmax(0, 1fr) auto;
  gap: 6px 8px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--border);
}
.composer-input {
  grid-area: input;
  resize: none;
  min-height: 58px;
  max-height: 140px;
}
.composer-send {
  grid-area: send;
  min-width: 74px;
}
.composer-reset {
  grid-area: reset;
  min-width: 74px;
}
.composer-hint {
  grid-column: 1 / -1;
  font-size: 11px;
  color: var(--text-3);
  line-height: 1.4;
}

/* busy: spinner replaces the Send label. This one disabled button KEEPS its
   accent fill — it is working, not dead. */
.chat.is-busy .composer-send {
  position: relative;
  color: transparent;
}
.chat.is-busy .composer-send:disabled {
  background: var(--cta);
  border-color: transparent;
  color: transparent;
}
.chat.is-busy .composer-send::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--spinner-track);
  border-top-color: var(--cta-ink);
  animation: spin 0.8s linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* ----------------------------------------------------------------- toasts */

/* Clear of every control in the app: not the rail's add button (where these
   used to land), not the composer's Send/Reset — the top of the transcript,
   which is empty by construction now that turns grow up from the composer. */
.toast-stack {
  position: fixed;
  right: 14px;
  top: 130px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  max-width: 340px;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-2);
  color: var(--text-1);
  font-size: 13px;
  line-height: 1.5;
  padding: 10px 12px;
  cursor: pointer;
  overflow-wrap: break-word;
  animation: toast-in var(--speed-slow) cubic-bezier(0.2, 0.9, 0.3, 1.15);
  transition: opacity var(--speed-slow) ease, transform var(--speed-slow) ease;
}
.toast--ok {
  border-left-color: var(--success);
}
.toast--error {
  border-left-color: var(--failure);
}
.toast--info {
  border-left-color: var(--accent);
}
.toast.is-leaving {
  opacity: 0;
  transform: translateY(-6px);
}
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
}

/* ---------------------------------------------------------------- dialogs */

.dialog {
  border: 1px solid var(--border-strong);
  background: var(--panel);
  color: var(--text-1);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-2);
  padding: 0;
  width: min(400px, calc(100vw - 48px));
}
.dialog::backdrop {
  background: var(--scrim);
  backdrop-filter: blur(3px);
}
.dialog[open] {
  animation: dialog-in 180ms cubic-bezier(0.2, 0.9, 0.3, 1.1);
}
@keyframes dialog-in {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.97);
  }
}
.dialog-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
}
.dialog-title {
  font-size: 15px;
  font-weight: 600;
}
.dialog-body {
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-2);
}
.dialog-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font: 600 10px var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.dialog-input {
  font-size: 13px;
}
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 2px;
}
/* a matched pair, whatever the labels say — and the quiet one still has a
   body, so the row reads as two buttons rather than a link and a button */
.dialog-actions .btn {
  min-width: 92px;
}
.dialog-actions .btn--ghost {
  background: var(--bg-2);
  border-color: var(--border);
}
.dialog-actions .btn--ghost:hover:not(:disabled) {
  border-color: var(--border-strong);
}

/* boot failure overlay */
.boot-error {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  background: var(--scrim-strong);
  backdrop-filter: blur(3px);
}
.boot-error-card {
  width: min(420px, calc(100vw - 48px));
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-2);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.boot-error-title {
  font-size: 15px;
  font-weight: 600;
}
.boot-error-detail {
  font: 400 11px/1.6 var(--font-mono);
  color: var(--failure);
  background: var(--bg-0);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 8px 10px;
  overflow-wrap: break-word;
}
.boot-error-hint {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.55;
}

/* ------------------------------------------------------------------ login */

.login-body {
  display: grid;
  place-items: center;
  background:
    radial-gradient(
      1100px 560px at 50% -8%,
      var(--login-glow),
      transparent 60%
    ),
    radial-gradient(
      760px 420px at 50% 112%,
      var(--login-glow-2),
      transparent 62%
    ),
    var(--bg-0);
}

.login-card {
  width: 340px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-2);
  padding: 24px 24px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.login-card.is-error {
  animation: shake 340ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-5px); }
  40%, 60% { transform: translateX(5px); }
}

.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
}
.login-brand .glyph {
  width: 28px;
  height: 28px;
  flex: none;
}
.login-lockup {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.login-title {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-1);
}
.login-title-sub {
  font: 600 10px/1 var(--font-ui);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.login-sub {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.5;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.pw-wrap {
  position: relative;
}
.pw-input {
  padding-right: 58px;
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
}
.pw-toggle {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 10px;
  padding: 4px 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
}
.pw-toggle:hover:not(:disabled) {
  color: var(--text-1);
}
.login-error {
  color: var(--failure);
  font-size: 12px;
  min-height: 16px;
  line-height: 1.35;
}
.login-submit {
  padding: 8px 12px;
  font-size: 13px;
}
.login-hint {
  font-size: 11px;
  line-height: 1.9; /* room for the inline code chips */
  color: var(--text-3);
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
