/* ------------------------------------------------------------------ */
/* Brand font — local files only (Demo/assets/fonts), no CDN/network.  */
/* Only two weights exist (400/700) — never use 500/600 anywhere below */
/* (synthetic-bold looks broken); map 500/600 design-spec weights to   */
/* the nearest of 400/700.                                            */
/* ------------------------------------------------------------------ */

@font-face {
  font-family: 'Sarabun';
  src: url('fonts/Sarabun-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Sarabun';
  src: url('fonts/Sarabun-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Brand (SuperApp ui-kit design tokens) */
  --brand-primary: #034ea1;
  --brand-primary-hover: #033f82;
  --brand-primary-active: #023166;
  --brand-primary-subtle: #e6f0fa;
  --brand-primary-subtle-hover: #cde5fd;
  --brand-primary-subtle-active: #acd5fe;
  --focus-ring: #11ad95;

  /* Neutrals */
  --bg: #f7f7f7;
  --bg-panel: #ffffff;
  --bg-panel-2: #eeeeee;
  --border: #d9d9d9;
  --border-strong: #bfbfbf;

  --text: #2e2e2e;
  --text-muted: #565656;
  /* Token sheet's text_muted (#8C8C8C) fails 4.5:1 on white (~3.4:1) —
     kept out of the palette; this darker neutral gray (~5.3:1) is used
     wherever tertiary/footnote text needs to stay readable. */
  --text-dim: #6b6b6b;

  /* Diagram actor categories — no SuperApp token maps to these 8 kinds,
     so this custom, individually contrast-checked (>=4.5:1 on white)
     categorical palette is kept from the previous pass. */
  --c-browser: #0369a1;
  --c-frontend: #0e7490;
  --c-gateway: #6d28d9;
  --c-backend: #15803d;
  --c-idp: #4338ca;
  --c-db: #c2410c;
  --c-attacker: #b91c1c;
  --c-external: #475569;

  /* Status (control steps) — mapped to SuperApp semantic tokens.
     status-poc's text is darkened from the spec's #946D01 (~3.9:1 on its
     own #FEEFC8 chip bg) to #6b4e00 (~6.8:1) to clear the 4.5:1 gate. */
  --status-live: #197d3f;
  --status-live-bg: #daffe5;
  --status-poc: #6b4e00;
  --status-poc-bg: #feefc8;
  --status-spec: #284aa9;
  --status-spec-bg: #c5dcff;

  /* Step kind (threat/gap/note) — threat maps to semantic error; gap has
     no matching token (kept as a previously-verified custom orange);
     note uses a contrast-safe blue (SuperApp's accent #0F78ED measures
     ~4.3:1 on white as text, so a safer blue is used here instead). */
  --kind-threat: #a81a1a;
  --kind-threat-bg: #ffd7d7;
  --kind-gap: #c2410c;
  --kind-note: #2563eb;
  --kind-control: var(--text-dim);

  --arrow-color: #b0b0b0;
  --arrow-color-active: var(--brand-primary);

  /* Shape */
  --radius-card: 8px;
  --radius-btn: 12px;
  --radius-btn-sm: 8px;
  --radius-chip: 4px;
  --radius-pill: 999px;
  --shadow-card: 0 0 4.534px rgba(139, 139, 139, 0.2);
  --shadow-panel: 0 4px 20px 0 rgba(139, 139, 139, 0.25);
}

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Sarabun', 'Leelawadee UI', 'Segoe UI', Tahoma, sans-serif;
  font-size: 16px;
  line-height: 1.5;
}

button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

h1, h2, h3, h4 {
  margin: 0 0 0.4em 0;
  font-weight: 700;
}

p {
  margin: 0 0 0.6em 0;
}

summary {
  cursor: pointer;
}

a, button, [tabindex] {
  outline: none;
}

a:focus-visible, button:focus-visible, [tabindex]:focus-visible, summary:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

#app {
  /* Wide enough that 1920px viewports get real extra width (cards, lanes,
     caption column) instead of a centered 1400px island with empty gutters
     — still capped so lines don't run edge-to-edge on very wide monitors.
     No effect at 1280/1600px (viewport is already narrower than this). */
  max-width: 1700px;
  margin: 0 auto;
  /* --app-vpad = top+bottom padding, kept in sync manually — used by
     [data-testid="stage"] to compute a full-viewport-height flex column. */
  --app-vpad: 34px;
  padding: 14px 24px 20px;
}

/* ---------------------------------------------------------------- */
/* Hub */
/* ---------------------------------------------------------------- */

/* Hub content (status panel + card grid) is naturally short and can't be
   stretched to fill a tall viewport — center it vertically instead of
   leaving it pinned to the top with a large empty gap below, so the gap
   reads as an intentional layout choice rather than unfinished content.
   `safe center` falls back to top-alignment the moment content is taller
   than the viewport, so the first card never becomes unreachable and
   1280x720 (where content already nearly fills the space) keeps behaving
   the same as before. */
#hub-view[hidden] {
  display: none;
}

#hub-view {
  display: flex;
  flex-direction: column;
  justify-content: safe center;
  min-height: calc(100vh - var(--app-vpad));
}

.hub-header {
  margin-bottom: 10px;
}

.hub-header h1 {
  font-size: 28px;
  margin-bottom: 2px;
  color: var(--text);
}

.hub-lead {
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 400;
  margin-bottom: 0;
}

/* Status panel is a full-width horizontal strip on top, module grid below
   — both full-width children, stacked in DOM order (status-summary first,
   hub-modules second), so no explicit grid-column layout is needed here
   any more. This is what removed the old "status panel = short left
   column, card grid = tall right column, empty gap under the status
   panel" asymmetry. */
.hub-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.status-summary {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 16px;
  margin: 0;
}

.status-summary h2 {
  font-size: 16px;
  margin-bottom: 6px;
}

.status-summary-note {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.35;
  margin-bottom: 10px;
}

.status-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}

.chip {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  padding: 5px 8px;
  border-radius: var(--radius-chip);
}

.chip-live { color: var(--status-live); background: var(--status-live-bg); }
.chip-poc { color: var(--status-poc); background: var(--status-poc-bg); }
.chip-spec { color: var(--status-spec); background: var(--status-spec-bg); }

.status-details summary {
  font-size: 12px;
  color: var(--brand-primary);
  font-weight: 700;
  padding: 4px 0;
}

.status-summary-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  margin-top: 8px;
}

.status-summary-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  line-height: 1.3;
}

.status-summary-table tr:hover td {
  background: var(--bg);
}

.status-summary-table tr:last-child td {
  border-bottom: none;
}

.status-summary-table td:first-child {
  color: var(--text-muted);
  width: 58%;
}

.cell-live { color: var(--status-live); }
.cell-poc { color: var(--status-poc); }
.cell-spec { color: var(--status-spec); }

.hub-modules h2 {
  font-size: 16px;
  margin-bottom: 6px;
}

.hub-grid {
  display: grid;
  /* Explicit 3 columns (not auto-fit) — 6 module cards in a fixed 3x2
     grid. auto-fit at wide viewports was resolving to 5 columns here
     (plenty of spare width, 300px minimum), rendering 6 cards as an
     uneven 5+1 split — exactly the asymmetry this fixes. align-items is
     the grid default (stretch) but stated explicitly since equal card
     height in a row depends on it. */
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  align-items: stretch;
}

@media (max-width: 1040px) {
  .hub-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .hub-grid {
    grid-template-columns: 1fr;
  }
}

.module-card {
  position: relative;
  display: flex;
  flex-direction: column;
  text-align: left;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 16px;
  cursor: pointer;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.module-card:hover,
.module-card:focus-visible {
  background: var(--bg-panel-2);
  box-shadow: var(--shadow-panel);
}

.module-card-order {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background: var(--brand-primary-subtle);
  color: var(--brand-primary);
  font-size: 12px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.module-card-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  margin: 0 30px 4px 0;
}

.module-card-subtitle {
  font-size: 14px;
  font-weight: 400;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.module-card-meta {
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 0;
  /* .module-card is a flex column now (title/subtitle/meta stacked) and
     stretches to the tallest card in its grid row — pushing this line to
     the bottom edge with margin-top:auto is what keeps the step-count
     flush with the card border regardless of how many lines the title/
     subtitle above it wrap to, so bottom edges line up across a row. */
  margin-top: auto;
}

/* Placed after the base .module-card-title/.module-card-subtitle rules
   (not up near .hub-grid) — a media query adds no specificity, so if it
   sat before the base rule the later base rule would win the cascade tie
   and silently override this bump at every width. */
@media (min-width: 1600px) {
  .module-card-title {
    font-size: 20px;
  }
  .module-card-subtitle {
    font-size: 16px;
  }
}

/* ---------------------------------------------------------------- */
/* Module stage */
/* ---------------------------------------------------------------- */

/* Header is deliberately squat — everything beyond the title and the
   source-line/info-toggle row lives inside the collapsed <details> so the
   sequence diagram, caption panel, controls, and legend all stay above the
   fold at 1280x720. */
.module-header {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 8px;
  flex: 0 0 auto;
}

.module-heading {
  flex: 1 1 auto;
  min-width: 0;
}

.module-heading h2 {
  font-size: 20px;
  line-height: 1.25;
  margin-bottom: 4px;
}

.module-meta-row {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 10px;
}

.module-source-line {
  font-size: 12px;
  line-height: 1.25;
  font-weight: 400;
  color: var(--text-dim);
}

.module-status-details {
  margin: 0;
}

.module-status-details summary {
  font-size: 12px;
  line-height: 1.25;
  color: var(--brand-primary);
  font-weight: 700;
  list-style: none;
}

.module-status-details summary::-webkit-details-marker {
  display: none;
}

.module-purpose,
.module-hook,
.module-status-note {
  font-size: 13px;
  font-weight: 400;
  color: var(--text-muted);
  margin: 6px 0 0;
}

.module-hook {
  font-style: italic;
}

.step-indicator {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}

.btn {
  background: var(--brand-primary-subtle);
  border: none;
  color: var(--brand-primary);
  font-weight: 700;
  border-radius: var(--radius-btn-sm);
  padding: 8px 16px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.btn:hover:not(:disabled) {
  background: var(--brand-primary-subtle-hover);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  background: var(--bg-panel-2);
  color: var(--text-dim);
}

.btn-play[data-playing="true"] {
  background: var(--brand-primary);
  color: #fff;
}

/* Sequence lanes */

.stage-body {
  /* Below the 2-column breakpoint this wrapper is transparent to layout —
     lanes-scroll/caption-panel behave exactly as direct flex children of
     #module-view, unchanged from the single-column design. */
  display: contents;
}

.lanes-scroll {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: auto;
  scrollbar-gutter: stable;
  /* Takes whatever height is left in the module-view flex column; never
     smaller than 190px even on a short viewport. */
  flex: 1 1 auto;
  min-height: 190px;
  padding: 12px 0 16px;
  margin-bottom: 12px;
}

.lanes-header {
  display: flex;
  position: sticky;
  top: 0;
  background: var(--bg-panel);
  z-index: 2;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--border);
}

/* Actor header as a coloured chip (per-kind pale fill), not floating text —
   long names truncate with an ellipsis + native title tooltip (engine.js
   sets title=labelTh) instead of wrapping across lines. box-sizing:border-box
   (global) means the border below does not change the element's rendered
   width, so the JS-computed lane width/centerX geometry used for arrow
   placement is unaffected. */
.lane-header {
  flex: 0 0 auto;
  min-width: 0;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
  padding: 5px 6px;
  border: 2px solid var(--bg-panel);
  border-radius: var(--radius-chip);
  /* Presentation audience can't hover a title tooltip — full name must be
     readable on-screen. Wrap up to ~2 lines instead of ellipsis-truncating;
     engine.js keeps `title` as a supplementary (not sole) affordance and
     drops chip font-size a notch (10px, intentionally off the 12/13/14/16/
     20/24/28/32 type scale — the scale has no step below 12, and this
     shrink exists purely to help long labels fit 2 lines instead of 3+ in
     a narrow lane; snapping it to 12 would make it identical to the base
     size and silently remove the fit-driven effect it exists for) when the
     computed lane is narrow. */
  white-space: normal;
  overflow-wrap: break-word;
  word-break: break-word;
  background: color-mix(in srgb, currentColor 16%, white);
}

.actor-browser { color: var(--c-browser); }
.actor-frontend { color: var(--c-frontend); }
.actor-gateway { color: var(--c-gateway); }
.actor-backend { color: var(--c-backend); }
.actor-idp { color: var(--c-idp); }
.actor-db { color: var(--c-db); }
.actor-attacker { color: var(--c-attacker); }
.actor-external { color: var(--c-external); }

.lanes-track {
  position: relative;
  /* Holds only the arrow rows now (lifelines live in the separate
     .lifelines-layer below, which never moves). margin-top is a static 8px
     base gap only (spacing scale 4/8/12/16/20/24/32 — kept in sync with
     .lifelines-layer's own margin-top and engine.js's LANES_TRACK_TOP_GAP;
     all 3 must change together) — the per-step centering offset engine.js
     computes is applied as a `transform: translateY()` instead of added
     into this margin. A dynamic margin-top here would (a) itself need to
     be undone on the sibling below, and (b) since .lifelines-layer is a
     zero-height empty box, CSS margin-collapsing-through would let that
     dynamic margin bleed into .lifelines-layer's own position — quietly
     reproducing the "centering drags the lifeline down" bug via the flow
     instead of via this element directly. transform never participates in
     flow/margin collapse, so it can't leak into the sibling no matter what. */
  margin-top: 8px;
  transition: transform 0.35s ease;
}

/* Separate layer, sibling of .lanes-track (not a child) — always a
   zero-height empty box (no content: lifelines are absolutely positioned
   children that don't contribute to its own auto height) with the same
   static 8px margin-top as .lanes-track (see sync note above), so its
   rendered position never depends on how tall .lanes-track's revealed-row
   cluster currently is. Lifelines represent an actor's timeline, which
   exists for the pane's full height regardless of how many/few steps are
   currently revealed, so they must not move when the arrow-row cluster
   centers itself. engine.js sizes each lifeline to max(diagram height,
   pane height) so the line always reaches the bottom of the visible pane
   even on modules with few steps. */
.lifelines-layer {
  position: relative;
  height: 0;
  margin-top: 8px;
}

.lifeline {
  position: absolute;
  top: 0;
  width: 2px;
  background: var(--border-strong);
}

.arrow-row {
  position: absolute;
  left: 0;
  right: 0;
  opacity: 1;
  transition: opacity 0.35s ease;
}

.arrow-row.arrow-hidden {
  display: none;
}

.arrow-row.arrow-past {
  opacity: 0.4;
}

.arrow-row.arrow-current {
  opacity: 1;
}

.arrow-line {
  position: absolute;
  top: 30px;
  height: 3px;
  background: var(--arrow-color);
}

.arrow-dir-right::after,
.arrow-dir-left::after {
  content: '';
  position: absolute;
  top: -6px;
  border: 7px solid transparent;
}

.arrow-dir-right::after {
  right: -1px;
  border-left-color: var(--arrow-color);
}

.arrow-dir-left::after {
  left: -1px;
  border-right-color: var(--arrow-color);
}

/* Current step = the one clear focal point in the diagram: thicker line,
   brand-blue, soft glow. */
.arrow-row.arrow-current .arrow-line {
  background: var(--arrow-color-active);
  height: 5px;
  box-shadow: 0 1px 6px rgba(3, 78, 161, 0.35);
}

.arrow-row.arrow-current .arrow-dir-right::after {
  border-left-color: var(--arrow-color-active);
}

.arrow-row.arrow-current .arrow-dir-left::after {
  border-right-color: var(--arrow-color-active);
}

.arrow-label {
  position: absolute;
  top: 6px;
  text-align: center;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
  white-space: nowrap;
}

.arrow-row.arrow-current .arrow-label {
  color: var(--arrow-color-active);
  font-weight: 700;
}

.arrow-self .self-loop {
  position: absolute;
  top: 10px;
  width: 30px;
  height: 26px;
  border: 2px solid var(--arrow-color);
  border-left: none;
  border-radius: 0 12px 12px 0;
}

.arrow-row.arrow-current .self-loop {
  border-color: var(--arrow-color-active);
}

.arrow-label-self {
  top: 40px;
  text-align: left;
  white-space: normal;
  max-width: 220px;
}

/* Caption panel — visual hierarchy: plain (hero) > caption (secondary) >
   highlight (quiet aside) > source (footnote). */

.caption-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 14px 18px;
  margin-bottom: 10px;
  /* Full available width — only the plain-hero line (.plain) is width-capped
     for headline readability; caption/highlight/source use all the room so
     they reflow to fewer lines and the source line stays visible. */
  max-width: none;
  /* Natural content height, not a fixed budget — must be the ONLY
     non-shrinking sibling of .lanes-scroll (flex:0 0 auto) or the flex
     shrink phase would squeeze this panel instead of the diagram on every
     step. overflow-y:auto has no effect day-to-day (no max-height) but is
     kept as a graceful-degradation valve, consistent with scrollTop=0
     reset in showStep. */
  flex: 0 0 auto;
  overflow-y: auto;
}

.caption-meta-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.step-kind {
  font-size: 12px;
  font-weight: 700;
  color: var(--kind-control);
}

.step-kind[data-kind="threat"] { color: var(--kind-threat); }
.step-kind[data-kind="gap"] { color: var(--kind-gap); }
.step-kind[data-kind="note"] { color: var(--kind-note); }

.status-badge {
  font-size: 12px;
  font-weight: 700;
  padding: 5px 8px;
  border-radius: var(--radius-chip);
}

.status-badge.status-live { color: var(--status-live); background: var(--status-live-bg); }
.status-badge.status-poc { color: var(--status-poc); background: var(--status-poc-bg); }
.status-badge.status-spec { color: var(--status-spec); background: var(--status-spec-bg); }

.plain-kicker {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 2px;
}

.plain {
  font-size: 24px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--text);
  margin-bottom: 12px;
  /* Headline-length line cap so the hero stays scannable even though the
     panel itself is now full-width — caption/highlight/source are not
     capped and use the full panel width. */
  max-width: 640px;
}

.caption {
  font-size: 20px;
  line-height: 1.55;
  font-weight: 400;
  color: var(--text-muted);
  margin-bottom: 10px;
}

.highlight {
  font-size: 13px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--text-dim);
  border-left: 2px solid var(--status-poc);
  padding-left: 9px;
  margin: 0 0 10px;
}

.highlight::before {
  content: '\25B8\0020';
  color: var(--status-poc);
  font-weight: 700;
}

.source-row {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
  margin-bottom: 0;
}

.source-tag {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  background: var(--bg-panel-2);
  border-radius: var(--radius-chip);
  padding: 2px 6px;
  margin-right: 6px;
  vertical-align: middle;
}

/* Controls */

.controls {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
  flex: 0 0 auto;
}

/* Thin progress bar (replaces 14 individually-legible dots, which stop
   being readable past ~8 steps) — still N real <button data-testid=
   "step-dot"> elements so the harness's count/data-active contract is
   unchanged; only the visual treatment is a bar instead of circles. */
.step-dots {
  flex: 1 1 auto;
  display: flex;
  gap: 2px;
  height: 8px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--bg-panel-2);
}

.step-dot {
  flex: 1 1 0;
  min-width: 3px;
  height: 100%;
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
}

/* Segments before the active one read as "completed" (progress fill);
   :has() is a progressive enhancement — unsupported browsers just show
   the active segment highlighted against a neutral bar, still fully
   legible as a progress indicator. */
.step-dot:has(~ .step-dot[data-active="true"]) {
  background: var(--brand-primary-subtle-active);
}

.step-dot[data-active="true"] {
  background: var(--brand-primary);
}

/* Legend — quiet footer strip, still explanatory (AC-09 requires the
   status/kind meanings to stay legible, not just present in the DOM). */

.legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
  border-top: 1px solid var(--border);
  padding-top: 8px;
  margin-bottom: 10px;
  flex: 0 0 auto;
}

.legend-group-label {
  font-weight: 700;
  color: var(--text-dim);
}

.legend-divider {
  color: var(--border-strong);
}

.legend-item.kind-threat { color: var(--kind-threat); }
.legend-item.kind-gap { color: var(--kind-gap); }
.legend-item.kind-note { color: var(--kind-note); }
.legend-item.status-live { color: var(--status-live); }
.legend-item.status-poc { color: var(--status-poc); }
.legend-item.status-spec { color: var(--status-spec); }

/* Recap */

.module-recap {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 18px 20px;
  /* Natural content height (can be tall — keyFacts/misconceptions/glossary).
     Must not shrink, or the flex shrink phase would crush it to an
     unreadable sliver; the module-view's own overflow-y:auto is what makes
     it reachable when it doesn't fit the remaining space. */
  flex: 0 0 auto;
  margin-top: 12px;
}

.recap-heading {
  font-size: 20px;
}

.recap-status-line {
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 400;
}

.recap-subheading {
  font-size: 14px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: 14px;
}

.recap-list {
  margin: 0;
  padding-left: 20px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text-muted);
}

.recap-list li {
  margin-bottom: 8px;
}

.mc-wrong {
  display: block;
  color: var(--kind-threat);
}

.mc-right {
  display: block;
  color: var(--status-live);
}

.recap-glossary li {
  color: var(--text);
}

/* ---------------------------------------------------------------- */
/* Responsive floor (1280x720) */
/* ---------------------------------------------------------------- */

@media (max-width: 1300px) {
  #app {
    --app-vpad: 36px;
    padding: 16px 16px 20px;
  }
}

/* ---------------------------------------------------------------- */
/* Module view — fills the viewport height so the sequence diagram gets
   whatever room is left, instead of a fixed pixel budget. */
/* ---------------------------------------------------------------- */

#module-view[hidden] {
  display: none;
}

#module-view {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--app-vpad));
  /* Not `hidden` — recap (last step) or an unusually long caption on any
     step can legitimately exceed the fixed height; this lets the stage
     itself scroll to reach it instead of clipping content silently. In the
     normal case (recap hidden, typical caption length) content fits exactly
     and no scrollbar appears. */
  overflow-y: auto;
}

/* Wide screens: sequence needs height more than it needs width — split into
   diagram (left, ~62%) and caption panel (right, ~38%), both stretching to
   the full available height. Below this breakpoint .stage-body is
   `display:contents` (see above), so nothing here changes 1280x720 layout. */
@media (min-width: 1500px) {
  .stage-body {
    display: flex;
    flex-direction: row;
    gap: 16px;
    flex: 1 1 auto;
    min-height: 0;
    margin-bottom: 12px;
  }

  .lanes-scroll {
    /* 70/30 (was 62/38) — actor chips need the extra width now that they
       wrap to full readable labels instead of ellipsis-truncating. */
    flex: 70 1 0;
    min-height: 0;
    margin-bottom: 0;
  }

  .caption-panel {
    flex: 30 1 0;
    min-height: 0;
    margin-bottom: 0;
    /* Right column is tall (matches the diagram's height) but its own text
       is short — center it vertically instead of leaving it pinned to the
       top with empty space below. `safe` falls back to top-alignment if
       content ever grows taller than the column, so nothing becomes
       unreachable by scroll. */
    display: flex;
    flex-direction: column;
    justify-content: safe center;
  }
}

/* On genuinely short viewports, stop trying to fit everything in one
   screen — let the whole page scroll instead of crushing the diagram/
   caption unreadably small. Caption and controls stay reachable either way.
   Placed last so it wins (equal specificity, source order) over the
   min-width:1500px lanes-scroll rule on short+wide viewports. */
@media (max-height: 700px) {
  #module-view {
    height: auto;
    overflow-y: visible;
  }

  .lanes-scroll {
    flex: 0 0 auto;
    max-height: 220px;
  }
}
