/* static/tutorial/chassis/deck.css: every tour's chassis (spec 2026-10-08-tutorial-revamp-design.md §7.1, §7.4).
   A tour's own figures, legends and replicas live in its tour.css, loaded after this file.
   Rules this file keeps:
   1. No font-size under 16px (tests/test_type_scale_floor.py, tests/test_tour_chassis_static.py).
   2. No green: green marks a graded call and nothing in a tour is one.
   3. An element shown and hidden with `hidden` gets its [hidden] rule AFTER any rule giving it a display
      (tests/test_hidden_needs_display_override.py).
   4. The motion tokens are CETourLogic.MOTION's numbers (chassis/logic.js; the static test compares). */
:root {
  --bg-void: #06080c;
  --bg-surface: #111113;
  --bg-card: #111113;
  --bg-detail: #18181b;
  --bg-elevated: #18181b;
  --text-primary: #f4f4f5;
  --text-secondary: #e4e4e7;
  --text-muted: #d4d4d8;
  --text-dim: #b8b8c0;
  --border-subtle: rgba(255, 255, 255, 0.06);
  --border-default: rgba(255, 255, 255, 0.1);
  --border-strong: rgba(255, 255, 255, 0.16);
  --font-sans: 'IBM Plex Sans', -apple-system, system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', 'SF Mono', monospace;
  --tour-dim: 0.38;
  --tour-fade-in: 200ms;
  --tour-fade-out: 150ms;
  --tour-swap-out: 120ms;
  --tour-swap-in: 180ms;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  display: flex; flex-direction: column; height: 100vh; height: 100dvh; overflow: hidden;
  background: var(--bg-void); color: var(--text-secondary);
  font-family: var(--font-sans); font-size: 16px; line-height: 1.35;
}

/* ─── Chassis ─── */
.tour-top { position: relative; z-index: 11; flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 4px 16px; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); }
.tour-name { font-size: 16px; font-weight: 600; color: var(--text-primary); }
.tour-dots { display: flex; align-items: center; }
.tour-dots[hidden] { display: none; }
/* A phone's counter in the dots' place, for a deck of more slides than the bar holds dots (Bill, 2026-10-14, D5;
   chassis/deck.js placeCount): "3 of 10", 16px, as tall as a dot. */
.tour-count { margin: 0; font-size: 16px; min-height: 44px; display: flex; align-items: center; color: var(--text-primary); }
.tour-count[hidden] { display: none; }
.tour-dot { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0; border: 0; background: none; cursor: pointer; }
.tour-dot::before { content: ''; width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--text-muted); }
.tour-dot[aria-current="step"]::before { background: var(--text-primary); border-color: var(--text-primary); }
.tour-dot:focus-visible { outline: 2px solid var(--text-primary); outline-offset: -8px; }
.tour-skip { min-height: 44px; padding: 0 12px; border: 1px solid var(--border-strong); border-radius: 6px; background: none; color: var(--text-primary); font: inherit; font-size: 16px; cursor: pointer; }
.tour-stage { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 12px 16px; }
/* The opening line, slide 1 only (chassis/deck.js), with its generated "N slides · M min". */
.tour-intro { max-width: 1080px; margin: 0 auto 8px; font-size: 16px; color: var(--text-secondary); }
.tour-intro[hidden] { display: none; }
.tour-length { color: var(--text-dim); white-space: nowrap; }
/* The tours menu (scripts/build_tours.py menu_html; Bill, 2026-10-20, option A "Build tight rows"), in every deck that
   carries it: one line per tour, the title on the left at 18px, its length on the right in the body face at 16px, a
   hairline between rows, each row a 44px target. Nothing ellipsizes: a row that cannot fit its line is a failure
   (tests/test_tour_menu_browser.py), never cut. */
.tour-menu { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.tour-menu-item { display: flex; flex-wrap: nowrap; align-items: center; justify-content: space-between; gap: 10px; min-height: 44px; }
.tour-menu-item[hidden] { display: none; }
.tour-menu-item + .tour-menu-item { border-top: 1px solid var(--border-default); }
.tour-menu-link { flex: 1 1 auto; min-width: 0; display: inline-flex; align-items: center; min-height: 44px; font-size: 18px; font-weight: 600; color: var(--text-primary); text-underline-offset: 4px; white-space: nowrap; }
.tour-menu-item .tour-length { flex: none; font: 16px/1.3 var(--font-sans); font-variant-numeric: tabular-nums; color: var(--text-muted); }
.tour-slide { display: flex; flex-direction: column; gap: 10px; max-width: 1080px; margin: 0 auto; }
.tour-slide[hidden] { display: none; }
.tour-h { font-size: 20px; font-weight: 600; line-height: 1.25; color: var(--text-primary); }
/* The deck focuses each slide's heading so a screen reader starts there. Chrome counts a script's focus with
   no pointer before it (the first load) as :focus-visible, which drew a box on slide 1's heading; the ring
   needs a key pressed since the last pointer (chassis/deck.js sets data-keys on <html>). */
.tour-h:focus, .tour-h:focus-visible { outline: none; }
:root[data-keys] .tour-h:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.tour-asof { font-size: 16px; color: var(--text-dim); }
/* A term printed inline straight against its definition ran together ("BUY:below"); a space keeps them apart.
   At the start of a line (a grid or block <dd>) the space collapses away. */
.tour-stage dt + dd::before { content: ' '; }
.tour-captions { flex: 0 0 auto; max-height: 30vh; overflow-y: auto; padding: 8px 16px; border-top: 1px solid var(--border-strong); background: var(--bg-surface); font-size: 17px; line-height: 1.5; color: var(--text-muted); }
.tour-captions[hidden] { display: none; }
.tour-caption + .tour-caption { margin-top: 6px; }
.tour-caption.is-now { color: var(--text-primary); }
/* Access: the transcript switch (the only route to captions) and, once a slide is recorded, Mute (Bill, 2026-10-08:
   out of the bar, so the bar stays one row on a phone), a small panel above the nav. */
.tour-access { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; padding: 8px 16px; border-top: 1px solid var(--border-default); background: var(--bg-surface); font-size: 16px; }
.tour-access[hidden] { display: none; }
.tour-access-title { font-weight: 600; color: var(--text-primary); }
.tour-access-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.tour-access-row[hidden] { display: none; }
/* A short, wide window, a phone on its side (Bill, 2026-10-09: the stacked panel covered most of the slide on an iPhone
   in landscape): Access is one compact row, each option's label beside its button, its visible title gone (the
   group's aria-labelledby still reads it: a referenced node names the group even when it is not rendered); and the captions drawer is about two lines (17px x 1.5 each, plus its padding and border), deck.js
   keeping the current line in view. */
@media (max-height: 500px) and (orientation: landscape) {
  .tour-access { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px 24px; padding: 4px 16px; }
  .tour-access-title { display: none; }
  .tour-access-row { justify-content: flex-start; gap: 8px; }
  .tour-captions { max-height: calc(3em + 17px); }
}
/* One row at every width down to 375 px (Bill, 2026-10-08): Back and Next are arrows, Mute is in Access. */
.tour-nav { flex: 0 0 auto; display: flex; flex-wrap: nowrap; justify-content: center; gap: 8px; padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--border-default); background: var(--bg-surface); }
/* A phone (Bill, batches B and C, 2026-10-10): deck.js moves Skip to the end of the control row (placeSkip, on the
   deck's own PHONE query), so these rules key on where Skip sits, never on a width. The top bar then holds the dots
   alone, centred (eight 44 px dots fill a 375 px bar, so its sides close to 8 px), the deck's name kept for screen
   readers only; the row closes its gaps so Back, Pause, Access, Next and Skip stay one row at 375 px. No control
   shrinks below its words: a row that cannot hold them scrolls sideways, which scripts/browser_checks/tour_check.py
   fails, rather than spilling a label out of its button.
   :has() needs iOS/Safari 15.4 or later (Chrome 105, Firefox 121). An older Safari drops the three :has() rules whole:
   Skip still moves to the end of the row (deck.js), but the row keeps its 8 px gaps and 16 px sides and the top bar
   its 16 px sides, so on a 375 px phone the row (about 396 px) and the eight dots (352 px in 343) run past the screen
   and the page scrolls sideways a little; every control still shows and works. */
.tour-nav > button { flex-shrink: 0; }
.tour-top:not(:has(> .tour-skip)) { justify-content: center; padding-left: 8px; padding-right: 8px; }
.tour-top:not(:has(> .tour-skip)) .tour-name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.tour-nav:has(> .tour-skip) { gap: 4px; padding-left: 8px; padding-right: 8px; }
/* Under 360 px (Bill, 2026-10-12, option d): Skip reads "Skip" on every slide (CETourLogic.skipLabel), and the
   text buttons (Pause, Access, the closing word, Skip) take 8 px sides instead of 14 (Skip's 12), so a deck's last
   slide, where "Board" takes the arrow's place, fits a 320 px screen. The row keeps its 4 px gaps; every target
   keeps its 44 px and its 16 px type; 360 px and up is unchanged. */
@media (max-width: 359.98px) {
  .tour-nav > .tour-btn:not(.tour-btn--icon), .tour-nav > .tour-skip { padding-left: 8px; padding-right: 8px; }
}
/* Skip stays above the autoplay unlock overlay (.tour-unlock, z-index 10) in the row as it does in the top bar
   (z-index 11): only Skip is lifted, so the overlay still covers the rest of the row. */
.tour-nav > .tour-skip { position: relative; z-index: 11; }
.tour-btn { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; padding: 0 14px; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--bg-detail); color: var(--text-primary); font: inherit; font-size: 16px; cursor: pointer; }
.tour-btn[hidden] { display: none; }
.tour-btn--icon { min-width: 44px; padding: 0 12px; font-size: 20px; line-height: 1; }
.tour-btn:disabled { opacity: 0.45; cursor: default; }
.tour-btn[aria-pressed="true"] { border-color: var(--text-primary); }
.tour-btn--primary { background: var(--text-primary); border-color: var(--text-primary); color: var(--bg-void); font-weight: 600; }
.tour-unlock { position: fixed; inset: 0; z-index: 10; display: flex; align-items: center; justify-content: center; background: rgba(6, 8, 12, 0.88); }
.tour-unlock[hidden] { display: none; }
.tour-unlock-btn { min-height: 44px; padding: 12px 24px; border: 1px solid var(--text-primary); border-radius: 8px; background: var(--bg-detail); color: var(--text-primary); font: inherit; font-size: 18px; cursor: pointer; }
.tour-skip:focus-visible, .tour-btn:focus-visible, .tour-unlock-btn:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.tour-btn--primary:focus-visible { outline-color: var(--text-dim); }
.tour-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ─── The figure follows the voice (spec §7.4) ───
   chassis/deck.js marks the parts the current paragraph names (.is-now, from data-beats). By default the
   whole figure shows and the rest dims to --tour-dim. A slide with .tour-swap shows only the lit parts, so
   each beat is its own one-screen composition and the highlight changes what is on screen rather than
   scrolling to it. deck.js sets .tour-swap per slide (Bill, 2026-10-08): always on a phone (<= 640px wide);
   on a wider window only when that slide's whole figure, dimmed, overflows the room above the controls with
   the captions closed (measured when the slide is shown, on a resize and once the fonts are in; never in the
   swap layout, so it cannot oscillate). The Exchanges tour at 1536x730: slides 1 and 3 dim, slide 2 swaps.
   .tour-measuring holds every transition off while deck.js measures and settles a slide. */
.tour-fig [data-beat] { transition: opacity var(--tour-fade-out) ease-in; }
.tour-fig [data-beat].is-now { transition: opacity var(--tour-fade-in) ease-out; }
.tour-fig[data-now] [data-beat]:not(.is-now) { opacity: var(--tour-dim); }

.tour-swap .tour-fig [data-beat] { transition: opacity var(--tour-swap-out) ease-in, display var(--tour-swap-out) allow-discrete; }
.tour-swap .tour-fig [data-beat].is-now { transition: opacity var(--tour-swap-in) ease-out; }
.tour-swap .tour-fig[data-now] [data-beat]:not(.is-now) { display: none; opacity: 0; }
.tour-swap .tour-fig[data-now] .rep-window:not(:has(.is-now)) { display: none; }
@starting-style { .tour-swap .tour-fig[data-now] [data-beat].is-now { opacity: 0; } }
.tour-slide.tour-measuring .tour-fig [data-beat], .tour-slide.tour-measuring .tour-fig [data-beat].is-now { transition: none; }
@media (max-width: 480px) {
  /* The dots and Skip need the top bar at 375px; the dialog keeps its name (aria-label on the layer). */
  .tour-name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) {
  .tour-fig [data-beat], .tour-fig [data-beat].is-now,
  .tour-swap .tour-fig [data-beat], .tour-swap .tour-fig [data-beat].is-now { transition: none; }
}
