/* MySummaries homepage — the portal's skin on the marketing site.
   Tokens mirror hosting/src/app.css: a cool zinc ramp, flat fills, bordered
   panels (never shadowed, except true overlays), one orange accent. Light page
   with graphite bands — the same two skins the portal switches between, here
   used as sections rather than themes.
   styles.css (terms/privacy/contact) is untouched; this file owns the
   homepage and, since the articles carry the homepage's product mock-ups,
   the generated pages under articles/ (scripts/buildSeoArticles.mjs). */

/* ---------------------------------------------------------------- tokens */
:root {
  /* light (zinc) */
  --surface: #ffffff;
  --field: #fbfbfc;
  --field-2: #f6f6f8;
  --ink: #18181b;
  --ink-2: #3f3f46;
  --muted: #52525b;
  --faint: #a1a1aa;
  --line: #e4e4e7;
  --line-soft: #f1f1f3;

  /* graphite (the portal's dark), for the bands */
  --d-field: #1a1a1e;
  --d-field-2: #212126;
  --d-surface: #2b2b31;
  --d-line: #3d3d46;
  --d-ink: #f2f2f5;
  --d-muted: #b8b8c2;
  --d-faint: #8a8a94;

  /* the one accent */
  --accent: #ef562f;
  --accent-strong: #cc4522;
  --accent-soft: #fe795d;
  --accent-wash: #fff5f2;
  --accent-line: #ffd5cc;
  --accent-ink: #a5371b;

  /* demo greens/ambers (the merge review's own colours) */
  --ok: #16a34a;
  --ok-wash: #f0fdf4;
  --warn: #d97706;
  --warn-wash: #fffbeb;

  --font-ui: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  /* Wordmark only — headings run on --font-ui. Fraunces' angled f and S read
     as a lean at display sizes, so the display face IS the UI face now. */
  --font-wordmark: "Fraunces", Georgia, serif;

  --radius: 12px;
  --shell: 1120px;
}

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--field);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

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

.shell {
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: 24px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--surface);
  padding: 8px 14px;
  border-radius: 8px;
  z-index: 100;
}
.skip-link:focus {
  left: 12px;
  top: 12px;
}

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

/* ---------------------------------------------------------------- type */
.display {
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.08;
}

/* Attio-style statement: bold claim, grey continuation, same size. */
.statement {
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  line-height: 1.35;
  font-weight: 600;
  letter-spacing: -0.01em;
  max-width: 34ch;
}
.statement span {
  color: var(--faint);
  font-weight: 600;
}
.statement--lg {
  font-size: clamp(1.7rem, 3.4vw, 2.4rem);
  max-width: 30ch;
}

.eyebrow {
  display: inline-block;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
  background: var(--field-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 12px;
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: 10px;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.2;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s;
}
.btn--sm {
  padding: 7px 13px;
  font-size: 13px;
}
/* Flat orange, darkens on hover — no lift, like the portal. */
.btn--primary {
  background: var(--accent);
  color: #fff;
}
.btn--primary:hover {
  background: var(--accent-strong);
}
/* the portal's soft accent control (the resit button): bordered wash, not fill */
.btn--soft {
  background: var(--accent-wash);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}
.btn--soft:hover {
  background: #ffe9e2;
}
.btn--ghost {
  background: var(--surface);
  border-color: var(--line);
  color: var(--ink-2);
}
.btn--ghost:hover {
  background: var(--field-2);
}
/* Ghost on graphite */
.btn--ghost-dark {
  background: transparent;
  border-color: var(--d-line);
  color: var(--d-ink);
}
.btn--ghost-dark:hover {
  background: var(--d-surface);
}

/* ---------------------------------------------------------------- announce */
.announce {
  display: block;
  background: var(--ink);
  color: #d4d4d8;
  font-size: 13px;
  text-align: center;
  padding: 8px 16px;
}
.announce b {
  color: #fff;
  font-weight: 600;
}
.announce:hover b {
  text-decoration: underline;
}

/* ---------------------------------------------------------------- header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(251, 251, 252, 0.88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
}
.site-header.is-scrolled {
  border-bottom-color: var(--line);
}
.nav-shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 60px;
}

/* The lockup. Fraunces 600 at the face's default optical size — the portal's
   arrangement (Wordmark.svelte), which this site now follows. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
}
.brand-mark {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}
.brand-mark .mark-path {
  fill: none;
  stroke: currentColor;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.brand-mark .mark-node {
  fill: currentColor;
}
.brand-mark .mark-node-synapse {
  fill: var(--accent);
}
.brand-type {
  font-family: var(--font-wordmark);
  font-weight: 600;
  font-size: 19px;
  letter-spacing: -0.01em;
  line-height: 1;
}

.primary-nav {
  display: flex;
  align-items: center;
  gap: 26px;
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
}
.primary-nav a:hover {
  color: var(--ink);
}
.primary-nav .btn {
  color: #fff;
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  padding: 0 9px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: var(--ink);
}

@media (max-width: 860px) {
  .nav-toggle {
    display: flex;
  }
  .primary-nav {
    position: absolute;
    top: calc(100% + 1px);
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    padding: 12px 24px 18px;
    background: var(--field);
    border-bottom: 1px solid var(--line);
    display: none;
  }
  .primary-nav.is-open {
    display: flex;
  }
  .primary-nav a {
    padding: 10px 4px;
  }
}

/* ---------------------------------------------------------------- hero */
/* The copy gets a screenful of its own before the band arrives */
.home-hero {
  padding: clamp(56px, 7vw, 116px) 0 0;
  text-align: center;
}
.home-hero h1 {
  font-size: clamp(2.1rem, 4.6vw, 3.1rem);
  max-width: 24ch;
  margin-inline: auto;
}
.hero-lead {
  max-width: 68ch;
  margin: 18px auto 0;
  font-size: clamp(0.95rem, 1.4vw, 1.05rem);
  color: var(--muted);
}
.hero-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 28px;
  flex-wrap: wrap;
}
.hero-fine {
  margin-top: 18px;
  font-size: 13px;
  color: var(--faint);
}

/* --------------------------------------------- the hero's scroll handoff
   On load the board window has the fold to itself. Scrolling hands it over:
   the copy above fades out, the satellites fade in and the board gives them
   a little room, all off one `--p` (0→1) stage.js writes over
   the first screenful, so scrolling back up plays it backwards for free. The
   class is added only when motion is allowed, so no-JS and reduced-motion
   keep the static composition with everything visible. The stage rising is
   the scroll itself — lifting its box by transform only opens a gap under
   the band, since everything after it stays where it was. */
.home-hero.is-scrolly {
  --sat: 0;
}
/* One step, matching the copy above: the first scroll of any size brings the
   satellites fully in — a scrub left them stranded half-faded at a small
   scroll. Only the board's own scale still reads `--p`. */
.home-hero.is-scrolly.is-handed {
  --sat: 1;
}
/* The copy does not move: it is pinned under the header and fades where it
   stands while the stage rides up over it (the stage is painted later, so it
   covers the sticky shell without a z-index). Sticky ends at the hero's own
   bottom, and by then the shell is transparent and buried under the band. */
.home-hero.is-scrolly > .shell {
  position: sticky;
  top: 60px;
  transition: opacity 0.35s ease;
}
/* one step, not a scrub: any scroll at all takes the copy out, and coming
   back to the very top brings it back. Only the band's own arrival (the
   satellites, the board's scale) stays tied to `--p`. */
.home-hero.is-scrolly.is-handed > .shell {
  opacity: 0;
}
/* and the board gives ground as the satellites arrive: it owns the fold at
   near full-bleed and settles back to about 84%. Scaled, not resized: its box
   stays put, so the band's height never moves and the masonry never reflows
   mid-scroll — it just breathes in from the middle. */
.home-hero.is-scrolly .win--board {
  transform: scale(calc(1 - var(--p, 0) * 0.12));
}

/* ------------------------------------------------------------- the stage
   The looping product demo lives here eventually. For now the same windows,
   composed statically: real DOM, ready to be driven. */
.stage {
  position: relative;
  margin-top: clamp(48px, 5vw, 84px);
  border-top: 1px solid var(--line);
  /* flat, not a gradient: the board window's own canvas is one step lighter
     and runs to its bottom edge, so everything outside it is this one grey
     rather than a fade that read as a white bar under the short masonry */
  background: #ededf1;
  overflow: hidden;
}
/* No height of its own: the board window is the one window left IN FLOW, so
   the band is exactly as tall as the board at any width — the masonry gets
   shorter as the columns get wider, and there is never dead space under it.
   Everything else floats above it, absolute against this box. */
.stage-inner {
  position: relative;
  max-width: 1800px;
  margin-inline: auto;
  padding: 24px 0 36px;
}
/* Below the design width the stage does NOT reflow — it shrinks. The
   composition IS the product shot (one wide board with satellites floating
   around it); reflowing it stacked the windows and let them overlap, so the
   whole band is zoomed to fit instead and stays one picture at every size.
   `zoom` and not `transform: scale()` on purpose: zoom shrinks the layout
   box too, so the band stays exactly as tall as what it holds. It also makes
   a rendered pixel ≠ a CSS pixel — stage.js divides by `currentCSSZoom`
   before writing any measured transform. The design width matches the
   max-width above, so anything wider renders 1:1 and only narrow screens
   pay for a fractional scale. */
@media (max-width: 1800px) {
  .stage-inner {
    zoom: calc(100vw / 1800px);
  }
}
/* On a phone the board alone is the picture — the satellites shrink past
   reading at this zoom and just clutter its edges. Hidden, never restacked
   or rebuilt: there is one composition on this page, at one size, and small
   screens see less of it rather than a different one. */
@media (max-width: 720px) {
  .stage .win:not(.win--board) {
    display: none;
  }
}

/* window chrome */
.win {
  position: absolute;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 24px 48px -24px rgba(24, 24, 27, 0.18);
  overflow: hidden;
  text-align: left;
}
.win-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 38px;
  padding: 0 12px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
}
.win-dots {
  display: inline-flex;
  gap: 5px;
  flex-shrink: 0;
}
.win-dots i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--line);
}
.win-dots i:nth-child(1) {
  background: #fc5753;
}
.win-dots i:nth-child(2) {
  background: #fdbc40;
}
.win-dots i:nth-child(3) {
  background: #33c748;
}
.win-crumb {
  color: var(--faint);
  font-weight: 500;
}
.win-crumb b {
  color: var(--ink-2);
  font-weight: 600;
}

/* board window — the portal's own chrome in miniature, in EDIT mode: the bar
   wears the orange edit wash and rule, the surface an inset ring, and the tab
   strip is gone (the portal hides it while authoring). */
.win--board {
  position: relative;
  /* A share of the design box, not a pixel width: the ~200px of stage left
     either side is exactly the room the satellites sit in, so the two numbers
     move together. The handoff scales it back to ~88% rather than resizing
     it, so the 16:9 box below is measured against this and nothing the
     masonry holds can overflow it at the settled size. */
  width: 75%;
  /* a monitor's shape, not the masonry's: the sections fill a 16:9 box and
     the canvas takes up whatever slack is left at the bottom */
  aspect-ratio: 16 / 9;
  /* its own stacking context, pinned to the floor: the scene overlays inside
     it (scratchpad, review, views) carry z-indexes of their own, and without
     this they escape into the stage and paint over the satellites. The board
     is the thing being worked in; the left-hand windows sit on top of it. */
  z-index: 1;
  margin-inline: auto;
  /* the canvas, not white: the masonry runs short of the 16:9 box and the
     slack below it is board canvas, the same as the gaps between columns */
  background: var(--field-2);
}
/* the edit-mode inset ring, drawn over the children (backgrounds would bury
   a box-shadow on the window itself) but under the modal overlays */
.win--board::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 38;
  pointer-events: none;
  border-radius: 14px;
  box-shadow: inset 0 0 0 2px rgba(239, 86, 47, 0.13);
}
/* -------------------------------------------------------- study mode
   The loop opens on the board in STUDY mode, which is how the portal is
   entered: the four surfaces made out of these notes are tabs in the bar,
   and the authoring tools are not there at all. `is-study` swaps the two
   sets and drops the edit chrome; the animation clicks Edit to leave it. */
.bar-tabs {
  display: none;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.win--board.is-study .bar-tabs {
  display: inline-flex;
}
.win--board.is-study .bar-views,
.win--board.is-study .bar-tools {
  display: none;
}
/* the edit-only chrome: the orange bar wash and the inset ring */
.win--board.is-study .win-bar {
  border-bottom-color: var(--line-soft);
  background-image: none;
}
.win--board.is-study::after {
  box-shadow: none;
}
.btab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-style: normal;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
  padding: 3px 7px;
  border-radius: 8px;
  white-space: nowrap;
}
.btab svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}
.btab.is-on {
  background: rgba(24, 24, 27, 0.06);
  color: var(--ink-2);
  font-weight: 600;
}
/* the walk: each surface lights accent in turn, with the same radiating ring
   the scratchpad buttons use, and names itself in a tooltip while it is lit */
.stage.is-live .btab.is-cue {
  background: var(--accent-wash);
  color: var(--accent-ink);
  animation: a-ring 0.7s ease-out 2;
}
.btab-tip {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  translate: -50%;
  z-index: 40;
  width: max-content;
  max-width: 190px;
  background: var(--ink);
  color: #e4e4e7;
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  line-height: 1.4;
  padding: 5px 9px;
  border-radius: 7px;
  text-align: center;
  white-space: normal;
  pointer-events: none;
  box-shadow: 0 8px 16px -8px rgba(24, 24, 27, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s;
}
.btab-tip::after {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 50%;
  translate: -50%;
  border: 4px solid transparent;
  border-bottom-color: var(--ink);
}
.stage.is-live .btab.is-cue .btab-tip {
  opacity: 1;
  visibility: visible;
}
/* and the same cue on Edit, just before it is clicked */
.stage.is-live .mode-toggle i.is-cue {
  background: var(--accent-wash);
  color: var(--accent-ink);
  animation: a-ring 0.7s ease-out 2;
}

/* the portal's .study-bar--edit: orange wash + rule, still border-bottom 1px */
.win--board .win-bar {
  border-bottom-color: rgba(239, 86, 47, 0.4);
  background-image: linear-gradient(to bottom, rgba(254, 121, 93, 0.16), rgba(254, 121, 93, 0.06));
}
.crumb-sep {
  font-style: normal;
  color: var(--faint);
  padding: 0 1px;
}
.crumb-chev {
  width: 10px;
  height: 10px;
  fill: none;
  stroke: var(--faint);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.bar-sep {
  width: 1px;
  height: 16px;
  background: var(--line);
  margin: 0 2px;
  flex-shrink: 0;
}
/* Edit/Study — the segmented mode toggle in its grey well. The white pill is
   one element behind both halves so it SLIDES between them; `inline-grid` with
   1fr columns makes the two halves equal width, which is what lets the pill be
   a flat 50% and the travel a flat 100%. */
.mode-toggle {
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: center;
  gap: 1px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(24, 24, 27, 0.06);
  flex-shrink: 0;
}
.mode-toggle::before {
  content: "";
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc(50% - 2.5px);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(24, 24, 27, 0.08);
  transition: translate 0.34s cubic-bezier(0.4, 0, 0.2, 1);
}
/* the 1px is the grid gap the pill has to clear on its way across */
.win--board.is-study .mode-toggle::before {
  translate: calc(100% + 1px);
}
.mode-toggle i {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-style: normal;
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  padding: 2px 8px;
  border-radius: 8px;
  transition: color 0.34s ease;
}
.mode-toggle i svg {
  width: 11px;
  height: 11px;
}
.mode-toggle i.is-on {
  color: var(--accent-strong);
  font-weight: 600;
}
/* the bar's tools: Views, + Add, Fill board, Sort by, undo/redo, Scratchpad */
.bar-tool {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-style: normal;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
  padding: 3px 7px;
  border-radius: 8px;
  white-space: nowrap;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.bar-tool svg {
  width: 11px;
  height: 11px;
}
.bar-tool .spark {
  width: 10px;
  height: 10px;
  color: var(--accent-ink);
  opacity: 0.75;
}
.bar-tool.is-on {
  background: rgba(24, 24, 27, 0.07);
  color: var(--ink);
}
.bar-tools {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}
.bar-icon {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 7px;
  color: var(--faint);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.bar-icon svg {
  width: 12px;
  height: 12px;
}
.bar-icon.is-on {
  background: var(--accent-wash);
  color: var(--accent-ink);
}
/* Views + its dropdown, anchored to the button like the portal's bar menus */
.bar-views {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
.views-menu {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 46;
  min-width: 150px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 16px 32px -12px rgba(24, 24, 27, 0.28);
  opacity: 0;
  visibility: hidden;
  translate: 0 -4px;
  transition: opacity 0.18s ease, translate 0.18s ease, visibility 0.18s;
}
.bar-views.is-open .views-menu {
  opacity: 1;
  visibility: visible;
  translate: none;
}
.views-menu i {
  font-style: normal;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-2);
  padding: 4px 8px;
  border-radius: 8px;
  white-space: nowrap;
  transition: background-color 0.2s ease;
}
.views-menu i.is-on {
  background: var(--field-2);
  color: var(--ink);
  font-weight: 600;
}
/* the title tile: part-coloured crumb over the board's name + mono meta */
.board-title {
  padding: 9px 14px 2px;
  background: var(--field-2);
}
.part-crumb {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #5a4d85;
}
.part-crumb i {
  width: 8px;
  height: 8px;
  border-radius: 2.5px;
  background: #7b6da6;
}
.board-name {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 1px;
}
.board-name b {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.board-name small {
  margin-left: auto;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 9.5px;
  color: var(--faint);
}
/* Four columns; the vessels table is a widened card (span 2), so the grid is
   placed explicitly: table over columns 1–2, two stacks below it, and the
   outer stacks running the full height — masonry in miniature. */
.board-cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* Row 1 is the wide table's own height and nothing more. Left auto, the
     outer two stacks span both rows and the sizing algorithm hands row 1 a
     share of their height, which opened a gap under the table; `1fr` on row 2
     sends that slack there instead. */
  grid-template-rows: auto 1fr;
  gap: 10px;
  padding: 10px 12px 12px;
  background: var(--field-2);
  align-items: start;
}
.board-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.board-wide {
  grid-column: 1 / 3;
  grid-row: 1;
}
.board-col--a {
  grid-column: 1;
  grid-row: 2;
}
.board-col--b {
  grid-column: 2;
  grid-row: 2;
}
.board-col--c {
  grid-column: 3;
  grid-row: 1 / 3;
}
.board-col--d {
  grid-column: 4;
  grid-row: 1 / 3;
}

/* mini section card — the portal's section grammar in miniature */
.sec {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}
.sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line-soft);
  background: #fcfcfd;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
/* study-prose in miniature: the dense printed-sheet serif her boards render in,
   bold lead-ins, muted bullet markers, hairline tables */
.sec-body {
  padding: 7px 10px 8px;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-2);
}
.sec-body p + p {
  margin-top: 4px;
}
.sec-body b {
  font-weight: 600;
  color: var(--ink);
}
.sec-body ul {
  margin: 0;
  padding-left: 14px;
  list-style: disc;
}
.sec-body li {
  margin: 2px 0;
}
.sec-body li::marker {
  color: var(--faint);
}
.sec-body table {
  width: 100%;
  border-collapse: collapse;
  font-size: 10px;
  line-height: 1.4;
}
.sec-body th,
.sec-body td {
  border: 1px solid var(--line);
  padding: 3px 6px;
  text-align: left;
  vertical-align: top;
}
.sec-body th {
  background: var(--field-2);
  font-family: var(--font-ui);
  font-size: 9px;
  font-weight: 600;
}
/* the LaTeX card: KaTeX-style set equation */
.eq {
  text-align: center;
  font-style: italic;
  font-size: 13px;
  letter-spacing: 0.01em;
  padding: 3px 0 2px;
  color: var(--ink);
}
.eq sub,
.eq sup {
  font-size: 8.5px;
}
/* the figure card: image body flush under the head, like a board image section */
.sec-img {
  background: var(--surface);
}
.sec-img svg,
.sec-img img {
  display: block;
  width: 100%;
  height: auto;
}

/* surface identity tones — the app's TONES, on the light skin:
   cards coral, exam sky, oral violet, lecture teal */
.win--card,
.fw-cards {
  --tone: #ef562f;
  --tone-ink: #a5371b;
  --tone-wash: #fff5f2;
  --tone-line: #ffd5cc;
}
.win--notif,
.fw-exam {
  --tone: #1c78d2;
  --tone-ink: #175e9e;
  --tone-wash: #eff6fd;
  --tone-line: #cbe2f7;
}
.win--viva,
.fw-oral {
  --tone: #6a4bd6;
  --tone-ink: #5334b8;
  --tone-wash: #f4f0fe;
  --tone-line: #ded2f8;
}
.win--lecture,
.fw-lecture {
  --tone: #12977c;
  --tone-ink: #0c6f5b;
  --tone-wash: #ecf9f5;
  --tone-line: #c4eadf;
  --hl-bg: #d9f3ec;
  --hl-ink: #0c6f5b;
}
.win-bar--tone {
  background: var(--tone-wash);
  border-bottom-color: var(--tone-line);
  color: var(--tone-ink);
}
.tone-chip {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: var(--surface);
  border: 1px solid var(--tone-line);
  color: var(--tone);
  display: grid;
  place-items: center;
}
.tone-chip svg {
  width: 13px;
  height: 13px;
}
.win-meta {
  margin-left: auto;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
  font-weight: 600;
  color: var(--tone-ink);
  opacity: 0.75;
}
.pill {
  flex-shrink: 0;
  font-size: 9px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 999px;
  letter-spacing: 0.02em;
  text-transform: none;
}
.pill--due,
.pill--src {
  background: var(--accent-wash);
  color: var(--accent-ink);
  border: 1px solid var(--accent-line);
}
.pill--ok {
  background: var(--ok-wash);
  color: var(--ok);
  border: 1px solid #bbf7d0;
}

/* Every window but the board is a satellite: they arrive on `--sat`, which
   the hero's scroll handoff drives (below) and which defaults to 1 without
   JS. The notification's own loop still owns when IT is in, so it multiplies
   through the same factor rather than fighting for opacity. */
.stage .win:not(.win--board) {
  opacity: var(--sat, 1);
  transition: opacity 0.35s ease;
}
/* Three layers, all positive — a negative z-index escapes the zoomed stage's
   stacking context in Chrome and paints under the band's own background. The
   right-hand pair keeps `auto` and so tucks BEHIND the board, reading as
   windows peeking out from under it; the left three sit in front, since they
   are the ones whose own loops the eye follows. */
.stage .win--tools,
.stage .win--notif,
.stage .win--lecture {
  z-index: 2;
}

/* flashcard window — the drill card.
   The right-hand pair sits BEHIND the board, so it has to tuck under the
   board's edge or it reads as two windows parked beside it — which is what
   the board's scale-back to 88% left them doing, flush against nothing. The
   offsets differ because the widths do: both left edges land 30px under the
   board's settled edge, so the pair also lines up as a column. */
.win--card {
  right: 30px;
  /* halfway down the board, not perched on its top corner */
  top: 310px;
  width: 306px;
}
.win-bar .bar-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card-q {
  padding: 15px 16px 6px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.45;
}
.card-a {
  padding: 0 16px 14px;
  font-size: 13px;
  color: var(--muted);
  border-bottom: 1px solid var(--line-soft);
}
.card-grades {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  padding: 12px;
}
.card-grades i {
  font-style: normal;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  padding: 8px 0;
  border-radius: 9px;
  border: 1px solid var(--line);
  color: var(--muted);
}
.card-grades i.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* each button says what it books — ivlLabel() in packages/study-core/deck.ts */
.card-grades b {
  display: block;
  font-weight: 600;
}
.card-grades em {
  display: block;
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
}

/* notification card — slides through mid-left on its own sparse loop */
.win--notif {
  left: 64px; /* see .win--tools */
  top: 400px;
  width: 272px;
  border-radius: 12px;
}
.notif {
  display: flex;
  gap: 10px;
  padding: 12px;
}
.notif-mark {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--tone-wash);
  border: 1px solid var(--tone-line);
  color: var(--tone);
  display: grid;
  place-items: center;
}
.notif-mark svg {
  width: 18px;
  height: 18px;
}
.notif b {
  display: block;
  font-size: 12.5px;
}
.notif p {
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.45;
  margin-top: 2px;
}

/* viva window — the live examiner */
.win--viva {
  right: 6px; /* see .win--card: 30px under the board's edge, same left edge */
  bottom: 30px;
  width: 330px;
}
.viva-live {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--tone-ink);
}
.viva-live::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--tone);
}
.is-live .viva-live::before {
  animation: a-pulse 1.4s ease-in-out infinite;
}
@keyframes a-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}
.viva-body {
  padding: 12px 14px;
}
.viva-body b {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.viva-body p {
  font-size: 12.5px;
  line-height: 1.5;
  margin-top: 3px;
}
.viva-meter {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 18px;
  padding: 0 14px 12px;
}
.viva-meter i {
  width: 4px;
  border-radius: 2px;
  background: var(--tone);
  opacity: 0.75;
}
.viva-meter i:nth-child(odd) {
  height: 60%;
}
.viva-meter i:nth-child(even) {
  height: 100%;
}
.viva-meter i:nth-child(3n) {
  height: 35%;
}
.viva-meter span {
  margin-left: auto;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

/* lecture player */
.win--lecture {
  left: 34px; /* see .win--tools */
  bottom: 44px;
  width: 302px;
}
.lecture {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px 12px;
}
.lecture-play {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--tone);
  display: grid;
  place-items: center;
}
.lecture-play svg {
  width: 12px;
  height: 12px;
  fill: #fff;
  margin-left: 1px;
}
.lecture-main {
  min-width: 0;
  flex: 1;
}
.lec-progress {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--tone-line);
  overflow: hidden;
  margin-bottom: 7px;
}
.lec-progress i {
  display: block;
  height: 100%;
  background: var(--tone);
  transform-origin: left;
  transform: scaleX(0.32);
}
.stage.is-live .lec-progress i {
  animation: a-progress 24s linear infinite;
}
@keyframes a-progress {
  from {
    transform: scaleX(0.04);
  }
  to {
    transform: scaleX(0.97);
  }
}
.lecture p {
  font-size: 11px;
  color: var(--muted);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lecture p mark {
  background: var(--hl-bg);
  color: var(--hl-ink);
  border-radius: 3px;
  padding: 0 2px;
}

/* ------------------------------------------------- the board's edit scenery
   The floating section toolbar, the drop veil, the drag ghost, the scratchpad
   panel, the merge-review diff and the Views mind map — all faithful minis of
   the portal's own components (SectionCard's pill, ScratchPanel, MergeReview/
   BlockDiff, Views). State classes live in the markup so the static
   composition reads finished; stage.js drives them when live. */

/* the floating toolbar pill (SectionCard's) */
.sec-pill {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  translate: -50%;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 3px 5px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 10px 24px -10px rgba(24, 24, 27, 0.3);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.22s ease, visibility 0.22s;
}
.pill-b {
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 3px;
  border-radius: 999px;
  font-style: normal;
  font-size: 10.5px;
  line-height: 1;
  color: var(--ink-2);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.pill-b svg {
  width: 11px;
  height: 11px;
}
.pill-b.is-on {
  background: var(--accent);
  color: #fff;
}
.pill-sep {
  width: 1px;
  height: 12px;
  background: var(--line);
  margin: 0 3px;
}
.pb-bold {
  font-weight: 700;
}
.pb-italic {
  font-style: italic;
  font-family: Georgia, serif;
}
.pb-under {
  text-decoration: underline;
}
/* the active card: ring + pill, the portal's editing state */
.sec--conduction.is-edit {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 2px rgba(239, 86, 47, 0.15);
  z-index: 5;
}
.sec--conduction.is-edit .sec-pill {
  opacity: 1;
  visibility: visible;
}
/* the hover tooltip on a toolbar tool — shown while its button is `is-on` */
.pill-b {
  position: relative;
}
.pill-tip {
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  translate: -50%;
  z-index: 20;
  width: max-content;
  max-width: 150px;
  background: var(--ink);
  color: #e4e4e7;
  font-style: normal;
  font-size: 9.5px;
  font-weight: 500;
  line-height: 1.35;
  padding: 4px 8px;
  border-radius: 7px;
  text-align: center;
  white-space: normal;
  pointer-events: none;
  box-shadow: 0 8px 16px -8px rgba(24, 24, 27, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s;
}
.pill-tip b {
  color: #fff;
}
.pill-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50%;
  border: 4px solid transparent;
  border-top-color: var(--ink);
}
.pill-b.is-on .pill-tip {
  opacity: 1;
  visibility: visible;
}
/* tools near the pill's right end pin their tooltip to the window edge —
   centred, it would clip against the window's overflow */
.tool-pill .pb-format .pill-tip,
.tool-pill .pb-check .pill-tip {
  left: auto;
  right: -14px;
  translate: none;
}
.tool-pill .pb-format .pill-tip::after,
.tool-pill .pb-check .pill-tip::after {
  left: auto;
  right: 16px;
  translate: none;
}

/* moving a section to another column: transform-only FLIP — stage.js measures
   the destination and sets --mx/--my; the drop mark is the portal's own
   insertion bar */
.stage.is-live .sec--co.is-lift {
  z-index: 7;
  transform: scale(1.03) rotate(0.6deg);
  box-shadow: 0 16px 32px -12px rgba(24, 24, 27, 0.35);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.stage.is-live .sec--co.is-moved {
  z-index: 7;
  transform: translate(var(--mx, 0px), var(--my, 0px));
  box-shadow: none;
  transition: transform 0.8s cubic-bezier(0.5, 0.08, 0.3, 1), box-shadow 0.4s ease 0.5s;
}
/* In flow, always occupying its 3px — it is also the measuring anchor for
   the move, so it must never appear/disappear from layout mid-loop */
.drop-mark {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.drop-mark.is-mark {
  opacity: 1;
}

/* the nudge on Views before it is "clicked" — easy to miss otherwise */
.stage.is-live .tool-views.is-cue {
  background: var(--accent-wash);
  color: var(--accent-ink);
  animation: a-ring 0.7s ease-out 2;
}
/* the highlighter's mark on the lead phrase */
.sec-body b.is-marked {
  background: rgba(253, 230, 138, 0.55);
  border-radius: 3px;
  transition: background-color 0.4s ease;
}

/* drop veil — ScratchPanel's drag-over overlay */
.drop-veil {
  position: absolute;
  inset: 38px 0 0;
  z-index: 34;
  display: grid;
  place-items: center;
  background: rgba(239, 86, 47, 0.06);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease, visibility 0.25s;
}
.win--board.is-dropping .drop-veil {
  opacity: 1;
  visibility: visible;
}
.veil-card {
  border: 2px dashed var(--accent);
  background: var(--surface);
  border-radius: 14px;
  padding: 12px 22px;
  text-align: center;
  box-shadow: 0 18px 36px -18px rgba(24, 24, 27, 0.3);
}
.veil-card b {
  display: block;
  font-size: 13px;
  color: var(--ink);
}
.veil-card p {
  margin-top: 2px;
  font-size: 11px;
  color: var(--muted);
}

/* the photo being dragged in */
.drag-ghost {
  position: absolute;
  left: 6%;
  /* clear of the window's 38px chrome: at a negative top the photo started
     half clipped by the window's overflow and half over the title bar, which
     read as it never appearing at all. The drag's y translate below is offset
     by the same amount, so it still lands on the scratchpad. */
  top: 46px;
  z-index: 36;
  width: 144px;
  opacity: 0;
  pointer-events: none;
  transform: rotate(-3deg);
  filter: drop-shadow(0 10px 18px rgba(24, 24, 27, 0.25));
}
.drag-ghost img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 6px;
}
.drag-ghost.is-in {
  opacity: 1;
  transition: opacity 0.3s ease;
}
.drag-ghost.is-drag {
  transform: translate(560px, 132px) rotate(2deg) scale(0.96);
  transition: transform 1.4s cubic-bezier(0.45, 0.08, 0.35, 1), opacity 0.3s ease;
}
.drag-ghost.is-drop {
  opacity: 0;
  transform: translate(560px, 132px) rotate(2deg) scale(0.7);
  transition: transform 0.35s ease, opacity 0.3s ease;
}

/* the scratchpad — the portal's right sidebar, inside the window */
.scratch {
  position: absolute;
  top: 38px;
  right: 0;
  bottom: 0;
  z-index: 30;
  width: 300px;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -12px 0 24px -18px rgba(24, 24, 27, 0.25);
  transform: translateX(103%);
  transition: transform 0.28s ease;
}
.win--board.show-scratch .scratch {
  transform: none;
}
/* The portal blurs the board back behind an open panel. z-index 29 puts this
   under the scratchpad and far under the review modal (40), so the modal and
   its own scrim paint over the top of it sharp — a backdrop-filter only
   touches what is painted BEHIND it. Inset past the bar: the chrome stays
   readable, the way the portal leaves its own bar alone. */
.scratch-scrim {
  position: absolute;
  inset: 38px 0 0;
  z-index: 29;
  background: rgba(24, 24, 27, 0.14);
  backdrop-filter: blur(2.5px);
  -webkit-backdrop-filter: blur(2.5px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s ease, visibility 0.28s;
}
.win--board.show-scratch .scratch-scrim {
  opacity: 1;
  visibility: visible;
}
.scratch-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 9px;
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.scratch-swatch {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  background: var(--faint);
}
.scratch-acts {
  margin-left: auto;
  display: inline-flex;
  gap: 2px;
}
.scratch-acts i {
  font-style: normal;
  font-size: 10.5px;
  font-weight: 500;
  color: var(--muted);
  padding: 2px 6px;
  border-radius: 7px;
}
.scratch-tabs {
  display: flex;
  gap: 3px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-soft);
}
.scratch-tabs i {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-style: normal;
  font-size: 10.5px;
  font-weight: 500;
  color: var(--muted);
  padding: 2px 8px;
  border-radius: 8px;
}
.scratch-tabs i.is-on {
  background: rgba(24, 24, 27, 0.05);
  color: var(--ink);
  font-weight: 600;
}
.tab-count {
  font-size: 9px;
  font-weight: 700;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--line-soft);
  color: var(--muted);
}
.tab-count.tc-on {
  background: var(--accent);
  color: #fff;
}
.scratch-list {
  flex: 1;
  min-height: 0;
  padding: 8px;
  overflow: hidden;
}
.scr-item {
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface);
}
.scr-thumb {
  border-bottom: 1px solid var(--line-soft);
  background: #fdfdfb;
}
/* the board photo is 3:2 and the thumb strip is not — crop, do not letterbox */
.scr-thumb img {
  display: block;
  width: 100%;
  height: 64px;
  object-fit: cover;
}
.scr-pad {
  padding: 7px 9px 9px;
}
.scr-title {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink);
}
/* space reserved from the start (visibility, not display) — the item must not
   grow mid-loop */
.scr-text {
  margin-top: 3px;
  font-size: 10.5px;
  line-height: 1.45;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.scr-item.is-lifted .scr-text {
  visibility: visible;
  opacity: 1;
}
.scr-actions {
  display: grid;
  margin-top: 7px;
}
.scr-actions > span {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  gap: 4px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s;
}
.scr-item .row-a {
  opacity: 1;
  visibility: visible;
}
.scr-item.is-busy .row-a,
.scr-item.is-lifted .row-a {
  opacity: 0;
  visibility: hidden;
}
.scr-item.is-busy .row-b {
  opacity: 1;
  visibility: visible;
}
.scr-item.is-lifted .row-c {
  opacity: 1;
  visibility: visible;
}
/* mini buttons — the portal's btn-primary / btn-ghost at scratchpad scale */
.sbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 8px;
  white-space: nowrap;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease,
    box-shadow 0.25s ease;
}
.sbtn.primary {
  background: var(--accent);
  color: #fff;
}
.sbtn.ghost {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  font-weight: 500;
}
.is-live .sbtn.ghost.is-hit {
  border-color: var(--accent);
  background: var(--accent-wash);
  color: var(--accent-ink);
  animation: a-settle 0.5s ease;
}
/* busy row — Busy.svelte's pulse dot + indeterminate sweep */
.busy-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.is-live .busy-dot {
  animation: a-pulse 1.2s ease-in-out infinite;
}
.busy-note {
  font-size: 10.5px;
  color: var(--accent-ink);
  white-space: nowrap;
}
.busy-line {
  flex: 1;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-line);
  overflow: hidden;
}
.busy-line i {
  display: block;
  height: 100%;
  width: 40%;
  background: var(--accent);
}
.is-live .busy-line i {
  animation: a-sweep 1.15s ease-in-out infinite;
}

/* the merge review — MergeReview's modal + BlockDiff's rows */
.review {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgba(24, 24, 27, 0.35);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}
.win--board.is-review .review {
  opacity: 1;
  visibility: visible;
}
.review-panel {
  width: min(620px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 24px 48px -12px rgba(24, 24, 27, 0.35);
  overflow: hidden;
  transform: translateY(10px) scale(0.985);
  transition: transform 0.3s ease;
}
.win--board.is-review .review-panel {
  transform: none;
}
.review-head {
  padding: 9px 12px 8px;
  border-bottom: 1px solid var(--line-soft);
}
.review-head b {
  font-size: 12.5px;
  color: var(--ink);
}
.review-head p {
  margin-top: 1px;
  font-size: 10.5px;
  color: var(--muted);
}
.review-ops {
  padding: 10px;
  background: var(--field-2);
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow: hidden;
}
.op {
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface);
}
.op--edit {
  border-left-color: #fcd34d;
}
.op--new {
  border-left-color: #22c55e;
  background: var(--ok-wash);
}
.op-head {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 6px 9px;
  border-bottom: 1px solid var(--line-soft);
}
.op--new .op-head {
  border-bottom-color: #dcfce7;
  background: rgba(220, 252, 231, 0.6);
}
.op-tag {
  font-style: normal;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 1.5px 5px;
  border-radius: 4px;
  margin-top: 1px;
  white-space: nowrap;
}
.op--edit .op-tag {
  background: var(--warn-wash);
  color: #92400e;
  box-shadow: inset 0 0 0 1px #fde68a;
}
.op--new .op-tag {
  background: #dcfce7;
  color: #166534;
  box-shadow: inset 0 0 0 1px #bbf7d0;
}
.op-name {
  min-width: 0;
  flex: 1;
}
.op-name b {
  display: block;
  font-size: 11.5px;
  color: var(--ink);
}
.op-name small {
  display: block;
  font-size: 10px;
  color: var(--muted);
}
.op-apply {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  color: var(--muted);
  margin-top: 1px;
}
/* the ticked checkbox, drawn */
.ck {
  width: 9px;
  height: 9px;
  border-radius: 2.5px;
  background: var(--accent);
  position: relative;
  flex-shrink: 0;
  margin-top: 1px;
}
.ck::after {
  content: "";
  position: absolute;
  left: 2.5px;
  top: 0.5px;
  width: 3px;
  height: 5.5px;
  border: solid #fff;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
/* diff rows — BlockDiff verbatim: same gray, − red strike, + green */
.op-rows {
  padding: 4px 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.op-rows .row {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 2px 9px;
  font-size: 10px;
  line-height: 1.45;
}
.op-rows .row b {
  font-weight: 700;
  flex-shrink: 0;
}
.op-rows .gut {
  width: 9px;
  flex-shrink: 0;
}
.op-rows .same {
  color: var(--faint);
}
.op-rows .del {
  background: #fee2e2;
  color: #7f1d1d;
  box-shadow: inset 0 0 0 1px #fecaca;
}
.op-rows .del span {
  text-decoration: line-through;
}
.op-rows .add {
  background: #dcfce7;
  color: #14532d;
  box-shadow: inset 0 0 0 1px #bbf7d0;
}
.review-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--line-soft);
}
.review-foot p {
  flex: 1;
  font-size: 10.5px;
  color: var(--muted);
}

/* Views → Algorithm: a mermaid-style flowchart of the double circulation —
   numbered, colour-coded nodes on one loop */
.viewfull {
  position: absolute;
  inset: 0;
  z-index: 45;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s;
}
.win--board.is-views .viewfull {
  opacity: 1;
  visibility: visible;
}
.vf-head {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 12px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
  flex-shrink: 0;
}
.vf-head svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
}
.vf-sub {
  color: var(--faint);
  font-weight: 500;
}
.vf-close {
  margin-left: auto;
  font-style: normal;
  font-size: 12px;
  color: var(--faint);
}
.vf-body {
  position: relative;
  flex: 1;
  background: var(--field);
}
.vf-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.vf-lines path {
  fill: none;
  stroke: #d4d4d8;
  stroke-width: 1.4;
}
.vf-node {
  position: absolute;
  left: var(--x);
  top: var(--y);
  translate: -50% -50%;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-style: normal;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px 10px 4px 6px;
  box-shadow: 0 4px 10px -6px rgba(24, 24, 27, 0.25);
  white-space: nowrap;
}
.vf-node b {
  display: grid;
  place-items: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--field-2);
  color: var(--muted);
  font-size: 8.5px;
  font-weight: 700;
}
.vn-blue {
  box-shadow: inset 3px 0 0 #6b96d8, 0 4px 10px -6px rgba(24, 24, 27, 0.25);
}
.vn-red {
  box-shadow: inset 3px 0 0 var(--accent), 0 4px 10px -6px rgba(24, 24, 27, 0.25);
}
.vn-teal {
  box-shadow: inset 3px 0 0 #12977c, 0 4px 10px -6px rgba(24, 24, 27, 0.25);
}


/* --------------------------------------------- the section-tools window
   The edit toolbar demonstrated: the pill spotlights Format / Check /
   Complete while a caption names each, and the section answers with the
   portal's own waiting vocabulary (shimmer, tint, "checks out"). */
.win--tools {
  /* 30px over the board's settled edge — see .win--card. The left three sit
     in FRONT of the board rather than behind it, so they overlap it where
     the right pair tucks under, but the offset is the same and every right
     edge here lands at the same x. Widths differ, so the numbers do. */
  left: 36px;
  top: 140px;
  width: 300px;
  --tone: #ef562f;
  --tone-ink: #a5371b;
  --tone-wash: #fff5f2;
  --tone-line: #ffd5cc;
}
.tool-stage {
  position: relative;
  padding: 64px 10px 10px;
}
.tool-pill {
  position: absolute;
  left: 50%;
  top: 32px;
  bottom: auto;
  translate: -50%;
  opacity: 1;
  visibility: visible;
}
/* two stacked bodies — the dictated mess and the formatted result — so the
   swap is a cross-fade, never a rewrite; space is the taller of the two */
.tool-body {
  display: grid;
}
.tsb {
  grid-area: 1 / 1;
  transition: opacity 0.35s ease, visibility 0.35s;
}
.tsb ul {
  margin: 0;
  padding-left: 14px;
  list-style: disc;
}
.tsb-b {
  opacity: 0;
  visibility: hidden;
}
.tool-sec.is-fixed .tsb-a {
  opacity: 0;
  visibility: hidden;
}
.tool-sec.is-fixed .tsb-b {
  opacity: 1;
  visibility: visible;
}
/* the in-card proposal — SectionCard's review panel, in miniature */
.tool-diff {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--accent-line);
  border-radius: 10px;
  box-shadow: 0 10px 22px -12px rgba(24, 24, 27, 0.35);
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}
.tool-sec.is-diff .tool-diff {
  opacity: 1;
  visibility: visible;
}
.tool-diff-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border-bottom: 1px solid var(--accent-line);
  background: var(--accent-wash);
}
.tool-diff-head b {
  font-size: 10.5px;
  color: var(--ink);
}
.tool-diff-head small {
  font-size: 9.5px;
  color: var(--muted);
}
.tool-diff-head .sbtn {
  margin-left: auto;
  padding: 1.5px 7px;
  font-size: 9.5px;
}
.tool-diff .op-rows {
  overflow: hidden;
}
.tool-diff .op-rows .row {
  font-size: 9px;
  padding: 1.5px 7px;
}


/* ---------------------------------------------------------------- platform */
.platform {
  border-top: 1px solid var(--line);
  background: var(--surface);
  padding: clamp(64px, 9vw, 120px) 0;
}
.platform-head {
  max-width: 720px;
  margin-bottom: clamp(48px, 7vw, 88px);
}
.platform-head .eyebrow {
  margin-bottom: 20px;
}

.pillars {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
.pillar-rail {
  position: sticky;
  top: 96px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.pillar-rail a {
  position: relative;
  padding: 8px 0 8px 18px;
  font-size: 15.5px;
  font-weight: 600;
  color: var(--faint);
  transition: color 0.2s;
}
.pillar-rail a::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 2px;
  border-radius: 2px;
  background: transparent;
  transition: background-color 0.2s;
}
.pillar-rail a.is-active {
  color: var(--ink);
}
.pillar-rail a.is-active::before {
  background: var(--accent);
}

.pillar-flow {
  display: flex;
  flex-direction: column;
  gap: clamp(56px, 8vw, 104px);
  min-width: 0;
}
.pillar {
  scroll-margin-top: 110px;
}
.pillar > .statement {
  margin-bottom: 28px;
}

/* Attio's hairline cell grid: 1px gaps over the line colour. */
.cellgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.cell {
  background: var(--field);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.cell h3 {
  font-size: 15px;
  font-weight: 600;
}
.cell > p {
  font-size: 13.5px;
  color: var(--muted);
  max-width: 44ch;
}
.cell-demo {
  margin-top: 18px;
}
@media (max-width: 720px) {
  .cellgrid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 980px) {
  .pillars {
    grid-template-columns: 1fr;
  }
  .pillar-rail {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 2px 18px;
  }
  .pillar-rail a {
    padding-left: 0;
  }
  .pillar-rail a::before {
    display: none;
  }
}

/* demo panels inside cells — small, self-contained components */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  font-size: 12px;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.panel-body {
  padding: 12px;
}

.rowline {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
}
.rowline + .rowline {
  border-top: 1px solid var(--line-soft);
}
.rowline b {
  font-size: 12px;
  font-weight: 600;
}
.rowline small {
  color: var(--faint);
  font-size: 11px;
  margin-left: auto;
  white-space: nowrap;
}
.doc-ico {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  display: grid;
  place-items: center;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
}

/* merge diff */
.diff-line {
  padding: 5px 12px;
  font-size: 11.5px;
  line-height: 1.5;
  border-left: 3px solid transparent;
}
.diff-line--add {
  background: var(--ok-wash);
  border-left-color: var(--ok);
}
.diff-line--edit {
  background: var(--warn-wash);
  border-left-color: var(--warn);
}
.diff-actions {
  display: flex;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--line-soft);
}

/* section tools menu */
.menu {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: 0 8px 24px -12px rgba(24, 24, 27, 0.25);
  overflow: hidden;
  width: max-content;
  font-size: 12.5px;
}
.menu i {
  display: block;
  font-style: normal;
  padding: 7px 14px;
  color: var(--ink-2);
}
.menu i + i {
  border-top: 1px solid var(--line-soft);
}
.menu i.is-on {
  background: var(--accent-wash);
  color: var(--accent-ink);
  font-weight: 600;
}

/* spark — the AI mark (four-point star, from the portal) */
.spark {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* exam marking */
.mark-q {
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 600;
  border-bottom: 1px solid var(--line-soft);
}
.mark-ans {
  padding: 10px 12px;
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.55;
}
.mark-verdict {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--line-soft);
  background: var(--field-2);
  font-size: 11.5px;
  line-height: 1.5;
}
.mark-score {
  flex-shrink: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* read-along ticker */
.ticker {
  padding: 14px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--faint);
}
.ticker mark {
  background: none;
  color: var(--ink);
}
.ticker mark b {
  background: var(--accent-wash);
  color: var(--accent-ink);
  border-radius: 3px;
  padding: 0 3px;
  font-weight: 600;
}

/* feature grid — the Attio composition: the cellgrid plus one full-width
   stage row, satellites above and below in the same hairline grid. The
   stage is a `.feat-win`: the hero's window chrome (win-bar, dots, crumb)
   as an in-flow element rather than an absolutely-positioned one. The
   Board section is the reference; Cards/Exam/Lecture/Oral copy it. */
.featgrid .feat-main {
  grid-column: 1 / -1;
}
/* mode dividers — the portal's page-break grammar: small caps over a
   hairline to the edge. One per half when a section splits by mode. */
.featgrid .feat-div {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 24px 10px;
  background: var(--field);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.featgrid .feat-div::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-soft);
}
.feat-stage {
  display: grid;
  justify-items: center;
}
.feat-win {
  width: min(620px, 100%);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 24px 48px -24px rgba(24, 24, 27, 0.18);
  overflow: hidden;
  text-align: left;
}
.feat-win .win-bar {
  cursor: default;
  touch-action: auto;
}
.feat-win .win-meta {
  color: var(--muted);
}
/* the stage runs a size up from the hero's miniature review */
.fw-review .review-head b {
  font-size: 13px;
}
.fw-review .op-name b {
  font-size: 12.5px;
}
.fw-review .op-name small {
  font-size: 10.5px;
}
.fw-review .op-rows .row {
  font-size: 11px;
}
/* must-know core — CoreStrip's checklist as a satellite component */
.core-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 12px;
  font-size: 11.5px;
  line-height: 1.5;
}
.core-item + .core-item {
  border-top: 1px solid var(--line-soft);
}
.core-item .ck {
  margin-top: 3px;
}

/* --- exam marking, at the exam route's own grammar ------------------------
   The marked question is: a number, the question, a banded marks pill, the
   answer in a field, the marker's comment, a Missed list with the red minus,
   then the model answer behind the primary rule. `band()` in the portal is
   three tiers over 75 / 50, which is what --ok / --warn / --bad are here. */
.mk-band {
  flex-shrink: 0;
  border-radius: 999px;
  border: 1px solid;
  padding: 1px 8px;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.mk-band--ok {
  color: var(--ok);
  background: var(--ok-wash);
  border-color: #bbf7d0;
}
.mk-band--warn {
  color: var(--warn);
  background: var(--warn-wash);
  border-color: #fde68a;
}
.mk-band--bad {
  color: #b91c1c;
  background: #fef2f2;
  border-color: #fecaca;
}
.mk-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}

/* the paper's own score header — the portal's 64px banded ring plus the line
   of marks and time under the board's name */
.mk-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.mk-ring {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid;
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.mk-headline {
  display: block;
  min-width: 0;
}
.mk-headline b {
  display: block;
  font-size: 14px;
  font-weight: 600;
}
.mk-headline small {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

.mk-card {
  padding: 16px 18px 18px;
}
.mk-q {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.mk-no {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
  margin-top: 1px;
}
.mk-ask {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
}
.mk-ans {
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--field-2);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
.mk-note {
  margin-top: 12px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
.mk-missed {
  margin-top: 12px;
}
.mk-miss {
  display: flex;
  gap: 8px;
  margin-top: 4px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
.mk-miss b {
  color: #f87171;
  font-weight: 600;
}
/* the model answer sits behind the primary rule, as in the portal */
.mk-model {
  margin-top: 12px;
  padding-left: 12px;
  border-left: 2px solid var(--accent-line);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
.mk-model em {
  font-style: normal;
  font-weight: 600;
  color: var(--ink);
}
.mk-model em::after {
  content: " — ";
  color: var(--faint);
}

/* the MCQ answer sheet: the right option green, your pick tagged */
.mcq-ask {
  padding: 12px;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
}
.mcq-opts {
  display: grid;
  gap: 6px;
  padding: 0 12px 12px;
}
.mcq-opt {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
.mcq-gl {
  width: 10px;
  flex-shrink: 0;
  font-style: normal;
  text-align: center;
  font-weight: 700;
}
.mcq-opt span {
  flex: 1;
  min-width: 0;
}
.mcq-opt em {
  flex-shrink: 0;
  /* the row aligns to flex-start so a wrapped option still reads top-down;
     this tag is 9.5px against 12px text, so it needs the nudge back onto the
     first line's optical centre */
  margin-top: 2px;
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.7;
}
.mcq-opt--right {
  border-color: #bbf7d0;
  background: var(--ok-wash);
  color: #14532d;
  font-weight: 500;
}
.mcq .mk-model {
  margin: 0 12px 12px;
}

/* "Where marks go missing" — banded percentage, section, asked-count */
.weak-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
}
.weak-row + .weak-row {
  border-top: 1px solid var(--line-soft);
}
.weak-row .mk-band {
  width: 44px;
  text-align: center;
  padding: 1px 0;
}
.weak-row b {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.weak-row small {
  flex-shrink: 0;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

/* exam conditions — the sitting header's clock pill, which turns at 5:00 */
.ex-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
}
.ex-clock {
  flex-shrink: 0;
  border-radius: 999px;
  padding: 2px 9px;
  background: var(--ink);
  color: var(--surface);
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ex-clock.is-low {
  background: #dc2626;
}
.ex-answered {
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}
.ex-submit {
  margin-left: auto;
}

/* --- the recorded oral, at the recorded route's own grammar ---------------
   Question, recorder, then the mark. The score header and the bands are the
   exam section's mk-* components; only the recorder and the criteria rows are
   new. Nothing in here is a conversation — no turns, no live badge. */
.vv-state {
  margin-left: auto;
}
.vv-done {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--tone-ink);
}
.vv-ask {
  padding: 14px 16px 12px;
}
.vv-ask .mk-ask {
  margin-top: 4px;
}
.vv-rec {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px 12px;
  border-bottom: 1px solid var(--line-soft);
}
/* the meter is the input level while you speak, so here it is a flex child of
   the recorder row rather than the hero window's own footer strip */
.fw-oral .vv-rec .viva-meter {
  flex: 1;
  min-width: 0;
  height: 20px;
  padding: 0;
}
.fw-oral.is-marked .viva-meter i {
  animation: none;
  opacity: 0.25;
}
.vv-clock {
  flex-shrink: 0;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.vv-clock em {
  font-style: normal;
  font-weight: 500;
  font-size: 11px;
  color: var(--faint);
}
.vv-clock em::before {
  content: " / ";
}
/* The mark block is present the whole loop — dimmed while you are still
   talking, full when the mark lands. Hiding it outright left a third of the
   window empty for half the loop, which reads as broken rather than as
   waiting; and dimming is opacity, so nothing moves. */
.fw-oral.is-live:not(.is-marked) .vv-score,
.fw-oral.is-live:not(.is-marked) .vv-crit {
  opacity: 0.28;
}
.fw-oral.is-live .vv-score,
.fw-oral.is-live .vv-crit {
  transition: opacity 0.5s ease;
}
.vv-crit {
  display: grid;
  gap: 12px;
  padding: 4px 18px 18px;
}
.cr-row {
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  padding: 10px 12px;
}
.cr-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.cr-top b {
  font-size: 13px;
  font-weight: 600;
}
.cr-bar {
  display: block;
  margin-top: 8px;
  height: 5px;
  border-radius: 999px;
  background: var(--field-2);
  overflow: hidden;
}
.cr-bar i {
  display: block;
  height: 100%;
  width: calc(var(--p) * 100%);
  border-radius: 999px;
  background: var(--tone);
  transform-origin: left;
}
.cr-why {
  margin-top: 8px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
.cr-fix {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--faint);
}
.cr-fix em {
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.cr-fix em::after {
  content: " · ";
}

/* the transcript tab: the wording that cost you, tinted in place, and one of
   them opened out. Two tints only — the rose the route gives a knowledge gap
   and the amber it gives a thin answer. */
.wd-text {
  padding: 12px 14px;
  font-size: 12.5px;
  line-height: 1.9;
  color: var(--ink-2);
}
.wd-m {
  /* no side padding: the phrase butts straight onto the punctuation after it,
     and 3px of tint there reads as a space before the comma */
  border-radius: 3px;
  padding: 1px 0;
}
/* static truth (no JS, reduced motion): both phrases already tinted */
.wd-m--gap {
  background: #fef2f2;
  color: #b91c1c;
}
.wd-m--thin {
  background: var(--warn-wash);
  color: var(--warn);
}
/* live, they arrive one at a time as the marker works down the transcript */
.is-live .wd-m {
  background: transparent;
  color: inherit;
  transition: background-color 0.45s ease, color 0.45s ease;
}
.is-live .wd-m--gap.is-lit {
  background: #fef2f2;
  color: #b91c1c;
}
.is-live .wd-m--thin.is-lit {
  background: var(--warn-wash);
  color: var(--warn);
}
.wd-card {
  margin: 0 14px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  padding: 10px 12px;
}
.wd-tag {
  display: inline-block;
  border-radius: 999px;
  padding: 1px 8px;
  background: var(--warn-wash);
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--warn);
}
.wd-card blockquote {
  margin-top: 8px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.55;
  color: var(--ink-2);
}
.wd-card p {
  margin-top: 8px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
.wd-card small {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--faint);
}

/* the examiner strip, and the field it is resolved from */
.pe-field {
  display: flex;
  align-items: center;
  padding: 9px 12px;
  font-size: 13px;
  color: var(--ink);
}
.pe-field::after {
  content: "";
  width: 1px;
  height: 15px;
  margin-left: 2px;
  background: var(--accent);
}
.pe-strip {
  padding: 10px 12px;
}
.pe-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.pe-row b {
  font-size: 13px;
  font-weight: 600;
}
.pill--exam {
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.pe-emph {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 9px;
}
.pe-emph span {
  border-radius: 6px;
  background: var(--field-2);
  border: 1px solid var(--line);
  padding: 2px 7px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--muted);
}

/* one row of the Oral tab's "Ready to sit" table */
.st-head,
.st-row {
  display: grid;
  grid-template-columns: 1fr 3.5rem 2.5rem 2.5rem;
  align-items: start;
  gap: 8px;
  padding: 8px 12px;
}
.st-head {
  border-bottom: 1px solid var(--line-soft);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.st-head span:not(:first-child),
.st-row .st-n {
  text-align: right;
}
.st-name {
  display: block;
  min-width: 0;
}
.st-name b {
  font-size: 12.5px;
  font-weight: 600;
}
.st-name small {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.st-n {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.st-n--best {
  font-weight: 700;
  color: var(--ok);
}
.st-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 12px 12px;
}
.st-tags i {
  font-style: normal;
  border-radius: 6px;
  padding: 2px 7px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
}
.st-type {
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
  text-transform: uppercase;
}
.st-ready {
  background: var(--ok-wash);
  border: 1px solid #bbf7d0;
  color: var(--ok);
}
.st-pace {
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.st-tags small {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

@media (max-width: 720px) {
  /* three number columns leave the name ~90px; the opener truncates to an
     ellipsis and reads as a bug, so at this width the row is title + numbers */
  .st-name small {
    display: none;
  }
}

/* spoken feedback — the read-along walker over one line of the debrief */
.sp-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px;
}
.sp-play {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
}
.sp-play svg {
  width: 14px;
  height: 14px;
}
.sp-row .ticker {
  padding: 0;
  font-size: 12.5px;
  line-height: 1.65;
}

/* mini page picker — PdfPicker as a satellite component */
.mp-file {
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
  color: var(--ink);
}
.mp-count {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.mp-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 10px 12px;
  background: var(--field-2);
}

/* views mind map — the board redrawn, as an abstract visual */
.mindmap {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 6px;
}
.mindmap .mm-link {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.5;
}
.mindmap .mm-node {
  transform-box: fill-box;
  transform-origin: center;
}
.mindmap .mm-node rect {
  fill: var(--surface);
  stroke: var(--line);
}
.mindmap .mm-root rect {
  fill: var(--accent-wash);
  stroke: var(--accent-line);
}
.mindmap text {
  font-size: 10.5px;
  font-weight: 600;
  fill: var(--ink);
  text-anchor: middle;
}
.mindmap .mm-root text {
  fill: var(--accent-ink);
}

/* --- audio lectures, at the lecture route's own grammar -------------------
   The route is two screens: pick a length and the sections, then listen —
   the player over a transcript panel whose every word is a seek target. The
   picker's section chips carry the board's heat, because that is what decides
   which section is worth ten minutes; ±15s is the phone player's own step. */

/* the picker: a length is chosen from four, and the sections it may draw on */
.lec-ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  padding: 10px 12px;
}
.lec-ask b {
  font-size: 12px;
  font-weight: 600;
}
.lec-mins {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: 8px;
  background: var(--field-2);
}
.lec-mins i {
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  padding: 3px 7px;
  border-radius: 6px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.lec-mins i.is-on {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 2px 6px;
  color: var(--accent-ink);
}
.lec-secs {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 0 12px 12px;
}
.lec-secs i {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-style: normal;
  font-size: 11.5px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: var(--accent-wash);
  color: var(--accent-ink);
}
/* the board's heat, on the chip: the same two bands the overlay paints —
   `weak` red, `shaky` amber (heat.ts / the board route's TONE) */
.lec-secs i::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: var(--line);
}
.lec-secs i.sec-hot::before {
  background: rgb(239 68 68 / 0.85);
}
.lec-secs i.sec-warm::before {
  background: rgb(245 158 11 / 0.85);
}
.lec-why {
  padding: 0 12px 12px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--faint);
}

/* the proposed set: a title, the one line that separates it from the others,
   and the length it was planned for */
.idea {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 12px;
}
.idea + .idea {
  border-top: 1px solid var(--line-soft);
}
.idea-main {
  min-width: 0;
  flex: 1;
}
.idea-main b {
  display: block;
  font-size: 12px;
  font-weight: 600;
}
.idea-main small {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
}
.idea i {
  flex-shrink: 0;
  font-style: normal;
  font-size: 10.5px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--field-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* the listening stage */
.lec-top {
  padding: 14px 18px 0;
}
.lec-top b {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
}
.lec-top small {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}
.lec-player {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
}
.lec-play {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--tone);
  display: grid;
  place-items: center;
}
.lec-play svg {
  width: 14px;
  height: 14px;
  fill: #fff;
  margin-left: 1px;
}
/* ±15s — the step the phone player uses (BoardLectureScreen's `nudge`). The
   numeral rides inside the arc, so the icon has to be big enough to read it:
   26px puts "15" at about 10px. */
.lec-skip {
  flex-shrink: 0;
  color: var(--ink-2);
}
.lec-skip svg {
  display: block;
  width: 26px;
  height: 26px;
}
.lec-skip text {
  fill: currentColor;
  stroke: none;
  font-size: 9px;
  font-weight: 700;
  text-anchor: middle;
  font-family: var(--font-ui);
}
/* the scrubber keeps a floor and wraps rather than being squeezed to a stub:
   below ~560px the three controls fill row one and the bar takes row two */
.lec-bar {
  flex: 1 1 140px;
  height: 4px;
  border-radius: 2px;
  background: var(--tone-line);
  overflow: hidden;
}
.lec-fill {
  display: block;
  height: 100%;
  background: var(--tone);
  transform-origin: left;
  transform: scaleX(var(--p, 0.45));
}
.lec-time {
  flex-shrink: 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-2);
}
.lec-time em {
  font-style: normal;
  color: var(--faint);
}
.lec-time em::before {
  content: " / ";
}
.lec-speed {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 18px 14px;
  border-bottom: 1px solid var(--line-soft);
}
.lec-speed i {
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.lec-speed i.is-on {
  background: var(--tone-wash);
  border: 1px solid var(--tone-line);
  padding: 1px 7px;
  color: var(--tone-ink);
}
.lec-transcript {
  padding: 14px 18px 18px;
}
.lec-script {
  margin-top: 8px;
}
.lec-script p {
  font-size: 13.5px;
  line-height: 1.9;
  color: var(--ink-2);
}
.lec-script p + p {
  margin-top: 10px;
}


/* ---------------------------------------------------------------- dark band */
.band {
  background: var(--d-field);
  color: var(--d-ink);
  padding: clamp(72px, 10vw, 136px) 0;
}
.band-head {
  text-align: center;
  max-width: 760px;
  margin-inline: auto;
}
.band-head .eyebrow {
  background: var(--d-surface);
  border-color: var(--d-line);
  color: var(--d-muted);
  margin-bottom: 22px;
}
.band-head h2 {
  font-size: clamp(2.2rem, 5vw, 3.6rem);
}
.band-lead {
  margin-top: 20px;
  font-size: clamp(1rem, 1.5vw, 1.1rem);
  color: var(--d-muted);
  max-width: 58ch;
  margin-inline: auto;
}
.band-tiles {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1px;
  background: var(--d-line);
  border: 1px solid var(--d-line);
  border-radius: var(--radius);
  overflow: hidden;
  margin-top: clamp(48px, 6vw, 80px);
}
.band-tile {
  background: var(--d-field-2);
  padding: 24px 20px;
}
.band-tile svg {
  width: 20px;
  height: 20px;
  color: var(--d-faint);
  margin-bottom: 32px;
}
.band-tile b {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
}
.band-tile p {
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--d-muted);
}
@media (max-width: 980px) {
  .band-tiles {
    grid-template-columns: repeat(2, 1fr);
  }
  .band-tile:last-child {
    grid-column: span 2;
  }
}
@media (max-width: 560px) {
  .band-tiles {
    grid-template-columns: 1fr;
  }
  .band-tile:last-child {
    grid-column: auto;
  }
}

/* ---------------------------------------------------------------- app */
.app-section {
  background: var(--surface);
  border-top: 1px solid var(--line);
  padding: clamp(64px, 9vw, 120px) 0;
}
.app-layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 6vw, 80px);
  align-items: center;
}
.app-copy .eyebrow {
  margin-bottom: 18px;
}
.app-copy h2 {
  font-size: clamp(1.9rem, 3.6vw, 2.6rem);
}
.app-copy > p {
  margin-top: 16px;
  color: var(--muted);
}
.app-points {
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 14.5px;
}
.app-points li {
  position: relative;
  padding-left: 24px;
}
.app-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
.app-note {
  margin-top: 20px;
  font-size: 13px;
  color: var(--faint);
}

/* phone frame — the animation slot for the app demo */
.phone {
  width: min(300px, 76vw);
  margin-inline: auto;
  border: 1px solid var(--line);
  border-radius: 36px;
  padding: 10px;
  background: var(--field);
}
.phone-screen {
  border-radius: 27px;
  background: #131316;
  color: var(--d-ink);
  aspect-ratio: 9 / 18.5;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: 11px;
}
.phone-notch {
  width: 84px;
  height: 20px;
  border-radius: 999px;
  background: #000;
  margin: 8px auto 4px;
  flex-shrink: 0;
}
.phone-body {
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.phone-title {
  font-size: 15px;
  font-weight: 700;
  margin: 4px 2px 2px;
}
.phone-row {
  background: #1e1e23;
  border: 1px solid #2e2e35;
  border-radius: 12px;
  padding: 9px 11px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.phone-row b {
  font-size: 11.5px;
  font-weight: 600;
}
.phone-row small {
  color: var(--d-faint);
  font-size: 10px;
}
.phone-row .pill--due {
  background: rgba(239, 86, 47, 0.15);
  border-color: rgba(239, 86, 47, 0.35);
  color: #ffb49b;
}
@media (max-width: 860px) {
  .app-layout {
    grid-template-columns: 1fr;
  }
  .app-visual {
    order: -1;
  }
}

/* ---------------------------------------------------------------- CTA */
.cta {
  background: var(--d-field);
  color: var(--d-ink);
  padding: clamp(80px, 11vw, 150px) 0;
  text-align: center;
}
.cta h2 {
  font-size: clamp(2.2rem, 5vw, 3.4rem);
}
.cta p {
  margin: 16px auto 0;
  color: var(--d-muted);
  max-width: 48ch;
}
.cta-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 30px;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------- footer */
.site-footer {
  background: var(--d-field);
  color: var(--d-muted);
  border-top: 1px solid var(--d-line);
  padding: clamp(48px, 6vw, 72px) 0 40px;
}
.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 4fr) repeat(3, minmax(0, 2fr));
  gap: 40px 24px;
}
.footer-brand {
  color: #f1e8dc; /* the wordmark's cream tone */
  display: flex;
  align-items: center;
  gap: 12px;
}
.footer-brand .brand-mark {
  width: 44px;
  height: 44px;
}
.footer-lockup {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
}
.footer-lockup strong {
  font-family: var(--font-wordmark);
  font-weight: 600;
  font-size: 26px;
  letter-spacing: -0.01em;
}
/* Tagline tracked out to the wordmark's width — matched in static CSS, like
   styles.css does; nudge the spacing if either face ever changes. */
.footer-lockup small {
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.164em;
  margin-right: -0.164em;
  color: var(--d-muted);
}
.footer-col b {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--d-faint);
  margin-bottom: 14px;
}
.footer-col nav {
  display: flex;
  flex-direction: column;
  gap: 9px;
  font-size: 14px;
}
.footer-col a:hover {
  color: var(--d-ink);
}
.footer-base {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: clamp(40px, 6vw, 64px);
  padding-top: 24px;
  border-top: 1px solid var(--d-line);
  font-size: 12.5px;
  color: var(--d-faint);
}
.lang-select {
  appearance: none;
  background: var(--d-surface);
  border: 1px solid var(--d-line);
  color: var(--d-muted);
  font-family: inherit;
  font-size: 12.5px;
  border-radius: 8px;
  padding: 6px 28px 6px 10px;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a8a94' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
@media (max-width: 860px) {
  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
  .footer-brand {
    grid-column: 1 / -1;
  }
}

/* ------------------------------------------------- stage & vignette animation
   Driven by stage.js. Every rule here is inert until it adds `.is-live` to a
   timeline's root, so no-JS and prefers-reduced-motion keep the static
   composition. The waiting vocabulary copies the portal's patterns
   (hosting/src/app.css): shimmer sweep, indeterminate sweep, and the
   hold-then-fade merge tints. Transform/opacity/clip only — nothing moves
   layout mid-loop. */

/* window dragging */
.win {
  translate: var(--dx, 0px) var(--dy, 0px);
}
.win-bar {
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

/* generic vocabulary: JS marks animated items `a` + a state class */
.is-live .a {
  transition: opacity 0.45s ease, transform 0.45s ease, clip-path 0.9s ease,
    background-color 0.5s ease, border-color 0.5s ease, filter 0.5s ease;
}
.is-live .a.a-out {
  opacity: 0;
  transform: translateY(8px);
}
.is-live .a.a-up {
  opacity: 0;
  transform: translateY(-14px);
}
.is-live .a.a-pop {
  opacity: 0;
  transform: scale(0.92);
}
.is-live .a.a-veil {
  clip-path: inset(0 100% 0 0);
}
@keyframes a-settle {
  0% {
    transform: scale(0.9);
  }
  60% {
    transform: scale(1.07);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes a-shimmer {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}

/* a scratchpad click radiates the same ring the Views nudge uses */
.is-live .scratch .sbtn.is-hit,
.is-live .scratch .sbtn.primary.a-wait.is-hit {
  animation: a-settle 0.5s ease, a-ring 0.7s ease-out;
}

/* waiting buttons / grades: dim until `is-hit` fills them accent */
.is-live .btn.a-wait,
.is-live .sbtn.primary.a-wait,
.is-live .card-grades i.is-on.a-wait {
  background: var(--field-2);
  border-color: var(--line);
  color: var(--muted);
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}
.is-live .btn.a-wait.is-hit,
.is-live .sbtn.primary.a-wait.is-hit,
.is-live .card-grades i.is-on.a-wait.is-hit {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  animation: a-settle 0.5s ease;
}
.is-live .is-stamp {
  animation: a-settle 0.5s ease;
}

/* shimmer pass — the portal's .skeleton sweep, as an overlay so text stays */
.sec,
.menu i {
  position: relative;
}
.is-live .is-scan::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent 30%,
    rgba(161, 161, 170, 0.22) 50%,
    transparent 70%
  );
  background-size: 300% 100%;
  animation: a-shimmer 1.3s ease-in-out infinite;
}

/* merge tints — the portal's hold-then-fade .flash-add/.flash-edit */
.is-live .a-flash-add {
  animation: a-flash-add 3s ease-out;
}
.is-live .a-flash-edit {
  animation: a-flash-edit 3s ease-out;
}
@keyframes a-flash-add {
  0%,
  55% {
    background-color: var(--ok-wash);
    box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.45);
  }
  100% {
    background-color: var(--surface);
    box-shadow: 0 0 0 2px rgba(22, 163, 74, 0);
  }
}
@keyframes a-flash-edit {
  0%,
  55% {
    background-color: var(--warn-wash);
    box-shadow: 0 0 0 2px rgba(217, 119, 6, 0.45);
  }
  100% {
    background-color: var(--surface);
    box-shadow: 0 0 0 2px rgba(217, 119, 6, 0);
  }
}

/* pill pops — box-shadow rings, safe on inline elements */
@keyframes a-ring {
  0% {
    box-shadow: 0 0 0 0 rgba(239, 86, 47, 0.45);
  }
  100% {
    box-shadow: 0 0 0 14px rgba(239, 86, 47, 0);
  }
}
@keyframes a-ring-ok {
  0% {
    box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.4);
  }
  100% {
    box-shadow: 0 0 0 10px rgba(22, 163, 74, 0);
  }
}
.is-live .rowline.is-ring {
  animation: a-ring 1s ease-out;
  border-radius: 8px;
}
.is-live .scr-item.is-ring {
  animation: a-ring 1s ease-out;
}
.is-live .pill--ok.is-hit {
  animation: a-ring-ok 0.9s ease-out;
}

/* digit spans (setCount) inherit their pill's weight */
.n {
  font-weight: inherit;
  font-style: normal;
}

/* busy button — the portal's indeterminate .progress-sweep */
.is-live .btn.is-busy {
  position: relative;
  overflow: hidden;
  color: rgba(255, 255, 255, 0.4);
  transition: color 0.3s ease;
}
.is-live .btn.is-busy::after {
  content: "";
  position: absolute;
  inset-block: 0;
  left: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.8), transparent);
  animation: a-sweep 1.15s ease-in-out infinite;
}
@keyframes a-sweep {
  0% {
    transform: translateX(-110%);
  }
  100% {
    transform: translateX(260%);
  }
}

/* merge review lines settle to plain once approved */
.is-live .diff-line.is-settled {
  background: var(--field);
  border-left-color: var(--line);
}

/* word highlight (lecture bar + read-along ticker) — a window's tone can
   override via --hl-bg/--hl-ink; everything else falls back to accent */
.is-live .w.is-lit {
  background: var(--hl-bg, var(--accent-wash));
  color: var(--hl-ink, var(--accent-ink));
  border-radius: 3px;
  box-shadow: 0 0 0 2px var(--hl-bg, var(--accent-wash));
}
.stage.is-live .lecture p mark,
.ticker.is-live mark {
  background: none;
  color: inherit;
}
.ticker.is-live mark b {
  background: none;
  color: inherit;
  padding: 0;
  font-weight: inherit;
}

/* hero stage states */
.stage.is-live .win--notif {
  opacity: 0;
  transform: translateX(-28px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.stage.is-live .win--notif.is-in {
  opacity: var(--sat, 1);
  transform: none;
}

/* flashcard faces: face 2 stacked over face 1, cross-faded — never text edits */
.card-faces {
  position: relative;
}
.card-face--2 {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: var(--surface);
}
.is-live .card-face {
  transition: opacity 0.45s ease, transform 0.45s ease;
}
.is-live .card-face--2 {
  transform: translateX(20px);
}
.is-live .show-2 .card-face:first-child {
  opacity: 0;
  transform: translateX(-20px);
}
.is-live .show-2 .card-face--2 {
  opacity: 1;
  transform: none;
}
.is-live .card-a {
  transition: filter 0.5s ease, opacity 0.5s ease;
}
.is-live .qa-hidden .card-a {
  filter: blur(5px);
  opacity: 0.4;
}

/* viva bars breathe — scaleY, so layout never moves */
.is-live .viva-meter i {
  transform-origin: bottom;
  animation: a-breathe 1s ease-in-out infinite alternate;
}
.is-live .viva-meter i:nth-child(2n) {
  animation-duration: 0.8s;
  animation-delay: -0.35s;
}
.is-live .viva-meter i:nth-child(3n) {
  animation-duration: 1.25s;
  animation-delay: -0.6s;
}
@keyframes a-breathe {
  from {
    transform: scaleY(0.3);
  }
  to {
    transform: scaleY(1);
  }
}

/* ---------------------------------------------------------------- reveal */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s ease, transform 0.6s ease;
  transition-delay: var(--delay, 0ms);
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --------------------------------------- the blank board → paper → landing
   The loop opens on the portal's empty state (Board.svelte's "pick a way
   in"), a preprint is dragged onto the PDF row, PdfPicker's modal ticks its
   pages and measures them, and the sections land one by one — only then
   does the authoring story begin. All driven by stage.js. */
.board-empty {
  position: absolute;
  inset: 38px 0 0;
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--field-2);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s ease, visibility 0.35s;
}
.win--board.is-blank .board-empty {
  opacity: 1;
  visibility: visible;
}
.win--board.is-blank .board-title,
.win--board.is-blank .board-cols {
  visibility: hidden;
}
.empty-card {
  width: min(560px, 100%);
  text-align: center;
}
.empty-crumb {
  display: flex;
  justify-content: center;
  gap: 7px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.empty-crumb i {
  font-style: normal;
}
.empty-name {
  margin-top: 10px;
  font-family: var(--font-wordmark);
  font-weight: 600;
  font-size: 26px;
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--ink);
}
.empty-sub {
  margin-top: 7px;
  font-size: 12.5px;
  color: var(--muted);
}
.empty-ways {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
}
.way {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.way-ico {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 9px;
  background: var(--field-2);
  color: var(--ink-2);
  flex-shrink: 0;
  transition: background-color 0.25s ease, color 0.25s ease;
}
.way-ico svg {
  width: 13px;
  height: 13px;
}
.way-text {
  min-width: 0;
}
.way-text b {
  display: block;
  font-size: 12.5px;
  color: var(--ink);
}
.way-text small {
  display: block;
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--muted);
}
.way-chev {
  margin-left: auto;
  align-self: center;
  font-style: normal;
  color: var(--faint);
}
/* the row the paper is held over — the portal's hover ring */
.stage.is-live .way.is-over {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(239, 86, 47, 0.14);
}
.stage.is-live .way.is-over .way-ico {
  background: var(--accent-wash);
  color: var(--accent-ink);
}

/* the paper: in from the window's top-left corner, shrinking as it travels
   to the PDF row. --gx/--gy are measured by stage.js so it lands on the row
   at any width. */
.drag-ghost--paper {
  left: -30px;
  top: -70px;
  width: 250px;
  transform: rotate(-5deg);
}
.drag-ghost--paper img {
  border-radius: 4px;
}
.drag-ghost--paper.is-drag {
  transform: translate(var(--gx, 520px), var(--gy, 320px)) rotate(2deg) scale(0.42);
  transition: transform 1.5s cubic-bezier(0.45, 0.08, 0.35, 1), opacity 0.3s ease;
}
.drag-ghost--paper.is-drop {
  opacity: 0;
  transform: translate(var(--gx, 520px), var(--gy, 320px)) rotate(1deg) scale(0.3);
  transition: transform 0.35s ease, opacity 0.3s ease;
}

/* the page picker — PdfPicker's modal in miniature */
.picker {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgba(24, 24, 27, 0.35);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}
.win--board.is-picker .picker {
  opacity: 1;
  visibility: visible;
}
.picker-panel {
  width: min(800px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 24px 48px -12px rgba(24, 24, 27, 0.35);
  overflow: hidden;
  transform: translateY(10px) scale(0.985);
  transition: transform 0.3s ease;
}
.win--board.is-picker .picker-panel {
  transform: none;
}
.picker-head {
  display: flex;
  align-items: flex-start;
  padding: 10px 14px 9px;
  border-bottom: 1px solid var(--line-soft);
}
.picker-head b {
  font-size: 13px;
  color: var(--ink);
}
.picker-head p {
  margin-top: 1px;
  font-size: 10.5px;
  color: var(--muted);
}
.picker-count {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 10.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
.picker-bar {
  width: 120px;
  height: 6px;
  border-radius: 3px;
  background: var(--line);
  overflow: hidden;
}
.picker-bar i {
  display: block;
  width: 60%;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
}
.picker-acts {
  margin-left: auto;
  display: inline-flex;
  gap: 4px;
}
.picker-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
  padding: 10px 14px;
  background: var(--field-2);
}
.pg {
  margin: 0;
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 2px var(--accent-line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
}
.pg img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  object-position: top;
  background: var(--field-2);
}
.pg figcaption {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 6px;
  font-size: 9.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.pg figcaption .ck {
  margin-top: 0;
}
.picker-foot {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-top: 1px solid var(--line-soft);
}
.picker-note {
  flex: 1;
  min-width: 0;
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Busy.svelte's compact row, in the footer's left slot once Add is clicked */
.picker-busy {
  flex: 1;
  min-width: 0;
  display: none;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px solid var(--accent-line);
  border-radius: 8px;
  background: var(--accent-wash);
}
.picker.is-busy .picker-note {
  display: none;
}
.picker.is-busy .picker-busy {
  display: flex;
}
.picker-clock {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--accent-ink);
}
.picker.is-busy .sb-addpages {
  opacity: 0.5;
}

/* the sections landing: each card rises in on its own delay (stage.js sets
   --land in reading order). `backwards`, never `forwards` — a held end
   keyframe would pin transform and break the card drag later in the loop,
   so the class comes off again once the last card is down. */
@keyframes a-land {
  0% {
    opacity: 0;
    transform: translateY(10px) scale(0.96);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}
.stage.is-live .win--board.is-landing .board-title {
  animation: a-land 0.45s ease backwards;
}
.stage.is-live .win--board.is-landing .sec {
  animation: a-land 0.55s ease backwards;
  animation-delay: var(--land, 0ms);
}
/* the loop's cut back to blank: the built board fades before the reset */
.win--board.is-cut .board-title,
.win--board.is-cut .board-cols {
  opacity: 0;
  transition: opacity 0.4s ease;
}

/* --- Board feature section: the reference vignettes the other stages copy.
   The rules follow the standing pattern — everything below is inert until
   stage.js puts `.is-live` on the vignette's own root, and the static
   (no-JS / reduced-motion) composition is the finished state: pages
   ticked, rows revealed, links drawn. reset() ADDS the waiting classes. */

/* the mini picker: a page starts unticked (is-off) and ticks on */
.is-live .mpg {
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.is-live .mpg .ck,
.is-live .op-rows .row .ck {
  transition: background-color 0.25s ease, box-shadow 0.25s ease;
}
.is-live .mpg .ck::after,
.is-live .op-rows .row .ck::after {
  transition: opacity 0.2s ease;
}
.is-live .mpg.is-off {
  border-color: var(--line);
  box-shadow: 0 0 0 2px transparent;
}
.is-live .mpg.is-off .ck {
  background: var(--field-2);
  box-shadow: inset 0 0 0 1px var(--line);
}
.is-live .mpg.is-off .ck::after {
  opacity: 0;
}

/* the merge stage: a diff row reconsidered — unticked, it dims out */
.is-live .op-rows .row {
  transition: opacity 0.3s ease;
}
.is-live .op-rows .row.is-off {
  opacity: 0.45;
}
.is-live .op-rows .row.is-off .ck {
  background: var(--field-2);
  box-shadow: inset 0 0 0 1px var(--line);
}
.is-live .op-rows .row.is-off .ck::after {
  opacity: 0;
}
/* the diff rows also veil-reveal; they carry .a, whose transition list is
   the generic one — this opacity rule must not clobber it */
.is-live .op-rows .row.a {
  transition: opacity 0.45s ease, transform 0.45s ease, clip-path 0.9s ease,
    background-color 0.5s ease, border-color 0.5s ease, filter 0.5s ease;
}

/* the mind map draws itself: pathLength="1" normalises every link's dash,
   so one offset strokes them all in; nodes pop via the generic .a verbs */
.is-live .mm-link,
.is-live .draw {
  stroke-dasharray: 1;
  transition: stroke-dashoffset 0.7s ease;
}
.is-live .mm-link.a-undrawn,
.is-live .draw.a-undrawn {
  stroke-dashoffset: 1;
}

/* --- Cards feature section (PLATFORM_PLAN.md session 1). Same rules as the
   Board block above: everything is inert until stage.js puts `.is-live` on
   the vignette's own root, and the static composition is the finished state
   — curves drawn, dot on the last tick, the first interval row showing. */

/* stacked cross-fade: the only honest way to "change" translated text. The
   base state (one child carries `on`) is NOT is-live-scoped, or the no-JS
   page would stack all three rows on top of each other. */
.xfade {
  display: grid;
}
.xfade > * {
  grid-area: 1 / 1;
  opacity: 0;
}
.xfade > .on {
  opacity: 1;
}
.is-live .xfade > * {
  transition: opacity 0.5s ease;
}
.iv-rows .card-grades {
  padding: 0;
}

/* the spacing curve. The claim is "reviews land just before you'd forget", so
   the drawing is built on a fixed forget-threshold: every decay ends ON it and
   what visibly grows is how long each one takes to get there. The dot rides
   the review points and comes to rest where the NEXT review is due — on the
   threshold at 60d, which is the sentence, drawn. Positions are in user units
   (SVG transforms always are), so `--x`/`--y` are viewBox coordinates. */
.spacing {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
.sr-thresh {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}
.sr-ticks path {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
}
.sr-lift {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
}
.sr-curve {
  fill: none;
  stroke: var(--faint);
  stroke-width: 1.6;
  stroke-linecap: round;
}
.sr-dot {
  fill: var(--accent);
  transform: translate(var(--x, 314px), var(--y, 84px));
}
.is-live .sr-dot {
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.sr-lab text {
  fill: var(--faint);
  font-size: 9px;
  font-weight: 600;
  text-anchor: middle;
}

/* the drill stage — the hero's card window at readable scale */
.fw-cards .win-bar .pill {
  margin-left: auto;
}
/* the divider above the grades belongs to the deck, not to one face: face 2
   is absolutely positioned and shorter, so its own border would float */
.fw-cards .card-faces {
  border-bottom: 1px solid var(--line-soft);
}
.fw-cards .card-a {
  border-bottom: 0;
}
.fc-prov {
  padding: 14px 18px 0;
}
.fw-cards .card-q {
  padding: 14px 18px 8px;
  font-size: 15px;
}
.fw-cards .card-a {
  padding: 0 18px 18px;
  font-size: 14px;
}
.fw-cards .card-grades {
  padding: 14px 18px;
  gap: 8px;
}
/* Again is the only tinted grade — the portal's rule (Cards.svelte): it is
   the one that costs something, and four tinted buttons read as four warnings */
.fw-cards .card-grades i:first-child {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.is-live .card-grades i.is-press {
  animation: a-settle 0.5s ease;
  border-color: var(--ink);
  color: var(--ink);
}
.is-live .fw-cards .card-grades i:first-child.is-press {
  border-color: var(--accent);
  color: #fff;
}

/* --- Exam + Oral feature sections (PLATFORM_PLAN.md session 2). Same rules
   as the Board and Cards blocks: inert until stage.js puts `.is-live` on the
   vignette's own root, and the static composition is the finished state —
   the paper fully marked, the answer sheet ticked, the transcript at three
   turns with its debrief showing. reset() ADDS the waiting classes. */


/* the answer sheet: an option is grey until it is marked, and the "your
   answer" tag arrives with the tick */
.is-live .mcq-opt {
  transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.is-live .mcq-opt em,
.is-live .mcq-gl {
  transition: opacity 0.25s ease;
}
.is-live .mcq-opt.is-blank {
  border-color: var(--line);
  background: var(--surface);
  color: var(--muted);
  font-weight: 400;
}
.is-live .mcq-opt.is-blank em,
.is-live .mcq-opt.is-blank .mcq-gl {
  opacity: 0;
}

/* the resit control has nothing to fill (it is already the soft accent), so
   the click is the portal's own press: settle plus the accent ring */
.is-live .btn--soft.is-hit {
  animation: a-settle 0.5s ease, a-ring 0.7s ease-out;
}

/* exam conditions: the clock is a plain colour swap, which is what the
   portal does at five minutes — bg-gray-900 → bg-red-600 */
.is-live .ex-clock {
  transition: background-color 0.4s ease;
}

/* The viva turns, the readiness pill and the marks pill all carry `.a` and
   ride the generic verbs (a-out / a-pop / is-stamp) — nothing new needed. */

/* --- Lecture feature section (PLATFORM_PLAN.md session 3). Same rules as the
   blocks above: inert until stage.js puts `.is-live` on the vignette's own
   root, and the static composition is the finished state — the sections
   picked, the ideas listed, every chunk spoken, the ticks drawn. The stage's
   read-along is the section's always-on element: one clock writes the lit
   word, the digits and the bar together, so the three can never disagree. */

/* the picker: a section chip is unpicked until it is picked */
.is-live .lec-secs i {
  transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.is-live .lec-secs i.is-off {
  background: var(--surface);
  border-color: var(--line);
  color: var(--muted);
}

/* the read-along. The lit word is the shared `.w.is-lit` rule (teal here, off
   the window's own --hl-bg); what is BEHIND the playhead greys out, which is
   what the route does — a highlight with no wake reads as a random word
   rather than as playback. */
.is-live .lec-script .w {
  transition: color 0.3s ease;
}
.is-live .lec-script .w.is-said {
  color: var(--faint);
}

