/* ==========================================================================
   SceneSense.AI — product interface
   ==========================================================================
   The visual system is the one the marketing site wears
   (rajarshi-ray29.github.io/scenesense.html): the same purple palette, Manrope,
   pill geometry, an aurora-lit ground under everything, hairline borders and
   cards that lift on hover. Not one colour is new — the palette below is the
   product's own, which that page mirrors as `--ss-*`. What changed is the
   shape, the rhythm and the alignment around it.

   Structure notes that carry a bug behind them are kept where they were; they
   are about behaviour, not looks, and are as true of this skin as the last.
   ========================================================================== */

:root {
  /* ---- palette (unchanged) ---- */
  --purple-700: #6d28d9;
  --purple-600: #7c3aed;
  --purple-500: #8b5cf6;
  --purple-100: #ede9fe;
  --purple-50: #f5f3ff;
  --ink: #1f2330;
  --muted: #6b7280;
  --faint: #9aa0b2;
  --border: #e7e5ef;
  --line-strong: #d9d5e8;
  --bg: #fafafc;
  --card: #ffffff;

  /* ---- chart ramp: color-mix(in oklab, var(--purple-700) N%, #fff) ----
     The shipped scale has two dark steps and two near-whites with an OKLCH-L
     cliff of 0.34 between them, so it cannot supply a ramp. These are the
     validated tints: monotone L, adjacent dL >= 0.06, light end 2.12:1 on
     --card and 2.04:1 on --bg. Ship the literals, keep the derivation here —
     the value on screen must be the value that was checked. */
  --heat-1: #b6a8f4;   /* 45%  — only ever ships with a readout line and a table */
  --heat-2: #9b7fec;   /* 63%  */
  --heat-3: #8253e1;   /* 81%  */
  --heat-4: #6d28d9;   /* 100% = --purple-700 */

  /* Confidence is ORDINAL, not status: low < medium < high, and none of them
     is "bad" — low means read this one first. */
  --conf-low: #b6a8f4;
  --conf-mid: #8f70e8;   /* 72% */
  --conf-high: #6d28d9;

  /* The one legal two-shade pair on this palette (dE 25.4 normal, 22.3 protan).
     2.58:1 on white, so it ships only beside a visible count. */
  --mark-2: #a794f0;     /* 55% */

  /* De-emphasis. Neither is a series colour. */
  --track: var(--line-strong);   /* remainders, unfilled meters, context columns */
  --residue: var(--border);      /* the "everything else" slice of a part-to-whole */

  /* Status is state, never a series. Lifted out of the review CSS so the same
     green is not redefined in four places. */
  --ok: #16a34a;   --ok-bg: #dcfce7;   --ok-ink: #15803d;
  --warn: #d97706; --warn-bg: #fef3c7; --warn-ink: #b45309;
  --bad: #dc2626;  --bad-bg: #fee2e2;  --bad-ink: #b91c1c;
  --note-bg: #fffbeb; --note-line: #fde68a; --note-ink: #78350f;

  /* The site's two gradients: 135° for marks and buttons, a wider sweep for
     the long thin things (progress, rules, the nav-progress bar). */
  --grad: linear-gradient(135deg, var(--purple-600), var(--purple-500));
  --grad-wide: linear-gradient(100deg, var(--purple-700), var(--purple-600) 45%, var(--purple-500));

  /* ---- geometry ----
     One scale, so a dialog, a card and a chip are visibly the same family.
     --radius is the old name and stays: it is used across the file. */
  --r-xs: 8px;
  --r-sm: 11px;
  --r-md: 14px;
  --r-lg: 18px;
  --r-xl: 24px;
  --radius: 18px;
  --pill: 999px;

  /* ---- elevation ----
     Purple-tinted, never grey: a neutral drop shadow on this ground reads as
     dirt. --shadow is the site's own `--ss-shadow`. */
  --shadow-xs: 0 1px 2px rgba(24, 16, 60, 0.05);
  --shadow: 0 1px 2px rgba(24, 16, 60, 0.05), 0 8px 24px -12px rgba(109, 40, 217, 0.18);
  --shadow-lg: 0 2px 6px rgba(24, 16, 60, 0.05), 0 20px 48px -20px rgba(109, 40, 217, 0.3);
  --glow: 0 6px 20px -8px rgba(124, 58, 237, 0.5);
  --glow-lg: 0 10px 30px -10px rgba(124, 58, 237, 0.55);
  --ring: 0 0 0 3px rgba(139, 92, 246, 0.16);

  --font: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Mono is for anything you read as a value rather than as prose: timestamps,
     durations, counts, citation ranges. It also makes digits line up. */
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --sidebar-w: 268px;
  --gutter: 32px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
/* Safari inflates text in landscape unless told not to. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: rgba(124, 58, 237, 0.14);
}
::selection { background: rgba(139, 92, 246, 0.24); }
/* touch-action kills the 300ms double-tap wait that older mobile engines add. */
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit;
         touch-action: manipulation; }
input, select { font: inherit; }
a { color: inherit; }
.hidden { display: none !important; }

/* Every scrollable region in the app: thin, purple, and invisible until there
   is something to scroll. */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border: 3px solid transparent;
                            background-clip: padding-box; border-radius: var(--pill); }
::-webkit-scrollbar-thumb:hover { background: #c6c0dd; background-clip: padding-box; }

:focus-visible { outline: 2px solid var(--purple-500); outline-offset: 2px; }

/* ---------- atmosphere ----------
   The site's signature ground: two soft purple auroras and a faint rule grid,
   masked away before it reaches the content. Fixed and behind everything, so
   it costs no layout; `pointer-events: none` keeps it out of every hit test.
   The shell above it is translucent in the places worth seeing through. */
.atmosphere {
  position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(ellipse 90% 55% at 50% -12%, rgba(124, 58, 237, 0.09), transparent 70%),
    var(--bg);
}
.aurora { position: absolute; border-radius: 50%; filter: blur(110px); opacity: 0.5; }
.aurora-1 {
  width: 48vw; height: 48vw; top: -18vw; left: -10vw;
  background: radial-gradient(circle, rgba(139, 92, 246, 0.3), transparent 65%);
  animation: drift-1 32s ease-in-out infinite alternate;
}
.aurora-2 {
  width: 38vw; height: 38vw; top: 30vh; right: -14vw;
  background: radial-gradient(circle, rgba(124, 58, 237, 0.16), transparent 65%);
  animation: drift-2 38s ease-in-out infinite alternate;
}
@keyframes drift-1 { to { transform: translate(10vw, 10vh) scale(1.12); } }
@keyframes drift-2 { to { transform: translate(-8vw, -7vh) scale(1.18); } }

.grid-overlay {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    linear-gradient(rgba(31, 35, 48, 0.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(31, 35, 48, 0.028) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse 90% 65% at 50% 0%, black 25%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse 90% 65% at 50% 0%, black 25%, transparent 78%);
}

/* dvh tracks the shrinking/growing browser chrome on phones; vh is the fallback
   for engines without it, and --app-h (set from visualViewport, see app.js) wins
   on mobile so an open keyboard doesn't push the composer off screen. */
.app { display: flex; height: 100vh; height: 100dvh; }

/* ---------- sidebar ---------- */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  /* Translucent so the aurora reads through the rail rather than stopping at
     it; the blur is what keeps text on it legible. */
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  backdrop-filter: blur(20px) saturate(1.4);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 22px 14px 16px;
  /* Width is the only thing that animates when the rail collapses; the labels
     inside it are display:none, which cannot be tweened and should not be. */
  transition: width 0.24s var(--ease);
}
.brand { display: flex; align-items: center; gap: 11px; padding: 2px 8px 24px; }
.brand-mark {
  width: 36px; height: 36px; border-radius: 12px;
  background: var(--grad);
  color: #fff; font-weight: 800; font-size: 17px; letter-spacing: -0.02em;
  display: grid; place-items: center;
  box-shadow: var(--glow);
  flex-shrink: 0;
}
.brand-name { font-weight: 800; font-size: 17px; letter-spacing: -0.03em; }
/* The wordmark's ".AI" is the one place the gradient is used as type, exactly
   as it is on the site. */
.brand-name span {
  background: var(--grad); -webkit-background-clip: text; background-clip: text;
  color: transparent;
}

nav { display: flex; flex-direction: column; gap: 3px; }
.nav-item {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px; border-radius: var(--r-sm);
  font-weight: 600; font-size: 14px; color: var(--muted);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
  text-align: left;
}
.nav-item svg { width: 18px; height: 18px; flex-shrink: 0; }
.nav-item:hover { background: rgba(139, 92, 246, 0.07); color: var(--purple-700); }
.nav-item.active {
  color: var(--purple-700); font-weight: 700;
  background: linear-gradient(100deg, rgba(124, 58, 237, 0.13), rgba(139, 92, 246, 0.07));
}
/* The active rail: a short gradient bar on the leading edge, the same device
   the site uses for its section eyebrows. */
.nav-item.active::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 3px; height: 18px; border-radius: var(--pill); background: var(--grad);
}

/* The toggle that collapses the rail. It stays in the brand row at both widths,
   so the control that shrank the sidebar is the one that brings it back. The
   glyph is the panel-with-a-spine icon rather than an arrow: it names the thing
   it acts on, so it needs no second state to say which way it now points. */
.nav-collapse {
  display: grid; place-items: center; margin-left: auto; flex-shrink: 0;
  width: 32px; height: 32px; border-radius: var(--r-xs); color: var(--faint);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.nav-collapse:hover { background: rgba(139, 92, 246, 0.09); color: var(--purple-700); }
.nav-collapse svg { width: 18px; height: 18px; }

/* ---------- the collapsed rail ----------
   Desktop only, and app.js is what enforces that: at phone size the sidebar is
   already an off-canvas drawer, so the class is never set there (and is dropped
   on the way into that breakpoint). Everything that goes is prose — chat titles,
   the wordmark, the account's name and address — because none of it survives
   72px; every icon stays, named by its title attribute. */
body.rail-collapsed { --sidebar-w: 72px; }
body.rail-collapsed .sidebar { padding-left: 12px; padding-right: 12px; }
/* Mark over toggle: at this width a wordmark and a trailing button cannot
   share a row, and the mark is the half worth keeping. */
body.rail-collapsed .brand {
  flex-direction: column; gap: 10px; padding: 2px 0 20px;
}
body.rail-collapsed .brand-name { display: none; }
body.rail-collapsed .nav-collapse { margin-left: 0; }
body.rail-collapsed .nav-item { justify-content: center; padding: 11px 0; }
body.rail-collapsed .nav-item span { display: none; }
body.rail-collapsed .nav-item svg { width: 20px; height: 20px; }
body.rail-collapsed .rail,
body.rail-collapsed .user-meta,
body.rail-collapsed .user-chevron,
body.rail-collapsed #db-pill-text { display: none; }
/* The pill keeps its dot, which is the whole signal: indexing or idle. */
body.rail-collapsed .db-pill { padding: 8px; }
body.rail-collapsed .sidebar-footer {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding-left: 0; padding-right: 0;
}
body.rail-collapsed .user-box { padding: 7px; justify-content: center; }

.sidebar-footer { margin-top: auto; padding: 10px 6px 0; border-top: 1px solid var(--border); }
.db-pill {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono);
  font-size: 11px; font-weight: 500; letter-spacing: -0.02em; color: var(--muted);
  background: var(--card); border: 1px solid var(--border);
  padding: 6px 12px; border-radius: var(--pill);
  box-shadow: var(--shadow-xs);
}
.db-pill .dot { width: 7px; height: 7px; border-radius: 50%; background: #d1d5db; flex-shrink: 0; }
.db-pill.ok .dot { background: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16); }
.db-pill.busy .dot { background: var(--purple-500); animation: pulse-dot 2s infinite; }
@keyframes pulse-dot {
  0% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.5); }
  70% { box-shadow: 0 0 0 8px rgba(139, 92, 246, 0); }
  100% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0); }
}

/* ---------- main ---------- */
.main { flex: 1; overflow-y: auto; scroll-behavior: smooth; }
.view { max-width: 1080px; margin: 0 auto; padding: 34px var(--gutter) 64px; }

/* The page header is a block, not a row: an eyebrow that names the surface, the
   title under it, and the actions aligned to the title's baseline. A hairline
   closes it, so every view starts on the same horizontal line. */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  padding-bottom: 18px; margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
}
.page-head h2 {
  font-size: 27px; font-weight: 800; letter-spacing: -0.035em; line-height: 1.15;
}
/* The site's `.section-eyebrow`, verbatim in spirit: mono, uppercase, widely
   tracked, with a short gradient rule in front of it. The rule is a background
   image rather than a second pseudo-element — ::before is already spending
   itself on the text, which it reads off the markup's data-eyebrow. */
.page-head h2::before {
  content: attr(data-eyebrow);
  display: block;
  font-family: var(--mono);
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--purple-600); margin-bottom: 10px;
  padding-left: 40px;
  background-image: var(--grad-wide);
  background-size: 28px 1.5px; background-repeat: no-repeat;
  background-position: left 0.55em;
}
.page-head h2:not([data-eyebrow])::before { display: none; }

.toolbar { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; }
.search {
  display: flex; align-items: center; gap: 9px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--pill); padding: 0 16px; width: 250px; max-width: 100%;
  box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, box-shadow 0.2s;
}
.search svg { width: 15px; height: 15px; color: var(--faint); flex-shrink: 0; }
.search input { border: none; outline: none; background: none; padding: 9px 0; width: 100%;
                font-size: 13.5px; }
.search input::placeholder { color: var(--faint); }
/* iOS ignores most styling on a native select, so it is drawn from scratch —
   without appearance:none the radius and border are simply dropped there. */
.sort {
  -webkit-appearance: none; appearance: none;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--pill); padding: 9px 34px 9px 16px; color: var(--ink);
  font-weight: 600; font-size: 13px; outline: none; box-shadow: var(--shadow-xs);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa0b2' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 13px;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.sort:hover { border-color: var(--line-strong); }
.search:focus-within, .sort:focus { border-color: var(--purple-500); box-shadow: var(--ring); }

/* ---------- buttons ----------
   Pills, like the site's. Primary carries the gradient and a coloured glow;
   secondary is the white card version of the same shape; danger is the only
   place red appears as a fill. All three lift a pixel on hover. */
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--grad); color: #fff; font-weight: 700; font-size: 14px;
  letter-spacing: -0.01em;
  padding: 11px 24px; border-radius: var(--pill);
  box-shadow: var(--glow);
  transition: box-shadow 0.25s var(--ease), transform 0.2s var(--ease), filter 0.2s;
}
.btn-primary svg { width: 16px; height: 16px; }
.btn-primary:hover { box-shadow: var(--glow-lg); transform: translateY(-1px); filter: saturate(1.08); }
.btn-primary:active { transform: translateY(0) scale(0.985); }
.btn-primary:disabled { opacity: 0.5; cursor: default; box-shadow: none; transform: none; filter: none; }

.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--card); color: var(--ink); font-weight: 700; font-size: 13px;
  padding: 9px 18px; border-radius: var(--pill); border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, color 0.2s, transform 0.2s var(--ease);
}
.btn-secondary svg { width: 15px; height: 15px; }
.btn-secondary:hover {
  border-color: var(--line-strong); background: var(--purple-50);
  color: var(--purple-700); transform: translateY(-1px);
}
.btn-secondary:active { transform: none; }
.btn-secondary:disabled { opacity: 0.5; cursor: default; transform: none; }
.btn-secondary.spinning svg { animation: spin 0.9s linear infinite; }
.btn-secondary.active {
  background: var(--purple-100); border-color: rgba(124, 58, 237, 0.4); color: var(--purple-700);
}

.btn-danger {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: #dc2626; color: #fff; font-weight: 700; font-size: 13px;
  padding: 10px 18px; border-radius: var(--pill);
  box-shadow: 0 6px 18px -8px rgba(220, 38, 38, 0.6);
  transition: background 0.2s, opacity 0.2s, transform 0.2s var(--ease);
}
.btn-danger svg { width: 15px; height: 15px; }
.btn-danger:hover:not(:disabled) { background: #b91c1c; transform: translateY(-1px); }
.btn-danger:disabled { opacity: 0.42; cursor: default; box-shadow: none; transform: none; }

/* ---------- edit mode ---------- */
.action-bar {
  display: flex; align-items: center; gap: 16px;
  background: rgba(255, 255, 255, 0.8);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid rgba(124, 58, 237, 0.22);
  border-radius: var(--r-md); padding: 11px 18px; margin-bottom: 20px;
  box-shadow: var(--shadow-xs);
}
.action-spacer { flex: 1; }
.select-all { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: 13.5px; cursor: pointer; }
.selected-count { font-family: var(--mono); font-size: 12px; color: var(--muted); }
input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--purple-600); cursor: pointer; }

.card.selectable { padding-left: 50px; position: relative; }
.card-check { position: absolute; left: 18px; top: 22px; width: 17px; height: 17px; margin: 0; }
.card.selected {
  border-color: var(--purple-500); background: var(--purple-50);
  box-shadow: var(--shadow), 0 0 0 1px rgba(139, 92, 246, 0.3);
}
.card.removed { opacity: 0.66; border-style: dashed; }
.badge.stage.removed-badge { background: #fff1f2; color: #be123c; }
.reindex-btn {
  align-self: flex-start; font-size: 12px; font-weight: 700;
  color: var(--purple-700); background: var(--purple-50);
  border: 1px solid rgba(124, 58, 237, 0.16); border-radius: var(--pill); padding: 5px 13px;
  transition: border-color 0.2s, background 0.2s;
}
.reindex-btn:hover { border-color: var(--purple-500); background: var(--purple-100); }

/* ---------- confirm modal ---------- */
.confirm-card { width: min(500px, 100%); }
.confirm-body { color: var(--ink); line-height: 1.65; }
.confirm-list {
  list-style: none; margin: 14px 0; max-height: 190px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 6px;
  background: var(--bg);
}
.confirm-list li {
  font-size: 13px; padding: 7px 11px; border-radius: var(--r-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.confirm-list li:nth-child(odd) { background: var(--card); }
.confirm-note { font-size: 12.5px; color: var(--muted); line-height: 1.6; }
.confirm-note code {
  font-family: var(--mono); font-size: 11.5px;
  background: var(--purple-50); border-radius: 5px; padding: 1px 5px;
}
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 24px; }

/* ---------- toast ---------- */
.toast {
  position: fixed; bottom: calc(30px + env(safe-area-inset-bottom)); left: 50%;
  transform: translateX(-50%);
  background: rgba(24, 20, 42, 0.94);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: #fff; font-weight: 600; font-size: 13.5px;
  padding: 12px 22px; border-radius: var(--pill); z-index: 80;
  box-shadow: 0 14px 40px -12px rgba(24, 16, 60, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.09);
  max-width: min(560px, 90vw); text-align: center;
}

/* ---------- banner ---------- */
.banner {
  background: linear-gradient(100deg, var(--purple-50), rgba(237, 233, 254, 0.5));
  border: 1px solid rgba(124, 58, 237, 0.18);
  color: var(--purple-700); font-weight: 600; font-size: 13.5px;
  border-radius: var(--r-md); padding: 12px 18px; margin-bottom: 20px;
  display: flex; align-items: center; gap: 12px;
}
.banner .spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(124, 58, 237, 0.2); border-top-color: var(--purple-600);
  animation: spin 0.8s linear infinite; flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- cards ---------- */
/* min(300px, 100%) keeps the track from overflowing a phone narrower than the
   300px floor — a plain minmax(300px, 1fr) scrolls sideways at 320px. */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 16px; }
.card {
  position: relative; overflow: hidden;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px;
  display: flex; flex-direction: column; gap: 11px;
  box-shadow: var(--shadow-xs);
  cursor: pointer; text-align: left;
}
/* The sheen the site's feature cards wear: a purple wash that fades in from the
   top-left on hover. ::before rather than a background so the card's own
   background stays available to states like .selected.
   z-index:-1 + isolation keeps it above the card's background but under its
   content, without needing a `position` on the children — .card-check is
   absolutely positioned and a blanket `.card > * { position: relative }` here
   silently un-positioned it, dropping the tick into the middle of the title. */
.card { isolation: isolate; }
.card::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(340px circle at 0% 0%, rgba(124, 58, 237, 0.1), transparent 62%);
  opacity: 0; transition: opacity 0.3s var(--ease);
}
.card:hover::before { opacity: 1; }
.card:hover { border-color: rgba(124, 58, 237, 0.42); box-shadow: var(--shadow); }
.card-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.card h3 { font-size: 15px; font-weight: 700; line-height: 1.4; letter-spacing: -0.015em; }
.badge {
  font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: var(--pill);
  white-space: nowrap; flex-shrink: 0; letter-spacing: -0.01em;
}
.badge.duration {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  background: var(--purple-50); color: var(--purple-700);
  border: 1px solid rgba(124, 58, 237, 0.14);
}
.badge.stage { background: #f4f4f7; color: var(--muted); border: 1px solid var(--border); }
.badge.stage.err {
  background: #fef2f2; color: #dc2626; border-color: #fecaca;
  white-space: normal; line-height: 1.45; text-align: left;
  overflow-wrap: anywhere; flex-shrink: 1; align-self: stretch;
}
.card-summary {
  color: var(--muted); font-size: 13px; line-height: 1.6;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  background: var(--purple-50); color: var(--purple-700);
  font-size: 11.5px; font-weight: 600; padding: 5px 12px; border-radius: var(--pill);
  border: 1px solid rgba(124, 58, 237, 0.14);
}
.collection-tag {
  align-self: flex-start;
  font-family: var(--mono);
  font-size: 9.5px; font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--purple-700); background: var(--purple-50);
  border: 1px solid rgba(124, 58, 237, 0.14); border-radius: var(--pill); padding: 3px 10px;
}
.chip.collection { background: var(--purple-600); color: #fff; border-color: var(--purple-600); }
.progress-track { height: 5px; background: rgba(124, 58, 237, 0.1); border-radius: var(--pill); overflow: hidden; }
.progress-fill { height: 100%; background: var(--grad-wide); border-radius: var(--pill); }
/* A download running ahead of the video being transcribed — distinct on sight,
   so two moving bars don't read as one video somehow being processed twice. */
.progress-fill.downloading { background: linear-gradient(100deg, #7cc3e8, #4b9fd1); }
.card .stage-line { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

/* ---------- topic cards ---------- */
.topic-cards { display: flex; flex-direction: column; gap: 12px; }
.topic-card {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 18px; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px 24px 22px 26px;
  box-shadow: var(--shadow-xs);
}
/* The topic's own colour as a rail down the leading edge — the same thing its
   citations wear in chat, so a topic is recognisable before you read its name.
   Falls back to the brand gradient for a topic with no colour set. */
.topic-card::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--topic-c, var(--grad));
  opacity: 0.85;
}
.topic-card:hover { border-color: rgba(124, 58, 237, 0.42); box-shadow: var(--shadow); }
.topic-main { flex: 1; min-width: 0; }
.topic-name {
  display: flex; align-items: center;
  font-size: 19px; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 5px;
}
.topic-meta { font-size: 12.5px; color: var(--muted); font-weight: 500; margin-bottom: 14px; }
.topic-src { color: var(--purple-600); font-weight: 600; }
.topic-progress {
  height: 5px; background: rgba(124, 58, 237, 0.1); border-radius: var(--pill);
  overflow: hidden; max-width: 440px;
}
.topic-progress-fill {
  height: 100%; border-radius: var(--pill);
  background: var(--topic-c, var(--grad-wide));
}
.topic-dot {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--topic-c); margin-right: 9px; flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(31, 35, 48, 0.1);
}
.topic-status { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 8px; }
.topic-arrow {
  display: grid; place-items: center; flex-shrink: 0;
  width: 34px; height: 34px; border-radius: 50%;
  font-size: 15px; color: var(--purple-600);
  background: var(--purple-50); border: 1px solid rgba(124, 58, 237, 0.14);
  transition: background 0.25s var(--ease), color 0.25s, transform 0.25s var(--ease);
}
.topic-card:hover .topic-arrow {
  background: var(--grad); color: #fff; border-color: transparent;
  transform: translateX(3px); box-shadow: var(--glow);
}
.back-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 700; font-size: 13px; color: var(--muted);
  padding: 5px 0; margin-bottom: 10px;
  transition: color 0.2s;
}
.back-link:hover { color: var(--purple-700); }
.empty-note { color: var(--muted); font-size: 14px; }
.view-intro { color: var(--muted); line-height: 1.7; margin: -6px 0 26px; max-width: 640px; font-size: 14px; }

/* ---------- connectors ---------- */
.sources-list { display: flex; flex-direction: column; gap: 28px; }
.source-group h4 {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--mono);
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.22em; font-weight: 500;
  color: var(--purple-600); margin-bottom: 12px;
}
.source-group h4::before {
  content: ""; width: 28px; height: 1.5px; background: var(--grad-wide); flex-shrink: 0;
}
.source-row {
  display: flex; align-items: center; gap: 16px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 15px 20px; margin-bottom: 9px;
  box-shadow: var(--shadow-xs);
}
.source-icon { flex-shrink: 0; color: var(--muted); display: grid; place-items: center;
               width: 34px; height: 34px; border-radius: var(--r-sm);
               background: var(--bg); border: 1px solid var(--border); }
.source-icon svg { width: 18px; height: 18px; }
.source-body { flex: 1; min-width: 0; }
.source-name { font-weight: 700; font-size: 14.5px; letter-spacing: -0.015em;
               display: flex; align-items: center; gap: 8px; }
.source-path {
  font-family: var(--mono); font-size: 11.5px; color: var(--muted); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A .btn-secondary that removes something. It has to say so on hover too —
   the base hover turns the label purple, which on a Remove button is wrong. */
.danger-text { color: #dc2626; }
.danger-text:hover { color: #b91c1c; }
/* A row that knows its Drive folder is clickable; one that doesn't looks inert,
   so nobody clicks hopefully at a row that cannot go anywhere. With no label
   saying so, hover and the pointer are the only affordance — hence the lift. */
.source-row.linked { cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s var(--ease); }
.source-row.linked:hover { border-color: rgba(124, 58, 237, 0.42); box-shadow: var(--shadow); transform: translateY(-2px); }
.source-row.linked:hover .source-icon { color: var(--purple-600); border-color: rgba(124, 58, 237, 0.2); background: var(--purple-50); }
.danger-text:hover { border-color: #fecaca; background: #fef2f2; }

.source-card { width: min(640px, 100%); }
.tabs { display: flex; gap: 8px; margin-bottom: 18px; }
.tab {
  flex: 1; padding: 10px; border-radius: var(--pill); font-weight: 700; font-size: 13px;
  border: 1px solid var(--border); background: var(--card); color: var(--muted);
}
.tab.active { background: var(--purple-50); border-color: var(--purple-500); color: var(--purple-700); }
.gdrive-note {
  background: var(--purple-50); border: 1px solid rgba(124, 58, 237, 0.16);
  border-radius: var(--r-md); padding: 13px 16px; font-size: 13px;
  line-height: 1.6; color: var(--ink); margin-bottom: 16px;
}
.drive-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.drive-links .chip { cursor: pointer; }
.upload-drop {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 100%; padding: 20px; margin-bottom: 16px;
  border: 1.5px dashed rgba(124, 58, 237, 0.3); border-radius: var(--r-md);
  background: var(--purple-50); transition: border-color 0.2s, background 0.2s;
}
.upload-drop:hover { border-color: var(--purple-500); background: var(--purple-100); }
.upload-drop:disabled { opacity: 0.6; cursor: default; }
.upload-icon { font-size: 20px; }
.upload-main { font-weight: 800; font-size: 14px; color: var(--purple-700); }
.upload-hint { font-size: 12px; color: var(--muted); }

.drive-link-row { display: flex; gap: 8px; margin-bottom: 12px; }
.drive-link-row input {
  flex: 1; border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 10px 14px; outline: none; font-size: 13px;
}
.drive-link-row input:focus { border-color: var(--purple-500); box-shadow: var(--ring); }

.source-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* Monochrome icon buttons — the label lives in the tooltip. */
.icon-action {
  width: 34px; height: 34px; flex-shrink: 0;
  display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--card); color: var(--muted);
  transition: color 0.2s, border-color 0.2s, background 0.2s, transform 0.15s;
}
.icon-action svg { width: 16px; height: 16px; }
.icon-action:hover { color: var(--purple-700); border-color: rgba(124, 58, 237, 0.3); background: var(--purple-50); }
.icon-action:active { transform: scale(0.94); }
.icon-action:disabled { opacity: 0.4; cursor: default; }
.icon-action.destructive:hover { color: #dc2626; border-color: #fecaca; background: #fef2f2; }
.icon-action.spinning svg { animation: spin 0.9s linear infinite; }

.picker-path { display: flex; gap: 8px; margin-bottom: 12px; }
.picker-path input {
  flex: 1; border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 10px 14px; outline: none; font-size: 13px; font-family: var(--mono);
}
.picker-path input:focus { border-color: var(--purple-500); box-shadow: var(--ring); }
.browse-list {
  max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 6px;
}
.browse-item { text-align: left; padding: 9px 12px; border-radius: var(--r-xs); font-size: 13.5px; }
.browse-item:hover { background: var(--purple-50); color: var(--purple-700); }

/* ---------- chat ---------- */
/* The composer is pinned to the bottom of the viewport by filling the column
   rather than by subtracting a guessed header height from 100vh — that guess
   was wrong on every phone, where 100vh also includes the browser's own chrome. */
/* Every other view ends in 64px of air so its last card doesn't sit on the
   window edge; this one ends in the composer, which is furniture rather than
   content and reads as unmoored with a gap of that size under it. */
#view-ask { display: flex; flex-direction: column; height: 100%; padding-bottom: 16px; }
.chat { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.chat-scroll {
  flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 18px;
  padding: 8px 2px 20px; overscroll-behavior: contain;
}
.chat-empty { margin: auto; text-align: center; color: var(--muted); max-width: 520px; padding: 0 8px; }
.chat-empty p { margin: 10px 0 26px; line-height: 1.6; font-size: 14.5px; }
.suggestions { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; }
.suggestion {
  cursor: pointer; font-size: 13px; padding: 9px 16px;
  background: var(--card); border-color: var(--border); color: var(--ink);
  box-shadow: var(--shadow-xs);
  transition: transform 0.2s var(--ease), background 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s;
}
.suggestion:hover {
  background: var(--purple-50); border-color: rgba(124, 58, 237, 0.35);
  color: var(--purple-700); box-shadow: var(--shadow);
}

/* ---------- video dock ---------- */
/* Cited videos docked above the conversation. The stage is a fixed height
   rather than 16:9: at chat width that ratio would swallow the viewport, and
   .chat-scroll is flex:1 so it absorbs whatever the dock takes. */
.video-dock {
  display: flex; flex-direction: column; gap: 10px;
  padding-bottom: 14px; margin-bottom: 4px; border-bottom: 1px solid var(--border);
}
.dock-bar { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* More tabs than fit scroll sideways; the bar never wraps into the stage. */
.dock-tabs {
  display: flex; gap: 6px; flex: 1; min-width: 0;
  overflow-x: auto; scrollbar-width: thin; padding-bottom: 2px;
}
.dock-tab {
  display: inline-flex; align-items: center; flex-shrink: 0;
  max-width: 220px; min-width: 0;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--pill);
  box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s;
}
.dock-tab:hover { border-color: var(--purple-500); }
.dock-tab-name {
  min-width: 0; padding: 6px 5px 6px 14px;
  font-size: 12px; font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Always visible — a hover-revealed × is unreachable on a touchscreen. */
.dock-tab-close {
  flex-shrink: 0; width: 20px; height: 20px; margin-right: 5px;
  border-radius: 50%; display: grid; place-items: center;
  font-size: 10px; color: var(--faint);
}
.dock-tab-close:hover { background: var(--purple-100); color: var(--ink); }
.dock-tab.active { background: var(--grad); border-color: transparent; box-shadow: var(--glow); }
.dock-tab.active .dock-tab-name, .dock-tab.active .dock-tab-close { color: #fff; }
.dock-tab.active .dock-tab-close:hover { background: rgba(255, 255, 255, 0.25); }
/* The topic's pastel, worn the way this video's citations wear it below. */
.dock-tab.tinted.active {
  background: color-mix(in srgb, var(--cite-c) 45%, #fff);
  border-color: var(--cite-c); box-shadow: var(--shadow-xs);
}
.dock-tab.tinted.active .dock-tab-name,
.dock-tab.tinted.active .dock-tab-close {
  color: color-mix(in srgb, var(--cite-c) 32%, #000);
}
.dock-tab.tinted.active .dock-tab-close:hover { background: var(--cite-c); }
.dock-stage { height: min(36vh, 300px); }
/* The stage is a height-capped row, but the media box keeps a 16:9 shape and
   centers, rather than stretching the full row into black side-bars. The bare
   (non-auto) aspect-ratio deliberately overrides the video's intrinsic one so
   the frame never jumps when metadata arrives; an odd-shaped video letterboxes
   inside it via <video>'s default object-fit. */
#dock-player, #dock-embed {
  display: block; height: 100%; width: auto; max-width: 100%; margin: 0 auto;
  aspect-ratio: 16 / 9;
  border: 0; border-radius: var(--r-md); background: #000;
  box-shadow: var(--shadow);
}
.video-dock.collapsed .dock-stage { display: none; }
.dock-toggle svg { width: 16px; height: 16px; transition: transform 0.2s var(--ease); }
.video-dock.collapsed .dock-toggle svg { transform: rotate(180deg); }

/* The cited seconds of the active video, under the stage. They wrap, and a
   conversation that cites one video very many times scrolls inside the row
   instead of squeezing the chat. Hover shows the passage the citation quoted. */
.dock-moments { display: flex; flex-wrap: wrap; gap: 6px; max-height: 76px; overflow-y: auto; }
.dock-moment {
  padding: 5px 12px; border-radius: var(--pill);
  background: var(--purple-50); border: 1px solid rgba(124, 58, 237, 0.16);
  font-family: var(--mono); font-size: 11px; font-weight: 500; color: var(--purple-700);
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.dock-moment:hover { background: var(--purple-500); border-color: var(--purple-500); color: #fff; }
.dock-moments.tinted .dock-moment {
  background: color-mix(in srgb, var(--cite-c) 25%, #fff);
  border-color: color-mix(in srgb, var(--cite-c) 60%, #fff);
  color: color-mix(in srgb, var(--cite-c) 32%, #000);
}
.dock-moments.tinted .dock-moment:hover {
  background: var(--cite-c);
  border-color: var(--cite-c);
  color: color-mix(in srgb, var(--cite-c) 28%, #000);
}
.video-dock.collapsed .dock-moments { display: none; }

/* The written-out form of the same citations: which video, which second, and
   the transcript being spoken there. Stacked layouts have no room for it, so
   it only exists at the width where the dock runs the full height beside the
   chat — the media query below swaps it in for the chips. */
.dock-guide { display: none; }
.dock-guide-group {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 10px;
  box-shadow: var(--shadow-xs);
}
.dock-guide-group.active { border-color: rgba(124, 58, 237, 0.42); }
.dock-guide-group.tinted.active {
  border-color: var(--cite-c);
  background: color-mix(in srgb, var(--cite-c) 10%, #fff);
}
.dock-guide-title {
  font-size: 11px; font-weight: 800; letter-spacing: 0.02em; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 0 4px 4px;
}
.dock-guide-group.tinted .dock-guide-title {
  color: color-mix(in srgb, var(--cite-c) 40%, var(--muted));
}
.dock-guide-row {
  display: flex; gap: 11px; align-items: flex-start; text-align: left;
  padding: 7px 9px; border-radius: var(--r-xs); transition: background 0.2s;
}
.dock-guide-row:hover { background: var(--purple-50); }
.dock-guide-group.tinted .dock-guide-row:hover {
  background: color-mix(in srgb, var(--cite-c) 22%, #fff);
}
.dock-guide-row .ts {
  flex-shrink: 0; padding-top: 2px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600; color: var(--purple-700);
}
.dock-guide-group.tinted .dock-guide-row .ts {
  color: color-mix(in srgb, var(--cite-c) 32%, #000);
}
.dock-guide-row .what {
  font-size: 12.5px; line-height: 1.5; color: var(--ink); min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* On a wide window the dock moves beside the conversation instead of on top
   of it: stacked, a full-width stage was mostly black bars and the chat under
   it lost half its height. Grid rather than a wrapper because the dock, the
   scrollback and the composer are siblings; the dock takes the left column at
   its natural 16:9 height and the chat keeps the whole viewport's height on
   the right. A collapsed (or absent) dock falls back to the stacked strip —
   that is what the :has() guard says — so the chevron still means "give the
   space back to the chat". The column is capped by viewport height too, so a
   short, very wide window can't grow a stage taller than the chat beside it. */
@media (min-width: 1100px) {
  /* A watch-page layout while a video is up. The Ask view drops its centered
     1080px cap (the other views keep theirs — a chat alone at full width is
     just long lines), and the columns divide the way YouTube's do: the chat
     is a fixed-width rail like the related-videos column, and the player
     takes everything else, bounded only by what the viewport's height can
     show at 16:9 so an ultrawide can't grow a video taller than the screen. */
  #view-ask:has(> .chat > .video-dock:not(.hidden):not(.collapsed)) {
    max-width: 1880px;
  }
  .chat:has(> .video-dock:not(.hidden):not(.collapsed)) {
    display: grid;
    grid-template-columns:
      minmax(0, calc((100dvh - 230px) * 16 / 9))
      clamp(400px, 28%, 480px);
    grid-template-rows: minmax(0, 1fr) auto;
    /* Every direct child of .chat needs a named home here, or the grid
       auto-places it into an implicit cell of its own — which is why the AI
       disclaimer lives inside the composer form rather than beside it. */
    grid-template-areas: "dock scroll" "dock composer";
    column-gap: 28px;
  }
  /* Inert outside the grid above: grid-area does nothing to flex children,
     and the dock rule can't match while the dock is collapsed. */
  .chat > .video-dock:not(.collapsed) {
    grid-area: dock;
    border-bottom: 0; padding-bottom: 0; margin-bottom: 0; min-width: 0;
  }
  .chat > .chat-scroll { grid-area: scroll; }
  .chat > .composer { grid-area: composer; }
  /* Width-driven in the side column: the stage fills it, and the same
     aspect-ratio now computes height from width instead of the reverse. */
  .video-dock:not(.collapsed) .dock-stage { height: auto; }
  .video-dock:not(.collapsed) #dock-player,
  .video-dock:not(.collapsed) #dock-embed { width: 100%; height: auto; }
  /* Beside the chat there is a column of room under the player: trade the
     compact chips for the written-out guide and let it fill and scroll. */
  .video-dock:not(.collapsed) .dock-moments { display: none; }
  .video-dock:not(.collapsed) .dock-guide {
    display: flex; flex-direction: column; gap: 10px;
    flex: 1; min-height: 0; overflow-y: auto;
  }
}

/* Fullscreen is the stage (see the button's wiring), and whichever player is
   visible fills it edge to edge. After the media query above on purpose: it
   must win over both of the stage's sizing modes. */
.dock-stage:fullscreen { height: 100%; background: #000; }
.dock-stage:fullscreen #dock-player,
.dock-stage:fullscreen #dock-embed {
  width: 100%; height: 100%; max-width: none; margin: 0;
  aspect-ratio: auto; border-radius: 0; box-shadow: none;
}

/* ---------- messages ---------- */
/* break-word first for Safari older than 15.4, which drops `anywhere`. */
.msg {
  max-width: 76%; border-radius: var(--r-xl); padding: 15px 20px;
  line-height: 1.7; font-size: 14.5px;
  overflow-wrap: break-word; overflow-wrap: anywhere;
}
.stopped-note { color: var(--muted); font-style: italic; }
.msg.user {
  align-self: flex-end; background: var(--grad); color: #fff;
  border-bottom-right-radius: 8px; font-weight: 500;
  box-shadow: var(--glow);
  /* Line breaks the user typed are part of the question — a wrapped-only bubble
     would silently reflow a pasted list into one paragraph. Assistant messages
     are rendered markdown and must not get this. */
  white-space: pre-wrap;
}
.msg.assistant {
  align-self: flex-start; background: var(--card);
  border: 1px solid var(--border); border-bottom-left-radius: 8px;
  box-shadow: var(--shadow-xs);
}
.msg.assistant h1, .msg.assistant h2, .msg.assistant h3 {
  font-size: 15px; font-weight: 800; letter-spacing: -0.015em; margin: 14px 0 5px;
}
.msg.assistant h1:first-child, .msg.assistant h2:first-child, .msg.assistant h3:first-child { margin-top: 0; }
.msg.assistant p { margin: 7px 0; }
.msg.assistant p:first-child { margin-top: 0; }
.msg.assistant p:last-child { margin-bottom: 0; }
.msg.assistant ul, .msg.assistant ol { margin: 7px 0 7px 20px; }
.msg.assistant li { margin: 4px 0; }
.msg.assistant strong { font-weight: 800; }
.msg.assistant code {
  font-family: var(--mono); background: var(--purple-50); color: var(--purple-700);
  border: 1px solid rgba(124, 58, 237, 0.12);
  border-radius: 6px; padding: 1px 5px; font-size: 12px;
}
/* The spinner and the live step sit on one row; completed steps stack beneath.
   A plain flex row put the whole history alongside the spinner instead. */
.msg.thinking {
  color: var(--muted);
  display: grid; grid-template-columns: auto 1fr; gap: 5px 10px; align-items: center;
}
.msg.thinking .step-log { grid-column: 2; }

.cite {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; margin: 0 2px;
  background: var(--purple-100); color: var(--purple-700);
  border-radius: var(--pill); font-family: var(--mono);
  font-size: 10.5px; font-weight: 600;
  vertical-align: 1px; transition: background 0.2s, color 0.2s;
}
.cite:hover { background: var(--purple-500); color: #fff; }

/* A citation carrying its time range ("04:42–05:59") instead of a number:
   same pill, free width, digits aligned so a row of them reads as a column.
   Each end is its own button — start plays where the discussion begins, end
   jumps to where it wraps up — so hover highlights the half you'd hit, not
   the whole pill. */
.cite.cite-range {
  min-width: 0; padding: 0 2px;
  font-size: 10px; letter-spacing: -0.02em;
  white-space: nowrap;
}
.cite.cite-range:hover { background: var(--purple-100); color: var(--purple-700); }
.cite.tinted.cite-range:hover {
  background: color-mix(in srgb, var(--cite-c) 45%, #fff);
  color: color-mix(in srgb, var(--cite-c) 32%, #000);
}
.cite-range .cr-t {
  display: inline-flex; align-items: center; height: 100%;
  padding: 0 6px; border-radius: var(--pill);
  transition: background 0.2s, color 0.2s;
}
.cite-range .cr-t:hover { background: var(--purple-500); color: #fff; }
.cite.tinted .cr-t:hover {
  background: var(--cite-c);
  color: color-mix(in srgb, var(--cite-c) 28%, #000);
}
.cite-range .cr-sep { opacity: 0.5; }

/* One second instead of a range: the answer resolved the sentence inside the
   passage, so there is a single place to send you and no second end to offer.
   The two-ended pill above stays for answers saved before this existed — hence
   the padding here, which .cite-range gives away to its halves. */
.cite.cite-moment { padding: 0 9px; font-size: 10.5px; }
.cite.cite-moment:hover { background: var(--purple-500); color: #fff; }
.cite.tinted.cite-moment:hover {
  background: var(--cite-c);
  color: color-mix(in srgb, var(--cite-c) 28%, #000);
}

/* Per-answer confidence: retrieval match strength, worded and colored as a
   signal rather than a verdict. */
.msg-conf {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 14px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--pill); padding: 4px 12px;
}
.msg-conf .conf-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.msg-conf.conf-high .conf-dot { background: #16a34a; box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.14); }
.msg-conf.conf-medium .conf-dot { background: #d97706; box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.14); }
.msg-conf.conf-low .conf-dot { background: #dc2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.14); }
.msg-meta { font-family: var(--mono); font-size: 10.5px; opacity: 0.85; margin-top: 6px; }

/* What a refusal offers instead. It backs no claim, so it deliberately wears
   none of the citation chrome above — a card-coloured chip under a hairline,
   not a purple pill inline with the prose. The label takes the full row so the
   chips wrap under it however many there are. */
.near-row {
  display: flex; flex-wrap: wrap; gap: 7px; align-items: stretch;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
}
.near-label {
  flex: 1 1 100%;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--purple-600);
  padding-left: 34px;
  background-image: var(--grad-wide);
  background-size: 24px 1.5px; background-repeat: no-repeat;
  background-position: left 0.62em;
}
.near-chip {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px;
  max-width: 100%; text-align: left; padding: 7px 14px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--pill); box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
/* A pill bows its ends around two lines of text, so a chip that carries the
   "covers" line takes the card radius instead. */
.near-chip:has(.c) { border-radius: var(--r-md); }
.near-chip:hover { border-color: rgba(124, 58, 237, 0.4); background: var(--purple-50);
                   box-shadow: var(--shadow); }
.near-chip .t { flex-shrink: 0; font-family: var(--mono); font-size: 11px;
                font-weight: 600; color: var(--purple-700); }
.near-chip .n { font-size: 12.5px; font-weight: 600; color: var(--ink);
                min-width: 0; overflow-wrap: anywhere; }
.near-chip .c { flex: 1 1 100%; font-size: 11.5px; color: var(--muted); line-height: 1.5; }

/* ---------- composer ----------
   The question box is one capsule: the field and the send button share a
   border, the way the site's own chat mock draws it. The scope controls sit
   below it, outside the capsule, so the box itself stays a single clean shape. */
.composer { display: flex; flex-direction: column; gap: 11px; padding-top: 16px; }

/* The standing trust line under the question box. Informational, not a
   warning: muted, small, and always there. */
.ai-disclaimer {
  margin: 0; font-size: 11px; line-height: 1.55; color: var(--muted);
  text-align: center;
}

.composer-row {
  display: flex; gap: 8px; align-items: flex-end;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-xl); padding: 7px 7px 7px 8px;
  box-shadow: var(--shadow);
  transition: border-color 0.2s, box-shadow 0.2s;
}
.composer-row:focus-within { border-color: var(--purple-500); box-shadow: var(--shadow), var(--ring); }
.composer-row textarea {
  flex: 1; min-width: 0;
  background: none; border: none; border-radius: var(--r-lg);
  padding: 12px 12px 12px 14px; font-size: 15px; outline: none;
  font-family: inherit; line-height: 1.55; color: inherit;
  /* Height is set from the content by autoGrow(); this is the floor, and the
     ceiling is a max-height so a long question scrolls instead of eating the
     conversation. `resize: none` because the drag handle fights that. */
  min-height: 46px; max-height: 40vh; resize: none; overflow-y: auto;
}
.composer-row textarea::placeholder { color: var(--faint); }
.btn-send {
  width: 46px; height: 46px; flex-shrink: 0; padding: 0; border-radius: 50%;
  display: grid; place-items: center;
}
.btn-send svg { width: 18px; height: 18px; }
/* One button, two jobs: send, or stop the answer that is being written. */
.btn-send .stop-icon, .btn-send.stopping .send-icon { display: none; }
.btn-send.stopping .stop-icon { display: block; }
.btn-send.stopping { background: var(--ink); box-shadow: 0 6px 20px -8px rgba(31, 35, 48, 0.6); }

.composer-controls {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-left: 4px;
}
.picker { position: relative; }
.picker-btn {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 700; color: var(--muted);
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--pill); padding: 6px 14px;
  box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.picker-btn:hover { border-color: var(--line-strong); background: var(--purple-50); color: var(--purple-700); }
/* Chosen is a state worth seeing from across the composer: the button is the
   only place the question's topic is shown. */
.picker-btn.chosen {
  background: var(--purple-100); border-color: rgba(124, 58, 237, 0.35); color: var(--purple-700);
}
.picker-btn .caret { font-size: 9px; font-weight: 800; opacity: 0.7; }
/* The name can be long; the button gives it a ceiling rather than pushing the
   video chips beside it off the row. */
.picker-btn #topic-label {
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.picker-menu {
  position: absolute; bottom: calc(100% + 10px); left: 0; z-index: 30;
  min-width: 260px; max-height: 300px; overflow-y: auto;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 7px; box-shadow: var(--shadow-lg);
}
.menu-item {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  padding: 9px 12px; border-radius: var(--r-sm);
}
.menu-item:hover { background: var(--purple-50); }
/* One topic at a time, so the tick marks a choice, not a count. */
.menu-item.on { background: var(--purple-100); }
.menu-item.on .menu-main::after { content: " ✓"; color: var(--purple-600); }
.menu-note strong { color: var(--purple-700); }
.menu-main { font-size: 13.5px; font-weight: 700; }
.menu-hint { font-size: 11.5px; color: var(--muted); }
.menu-title {
  font-family: var(--mono);
  font-size: 9.5px; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--purple-600); padding: 8px 12px 5px;
}
.menu-note { font-size: 11.5px; color: var(--muted); padding: 0 12px 8px; line-height: 1.5; }

/* ---------- question scope chips ---------- */
.scope-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.scope-chip {
  display: inline-flex; align-items: center; gap: 7px; max-width: 340px; min-width: 0;
  font-size: 12.5px; font-weight: 700; color: var(--purple-700);
  background: var(--purple-100); border: 1px solid transparent;
  border-radius: var(--pill); padding: 5px 7px 5px 13px;
}
.scope-chip.video { background: var(--card); border-color: rgba(124, 58, 237, 0.2); box-shadow: var(--shadow-xs); }
.scope-chip.err { background: #fef2f2; border-color: #fecaca; color: #b91c1c; }
/* The title gives up space last: it is what identifies the chip, so the stage
   beside it keeps its own width and the title takes whatever is left. */
.scope-chip-text {
  flex: 1 1 auto; min-width: 70px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scope-chip-icon { font-size: 11px; opacity: 0.8; }
.scope-chip-stage { font-family: var(--mono); font-size: 10.5px; color: var(--muted); flex-shrink: 0; }
.scope-chip .dot-spinner { width: 11px; height: 11px; border-width: 2px; flex-shrink: 0; }
/* Hidden rather than absent, so revealing it on hover doesn't reflow the row. */
.scope-chip-x {
  width: 17px; height: 17px; flex-shrink: 0; border-radius: 50%;
  display: grid; place-items: center; line-height: 1; font-size: 13px;
  color: inherit; opacity: 0; transition: opacity 0.15s, background 0.15s;
}
.scope-chip:hover .scope-chip-x, .scope-chip-x:focus-visible { opacity: 1; }
.scope-chip-x:hover { background: rgba(109, 40, 217, 0.16); }
.scope-chip.err .scope-chip-x:hover { background: rgba(185, 28, 28, 0.14); }

/* ---------- Clara's greeting + live steps ---------- */
.chat-greeting {
  font-size: 30px; font-weight: 800; letter-spacing: -0.04em; line-height: 1.18;
  color: var(--ink); margin-bottom: 4px;
}
/* Finished steps stay visible, dimmed, so the path taken is legible at a glance. */
.step-now { font-weight: 600; }
.step-log { margin-top: 7px; }
.step-done {
  font-size: 12.5px; color: var(--muted); padding-left: 20px; position: relative;
  line-height: 1.75;
}
.step-done::before { content: "✓"; position: absolute; left: 2px; color: var(--purple-500); }
.dot-spinner {
  width: 13px; height: 13px; border-radius: 50%; display: inline-block;
  border: 2px solid rgba(124, 58, 237, 0.18); border-top-color: var(--purple-600);
  animation: spin 0.8s linear infinite; vertical-align: -2px;
}

/* ---------- sidebar: recent chats ---------- */
/* The rail owns the space under the nav and is the only thing that scrolls, so
   the chat list can grow without pushing the account button off screen. */
.rail {
  margin-top: 22px; flex: 1; min-height: 0;
  display: flex; flex-direction: column; gap: 14px; overflow-y: auto;
}
.side-section { display: flex; flex-direction: column; min-height: 0; }
.side-section.recent { flex: 1; }
.side-section.collapsed { flex: 0 0 auto; }
.side-section.collapsed .recent-list { display: none; }
.side-head { display: flex; align-items: center; justify-content: space-between; padding: 0 12px 9px; }
.side-toggle {
  display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0;
  font-family: var(--mono);
  font-size: 9.5px; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--muted); padding: 2px 0; text-align: left;
  transition: color 0.2s;
}
.side-toggle:hover { color: var(--purple-600); }
.side-toggle .caret {
  width: 12px; height: 12px; flex-shrink: 0;
  transform: rotate(90deg); transition: transform 0.22s var(--ease);
}
.side-section.collapsed .side-toggle .caret { transform: rotate(0deg); }

.recent { display: flex; flex-direction: column; min-height: 0; }
.icon-btn.small {
  width: 22px; height: 22px; border-radius: 50%; font-size: 15px; line-height: 1;
  background: none; border: none; color: var(--faint);
}
.icon-btn.small:hover { background: var(--purple-100); color: var(--purple-700); }
.recent-list { overflow-y: auto; display: flex; flex-direction: column; gap: 1px; padding-right: 2px; }
.recent-empty { font-size: 12.5px; color: var(--muted); padding: 2px 12px; }
.recent-item {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 8px 8px 12px; border-radius: var(--r-sm); cursor: pointer;
  transition: background 0.2s;
}
.recent-item:hover { background: rgba(139, 92, 246, 0.07); }
.recent-item.on { background: var(--purple-100); }
.recent-title {
  flex: 1; min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recent-item:hover .recent-title { color: var(--ink); }
.recent-item.on .recent-title { color: var(--purple-700); font-weight: 700; }
.recent-del {
  opacity: 0; flex-shrink: 0; width: 18px; height: 18px; border-radius: 50%;
  color: var(--faint); font-size: 14px; line-height: 1;
}
.recent-item:hover .recent-del { opacity: 1; }
.recent-del:hover { background: #fef2f2; color: #dc2626; }

/* ---------- modals ---------- */
.modal {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(23, 16, 46, 0.4);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  display: grid; place-items: center; padding: 28px;
}
.modal-card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-xl); box-shadow: var(--shadow-lg);
  width: min(720px, 100%); max-height: 88vh; overflow-y: auto; padding: 28px;
  overscroll-behavior: contain;
}
.player-card { width: min(900px, 100%); padding: 18px; }
.modal-head {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
  padding-bottom: 16px; margin-bottom: 18px; border-bottom: 1px solid var(--border);
}
.modal-title { font-size: 18px; font-weight: 800; letter-spacing: -0.025em; line-height: 1.35; }
.icon-btn {
  width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; color: var(--muted); font-size: 13px;
  background: var(--bg); border: 1px solid var(--border);
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.icon-btn:hover { color: var(--ink); border-color: var(--line-strong); background: var(--purple-50); }
/* max-height keeps a 16:9 video inside a phone held sideways, where the card is
   wide and the viewport is barely 400px tall. */
#player { width: 100%; border-radius: var(--r-md); background: #000; aspect-ratio: 16/9; max-height: 68dvh; }

.detail-section { margin-top: 22px; }
.detail-section h4 {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--mono);
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.22em; font-weight: 500;
  color: var(--purple-600); margin-bottom: 12px;
}
.detail-section h4::before {
  content: ""; width: 28px; height: 1.5px; background: var(--grad-wide); flex-shrink: 0;
}
.detail-summary { line-height: 1.7; color: var(--ink); font-size: 14.5px; }
.moments { display: flex; flex-direction: column; gap: 6px; }
.moment {
  display: flex; gap: 14px; align-items: center; text-align: left;
  padding: 10px 14px; border-radius: var(--r-sm); border: 1px solid var(--border);
  transition: border-color 0.2s, background 0.2s;
}
.moment:hover { border-color: rgba(124, 58, 237, 0.42); background: var(--purple-50); }
.moment .ts {
  font-family: var(--mono); font-weight: 600; font-size: 11px;
  color: var(--purple-700); background: var(--purple-100);
  border-radius: var(--pill); padding: 4px 10px; flex-shrink: 0;
}
.moment .label { font-size: 13.5px; }
.watch-btn { margin-top: 6px; width: 100%; justify-content: center; display: flex; }

/* The phone layout lives in one block at the end of this file — see
   "small screens" there. */

/* ---------- login gate ---------- */
.login-card { width: min(400px, 100%); }
.login-card .picker-path input { font-family: inherit; }
body.hosted .local-only { display: none !important; }
.upload-input { display: none; }

/* ---------- auth screens ---------- */
/* Flex + margin:auto rather than place-items:center: a centred grid item taller
   than its container has its top clipped and unreachable, which is exactly what
   a phone keyboard does to this card. */
.auth-screen {
  position: fixed; inset: 0; z-index: 100;
  display: flex; overflow-y: auto; padding: 24px;
  padding-left: max(24px, env(safe-area-inset-left));
  padding-right: max(24px, env(safe-area-inset-right));
  padding-bottom: max(24px, env(safe-area-inset-bottom));
  /* The sign-in screen is the one full-bleed surface in the app, so it gets the
     site's atmosphere at full strength rather than the muted version behind
     the shell. */
  background:
    radial-gradient(ellipse 70% 50% at 15% 0%, rgba(139, 92, 246, 0.2), transparent 60%),
    radial-gradient(ellipse 60% 50% at 90% 100%, rgba(124, 58, 237, 0.16), transparent 60%),
    var(--bg);
}
.auth-card {
  margin: auto;
  width: min(420px, 100%);
  background: rgba(255, 255, 255, 0.82);
  -webkit-backdrop-filter: blur(24px) saturate(1.5); backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, 0.8);
  border-radius: var(--r-xl); padding: 36px 34px;
  box-shadow: var(--shadow-lg);
}
.auth-tagline { text-align: center; color: var(--muted); font-size: 14px; margin-bottom: 24px; }
.auth-social { display: flex; flex-direction: column; gap: 10px; }
.social-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 12px 16px; border-radius: var(--pill);
  border: 1px solid var(--border); background: var(--card);
  font-weight: 700; font-size: 14px; box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease);
}
.social-btn:hover { border-color: var(--line-strong); background: var(--purple-50); transform: translateY(-1px); }
.auth-divider {
  display: flex; align-items: center; gap: 14px; font-family: var(--mono);
  color: var(--faint); font-size: 10px; font-weight: 500; letter-spacing: 0.2em;
  text-transform: uppercase; margin: 18px 0 4px;
}
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.auth-form { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.auth-input {
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 13px 16px; outline: none; font-size: 14.5px; background: var(--card);
  /* The global reset covers input and select; a textarea wearing this class
     would otherwise render in the browser's monospace default. */
  font-family: inherit;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.auth-input::placeholder { color: var(--faint); }
.auth-input:focus { border-color: var(--purple-500); box-shadow: var(--ring); }
.auth-submit { width: 100%; margin-top: 6px; padding: 13px 24px; }
.auth-switch { text-align: center; font-size: 13px; color: var(--muted); margin-top: 18px; }
.link-btn { color: var(--purple-700); font-weight: 700; font-size: 13px; text-decoration: underline; }
.auth-error { color: #dc2626; font-size: 13px; text-align: center; margin-top: 12px; min-height: 18px; }

/* ---------- early access ---------- */
.auth-beta {
  margin: -8px 0 20px; padding: 11px 16px;
  border-radius: var(--r-md); background: var(--purple-50);
  border: 1px solid rgba(124, 58, 237, 0.16);
  text-align: center; font-size: 12.5px; color: var(--ink); line-height: 1.7;
}
.auth-beta .link-btn { font-size: 12.5px; white-space: nowrap; }
.early-card { text-align: center; }
.early-card .confirm-body { margin-bottom: 6px; }
.beta-pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 14px; border-radius: var(--pill);
  background: var(--card); color: var(--muted);
  border: 1px solid var(--border); box-shadow: var(--shadow-xs);
  font-family: var(--mono);
  font-size: 10.5px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase;
}
/* The live dot from the site's hero eyebrow. */
.beta-pill::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--purple-500); animation: pulse-dot 2.2s infinite;
}
.early-title { font-size: 23px; font-weight: 800; letter-spacing: -0.035em; margin: 14px 0 10px; }
/* An anchor, not a button — .btn-primary is written for one, so the display
   and the centring it inherits from a button have to be said here. */
.early-cta {
  display: flex; margin-top: 22px; text-align: center; font-size: 14.5px;
  text-decoration: none; padding: 13px 24px;
}
.early-alt { font-size: 12.5px; color: var(--muted); margin-top: 12px; line-height: 1.65; }
.early-alt a { color: var(--purple-700); font-weight: 700; text-decoration: underline; }
.early-back { margin-top: 18px; }

/* ---------- drive import ---------- */
.drive-preview {
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 13px 16px;
  max-height: 190px; overflow-y: auto; margin: 14px 0; font-size: 13px;
}
.drive-preview li { list-style: none; padding: 5px 0; display: flex; justify-content: space-between; gap: 12px; }
.drive-preview li span:last-child { font-family: var(--mono); font-size: 11.5px; color: var(--muted); flex-shrink: 0; }

/* ---------- signed-in user ---------- */
.user-box {
  display: flex; align-items: center; gap: 11px;
  margin-top: 10px; padding: 8px 12px 8px 8px;
  border: 1px solid var(--border); border-radius: var(--pill); background: var(--card);
  box-shadow: var(--shadow-xs);
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: inherit;
  transition: border-color 0.2s, background 0.2s;
}
.user-avatar {
  width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
  background: var(--grad);
  color: #fff; font-weight: 800; font-size: 13px;
  display: grid; place-items: center; text-transform: uppercase;
  box-shadow: var(--glow);
}
.user-meta { flex: 1; min-width: 0; }
.user-name { font-size: 12.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-email { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-box:hover { border-color: var(--line-strong); background: var(--purple-50); }
.user-chevron { width: 14px; height: 14px; flex-shrink: 0; color: var(--faint); }

/* Anchored by positionUserMenu() to whichever button opened it — the sidebar
   profile box, or the top-bar avatar on a phone. Fixed rather than absolute so
   it is never clipped by the sidebar's scroll, and it sits above the drawer
   (z-index 60) when opened from inside it. */
.user-menu {
  position: fixed; top: 0; left: 0; z-index: 70;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg); padding: 7px; overflow: hidden; min-width: 210px;
}
.user-menu-head { padding: 10px 12px 9px; border-bottom: 1px solid var(--border); margin-bottom: 5px; }
.user-menu-item {
  display: block; width: 100%; text-align: left; padding: 10px 12px;
  border: none; background: none; border-radius: var(--r-sm); cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--ink); font-family: inherit;
  transition: background 0.15s;
}
.user-menu-item:hover { background: var(--purple-50); color: var(--purple-700); }
.user-menu-item.danger { color: #dc2626; }
.user-menu-item.danger:hover { background: #fef2f2; color: #dc2626; }

/* ---------- personalization ---------- */
.settings-section { margin-bottom: 20px; }
.settings-heading {
  font-family: var(--mono);
  font-size: 10px; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--purple-600); margin-bottom: 8px;
}
.avatar-picker { display: flex; align-items: center; gap: 18px; margin-bottom: 22px; }
.avatar-drop {
  position: relative; width: 76px; height: 76px; border-radius: 50%; flex-shrink: 0;
  border: none; padding: 0; overflow: hidden; cursor: pointer;
  background: var(--grad);
  display: grid; place-items: center;
  box-shadow: var(--glow);
}
.avatar-initial { color: #fff; font-weight: 800; font-size: 28px; }
.avatar-preview { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.avatar-overlay {
  position: absolute; inset: auto 0 0 0; padding: 5px 0;
  background: rgba(20, 16, 45, 0.66); color: #fff;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
  opacity: 0; transition: opacity 0.2s;
}
.avatar-drop:hover .avatar-overlay, .avatar-drop:focus-visible .avatar-overlay { opacity: 1; }
.avatar-copy { min-width: 0; }
.link-btn {
  margin-top: 6px; padding: 0; background: none; border: none;
  font-size: 12.5px; font-weight: 700; color: var(--purple-700);
  text-decoration: underline; cursor: pointer;
}
/* The sidebar avatar shows an uploaded picture as a background so the letter
   underneath remains the fallback if the image ever fails to paint. */
.user-avatar.has-photo { background-size: cover; background-position: center; }

/* ---------- add-source dialog ---------- */
.field-label {
  display: block; font-family: var(--mono);
  font-size: 10px; font-weight: 500; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.18em; margin-bottom: 8px;
}
.field-hint { font-size: 12.5px; color: var(--muted); margin: -2px 0 9px; line-height: 1.6; }

/* ---------- topic color picker (Edit topic dialog) ---------- */
/* The rename input sits above and closes the gap; the label needs its own. */
#rename-modal .field-label { margin-top: 16px; }
#rename-modal .field-label[for="rename-input"] { margin-top: 0; }
.color-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 4px; }
.color-swatch {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: var(--sw); border: 2px solid transparent;
  display: grid; place-items: center;
  color: #fff; font-size: 12px; font-weight: 800;
  /* The inset ring keeps the palest swatches from melting into the card. */
  box-shadow: inset 0 0 0 1px rgba(31, 35, 48, 0.12);
  transition: transform 0.15s var(--ease), box-shadow 0.15s;
}
.color-swatch:hover { transform: scale(1.14); }
.color-swatch.on {
  box-shadow: inset 0 0 0 1px rgba(31, 35, 48, 0.12),
              0 0 0 2px var(--card), 0 0 0 4px var(--sw);
}
.drive-result {
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 13px 16px;
  margin-top: 16px; max-height: 200px; overflow-y: auto; font-size: 13px;
}
.drive-result ul { list-style: none; margin-top: 8px; }
.drive-result li { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; }
.drive-result li span:last-child { font-family: var(--mono); font-size: 11.5px; color: var(--muted); flex-shrink: 0; }

/* Dialog inputs should fill the card, not size to their default width. */
.source-card .auth-input { width: 100%; }
.source-card .confirm-actions { align-items: center; }

/* ---------- instructor dashboard ---------- */
/* Five reads, one instrument. Every chart here is one series plus context, so
   nothing is categorical: an ordinal ramp carries magnitude, emphasis carries
   "this one is the point", and the two-shade pair carries a two-way split.
   Gradients and --glow are chrome and never touch a data mark — a gradient
   inside a 10px bin is texture, and at full saturation everywhere there is
   nothing left to emphasise. */

.dash-topics { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.dash-chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px;
  border-radius: var(--pill); border: 1px solid var(--border); background: var(--card);
  font-size: 13px; font-weight: 600; color: var(--ink); cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease);
}
.dash-chip:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.dash-chip.active {
  border-color: rgba(124, 58, 237, 0.4); background: var(--purple-50);
  color: var(--purple-700); font-weight: 700;
}
.dash-chip .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--line-strong); }
.dash-chip .dot.on { background: var(--ok); box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.16); }

.dash-card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 26px 28px; margin-bottom: 16px;
  box-shadow: var(--shadow-xs);
}
.dash-card.lead { box-shadow: var(--shadow); }
.dash-head-card { padding: 16px 18px; }
/* Held while a regroup or a topic change is in flight: the frame stays, so
   nothing jumps and no skeleton flashes. */
.dash-busy { opacity: 0.55; pointer-events: none; }

.dash-view-tabs { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 16px; }
.dash-view-tab {
  border: 1px solid var(--border); background: var(--card); color: var(--muted);
  border-radius: var(--pill); padding: 9px 18px; font-size: 13px; font-weight: 700;
  cursor: pointer; box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.dash-view-tab:hover { border-color: var(--line-strong); color: var(--ink); }
.dash-view-tab.active {
  border-color: transparent; color: #fff; background: var(--grad); box-shadow: var(--glow);
}

/* ---- card anatomy ----
   Eyebrow names the tile; the h3 is the finding, computed, with its numbers in
   it; the readout is always on; the last line says what the read cannot see. */
.viz-head { display: flex; align-items: flex-start; justify-content: space-between;
            gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.viz-eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.22em; color: var(--purple-600);
}
.viz-eyebrow::before {
  content: ""; width: 28px; height: 2px; border-radius: 2px;
  background-image: var(--grad-wide);
}
.viz-chrome { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.dash-card h3 {
  font-size: 19px; font-weight: 800; letter-spacing: -0.025em; line-height: 1.35;
  max-width: 52ch; margin-bottom: 0;
}
.dash-card h3 + .viz-hero { margin-top: 14px; }
.dash-hint { color: var(--muted); font-size: 12.5px; margin-bottom: 16px;
             line-height: 1.6; max-width: 68ch; }
.dash-card h3 + .dash-hint { margin-top: 8px; }
.viz-note { margin: 20px 0 0; font-size: 12.5px; color: var(--muted);
            line-height: 1.6; max-width: 68ch; }
.dash-empty { text-align: center; padding: 38px 20px; }
.dash-empty p { color: var(--muted); font-size: 13.5px; margin: 10px auto 18px;
                max-width: 440px; line-height: 1.65; }

/* Hero: one per tile at most, and only where a single number is the answer.
   Proportional figures — tabular digits at 48px read loose. */
.viz-hero { font-size: 48px; font-weight: 800; letter-spacing: -0.045em;
            line-height: 1; color: var(--ink); font-variant-numeric: proportional-nums; }
.viz-hero-cap { margin-top: 8px; font-size: 13.5px; color: var(--muted);
                line-height: 1.5; max-width: 46ch; }

/* The permanent readout. On touch this *is* the tooltip, which is why it is
   never optional and never a transition. */
.viz-readout { margin: 10px 0 0; font-family: var(--mono); font-size: 11px;
               color: var(--muted); line-height: 1.5; min-height: 17px; }
.viz-readout b { color: var(--ink); font-weight: 700; }

.viz-tip {
  position: fixed; z-index: 90; pointer-events: none; opacity: 0;
  background: var(--ink); color: #fff; border-radius: var(--r-xs);
  padding: 8px 11px; font-size: 11.5px; line-height: 1.5; max-width: 260px;
  box-shadow: var(--shadow-lg); transition: opacity 0.12s;
}
.viz-tip.on { opacity: 1; }
.viz-tip b { font-family: var(--mono); font-size: 12px; }
.viz-tip span { color: rgba(255, 255, 255, 0.72); }

.viz-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: center;
              font-size: 11.5px; font-weight: 600; color: var(--muted); }
.viz-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
                margin-right: 6px; vertical-align: -1px; font-style: normal; }
.viz-legend b { font-family: var(--mono); font-size: 11px; color: var(--ink);
                font-weight: 600; margin-left: 5px; }
.viz-legend .ramp { display: inline-flex; gap: 2px; margin-right: 6px; vertical-align: -1px; }
.viz-legend .ramp i { margin: 0; }

/* Chart · Table switch. The chart is hidden while the table shows; never both. */
.viz-tw { display: inline-flex; border: 1px solid var(--border); border-radius: var(--pill);
          overflow: hidden; background: var(--card); }
.viz-tw button { padding: 5px 13px; font-size: 11.5px; font-weight: 700;
                 color: var(--muted); background: none; border: 0; cursor: pointer;
                 transition: background 0.2s, color 0.2s; }
.viz-tw button.active { background: var(--purple-50); color: var(--purple-700); }
/* Visible to a screen reader, not to the eye. Used by the table caption, which
   otherwise printed "The same values as the chart above." on screen. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
           overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.viz-table { width: 100%; border-collapse: collapse; margin-top: 14px;
             font-family: var(--mono); font-size: 11.5px; }
.viz-table th { text-align: left; font-size: 10px; text-transform: uppercase;
                letter-spacing: 0.12em; color: var(--muted); font-weight: 600;
                padding: 7px 10px 7px 0; border-bottom: 1px solid var(--line-strong); }
.viz-table td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--border);
                color: var(--ink); font-variant-numeric: tabular-nums; }
.viz-table td.t { font-family: var(--font); font-variant-numeric: normal; }
.viz-scroll { overflow-x: auto; }

/* A ranked bar: one track, segments as flex children, 2px of surface between
   them rather than a stroke. Square at the baseline, rounded at the data end. */
.rank-bar { display: flex; height: 12px; gap: 2px; background: none; }
.rank-bar .seg { min-width: 3px; border-radius: 0; }
.rank-bar .seg:last-child { border-radius: 0 4px 4px 0; }
.rank-bar .seg.a { background: var(--purple-700); }
.rank-bar .seg.b { background: var(--mark-2); }
.rank-bar .seg.c { background: var(--track); }

.meter { height: 8px; border-radius: var(--pill); background: var(--heat-1);
         overflow: hidden; }
.meter > i { display: block; height: 100%; border-radius: var(--pill);
             background: var(--purple-700); }

/* ---- the at-a-glance strip ----
   Four tiles, each a button naming the verb it performs. Replaces three counts
   that changed nothing a professor does on Monday. */
.glance { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
          gap: 10px; margin-top: 18px; }
.glance-tile {
  display: block; text-align: left; padding: 15px 16px 14px;
  border-radius: var(--r-md); background: var(--bg); border: 1px solid var(--border);
  cursor: pointer; color: inherit;
  transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease);
}
.glance-tile:hover { border-color: rgba(124, 58, 237, 0.4); background: var(--card);
                     transform: translateY(-1px); }
.glance-verb { display: block; font-family: var(--mono); font-size: 9.5px;
               font-weight: 600; text-transform: uppercase; letter-spacing: 0.18em;
               color: var(--purple-600); margin-bottom: 9px; }
.glance-n { display: block; font-size: 27px; font-weight: 800; letter-spacing: -0.04em;
            line-height: 1.05; color: var(--ink); font-variant-numeric: proportional-nums; }
.glance-n.small { font-size: 17px; letter-spacing: -0.02em; line-height: 1.3; }
.glance-l { display: block; margin-top: 7px; font-size: 11.5px; color: var(--muted);
            line-height: 1.45; }
/* The three old headline counts, kept as one caption. */
.dash-caption { margin-top: 12px; font-family: var(--mono); font-size: 10.5px;
                color: var(--muted); letter-spacing: 0.01em; }

.dash-link-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.dash-link-row input { flex: 1; min-width: 220px; font-size: 12.5px; font-family: var(--mono); }

/* A tile's own header row: title on the left, its one control on the right. */
.cluster-bar { display: flex; align-items: center; justify-content: space-between;
               gap: 10px; margin-bottom: 8px; }
.cluster-bar h3 { margin: 0; }

/* ---------- 1. confusion timeline ----------
   One strip per video in library order, including the ones nobody asked about —
   silence is the row you cannot get anywhere else. Two lanes: an answer landed
   here, and a question landed here that the video did not answer. Colour is a
   quantized ordinal ramp, so 1 and 3 read apart without pretending to be
   continuous. */
.tl-scroll { margin-top: 4px; }
.tl-grid { display: flex; flex-direction: column; gap: 7px; }
.tl-row { display: grid; grid-template-columns: minmax(120px, 210px) 1fr auto;
          align-items: center; gap: 14px; }
.tl-row.silent .tl-name { color: var(--muted); }
.tl-name { min-width: 0; font-size: 12.5px; font-weight: 600; line-height: 1.35;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-name .pos { font-family: var(--mono); font-size: 10.5px; color: var(--muted);
                margin-right: 7px; }
.tl-strip {
  position: relative; display: block; width: 100%; padding: 0; border: 0;
  background: none; cursor: pointer; border-radius: 3px;
}
.tl-strip:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--purple-700); }
.tl-lane { display: flex; gap: 2px; }
.tl-lane + .tl-lane { margin-top: 2px; }
.tl-cell { flex: 1 1 0; height: 13px; border-radius: 2px; background: var(--bg); }
.tl-lane.gaps .tl-cell { height: 5px; }
.tl-cell.h1 { background: var(--heat-1); }
.tl-cell.h2 { background: var(--heat-2); }
.tl-cell.h3 { background: var(--heat-3); }
.tl-cell.h4 { background: var(--heat-4); }
.tl-lane.gaps .tl-cell.g { background: var(--mark-2); }
/* Past the end of a shorter video: the shared clock has to show that the strip
   stopped, not that the video went quiet. */
.tl-cell.past { background: none; }
.tl-cell.on, .tl-cell.at { box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--purple-700); }
/* The featured run, bracketed under the strip it belongs to. Its own positioned
   wrapper — .card is isolation:isolate and a blanket positioning rule here
   would drop the edit-mode tick into the title. */
.tl-runwrap { display: block; position: relative; height: 9px; }
.tl-run { position: absolute; top: 3px; height: 2px; border-radius: 2px;
          background: var(--purple-700); }
.tl-tail { display: flex; align-items: center; gap: 10px; justify-content: flex-end; }
.tl-count { font-family: var(--mono); font-size: 10.5px; color: var(--muted);
            white-space: nowrap; text-align: right; min-width: 40px; }
.tl-count.none { color: var(--faint); }
/* Questions per minute against the course median. Position carries the value,
   so nothing here is coloured by it. */
.tl-dens { position: relative; display: block; width: 46px; height: 13px; flex-shrink: 0; }
.tl-dens > i { position: absolute; font-style: normal; }
.tl-dens .med { top: 2px; width: 1px; height: 9px; background: var(--line-strong); }
.tl-dens .dot { top: 5px; width: 5px; height: 5px; border-radius: 50%;
                margin-left: -2px; background: var(--track); }
.tl-dens .dot.over { background: var(--purple-700); }
.tl-axis { display: flex; justify-content: space-between; margin: 9px 0 0;
           padding-left: calc(min(210px, 34%) + 14px); font-family: var(--mono);
           font-size: 10.5px; color: var(--muted); }
.tl-feature { border: 1px solid var(--border); border-radius: var(--r-md);
              background: var(--bg); padding: 14px 16px; margin-bottom: 14px; }
.tl-feature + .tl-feature { margin-top: -6px; }
.tl-feature-head { display: flex; justify-content: space-between; align-items: baseline;
                   gap: 12px; flex-wrap: wrap; }
.tl-feature-title { font-size: 13.5px; font-weight: 700; letter-spacing: -0.015em; }
.tl-feature-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.tl-feature-q .who { font-family: var(--mono); font-size: 10px; font-weight: 600;
                     text-transform: uppercase; letter-spacing: 0.12em;
                     color: var(--purple-600); margin-right: 7px; }
.tl-feature-q { margin-top: 9px; font-size: 12.5px; color: var(--muted);
                line-height: 1.55; padding-left: 12px;
                border-left: 2px solid var(--purple-100); overflow-wrap: anywhere; }

/* The drill-down opens under its own strip and re-renders only itself. */
.tl-drill { grid-column: 1 / -1; margin: 2px 0 8px; padding: 2px 15px;
            background: var(--bg); border: 1px solid var(--border);
            border-radius: var(--r-md); }
.tl-q { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 12px;
        padding: 12px 0; border-top: 1px solid var(--border); }
.tl-q:first-child { border-top: 0; }
.tl-q-text { flex: 1 1 100%; font-size: 13px; font-weight: 600; line-height: 1.55;
             overflow-wrap: anywhere; }
.tl-q-meta { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
.tl-jump, .gap-play {
  display: inline-flex; align-items: center; flex-shrink: 0;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  color: var(--purple-700); background: var(--purple-50);
  border: 1px solid rgba(124, 58, 237, 0.16); border-radius: var(--pill);
  padding: 5px 13px; cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.tl-jump { margin-left: auto; }
.tl-jump:hover, .gap-play:hover { background: var(--purple-500);
                                  border-color: var(--purple-500); color: #fff; }
.tag-gap { font-family: var(--mono); font-size: 10px; font-weight: 600;
           text-transform: uppercase; letter-spacing: 0.1em; color: var(--purple-700);
           background: var(--purple-50); border-radius: var(--pill); padding: 2px 9px; }

/* ---------- 2. gap list ----------
   Two sections, because they are two different decisions: off-map means record
   new material, near-miss means extend the lecture that already comes close. */
.gap-section-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
                    margin: 22px 0 4px; }
.gap-section-head:first-of-type { margin-top: 6px; }
.gap-section-head h4 { font-size: 13.5px; font-weight: 800; letter-spacing: -0.015em; }
.gap-section-head .sub { font-size: 12px; color: var(--muted); }
.gap-row { border-top: 1px solid var(--border); padding: 15px 0; }
.gap-row:first-child { border-top: 0; }
.gap-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.gap-label { font-size: 13.5px; font-weight: 700; letter-spacing: -0.015em;
             overflow-wrap: anywhere; }
.gap-count { font-family: var(--mono); font-size: 11px; color: var(--muted);
             flex-shrink: 0; white-space: nowrap; }
/* When it was raised: a gap hit last night and one hit once in August are not
   the same row, and they used to look identical. */
.gap-when { margin-top: 5px; font-family: var(--mono); font-size: 11px;
            color: var(--muted); }
.gap-samples { display: flex; flex-direction: column; gap: 7px; margin-top: 11px; }
.gap-sample { font-size: 12.5px; color: var(--muted); line-height: 1.55;
              padding-left: 12px; border-left: 2px solid var(--purple-100);
              overflow-wrap: anywhere; }
.gap-near { margin-top: 11px; padding: 10px 13px; font-size: 12px; line-height: 1.6;
            color: var(--muted); background: var(--purple-50); border-radius: var(--r-xs);
            display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gap-near strong { color: var(--purple-700); font-weight: 700; }
.gap-near .txt { flex: 1 1 240px; min-width: 0; }
/* Where in that video the closest moment sits — the same strip as tile 1, one
   marker, so "closest" is shown rather than only named. */
.gap-mini { position: relative; height: 6px; border-radius: 3px; background: var(--card);
            border: 1px solid var(--border); flex: 1 1 120px; min-width: 90px; }
.gap-mini > i { position: absolute; top: -1px; width: 3px; height: 6px;
                border-radius: 2px; background: var(--purple-700); }
.gap-fold { margin-top: 18px; }
.gap-fold-list { margin-top: 10px; display: flex; flex-direction: column; gap: 9px; }
.gap-fold-row { font-size: 12.5px; color: var(--muted); line-height: 1.55;
                display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.gap-fold-row .who { font-family: var(--mono); font-size: 10.5px; flex-shrink: 0; }
.gap-points { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.gap-points h4 { font-size: 13px; font-weight: 800; margin-bottom: 4px; }
.gap-point-row { display: flex; align-items: center; gap: 12px; padding: 7px 0;
                 font-size: 12.5px; }
.gap-point-row .n { font-family: var(--mono); font-size: 11px; color: var(--muted);
                    flex-shrink: 0; }
.gap-point-row .t { flex: 1 1 auto; min-width: 0; overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }

/* ---------- 3. questions ---------- */
.feed-group-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
                   margin: 22px 0 4px; }
.feed-group-head:first-of-type { margin-top: 6px; }
.feed-group-head h4 { font-size: 13.5px; font-weight: 800; letter-spacing: -0.015em; }
.feed-group-head .sub { font-size: 12px; color: var(--muted); }
.feed-row { border-top: 1px solid var(--border); padding: 15px 0; }
.feed-row:first-child { border-top: 0; }
.feed-eyebrow { font-family: var(--mono); font-size: 10px; font-weight: 500;
                text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted);
                margin-bottom: 6px; }
.feed-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.feed-q { font-size: 14px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.5;
          overflow-wrap: anywhere; }
.feed-count { font-family: var(--mono); font-size: 11px; color: var(--muted);
              flex-shrink: 0; white-space: nowrap; }
.feed-bar { margin-top: 10px; max-width: 320px; }
.feed-src { margin-top: 9px; font-size: 12px; color: var(--muted); line-height: 1.55; }
.feed-src strong { color: var(--ink); font-weight: 700; }
.feed-more {
  display: inline-flex; align-items: center; margin-top: 10px;
  font-size: 11.5px; font-weight: 700; color: var(--muted);
  background: var(--card); border: 1px solid var(--border); border-radius: var(--pill);
  padding: 4px 13px; cursor: pointer;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.feed-more:hover { border-color: rgba(124, 58, 237, 0.4); background: var(--purple-50);
                   color: var(--purple-700); }
.feed-variants { display: flex; flex-direction: column; gap: 7px; margin-top: 10px; }
.feed-variant { font-size: 12.5px; color: var(--muted); line-height: 1.55;
                padding-left: 12px; border-left: 2px solid var(--purple-100);
                overflow-wrap: anywhere; }

/* ---------- 4. trust ----------
   The outcome split as one part-to-whole, the answered-only confidence beside
   it, and the instructor's own progress — the only calibration on this page
   that is not the app scoring itself. */
.trust-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
              gap: 18px 26px; align-items: start; }
.trust-block h4 { font-size: 12.5px; font-weight: 700; color: var(--ink); margin-bottom: 9px; }
.trust-bar { display: flex; height: 14px; gap: 2px; margin-bottom: 10px; }
.trust-bar .seg { min-width: 3px; border-radius: 0; }
.trust-bar .seg:first-child { border-radius: 4px 0 0 4px; }
.trust-bar .seg:last-child { border-radius: 0 4px 4px 0; }
.trust-bar .seg:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--purple-700); }
.trust-bar .seg.answered { background: var(--purple-700); }
.trust-bar .seg.refused { background: var(--mark-2); }
.trust-bar .seg.other { background: var(--track); }
.trust-bar .seg.unknown { background: var(--residue); }
.trust-bar .seg.high { background: var(--conf-high); }
.trust-bar .seg.mid { background: var(--conf-mid); }
.trust-bar .seg.low { background: var(--conf-low); }
.trust-meterline { display: flex; justify-content: space-between; align-items: baseline;
                   gap: 10px; font-family: var(--mono); font-size: 11px;
                   color: var(--muted); margin-bottom: 7px; }
.trust-meterline b { color: var(--ink); font-weight: 700; }

/* Answer review: a bounded lane, sorted by the evidence rather than by the
   confidence the answer awarded itself. */
.rev-tabs { display: flex; gap: 6px; }
.rev-tab { border: 1px solid var(--border); background: var(--card);
           border-radius: var(--pill); padding: 6px 14px; font-size: 12px;
           font-weight: 700; cursor: pointer; color: var(--muted); }
.rev-tab:hover { border-color: var(--line-strong); color: var(--ink); }
.rev-tab.active { border-color: rgba(124, 58, 237, 0.4); background: var(--purple-50);
                  color: var(--purple-700); }
.rev-card { border-top: 1px solid var(--border); padding: 16px 0; }
.rev-q { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
         font-size: 11.5px; color: var(--muted); margin-bottom: 6px; }
.rev-student { font-weight: 700; color: var(--ink); }
.rev-when { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
/* Confidence is ordinal, not status: low is "read this one", never "bad". */
.rev-conf { font-weight: 700; text-transform: capitalize; color: var(--muted); }
.rev-conf i { display: inline-block; width: 8px; height: 8px; border-radius: 50%;
              margin-right: 5px; font-style: normal; }
.rev-conf.conf-high i { background: var(--conf-high); }
.rev-conf.conf-medium i { background: var(--conf-mid); }
.rev-conf.conf-low i { background: var(--conf-low); }
/* Not a refusal: the app answered about itself, or the row predates outcome
   recording. Quiet on purpose — it is a label, not an alarm, and never red. */
.rev-outcome.rev-out-quiet { background: var(--bg); color: var(--muted);
                             box-shadow: inset 0 0 0 1px var(--border); }
.rev-outcome { font-family: var(--mono); font-size: 10px; font-weight: 600;
               text-transform: uppercase; letter-spacing: 0.1em; padding: 2px 9px;
               border-radius: var(--pill); background: var(--purple-50);
               color: var(--purple-700); }
/* Why this card is in the queue, in the terms the instructor can judge. */
.rev-why { font-family: var(--mono); font-size: 10.5px; color: var(--muted);
           margin-bottom: 8px; }
.rev-badge { font-weight: 700; border-radius: var(--pill); padding: 2px 10px; font-size: 10.5px;
             text-transform: uppercase; letter-spacing: 0.06em; }
.rev-badge.rev-ok { background: var(--ok-bg); color: var(--ok-ink); }
.rev-badge.rev-flag { background: var(--bad-bg); color: var(--bad-ink); }
.rev-badge.rev-fix { background: var(--warn-bg); color: var(--warn-ink); }
.rev-question { font-size: 14.5px; font-weight: 700; letter-spacing: -0.015em; margin-bottom: 8px; }
.rev-answer { font-size: 13px; color: var(--ink); line-height: 1.65;
              max-height: 130px; overflow-y: auto; background: var(--bg);
              border: 1px solid var(--border);
              border-radius: var(--r-md); padding: 12px 15px; }
.rev-answer p { margin: 0 0 7px; } .rev-answer p:last-child { margin: 0; }
.rev-answer ul, .rev-answer ol { margin: 0 0 7px 18px; }
.rev-answer h3 { font-size: 13px; margin: 6px 0 4px; }
.rev-cites { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.cite-mini { border: 1px solid var(--border); background: var(--card);
             border-radius: var(--pill); padding: 4px 12px; font-size: 11.5px;
             cursor: pointer; color: var(--muted); max-width: 100%;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
             transition: border-color 0.2s, color 0.2s, background 0.2s; }
.cite-mini:hover { border-color: rgba(124, 58, 237, 0.4); color: var(--purple-700);
                   background: var(--purple-50); }
.rev-correction { margin-top: 10px; font-size: 12.5px; background: var(--note-bg);
                  border: 1px solid var(--note-line); border-radius: var(--r-md);
                  padding: 10px 14px; color: var(--note-ink); line-height: 1.6; }
.rev-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.rev-correct { margin-top: 10px; display: flex; flex-direction: column; gap: 9px; }
/* 16px: below it iOS zooms the layout on focus and never zooms back. */
.rev-correct textarea { border: 1px solid var(--border); border-radius: var(--r-md);
                        padding: 11px 14px; font: inherit; font-size: 16px;
                        resize: vertical; background: var(--card); outline: none; }
.rev-correct textarea:focus { border-color: var(--purple-500); box-shadow: var(--ring); }
.rev-correct .btn-primary { align-self: flex-start; }
.rev-more { margin-top: 14px; }

/* ---------- 5. timing and participation ----------
   The day axis is continuous: plotting only the days that had questions put a
   27-day gap and a single night side by side at the same width. */
.tim-card + .tim-card { margin-top: 16px; }
/* A button, so the strip is one tab stop: the reset keeps it looking like the
   chart it is, and .tim-col.on becomes the focus indicator. */
.tim-chart { display: flex; align-items: flex-end; gap: 2px; height: 132px;
             padding: 0 0 22px; position: relative; overflow-x: auto;
             appearance: none; border: 0; background: none; font: inherit;
             color: inherit; text-align: left; width: 100%; cursor: default; }
.tim-chart:focus-visible { outline: none; }
.tim-col { flex: 1 1 0; max-width: 24px; min-width: 3px; height: 100%;
           display: flex; flex-direction: column; justify-content: flex-end;
           position: relative; }
.tim-col > i { display: block; width: 100%; border-radius: 4px 4px 0 0;
               background: var(--purple-700); font-style: normal; }
.tim-col.zero > i { background: none; }
.tim-col.on > i { box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--purple-700); }
.tim-axis { display: flex; gap: 2px; font-family: var(--mono); font-size: 10.5px;
            color: var(--muted); margin-top: -18px; }
.tim-axis span { flex: 1 1 0; max-width: 24px; min-width: 3px; text-align: center;
                 white-space: nowrap; overflow: visible; }
.tim-baseline { height: 1px; background: var(--line-strong); margin-top: 0; }
/* 7 x 24, gated: three weekdays and one hour would light 4 of 168 cells. */
.punch { display: grid; grid-template-columns: 34px repeat(24, 1fr); gap: 2px;
         align-items: center; }
.punch .rowlab, .punch .collab { font-family: var(--mono); font-size: 10px;
                                 color: var(--muted); text-align: center; }
.punch .rowlab { text-align: right; padding-right: 6px; }
.punch .cell { height: 14px; border-radius: 2px; background: var(--bg); }
.punch .cell.h1 { background: var(--heat-1); }
.punch .cell.h2 { background: var(--heat-2); }
.punch .cell.h3 { background: var(--heat-3); }
.punch .cell.h4 { background: var(--heat-4); }

/* Participation: bins in the chart, names only in the lists. A chart that
   reveals who is in a bin is a leaderboard that has hidden its axis labels. */
.part-rows { display: flex; flex-direction: column; gap: 9px; }
.part-row { display: grid; grid-template-columns: 74px 1fr 34px; align-items: center;
            gap: 12px; font-size: 12.5px; }
.part-row .lab { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.part-row .n { font-family: var(--mono); font-size: 11px; color: var(--ink);
               font-weight: 600; text-align: right; }
.part-row .track { height: 12px; background: var(--bg); border-radius: 0 4px 4px 0; }
/* No minimum width: an empty bin must draw nothing, or the chart says one
   student is in a bucket that holds none. */
/* The accent lands on the bin the card's title names — the students who never
   asked — and the rest is context. Counts stay in --ink beside every bar. */
.part-row .track > i { display: block; height: 100%; border-radius: 0 4px 4px 0;
                       background: var(--track); font-style: normal; }
.part-row.zero .track > i { background: var(--purple-700); }

.silent-head { font-size: 13.5px; font-weight: 700; letter-spacing: -0.015em;
               margin: 22px 0 4px; }
.silent-sub { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.silent-list { display: flex; flex-direction: column; }
.silent-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 14px;
              padding: 10px 0; border-top: 1px solid var(--border); font-size: 12.5px; }
.silent-row:first-child { border-top: 0; }
.silent-row .meta { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.silent-row .mail { margin-left: auto; }
.silent-note { margin-top: 12px; font-size: 12.5px; color: var(--muted); line-height: 1.6; }

/* ---------- the printable sheet ---------- */
.sheet-scrim { position: fixed; inset: 0; z-index: 80; background: rgba(24, 16, 60, 0.36);
               display: flex; overflow-y: auto; padding: 32px 20px; }
.sheet { margin: auto; width: min(720px, 100%); background: var(--card);
         border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 30px 34px; }
.sheet h3 { font-size: 19px; font-weight: 800; letter-spacing: -0.025em; margin-bottom: 4px; }
.sheet-sub { font-size: 12.5px; color: var(--muted); margin-bottom: 20px; }
.sheet-body { font-size: 13px; line-height: 1.7; color: var(--ink);
              white-space: pre-wrap; font-family: var(--font);
              background: var(--bg); border: 1px solid var(--border);
              border-radius: var(--r-md); padding: 18px 20px; max-height: 52vh;
              overflow-y: auto; overflow-wrap: anywhere; }
.sheet-actions { display: flex; gap: 8px; margin-top: 18px; flex-wrap: wrap; }
.sheet-note { margin-top: 14px; font-size: 12px; color: var(--muted); }

/* ---------- connector picker ---------- */
/* Two equal cards. They collapse to one column below 420px rather than at the
   phone breakpoint: the constraint is the card's own width inside the dialog,
   which is the same on a narrow desktop window. */
.source-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.source-choice {
  display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 13px 15px; border-radius: var(--r-md); background: var(--card);
  border: 1px solid var(--border); color: var(--ink);
  transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
.source-choice svg { flex-shrink: 0; }
.source-choice .choice-text { display: block; min-width: 0; }
.source-choice .choice-name { display: block; font-size: 14px; font-weight: 700; }
.source-choice .choice-sub {
  display: block; font-size: 12px; color: var(--muted); margin-top: 2px;
  /* Titles like "Uses YouTube's own captions" must not widen the card and push
     the grid out of the dialog. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.source-choice:hover { border-color: var(--line-strong); box-shadow: var(--shadow-xs); }
.source-choice.selected {
  border-color: var(--purple-500); background: var(--purple-50);
  box-shadow: 0 0 0 1px rgba(139, 92, 246, 0.3);
}
/* The selected card is already outlined in purple, so the focus ring has to be
   something else or keyboard focus is invisible on the one that matters. */
.source-choice:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Connectors that exist as a plan, not a feature. Dimmed but clickable — the
   click explains what's missing — and never highlighted as selectable. */
.source-choice.soon { opacity: 0.5; cursor: default; }
.source-choice.soon:hover { border-color: var(--border); box-shadow: none; }
.soon-badge {
  display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: var(--pill);
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  vertical-align: 1px;
  color: var(--muted); border: 1px solid var(--border); background: var(--bg);
}

@media (max-width: 420px) {
  .source-picker { grid-template-columns: 1fr; }
}

/* Same box as #player: exactly one of the two is visible at a time, so they
   must reserve the same space or the card jumps between video kinds. */
#player-embed {
  width: 100%; border: 0; border-radius: var(--r-md); background: #000;
  aspect-ratio: 16/9; max-height: 68dvh;
}

/* The shell stays hidden until the session is confirmed, so a script error
   fails closed instead of exposing the app. */
body.booting .app { visibility: hidden; }

/* ---------- empty states ---------- */
.hero { display: grid; place-items: center; min-height: calc(100vh - 160px); }
.hero-card {
  text-align: center; max-width: 560px;
  background: rgba(255, 255, 255, 0.86);
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  border: 1px solid var(--border);
  border-radius: var(--r-xl); padding: 48px 44px; box-shadow: var(--shadow-lg);
}
.hero-icon {
  width: 60px; height: 60px; margin: 0 auto 20px;
  display: grid; place-items: center; font-size: 27px;
  border-radius: var(--r-lg);
  background: linear-gradient(135deg, var(--purple-50), var(--purple-100));
  border: 1px solid rgba(124, 58, 237, 0.2);
}
.hero-card h1 { font-size: 26px; font-weight: 800; letter-spacing: -0.04em; margin-bottom: 12px; }
.hero-card p { color: var(--muted); line-height: 1.7; margin-bottom: 28px; font-size: 14.5px; }
.hero-note { margin-top: 18px; font-size: 12px; color: var(--muted); line-height: 1.6; }
.steps { list-style: none; text-align: left; margin: 0 0 28px; display: flex;
         flex-direction: column; gap: 14px; }
.steps li { display: flex; gap: 14px; align-items: flex-start;
            font-size: 14px; line-height: 1.6; color: var(--ink); }
.step-n {
  flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%;
  background: var(--grad); color: #fff;
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  display: grid; place-items: center;
  box-shadow: var(--glow);
}
.hero-card .btn-primary { padding: 13px 28px; }

.confirm-card .auth-input { width: 100%; }
/* Rename affordance on a topic card — visible on hover or keyboard focus. */
.topic-rename {
  opacity: 0; flex-shrink: 0; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; color: var(--muted);
  border: 1px solid transparent; transition: opacity .2s, color .2s, border-color .2s, background .2s;
}
.topic-rename svg { width: 15px; height: 15px; }
.topic-card:hover .topic-rename, .topic-rename:focus { opacity: 1; }
.topic-rename:hover { color: var(--purple-700); border-color: var(--border); background: var(--purple-50); }

/* Stop control inside the indexing banner */
.banner { gap: 12px; }
.banner-text { flex: 1; }
.banner-stop { flex-shrink: 0; padding: 6px 16px; font-size: 12.5px; }

.drive-banner { background: #fffbeb; border-color: #fde68a; color: #92400e; }
.drive-ok { background: #f0fdf4; border-color: #bbf7d0; color: #166534; }

/* ---------- motion ----------
   Entrances only, and short. Anything that animates on every poll would be
   noise: the KB re-renders several times a second while indexing, so cards
   animate on view entry rather than on each repaint. */
@keyframes fade-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop-in { from { opacity: 0; transform: scale(0.97) translateY(8px); } to { opacity: 1; transform: none; } }

.view:not(.hidden) { animation: fade-in 0.24s var(--ease) both; }
.chat-empty { animation: fade-up 0.4s var(--ease) both; }
.chat-greeting { animation: fade-up 0.5s var(--ease) both; }
.suggestions .chip { animation: fade-up 0.45s var(--ease) both; }
.suggestions .chip:nth-child(1) { animation-delay: 0.06s; }
.suggestions .chip:nth-child(2) { animation-delay: 0.12s; }
.suggestions .chip:nth-child(3) { animation-delay: 0.18s; }

.msg { animation: fade-up 0.3s var(--ease) both; }
.step-done { animation: fade-in 0.2s ease both; }

.modal:not(.hidden) { animation: fade-in 0.18s ease both; }
.modal:not(.hidden) .modal-card { animation: pop-in 0.26s var(--ease) both; }
.user-menu:not(.hidden) { animation: pop-in 0.18s var(--ease) both; }
.picker-menu:not(.hidden) { animation: pop-in 0.18s var(--ease) both; }
.toast { animation: fade-up 0.26s var(--ease) both; }

/* Interaction polish: the whole surface should feel like it responds. */
.card { transition: transform 0.28s var(--ease), box-shadow 0.28s var(--ease), border-color 0.28s; }
.card:not(.removed):hover { transform: translateY(-3px); }
.topic-card { transition: transform 0.28s var(--ease), box-shadow 0.28s var(--ease), border-color 0.28s; }
.topic-card:hover { transform: translateY(-3px); }
.nav-item:active { transform: scale(0.98); }
.progress-fill, .topic-progress-fill { transition: width 0.5s var(--ease); }

/* Someone who has asked their system to calm down means it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ============================================================
   Touch input
   Independent of width: a hover-only affordance is unreachable with a finger
   however big the screen is, and a :hover lift sticks after a tap until you
   tap something else.
   ============================================================ */
@media (hover: none) {
  .topic-rename, .recent-del, .avatar-overlay, .scope-chip-x { opacity: 1; }
  .card:hover, .card:not(.removed):hover, .topic-card:hover,
  .source-row.linked:hover, .chip.suggestion:hover, .btn-primary:hover,
  .btn-secondary:hover, .dash-chip:hover, .social-btn:hover {
    transform: none;
  }
  .card:hover, .topic-card:hover, .source-row.linked:hover { box-shadow: var(--shadow-xs); }
  .card:hover::before { opacity: 0; }
  /* Data marks no longer carry a glow at all, so there is nothing to cancel;
     .glance-tile is the one dashboard mark that still lifts. */
  .glance-tile:hover { transform: none; }
  .near-chip:hover { box-shadow: var(--shadow-xs); }
  /* Everything a hover would have said lives in .viz-readout on touch, and a
     tablet is hover:none at 1024px — well outside the phone query. */
  .viz-tip { display: none; }
}

/* Anything you have to hit with a fingertip. */
@media (pointer: coarse) {
  input[type="checkbox"], .card-check { width: 20px; height: 20px; }
  .card.selectable { padding-left: 54px; }
  .recent-del { width: 30px; height: 30px; display: grid; place-items: center; font-size: 17px; }
  .scope-chip-x { width: 24px; height: 24px; font-size: 16px; }
  .scope-chip { padding: 5px 6px 5px 13px; }
  .icon-btn.small { width: 30px; height: 30px; font-size: 19px; }
  .topic-rename { width: 38px; height: 38px; }
  .icon-action { width: 38px; height: 38px; }
  .cite { min-width: 22px; height: 22px; }
  .tl-jump, .feed-more, .viz-tw button, .gap-play, .cite-mini,
  .rev-actions .btn-secondary { min-height: 44px; }
  /* The strip is the tap band; the marks keep their measured heights, and
     binAt() reads only rect.left/width so growing it cannot shift a bin. */
  .tl-strip { min-height: 44px; display: grid; align-content: center; }
}

/* ============================================================
   Phone shell
   The sidebar becomes a drawer, a top bar takes over its navigation, and the
   shell owns the viewport so only the content column scrolls.

   The condition is "narrow, or short and touched": a phone on its side is
   ~800x390, which is wide enough for the desktop sidebar but nowhere near tall
   enough for it — and the fixed column leaves almost nothing for the answer.
   Height alone would catch a short desktop window, hence pointer.
   MOBILE_MQ in app.js repeats this query; change both together.
   ============================================================ */
.sort.wide { width: 100%; }
.auth-input.tall { min-height: 76px; resize: vertical; font-size: 13.5px; line-height: 1.55; padding: 12px 14px; }

.topbar { display: none; }
.nav-scrim { display: none; }
.nav-close { display: none; }

@media (max-width: 760px), (max-height: 480px) and (pointer: coarse) {
  /* Only .main scrolls. Without this the whole document scrolls under the
     browser chrome and the composer ends up somewhere below the fold. */
  html, body { height: 100%; overflow: hidden; overscroll-behavior: none; }

  .app {
    flex-direction: column;
    height: var(--app-h, 100dvh);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
  }
  .main { flex: 1; min-height: 0; }
  /* The grid is drawn for a desktop-sized ground; at phone size it only makes
     the shell busy behind small type. */
  .grid-overlay { display: none; }

  /* ---- top bar ---- */
  .topbar {
    display: flex; align-items: center; gap: 6px; flex-shrink: 0;
    padding: 8px; padding-top: calc(8px + env(safe-area-inset-top));
    background: rgba(255, 255, 255, 0.85);
    -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
    border-bottom: 1px solid var(--border);
    position: relative; z-index: 20;
  }
  .topbar-title { flex: 1; text-align: center; font-weight: 800; font-size: 16px; letter-spacing: -0.03em; }
  .topbar-title span {
    background: var(--grad); -webkit-background-clip: text; background-clip: text;
    color: transparent;
  }
  .topbar-btn {
    width: 42px; height: 42px; flex-shrink: 0; border-radius: var(--r-sm);
    display: grid; place-items: center; color: var(--ink);
  }
  .topbar-btn svg { width: 22px; height: 22px; }
  .topbar-btn:active { background: var(--purple-50); color: var(--purple-700); }

  /* ---- sidebar as a drawer ---- */
  .sidebar {
    position: fixed; top: 0; left: 0; z-index: 60;
    width: min(84vw, 300px); height: var(--app-h, 100dvh);
    background: rgba(255, 255, 255, 0.96);
    padding-top: calc(20px + env(safe-area-inset-top));
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
    padding-left: calc(12px + env(safe-area-inset-left));
    padding-right: 12px;
    box-shadow: 0 0 54px rgba(24, 16, 60, 0.24);
    transform: translateX(-102%);
    transition: transform 0.26s var(--ease);
    overscroll-behavior: contain;
  }
  body.nav-open .sidebar { transform: none; }
  .nav-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(23, 16, 46, 0.42);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    opacity: 0; pointer-events: none; transition: opacity 0.24s;
  }
  body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }
  .nav-close {
    display: grid; place-items: center; margin-left: auto;
    width: 34px; height: 34px; border-radius: 50%; color: var(--muted);
  }
  /* Nothing to collapse to behind a scrim, and it would take the margin-left
     that pushes the close button to the end of the brand row. */
  .nav-collapse { display: none; }
  .nav-close svg { width: 19px; height: 19px; }
  .brand { padding-bottom: 18px; }
  .nav-item { padding: 12px 14px; }

  /* ---- content ---- */
  .view { padding: 18px 16px calc(28px + env(safe-area-inset-bottom)); }
  .page-head { gap: 12px; padding-bottom: 14px; margin-bottom: 18px; }
  .page-head h2 { font-size: 21px; }
  .page-head h2::before { font-size: 10px; margin-bottom: 6px; }
  .view-intro { font-size: 13.5px; margin: -2px 0 20px; }

  .toolbar { width: 100%; gap: 8px; }
  .search { flex: 1 1 100%; width: auto; }
  .toolbar .sort { flex: 1 1 auto; min-width: 0; }
  .toolbar .btn-secondary { flex: 1 1 auto; justify-content: center; padding: 11px 16px; }
  .page-head > .btn-primary { width: 100%; }

  /* 16px is the threshold under which iOS zooms the page on focus and never
     zooms back out. Every field the app can focus has to clear it. */
  input, select, textarea,
  .search input, .sort, .auth-input, .composer-row textarea,
  .drive-link-row input, .picker-path input { font-size: 16px; }
  .sort { padding: 11px 34px 11px 16px; }
  .search input { padding: 11px 0; }

  /* ---- Ask Clara AI ---- */
  /* The top bar already carries + New chat, and chat space is scarce here. */
  #view-ask { padding-bottom: calc(6px + env(safe-area-inset-bottom)); }
  #view-ask .page-head { display: none; }
  .chat-greeting { font-size: 22px; }
  .chat-empty p { font-size: 14px; margin-bottom: 20px; }
  .chat-empty { max-width: 100%; }
  .msg { max-width: 92%; font-size: 15px; padding: 13px 16px; border-radius: var(--r-lg); }
  .composer { padding-top: 12px; gap: 9px; }
  .composer-row { padding: 6px 6px 6px 7px; border-radius: var(--r-lg); }
  .composer-row textarea { padding: 11px 10px 11px 12px; min-height: 44px; max-height: 32vh; }
  .btn-send { width: 44px; height: 44px; }
  .picker-menu { max-height: 46dvh; min-width: 220px; }
  .suggestion { font-size: 13.5px; padding: 10px 16px; }

  /* ---- knowledge base ---- */
  .hero { min-height: auto; padding: 8px 0 24px; }
  .hero-card { padding: 30px 22px; border-radius: var(--r-lg); }
  .hero-card h1 { font-size: 22px; }
  .hero-card p { font-size: 14px; }
  .steps li { font-size: 13.5px; }
  .topic-card { padding: 18px 16px 18px 18px; gap: 10px; }
  .topic-name { font-size: 17px; }
  .card { padding: 17px; }

  .action-bar { flex-wrap: wrap; gap: 10px 14px; padding: 12px 16px; }
  .action-spacer { display: none; }
  .action-bar .btn-danger { flex: 1 1 100%; justify-content: center; padding: 12px 16px; }

  /* ---- connectors ---- */
  /* Icon + name take the first line; the buttons wrap onto the second rather
     than squeezing the topic name to nothing. */
  .source-row { flex-wrap: wrap; gap: 10px 12px; padding: 15px; }
  .source-body { flex: 1 1 calc(100% - 50px); }
  .source-actions { margin-left: auto; }

  /* ---- modals ---- */
  /* Top-aligned and scrolled by the overlay itself, so a focused field can be
     scrolled into view when the keyboard covers the lower half of the screen. */
  .modal {
    display: block; overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
  }
  .modal-card { width: 100%; max-height: none; padding: 22px 18px; border-radius: var(--r-lg); }
  .modal-title { font-size: 16px; }
  .player-card { padding: 12px; }
  .confirm-actions { gap: 8px; }
  .confirm-actions > button { flex: 1; justify-content: center; text-align: center; padding: 12px 10px; }
  .confirm-list { max-height: 42dvh; }

  /* ---- auth ---- */
  .auth-screen { padding-top: 18px; }
  .auth-card { padding: 28px 22px; border-radius: var(--r-xl); }
  .avatar-picker { gap: 14px; }

  /* Below 16px iOS zooms the page in on focus and never zooms back out. */
  .auth-input.tall { font-size: 16px; }
  .check-grid { grid-template-columns: 1fr; }
  .know-row { flex-wrap: wrap; }

  /* ---- dashboard ---- */
  .dash-card { padding: 18px 16px; }
  .dash-card h3 { font-size: 16.5px; }
  .viz-hero { font-size: 34px; }
  .glance { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .glance-tile { padding: 12px 14px; }
  .glance-n { font-size: 21px; }
  .glance-n.small { font-size: 15px; }
  /* The title stacks over its strip rather than eating two thirds of the width;
     the strip is what the finger aims at. */
  .tl-row { grid-template-columns: 1fr auto; gap: 4px 10px; }
  .tl-name { grid-column: 1 / -1; white-space: normal; }
  .tl-axis { padding-left: 0; }
  .tl-drill { padding: 2px 12px; }
  /* Charts scroll inside their own box rather than compressing to sub-pixel
     bars — the page itself must never scroll sideways. */
  .tim-chart { height: 116px; }
  .tim-col, .tim-axis span { min-width: 16px; }
  .punch { grid-template-columns: 30px repeat(24, 16px); overflow-x: auto; }
  .trust-grid { grid-template-columns: 1fr; }
  .part-row { grid-template-columns: 64px 1fr 30px; }
  .sheet { padding: 22px 18px; }
  /* The count drops under the question rather than squeezing it into a column
     three words wide. */
  .gap-head, .feed-head { flex-wrap: wrap; }
  .gap-label, .feed-q { flex: 1 1 100%; }
  /* ---- ask view ---- */
  /* Full-width chips: a "covers" line has nowhere to go beside a title here. */
  .near-chip { flex: 1 1 100%; }

  /* ---- video dock ---- */
  /* The chat below it also holds the composer and, often, the keyboard —
     the stage gives more of the screen back on a phone. Its tabs are already
     touch-safe: the × is always visible, and none of it is a text input, so
     the 16px focus-zoom rule doesn't apply. */
  .dock-stage { height: min(30vh, 240px); }
}

/* Phone held sideways: the shell is ~390px tall, so trim the vertical padding,
   let the drawer scroll rather than clipping its footer, and keep the content
   column readable instead of stretching lines the full width of the screen. */
@media (max-height: 480px) and (pointer: coarse) {
  .topbar { padding-top: calc(4px + env(safe-area-inset-top)); padding-bottom: 4px; }
  .topbar-btn { height: 36px; }
  .view { padding-top: 12px; }
  .hero { padding: 4px 0 12px; }
  .hero-card { padding: 22px 20px; }
  .sidebar { overflow-y: auto; }
  .modal-title { font-size: 15px; }
  #player, #player-embed { max-height: 62dvh; }
  .msg { max-width: 82%; }
  /* Sideways, the shell is ~390px tall; an expanded dock still leaves the
     composer and a message of context on screen at this height. */
  .dock-stage { height: 38vh; }
}


/* ==========================================================================
   Knowledge Base — the graph
   ==========================================================================
   The library as one picture: a course is a hub, a lecture hangs off it, and
   every ~60s passage hangs off that. It is the default view because the question
   it answers is the creator's own — "I know I explained this somewhere" — and a
   page of topic cards cannot answer it.

   It is not a panel. The canvas has no ground, no border and no shadow of its
   own: it is transparent and the page — aurora, grid overlay and all — shows
   through it, so the graph reads as this page rather than as a window cut into
   it. Everything around it is the app's own furniture: the same pill search, the
   same chips, the same hairlines. Node colours are the topics' own pastels,
   solved down to a fixed contrast against the page (graphTone in app.js),
   because a pastel picked to sit behind dark text is 2:1 as a mark on white.
   ========================================================================== */

/* The two landing views, switched in the page head of both. A segmented pill,
   the same geometry as every other control on the page. */
.kb-switch {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--purple-50); border: 1px solid var(--border);
  border-radius: var(--pill);
}
.kb-switch-btn {
  border: 0; background: none; cursor: pointer;
  font: 600 12.5px var(--font); color: var(--muted);
  padding: 6px 15px; border-radius: var(--pill);
  transition: color 0.18s, background 0.18s, box-shadow 0.18s;
}
.kb-switch-btn:hover { color: var(--purple-700); }
.kb-switch-btn.active {
  background: var(--card); color: var(--purple-700);
  box-shadow: var(--shadow-xs);
}

/* The graph wants the room, and only the graph: the class is set by renderKB,
   so the topic cards and the video grid keep the app's 1080px column. */
body.kb-wide #view-kb { max-width: 1400px; }

/* ---- the search ----
   The page's own search pill, widened: this is the one control the view exists
   for, so it leads rather than sitting in a toolbar with everything else. */
.graph-search {
  display: flex; align-items: center; gap: 11px;
  height: 48px; padding: 0 8px 0 18px; margin-bottom: 22px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--pill); box-shadow: var(--shadow-xs);
  transition: border-color 0.2s, box-shadow 0.2s;
}
.graph-search:focus-within { border-color: var(--purple-500); box-shadow: var(--ring); }
.graph-search > svg { width: 17px; height: 17px; color: var(--faint); flex-shrink: 0; }
.graph-search input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font: 500 15px var(--font); color: var(--ink);
}
.graph-search input::placeholder { color: var(--faint); }
.graph-search input::-webkit-search-cancel-button { display: none; }
/* A count is a value, so it is mono — and it is the first thing to read after
   typing: how much of the library said this. */
.graph-count {
  font: 600 12px var(--mono); color: var(--purple-600);
  white-space: nowrap; flex-shrink: 0;
}
.graph-clear {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: var(--pill);
  border: 0; background: var(--purple-50); color: var(--muted);
  cursor: pointer; font-size: 12px; line-height: 1;
}
.graph-clear:hover { background: var(--purple-100); color: var(--purple-700); }

/* ---- the page, in two columns ---- */
/* Two columns while there is room for two. The test is the width of *this*
   view, not of the window: the sidebar takes 268px of it, so a 1000px window is
   already too narrow here while the phone query is nowhere near firing. */
#kb-graph { container-type: inline-size; }
.graph-layout {
  display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 30px;
  align-items: start;
}
@container (max-width: 860px) {
  .graph-layout { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  /* Chips and toggles stop sharing a row before the last toggle runs off it. */
  .graph-foot { flex-direction: column; align-items: flex-start; gap: 12px; }
  .graph-results {
    border-left: 0; border-top: 1px solid var(--border);
    padding-left: 0; padding-top: 20px; max-height: none; overflow: visible;
  }
}
.graph-main { min-width: 0; }

/* No background, no border, no shadow. The canvas is transparent and this box
   only gives it a size and a place to put the loading line. */
.graph-stage { position: relative; height: clamp(420px, 54vh, 640px); }
#graph-canvas {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  cursor: grab; touch-action: none; border-radius: var(--r-lg);
}
#graph-canvas:active { cursor: grabbing; }
#graph-canvas:focus-visible { outline: 2px solid var(--purple-500); outline-offset: 2px; }

.graph-status {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  font: 500 13px var(--font); color: var(--muted);
}

/* ---- courses and toggles, under the canvas as page furniture ---- */
.graph-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  padding-top: 16px; border-top: 1px solid var(--border);
}
.graph-topics { display: flex; flex-wrap: wrap; gap: 7px; flex: 1; min-width: 0; }
/* A course is a chip, like every other filter in this app. */
.graph-topic {
  display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid var(--border); background: var(--card); cursor: pointer;
  padding: 5px 13px 5px 10px; border-radius: var(--pill);
  font: 600 12px var(--font); color: var(--muted);
  transition: border-color 0.16s, color 0.16s, background 0.16s;
}
.graph-topic:hover { border-color: var(--purple-500); color: var(--purple-700); }
.graph-topic.on { background: var(--purple-50); border-color: var(--purple-500); color: var(--purple-700); }
.graph-topic-c { font: 600 11px var(--mono); color: var(--faint); }
.graph-topic.on .graph-topic-c { color: var(--purple-600); }
.graph-dot {
  width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0;
  background: var(--gc, var(--purple-500));
}

.graph-toggles { display: flex; gap: 16px; flex-shrink: 0; }
.graph-toggles label {
  display: flex; align-items: center; gap: 7px; cursor: pointer;
  font: 500 12px var(--font); color: var(--muted);
}
.graph-toggles label:hover { color: var(--purple-700); }

.graph-hint { margin-top: 12px; font: 500 11px var(--mono); color: var(--faint); }

/* ---- the right column ----
   Plain page content behind a hairline, not a floating card: the graph and the
   list are one surface, and boxing the list would put a panel back. */
.graph-results {
  border-left: 1px solid var(--border); padding-left: 26px;
  max-height: clamp(520px, 70vh, 780px); overflow-y: auto;
}
.graph-panel-h {
  font: 600 10.5px var(--mono); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--purple-600); margin-bottom: 12px;
}

/* ---- before a search: what is in here ---- */
.graph-figures { display: flex; gap: 20px; margin-bottom: 14px; }
.graph-figures div { display: flex; flex-direction: column; gap: 1px; }
/* Figures at display size are Manrope with proportional figures: tabular digits
   this large read loose. Same rule as the dashboard's stat tiles. */
.graph-figures b { font: 800 24px var(--font); color: var(--ink); letter-spacing: -0.02em; }
.graph-figures span {
  font: 600 9.5px var(--mono); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--faint);
}
.graph-blurb { font: 400 13px var(--font); line-height: 1.7; color: var(--muted); }
.graph-warn {
  margin-top: 14px; padding: 11px 13px; border-radius: var(--r-sm);
  background: var(--note-bg); border: 1px solid var(--note-line);
  font: 400 12.5px var(--font); line-height: 1.6; color: var(--note-ink);
}
.graph-warn b { font-weight: 700; }

/* ---- after a search: the seconds to click ---- */
.graph-hits { display: block; }
.graph-hit { margin-bottom: 18px; }
.graph-hit-h {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: 0; background: none; cursor: pointer; text-align: left; padding: 0;
  font: 700 13px var(--font); color: var(--ink);
}
.graph-hit-h:hover .graph-hit-t { color: var(--purple-700); text-decoration: underline; }
.graph-hit-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.graph-hit-n {
  font: 600 10.5px var(--mono); color: var(--purple-700);
  background: var(--purple-50); border-radius: var(--pill); padding: 2px 8px;
}
.graph-hit-c {
  font: 500 10px var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--faint); margin: 4px 0 8px 17px;
}
.graph-moment {
  display: flex; gap: 10px; text-align: left; cursor: pointer;
  border: 0; border-left: 2px solid var(--border); background: none;
  padding: 6px 8px 6px 10px; margin-left: 17px; width: calc(100% - 17px);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  transition: background 0.16s, border-color 0.16s;
}
.graph-moment:hover { background: var(--purple-50); border-left-color: var(--purple-500); }
.graph-moment .ts { font: 600 10.5px var(--mono); color: var(--purple-600); flex-shrink: 0; padding-top: 2px; }
.graph-moment .q { font: 400 12px var(--font); line-height: 1.6; color: var(--muted); }
.graph-moment mark { background: var(--purple-100); color: var(--purple-700); font-weight: 600; border-radius: 3px; padding: 0 2px; }
.graph-more { font: 500 11.5px var(--font); color: var(--faint); margin: 6px 0 0 27px; }

/* The summary-only matches, kept visibly apart from the moments above them:
   they are lectures about the subject, not seconds at which it was said. */
.graph-aside { margin-top: 6px; padding-top: 16px; border-top: 1px solid var(--border); }
.graph-aside .graph-panel-h { margin-bottom: 8px; }
.graph-aside .graph-blurb { font-size: 12px; margin-bottom: 13px; }

/* ---- phone ----
   Same query as the shell, verbatim: narrow, or short and touched. One column,
   the list under the graph, and the canvas short enough to leave room for it. */
@media (max-width: 760px), (max-height: 480px) and (pointer: coarse) {
  body.kb-wide #view-kb { max-width: none; }
  .graph-layout { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .graph-stage { height: clamp(320px, 44vh, 460px); }
  .graph-search { height: 46px; margin-bottom: 18px; padding-left: 15px; }
  /* Below 16px iOS zooms the page on focus and never zooms back. */
  .graph-search input { font-size: 16px; }
  .graph-results {
    border-left: 0; border-top: 1px solid var(--border);
    padding-left: 0; padding-top: 20px; max-height: none; overflow: visible;
  }
  .graph-hint { display: none; }
}
/* A long course name must not stretch its chip across the row. */
.graph-topic-n { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
