/* OYEE — «Une question à la fois»: the homepage experience.
 * «Pièce par pièce» (docs/redesign/design-spec.md). The whole experience is built from one
 * object, the piece of the logo's open O. Ink is what is set, vermilion is what is in human
 * hands right now, a hairline outline is what is still open.
 *
 *  0. Tokens (§2, §5.2)                 8. Choose (§3.7)
 *  1. Base, faces, focus (§2.3, §4.7)   9. Finale (§3.8)
 *  2. Header, map button, comfort (§4)  10. Rings (§3.1, §4.2, §5.5)
 *  3. Deck frame and grid (§2.5, §3.0)  11. Bottom bar (§4.3)
 *  4. Actions and notes (§3.0)          12. Map dialog (§4.4)
 *  5. Cover and how (§3.1, §3.2)        13. Motion and calm (§5)
 *  6. Question and answer (§3.3, §3.4)  14. Exhibit frame and shared vocabulary (§6)
 *  7. Try and insight (§3.5, §3.6)      15. Comfort, contrast, forced colours, no JS, print, short screens (§9, §3.9)
 *                                       16. The action in view: whole above the bar, or wholly below (§3.0)
 *
 * CSP: no inline styles anywhere; geometry reaches CSS through custom properties set with CSSOM.
 * Never hard-code the number of concepts: the ring is data-driven.
 */

/* ============================================================ 0. Tokens */

:root {
  /* colour = role (§2.1) */
  --paper: #f3efe5;
  --paper-hi: #faf7f0;
  --paper-lo: #e8e2d4;
  --ink: #24241f;
  --ink-2: #57544c;
  --ink-3: #7d796e;
  --rule: #cdc7b8;
  --vermilion: #b73521;
  --yellow: #edc955;
  --sage: #dce3d3;

  /* the two faces (§2.3) */
  --voice: Georgia, "Times New Roman", serif;
  --tool: "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* fluid type scale, 390 → 1440 (§2.4) */
  --fs-cover: clamp(2.5rem, 1.479rem + 4.19vw, 5.25rem);
  --fs-question: clamp(2.25rem, 1.414rem + 3.429vw, 4.5rem);
  --fs-answer: clamp(1.875rem, 1.318rem + 2.286vw, 3.375rem);
  --fs-h2: clamp(1.75rem, 1.379rem + 1.524vw, 2.75rem);
  --fs-choice: clamp(1.375rem, 1.143rem + 0.952vw, 2rem);
  --fs-material: clamp(1.5rem, 1.129rem + 1.524vw, 2.5rem);
  --fs-piece: clamp(1.125rem, 0.939rem + 0.762vw, 1.625rem);
  --fs-lead: clamp(1.1875rem, 1.071rem + 0.476vw, 1.5rem);
  --fs-body: clamp(1.0625rem, 1.016rem + 0.19vw, 1.1875rem);
  --fs-num-lg: clamp(1.75rem, 1.379rem + 1.524vw, 2.75rem);
  --fs-num: clamp(0.875rem, 0.829rem + 0.19vw, 1rem);
  --fs-ui: clamp(1rem, 0.977rem + 0.095vw, 1.0625rem);
  --fs-small: clamp(0.875rem, 0.852rem + 0.095vw, 0.9375rem);
  --fs-note: clamp(1.0625rem, 0.993rem + 0.286vw, 1.25rem); /* the educator's note, 17 → 20 (§6.3) */

  /* space, grid, lines, radii, layers (§2.5) */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px;
  --sp-7: 48px; --sp-8: 64px; --sp-9: 96px; --sp-10: 144px;
  --margin: clamp(1.25rem, 0.229rem + 4.19vw, 4rem);
  --gap: clamp(1rem, 0.814rem + 0.762vw, 1.5rem);
  --chrome-top: 72px; --chrome-bottom: 80px;
  --hair: 1px; --line: 2px; --weight-max: 8px;
  --radius: 0;
  --z-slide: 1; --z-chrome: 20; --z-flight: 30; --z-popover: 40; --z-skip: 50;

  /* motion (§5.2): no springs, no overshoot, no bounce */
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in: cubic-bezier(.55, 0, .75, .2);
  --ease-inout: cubic-bezier(.65, 0, .35, 1);
  --t-state: 160ms; --t-move: 360ms; --t-enter: 520ms; --t-leave: 240ms; --t-draw: 900ms; --t-path: 1600ms;

  /* derived (not part of the §2 block) */
  --edge: var(--ink-3);   /* 1px outlines of open things; ink under stronger contrast */
  --lh-add: 0;            /* reading comfort adds .15 to every line height */
  --hang: 24px;           /* the hanging axis: marks sit this far left of the text column */
  /* Georgia at 40px or more tracks -0.012em; everything else 0 (resolved per element; the
   * half pixel makes exactly 40px count, where (40px - 1em) would be 0) */
  --track: clamp(-0.012em, (39.5px - 1em) * 1000, 0em);
  /* --margin at the default text size, in px: the header's cluster is set in px (§4.1), so a larger
   * default font must not widen its padding past the 320px budget */
  --margin-px: clamp(20px, 3.664px + 4.19vw, 64px);
}
@media (max-width: 1023px) { :root { --chrome-top: 64px; --chrome-bottom: 72px; --hang: 16px; } }
@media (max-width: 599px) { :root { --chrome-top: 56px; --chrome-bottom: 64px; } }
@media (max-width: 360px) { :root { --margin: 16px; --margin-px: 16px; } }
@media (max-height: 560px) {
  :root {
    --chrome-top: 48px; --chrome-bottom: 52px;
    --fs-cover: 2rem; --fs-question: 1.875rem; --fs-answer: 1.5rem; --fs-material: 1.375rem; --fs-piece: 1.0625rem;
  }
}

/* ============================================================ 1. Base, faces, focus */

[hidden] { display: none !important; }

html { background: var(--paper); -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
.flow-body, .flow-body *, .flow-body *::before, .flow-body *::after { box-sizing: border-box; }
.flow-body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--fs-body)/calc(1.5 + var(--lh-add)) var(--voice);
  font-kerning: normal;
  font-synthesis: none;
  text-transform: none;
  hyphens: manual;
}
.flow-body :where(h1, h2, h3, p, ol, ul, figure, dl, dd) { margin: 0; }
.flow-body :where(ol, ul) { padding: 0; list-style: none; }
.flow-body :where(h1, h2, h3) { font: inherit; }
.flow-body :where(button, input, select, textarea) { font: inherit; color: inherit; }
.flow-body :where(button) { border-radius: 0; }
.flow-body svg { display: block; }
.flow-body ::selection { background: var(--yellow); color: var(--ink); }

/* links: ink with a quiet underline; on hover the underline becomes the visitor's mark.
 * Zero specificity (:where), so every component class below restyles links freely.
 *
 * Every hover look inside the deck waits for a real pointer move after a slide change: the engine
 * keeps .flow.is-pointer-stale until then, so a mouse resting where the next slide's row lands
 * never makes that row look chosen. The scope is :where(), which adds no specificity. Focus looks
 * are never scoped. */
:where(.flow-body a) {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--edge);
  text-underline-offset: .2em;
}
:where(.flow-body a:not(.flow.is-pointer-stale a):hover) { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }

/* focus: 2px vermilion, offset 3px, always landing on paper (§4.7). The outline is only
 * dropped where :focus-visible exists (a browser without it keeps its own ring). */
.flow-body :focus:not(:focus-visible) { outline: none; }
.flow-body :focus-visible { outline: 2px solid var(--vermilion); outline-offset: 3px; border-radius: 0; }

.sr-only,
.flow-next-label {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  /* on focus it sits over the logo, whole: never a sliver of the mark beside it */
  position: fixed; z-index: var(--z-skip); top: calc(var(--chrome-top) / 2 - 22px); left: calc(var(--margin) - 8px);
  min-width: 116px; min-height: 44px;
  padding: 12px 16px;
  background: var(--paper-hi); color: var(--ink);
  border: 1px solid var(--ink);
  font: 400 15px/1.2 var(--tool);
  text-decoration: none;
  transform: translateY(calc(-100% - 16px));
}
.skip:focus, .skip:focus-visible { transform: none; }

/* ============================================================ 2. Header (§4.1–§4.2, §4.5) */

.flow-top {
  --cluster: 28px;
  /* one above the bottom bar: the comfort panel it holds may reach down over the bar (400 % zoom) */
  position: fixed; z-index: calc(var(--z-chrome) + 1); top: 0; left: 0; right: 0;
  height: var(--chrome-top);
  display: flex; align-items: center; gap: var(--cluster);
  padding: 0 var(--margin-px);
  background: var(--paper);
  font-family: var(--tool);
}
@media (max-width: 1023px) { .flow-top { --cluster: 20px; } }
@media (max-width: 599px) { .flow-top { --cluster: 12px; } }
@media (max-width: 359px) { .flow-top { --cluster: 6px; } }

/* the logo at the brand minimum: 100px wide, never moved, recoloured or shrunk */
.flow-brand { flex: none; display: block; margin-right: auto; padding: 7px 0; line-height: 0; text-decoration: none; }
.flow-logo { display: block; width: 100px; min-width: 100px; height: auto; }

.flow-nav { display: flex; align-items: center; gap: var(--cluster); }
.flow-nav-link, .flow-lang {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 24px;
  text-decoration: none;
  white-space: nowrap;
}
.flow-nav-link { font-size: 15px; color: var(--ink); }
.flow-nav-link:hover { text-decoration: underline; text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; text-underline-offset: .25em; }
.flow-lang { font-size: 14px; color: var(--ink-2); }
.flow-lang:hover { color: var(--ink); }

.flow-map-button {
  appearance: none; margin: 0; border: 0; background: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 2px;
  color: var(--ink); font: 400 15px/1.15 var(--tool);
  white-space: nowrap;
}
.flow-ring { flex: none; display: block; width: 32px; height: 32px; }
.flow-map-button:hover .flow-map-label { text-decoration: underline; text-decoration-thickness: 2px; text-decoration-color: var(--vermilion); text-underline-offset: .2em; }
@media (max-width: 599px) {
  .flow-ring { width: 28px; height: 28px; }
  .flow-map-button { min-width: 44px; justify-content: center; gap: 6px; }
  .flow-map-label {
    position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
/* On phones the label shows only where the map matters: the how and insight slides (§4.2), on
 * one line or not at all: flow.js marks the header .is-label-cramped when «Votre carte» cannot
 * sit beside the 100px logo (360–389px, and EN at 390px), and it stays a spoken name only. Up to
 * 430px the cluster closes to 8px while the label shows. */
@media (min-width: 360px) and (max-width: 599px) {
  html:is([data-flow-kind="how"], [data-flow-kind="insight"]) .flow-top:not(.is-label-cramped) .flow-map-label {
    position: static; width: auto; height: auto; margin: 0; overflow: visible;
    clip: auto; clip-path: none; white-space: nowrap;
    font-size: 14px; line-height: 1.15;
  }
}
@media (min-width: 360px) and (max-width: 430px) {
  html:is([data-flow-kind="how"], [data-flow-kind="insight"]) .flow-top:not(.is-label-cramped) { --cluster: 8px; }
}
/* the cover's big ring is the map: the button keeps its place but is hidden (§4.6). It comes
 * back at once, never half-faded: its label must be readable the moment it can be read. */
html[data-flow-kind="cover"] .flow-map-button { visibility: hidden; }

/* the comfort menu: ◐ Confort de lecture (label visually hidden below 1200px) */
.flow-comfort { position: relative; flex: none; }
.flow-comfort > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-width: 44px; height: 44px; padding: 0;
  font: 400 15px/1 var(--tool); color: var(--ink);
  user-select: none;
}
.flow-comfort > summary::-webkit-details-marker { display: none; }
.comfort-icon { font-size: 20px; line-height: 1; }
.comfort-label {
  position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
@media (min-width: 1200px) {
  .flow-comfort > summary { padding: 0 2px; }
  .comfort-label { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; clip-path: none; }
  .flow-comfort > summary:hover .comfort-label { text-decoration: underline; text-decoration-thickness: 2px; text-decoration-color: var(--vermilion); text-underline-offset: .2em; }
}
/* ◐ may narrow to 36px (§4.1); the map button keeps its 44px square (§9.2), which reaches 4px into
 * the 6px gaps on each side (the targets stay 2px apart), so the English cluster fits at 320 */
@media (max-width: 359px) {
  .flow-comfort > summary { min-width: 36px; }
  .flow-map-button { margin-inline: -4px; }
}

.comfort-panel {
  /* the same panel as the site pages' (site.css .site-top .comfort-panel): its top on the header's
   * bottom edge (◐ is 44px, centred in the header), its right edge on ◐'s, which ends the header */
  position: absolute; z-index: var(--z-popover); top: calc(100% + (var(--chrome-top) - 44px) / 2); right: 0;
  width: 300px; max-width: calc(100vw - 2 * var(--margin));
  /* on a short screen (400 % zoom: 320×256) the panel scrolls inside itself, so its last link
   * can always be reached and seen */
  max-height: calc(100vh - var(--chrome-top) - 6px);
  max-height: calc(100dvh - var(--chrome-top) - 6px);
  overflow-y: auto; overscroll-behavior: contain;
  padding: 16px 20px;
  background: var(--paper-hi);
  border: 1px solid var(--ink);
  font: 400 15px/1.35 var(--tool); color: var(--ink);
}
/* WCAG 2.4.11: once keyboard focus has left the open menu, the panel must not cover the control
 * that now has focus. immersive.js leaves <details> open until a click or Escape, so the panel
 * steps aside while a control outside it shows keyboard focus (it comes back with focus on ◐).
 * Harmless once immersive.js closes the menu on focusout (engine change request E2). */
.flow-body:has(:focus-visible:not(.flow-comfort, .flow-comfort *)) .flow-comfort[open] > .comfort-panel { display: none; }
.comfort-panel label { display: flex; align-items: center; gap: 12px; min-height: 44px; margin: 0; font: 400 15px/calc(1.3 + var(--lh-add)) var(--tool); color: var(--ink); cursor: pointer; }
.comfort-panel input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  flex: none; display: inline-grid; place-content: center;
  width: 20px; height: 20px; margin: 0;
  background: var(--paper-hi); border: 1px solid var(--ink); border-radius: 0;
  cursor: pointer;
}
.comfort-panel input[type="checkbox"]:checked { background: var(--ink); }
.comfort-panel input[type="checkbox"]:checked::before { content: ""; width: 8px; height: 8px; background: var(--paper-hi); }
.comfort-panel input[type="checkbox"]:disabled { cursor: default; border-color: var(--ink-3); }
.comfort-panel input[type="checkbox"]:disabled:checked { background: var(--ink-3); }
.comfort-panel label:has(input:disabled) { cursor: default; }
.comfort-panel .fine { margin: 8px 0 0; font: 400 14px/calc(1.4 + var(--lh-add)) var(--tool); color: var(--ink-2); }
.comfort-panel a {
  display: inline-flex; align-items: center; min-height: 44px; margin-top: 4px;
  font: 400 15px/calc(1.3 + var(--lh-add)) var(--tool); color: var(--ink);
  text-decoration: underline; text-decoration-color: var(--ink-3); text-decoration-thickness: 1px; text-underline-offset: .25em;
}
.comfort-panel a:hover { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }

/* ============================================================ 3. Deck frame and grid (§2.5, §3.0) */

/* Live, everything is fixed, so the document itself never scrolls. No overflow:hidden on the
 * root: it would also make accessibility checkers treat the whole deck as clipped. */
html.flow-live { overscroll-behavior: none; }

.flow:focus { outline: none; }
.flow.is-live { position: fixed; inset: 0; z-index: var(--z-slide); overflow: hidden; }

.flow.is-live .slide {
  --pad-y: clamp(24px, 5vh, 56px);
  /* the slide ends where the bottom bar begins: the bar grows with a larger default font (its count
   * wraps), and the engine measures it into --flow-bar */
  position: absolute; inset: var(--chrome-top) 0 max(var(--chrome-bottom), var(--flow-bar, 0px));
  overflow: hidden auto;
  overscroll-behavior: contain;
  /* a slide is a scroll container, so it must repeat the deck's touch-action (touch-action
   * stops at the nearest scroll container): horizontal swipes stay with the deck */
  touch-action: pan-y pinch-zoom;
  scrollbar-width: thin; scrollbar-color: var(--rule) transparent;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: auto;
  column-gap: var(--gap);
  align-content: safe center;
  padding: var(--pad-y) var(--margin) calc(var(--pad-y) * 1.4);
  container: slide / inline-size;
  visibility: hidden;
}
.flow.is-live .slide > * { grid-column: 2 / 10; min-width: 0; }

@media (max-width: 1023px) {
  .flow.is-live .slide { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .flow.is-live .slide > * { grid-column: 1 / -1; }
}
@media (max-width: 599px) {
  /* top-aligned, so the bottom padding needs no optical lift: a slide that fits never scrolls */
  .flow.is-live .slide { grid-template-columns: repeat(4, minmax(0, 1fr)); align-content: start; padding-top: max(24px, 14vh); padding-bottom: 24px; }
}
@media (max-width: 599px) and (max-height: 760px) {
  .flow.is-live .slide { padding-top: max(20px, 7vh); } /* 360×740, 375×667: the action stays above the bar */
}

/* type roles shared by every slide (§2.4)
 *
 * A larger default font (200 % text, §9.10): the sizes are rem-based, so on a phone a word or a run
 * the copy binds («pas de cours», «à l’intérieur ?», «une à une et regardez») may be wider than
 * its column. Each size below is capped by its column: the slide's content box (100cqi, less any
 * indent) over the longest run it sets, in em, plus air for reading comfort's spacing. At the
 * default text size no cap bites from 320px up. The last resort breaks a run inside rather than
 * let the slide clip it (outside exhibits, which size their own material). */
.flow .slide > :not(.exhibit) { overflow-wrap: break-word; }
.cover-line-2, .question, .answer, .insight, .how, .choose-h, .finale-h, .x-sentence { letter-spacing: var(--track); }
.question, .how, .choose-h, .finale-h, .cover { text-wrap: balance; }
.answer, .insight, .answer-detail, .insight-aside, .how-line, .finale-line, .try-lead, .x-note, .exhibit-fallback, .door-line, .promise { text-wrap: pretty; }

/* ============================================================ 4. Actions and notes */

.slide-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 28px; margin-top: 40px; }

.go {
  display: inline-flex; align-items: center; justify-content: center; gap: .6em;
  font: 400 var(--fs-ui)/1.2 var(--tool);
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
  scroll-margin-block: 8px; /* tabbing to an action flush with the bar scrolls its focus ring into view */
}
/* primary: ink fill, paper text, an arrow; one filled button per slide */
.go-primary,
.go-continue,
[data-flow-finish].is-primary {
  min-height: 56px; padding: 0 22px;
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--ink);
}
.go-primary::after,
.go-continue::after,
[data-flow-finish].is-primary::after { content: "→"; content: "→" / ""; font-family: var(--tool); transition: transform var(--t-state) var(--ease-out); }
:where(.flow:not(.is-pointer-stale)) .go-primary:hover::after,
:where(.flow:not(.is-pointer-stale)) .go-continue:hover::after,
:where(.flow:not(.is-pointer-stale)) [data-flow-finish].is-primary:hover::after { transform: translateX(3px); }
.go-primary:active, .go-continue:active { transform: translateY(1px); }
/* quiet: text on a 1px underline, a 44px hit area */
.go-quiet {
  min-height: 44px; padding: 0;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ink);
  text-underline-offset: .25em;
}
:where(.flow:not(.is-pointer-stale)) .go-quiet:hover { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }
[data-flow-finish].is-primary { text-decoration: none; }
/* the booking button: the only vermilion fill on its screen (§3.8) */
[data-finale-primary] { background: var(--vermilion); border-color: var(--vermilion); color: var(--paper); }
@media (max-width: 599px) {
  .go-primary, .go-continue, [data-flow-finish].is-primary { min-height: 52px; }
  /* at 200 % text size a label may not fit the phone's width: it wraps inside its button */
  .go { white-space: normal; max-width: 100%; text-align: left; }
}
/* A returning visitor (the engine unhides «Reprendre là où vous en étiez», first in the DOM):
 * resuming is the one filled button, «Commencer» becomes a quiet link beside «Recommencer». */
.slide-actions:has(> [data-flow-resume]:not([hidden])) > [data-flow-resume] {
  min-height: 56px; padding: 0 22px;
  background: var(--ink); color: var(--paper); border: 1px solid var(--ink);
  text-decoration: none;
}
.slide-actions:has(> [data-flow-resume]:not([hidden])) > [data-flow-resume]::after { content: "→"; content: "→" / ""; font-family: var(--tool); transition: transform var(--t-state) var(--ease-out); }
:where(.flow:not(.is-pointer-stale)) .slide-actions:has(> [data-flow-resume]:not([hidden])) > [data-flow-resume]:hover::after { transform: translateX(3px); }
.slide-actions:has(> [data-flow-resume]:not([hidden])) > .go-primary {
  min-height: 44px; min-width: 0; padding: 0;
  background: none; color: var(--ink); border: 0;
  text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--ink); text-underline-offset: .25em;
}
.slide-actions:has(> [data-flow-resume]:not([hidden])) > .go-primary::after { content: none; }
:where(.flow:not(.is-pointer-stale)) .slide-actions:has(> [data-flow-resume]:not([hidden])) > .go-primary:hover { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }
@media (max-width: 599px) {
  .slide-actions:has(> [data-flow-resume]:not([hidden])) > [data-flow-resume] { min-height: 52px; min-width: 60%; white-space: normal; text-align: left; padding: 10px 18px; }
}

/* the educator's note: ink-2, a 2px × 1.1em vermilion tick hanging in the margin */
.answer-detail, .insight-aside { position: relative; font: 400 min(var(--fs-lead), (100cqi - 14px) / 9.4)/calc(1.4 + var(--lh-add)) var(--voice); color: var(--ink-2); max-width: 34em; } /* «des renseignements» 8.7em */
.answer-detail::before, .insight-aside::before, .x-note::before {
  content: ""; position: absolute; left: calc(-1 * var(--hang) - 1px); top: .2em;
  width: 2px; height: 1.1em; background: var(--vermilion);
}
@media (max-width: 599px) {
  .answer-detail, .insight-aside { padding-left: 14px; }
  .answer-detail::before, .insight-aside::before { left: 0; }
}

/* headings the engine focuses: no box, a 3px × 1em vermilion tick in the hanging margin */
.slide [data-focus] { position: relative; }
.slide [data-focus]:focus-visible { outline: none; }
.slide [data-focus]:focus-visible::before {
  content: ""; position: absolute; left: calc(-1 * var(--hang) - 1.5px); top: .1em;
  width: 3px; height: 1em; background: var(--vermilion);
}
@media (max-width: 599px) { .slide [data-focus]:focus-visible::before { left: -12px; } }

/* ============================================================ 5. Cover (§3.1) and how (§3.2) */

.cover { color: var(--ink); }
.cover .cover-line { display: block; font: 400 var(--fs-lead)/calc(1.4 + var(--lh-add)) var(--voice); color: var(--ink-2); max-width: 34em; }
.cover .cover-line-2 {
  margin-top: .35em;
  font: 400 min(var(--fs-cover), 100cqi / 6)/calc(1.06 + var(--lh-add)) var(--voice); /* «à l’intérieur ?» 5.6em */
  color: var(--ink); max-width: 12em;
}
.cover-meta { margin-top: 24px; font: 400 min(var(--fs-small), 100cqi / 11.8)/calc(1.4 + var(--lh-add)) var(--tool); color: var(--ink-2); font-variant-numeric: tabular-nums lining-nums; text-wrap: balance; } /* «environ 10 minutes ·» 11em */
/* who is behind it, for the visitor who came for a workshop: one quiet line, never a second button */
.cover-by { margin-top: 8px; font: 400 min(var(--fs-small), 100cqi / 11.8)/calc(1.4 + var(--lh-add)) var(--tool); color: var(--ink-2); max-width: 42em; }
.cover-by-link { color: var(--ink); text-decoration: none; white-space: nowrap; }
.cover-by-link > span { text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--ink); text-underline-offset: .25em; }
.cover-by-link::after { content: " →"; content: " →" / ""; }
:where(.flow:not(.is-pointer-stale)) .cover-by-link:hover > span { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }
.slide[data-kind="cover"] .slide-actions { margin-top: 36px; }
.cover-ring { position: relative; pointer-events: none; }

.flow.is-live .slide[data-kind="cover"] { bottom: 0; } /* no bottom bar on the cover */

@media (min-width: 1024px) {
  .flow.is-live .slide[data-kind="cover"] > .cover { grid-column: 2 / 8; grid-row: 1; align-self: center; }
  .flow.is-live .slide[data-kind="cover"] > .slide-actions { grid-column: 2 / 8; grid-row: 2; }
  .flow.is-live .slide[data-kind="cover"] > .cover-meta { grid-column: 2 / 8; grid-row: 3; }
  .flow.is-live .slide[data-kind="cover"] > .cover-by { grid-column: 2 / 8; grid-row: 4; }
  /* the ring sits in cols 8–12, centred on the h1; its negative block margins cancel its
   * height in the grid, so the h1's row (and the actions under it) never stretch */
  .flow.is-live .slide[data-kind="cover"] > .cover-ring {
    --d: min(40vw, 58vh, 520px);
    grid-column: 8 / 13; grid-row: 1; align-self: center; justify-self: center;
    width: var(--d); height: var(--d); margin: calc(var(--d) / -2) 0;
  }
}
@media (max-width: 1023px) {
  .flow.is-live .slide[data-kind="cover"] > .cover-ring {
    grid-row: 1; justify-self: end;
    width: min(56vw, 38vh); aspect-ratio: 1;
    margin-bottom: 8px;
  }
  .flow.is-live .slide[data-kind="cover"] > .cover { grid-row: 2; }
  .flow.is-live .slide[data-kind="cover"] > .slide-actions { grid-row: 3; }
  .flow.is-live .slide[data-kind="cover"] > .cover-meta { grid-row: 4; }
  .flow.is-live .slide[data-kind="cover"] > .cover-by { grid-row: 5; }
}
@media (max-width: 599px) {
  .flow.is-live .slide[data-kind="cover"] { align-content: start; padding-top: 16px; }
  .flow.is-live .slide[data-kind="cover"] > .cover-ring { width: 56vw; margin-right: -4px; }
  .slide[data-kind="cover"] .go-primary { min-width: 60%; }
}
@media (max-width: 599px) and (max-height: 700px) {
  .flow.is-live .slide[data-kind="cover"] > .cover-ring { width: 40vw; }
}
/* 320×568: the line under the meta (who is behind it, «Voir les ateliers») stays on the screen */
@media (max-width: 599px) and (max-height: 600px) {
  .flow.is-live .slide[data-kind="cover"] > .cover-ring { width: 26vw; }
  .slide[data-kind="cover"] .slide-actions { margin-top: 24px; }
  .cover-meta { margin-top: 16px; }
}

.how { font: 400 min(var(--fs-answer), 100cqi / 5.9)/calc(1.14 + var(--lh-add)) var(--voice); max-width: 19em; } /* «pas de cours» 5.5em */
.how-line { margin-top: 20px; font: 400 var(--fs-lead)/calc(1.4 + var(--lh-add)) var(--voice); color: var(--ink-2); max-width: 34em; }
.beats { counter-reset: beat; margin-top: 40px; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: var(--gap); }
.beats > li, .method-steps > li {
  counter-increment: beat;
  border-top: 1px solid var(--ink);
  padding: 12px 0 0;
  font: 400 var(--fs-choice)/calc(1.25 + var(--lh-add)) var(--voice);
  color: var(--ink);
  text-wrap: balance;
}
.beats > li::before, .method-steps .step-n {
  display: block; margin-bottom: 10px;
  font: 400 var(--fs-small)/1.2 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
}
.beats > li::before { content: counter(beat); }
/* beat 4 on a phone: where the map is (the header's ring, which a phone shows without its label) */
.beat-gloss { display: none; }
@media (max-width: 599px) {
  html.flow-live .beat-gloss { display: block; margin-top: 2px; font: 400 var(--fs-small)/calc(1.4 + var(--lh-add)) var(--tool); color: var(--ink-2); }
}
@media (min-width: 1024px) { .flow.is-live .slide > .beats, .flow.is-live .slide > .method-steps { grid-column: 2 / 12; } }
/* Five cells in a row: a name is sized to its cell, never to the viewport. At --fs-choice alone,
 * «Cartographier» (6.2em, 6.3em under reading comfort) crosses into «Choisir» from 1024 to about
 * 1280px. Capped at a fifth of the row (less the gaps) over 6.4em: about 21px at 1024, 31px at 1440. */
@media (min-width: 1024px) {
  .beats, .method-steps { container-type: inline-size; }
  .beats > li, .method-steps > li { font-size: min(var(--fs-choice), calc((100cqw - 4 * var(--gap)) / 5 / 6.4)); }
}
@media (max-width: 1023px) {
  .beats { grid-template-columns: 1fr; margin-top: 28px; }
  .beats > li { display: flex; align-items: baseline; gap: 16px; padding: 9px 0 11px; }
  .beats > li::before { flex: none; width: 1.2em; margin: 0; }
  /* a name beside its number: «Une question» 5.9em (200 % text) */
  .beats > li { font-size: min(var(--fs-choice), (100cqi - 16px - 1.1rem) / 6.3); }
  .method-steps > li { font-size: min(var(--fs-choice), (100cqi - 44px - 1.1rem) / 6.6); } /* «Cartographier» 6.2em */
}

/* ============================================================ 6. Question (§3.3) and answer (§3.4) */

.slide-tag {
  position: relative;
  font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2);
  margin-bottom: 20px;
}
/* the piece glyph: the logo's segment, vermilion, hanging in the margin */
.slide-tag::before {
  content: ""; position: absolute; left: calc(-1 * var(--hang) - 6.5px); top: calc(.7em - 6px);
  width: 13px; height: 12.4px;
  background: var(--vermilion);
  -webkit-mask: url("/img/piece.svg") center / contain no-repeat;
  mask: url("/img/piece.svg") center / contain no-repeat;
}
@media (max-width: 599px) {
  .slide-tag { padding-left: 21px; }
  .slide-tag::before { left: 0; }
}
.question { font: 400 min(var(--fs-question), 100cqi / 6.5)/calc(1.08 + var(--lh-add)) var(--voice); max-width: 14em; } /* «ces machines,» 6.1em */

.answer { font: 400 min(var(--fs-answer), 100cqi / 8.7)/calc(1.14 + var(--lh-add)) var(--voice); max-width: 19em; } /* «De votre situation,» 8.2em */
.answer + .answer-detail { margin-top: 32px; }

/* S3, the staircase (H6): the machine sets the answer word by word. Line 2 starts two grid
 * columns in, line 3 four (24px and 48px on phones). The staircase owns the page up to its
 * right edge, so the ghost line can run off it. The three sentences are three lines: the size
 * is capped so the first sentence (FR «Elle devine le mot suivant.», 11.64em) fits the full
 * width and the longest repeated one (EN "Then the next one.", about 8.3em) fits beside a
 * four-step indent. On phones that is about 31px at 390 and 25px at 320; from about 600px
 * up the caps never bite. */
.answer[data-staircase] {
  --step: calc((100cqw - 11 * var(--gap)) / 12 + var(--gap));
  --pad-r: var(--margin);
  font-size: min(var(--fs-question),
    calc((100cqw + var(--margin) - var(--pad-r) - 4 * var(--step)) / 8.3),
    calc((100cqw + var(--margin) - var(--pad-r)) / 11.8));
  line-height: calc(1.08 + var(--lh-add));
  max-width: none;
}
.flow.is-live .slide > .answer[data-staircase] { margin-right: calc(-1 * var(--margin)); }
@media (min-width: 1024px) { .flow.is-live .slide > .answer[data-staircase] { grid-column: 2 / -1; } }
@media (max-width: 599px) { .answer[data-staircase] { --step: 12px; --pad-r: 8px; } }
.answer-line { display: block; padding-right: var(--pad-r); }
.answer-line + .answer-line { margin-top: .12em; }
.answer-line:nth-of-type(2) { margin-left: calc(2 * var(--step)); color: var(--ink-2); }
.answer-line:nth-of-type(3) { margin-left: calc(4 * var(--step)); color: var(--ink-3); }
.answer .w { position: relative; }
/* the ghost: «Puis le suivant.» again, in --rule, running off the right edge. Pure decoration
 * and aria-hidden, which WCAG 1.4.3 exempts from contrast; tests/flow.cjs leaves it out of its
 * axe runs by name (never hide a real failure from a checker). */
.answer-ghost {
  display: block; margin-top: .12em;
  margin-left: max(calc(6 * var(--step)), calc(100% - 4.2em));
  white-space: nowrap;
  color: var(--rule);
  user-select: none;
}

/* ============================================================ 7. Try (§3.5) and insight (§3.6) */

.flow.is-live .slide[data-kind="try"] { align-content: start; --pad-y: clamp(16px, 3.5vh, 40px); }
@media (max-width: 599px) { .flow.is-live .slide[data-kind="try"] { padding-top: 20px; padding-bottom: 24px; } }
.try-lead { font: 400 min(var(--fs-lead), 100cqi / 10)/calc(1.4 + var(--lh-add)) var(--voice); color: var(--ink); max-width: 34em; transition: color var(--t-state) linear; } /* «une à une et regardez» 9.4em */
.slide.is-done .try-lead { color: var(--ink-2); }
@media (min-width: 1024px) { .flow.is-live .slide > .try-lead { grid-column: 2 / 12; } }
.slide[data-kind="try"] .slide-actions { margin-top: 24px; }
/* «Continuer»: outlined until the visitor has explored, then filled (always usable). The fill
 * takes 320ms; the label switches at its midpoint, so it never crosses its own background.
 * Calm: instant (§13). */
.go-continue { background: var(--paper); color: var(--ink); transition: background-color 320ms linear, color 0s linear 160ms; }
.slide.is-done .go-continue { background: var(--ink); color: var(--paper); }

.insight { font: 400 min(var(--fs-answer), 100cqi / 8.5)/calc(1.14 + var(--lh-add)) var(--voice); max-width: 17em; } /* «is neighbourhood.» 8em */
.insight + .insight-aside { margin-top: 28px; }
/* the caption sits beside the ring, or under it when the text is set too large to fit beside */
.insight-map { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; margin-top: 28px; }
.insight-ring { flex: none; width: 112px; aspect-ratio: 1; }
.insight-map-caption { flex: 1 1 9em; font: 400 var(--fs-ui)/1.3 var(--tool); color: var(--ink); max-width: 16em; text-wrap: balance; }
@media (max-width: 599px) and (max-height: 700px) { .insight-map { margin-top: 20px; } .insight-ring { width: 80px; } } /* 320×640: «Continuer» stays whole */
@media (min-width: 1024px) {
  .flow.is-live .slide[data-kind="insight"] > .insight,
  .flow.is-live .slide[data-kind="insight"] > .insight-aside,
  .flow.is-live .slide[data-kind="insight"] > .slide-actions { grid-column: 2 / 9; }
  .flow.is-live .slide[data-kind="insight"] > .insight { grid-row: 1; }
  .flow.is-live .slide[data-kind="insight"] > .insight-aside { grid-row: 2; }
  .flow.is-live .slide[data-kind="insight"] > .slide-actions { grid-row: 3; }
  /* the Map moment: a ring of up to 240px in the aside, centred on the insight, its caption under
   * it (the negative block margins keep the ring from stretching the insight's row). The ring is
   * never wider than the aside (three of the slide's twelve columns and their two gaps: about
   * 217px at 1024), and the caption stays inside the ring's width, on two balanced lines. */
  .flow.is-live .slide[data-kind="insight"] > .insight-map {
    --aside: calc((100cqw - 11 * var(--gap)) / 12 * 3 + 2 * var(--gap));
    --d: min(240px, var(--aside));
    grid-column: 10 / 13; grid-row: 1; align-self: center; justify-self: start;
    position: relative; display: block;
    width: var(--d); height: var(--d); margin: calc(var(--d) / -2) 0;
  }
  .flow.is-live .slide[data-kind="insight"] .insight-ring { width: 100%; }
  .flow.is-live .slide[data-kind="insight"] .insight-map-caption { position: absolute; left: 0; right: 0; top: calc(100% + 16px); max-width: none; text-align: center; }
}

/* ============================================================ 8. Choose (§3.7): the tributaries */

.choose-h { font: 400 min(var(--fs-answer), 100cqi / 6.2)/calc(1.14 + var(--lh-add)) var(--voice); max-width: 19em; } /* «maintenant ?» 5.7em */
.choices {
  --mt: 32px;
  --fs-q: min(var(--fs-choice), (100cqi - 24px) / 6.5); /* a question's size, capped by its column («ces machines,» 6.1em) */
  position: relative; margin-top: var(--mt);
}
.choices > li { position: relative; }
/* strands, CSS only: a trunk in the hanging margin, one elbow per row to its first baseline */
.choices::before {
  content: ""; position: absolute; left: calc(-1 * var(--hang)); top: calc(10px - var(--mt)); height: calc(var(--mt) - 10px);
  border-left: 1px solid var(--edge);
}
.choices > li::before {
  content: ""; position: absolute; left: calc(-1 * var(--hang)); top: 0;
  width: calc(var(--hang) - 6px);
  height: calc(16px + .98 * var(--fs-q));
  border-left: 1px solid var(--edge); border-bottom: 1px solid var(--edge);
  border-bottom-left-radius: 14px;
  pointer-events: none;
}
.choices > li:not(:last-child)::after {
  content: ""; position: absolute; left: calc(-1 * var(--hang)); bottom: 0;
  top: calc(16px + .98 * var(--fs-q) - 14px);
  border-left: 1px solid var(--edge);
}
.choices > li:focus-within::before,
:where(.flow:not(.is-pointer-stale)) .choices > li:has(> .choice:hover)::before { border-left: 2px solid var(--vermilion); border-bottom: 2px solid var(--vermilion); }
/* a choice hugs its question: its focus ring, its strand and its underline read as one mark */
.choice {
  /* the hint's column never gets narrower than its longest word: with a larger default font the
   * tag wraps first («Plus / profond»), never a hint cut at the slide's edge */
  display: grid; grid-template-columns: auto minmax(min-content, 1fr); grid-template-areas: "q q" "tag hint";
  align-content: center;
  width: fit-content; max-width: 100%;
  min-height: 64px; padding: 16px 0;
  color: var(--ink); text-decoration: none;
}
.choice-q { grid-area: q; font: 400 var(--fs-q, var(--fs-choice))/calc(1.25 + var(--lh-add)) var(--voice); max-width: 24em; text-wrap: balance; }
.choice-tag, .choice-hint { font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2); margin-top: 6px; }
.choice-tag { grid-area: tag; }
.choice-hint { grid-area: hint; }
.choice-hint::before { content: "·"; content: "·" / ""; margin: 0 .45em; }
/* where «Plus profond · comment ça marche» (13.9em, the widest; EN 10.7em) cannot sit on one line
 * beside the 24px strand (200 % text on a phone), the tag and the hint stack whole, never two
 * interleaved columns. (The slide is the container: .choices stays none, --fs-q reads the slide.) */
@container slide (max-width: 16.5em) {
  .choice { grid-template-columns: minmax(0, 1fr); grid-template-areas: "q" "tag" "hint"; }
  .choice-hint { margin-top: 0; }
  .choice-hint::before { content: none; }
}
.choice:is(:focus, :focus-visible) .choice-q,
:where(.flow:not(.is-pointer-stale)) .choice:hover .choice-q { text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--ink); text-underline-offset: .18em; }
.slide[data-kind="choose"] .slide-actions { margin-top: 24px; }
.choose-ring { display: none; }
@media (min-width: 1200px) {
  .flow.is-live .slide[data-kind="choose"] > .choose-ring {
    display: flow-root; grid-column: 10 / 13; grid-row: 1 / span 2; align-self: center; justify-self: center;
    width: 220px; aspect-ratio: 1;
  }
  .flow.is-live .slide[data-kind="choose"] > .choose-h { grid-row: 1; }
  .flow.is-live .slide[data-kind="choose"] > .choices { grid-row: 2; }
  .flow.is-live .slide[data-kind="choose"] > .slide-actions { grid-row: 3; }
}
@media (max-width: 599px) {
  /* the trunk sits at x = 0 and the rows are indented 24px */
  .choices { padding-left: 24px; }
  .choices::before { left: 0; }
  .choices > li::before { left: -24px; width: 18px; }
  .choices > li:not(:last-child)::after { left: -24px; }
}

/* ============================================================ 9. Finale (§3.8) */

.finale-h { font: 400 min(var(--fs-answer), 100cqi / 6.9)/calc(1.14 + var(--lh-add)) var(--voice); max-width: 19em; } /* «the Inquisitive» 6.4em */
.finale-line { margin-top: 20px; font: 400 min(var(--fs-lead), 100cqi / 7.9)/calc(1.4 + var(--lh-add)) var(--voice); color: var(--ink-2); max-width: 34em; } /* «et un enseignant» 7.3em */
.slide[data-kind="finale"] > .finale-line:first-of-type { color: var(--ink); }

/* 1. the path */
.finale-ring { width: 240px; aspect-ratio: 1; margin-top: 28px; }
.finale-count { margin-top: 24px; font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2); font-variant-numeric: tabular-nums lining-nums; text-wrap: balance; }
.finale-trail { margin-top: 10px; display: flex; flex-wrap: wrap; column-gap: .45em; font: 400 min(var(--fs-choice), 100cqi / 7)/calc(1.35 + var(--lh-add)) var(--voice); } /* «→ Localiser» 6.4em */
.finale-trail > li + li::before { content: "→"; content: "→" / ""; margin-right: .45em; font-family: var(--tool); color: var(--ink-2); } /* text under 24px: ink-2, never ink-3 (§2.1) */
@media (min-width: 1024px) {
  .flow.is-live #finale-path > .finale-h,
  .flow.is-live #finale-path > .finale-count,
  .flow.is-live #finale-path > .finale-trail,
  .flow.is-live #finale-path > .slide-actions { grid-column: 2 / 8; }
  .flow.is-live #finale-path > .finale-h { grid-row: 1; }
  .flow.is-live #finale-path > .finale-count { grid-row: 2; }
  .flow.is-live #finale-path > .finale-trail { grid-row: 3; }
  .flow.is-live #finale-path > .slide-actions { grid-row: 4; }
  .flow.is-live #finale-path > .finale-ring {
    --d: min(420px, 36vw);
    grid-column: 8 / 13; grid-row: 1 / span 4; align-self: center; justify-self: center;
    width: var(--d); height: var(--d); margin: calc(var(--d) / -2) 0;
  }
}

/* 3. the method, named: five steps and a return line (repeat) */
.method-steps { counter-reset: beat; position: relative; margin-top: 40px; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: var(--gap); padding-bottom: 36px; }
.method-steps .step-n { margin-bottom: 10px; }
/* the beat each step was lived as («une question»), when the copy has it */
.method-steps .step-gloss { display: block; margin-top: 6px; font: 400 min(var(--fs-small), (100cqi - 44px - 1.1rem) / 9.2)/1.4 var(--tool); color: var(--ink-2); text-wrap: balance; } /* «and in a workshop,» 8.5em */
/* the return line leaves from under the last step (half a cell in) and comes back up into step 1 */
.method-steps::after {
  content: ""; position: absolute; left: 0; right: calc((100% - 4 * var(--gap)) / 10); bottom: 10px; height: 20px;
  border: 1px solid var(--edge); border-top: 0;
  border-radius: 0 0 12px 12px;
}
.method-steps::before { /* the open chevron where the line returns into step 1: 12px arms ending on the line */
  content: ""; position: absolute; left: -5.5px; bottom: 24.5px; width: 12px; height: 12px;
  border-left: 1px solid var(--edge); border-top: 1px solid var(--edge);
  transform: rotate(45deg); transform-origin: 50% 50%;
}
@media (max-width: 1023px) {
  .method-steps { grid-template-columns: 1fr; padding: 0 0 0 28px; margin-top: 28px; }
  .method-steps > li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 16px; padding: 9px 0 11px; }
  .method-steps .step-n { flex: none; width: 1.2em; margin: 0; }
  .method-steps .step-gloss { flex: 1 1 100%; box-sizing: border-box; margin: 0; padding-left: calc(1.2em + 16px); } /* full row, indented under the name, never wider than the list */
  .method-steps::after { left: 0; right: auto; top: 22px; bottom: 22px; width: 14px; height: auto; border: 1px solid var(--edge); border-right: 0; border-radius: 12px 0 0 12px; }
  .method-steps::before { left: 10.5px; top: 16.5px; bottom: auto; transform: rotate(135deg); }
}
.slide#finale-method .finale-line { color: var(--ink-2); }
/* on phones the reveal is a long slide: it starts higher on short phones (360×740), and on every
 * phone once each step carries the beat it was lived as (twice as tall), so «Suivant» stays above
 * the bar at 390×844 */
@media (max-width: 599px) and (max-height: 760px) { .flow.is-live #finale-method { padding-top: 24px; } }
@media (max-width: 599px) { .flow.is-live #finale-method:has(.step-gloss) { padding-top: 24px; } }
/* short phones (375×667, 360×640, 360×740): the reason the method works (.finale-line) must be read
 * above the bar, before «Suivant». The rows close up and each beat sits beside its step's name,
 * wrapping in its own column there («Recevoir  une réponse directe, / puis un essai»); only a
 * beat with no room left beside a long name drops under it. The return line stays. */
@media (max-width: 599px) and (max-height: 760px) {
  .flow.is-live #finale-method .method-steps { margin-top: 20px; }
  .flow.is-live #finale-method .method-steps > li { padding: 5px 0 6px; column-gap: 12px; }
  .flow.is-live #finale-method .method-steps .step-gloss { flex: 1 1 0; min-width: 6.5em; padding-left: 0; }
  .flow.is-live #finale-method .method-steps::after { top: 16px; bottom: 16px; }
  .flow.is-live #finale-method .method-steps::before { top: 10.5px; }
  .flow.is-live #finale-method .finale-line { margin-top: 14px; }
}

/* 4. imagine it in person: doors as rows (§7.5) */
#finale-imagine .finale-h { font-size: min(var(--fs-question), 100cqi / 6.2); line-height: calc(1.08 + var(--lh-add)); max-width: 14em; } /* «en personne.» 5.7em */
.doors { margin-top: 32px; border-bottom: 1px solid var(--rule); }
@media (min-width: 1024px) { .flow.is-live .slide > .doors { grid-column: 2 / 12; } }
.doors > li { border-top: 1px solid var(--rule); }
:where(.flow:not(.is-pointer-stale)) .doors > li:hover { border-top-color: var(--ink); }
.door {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name arrow" "line arrow";
  align-items: center; column-gap: 24px;
  padding: 20px 0;
  color: var(--ink); text-decoration: none;
}
.door-name { grid-area: name; font: 400 min(var(--fs-choice), (100cqi - 44px) / 6.6)/calc(1.25 + var(--lh-add)) var(--voice); } /* beside its arrow: «Organisations» 6.2em */
.door-line { grid-area: line; margin-top: 2px; font: 400 var(--fs-body)/calc(1.45 + var(--lh-add)) var(--voice); color: var(--ink-2); }
.door::after { content: "→"; content: "→" / ""; grid-area: arrow; font: 400 20px/1 var(--tool); transition: transform var(--t-state) var(--ease-out); }
.door:focus-visible .door-name,
:where(.flow:not(.is-pointer-stale)) .door:hover .door-name { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
.door:focus-visible::after,
:where(.flow:not(.is-pointer-stale)) .door:hover::after { transform: translateX(4px); }
#finale-imagine .slide-actions { margin-top: 32px; }
/* the tallest slide: its bottom padding matches the top, so a slide that fits never scrolls
 * for the sake of its optical centre (1440×900, 1536×864) */
@media (min-width: 600px) { .flow.is-live #finale-imagine { padding-bottom: var(--pad-y); } }
/* The promise belongs to the booking button: it is the actions' own full-width row, set directly
 * under the button (12px). From 600px the quiet links share the button's row and the promise
 * takes the next one; on phones the quiet links wrap, so they come after the promise, never
 * between it and the button. */
.slide-actions > .promise { flex: 1 0 100%; order: 2; margin: 0; font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2); }
/* wherever the booking button and both quiet links cannot share one row (about 34em of the slide:
 * 600–760px in French, or text zoom), the quiet links follow the promise, as on phones */
@container slide (max-width: 37em) {
  .slide-actions > .promise { order: 1; }
  .slide-actions > .promise ~ .go { order: 2; }
}
@media (max-width: 599px) {
  .flow.is-live #finale-imagine { padding-top: 24px; } /* the whole slide fits in about 600px */
  .door { padding: 14px 0; }
  #finale-imagine .finale-line { margin-top: 14px; }
  .doors { margin-top: 20px; }
  #finale-imagine .slide-actions { margin-top: 24px; gap: 4px 24px; }
  .slide-actions > .promise { order: 1; margin: 8px 0 2px; }
  .slide-actions > .promise ~ .go { order: 2; }
}

/* ============================================================ 10. Rings */

/* A ring's holder size is the ring itself, lifted piece included: the SVG is scaled past its
 * holder by the viewBox slack (128 / 98) and pulled back by margins. The mini ring keeps its box. */
.ring { display: block; width: 130.6%; height: auto; margin: -15.3%; overflow: visible; }
.ring.ring-mini { width: 100%; height: 100%; margin: 0; }
/* Every piece, a lifted one included, is drawn inside its holder, so a holder clips the SVG's
 * viewBox slack: it never makes a slide scroll. flow-root keeps the SVG's negative margins
 * inside the holder, which is exactly as tall as it is wide. */
:is(.cover-ring, .insight-ring, .finale-ring, .map-ring) { display: flow-root; overflow: clip; }
.choose-ring { overflow: clip; }
.ring-seg {
  fill: transparent;
  stroke: var(--edge); stroke-width: 1px;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}
.ring-seg.is-seen { fill: var(--paper-lo); }
.ring-seg.is-mapped { fill: var(--ink); stroke: var(--ink); }
.ring-seg.is-here { fill: var(--vermilion); stroke: var(--vermilion); }
/* understood and current (insight, choose, header, map): docked in ink, the CSS property
 * overrides the attribute. On the cover the piece to explore next always stands out: the O
 * never closes. */
.ring:not(.ring-cover) .ring-seg.is-here.is-mapped { transform: none; fill: var(--ink); stroke: var(--ink); }
/* the small rings keep telling which piece is yours: a vermilion edge around the docked piece */
.ring:is(.ring-mini, .ring-large) .ring-seg.is-here.is-mapped { stroke: var(--vermilion); stroke-width: 2px; paint-order: stroke; }
.ring-path { fill: none; stroke: var(--vermilion); stroke-width: .8; stroke-linecap: round; stroke-linejoin: round; }
.ring-dot { fill: var(--vermilion); }

/* the finale ring draws the visitor's path (user-unit stroke; no non-scaling stroke on a dash draw) */
.ring-finale .ring-path { stroke-width: .6; stroke-dasharray: 1; stroke-dashoffset: 1; }
.is-drawn .ring-finale .ring-path { stroke-dashoffset: 0; transition: stroke-dashoffset var(--t-path) var(--ease-inout); }
.ring-finale .ring-dot { opacity: 0; }
.is-drawn .ring-finale .ring-dot { opacity: 1; transition: opacity var(--t-state) linear var(--t-path); }

/* the choose ring previews the piece a choice would add (never announced) */
.ring-choose .ring-seg { transition: transform var(--t-move) var(--ease-out), fill var(--t-state) linear, stroke var(--t-state) linear; }
.ring-choose .ring-seg.is-preview { transform: translate(var(--dx), var(--dy)); fill: var(--vermilion); stroke: var(--vermilion); }

/* ============================================================ 11. Bottom bar (§4.3) */

.flow-controls {
  position: fixed; z-index: var(--z-chrome); left: 0; right: 0; bottom: 0;
  min-height: var(--chrome-bottom); /* grows at 200 % text size; the slides follow (--flow-bar) */
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 0 var(--margin);
  background: var(--paper);
  font-family: var(--tool);
}
.flow-count {
  order: -1; margin: 0 auto 0 0; padding-right: 12px;
  font: 400 var(--fs-small)/1.3 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
  text-wrap: balance; /* at 320: «Notions explorées : / 0 sur 12», never a lone «12» */
}
.flow-prev, .flow-next {
  appearance: none; flex: none; margin: 0; padding: 0; cursor: pointer;
  display: inline-grid; place-items: center;
  width: 48px; height: 48px;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--ink); border-radius: 0;
  font: 400 20px/1 var(--tool);
}
.flow-prev > [aria-hidden], .flow-next > [aria-hidden] { display: block; transition: transform var(--t-state) var(--ease-out); }
.flow-prev:hover > [aria-hidden] { transform: translateX(-3px); }
.flow-next:hover > [aria-hidden] { transform: translateX(3px); }
.flow-prev:disabled { visibility: hidden; }
.flow-next.is-waiting { border-color: var(--ink-3); color: var(--ink-2); }
.flow-next:disabled { cursor: default; border-color: var(--ink-3); color: var(--ink-3); }
@media (max-width: 599px) { .flow-prev, .flow-next { width: 44px; height: 44px; } }
@media (max-width: 359px) { .flow-count { font-size: 14px; } }
html[data-flow-kind="cover"] .flow-controls { display: none; }
html[data-flow-kind="how"] .flow-count { visibility: hidden; }

/* ============================================================ 12. Map dialog (§4.4) */

.flow-map {
  position: fixed; inset: 0 0 0 auto;
  width: min(520px, 100%); max-width: 100%; height: 100%; max-height: none;
  margin: 0; padding: 32px;
  overflow: auto; overscroll-behavior: contain;
  background: var(--paper); color: var(--ink);
  border: 0; border-left: 1px solid var(--ink); border-radius: 0;
  font-family: var(--voice);
}
.flow-map::backdrop { background: rgb(36 36 31 / .28); }
.flow-map[open] { animation: sheet-in 320ms var(--ease-out); }
@keyframes sheet-in { from { transform: translateX(100%); } }
.map-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 16px; align-items: start; }
.map-head h2 { font: 400 var(--fs-h2)/calc(1.15 + var(--lh-add)) var(--voice); letter-spacing: var(--track); }
.map-count { grid-column: 1; margin-top: 6px; font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2); font-variant-numeric: tabular-nums lining-nums; }
.map-close {
  grid-column: 2; grid-row: 1 / span 2;
  appearance: none; cursor: pointer; margin: 0;
  min-height: 44px; padding: 0 16px;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--ink); border-radius: 0;
  font: 400 var(--fs-ui)/1 var(--tool);
}
.map-close:hover { background: var(--ink); color: var(--paper); }
.map-body { margin-top: 24px; }
.map-ring { width: clamp(200px, 50vw, 240px); aspect-ratio: 1; margin: 8px 0 28px; }
.map-group + .map-group { margin-top: 20px; }
.map-group-label { font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink); padding-bottom: 6px; }
.map-group-hint { color: var(--ink-2); }
.map-group-hint::before { content: "·"; content: "·" / ""; margin: 0 .4em 0 .1em; }
.map-item { border-top: 1px solid var(--rule); }
.map-group > ul { border-bottom: 1px solid var(--rule); }
.map-item > a {
  position: relative;
  display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; column-gap: 12px; align-items: center;
  min-height: 44px; padding: 6px 0;
  color: var(--ink); text-decoration: none;
}
.map-item > a::before {
  content: ""; width: 13px; height: 12.4px; background: var(--edge);
  -webkit-mask: url("/img/piece-open.svg") center / contain no-repeat;
  mask: url("/img/piece-open.svg") center / contain no-repeat;
}
.map-item.is-seen > a::before { background: var(--ink-2); }
.map-item.is-mapped > a::before { background: var(--ink); -webkit-mask-image: url("/img/piece.svg"); mask-image: url("/img/piece.svg"); }
.map-item.is-here > a::before { background: var(--vermilion); -webkit-mask-image: url("/img/piece.svg"); mask-image: url("/img/piece.svg"); }
.map-item > a[aria-current]::after { content: ""; position: absolute; left: -16px; top: calc(50% - .55em); width: 2px; height: 1.1em; background: var(--vermilion); }
.map-item-label { font: 400 19px/calc(1.3 + var(--lh-add)) var(--voice); }
.map-item > a:hover .map-item-label { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
.map-item-status { font: 400 var(--fs-small)/1.3 var(--tool); color: var(--ink-2); text-align: right; }
.map-site { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--ink); }
.map-site ul { columns: 2; column-gap: 24px; }
.map-site li { break-inside: avoid; }
.map-site a { display: inline-flex; align-items: center; min-height: 32px; font: 400 15px/1.25 var(--tool); text-decoration: none; }
.map-site a:hover { text-decoration: underline; }
@media (max-width: 599px) {
  .flow-map { width: 100%; border-left: 0; padding: 20px var(--margin) 32px; }
  .map-item > a[aria-current]::after { left: -12px; }
}

/* ============================================================ 13. Motion (§5) */

.flow[data-dir="forward"] { --dir: 1; }
.flow[data-dir="back"] { --dir: -1; }
.flow.is-live .slide.is-active { visibility: visible; z-index: 1; }
.flow.is-live .slide.is-leaving { visibility: visible; z-index: 0; pointer-events: none; animation: slide-out var(--t-leave) var(--ease-in) both; }
@keyframes slide-out { to { opacity: 0; transform: translateX(calc(var(--dir, 1) * -16px)); } }

/* reading order: tag → main text → detail → action; the exhibit counts as one child */
.flow.is-live .slide.is-active > * { animation: rise var(--t-enter) var(--ease-out) both; animation-delay: calc(120ms + var(--k, 0) * 110ms); }
.slide > :nth-child(2) { --k: 1; }
.slide > :nth-child(3) { --k: 2; }
.slide > :nth-child(n+4) { --k: 3; }
@keyframes rise { from { opacity: 0; transform: translateX(calc(var(--dir, 1) * 24px)); } }
@keyframes fade { from { opacity: 0; } }

/* the cover: the ring's pieces fade in, the first one lifts, then the lines rise */
.flow.is-live .slide[data-kind="cover"].is-active > .cover,
.flow.is-live .slide[data-kind="cover"].is-active > .cover-ring { animation: none; }
.flow.is-live .slide[data-kind="cover"].is-active .cover-line { animation: rise var(--t-enter) var(--ease-out) both; animation-delay: 640ms; }
.flow.is-live .slide[data-kind="cover"].is-active .cover-line-2 { animation-delay: 750ms; }
.flow.is-live .slide[data-kind="cover"].is-active > .slide-actions { animation-delay: 860ms; }
.flow.is-live .slide[data-kind="cover"].is-active > :is(.cover-meta, .cover-by) { animation-delay: 970ms; }
.flow.is-live .slide.is-active .ring-cover .ring-seg { animation: seg-in 240ms linear both; animation-delay: calc(var(--i, 0) * 40ms); }
.flow.is-live .slide.is-active .ring-cover .ring-seg.is-here {
  animation: seg-in 240ms linear both, seg-lift 480ms var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 40ms), 560ms;
}
@keyframes seg-in { from { opacity: 0; } }
@keyframes seg-lift {
  from { transform: none; fill: transparent; stroke: var(--edge); }
  to { transform: translate(var(--dx), var(--dy)); fill: var(--vermilion); stroke: var(--vermilion); }
}

/* the Map moment: 500ms after the text lands, the lifted piece docks into its slot in ink */
.flow.is-live .slide.is-active .ring-insight .ring-seg.is-docking { animation: dock 600ms var(--ease-inout) 1140ms both; }
@keyframes dock {
  0% { transform: translate(var(--dx), var(--dy)); fill: var(--vermilion); stroke: var(--vermilion); }
  70% { transform: none; fill: var(--vermilion); stroke: var(--vermilion); }
  100% { transform: none; fill: var(--ink); stroke: var(--ink); }
}

/* the staircase (H6): each word appears where a small ink block cursor sits.
 * 180ms per word, a 380ms breath at each full stop; the slide is .is-typesetting, then .is-set. */
.flow.is-live .slide.is-active > .answer[data-staircase] { animation: none; }
.slide.is-typesetting .answer[data-staircase] .w {
  animation: w-in 1ms linear both;
  animation-delay: calc(300ms + var(--i, 0) * 180ms + var(--line, 0) * 380ms);
}
@keyframes w-in { from { color: transparent; } }
.slide.is-typesetting .answer[data-staircase] .w::before {
  content: ""; position: absolute; left: .02em; bottom: .16em;
  width: .5em; height: .9em; background: var(--ink);
  opacity: 0; pointer-events: none;
  animation: cursor 180ms steps(1, end) both;
  animation-fill-mode: none;
  animation-delay: calc(120ms + var(--i, 0) * 180ms + var(--line, 0) * 380ms);
}
/* at each full stop the cursor rests after the period while the machine takes its breath */
.slide.is-typesetting .answer[data-staircase] .answer-line > .w:last-child::after {
  content: ""; position: absolute; left: calc(100% + .12em); bottom: .16em;
  width: .5em; height: .9em; background: var(--ink);
  opacity: 0; pointer-events: none;
  animation: cursor 380ms steps(1, end);
  animation-fill-mode: none;
  animation-delay: calc(300ms + var(--i, 0) * 180ms + var(--line, 0) * 380ms);
}
@keyframes cursor { from, to { opacity: 1; } }
.slide.is-typesetting .answer-ghost { visibility: hidden; }
.slide.is-set .answer-ghost { animation: fade var(--t-state) linear both; }
/* the note and the action arrive once the first sentence is written */
.flow.is-live .slide.is-typesetting > .answer-detail { animation-delay: 1300ms; }
.flow.is-live .slide.is-typesetting > .slide-actions { animation-delay: 1410ms; }

/* the finale method: the return line is drawn right to left once the five steps are in */
.flow.is-live .slide.is-active .method-steps::after { animation: return-line var(--t-draw) var(--ease-inout) 900ms both; }
.flow.is-live .slide.is-active .method-steps::before { animation: fade var(--t-state) linear 1760ms both; }
@keyframes return-line { from { clip-path: inset(0 0 0 100%); } to { clip-path: inset(0 0 0 0); } }
@media (max-width: 1023px) {
  .flow.is-live .slide.is-active .method-steps::after { animation-name: return-line-up; }
  @keyframes return-line-up { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 0 0); } }
}

/* calm (§5.7): no transforms, flights, sweeps, draws or scans; order kept, timing dropped.
 * A slide arrives at once (the spec allows a 160ms fade "or the change is instant"): instant
 * means text is never read, or measured, half-faded. */
html[data-calm="true"] .flow .slide.is-active > * { animation: none; }
html[data-calm="true"] .flow .slide.is-leaving { animation: none; visibility: hidden; }
html[data-calm="true"] .flow .slide.is-active .cover-line,
html[data-calm="true"] .flow .slide.is-set .answer-ghost,
html[data-calm="true"] .flow .slide.is-active > .answer[data-staircase],
html[data-calm="true"] .ring-seg,
html[data-calm="true"] .ring-dot,
html[data-calm="true"] .ring-path,
html[data-calm="true"] .flow .method-steps::before,
html[data-calm="true"] .flow .method-steps::after,
html[data-calm="true"] .flow-map[open] { animation: none !important; transition: none !important; }
html[data-calm="true"] :is(.go-continue, .try-lead) { transition: none; } /* never read half-faded */
html[data-calm="true"] .flow-prev:hover > [aria-hidden],
html[data-calm="true"] .flow-next:hover > [aria-hidden],
html[data-calm="true"] .go:hover::after,
html[data-calm="true"] .door:is(:hover, :focus-visible)::after { transform: none; }
html[data-calm="true"] .exhibit *,
html[data-calm="true"] .exhibit *::before,
html[data-calm="true"] .exhibit *::after {
  animation: none !important;
  transition-property: opacity, color, background-color, border-color !important;
  transition-duration: 120ms !important;
}
@media (prefers-reduced-motion: reduce) {
  .flow .slide.is-active > *, .flow .slide.is-active .cover-line, .flow .slide.is-set .answer-ghost { animation: none !important; }
  .flow .slide.is-leaving { animation: none; visibility: hidden; }
  .ring-seg, .ring-dot, .ring-path, .flow-map[open], .method-steps::before, .method-steps::after,
  .exhibit *, .exhibit *::before, .exhibit *::after { animation: none !important; transition-duration: 1ms !important; }
  .go-continue, .try-lead { transition: none; }
}

/* ============================================================ 14. Exhibit frame and shared vocabulary (§6) */

.exhibit { position: relative; margin-top: 24px; min-width: 0; }
@media (min-width: 1024px) { .flow.is-live .slide > .exhibit { grid-column: 2 / 12; } }
.exhibit-stage { container: exhibit / inline-size; position: relative; }
.exhibit-fallback {
  padding: 20px;
  background: var(--paper-lo);
  font: 400 var(--fs-body)/calc(1.5 + var(--lh-add)) var(--voice);
  color: var(--ink);
  max-width: 44em;
}

/* skeleton: .x-step · .x-material · .x-readout · .x-foot (.x-caption + .x-controls)
 *
 * The caption's markup contract (copy it):
 *   <p class="x-caption"><span class="x-caption-text">Plus le casier…</span> <span class="x-illustrative">Exemple illustratif</span></p>
 * flow.css glues « ·» to the end of .x-caption-text with a no-break space, and the honesty
 * label never breaks inside. A line can only wrap after the dot: «…probable. ·» / «Exemple
 * illustratif». Never « · Exemple» at the start of a line, never «Exemple / illustratif».
 * Below 560px the label takes its own line and the dot goes. */
.x-step, .x-caption, .x-illustrative, .x-label {
  font: 400 var(--fs-small)/1.4 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
}
.x-step { margin: 0 0 12px; }
.x-label { display: block; margin: 0 0 8px; }
.x-caption { margin: 0; max-width: 48em; text-wrap: pretty; }
.x-caption .x-illustrative { white-space: nowrap; }
.x-caption-text:has(~ .x-illustrative)::after { content: "\a0·"; content: "\a0·" / ""; }
@container exhibit (max-width: 559px) {
  .x-caption .x-illustrative { display: block; }
  .x-caption-text:has(~ .x-illustrative)::after { content: none; }
}
.x-material { position: relative; }
.x-readout { margin-top: 20px; }
/* the foot: caption above the controls; from 880px they share one row, caption left, controls
 * right (narrower, both would wrap beside each other) */
.x-foot { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; margin-top: 16px; }
@container exhibit (min-width: 880px) {
  .x-foot { flex-direction: row; align-items: center; justify-content: space-between; gap: 12px 32px; }
  .x-foot > .x-controls { flex: none; margin-left: auto; }
}

/* a word, token or phrase tile */
.x-piece {
  display: inline-block;
  margin: 0;
  padding: .28em .6em .32em;
  font: 400 var(--fs-piece)/1.1 var(--voice);
  color: var(--ink);
  background: var(--paper-hi);
  border: 1px solid var(--ink); border-radius: 0;
  box-shadow: none;
  text-align: center;
  white-space: nowrap;
  transition: background-color var(--t-state) linear, color var(--t-state) linear, border-color var(--t-state) linear;
}
button.x-piece { appearance: none; cursor: pointer; }
/* hover never repaints a pressed, machine or unavailable piece (it would put paper text on paper-lo) */
:where(.flow:not(.is-pointer-stale)) button.x-piece:not(.is-machine, [aria-pressed="true"], [aria-disabled="true"]):hover { background: var(--paper-lo); }
button.x-piece:active { transform: translateY(1px); }
/* unavailable (a piece that is its own button), or waiting to be read while the machine
 * holds the case (.is-dimmed inside an aria-disabled cell): dashed, still focusable */
.x-piece[aria-disabled="true"], [aria-disabled="true"] > .x-piece.is-dimmed { border-style: dashed; border-color: var(--edge); color: var(--ink-3); cursor: default; transform: none; }
.x-piece.is-machine,
.x-piece[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.x-piece.is-open { background: transparent; border: 1px dashed var(--edge); }
.x-piece-n {
  display: block; margin-top: .35em;
  font: 400 var(--fs-num)/1.2 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
}
:is(.x-piece.is-machine, .x-piece[aria-pressed="true"]) .x-piece-n { color: var(--paper-lo); }

/* where the next piece lands: a dashed box, and the proofreader's caret beneath (your turn) */
.x-slot {
  display: inline-block; position: relative;
  min-width: 4.5ch; min-height: 1.15em;
  padding: 0 .12em;
  line-height: 1.1;
  border: 1px dashed var(--edge);
  color: transparent;
  vertical-align: baseline;
}
.x-slot::after {
  content: ""; position: absolute; left: 50%; top: calc(100% + 6px);
  width: .34em; height: .34em; min-width: 8px; min-height: 8px;
  border-left: 2px solid var(--vermilion); border-top: 2px solid var(--vermilion);
  transform: translateX(-50%) rotate(45deg);
}
.x-slot.is-machine::after { border-color: var(--ink); }
.x-slot.is-preview { color: var(--ink-3); } /* ink-3 is text only at 24px or more (§2.1) */
@media (max-height: 560px) { .x-slot.is-preview { color: var(--ink-2); } } /* material is 22px here */

/* the line being composed or analysed, on a 1px ink baseline rule */
.x-sentence {
  margin: 0;
  font: 400 var(--fs-material)/calc(1.35 + var(--lh-add)) var(--voice);
  color: var(--ink);
  min-block-size: calc(var(--lines, 2) * (1.35em + var(--lh-add) * 1em) + .2em);
  padding-bottom: .16em;
  border-bottom: 1px solid var(--ink);
  text-wrap: pretty;
}

/* a bet's weight under a word: 1 → 8px (--p is 0–100 via CSSOM) */
.x-weight { border-bottom: max(1px, calc(var(--p, 0) * .08px)) solid var(--ink); }
.x-weight.is-mine { border-bottom-color: var(--vermilion); }

/* the chain of bets: the odds of each chosen word */
/* equal cells that wrap as a grid: a lone last cell never stretches wider than the others */
.x-bets { display: grid; grid-template-columns: repeat(auto-fill, minmax(max(5.5em, 25% - var(--gap)), 1fr)); gap: 20px var(--gap); margin: 0; padding: 0; list-style: none; }
.x-bet { min-width: 0; max-width: 14em; display: grid; row-gap: 8px; align-content: start; }
.x-bet-n { font: 400 var(--fs-num-lg)/1 var(--tool); font-variant-numeric: tabular-nums lining-nums; color: var(--ink); }
.x-bet-track, .x-bar-track { position: relative; height: 6px; }
.x-bet-track::before, .x-bar-track::before { content: ""; position: absolute; left: 0; right: 0; top: 2.5px; border-top: 1px solid var(--edge); }
.x-bet-track::after, .x-bar-track::after {
  content: ""; position: absolute; inset: 0;
  background: var(--ink);
  /* uncovered from the left (clip-path), never squeezed (scaleX): after a scaleX transition Chrome
   * may keep the bar rasterized at a reduced scale, its 6px of ink blurred at 1x. The cut runs across
   * only, so the ink's own edges stay on whole pixels. */
  clip-path: inset(-1px calc(100% - var(--p, 0) * 1%) -1px 0);
  transition: clip-path var(--t-move) var(--ease-out);
}
.x-bet.is-mine .x-bet-track::after { background: var(--vermilion); }
.x-bet-w { font: 400 var(--fs-piece)/1.2 var(--voice); color: var(--ink); overflow-wrap: anywhere; }
.x-punct-name { display: inline-block; margin-left: .4em; font: 400 max(14px, .55em)/1 var(--tool); color: var(--ink-2); }

/* area = probability: a case of compartments (5 or fewer, short labels). The 1px ink frame is
 * drawn above the compartments, so none can ever paint over it (a layout measured with
 * rounded sizes once hid the right side). Lay compartments out inside the floored inner box:
 * W = floor(rect.width - 2), H = floor(rect.height - 2). */
.x-case {
  position: relative;
  height: var(--case-h, 260px);
  background: var(--paper-lo);
  border: 1px solid transparent;
}
.x-case::after {
  content: ""; position: absolute; inset: -1px; z-index: 1;
  border: 1px solid var(--ink);
  pointer-events: none;
}
.x-cell {
  position: absolute;
  left: var(--x, 0); top: var(--y, 0); width: var(--w, 100%); height: var(--h, 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  margin: 0;
  padding: 24px 6px 6px; /* the percentage sits top-left; the piece is centred in the room under it */
  background: var(--paper-lo); color: var(--ink);
  border: 0; border-radius: 0;
  border-left: var(--cut-l, 0) solid var(--edge);
  border-top: var(--cut-t, 0) solid var(--edge);
  font: inherit;
  transition: background-color var(--t-state) linear;
}
/* a narrow compartment: less padding around the word */
.x-cell.is-tight > .x-piece { padding-left: .3em; padding-right: .3em; }
/* a short compartment: the percentage beside the piece, both on one line */
.x-cell.is-short { flex-direction: row; justify-content: flex-end; padding: 4px 6px; }
.x-cell.is-short > .x-cell-n { top: 50%; translate: 0 -50%; }
.x-cell.is-short > .x-piece { padding-left: .3em; padding-right: .3em; }
button.x-cell { appearance: none; cursor: pointer; }
:where(.flow:not(.is-pointer-stale)) button.x-cell:hover { background: var(--paper); }
button.x-cell:focus-visible { outline-offset: -6px; z-index: 2; } /* above the frame */
button.x-cell[aria-disabled="true"] { cursor: default; }
button.x-cell[aria-disabled="true"]:hover { background: var(--paper-lo); }
.x-cell > .x-piece { font-size: var(--fs-cell, var(--fs-piece)); }
button.x-cell:active > .x-piece { transform: translateY(1px); }
.x-cell-n {
  position: absolute; left: 8px; top: 6px;
  font: 400 var(--fs-num)/1.2 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
}
/* the remainder: «autres mots 4 %», hatched, readable, not clickable. Its chip keeps at least
 * 2px of hatch around it; in a small compartment it stacks (see prediction's .is-tight). */
.x-rest {
  padding: 2px;
  background:
    repeating-linear-gradient(-45deg, transparent 0 6px, var(--rule) 6px 7px),
    var(--paper-lo);
}
.x-rest-label {
  display: inline-block; max-width: 100%;
  padding: 2px 6px;
  background: var(--paper);
  font: 400 var(--fs-small)/1.3 var(--tool); color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
  text-align: center;
}

/* a ranked list (more than 5 candidates or long labels) */
.x-bars { display: grid; margin: 0; padding: 0; list-style: none; }
.x-bar {
  display: grid; grid-template-columns: minmax(6em, 2fr) minmax(0, 3fr) 4.5ch; align-items: center; column-gap: 16px;
  min-height: 44px; padding: 4px 0;
  width: 100%; margin: 0; border: 0; background: none; text-align: left; color: var(--ink);
  font: inherit;
}
.x-bar-label { font: 400 var(--fs-piece)/1.2 var(--voice); }
.x-bar-n { font: 400 var(--fs-num)/1.2 var(--tool); color: var(--ink); text-align: right; font-variant-numeric: tabular-nums lining-nums; }
:is(button, a).x-bar { cursor: pointer; }
:where(.flow:not(.is-pointer-stale)) :is(button, a).x-bar:hover { background: var(--paper-lo); }

/* the visitor's marks: they stay after the act (§6.6)
 *
 * The circle's clearance rule (every exhibit that circles words): an ellipse keeps 4px or more
 * from the glyphs around it and from any other ellipse. A word circled in running text
 * (.x-sentence, .x-doc, .x-output) gets .3em of margin a side, as much as its ellipse
 * overhangs it: with Georgia's .24em word space that leaves .24em to the next glyph and to a
 * circled neighbour's ellipse (5.3px at 22px, the smallest material size), and a circled word
 * that starts a line keeps its ellipse on the text column. The margin changes the
 * line, so circle words in a state change (a record), not while the visitor reads. A tile
 * (.x-piece) keeps the wider ellipse so its corners stay inside. */
@property --sweep { syntax: "<angle>"; inherits: false; initial-value: 360deg; }
.x-mark-circle { position: relative; }
.x-mark-circle::after {
  content: ""; position: absolute; inset: -.28em -.3em; pointer-events: none;
  border: 2px solid var(--vermilion); border-radius: 50%;
  -webkit-mask: conic-gradient(from -100deg, #000 var(--sweep), transparent 0);
  mask: conic-gradient(from -100deg, #000 var(--sweep), transparent 0);
  animation: x-sweep 360ms var(--ease-out) both;
}
@keyframes x-sweep { from { --sweep: 0deg; } }
:is(.x-sentence, .x-doc, .x-output) .x-mark-circle:not(.x-piece) { margin-inline: .3em; }
/* and the ellipse overhangs its word by .28em above and below: lines holding one are 1.6 apart,
 * so it clears the descenders of the line above (.18em, 4.4px at 24px) */
:is(.x-sentence, .x-doc, .x-output):has(.x-mark-circle:not(.x-piece)) { line-height: calc(1.6 + var(--lh-add)); }
.x-sentence:has(.x-mark-circle:not(.x-piece)) { padding-bottom: .3em; } /* and clears the baseline rule */
.x-piece.x-mark-circle::after { inset: -.5em -.7em; } /* a tile's corners stay inside the ellipse */
/* underline and strike: a 2px bar per line fragment, revealed left to right */
.x-mark-underline, .x-mark-strike {
  background-image: linear-gradient(var(--vermilion), var(--vermilion));
  background-repeat: no-repeat;
  background-size: 100% 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  animation: x-mark 240ms var(--ease-out) both;
}
.x-mark-underline { background-position: 0 calc(100% - .02em); padding-bottom: .06em; }
.x-mark-strike { background-position: 0 58%; }
@keyframes x-mark { from { background-size: 0 2px; } }
.x-mark-caret { position: relative; }
.x-mark-caret::after {
  content: ""; position: absolute; left: 0; top: calc(100% + 2px);
  width: 8px; height: 8px; margin-left: -5px;
  border-left: 2px solid var(--vermilion); border-top: 2px solid var(--vermilion);
  transform: rotate(45deg);
}

/* the educator's margin note: one visible per state */
.x-note { position: relative; margin: 16px 0 0; font: 400 var(--fs-note)/calc(1.45 + var(--lh-add)) var(--voice); color: var(--ink-2); max-width: 34em; }
@media (max-width: 1023px) { .x-note { padding-left: 14px; } .x-note::before { left: 0; } }

/* a document a person wrote / what the machine generated */
.x-doc { padding: clamp(20px, 2.2cqw, 24px); background: var(--paper-hi); border: 1px solid var(--edge); font: 400 var(--fs-body)/calc(1.5 + var(--lh-add)) var(--voice); color: var(--ink); }
.x-output { padding: clamp(20px, 2.2cqw, 24px); background: var(--paper-lo); font: 400 var(--fs-body)/calc(1.5 + var(--lh-add)) var(--voice); color: var(--ink); }

/* highlights: yellow = look here, sage = grounded; one at a time, never with vermilion text */
.x-highlight {
  background: linear-gradient(transparent .15em, var(--yellow) .15em, var(--yellow) calc(100% - .1em), transparent 0);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  padding: 0 .12em;
}
.x-safe { background: var(--sage); padding: 0 .2em; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
.x-safe.is-placeholder { font: 400 var(--fs-num)/1.2 var(--tool); font-variant-numeric: tabular-nums lining-nums; }

/* the exhibit's own buttons: instruments in ink and Tool; UI state is never vermilion */
.x-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.x-btn {
  appearance: none; margin: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  min-height: 44px; padding: .45em 18px; /* the label breathes at 200 % text; 44px wins at 100 % */
  font: 400 var(--fs-ui)/1.2 var(--tool);
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--ink); border-radius: 0;
  text-decoration: none;
  transition: background-color var(--t-state) linear, color var(--t-state) linear, border-color var(--t-state) linear;
}
.x-btn.is-strong,
:where(.flow:not(.is-pointer-stale)) .x-btn:hover { background: var(--ink); color: var(--paper); }
:where(.flow:not(.is-pointer-stale)) .x-btn.is-strong:hover { background: var(--ink-2); border-color: var(--ink-2); }
.x-btn:active { transform: translateY(1px); }
/* unavailable wins over every hover look (a strong button under the pointer never turns ink-2 on ink-2) */
.x-btn[aria-disabled="true"], .x-btn[aria-disabled="true"]:hover { cursor: default; transform: none; background: var(--paper); color: var(--ink-2); border-style: dashed; border-color: var(--edge); }
.x-btn-quiet {
  appearance: none; margin: 0; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; min-height: 44px;
  background: none; border: 0; border-radius: 0;
  font: 400 var(--fs-ui)/1.2 var(--tool); color: var(--ink);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .25em;
}
:where(.flow:not(.is-pointer-stale)) .x-btn-quiet:hover { text-decoration-color: var(--vermilion); text-decoration-thickness: 2px; }
.x-btn-quiet[aria-disabled="true"], .x-btn-quiet[aria-disabled="true"]:hover { text-decoration-thickness: 1px; cursor: default; color: var(--ink-2); text-decoration-color: var(--edge); }

/* a segmented choice: buttons with aria-pressed, or labels holding radios */
.x-seg { display: inline-flex; flex-wrap: nowrap; max-width: 100%; margin: 0; padding: 0; border: 1px solid var(--ink); }
.x-seg > :is(button, label) {
  appearance: none; margin: 0; cursor: pointer; position: relative;
  flex: 1 1 auto;
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
  min-height: 44px; padding: 8px 16px;
  font: 400 var(--fs-ui)/1.2 var(--tool); color: var(--ink);
  background: var(--paper);
  border: 0; border-left: 1px solid var(--ink); border-radius: 0;
}
.x-seg > :is(button, label):first-child { border-left: 0; }
:where(.flow:not(.is-pointer-stale)) .x-seg > :is(button, label):not([aria-pressed="true"], [aria-checked="true"], :has(input:checked)):hover { background: var(--paper-lo); }
.x-seg > [aria-pressed="true"], .x-seg > [aria-checked="true"], .x-seg > label:has(input:checked) { background: var(--ink); color: var(--paper); }
.x-seg input[type="radio"] { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.x-seg > :is(button, label):focus-visible, .x-seg > label:has(input:focus-visible) { outline: 2px solid var(--vermilion); outline-offset: -5px; z-index: 1; }
/* the ring sits inside the segment, on paper; on a pressed (ink) segment it turns paper-hi,
 * because vermilion on ink is never allowed (§2.2) */
.x-seg > :is([aria-pressed="true"], [aria-checked="true"]):focus-visible,
.x-seg > label:has(input:checked:focus-visible) { outline-color: var(--paper-hi); }
@container exhibit (max-width: 559px) {
  .x-seg { display: flex; flex-direction: column; width: 100%; }
  .x-seg > :is(button, label) { border-left: 0; border-top: 1px solid var(--ink); justify-content: flex-start; text-align: left; }
  .x-seg > :is(button, label):first-child { border-top: 0; }
}

/* a styled range: 44px hit area, 2px track, a 10 × 32 ink thumb; stops named in words */
.x-range { -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 44px; margin: 0; background: transparent; cursor: pointer; }
.x-range::-webkit-slider-runnable-track { height: 2px; background: linear-gradient(to right, var(--ink) var(--fill, 0%), var(--edge) var(--fill, 0%)); }
.x-range::-webkit-slider-thumb { -webkit-appearance: none; width: 10px; height: 32px; margin-top: -15px; background: var(--ink); border: 0; border-radius: 0; }
.x-range::-moz-range-track { height: 2px; background: var(--edge); }
.x-range::-moz-range-progress { height: 2px; background: var(--ink); }
.x-range::-moz-range-thumb { width: 10px; height: 32px; background: var(--ink); border: 0; border-radius: 0; }
.x-range-stops { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); margin: 4px 0 0; padding: 0; list-style: none; font: 400 var(--fs-small)/1.3 var(--tool); color: var(--ink-2); }
.x-range-stops > li:last-child { text-align: right; }
.x-range-stops > li:not(:first-child):not(:last-child) { text-align: center; }

/* text entry */
.x-input {
  display: block; width: 100%; height: 52px; margin: 0; padding: 0 14px;
  background: var(--paper-hi); color: var(--ink);
  border: 1px solid var(--edge); border-radius: 0;
  font: 400 17px/1.2 var(--tool);
}
.x-input:focus-visible { border-color: var(--ink); }

/* a 2D field of pieces: no axes, grid or ticks; children placed with --x/--y (0–100) */
.x-field { position: relative; border: 1px solid var(--rule); }
.x-field > .x-at { position: absolute; left: calc(var(--x, 0) * 1%); top: calc(var(--y, 0) * 1%); translate: -50% -50%; }
.x-field-region { font: 400 var(--fs-small)/1.3 var(--tool); color: var(--ink-2); }

/* SVG lines that encode a relation, a route or a weight */
.x-line { fill: none; stroke: var(--edge); stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.x-line.is-machine { stroke: var(--ink); }
.x-line.is-weight { stroke: var(--ink); stroke-width: var(--w, 1px); }
.x-line.is-mine { stroke: var(--vermilion); stroke-width: 2px; }

/* ============================================================ 15. Comfort, contrast, forced colours, no JS, print, short screens */

/* reading comfort (§9.7) */
html[data-reading="true"] { --lh-add: .15; }
html[data-reading="true"] .flow-body { word-spacing: .06em; letter-spacing: .01em; }
html[data-reading="true"] :is(.cover-line-2, .question, .answer, .insight, .how, .choose-h, .finale-h, .x-sentence) { letter-spacing: .01em; }
html[data-reading="true"] :is(.answer, .insight) { max-width: 17em; }
html[data-reading="true"] :is(.exhibit-fallback, .x-doc, .x-output) { max-width: 30em; }
html[data-reading="true"] .x-note { font-size: var(--fs-lead); } /* the slide's own notes are set at lead size already */
html[data-reading="true"] .answer[data-staircase] {
  max-width: none;
  /* with .01em letter- and .06em word-spacing the lines are wider: «Elle devine le mot suivant.»
   * about 12.2em, "Then the next one." about 8.7em, so each sentence still keeps its line */
  font-size: min(var(--fs-question),
    calc((100cqw + var(--margin) - var(--pad-r) - 4 * var(--step)) / 8.9),
    calc((100cqw + var(--margin) - var(--pad-r)) / 12.6));
}
/* ink-3 is for text of 24px or more (§2.1). Where the staircase is set smaller, its third line
 * takes ink-2: on a slide of 295px or less in reading comfort (the size is (100cqw + 8px) / 12.6:
 * 23.5px at 320), 275px or less otherwise (/ 11.8). */
@container slide (max-width: 295px) { html[data-reading="true"] .answer-line:nth-of-type(3) { color: var(--ink-2); } }
@container slide (max-width: 275px) { .answer-line:nth-of-type(3) { color: var(--ink-2); } }

/* stronger contrast (§9.8) */
html[data-contrast="true"] {
  --ink-2: #24241f;
  --ink-3: #57544c;
  --rule: #7d796e;
  --edge: #24241f;
}

/* forced colours (§9.9) */
@media (forced-colors: active) {
  /* a chosen segment keeps a fill of its own, or both options look alike once ink becomes Canvas */
  .x-seg > :is([aria-pressed="true"], [aria-checked="true"]), .x-seg > label:has(input:checked) { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  /* system colours must win over every ring state */
  .ring-seg { fill: Canvas !important; stroke: CanvasText !important; }
  .ring-seg.is-mapped { fill: CanvasText !important; stroke: CanvasText !important; }
  .ring-seg.is-here, .ring-choose .ring-seg.is-preview { fill: Highlight !important; stroke: Highlight !important; }
  /* the Map moment: the current piece docked is understood (CanvasText), still marked as yours */
  .ring:not(.ring-cover) .ring-seg.is-here.is-mapped { fill: CanvasText !important; stroke: Highlight !important; stroke-width: 2px; paint-order: stroke; }
  .ring-path { stroke: Highlight !important; }
  .ring-dot { fill: Highlight !important; }
  /* the map's glyphs follow the system's text colour whatever their state (the state rules above
   * are as specific: listed here, before the current piece's Highlight) */
  .slide-tag::before, .map-item > a::before,
  .map-item.is-seen > a::before, .map-item.is-mapped > a::before { background: CanvasText; forced-color-adjust: none; }
  /* the logo is an image in brand ink: on a dark system theme it would vanish, so it keeps a paper
   * plate of its own (never recoloured, never smaller: the plate grows around it) */
  .flow-brand { forced-color-adjust: none; background: #faf7f0; margin-left: -6px; padding: 7px 6px; }
  .flow-brand:focus-visible { outline-color: Highlight; }
  .map-item.is-here > a::before, .answer-detail::before, .insight-aside::before, .x-note::before,
  .slide [data-focus]:focus-visible::before, .map-item > a[aria-current]::after { background: Highlight; forced-color-adjust: none; }
  .x-mark-circle::after { border-color: Highlight; }
  .x-mark-underline { background: none; text-decoration: underline 2px Highlight; }
  .x-mark-strike { background: none; text-decoration: line-through 2px Highlight; }
  .x-weight { border-bottom-color: CanvasText; }
  .x-weight.is-mine { border-bottom-color: Highlight; }
  .x-slot::after { border-color: Highlight; }
  .x-bet-track::after, .x-bar-track::after { background: CanvasText; forced-color-adjust: none; }
  .x-bet.is-mine .x-bet-track::after { background: Highlight; }
  .x-slot.is-preview { color: GrayText; } /* a preview, not placed text */
  .answer .w::before, .answer .w::after { background: CanvasText; forced-color-adjust: none; }
  .go, .x-btn, .x-piece, .flow-prev, .flow-next, .map-close { border: 1px solid ButtonText; }
  /* quiet links stay underlined LinkText, never a hollow 44px box (the resume link, filled below, keeps it) */
  .go-quiet:not(.is-primary, [data-flow-resume]) { border: 0; }
  .x-cell { border-color: CanvasText; }
  .x-case::after { border-color: CanvasText; }
  .answer-ghost { color: GrayText; }
  /* the washes keep a cue when backgrounds are dropped */
  .x-highlight { forced-color-adjust: none; background: Mark; color: MarkText; }
  .x-safe { outline: 1px dashed CanvasText; outline-offset: 1px; }
  /* the filled buttons stay filled, in the system's own inverse pair: «Continuer» outlined
   * before done() and filled after it still differ */
  :is(.go-primary, .slide.is-done .go-continue, [data-flow-finish].is-primary, [data-finale-primary]) { forced-color-adjust: none; background: ButtonText; color: ButtonFace; border-color: ButtonText; }
  .slide-actions:has(> [data-flow-resume]:not([hidden])) > [data-flow-resume] { forced-color-adjust: none; background: ButtonText; color: ButtonFace; border-color: ButtonText; }
  .slide-actions:has(> [data-flow-resume]:not([hidden])) > .go-primary { forced-color-adjust: auto; background: none; color: LinkText; }
  /* buttons that keep their own colours keep a system focus ring too */
  :is(.go-primary, .go-continue, [data-flow-finish].is-primary, [data-finale-primary], [data-flow-resume]):focus-visible { outline-color: Highlight; }
}

/* without JavaScript: the deck reads top to bottom as ordinary sections (§3.9, §9.12). In-page
 * links (a choice, «Voir la réponse», the skip link, Shift+Tab) land below the sticky header, never
 * under it (WCAG 2.4.11). The comfort menu's switches need JavaScript (immersive.js): without it
 * the menu is not offered, like the map button and the deck controls. */
html:not(.flow-live) { scroll-padding-top: calc(var(--chrome-top) + 16px); }
html:not(.flow-live) .flow-top { position: sticky; top: 0; }
html:not(.flow-live) .flow-comfort { display: none; }
html:not(.flow-live) .flow .slide {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap);
  padding: 0 var(--margin); margin: 0 0 96px;
}
html:not(.flow-live) .flow .slide > * { grid-column: 2 / 10; min-width: 0; }
html:not(.flow-live) .flow .slide > :is(.exhibit, .beats, .method-steps, .doors) { grid-column: 2 / 12; }
html:not(.flow-live) .flow .flow-group:first-of-type .slide:first-child { padding-top: 48px; }
html:not(.flow-live) :is(.cover-ring, .insight-map, .choose-ring, .finale-ring, .flow-ring) { display: none; }
html:not(.flow-live) .method-steps::after, html:not(.flow-live) .method-steps::before { display: none; }
html:not(.flow-live) .method-steps { padding-bottom: 0; }
@media (max-width: 1023px) {
  html:not(.flow-live) .flow .slide { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  html:not(.flow-live) .flow .slide > *, html:not(.flow-live) .flow .slide > :is(.exhibit, .beats, .method-steps, .doors) { grid-column: 1 / -1; }
}
.flow-noscript { margin: 24px var(--margin) 48px; padding: 16px 20px; background: var(--paper-lo); font: 400 var(--fs-body)/1.5 var(--voice); }
.flow-foot { padding: 48px var(--margin) 64px; border-top: 1px solid var(--rule); font: 400 15px/1.4 var(--tool); color: var(--ink-2); }
.flow-foot ul { display: flex; flex-wrap: wrap; gap: 4px 24px; margin-bottom: 16px; }
.flow-foot a { display: inline-flex; align-items: center; min-height: 32px; color: var(--ink); }
html.flow-live .flow-foot { display: none; }

/* print: stacked slides, no chrome, ink on white, fallbacks shown, rings hidden */
@media print {
  html { background: #fff; }
  .flow-body { background: #fff; color: #000; }
  .flow-top, .flow-controls, .skip, .flow-map, .flow-foot, .flow-noscript,
  .cover-ring, .insight-map, .choose-ring, .finale-ring, .exhibit-stage, .answer-ghost { display: none !important; }
  .flow.is-live { position: static; overflow: visible; }
  .flow.is-live .slide, html:not(.flow-live) .flow .slide {
    position: static; display: block; visibility: visible !important;
    padding: 0 0 24pt; margin: 0; overflow: visible; break-inside: avoid;
  }
  .flow .slide > * { animation: none !important; opacity: 1 !important; transform: none !important; }
  .flow .slide .w { color: inherit !important; }
  .exhibit-fallback[hidden] { display: block !important; }
  .slide-actions:not(:has(> .promise)), .slide-actions > :not(.promise) { display: none; }
  .flow.is-live .slide > .answer[data-staircase], .flow .slide > .answer[data-staircase] { margin-right: 0; font-size: 2rem; }
  .answer-line { padding-right: 0; }
}

/* short laptop screens: 1366×768 and 1280×720, or 1920×1080 at 125 %, all minus the browser's
 * own bars, and 1536×864 (about 580–880px tall). Display type follows the height too, vertical space
 * tightens and the finale's doors take one row each, so every slide's action sits above the
 * bottom bar without scrolling. */
@media (min-width: 600px) and (min-height: 561px) and (max-height: 880px) {
  :root {
    --fs-cover: min(clamp(2.5rem, 1.479rem + 4.19vw, 5.25rem), 8.5vh);
    --fs-question: min(clamp(2.25rem, 1.414rem + 3.429vw, 4.5rem), 7.5vh);
    --fs-answer: min(clamp(1.875rem, 1.318rem + 2.286vw, 3.375rem), 5.6vh);
    --fs-choice: min(clamp(1.375rem, 1.143rem + 0.952vw, 2rem), 4.2vh);
    --fs-lead: min(clamp(1.1875rem, 1.071rem + 0.476vw, 1.5rem), 3.3vh);
    --fs-material: min(clamp(1.5rem, 1.129rem + 1.524vw, 2.5rem), 4.4vh);
  }
  .flow.is-live .slide { --pad-y: clamp(16px, 3vh, 32px); }
  .slide-actions { margin-top: 28px; }
  .flow.is-live .slide[data-kind="try"] { padding-bottom: 16px; } /* top-aligned: no optical lift */
  .answer + .answer-detail { margin-top: 20px; }
  .insight + .insight-aside { margin-top: 20px; }
  .how-line, .finale-line { margin-top: 14px; }
  .beats, .method-steps { margin-top: 28px; }
  .choices { --mt: 20px; }
  .choice { min-height: 56px; padding: 12px 0; }
  .choices > li::before { height: calc(12px + .98 * var(--fs-q)); }
  .choices > li:not(:last-child)::after { top: calc(12px + .98 * var(--fs-q) - 14px); }
  .doors { margin-top: 20px; }
  #finale-imagine .slide-actions { margin-top: 20px; }
}
/* the shortest of them (1366×768 and 1280×720 with the browser's bars, 1920×1080 at 150 %), and
 * phones with their toolbars (390×664): the try slide's exhibit sits closer to its lead and its
 * parts closer together, so its material keeps its size (the prediction case gives up the rest:
 * fitCase()) and a finished record fits with its «Continuer» */
@media (min-height: 561px) and (max-height: 700px) {
  .flow.is-live .slide[data-kind="try"] { --pad-y: 16px; }
  .slide[data-kind="try"] > .exhibit { margin-top: 14px; }
  .slide[data-kind="try"] > .slide-actions { margin-top: 16px; }
  .x-step { margin-bottom: 8px; }
  .x-foot { margin-top: 12px; }
}
@media (min-width: 1024px) and (min-height: 561px) and (max-height: 880px) {
  /* a door is one row: its name, its line, its arrow */
  .door { grid-template-columns: calc(7.5 * var(--fs-choice)) minmax(0, 1fr) auto; grid-template-areas: "name line arrow"; align-items: baseline; padding: 12px 0; } /* «Organisations» is 6.2em */
  .door-line { margin-top: 0; }
  .door::after { align-self: center; }
}

/* short screens and phone landscape: smaller tokens (in :root), no cover ring, top-aligned */
@media (max-height: 560px) {
  .flow.is-live .slide { align-content: start; --pad-y: 16px; padding-top: 16px; padding-bottom: 24px; }
  .flow.is-live .slide[data-kind="cover"] > .cover-ring { display: none; }
  .flow.is-live .slide[data-kind="cover"] > :is(.cover, .slide-actions, .cover-meta, .cover-by) { grid-column: 1 / -1; }
  .slide-actions { margin-top: 16px; }
  .slide[data-kind="cover"] .slide-actions { margin-top: 20px; }
  .go-primary, .go-continue, [data-flow-finish].is-primary { min-height: 48px; }
  /* the staircase keeps its three lines; the ghost line («and so on») has no room here */
  .answer-ghost { display: none; }
  .cover-meta { margin-top: 12px; }
  .how-line { margin-top: 12px; }
  .beats { margin-top: 16px; }
  .beats > li { padding: 8px 0 10px; }
  .answer + .answer-detail, .insight + .insight-aside { margin-top: 12px; }
  .choices { --mt: 16px; }
  .choice { min-height: 48px; padding: 10px 0; }
  .choices > li::before { height: calc(10px + .98 * var(--fs-q)); }
  .choices > li:not(:last-child)::after { top: calc(10px + .98 * var(--fs-q) - 14px); }
  .slide-tag { margin-bottom: 10px; }
  .method-steps { margin-top: 20px; }
  .doors { margin-top: 16px; }
  .door { padding: 10px 0; }
  .flow-prev, .flow-next { width: 44px; height: 44px; }
  .flow-top .flow-brand { padding: 0; }
  /* the Map moment stays, small and in the reading flow: ring and caption side by side */
  .flow.is-live .slide[data-kind="insight"] > .insight-map {
    grid-column: 1 / -1; grid-row: 3; align-self: start;
    display: flex; align-items: center; gap: 16px;
    width: auto; height: auto; margin: 16px 0 0;
  }
  .flow.is-live .slide[data-kind="insight"] > .slide-actions { grid-row: 4; }
  .flow.is-live .slide[data-kind="insight"] .insight-ring { width: 76px; }
  .flow.is-live .slide[data-kind="insight"] .insight-map-caption { position: static; text-align: left; }
  .flow.is-live #finale-path > .finale-ring { --d: 120px; width: 120px; height: 120px; grid-column: 1 / -1; grid-row: 2; justify-self: start; margin: 16px 0 0; }
  .flow.is-live #finale-path > .finale-count { grid-row: 3; }
  .flow.is-live #finale-path > .finale-trail { grid-row: 4; }
  .flow.is-live #finale-path > .slide-actions { grid-row: 5; }
}
/* a phone held sideways (844×390): the rings sit beside the text, as on a laptop. The path's ring is
 * centred on the text with negative block margins (it never stretches the rows, so «Suivant» stays
 * above the bar whatever the length of the trail), and the Map moment stays in view on every
 * insight: the ring above its caption, beside the insight and its note. */
@media (max-height: 560px) and (min-width: 600px) {
  .flow.is-live #finale-path > :is(.finale-h, .finale-count, .finale-trail, .slide-actions) { grid-column: 1 / 6; }
  .flow.is-live #finale-path > .finale-h { grid-row: 1; }
  .flow.is-live #finale-path > .finale-count { grid-row: 2; }
  .flow.is-live #finale-path > .finale-trail { grid-row: 3; }
  .flow.is-live #finale-path > .slide-actions { grid-row: 4; }
  .flow.is-live #finale-path > .finale-ring { --d: 156px; width: var(--d); height: var(--d); grid-column: 6 / -1; grid-row: 1 / span 4; justify-self: center; align-self: center; margin: calc(var(--d) / -2) 0; }
  .flow.is-live .slide[data-kind="insight"] > :is(.insight, .insight-aside, .slide-actions) { grid-column: 1 / 6; }
  .flow.is-live .slide[data-kind="insight"] > .insight { grid-row: 1; }
  .flow.is-live .slide[data-kind="insight"] > .insight-aside { grid-row: 2; }
  .flow.is-live .slide[data-kind="insight"] > .slide-actions { grid-row: 3; }
  .flow.is-live .slide[data-kind="insight"] > .insight-map {
    grid-column: 6 / -1; grid-row: 1 / span 3; align-self: center; justify-self: center;
    flex-direction: column; flex-wrap: nowrap; gap: 12px; margin: 0;
  }
  .flow.is-live .slide[data-kind="insight"] .insight-ring { width: 104px; }
  .flow.is-live .slide[data-kind="insight"] .insight-map-caption { flex: none; max-width: 12em; text-align: center; }
}
/* the same on a short desktop window: the text keeps its columns (2–8), the rings take the aside */
@media (max-height: 560px) and (min-width: 1024px) {
  .flow.is-live #finale-path > :is(.finale-h, .finale-count, .finale-trail, .slide-actions),
  .flow.is-live .slide[data-kind="insight"] > :is(.insight, .insight-aside, .slide-actions) { grid-column: 2 / 9; }
  .flow.is-live #finale-path > .finale-ring,
  .flow.is-live .slide[data-kind="insight"] > .insight-map { grid-column: 9 / 13; }
}
@media (max-height: 700px) and (min-width: 1024px) {
  .flow.is-live .slide[data-kind="insight"] > .insight-map { --d: min(200px, var(--aside)); }
}

/* ============================================================ 16. The action in view (§3.0) */

/* One rule on every slide: its action (the filled button, or the finish link) is whole above the
 * bottom bar on arrival, or wholly below it, never a sliver of a button. The engine measures it
 * (fitAction() in flow.js: on arrival, on resize, when the bar grows and when a comfort setting
 * changes). A slide that does not fit tightens as little as it takes: .is-lifted (its text starts
 * higher), then .is-snug (the short-screen tier's spacing and smaller rings too), then .is-tighter
 * (the booking slide on a phone: its lead and doors closer still); the tiers add up, and each is kept
 * only where it makes the action fit. When even that is not enough, the action waits wholly below the
 * fold (--push, set on the row), where scrolling finds it; the bar's → still moves on. The try
 * slide keeps the same rule through its exhibit (prediction.js fitCase()). */
.flow.is-live .slide > .slide-actions { padding-top: var(--push, 0px); }
.flow.is-live .slide > .slide-actions > .is-pushed { margin-top: var(--push-rest, 0px); } /* the lines after a whole action */
.flow.is-live .slide:is(.is-lifted, .is-snug):not([data-kind="cover"]),
.flow.is-live :is(#finale-method, #finale-imagine):is(.is-lifted, .is-snug) { padding-top: 16px; }
.flow.is-live .slide.is-snug > .slide-actions,
.flow.is-live #finale-imagine.is-snug > .slide-actions { margin-top: 16px; }
.flow.is-live .slide.is-snug > .slide-tag { margin-bottom: 10px; }
.flow.is-live .slide.is-snug > :is(.answer-detail, .insight-aside, .how-line, .finale-line, .finale-count),
.flow.is-live #finale-imagine.is-snug > .finale-line { margin-top: 12px; }
.flow.is-live .slide.is-snug > :is(.beats, .method-steps, .doors) { margin-top: 16px; }
.flow.is-live .slide.is-snug .choices { --mt: 16px; }
.flow.is-live .slide.is-snug .choice { min-height: 48px; padding: 10px 0; }
.flow.is-live .slide.is-snug .choices > li::before { height: calc(10px + .98 * var(--fs-q)); }
.flow.is-live .slide.is-snug .choices > li:not(:last-child)::after { top: calc(10px + .98 * var(--fs-q) - 14px); }
.flow.is-live .slide.is-snug .door { padding: 10px 0; }
.flow.is-live .slide.is-snug > .finale-trail { font-size: min(var(--fs-choice), 1.375rem, 100cqi / 7); } /* a long path in phone landscape: the phone's size */
@media (max-width: 1023px) {
  .flow.is-live .slide.is-snug > :is(.beats, .method-steps) > li { padding: 6px 0 8px; }
  /* the booking button and its promise on a phone with its toolbars (390×664, 375×667, 360×740):
   * the heading at answer size, the doors closer, their lines set small (still Georgia, ink-2) */
  #finale-imagine.is-snug .finale-h { font-size: min(var(--fs-answer), 100cqi / 6.2); line-height: calc(1.14 + var(--lh-add)); }
  #finale-imagine.is-snug .door { padding: 8px 0; }
  #finale-imagine.is-snug .door-line { font-size: var(--fs-small); line-height: calc(1.4 + var(--lh-add)); }
  /* .is-tighter (with .is-snug): the booking button and its whole promise on a 375×667 phone */
  #finale-imagine.is-tighter .finale-line { margin-top: 8px; font-size: var(--fs-body); line-height: calc(1.35 + var(--lh-add)); }
  #finale-imagine.is-tighter .doors { margin-top: 12px; }
  #finale-imagine.is-tighter .door { padding: 6px 0; }
  #finale-imagine.is-tighter > .slide-actions { margin-top: 12px; }
  #finale-imagine.is-tighter .slide-actions > .promise { margin-top: 6px; }
}
@media (max-width: 1023px) and (min-height: 561px) {
  .flow.is-live .slide.is-snug > .insight-map { margin-top: 16px; }
  .flow.is-live .slide.is-snug .insight-ring { width: 80px; }
  .flow.is-live .slide.is-snug > .finale-ring { width: 200px; margin-top: 16px; }
}
