/* Generated by gen-ai-era.js — edit the content JSON, not this part. */
:root { --pyr-stroke: rgba(255,255,255,.55); --slide-max: 980px; }
[data-theme="light"] { --pyr-stroke: rgba(20,24,40,.25); }
.ai-era { padding: 5.5rem 1.25rem 4rem; }
.module-main { max-width: var(--slide-max); margin: 0 auto; }
.slide { padding: 3.2rem 0 3.6rem; border-bottom: 1px solid var(--border-color, rgba(128,128,128,.25)); scroll-margin-top: 90px; }
.slide:last-child { border-bottom: 0; }
.slide h2 { font-family: 'Playfair Display', Georgia, serif; font-size: clamp(1.9rem, 3.4vw, 2.7rem); line-height: 1.15; margin: 0 0 1.2rem; text-wrap: balance; max-width: 22ch; }
.slide p { font-size: clamp(1.08rem, 1.5vw, 1.32rem); line-height: 1.6; max-width: 66ch; margin: 0 0 1rem; }
.intro { padding: 1rem 0 3rem; }
.eyebrow { font-family: 'Inter', sans-serif; font-size: .78rem; letter-spacing: .14em; text-transform: uppercase; color: var(--accent-primary, #6366f1); font-weight: 600; margin-bottom: 1rem; display: block; }
.intro h1 { font-family: 'Playfair Display', Georgia, serif; font-size: clamp(2.3rem, 5vw, 4rem); line-height: 1.08; margin: 0 0 1.1rem; max-width: 18ch; text-wrap: balance; }
.intro .intro-sub { font-size: clamp(1.15rem, 1.8vw, 1.5rem); line-height: 1.5; max-width: 60ch; color: var(--text-secondary, inherit); margin: 0 0 1.4rem; }
.intro .hint { font-size: .9rem; color: var(--text-tertiary, #888); }
.intro .hint kbd { font-family: 'JetBrains Mono', ui-monospace, monospace; border: 1px solid currentColor; border-radius: 4px; padding: 0 .35em; font-size: .85em; }
.figure-grid { margin-top: 1.4rem; display: grid; grid-template-columns: minmax(280px, 1fr) minmax(280px, 1.1fr); gap: 2rem 3rem; align-items: center; }
@media (max-width: 760px) { .figure-grid { grid-template-columns: 1fr; } }
.pyramid { width: 100%; height: auto; max-width: 520px; display: block; margin: 0 auto; }
.pyr-label { font-family: 'Inter', sans-serif; font-size: 17px; font-weight: 600; fill: #fff; }
.pyr-label.top { font-size: 15px; letter-spacing: .02em; }
.pyr-label.small { font-size: 13.5px; }
.pyr-band { font-family: 'Inter', sans-serif; font-size: 15px; font-weight: 700; fill: #1a1400; letter-spacing: .18em; }
.pyr-list { list-style: none; padding: 0; margin: 0 0 1.2rem; counter-reset: none; }
.pyr-list li { display: grid; grid-template-columns: 11rem 1fr; gap: .6rem; padding: .55rem 0; border-bottom: 1px dashed var(--border-color, rgba(128,128,128,.3)); font-size: 1.02rem; line-height: 1.45; }
.pyr-list li:last-child { border-bottom: 0; }
.pyr-list strong { color: var(--text-primary, inherit); }
@media (max-width: 480px) { .pyr-list li { grid-template-columns: 1fr; gap: .15rem; } }
.ai-line { border-left: 4px solid #f5b942; padding: .7rem 1rem; background: rgba(245,185,66,.12); border-radius: 0 10px 10px 0; font-size: 1.08rem !important; }
.levels { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.2rem; margin-top: 1.4rem; }
.level { position: relative; border: 1px solid var(--border-color, rgba(128,128,128,.3)); border-radius: 16px; padding: 1.5rem 1.4rem 1.3rem; background: var(--bg-card, rgba(128,128,128,.06)); }
.level--top { border-color: var(--accent-primary, #6366f1); box-shadow: 0 0 0 3px rgba(99,102,241,.15); }
.level-n { font-family: 'Playfair Display', serif; font-size: 2.4rem; line-height: 1; color: var(--accent-primary, #6366f1); margin-bottom: .6rem; }
.level h3 { font-size: 1.22rem; margin: 0 0 .6rem; line-height: 1.25; }
.level p { font-size: 1.02rem !important; line-height: 1.5; margin: 0 0 .7rem; }
.level-breaks { margin: 0 !important; padding-top: .6rem; border-top: 1px dashed var(--border-color, rgba(128,128,128,.35)); font-size: .96rem !important; }
.level-breaks .lb { display: inline-block; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: #e05a4e; font-weight: 700; margin-right: .4rem; }
.roadmap { list-style: none; padding: 0; margin: 1.4rem 0 0; display: grid; gap: 1rem; }
.roadmap li { display: grid; grid-template-columns: 3.2rem 1fr; gap: 1rem; align-items: start; padding: 1.1rem 1.2rem; border-radius: 14px; background: var(--bg-card, rgba(128,128,128,.06)); border: 1px solid var(--border-color, rgba(128,128,128,.3)); }
.rm-n { font-family: 'Playfair Display', serif; font-size: 2rem; line-height: 1; color: var(--accent-secondary, #10b981); }
.roadmap h3 { margin: .15rem 0 .4rem; font-size: 1.2rem; }
.roadmap p { margin: 0 !important; font-size: 1.04rem !important; }
.closing { text-align: center; padding: 4rem 0 2rem; }
.closing p { font-family: 'Playfair Display', Georgia, serif; font-size: clamp(1.5rem, 2.8vw, 2.2rem); line-height: 1.35; max-width: 30ch !important; margin: 0 auto 2rem !important; text-wrap: balance; }
.closing .btn { margin: 0 .3rem; }
.page-foot { text-align: center; font-size: .85rem; color: var(--text-tertiary, #888); padding: 2rem 0 0; }
/* teaching mode: one slide per screen */
html[data-teaching="true"] .slide { min-height: 78vh; display: flex; flex-direction: column; justify-content: center; }

/* ==== hand-written below this line: the generator never touches it ==== */

/* ---------------------------------------------------------------------------
   Simulation tokens. Two rules behind these values:
   • The panel rule is NOT --border-color: in the light theme that is
     rgba(0,0,0,.08) (css/style.css:70) and the edge vanishes on a projector.
   • #e05a4e is NOT promoted to "fault": it is already the colour of the
     Cyber-security floor, and making it mean "it broke" would read that floor
     as an error state, exactly where the text presents it as the hardest case.
   Accent roles, one each, never shared:
     --accent-primary   the human engineer, and every focus ring
     --accent-secondary what the machine emits
     --accent-tertiary  the marks the READER places
   --------------------------------------------------------------------------- */
:root {
    --sim-border: rgba(255, 255, 255, .16);
    --sim-fault: #ff6b5e;
    --sim-ok: #4ec9a0;
    --sim-ai-ink: #f5b942;
    --sim-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --sim-ease: cubic-bezier(.22, .61, .36, 1);
    --sim-panel: var(--bg-card);
    --sim-inner: var(--bg-secondary);
    /* [S] Two accent variants, declared once instead of scattered as hex.
       --accent-primary #6366f1 as plain TEXT measures 3.90:1 on the dark panel and
       4.05:1 on the light one: under the threshold in BOTH themes. Same hue, same role
       (the human engineer); the focus rings keep --accent-primary, that is a page rule.
       --sim-mark is the reader's own colour: --accent-tertiary is a global token that
       this page never re-based for the light theme, so the trace of what the students
       actually did was the weakest thing on a white screen (3.53:1). */
    --accent-ink: #8588f4;      /* 5.66:1 on the dark panel */
    --sim-mark: var(--accent-tertiary, #ec4899);
}
[data-theme="light"] {
    --sim-border: rgba(20, 24, 40, .26);
    --sim-fault: #c0392f;
    --sim-ok: #1e7d5c;
    --sim-ai-ink: #8a6100;
    /* [E] In the light theme --bg-card resolves to rgba(255,255,255,.9): the panel is
       white on white (1.00:1) and holds only by an edge at 1.46:1. The file says so
       itself l.50-53 about --border-color, and .roadmap li (generated l.38) uses exactly
       that. Measured: the old edge was 1.46:1 against the card it delimits. After:
       the panel itself reads against the page (1.10:1 of fill, a visible tint) and the
       edge goes to 1.75:1 inside / 1.93:1 outside. */
    --sim-panel: #f1f4f8;
    /* the boxes nested inside a panel invert instead of matching it:
       #f5f5f5 on #f1f4f8 is 1.03:1 and the nesting disappears. */
    --sim-inner: #ffffff;
    --accent-ink: #5154c6;      /* 5.54:1 on the light panel */
    --sim-mark: #b81e63;        /* 6.17:1 on white, was 3.53:1 */
}

/* Every simulation panel shares this frame; the figures style only their inside. */
.sim { margin: 1.6rem 0 0; }
.sim [class*="__panel"],
.sim .sim-breakage__term {
    background: var(--sim-panel);
    border: 1px solid var(--sim-border);
    border-radius: var(--radius, 14px);
    max-width: var(--slide-max);
}
.sim button:focus-visible,
.sim [tabindex]:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; border-radius: 4px; }
.sim code, .sim pre, .sim .mono {
    font-family: var(--sim-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
}
/* A figure whose script threw still leaves the prose and the static figure intact. */
.sim[data-sim-failed] { display: none; }
@media (max-width: 560px) {
    .sim code, .sim pre, .sim .mono { font-size: 12.5px; line-height: 1.45; }
}
html[data-teaching="true"] .sim code,
html[data-teaching="true"] .sim pre,
html[data-teaching="true"] .sim .mono { font-size: max(15px, 1.15em); }


/* The three level cards carry the same accent roles as the simulations, so a reader
   who meets a colour in a figure recognises whose lane it is:
     1 the AI user      → --accent-tertiary  (the marks a reader places, unexamined)
     2 the assisted dev → --accent-secondary (what the machine emits, then checked)
     3 the engineer     → --accent-primary   (the human who answers for it)
   Colour never carries the meaning alone: the number and the name are always there. */
.level[data-level="1"] .level-n { color: var(--accent-tertiary); }
.level[data-level="2"] .level-n { color: var(--accent-secondary); }
.level[data-level="3"] .level-n { color: var(--accent-primary); }
.level[data-level="1"] { border-color: color-mix(in srgb, var(--accent-tertiary) 30%, var(--border-color)); }
.level[data-level="2"] { border-color: color-mix(in srgb, var(--accent-secondary) 30%, var(--border-color)); }


/* ===================================================================
   Audit integration — visual audit of 2026-09-11, adjudicated list.
   Everything below is an override; none of it adds a duration or a curve.
   =================================================================== */

/* [A] Phone: the floating control rail parks itself as a bar at the bottom.
   css/style.css:247 puts .fixed-controls bottom/right in a COLUMN, so on a
   390 px screen it stands in the right margin of every line: 175 text blocks
   sit inside its x-band (measured), and it erases words — the theme toggle
   covers half of "Rejouer la panne" in sim-breakage (fig-sim-breakage-dark-390).
   A row at the bottom costs no reading measure; the 5.25rem gutter variant
   would have cost 48 px of it. */
@media (max-width: 760px) {
    .fixed-controls {
        top: auto; bottom: 10px; right: 10px; left: auto;
        transform: none;
        flex-direction: row; align-items: center; gap: 8px;
    }
    .ai-era { padding-bottom: 76px; }
}
/* NB for the record: both judges wrote that the operative rule is css/style.css:247
   (bottom/right, no top, no transform) and called `top:auto; transform:none` inert
   guards. Measured: courses/js-nodejs/course-style.css:713 loads AFTER it and sets
   `top: 50%; transform: translateY(-50%)`. Without `top:auto` the box keeps both
   `top` and `bottom`, stretches to 412 px tall and the buttons stay mid-screen. */


/* [B] Pyramid labels: the halo stops being the colour of its own floor.
   ai-era.html:123 gives every <text class="pyr-label"> stroke="<its own floor>",
   a halo that thickens the letter without separating it from anything. Measured
   white-on-floor: 2.23 to 4.96:1 at rest, and 1.54:1 in the light theme once
   another floor is pinned and this one falls to opacity .55. With a dark halo:
   16.4 to 17.7:1 at rest, 4.85 to 5.92:1 dimmed in light, 13.0 to 14.8 in dark.
   Dark INK (the rejected variant) disappears in the dimmed floors — crops/pdid.png. */
.pyramid .pyr-label {
    stroke: rgba(10, 12, 20, .88);
    stroke-width: 3.5px;
    stroke-linejoin: round;
    paint-order: stroke fill;
}


/* [C] The <noscript> line that replaces six empty role="figure" hooks.
   Styled here rather than with a style= attribute so the page keeps zero inline styles. */
.nojs-note {
    max-width: 66ch;
    margin: 2rem 0 0;
    padding: .9rem 1.1rem;
    border-left: 4px solid var(--sim-ai-ink);
    background: rgba(245, 185, 66, .12);
    border-radius: 0 10px 10px 0;
    font-size: 1rem;
    line-height: 1.55;
}


/* [D] Presentation mode at projector size. Measured before: .module-main = 920 px on a
   1920 screen (48 %), body text 21.12 px, section title 43.2 px, zoom 1. The rule to beat
   is teaching-mode.css:226 (`max-width: 920px`, no !important); --slide-max alone does not
   reach it. 1240 px and not 1520: --slide-max also caps the six figure panels (l.83 of the
   generated head), and 1240 is the width that was measured without document overflow.
   The !important on .level p / .roadmap p answers the generated rules l.34 and l.41.
   After: 1240 px wide, body 31.2 px, title 60.8 px. */
@media (min-width: 1400px) {
    html[data-teaching="true"] { --slide-max: 1240px; }
    html[data-teaching="true"] .ai-era .module-main { max-width: 1240px; }
    html[data-teaching="true"] .slide p     { font-size: clamp(1.45rem, 1.75vw, 1.95rem); line-height: 1.5; }
    html[data-teaching="true"] .slide h2    { font-size: clamp(2.8rem, 3.6vw, 3.8rem); }
    html[data-teaching="true"] .intro h1    { font-size: clamp(3.4rem, 4vw, 5rem); }
    html[data-teaching="true"] .intro .intro-sub { font-size: 1.75rem; }
    html[data-teaching="true"] .level h3    { font-size: 1.45rem; }
    html[data-teaching="true"] .level p,
    html[data-teaching="true"] .roadmap p   { font-size: 1.2rem !important; }
    html[data-teaching="true"] .level-breaks .lb { font-size: .95rem; }
    html[data-teaching="true"] .roadmap h3  { font-size: 1.35rem; }
    html[data-teaching="true"] .pyr-list li { font-size: 1.2rem; }
    html[data-teaching="true"] .ai-line     { font-size: 1.2rem !important; }
}

/* [D-budget] What pays for the type scale. A slide is visible from its top (teaching-mode.js
   scrolls it to y = 70) down to innerHeight: 1010 px on a 1920x1080 projector. Bigger type
   costs height, so the height has to come from somewhere; it comes from padding and gaps
   that a centred flex column of 78vh does not need, never from the lesson.
   Measured at 1920x1080, content extent of the six tallest slides, budget 1010:
     before       926 / 1629 / 1093 / 814 /  989 / 923   (2 slides cut: 619 px and 83 px)
     [D] alone   1052 / 1855 / 1065 / 946 / 1079 / 996   (4 slides cut — a regression)
     shipped      942 / 1520 /  968 / 848 /  957 / 884   (1 slide cut: 510 px)
   (the shipped row also carries [F] and [H], which shorten the figures and the cards).
   The one that stays over is "Trois façons de travailler", and it is cut LESS than before
   the integration: its figure alone is 1002 px and the three cards under it add 472 more.
   Splitting it in two is an author decision, not a CSS one (adjudication C17 says the
   same). Vertical centring is NOT touched. */
@media (min-width: 1400px) {
    html[data-teaching="true"] .slide { padding-top: 1.1rem; padding-bottom: 1.1rem; }
    html[data-teaching="true"] .slide h2 { margin-bottom: .8rem; }
    html[data-teaching="true"] .slide--sim p { margin-bottom: .6rem; }
    html[data-teaching="true"] .sim { margin-top: 1rem; }
    html[data-teaching="true"] .roadmap { gap: .7rem; }
    html[data-teaching="true"] .roadmap li { padding: .7rem 1.2rem; }
    html[data-teaching="true"] #la-pyramide .pyr-list li { padding: .3rem 0; }
    html[data-teaching="true"] #la-pyramide.slide p { margin-bottom: .5rem; }
}

/* [G] Presentation mode: the three slides that run off the screen are compacted, and
   only those three — present-0 / present-3 show a vertical centring worth keeping on
   the others, so `justify-content: center` is NOT touched globally.
   The section ids ARE the .slide elements (ai-era.html), hence `#id.slide`. */
@media (min-width: 1400px) {
    html[data-teaching="true"] #trois-facons-de-travailler.slide,
    html[data-teaching="true"] #la-pyramide.slide,
    html[data-teaching="true"] #ce-quon-va-faire.slide {
        min-height: 0; justify-content: flex-start;
    }
    html[data-teaching="true"] #trois-facons-de-travailler .levels { gap: .9rem 2.4rem; margin-top: .9rem; }
    html[data-teaching="true"] #la-pyramide .ai-line { font-size: 1rem !important; padding: .5rem .9rem; }
}


/* [E] The roadmap steps get the panel fill and a border that survives a projector.
   `.level` is deliberately NOT in this rule: [H] takes its frame away entirely. */
.roadmap li { background: var(--sim-panel); border-color: var(--sim-border); }


/* [F] `.slide p` (generated l.9, specificity 0,1,1) was beating every figure's own
   paragraph rule (0,1,0): eleven figure annotations measured at 19.2 px, i.e. exactly
   the prose size, larger than the monospace code they comment. The figures' scale was
   written and never applied. These selectors are (0,3,0) and give it back without
   touching the generated rule. `.sim` is in every one of them, so no prose is caught.
   Second block: the classroom. In presentation mode the same annotations used to ride
   `.slide p` up to 31.2 px; at 15 px they would become the smallest thing on the wall,
   so they are pinned to the figure code's own teaching size instead. */
.slide .sim .sim-flood__legend,
.slide .sim .sim-flood__verdict,
.slide .sim .sim-flood__note-p,
.slide .sim .sim-flood__key,
.slide .sim .sim-signature__note   { font-size: 15px; max-width: none; }
.slide .sim .sim-breakage__lead,
.slide .sim .sim-breakage__cap     { font-size: .95rem; max-width: none; }
.slide .sim .sim-signature__lede   { font-size: 16px; max-width: none; }
.slide .sim .sim-breakage__after,
.slide .sim .sim-breakage__end,
.slide .sim .sim-http__caption,
.slide .sim .sim-http__still-note  { font-size: 15px; line-height: 1.5; max-width: none; }

@media (min-width: 1400px) {
    html[data-teaching="true"] .slide .sim .sim-flood__legend,
    html[data-teaching="true"] .slide .sim .sim-flood__verdict,
    html[data-teaching="true"] .slide .sim .sim-flood__note-p,
    html[data-teaching="true"] .slide .sim .sim-flood__key,
    html[data-teaching="true"] .slide .sim .sim-signature__note,
    html[data-teaching="true"] .slide .sim .sim-breakage__lead,
    html[data-teaching="true"] .slide .sim .sim-breakage__cap,
    html[data-teaching="true"] .slide .sim .sim-breakage__after,
    html[data-teaching="true"] .slide .sim .sim-breakage__end,
    html[data-teaching="true"] .slide .sim .sim-http__caption,
    html[data-teaching="true"] .slide .sim .sim-http__still-note { font-size: 18px; }
    html[data-teaching="true"] .slide .sim .sim-signature__lede  { font-size: 19px; }
}


/* [H] The three "Niveau" cards stop rehearsing the figure above them.
   crops/levels-orig.png: sim-breakage draws three rounded cards with a coloured edge
   carrying "Niveau 1/2/3 …", and immediately underneath three rounded cards with a
   coloured edge carry THE SAME titles — six objects of one kind for two different
   things, a figure and the text that explains it. Undressing the second row is what
   tells them apart; the colour role survives as a rule on top (rose / violet / indigo,
   thicker for level 3, so --top keeps its rank without the indigo ring).
   Measured second defect: the three "QUAND ÇA CASSE" rules started at y 3144 / 3168 /
   3193 — a staircase, in a paragraph that asks the reader to compare the three. The
   subgrid puts them on one line. Each .level has exactly four direct children
   (.level-n, h3, p, p.level-breaks): `span 4` is checked, not assumed. */
.level {
    border: 0; border-radius: 0;
    background: none; box-shadow: none;
    border-top: 3px solid var(--border-color);
    padding: 1.1rem 0 0;
}
.level[data-level="1"] { border-top-color: var(--accent-tertiary); }
.level[data-level="2"] { border-top-color: var(--accent-secondary); }
.level[data-level="3"] { border-top-color: var(--accent-primary); border-top-width: 4px; }
.level h3 { text-wrap: balance; }
.level-breaks .lb { font-size: .84rem; }   /* was .72rem = 11.52 px */

@supports (grid-template-rows: subgrid) {
    @media (min-width: 761px) {
        .levels { gap: 2rem 2.4rem; grid-template-rows: auto auto 1fr auto; }
        .level {
            display: grid; grid-row: span 4; grid-template-rows: subgrid;
            row-gap: 0; align-content: start;
        }
    }
}


/* [N] The pyramid rides to the top of its cell. `align-items: center` (generated l.16)
   centred a 273 px drawing against a much taller column of text, leaving a hollow above
   it that reads, on a projector, like an image that has not finished loading — right
   beside a list that starts at the top. `.figure-grid` is used by this one figure and
   nothing else (ai-era.html, single occurrence), so the one-line rule is naturally
   scoped. The `display: contents` variant is rejected: it removes the .figure-text box
   that ai-era-sims.js needs to find `.ai-line` and to append the Replay button. */
.figure-grid { align-items: start; }


/* [P] The presentation toolbar stops being four invisible tab stops.
   teaching-mode.css:62-64 parks it at `top: -56px` with the comment "hidden by default"
   — parked, never hidden, so its four buttons are the FIRST focus stops on the page and
   the focus ring lands at y = -46, off screen. A screen reader heard "Quitter le mode
   présentation" before the first word of the course, with the mode off. WCAG 2.4.7 and
   4.1.2. ai-era.css is loaded after teaching-mode.css, and by this page only. */
html:not([data-teaching="true"]) .teaching-toolbar { visibility: hidden; }


/* [AA] "Module ·" with nothing after it, top left of the first screen projected in the
   session: the intro section has no <h2>, so .tm-title stays empty while teaching-mode.js
   writes the separator unconditionally. Structure checked in the live DOM:
   <div class="tm-center"><span class="tm-module-name">…<span class="tm-sep">·</span>
   <span class="tm-title" id="tm-title"></span>…
   Fixed here rather than in teaching-mode.js:312, which four courses share.
   On an engine without :has() the orphan stays — a silent, harmless degradation. */
.tm-center:has(.tm-title:empty) .tm-sep { display: none; }


/* [S] The contrast floor, for the values that live in the generated head.
   Measured on the live page, both themes, before -> after:
     .level-breaks .lb, light   #e05a4e  3.66:1 -> #b3392e  5.93:1
     .eyebrow, dark             #6366f1  4.43:1 -> #8588f4  6.83:1
     .eyebrow, light            #6366f1  4.47:1 -> #5154c6  7.11:1
   "QUAND ÇA CASSE" opens the sentence the section calls its own heart, and the eyebrow
   is the first line the room sees in presentation mode (present-0). In the dark theme
   #e05a4e already measures 5.41:1, so only the light variant moves. */
[data-theme="light"] .level-breaks .lb { color: #b3392e; }
.eyebrow { color: var(--accent-ink); }

/* ==== figures: assembled by integrate-sims.sh, do not edit here ==== */

/* ---------- sim-pyramid ---------- */
/* ------------------------------------------------------------------ sim-pyramid
   The figure is written here in its FINAL state: five floors at full opacity, the
   amber band drawn across all of them, the coloured key beside every line of the
   list, the amber rule down the left of .ai-line. `.is-armed` (added by the JS, and
   never under prefers-reduced-motion) installs the STARTING state; the beats undo
   it one by one. No JS, a broken JS or a reader who refuses motion all land on the
   same finished image, and the two-way hover / focus / tap link stays alive in all
   three cases.                                                                   */

/* ---------------------------------------------------------------- our chrome */
.sim-pyramid__host { margin: 1.4rem 0 0; }
.sim-pyramid__host .sim-pyramid__hint {
    margin: 0;
    max-width: 60ch;
    font-size: .95rem;               /* 15.2 px at 1280: no figure text below 15 px */
    line-height: 1.5;
    color: var(--text-secondary, #b8b8b8);
}
.sim-pyramid__status {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.sim-pyramid__grid .sim-pyramid__replay {
    display: inline-flex; align-items: center; gap: .45rem;
    margin: 1rem 0 0; padding: .4rem .95rem;
    font: inherit; font-size: .95rem; line-height: 1.4;
    border: 1px solid var(--sim-border, rgba(128, 128, 128, .4));
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary, inherit);
    cursor: pointer;
}
.sim-pyramid__grid .sim-pyramid__replay:hover {
    border-color: var(--accent-primary, #6366f1);
    color: var(--text-primary, inherit);
}
.sim-pyramid__grid .sim-pyramid__replay:focus-visible {
    outline: 3px solid var(--accent-primary, #6366f1); outline-offset: 2px;
}
.sim-pyramid__glyph { font-size: 1.05em; line-height: 1; }

/* ------------------------------------------------------------- FINAL STATE */
.sim-pyramid__floor {
    opacity: 1;
    transition: opacity 300ms cubic-bezier(.22, .61, .36, 1);
}
.sim-pyramid__clip {
    transform: none;
    transition: transform 700ms linear;                 /* a machine transports: linear */
}
.sim-pyramid__sweep {
    stroke: var(--text-primary, #f5f5f5);
    stroke-width: 2;                                     /* the SVG renders at ~0.82: 1.63 px on screen */
    opacity: 0;                                          /* the rule exists only while it sweeps */
    transform: translateY(var(--sim-pyramid-to, 4px));
    transition: transform 2500ms linear, opacity 120ms linear;
    pointer-events: none;
}
.sim-pyramid__hit { cursor: pointer; outline: none; stroke: transparent; stroke-width: 7; }
.sim-pyramid__ring { stroke: transparent; stroke-width: 3; }
.sim-pyramid__hit:focus-visible { stroke: var(--bg-primary, #0a0a0a); }
.sim-pyramid__hit:focus-visible + .sim-pyramid__ring { stroke: var(--accent-primary, #6366f1); }

.sim-pyramid__grid .sim-pyramid__item {
    position: relative;
    padding-left: .85rem;
    cursor: pointer;
}
.sim-pyramid__grid .sim-pyramid__item::before {           /* the coloured key: this line IS that floor */
    content: "";
    position: absolute; left: 0; top: .45rem; bottom: .45rem; z-index: 1;
    width: 3px; border-radius: 2px;
    background: var(--sim-pyramid-c, var(--sim-ai-ink, #f5b942));
    transform: scaleY(1);
    transform-origin: top left;
    transition: transform 300ms cubic-bezier(.22, .61, .36, 1);
}
/* [S] Les cinq filets sont le SEUL lien entre une ligne de la liste et son étage, et les
   couleurs d'étage sont calibrées pour un fond sombre : sur blanc elles mesurent 2,50 à
   4,06:1, sous le seuil de 3:1 pour quatre d'entre elles. Un filtre plutôt qu'une liste
   de couleurs, parce que ai-era-sims.js:212 relit les attributs fill= des polygones pour
   poser --sim-pyramid-c : la clé et l'étage ne doivent jamais diverger.
   Après : 4,71 à 7,03:1. Le ::after (le fond teinté de la ligne épinglée) n'est PAS
   touché : c'est une nappe à opacity .16, pas un porteur de sens. */
[data-theme="light"] .sim-pyramid__grid .sim-pyramid__item::before {
    filter: brightness(.7) saturate(1.1);
}
.sim-pyramid__grid .sim-pyramid__item::after {            /* the picked line, tinted with its own floor */
    content: "";
    position: absolute; inset: 0; border-radius: 8px;
    background: var(--sim-pyramid-c, var(--sim-ai-ink, #f5b942));
    opacity: 0;
    transition: opacity 300ms cubic-bezier(.22, .61, .36, 1);
    pointer-events: none;
}
.sim-pyramid__grid .sim-pyramid__name,
.sim-pyramid__grid .sim-pyramid__desc { position: relative; z-index: 1; }
.sim-pyramid__grid .sim-pyramid__item:focus { outline: none; }
.sim-pyramid__grid .sim-pyramid__item:focus-visible {
    outline: 3px solid var(--accent-primary, #6366f1); outline-offset: 2px; border-radius: 6px;
}

.sim-pyramid__grid .sim-pyramid__ai-line {
    position: relative;
    border-left-color: transparent;                      /* the 4 px rule below replaces the border */
}
.sim-pyramid__grid .sim-pyramid__ai-line::before {
    content: "";
    position: absolute; left: -4px; top: 0; bottom: 0;
    width: 4px; border-radius: 3px 0 0 3px;
    background: var(--sim-ai-ink, #f5b942);
    transform: scaleY(1);
    transform-origin: bottom;
    transition: transform 300ms cubic-bezier(.22, .61, .36, 1);
}

/* ---------------------------------------- STARTING STATE, installed by .is-armed */
.sim-pyramid__grid.is-armed .sim-pyramid__floor { opacity: .25; }
/* .25 over black still reads as a floor; over white it reads as nothing at all. */
[data-theme="light"] .sim-pyramid__grid.is-armed .sim-pyramid__floor { opacity: .34; }
.sim-pyramid__grid.is-armed .sim-pyramid__clip { transform: translateY(var(--sim-pyramid-h, 336px)); }
.sim-pyramid__grid.is-armed .sim-pyramid__sweep {
    opacity: 1;
    transform: translateY(var(--sim-pyramid-from, 306px));
}
.sim-pyramid__grid.is-armed .sim-pyramid__item::before { transform: scaleY(0); }
.sim-pyramid__grid.is-armed .sim-pyramid__ai-line::before { transform: scaleY(0); }

/* Arming is an instant cut, never a transition of its own. */
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__floor,
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__clip,
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__sweep,
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__item::before,
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__item::after,
.sim-pyramid__grid.sim-pyramid--jump .sim-pyramid__ai-line::before { transition: none; }

/* --------------------------------------------------------------- THE BEATS */
.sim-pyramid__grid.is-armed .sim-pyramid__floor.is-on { opacity: 1; }
.sim-pyramid__grid.is-armed .sim-pyramid__item.is-lit::before { transform: scaleY(1); }
.sim-pyramid__grid.is-armed.sim-pyramid--sweep .sim-pyramid__sweep {
    transform: translateY(var(--sim-pyramid-to, 4px));
}
.sim-pyramid__grid.is-armed.sim-pyramid--swept .sim-pyramid__sweep { opacity: 0; }
.sim-pyramid__grid.is-armed.sim-pyramid--band .sim-pyramid__clip { transform: none; }
.sim-pyramid__grid.is-armed.sim-pyramid--rail .sim-pyramid__ai-line::before { transform: scaleY(1); }

/* ----------------------------------------------------- THE READER'S HIGHLIGHT
   Alive at all times — before, during and after the sequence, and under
   prefers-reduced-motion, because pointing at something is not motion. The grid
   class is repeated so these rules outrank the beats above whatever the state. */
.sim-pyramid__grid.sim-pyramid__grid[data-lit] .sim-pyramid__floor { opacity: .55; }
/* A floor pushed into the background is still a floor you can read: white lettering
   over a washed-out fill disappears in the light theme, so the labels only step back
   to .8 and keep the halo in their own colour. */
.sim-pyramid__grid.sim-pyramid__grid[data-lit] .sim-pyramid__floor-label { opacity: .8; }
.sim-pyramid__grid.sim-pyramid__grid[data-lit="0"] .sim-pyramid__floor[data-floor="0"],
.sim-pyramid__grid.sim-pyramid__grid[data-lit="1"] .sim-pyramid__floor[data-floor="1"],
.sim-pyramid__grid.sim-pyramid__grid[data-lit="2"] .sim-pyramid__floor[data-floor="2"],
.sim-pyramid__grid.sim-pyramid__grid[data-lit="3"] .sim-pyramid__floor[data-floor="3"],
.sim-pyramid__grid.sim-pyramid__grid[data-lit="4"] .sim-pyramid__floor[data-floor="4"] { opacity: 1; }

.sim-pyramid__grid.sim-pyramid__grid[data-lit="0"] .sim-pyramid__item[data-floor="0"]::before,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="1"] .sim-pyramid__item[data-floor="1"]::before,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="2"] .sim-pyramid__item[data-floor="2"]::before,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="3"] .sim-pyramid__item[data-floor="3"]::before,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="4"] .sim-pyramid__item[data-floor="4"]::before {
    transform: scaleY(1) scaleX(2);                      /* the key doubles: width, not just colour */
}
.sim-pyramid__grid.sim-pyramid__grid[data-lit="0"] .sim-pyramid__item[data-floor="0"]::after,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="1"] .sim-pyramid__item[data-floor="1"]::after,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="2"] .sim-pyramid__item[data-floor="2"]::after,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="3"] .sim-pyramid__item[data-floor="3"]::after,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="4"] .sim-pyramid__item[data-floor="4"]::after { opacity: .16; }

.sim-pyramid__grid.sim-pyramid__grid[data-lit="0"] .sim-pyramid__item[data-floor="0"] .sim-pyramid__name,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="1"] .sim-pyramid__item[data-floor="1"] .sim-pyramid__name,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="2"] .sim-pyramid__item[data-floor="2"] .sim-pyramid__name,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="3"] .sim-pyramid__item[data-floor="3"] .sim-pyramid__name,
.sim-pyramid__grid.sim-pyramid__grid[data-lit="4"] .sim-pyramid__item[data-floor="4"] .sim-pyramid__name {
    text-decoration: underline;                          /* a second, colour-blind-proof signal */
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
    text-decoration-color: var(--sim-pyramid-c, currentColor);
}

/* -------------------------------------------------------------- small screens */
@media (max-width: 560px) {
    .sim-pyramid__host .sim-pyramid__hint { font-size: .88rem; }
    .sim-pyramid__grid .sim-pyramid__item { padding-left: .7rem; }
    .sim-pyramid__grid .sim-pyramid__replay { width: 100%; justify-content: center; }
}

/* [L] mode présentation : la consigne de la figure cesse d'être le plus petit texte du mur */
/* (0,3,1) : `html[data-teaching] .slide p` est (0,2,2) et gagnait, la consigne sortait
   à 31,2 px — la taille de la prose, pour une ligne de service. */
html[data-teaching="true"] .slide .sim-pyramid__host .sim-pyramid__hint { font-size: 18px; }

/* ---------- sim-signature ---------- */
/* sim-signature — vous ne signez que ce que vous expliquez.
   État final par défaut = l'état de REPOS honnête (six « ia », 0/6, « Signer » verrouillé) :
   la figure est pilotée par le lecteur, elle n'a pas d'état d'arrivée automatique.
   Seul html.js-on replie les explications ; sans JS elles sont ouvertes. */

.sim-signature__panel {
    --sim-sig-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    max-width: var(--slide-max);
    margin: 0;
    background: var(--bg-card);
    border: 1px solid var(--sim-border);
    border-radius: var(--radius);
    overflow: hidden;
    font-size: 16px;
    line-height: 1.55;
}

/* ------------------------------------------------------------------- en-tête */
.sim-signature__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: 10px 14px;
    border-bottom: 1px solid var(--sim-border);
}
.sim-signature__file {
    font-family: var(--sim-sig-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
    font-size: 15px;
    color: var(--text-secondary);
}
.sim-signature__lede {
    margin: 0;
    padding: 12px 14px 4px;
    font-size: 16px;
    color: var(--text-secondary);
}

/* --------------------------------------------------------------- pastilles */
/* Deux pastilles dans la MÊME cellule de grille : la largeur ne bouge jamais. */
.sim-signature__swap { display: inline-grid; justify-items: start; }
.sim-signature__swap > .sim-signature__pill {
    grid-area: 1 / 1;
    opacity: 0;
    transition: opacity 120ms cubic-bezier(.22, .61, .36, 1);
}
.sim-signature__swap[data-on="a"] > .sim-signature__pill:first-child { opacity: 1; }
.sim-signature__swap[data-on="b"] > .sim-signature__pill:last-child { opacity: 1; }

.sim-signature__pill {
    display: inline-flex;
    align-items: baseline;
    gap: .3em;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid var(--sim-border);
    font-family: var(--sim-sig-mono);
    font-variant-ligatures: none;
    font-size: 15px;
    line-height: 1.3;
    white-space: nowrap;
}
.sim-signature__pill--muted { color: var(--text-tertiary); background: transparent; }
/* [S] La pastille « ✓ vous » est le signal central de la figure, répété six fois :
   blanc sur --accent-primary mesure 4,47:1. #575ad4 est la même teinte à 5,50:1. */
.sim-signature__pill--own {
    color: #fff;
    background: #575ad4;
    border-color: #575ad4;
    font-weight: 600;
}
.sim-signature__tick { font-size: .95em; }

/* ------------------------------------------------------------ les six lignes */
.sim-signature__code {
    list-style: none;
    margin: 0;
    padding: 6px 0 10px;
}
.sim-signature__row {
    position: relative;
    padding: 1px 14px;
}
/* Le filet gauche de 3 px : présent en permanence, révélé par l'opacité. */
.sim-signature__row::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--accent-primary);
    opacity: 0;
    transition: opacity 120ms cubic-bezier(.22, .61, .36, 1);
}
.sim-signature__row[data-claimed="true"]::before { opacity: 1; }

.sim-signature__line {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    width: 100%;
    margin: 0;
    padding: 5px 6px 5px 4px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.sim-signature__line:hover { background: var(--bg-card-hover); }
.sim-signature__line:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: -2px;
}
.sim-signature__ln,
.sim-signature__src {
    font-family: var(--sim-sig-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
    font-size: 15px;
    line-height: 1.7;
    color: var(--text-primary);
    opacity: .6;
    transition: opacity 120ms cubic-bezier(.22, .61, .36, 1);
}
.sim-signature__row[data-claimed="true"] .sim-signature__ln,
.sim-signature__row[data-claimed="true"] .sim-signature__src { opacity: 1; }
.sim-signature__ln { color: var(--text-tertiary); }
.sim-signature__src {
    display: block;
    min-width: 0;
    white-space: pre;
    overflow-x: auto;          /* le débordement reste DANS le panneau, jamais sur le body */
    overscroll-behavior-x: contain;
}

/* ------------------------------------------------------------- explications */
.sim-signature__why {
    display: grid;
    grid-template-rows: 1fr;   /* sans JS : ouverte */
    transition: grid-template-rows 260ms cubic-bezier(.22, .61, .36, 1);
}
html.js-on .sim-signature__why[data-open="false"] { grid-template-rows: 0fr; }
.sim-signature__why-in {
    min-height: 0;
    overflow: hidden;
    opacity: 1;
    transition: opacity 260ms cubic-bezier(.22, .61, .36, 1);
}
html.js-on .sim-signature__why[data-open="false"] .sim-signature__why-in { opacity: 0; }
/* [AB] L'explication pend sous le code qu'elle explique. Mesuré à 1280 px : la note
   démarrait à x=165, c'est-à-dire AVANT la pastille (169), avant le numéro (246) et bien
   avant le code (265) — une annotation de la ligne 2 posée à gauche de la ligne 2.
   Deux étapes, parce qu'un padding seul serait juste dans une langue et faux dans
   l'autre : la gouttière mesure 67 px en français (« ✓ vous ») et 58 px en anglais
   (« ✓ you »), donc le code démarrait à 265 en FR et 256 en EN. Le min-width égalise la
   colonne de départ, le padding vaut alors 100 px dans les deux langues.
   ≤560 px la grille passe à « gut ln / src src » : le code y démarre au padding gauche
   de .sim-signature__line (4 px), d'où 4 px et pas 100 — mesuré à 390, pas estimé. */
.sim-signature__note {
    margin: 0;
    padding: 2px 6px 12px 20px;
    font-size: 15px;
    color: var(--text-secondary);
}
@media (min-width: 561px) {
    .sim-signature__gut  { min-width: 67px; }
    .sim-signature__note { padding-left: 100px; }
}

/* -------------------------------------------------------------------- pied */
.sim-signature__foot {
    padding: 12px 14px 10px;
    border-top: 1px solid var(--sim-border);
}
.sim-signature__track {
    position: relative;
    height: 4px;
    border-radius: 999px;
    background: var(--sim-border);
    overflow: hidden;
}
.sim-signature__fill {
    position: absolute;
    inset: 0;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 300ms linear, opacity 300ms linear;
}
.sim-signature__fill--base { background: var(--text-secondary); }
.sim-signature__fill--own { background: var(--accent-primary); opacity: 0; }

.sim-signature__footrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 12px 0 10px;
}
.sim-signature__status { margin: 0; font-size: 16px; color: var(--text-secondary); }
.sim-signature__num {
    font-family: var(--sim-sig-mono);
    font-variant-numeric: tabular-nums;
    font-size: 16px;
    color: var(--text-primary);
}
.sim-signature__count { color: var(--text-primary); }
.sim-signature__dash { padding: 0 .35em; }
.sim-signature__panel[data-signed="true"] .sim-signature__count { display: none; }
.sim-signature__panel[data-signed="true"] .sim-signature__dash { display: none; }
/* 6/6 non signé : la phrase serait fausse. On la retire SANS rendre sa place,
   pour que la ligne d'état ne saute pas juste avant la signature. */
.sim-signature__msgwrap[data-hide="true"] { visibility: hidden; }
.sim-signature__panel[data-signed="true"] .sim-signature__msg { color: var(--text-primary); }

.sim-signature__sign {
    display: inline-flex;
    align-items: baseline;
    gap: .4em;
    padding: 8px 18px;
    border-radius: 999px;
    border: 1px solid var(--sim-border);
    background: transparent;
    color: var(--text-tertiary);
    font: inherit;
    font-size: 15px;
    font-weight: 600;
    cursor: not-allowed;
}
.sim-signature__sign:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}
.sim-signature__tick--sign { opacity: 0; transition: opacity 120ms cubic-bezier(.22, .61, .36, 1); }
.sim-signature__sign[data-state="ready"] {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    color: #fff;
    cursor: pointer;
}
.sim-signature__sign[data-state="done"] {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    color: #fff;
    cursor: default;
}
.sim-signature__sign[data-state="done"] .sim-signature__tick--sign { opacity: 1; }

/* --------------------------------------------------------- le trait signé */
.sim-signature__sig { position: relative; height: 52px; }
.sim-signature__ink { display: block; width: 100%; height: 52px; }
.sim-signature__rule { stroke: var(--sim-border); stroke-width: 1; }
.sim-signature__stroke {
    fill: none;
    stroke: var(--accent-primary);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: var(--sim-sig-len, 461);   /* longueur mesurée du tracé */
    stroke-dashoffset: var(--sim-sig-len, 461);
    transition: stroke-dashoffset 600ms cubic-bezier(.22, .61, .36, 1);
}
.sim-signature__panel[data-signed="true"] .sim-signature__stroke { stroke-dashoffset: 0; }

/* ------------------------------------------------------ une colonne ≤ 560 px */
@media (max-width: 560px) {
    .sim-signature__panel { font-size: 15px; }
    .sim-signature__lede { font-size: 14px; padding: 10px 12px 2px; }
    .sim-signature__head { padding: 9px 12px; }
    .sim-signature__file { font-size: 12.5px; }
    .sim-signature__pill { font-size: 12.5px; padding: 1px 7px; }
    .sim-signature__row { padding: 1px 12px; }
    .sim-signature__line {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas: "gut ln" "src src";
        align-items: baseline;
        gap: 4px 8px;
    }
    .sim-signature__gut { grid-area: gut; }
    .sim-signature__ln { grid-area: ln; justify-self: start; }
    .sim-signature__src { grid-area: src; }
    .sim-signature__ln,
    .sim-signature__src { font-size: 12.5px; line-height: 1.45; }
    .sim-signature__note { font-size: 14px; padding: 4px 4px 12px 4px; }   /* [AB] mesuré à 390 : le code démarre à x=33, la note à 29+4 */
    .sim-signature__foot { padding: 10px 12px 8px; }
    .sim-signature__status { font-size: 14px; }
    .sim-signature__num { font-size: 14px; }
    .sim-signature__sign { font-size: 14px; padding: 8px 16px; }
    .sim-signature__footrow { gap: 8px 12px; padding: 10px 0 8px; }
    .sim-signature__sig,
    .sim-signature__ink { height: 44px; }
}

/* ---------- sim-roadmap-brace ---------- */
/* ---------------------------------------------------------------------------
   sim-roadmap-brace — six steps, one single project.

   A typographic grouping laid on the roadmap list that already exists: a brace
   in a gutter the list reserves for it, a label, and a colour index on the six
   numerals. NOTHING HERE MOVES: no transition, no animation, no starting
   state, no `.is-armed` rule — so reduced motion, no JavaScript and a broken
   script all give exactly the same picture, and the site's global
   `transition-duration: .01ms !important` has nothing to destroy.

   The one real risk of a fixed figure is a brace that sits half a floor off
   when the text changes language and therefore height. It is answered twice:
   the vertical span is MEASURED in JS (never a hard-coded height), and the
   horizontal room is RESERVED by the list's own padding — so the brace can
   never push the body sideways at any width, including the 1020-1100px band
   where the page has no outer gutter at all.
   --------------------------------------------------------------------------- */

/* The list becomes the containing block, and pays for the gutter itself. */
.sim-roadmap-brace-list {
    position: relative;
    padding-right: 4rem;
}

/* ---- the colour index -----------------------------------------------------
   Steps 1-3 are the foundations and step back to plain text; steps 4, 5 and 6
   take EXACTLY the three colours of the three profile cards, in that order.
   Two classes deep so this always beats `.rm-n`, whatever the file order. */
.sim-roadmap-brace-list .sim-roadmap-brace__base  { color: var(--text-secondary); }
.sim-roadmap-brace-list .sim-roadmap-brace__role1 { color: var(--accent-tertiary); }
.sim-roadmap-brace-list .sim-roadmap-brace__role2 { color: var(--accent-secondary); }
.sim-roadmap-brace-list .sim-roadmap-brace__role3 { color: var(--accent-primary); }

/* ---- the measured column --------------------------------------------------
   An <li> (only <li> is valid inside <ol>), taken out of the grid by
   position:absolute, so the existing grid and the hand-written numbering are
   untouched. `top` and `height` are written by JS after measurement. */
.sim-roadmap-brace-list .sim-roadmap-brace__col {
    position: absolute;
    right: 0;
    top: 0;
    width: 4rem;
    height: 0;
    display: flex;
    align-items: stretch;
    gap: .35rem;
    margin: 0;
    padding: 0 .4rem 0 .5rem;
    border: 0;
    border-radius: 0;
    background: none;
}
.sim-roadmap-brace-list .sim-roadmap-brace__col[hidden] { display: none; }

.sim-roadmap-brace__brace {
    flex: 0 0 22px;
    width: 22px;
    height: 100%;
    display: block;
    overflow: visible;
}
/* [Q] L'accolade était tracée avec --sim-border, c'est-à-dire la couleur réservée aux
   ARÊTES de panneau : 1,46:1 en clair et 1,53:1 en sombre, très en dessous du 3:1 qu'on
   demande à un tracé porteur de sens — et le brief ne donne qu'une chose à comprendre à
   cette figure. --text-tertiary mesure 4,74:1 en clair et 5,58:1 en sombre.
   Surtout PAS un accent : l'accolade embrasse les trois rôles (rose, violet, indigo) ;
   en prendre un attribuerait le projet à un seul. */
.sim-roadmap-brace__brace path {
    fill: none;
    stroke: var(--text-tertiary);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.sim-roadmap-brace__label {
    writing-mode: vertical-rl;
    text-orientation: mixed;
    align-self: center;
    max-height: 100%;
    overflow: hidden;
    font-size: 1rem;            /* [Q] 15,2 -> 16 px : l'étiquette du trait, pas un murmure */
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: .04em;
    white-space: nowrap;
    color: var(--text-primary);
}

/* ---- the note, under the list --------------------------------------------
   Two classes so it beats `.slide p` (clamp(1.08rem, …)). */
.sim .sim-roadmap-brace__note {
    margin: 0;
    max-width: 62ch;
    font-size: .98rem;
    line-height: 1.5;
    color: var(--text-secondary);
}

/* ---- the mobile label, above the group ------------------------------------
   Hidden everywhere else; its twin in the column is hidden below 620px, so
   exactly one of the two is ever in the accessibility tree. */
.sim-roadmap-brace-list .sim-roadmap-brace__head { display: none; }

/* ---- under 620px: no room in the gutter -----------------------------------
   The brace is dropped, the group keeps a 3px left rail, and the label moves
   above the group. The list swaps the right gutter for a small left one, so
   nothing overflows at 390px. */
@media (max-width: 619.98px) {
    .sim-roadmap-brace-list {
        padding-right: 0;
        padding-left: .8rem;
    }
    .sim-roadmap-brace-list .sim-roadmap-brace__col {
        right: auto;
        left: 0;
        width: 3px;
        padding: 0;
        background: var(--sim-border);
        border-radius: 999px;
    }
    .sim-roadmap-brace__brace,
    .sim-roadmap-brace__label { display: none; }

    .sim-roadmap-brace-list .sim-roadmap-brace__head {
        display: block;
        margin: .3rem 0 -.4rem;
        padding: 0;
        border: 0;
        border-radius: 0;
        background: none;
        font-size: .95rem;
        line-height: 1.2;
        letter-spacing: .04em;
        color: var(--text-secondary);
    }
}

/* [L] mode présentation : l'étiquette verticale de l'accolade, lisible depuis le fond de la salle */
html[data-teaching="true"] .sim-roadmap-brace__label { font-size: 17px; }

/* ---------- sim-http ---------- */
/* sim-http — trois époques, un refus : la forme ne bouge pas, seul le corps change.
   L'état écrit ici EST l'état final (l'échange 2026 entièrement dessiné, pastille 2026
   choisie). Aucune classe .is-armed : ce panneau ne s'anime que si le lecteur le demande. */

.sim-http__panel {
    --http-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --http-ui: Inter, system-ui, -apple-system, sans-serif;
    --http-fs: 13px;
    --http-lh: 1.5;
    --http-ease: cubic-bezier(.22, .61, .36, 1);
    --http-wire: 84px;
    max-width: var(--slide-max, 980px);
    margin: 0 auto;
    padding: 1.05rem 1.1rem 1rem;
    background: var(--bg-card);
    border: 1px solid var(--sim-border);
    border-radius: var(--radius, 14px);
}

/* --------------------------------------------------------------- pastilles d'époque */
.sim-http__eras {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin: 0 0 .95rem;
}

.sim-http__era {
    display: inline-flex;
    align-items: center;
    gap: .42rem;
    margin: 0;
    padding: .42rem .78rem;
    font: 600 .8rem/1 var(--http-ui);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    background: transparent;
    border: 1px solid var(--sim-border);
    border-radius: 999px;
    cursor: pointer;
}

.sim-http__era::before {
    content: '';
    width: 7px;
    height: 7px;
    border: 1px solid currentColor;
    border-radius: 50%;
    flex: none;
}

.sim-http__era[aria-checked="true"] {
    color: var(--text-primary);
    border-color: var(--accent-primary);
    box-shadow: inset 0 0 0 1px var(--accent-primary);
}

.sim-http__era[aria-checked="true"]::before {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px var(--bg-card);
}

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

/* ------------------------------------------------------------------------- la scène */
.sim-http__stage {
    display: grid;
    grid-template-columns: 1fr var(--http-wire) 1fr;
    row-gap: .55rem;
    align-items: stretch;
}

.sim-http__party {
    position: relative;
    grid-row: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    min-height: 42px;
    padding: .45rem .65rem;
    background: var(--sim-inner, var(--bg-secondary));
    border: 1px solid var(--sim-border);
    border-radius: 10px;
}

.sim-http__party--client { grid-column: 1; }
.sim-http__party--server { grid-column: 3; }

.sim-http__who {
    font: 600 .82rem/1.25 var(--http-ui);
    color: var(--text-primary);
}

/* l'anneau qui pulse une fois quand le point atteint le serveur */
.sim-http__party--server::after {
    content: '';
    position: absolute;
    inset: -3px;
    border: 2px solid var(--accent-secondary);
    border-radius: 12px;
    opacity: 0;
    transform: scale(.985);
    transition: opacity 120ms var(--http-ease), transform 120ms var(--http-ease);
    pointer-events: none;
}

.sim-http__party--server.is-hit::after {
    opacity: 1;
    transform: scale(1);
}

/* ------------------------------------------------------------------------- le fil */
.sim-http__wire {
    position: relative;
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    height: 22px;
    margin: 0 .5rem;
}

.sim-http__wire::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 2px;
    margin-top: -1px;
    background: var(--sim-border);
}

.sim-http__wire::after {
    content: '▸';
    position: absolute;
    right: -2px;
    top: 50%;
    transform: translateY(-50%);
    font: 11px/1 var(--http-ui);
    color: var(--text-tertiary);
}

.sim-http__runner {
    position: absolute;
    inset: 0;
    transform: translateX(0);
    transition: transform 620ms linear;
}

.sim-http__runner.is-nofx { transition: none; }
.sim-http__runner.is-end { transform: translateX(calc(100% - 7px)); }

.sim-http__dot {
    position: absolute;
    left: 0;
    top: 50%;
    width: 7px;
    height: 7px;
    margin-top: -3.5px;
    border-radius: 50%;
    background: var(--accent-secondary);
    opacity: 0;
}

.sim-http__wire.is-sending .sim-http__dot { opacity: 1; }

/* ---------------------------------------------------------------- les deux messages */
.sim-http__msg {
    grid-row: 2;
    padding: .5rem .55rem;
    background: var(--sim-inner, var(--bg-secondary));
    border: 1px solid var(--sim-border);
    border-radius: 10px;
    overflow-x: auto;
}

.sim-http__msg--req { grid-column: 1; }
.sim-http__msg--resp { grid-column: 3; }

/* le refus : un filet de 3 px, en plus du glyphe ✗ et du mot sur la pastille.
   box-shadow et non border : la largeur du bloc ne bouge pas d'un pixel. */
.sim-http__msg--resp[data-fault="1"] {
    box-shadow: inset 3px 0 0 var(--sim-fault);
}

.sim-http__lines {
    width: max-content;
    min-width: 100%;
    transition: opacity 180ms var(--http-ease);
}

.sim-http__lines.is-out { opacity: 0; }

.sim-http__line {
    display: grid;
    grid-template-columns: 1.35em auto;
    gap: .4em;
    align-items: baseline;
    min-height: calc(var(--http-fs) * var(--http-lh));
    transition: opacity 180ms var(--http-ease);
}

.sim-http__line.is-dim { opacity: .15; }

.sim-http__code {
    font-family: var(--http-mono);
    font-size: var(--http-fs);
    line-height: var(--http-lh);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
    color: var(--text-primary);
    white-space: pre;
}

/* le fragment que l'étiquette du bas désigne : un filet neutre, jamais une couleur seule */
.sim-http__mark {
    border-bottom: 2px solid var(--text-tertiary);
    padding-bottom: 1px;
}

/* ------------------------------------------------------------------- les repères 1-4 */
.sim-http__tie {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35em;
    height: 1.35em;
    flex: none;
    /* [L] .64rem = 10.24 px, the smallest text on the page, in --text-tertiary:
       4.30:1 on the light panel, under the threshold. These markers are what ties the
       legend to the exchange, and the lesson of this figure IS that the method, the
       headers, the status code and the body never move. 11.52 px / 8.03:1 measured. */
    font: 600 .72rem/1 var(--http-ui);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    border: 1px solid var(--sim-border);
    border-radius: 50%;
    user-select: none;
}

/* --------------------------------------------- les quatre étiquettes, sous leur place */
.sim-http__keys {
    display: grid;
    grid-template-columns: 1fr 1fr var(--http-wire) 1fr 1fr;
    margin-top: .7rem;
}

.sim-http__key {
    position: relative;
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .55rem .5rem 0 0;
    font: .76rem/1.3 var(--http-ui);
    color: var(--text-secondary);
    border-top: 1px solid var(--sim-border);
}

/* le filet qui remonte vers l'emplacement désigné */
.sim-http__key::before {
    content: '';
    position: absolute;
    top: -8px;
    left: 0;
    width: 1px;
    height: 8px;
    background: var(--sim-border);
}

.sim-http__keys-gap { display: block; }

/* ------------------------------------------------------------------------- légende */
.sim-http__caption {
    margin: .85rem 0 0;
    font: .84rem/1.5 var(--http-ui);
    color: var(--text-secondary);
}

.sim-http__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ------------------------------------------------------------------ pastille d'état */
.sim-http__status {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    flex: none;
    padding: .26rem .48rem;
    font: 600 .72rem/1 var(--http-ui);
    font-variant-numeric: tabular-nums;
    background: var(--bg-card);
    border: 1px solid currentColor;
    border-radius: 999px;
    transition: opacity 120ms var(--http-ease);
}

.sim-http__status[data-ok="1"] { color: var(--sim-ok); }
.sim-http__status[data-ok="0"] { color: var(--sim-fault); }
.sim-http__status.is-pending { opacity: 0; }
.sim-http__status-glyph { font-size: .8rem; }
.sim-http__status-num { letter-spacing: .02em; }

/* =============================================================== le tableau statique
   Le repli prefers-reduced-motion, et l'image sans JS. Ce n'est pas un pis-aller :
   les quatre époques côte à côte montrent d'un seul regard que les trois premières
   lignes gardent la même forme et que seul le corps change. */
.sim-http__still {
    margin: .95rem 0 0;
}

.sim-http__tablewrap { overflow-x: auto; }

.sim-http__table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
}

.sim-http__still-title {
    caption-side: top;
    padding: 0 0 .55rem;
    font: 600 .88rem/1.35 var(--http-ui);
    color: var(--text-primary);
    text-align: left;
}

.sim-http__erahead {
    padding: .35rem .5rem;
    font: 600 .78rem/1.2 var(--http-ui);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
    border-bottom: 1px solid var(--sim-border);
    white-space: nowrap;
}

.sim-http__corner {
    border-bottom: 1px solid var(--sim-border);
}

.sim-http__rowhead {
    padding: .5rem .7rem .5rem 0;
    font: 600 .76rem/1.3 var(--http-ui);
    color: var(--text-secondary);
    border-bottom: 1px solid var(--sim-border);
    vertical-align: top;
    white-space: nowrap;
}

.sim-http__cell {
    padding: .5rem .5rem;
    border-bottom: 1px solid var(--sim-border);
    vertical-align: top;
}

.sim-http__cellbody .sim-http__code {
    display: block;
    font-size: calc(var(--http-fs) - 1.5px);
}

/* sépare les en-têtes de la REQUÊTE de celui de la RÉPONSE, dans la même case :
   un filet court, pour qu'il ne se lise pas comme une séparation de ligne du tableau. */
.sim-http__cellrule {
    display: block;
    width: 2.2rem;
    height: 1px;
    margin: .35rem 0;
    background: var(--sim-border);
}

.sim-http__cellbody .sim-http__status--still { margin-top: .35rem; }

.sim-http__eralab { display: none; }

/* colonne choisie : gras + soulignement en plus du fond, jamais la couleur seule */
.sim-http__erahead.is-current {
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-thickness: 2px;
}

/* et deux rails verticaux qui encadrent la colonne : en thème sombre le fond seul se
   voit à peine, et de toute façon la couleur ne doit jamais porter seule l'information. */
.sim-http__erahead.is-current,
.sim-http__cell.is-current {
    background: var(--bg-tertiary);
    box-shadow: inset 1px 0 0 var(--sim-border), inset -1px 0 0 var(--sim-border);
}

.sim-http__still-note {
    margin: .6rem 0 0;
    font: .78rem/1.5 var(--http-ui);
    color: var(--text-tertiary);
}

/* ----------------------------------------------------------- l'un OU l'autre, jamais
   les deux. Seul html.js-on a le droit de masquer : sans JS, le lecteur voit tout. */
html.js-on .sim-http__still { display: none; }

/* [K] « Les quatre côte à côte » : le même échange OU le tableau, jamais les deux —
   miroir exact du repli prefers-reduced-motion ci-dessous, pour que le même contenu ne
   soit pas deux fois dans l'arbre d'accessibilité et que la section ne grandisse pas. */
html.js-on [data-sim="sim-http"].is-still .sim-http__stage,
html.js-on [data-sim="sim-http"].is-still .sim-http__keys { display: none; }
html.js-on [data-sim="sim-http"].is-still .sim-http__still { display: block; }
html.js-on [data-sim="sim-http"].is-still .sim-http__caption { margin-top: .95rem; }

/* les deux contrôles sur une rangée ; le bouton est un frère du radiogroup */
.sim-http__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .45rem .9rem;
    margin: 0 0 .95rem;
}
.sim-http__bar .sim-http__eras { margin: 0; }

/* un bouton neutre : surtout pas une cinquième pastille d'époque */
.sim-http__compare {
    align-self: center;
    margin: 0;
    padding: .32rem .8rem;
    font: 600 .8rem/1.35 var(--http-ui);
    color: var(--text-secondary);
    background: none;
    border: 1px dashed var(--sim-border);
    border-radius: 999px;
    cursor: pointer;
}
.sim-http__compare:hover { color: var(--text-primary); border-color: var(--border-hover, var(--sim-border)); }
.sim-http__compare[aria-expanded="true"] { border-style: solid; color: var(--text-primary); }
.sim-http__compare:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
    html.js-on .sim-http__stage,
    html.js-on .sim-http__keys { display: none; }
    html.js-on .sim-http__still { display: block; }
    html.js-on .sim-http__caption { margin-top: .95rem; }
    /* le tableau EST déjà la figure : le bouton n'aurait rien à basculer */
    .sim-http__compare { display: none; }
}

/* ------------------------------------------------------------------- ≤560 px : une colonne */
@media (max-width: 560px) {
    .sim-http__panel {
        --http-fs: 12.5px;
        --http-lh: 1.45;
        padding: .85rem .8rem .9rem;
    }

    .sim-http__stage { grid-template-columns: 1fr; }

    .sim-http__party--client { grid-column: 1; grid-row: 1; }
    .sim-http__msg--req { grid-column: 1; grid-row: 2; }
    .sim-http__wire { grid-column: 1; grid-row: 3; }
    .sim-http__party--server { grid-column: 1; grid-row: 4; }
    .sim-http__msg--resp { grid-column: 1; grid-row: 5; }

    .sim-http__wire {
        height: 34px;
        width: 100%;
        margin: .1rem 0;
    }

    .sim-http__wire::before {
        left: 50%;
        right: auto;
        top: 0;
        bottom: 0;
        width: 2px;
        height: auto;
        margin: 0 0 0 -1px;
    }

    .sim-http__wire::after {
        content: '▾';
        right: auto;
        left: 50%;
        top: auto;
        bottom: -3px;
        transform: translateX(-50%);
    }

    .sim-http__runner.is-end { transform: translateY(calc(100% - 7px)); }

    .sim-http__dot {
        left: 50%;
        top: 0;
        margin: 0 0 0 -3.5px;
    }

    /* empilé, un filet ne pourrait plus désigner l'emplacement au-dessus : les repères
       1-4 posés dans les blocs restent le seul rattachement, et la rangée redevient une
       simple légende. */
    .sim-http__keys {
        grid-template-columns: 1fr 1fr;
        column-gap: .6rem;
        row-gap: .1rem;
        margin-top: .6rem;
        padding-top: .5rem;
        border-top: 1px solid var(--sim-border);
    }

    .sim-http__key {
        padding: .18rem .4rem .18rem 0;
        border-top: 0;
    }

    .sim-http__key::before { display: none; }

    .sim-http__keys-gap { display: none; }

    /* le tableau devient quatre blocs empilés, avec l'étiquette d'époque répétée */
    .sim-http__table,
    .sim-http__table thead,
    .sim-http__table tbody,
    .sim-http__table tr,
    .sim-http__table th,
    .sim-http__table td { display: block; }

    .sim-http__table thead { display: none; }

    /* un <caption> dans un <table> passé en display:block se replie sur un mot par
       ligne : il faut le rendre bloc lui aussi. */
    .sim-http__still-title { display: block; }

    .sim-http__table tr {
        margin: 0 0 .55rem;
        padding: .55rem .6rem .35rem;
        border: 1px solid var(--sim-border);
        border-radius: 10px;
    }

    .sim-http__rowhead {
        padding: 0 0 .4rem;
        border: 0;
        font-size: .8rem;
        color: var(--text-primary);
    }

    .sim-http__cell {
        padding: .3rem 0 .45rem;
        border: 0;
    }

    .sim-http__cell + .sim-http__cell { border-top: 1px solid var(--sim-border); padding-top: .45rem; }

    /* empilé, la colonne choisie se marque comme en-tête de colonne au-dessus de 560 px :
       gras + soulignement, jamais un fond ni une couleur seule. */
    .sim-http__cell.is-current { background: transparent; box-shadow: none; }

    .sim-http__cell.is-current .sim-http__eralab {
        color: var(--text-primary);
        text-decoration: underline;
        text-underline-offset: 3px;
        text-decoration-thickness: 2px;
    }

    .sim-http__eralab {
        display: block;
        margin: 0 0 .15rem;
        font: 600 .7rem/1.2 var(--http-ui);
        font-variant-numeric: tabular-nums;
        color: var(--text-tertiary);
    }

    .sim-http__cellbody { display: block; overflow-x: auto; }
}

/* ------------------------------------------------------- mode présentation : 1,15× */
html[data-teaching="true"] .sim-http__panel { --http-fs: 15px; }

/* [L] Le bloc de mode présentation ne remontait que code/pre/.mono : sur un mur de 1920,
   le code passait à 18,4 px et TOUT l'appareil de lecture de la figure restait entre
   11,5 et 14 px — les noms des parties, les en-têtes du tableau, les repères ①②③④.
   C'est précisément cet appareil qui porte la démonstration « la forme ne change pas ». */
html[data-teaching="true"] .sim-http__who,
html[data-teaching="true"] .sim-http__era,
html[data-teaching="true"] .sim-http__erahead,
html[data-teaching="true"] .sim-http__rowhead,
html[data-teaching="true"] .sim-http__key,
html[data-teaching="true"] .sim-http__still-title { font-size: 16px; }
html[data-teaching="true"] .sim-http__tie      { font-size: 1rem; }
html[data-teaching="true"] .sim-http__compare  { font-size: 16px; }

/* [K+L] En salle, le tableau ouvert prend la largeur de l'écran, pas celle de la prose.
   Mesuré à 1920x1080, tableau ouvert dans la colonne de 1240 px : le contenu fait 1479 px,
   soit 239 px cachés derrière l'overflow-x — et la quatrième colonne (« 2026 · un refus »),
   celle qui porte la démonstration, était coupée en deux. On ne peut pas faire défiler un
   tableau latéralement sur un mur. Un tableau n'est pas de la prose : il a le droit de
   dépasser la mesure de lecture, et il reste de toute façon 115 px de marge de chaque côté.
   La marge négative se calcule contre la colonne (50 % de 1240 = 620) : à 1400 px de fenêtre
   elle vaut +4 px, donc la règle se dégrade toute seule vers le bas. */
@media (min-width: 1400px) {
    html[data-teaching="true"] [data-sim="sim-http"].is-still .sim-http__panel {
        max-width: none;
        margin-inline: calc(50% - 44vw);
    }
    /* et l'espacement se resserre : la section fait 1032 px pour 1010 utiles */
    html[data-teaching="true"] [data-sim="sim-http"].is-still .sim-http__bar { margin-bottom: .6rem; }
    html[data-teaching="true"] [data-sim="sim-http"].is-still .sim-http__caption { margin-top: .5rem; }
    html[data-teaching="true"] [data-sim="sim-http"].is-still .sim-http__still-note { margin-top: .35rem; }
}

/* ---------- sim-flood ---------- */
/* ── sim-flood — fourteen lines pour in, three of them are wrong ───────────────────────────────
   Final state in CSS: the fourteen lines are readable, the badge says "0 warnings", the question
   is asked, the buttons work. `html.js-on` is the only thing that folds the three explanations
   away — with no JavaScript at all the faults are already railed, glyphed and explained.
   `.is-armed` (added by ctx.arm, never under reduced motion) installs the STARTING state. */

[data-sim="sim-flood"] {
    --sf-ease: cubic-bezier(.22, .61, .36, 1);
    --sf-hover: rgba(255, 255, 255, .06);
    --sf-tint: rgba(255, 107, 94, .10);
    --sf-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

[data-theme="light"] [data-sim="sim-flood"] {
    --sf-hover: rgba(20, 24, 40, .05);
    --sf-tint: rgba(192, 57, 47, .08);
}

.sim-flood__panel {
    display: flex;
    flex-direction: column;
    max-width: var(--slide-max);
    margin: 0 auto;
    background: var(--bg-card);
    border: 1px solid var(--sim-border);
    border-radius: var(--radius);
    overflow: hidden;
}

/* ── header ─────────────────────────────────────────────────────────────────────────────────── */
.sim-flood__bar {
    position: relative;
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
    padding: .7rem .9rem .8rem;
    border-bottom: 1px solid var(--sim-border);
}

.sim-flood__file {
    font-family: var(--sf-mono);
    font-size: 15px;
    color: var(--text-secondary);
}

.sim-flood__badge {
    display: grid;
    margin-left: auto;
}

.sim-flood__face {
    grid-area: 1 / 1;
    justify-self: end;
    align-self: center;
    padding: .16rem .6rem;
    border-radius: 999px;
    font-size: 15px;
    line-height: 1.4;
    white-space: nowrap;
}

.sim-flood__face--gen {
    opacity: 0;
    background: var(--accent-secondary);
    color: #fff;
}

.sim-flood__face--clean {
    opacity: 1;
    color: var(--text-secondary);
    border: 1px solid var(--sim-border);
}

.sim-flood__prog {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    overflow: hidden;
}

.sim-flood__prog-fill {
    display: block;
    height: 100%;
    background: var(--accent-secondary);
    transform: scaleX(1);
    transform-origin: left center;
}

/* ── the code ───────────────────────────────────────────────────────────────────────────────── */
.sim-flood__code {
    list-style: none;
    margin: 0;
    padding: .55rem 0;
    overflow-x: auto;
    font-family: var(--sf-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
    font-size: 15px;
    line-height: 1.5;
}

.sim-flood__row {
    position: relative;
    min-width: max-content;
}

.sim-flood__tint {
    position: absolute;
    inset: 0;
    background: var(--sf-tint);
    opacity: 0;
    pointer-events: none;
    transition: opacity 260ms var(--sf-ease);
}

.sim-flood__rail,
.sim-flood__frail {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 3px;
    opacity: 0;
    pointer-events: none;
}

.sim-flood__rail {
    left: 0;
    /* [S] --sim-mark : le rôle « marques du LECTEUR ». --accent-tertiary est un jeton
       global de css/style.css que cette page ne rebascule jamais pour le thème clair :
       3,53:1 sur blanc, c'est-à-dire que la trace de ce que les étudiants ont fait était
       l'élément le plus faible de l'écran — et un visiteur neuf arrive en thème clair. */
    background: var(--sim-mark, var(--accent-tertiary));
    transition: opacity 120ms var(--sf-ease);
}

/* a wrong mark does not change colour by transition — a grey rail crossfades over the pink one */
.sim-flood__rail::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--text-tertiary);
    opacity: 0;
    transition: opacity 300ms var(--sf-ease);
}

.sim-flood__frail {
    left: 3px;
    background: var(--sim-fault);
    transition: opacity 260ms var(--sf-ease);
}

.sim-flood__hit {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: baseline;
    gap: 0 .5rem;
    width: 100%;
    margin: 0;
    padding: .05rem .6rem .05rem .85rem;
    background: none;
    border: 0;
    border-radius: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-user-select: text;
    user-select: text;
}

.sim-flood__hit--blank {
    cursor: default;
}

html.js-on [data-sim="sim-flood"]:not(.is-checked) .sim-flood__hit[data-line]:hover {
    background: var(--sf-hover);
}

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

[data-sim="sim-flood"].is-checked .sim-flood__hit {
    cursor: default;
}

.sim-flood__ln {
    min-width: 2ch;
    text-align: right;
    color: var(--text-tertiary);
    font-size: .87em;
}

.sim-flood__glyphs {
    display: grid;
    width: 1.1em;
    text-align: center;
}

.sim-flood__g {
    grid-area: 1 / 1;
    font-style: normal;
    opacity: 0;
    transition: opacity 120ms var(--sf-ease);
}

.sim-flood__g--mark,
.sim-flood__g--ok {
    color: var(--sim-mark, var(--accent-tertiary));
    font-weight: 700;
}

.sim-flood__g--dim {
    color: var(--text-tertiary);
    font-weight: 700;
    transition: opacity 300ms var(--sf-ease);
}

/* [J] The gutter is not empty before the first click. The only affordance this figure had
   was `:hover` (l.174), which does not exist under a finger and does not exist on a wall,
   so the gesture that IS the lesson never happened for half the readers and the greyed
   "Vérifier" read as a broken feature. A pale "?" says "this line can be pointed at"; it
   is the same glyph the row will wear in pink once marked, and it goes out the moment the
   grey "?" takes its other meaning ("marked by mistake"), i.e. on .is-checked.
   Two selectors, not one, because the figure must look the same whether the reader
   accepted motion or not: `:not(.is-armed)` is the reduced-motion / not-yet-scrolled case,
   `.is-asked` is the moment the animated version finishes asking. Never during the flood.
   `[data-line]` keeps the blank line 6 out of it: it is not a button and must not
   offer a mark. No new duration, no new curve: the 300 ms above is the figure's own. */
html.js-on [data-sim="sim-flood"]:not(.is-checked):not(.is-armed) .sim-flood__row:not(.is-marked) .sim-flood__hit[data-line] .sim-flood__g--dim,
html.js-on [data-sim="sim-flood"]:not(.is-checked).is-asked .sim-flood__row:not(.is-marked) .sim-flood__hit[data-line] .sim-flood__g--dim {
    opacity: .28;
}
html.js-on [data-sim="sim-flood"]:not(.is-checked) .sim-flood__hit[data-line]:hover .sim-flood__g--dim {
    opacity: .7;
}

.sim-flood__g--warn {
    color: var(--sim-fault);
    font-weight: 700;
}

.sim-flood__src {
    font: inherit;
    white-space: pre;
    color: var(--text-primary);
}

.sim-flood__src--comment {
    color: var(--text-secondary);
}

/* ── the explanation under a fault ──────────────────────────────────────────────────────────── */
.sim-flood__note {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 260ms var(--sf-ease);
}

.sim-flood__note-clip {
    overflow: hidden;
    min-height: 0;
}

.sim-flood__note-body {
    display: flex;
    gap: .45rem;
    max-width: 46rem;
    padding: .3rem .8rem .55rem 2.6rem;
    font-family: Inter, system-ui, sans-serif;
    font-size: 15px;
    line-height: 1.45;
    transition: opacity 260ms var(--sf-ease);
}

.sim-flood__note-glyph {
    color: var(--sim-fault);
    font-weight: 700;
    flex: none;
}

.sim-flood__note-p {
    margin: 0;
    color: var(--text-secondary);
}

/* ── foot ───────────────────────────────────────────────────────────────────────────────────── */
.sim-flood__foot {
    padding: .8rem .9rem .9rem;
    border-top: 1px solid var(--sim-border);
}

.sim-flood__foot-row {
    display: flex;
    align-items: center;
    gap: .6rem 1rem;
    flex-wrap: wrap;
}

.sim-flood__legend {
    flex: 1 1 16rem;
    margin: 0;
    font-size: 15px;
    line-height: 1.45;
    color: var(--text-secondary);
}

[data-sim="sim-flood"].is-max .sim-flood__legend {
    color: var(--text-primary);
}

.sim-flood__actions {
    display: flex;
    gap: .5rem;
    flex: none;
}

.sim-flood__btn {
    font: inherit;
    font-size: 15px;
    line-height: 1.4;
    padding: .4rem .95rem;
    border-radius: 999px;
    border: 1px solid var(--sim-border);
    cursor: pointer;
}

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

.sim-flood__btn--check {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    color: #fff;
}

/* disabled is not a dimmed version of the same pill: the fill goes away entirely,
   so "you have not marked anything yet" is legible without relying on opacity */
.sim-flood__btn--check:disabled {
    background: transparent;
    border-color: var(--sim-border);
    color: var(--text-tertiary);
    cursor: default;
}

.sim-flood__btn--reset {
    background: transparent;
    color: var(--text-secondary);
}

/* the score's slot is 0fr until there IS a score — truly zero, so nothing empty sits under
   the buttons at rest; the spacing lives inside the clip and grows with it. */
.sim-flood__verdict-slot {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 260ms var(--sf-ease);
}

[data-sim="sim-flood"].is-scored .sim-flood__verdict-slot {
    grid-template-rows: 1fr;
}

.sim-flood__verdict-clip {
    overflow: hidden;
    min-height: 0;
}

/* [V] the key to the three glyphs: same slot as the score, so it arrives with it and
   leaves with it (Recommencer closes the slot). */
.sim-flood__key {
    margin: 0;
    padding-top: .25rem;
    font-size: .9rem;
    line-height: 1.45;
    color: var(--text-secondary);
}

.sim-flood__verdict {
    margin: 0;
    padding-top: .7rem;
    font-size: 15px;
    line-height: 1.45;
    color: var(--text-primary);
}

.sim-flood__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── FINAL STATE: the three faults, railed, glyphed and explained (this is what no-JS shows) ─── */
.sim-flood__row--fault .sim-flood__frail,
.sim-flood__row--fault .sim-flood__tint,
.sim-flood__row--fault .sim-flood__g--warn {
    opacity: 1;
}

/* JavaScript is here, so the answer is hidden until the reader has committed to a guess */
html.js-on .sim-flood__row--fault:not(.is-fault) .sim-flood__frail,
html.js-on .sim-flood__row--fault:not(.is-fault) .sim-flood__tint,
html.js-on .sim-flood__row--fault:not(.is-fault) .sim-flood__g--warn {
    opacity: 0;
}

html.js-on .sim-flood__row--fault:not(.is-fault) .sim-flood__note {
    grid-template-rows: 0fr;
}

html.js-on .sim-flood__row--fault:not(.is-fault) .sim-flood__note-body {
    opacity: 0;
}

/* ── the marks the READER puts down ─────────────────────────────────────────────────────────── */
.sim-flood__row.is-marked .sim-flood__rail,
.sim-flood__row.is-marked .sim-flood__g--mark {
    opacity: 1;
}

.sim-flood__row.is-wrong .sim-flood__rail::after,
.sim-flood__row.is-wrong .sim-flood__g--dim {
    opacity: 1;
}

.sim-flood__row.is-right .sim-flood__g--ok {
    opacity: 1;
}

.sim-flood__row.is-wrong .sim-flood__g--mark,
.sim-flood__row.is-right .sim-flood__g--mark,
.sim-flood__row.is-right .sim-flood__g--warn {
    opacity: 0;
}

/* ── STARTING STATE, installed by .is-armed only ────────────────────────────────────────────── */
[data-sim="sim-flood"].is-armed .sim-flood__row {
    opacity: .06;
    transform: translateY(5px);
}

[data-sim="sim-flood"].is-armed .sim-flood__row.is-in {
    opacity: 1;
    transform: none;
    transition: opacity 180ms var(--sf-ease), transform 180ms var(--sf-ease);
}

[data-sim="sim-flood"].is-armed:not(.is-flowing) .sim-flood__prog-fill {
    transform: scaleX(0);
}

[data-sim="sim-flood"].is-armed.is-flowing .sim-flood__prog-fill {
    transition: transform 900ms linear;
}

[data-sim="sim-flood"].is-armed:not(.is-clean) .sim-flood__face--gen {
    opacity: 1;
}

[data-sim="sim-flood"].is-armed:not(.is-clean) .sim-flood__face--clean {
    opacity: 0;
}

[data-sim="sim-flood"].is-armed.is-clean .sim-flood__face {
    transition: opacity 120ms var(--sf-ease);
}

[data-sim="sim-flood"].is-armed:not(.is-asked) .sim-flood__legend {
    opacity: 0;
    transform: translateY(4px);
}

[data-sim="sim-flood"].is-armed.is-asked .sim-flood__legend {
    transition: opacity 300ms var(--sf-ease), transform 300ms var(--sf-ease);
}

[data-sim="sim-flood"].is-armed:not(.is-asked) .sim-flood__hit {
    pointer-events: none;
}

/* ── one column, smaller type, and any overflow stays inside the panel ──────────────────────── */
@media (max-width: 560px) {
    .sim-flood__code {
        font-size: 12.5px;
        line-height: 1.45;
    }

    .sim-flood__file,
    .sim-flood__face,
    .sim-flood__legend,
    .sim-flood__verdict,
    .sim-flood__btn,
    .sim-flood__note-body {
        font-size: 14px;
    }

    .sim-flood__hit {
        padding-left: .45rem;
        padding-right: .2rem;
        gap: 0 .3rem;
    }

    .sim-flood__glyphs {
        width: 1em;
    }

    .sim-flood__note-body {
        max-width: 19rem;
        padding-left: 1.9rem;
    }

    .sim-flood__actions {
        flex: 1 0 100%;
    }

    .sim-flood__btn {
        flex: 1 1 auto;
    }
}

/* presentation mode: the figure is read from the back of the room */
html[data-teaching="true"] .sim-flood__code {
    font-size: 17.25px;
}

html[data-teaching="true"] .sim-flood__note-body,
html[data-teaching="true"] .sim-flood__legend,
html[data-teaching="true"] .sim-flood__verdict {
    font-size: 17.25px;
}

/* ---------- sim-breakage ---------- */
/* sim-breakage — la même erreur, trois réactions, au même instant.
   État FINAL par défaut ; seul html.js-on a le droit de masquer (:not(.is-on)). */

.sim-breakage {
    --brk-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    display: block;
}

/* ------------------------------------------------------------ le terminal */
.sim-breakage__term {
    background: var(--bg-card);
    border: 1px solid var(--sim-border);
    border-radius: var(--radius);
    padding: .7rem .85rem .75rem;
}
.sim-breakage__bar {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}
.sim-breakage__spacer { flex: 1 1 auto; }
.sim-breakage__file {
    color: var(--text-secondary);
    font-size: .9rem;
}
.sim-breakage__mono {
    font-family: var(--brk-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
}
.sim-breakage__btn {
    font: inherit;
    font-size: .9rem;
    line-height: 1.2;
    padding: .38rem .85rem;
    border-radius: 999px;
    border: 1px solid var(--sim-border);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
}
/* [S] « Ça casse » est le SEUL bouton qui déclenche la démonstration des trois profils.
   --accent-primary en texte plein : 3,90:1 sur le panneau sombre, 4,05:1 sur le clair.
   --accent-ink est la même teinte, une valeur plus contrastée (5,66 / 5,54:1). */
.sim-breakage__btn--run {
    border-color: var(--accent-ink, var(--accent-primary));
    color: var(--accent-ink, var(--accent-primary));
    font-weight: 600;
}
.sim-breakage__btn:hover { border-color: var(--border-hover); }
.sim-breakage__btn:focus-visible,
.sim-breakage__n:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

.sim-breakage__trace {
    font-family: var(--brk-mono);
    font-variant-numeric: tabular-nums;
    font-variant-ligatures: none;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--text-secondary);
    background: var(--sim-inner, var(--bg-secondary));
    border: 1px solid var(--sim-border);
    border-radius: 8px;
    margin: .6rem 0 .55rem;
    padding: .55rem .7rem;
    overflow-x: auto;          /* le débordement reste DANS le panneau */
    white-space: pre;
}
.sim-breakage__tline { display: inline; }
.sim-breakage__status {
    padding: 0 .32em;
    border-radius: .5em;
    font-weight: 700;
}
.sim-breakage__status--bad { background: var(--sim-fault); color: var(--bg-primary); }
.sim-breakage__status--ok { background: var(--sim-ok); color: var(--bg-primary); }
.sim-breakage__hot {
    color: var(--text-primary);
    font-weight: 700;
    text-decoration: underline;
    text-decoration-color: var(--sim-fault);
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
}
.sim-breakage__lead {
    margin: 0;
    font-size: .95rem;
    color: var(--text-primary);
    display: flex;
    gap: .45rem;
    align-items: baseline;
}
.sim-breakage__mark { font-weight: 700; flex: none; }
.sim-breakage__mark--bad { color: var(--sim-fault); }
.sim-breakage__mark--ok { color: var(--sim-ok); }

/* -------------------------------------------------------- les trois cartes */
.sim-breakage__lanes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: .85rem;
    margin-top: .85rem;
}
.sim-breakage__lane {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--bg-card);
    border: 1px solid var(--sim-border);
    border-left: 3px solid var(--brk-accent);
    border-radius: var(--radius);
    padding: .65rem .7rem .7rem;
    cursor: pointer;
}
.sim-breakage__lane--1 { --brk-accent: var(--accent-tertiary); }
.sim-breakage__lane--2 { --brk-accent: var(--accent-secondary); }
.sim-breakage__lane--3 { --brk-accent: var(--accent-primary); }
.sim-breakage__lane::after {              /* la pulsation de départ : opacité seule */
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: var(--radius);
    border: 2px solid var(--brk-accent);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms cubic-bezier(.22,.61,.36,1);
}
.sim-breakage.is-pulse .sim-breakage__lane::after { opacity: 1; }

.sim-breakage__head {
    display: flex;
    align-items: flex-start;
    gap: .45rem;
    min-height: 2.6em;
}
.sim-breakage__n {                 /* la pastille de niveau EST le bouton « rejouer ce couloir » */
    flex: none;
    width: 1.7rem;
    height: 1.7rem;
    padding: 0;
    border-radius: 50%;
    border: 2px solid var(--brk-accent);
    background: transparent;
    color: var(--text-primary);
    font: inherit;
    font-weight: 700;
    font-size: .85rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}
.sim-breakage__lvl {
    font-size: .88rem;
    line-height: 1.3;
    color: var(--text-primary);
    font-weight: 600;
}
.sim-breakage__svg {
    display: block;
    width: 100%;
    height: auto;
    margin: .15rem 0 .2rem;
    overflow: visible;
}

/* trajectoires */
.sim-breakage__track {
    fill: none;
    stroke: var(--sim-border);
    stroke-width: 2;
}
.sim-breakage__prog {
    fill: none;
    stroke: var(--brk-accent);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 1000;
    transition: stroke-dashoffset 300ms linear;
}
.sim-breakage__prog--1 { transition-duration: 700ms; }
.sim-breakage__dash {
    fill: none;
    stroke: var(--text-tertiary);
    stroke-width: 2;
    stroke-dasharray: 5 5;
}
.sim-breakage__dot {
    fill: var(--brk-accent);
    stroke: var(--bg-card);
    stroke-width: 2;
    transition: transform 300ms linear;
}
.sim-breakage__orbit {
    transform-box: view-box;
    transform-origin: 150px 80px;
    transform: rotate(720deg);
    transition: transform 700ms linear;
}

/* nœuds */
.sim-breakage__node { transition: opacity 120ms cubic-bezier(.22,.61,.36,1); }
.sim-breakage__nodedot {
    fill: var(--bg-card);
    stroke: var(--brk-accent);
    stroke-width: 3;
}
.sim-breakage__lab {
    font-family: inherit;
    font-size: 16px;
    fill: var(--text-primary);
}
.sim-breakage__num {
    font-family: var(--brk-mono);
    font-variant-numeric: tabular-nums;
    font-size: 16px;
    font-weight: 700;
}
.sim-breakage__num--bad { fill: var(--sim-fault); }
.sim-breakage__num--ok { fill: var(--sim-ok); }
.sim-breakage__glyph { font-size: 14px; font-weight: 700; }
.sim-breakage__glyph--bad { fill: var(--sim-fault); }
.sim-breakage__glyph--ok { fill: var(--sim-ok); }
.sim-breakage__ring { fill: var(--bg-card); stroke-width: 3; }
.sim-breakage__ring--bad { stroke: var(--sim-fault); }
.sim-breakage__ring--ok { stroke: var(--sim-ok); }
.sim-breakage__err { transition: opacity 120ms cubic-bezier(.22,.61,.36,1); }
.sim-breakage__err-bad,
.sim-breakage__err-ok { transition: opacity 120ms cubic-bezier(.22,.61,.36,1); }
.sim-breakage__err-ok { opacity: 0; }
.sim-breakage__lane.is-fixed .sim-breakage__err-ok { opacity: 1; }
.sim-breakage__lane.is-fixed .sim-breakage__err-bad { opacity: 0; }

/* le contrat de la route : le même objet dans les deux couloirs, hors d'atteinte
   au-dessus du couloir 2, atteint et signé au bout de la descente du couloir 3 */
.sim-breakage__pillbox { fill: var(--bg-card); stroke-width: 2; }
.sim-breakage__pill--out .sim-breakage__pillbox {
    stroke: var(--text-tertiary);
    stroke-dasharray: 5 5;
}
.sim-breakage__pill--out .sim-breakage__pilltext { fill: var(--text-tertiary); }
.sim-breakage__pill--in .sim-breakage__pillbox { stroke: var(--brk-accent); stroke-width: 3; }

.sim-breakage__loopmark {
    font-size: 26px;
    font-weight: 700;
    fill: var(--brk-accent);
}
.sim-breakage__arrow {
    fill: none;
    stroke: var(--brk-accent);
    stroke-width: 3;
    stroke-dasharray: 100;
    transition: stroke-dashoffset 300ms cubic-bezier(.22,.61,.36,1);
}
.sim-breakage__arrowhead { fill: var(--brk-accent); }

/* la signature : réservée dans les trois cartes, tracée dans la troisième */
.sim-breakage__sigslot { min-height: 26px; }
.sim-breakage__sig { display: block; width: 100%; height: auto; }
.sim-breakage__sigpath {
    fill: none;
    stroke: var(--accent-primary);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 1000;
    transition: stroke-dashoffset 600ms cubic-bezier(.22,.61,.36,1);
}

.sim-breakage__after,
.sim-breakage__end {
    margin: .25rem 0 0;
    display: flex;
    gap: .4rem;
    align-items: baseline;
    font-size: .9rem;
    line-height: 1.4;
    color: var(--text-primary);
}
.sim-breakage__after { font-size: .84rem; color: var(--text-secondary); }
.sim-breakage__end { margin-top: auto; padding-top: .3rem; font-weight: 600; }

.sim-breakage__cap {
    margin: .85rem 0 0;
    font-size: .95rem;
    line-height: 1.5;
    color: var(--text-secondary);
}
.sim-breakage__cap strong { color: var(--text-primary); }
.sim-breakage__truth { color: var(--text-secondary); }

/* ------------------------------------------------- masquage : html.js-on seul */
.sim-breakage__reveal { transition: opacity 180ms cubic-bezier(.22,.61,.36,1), transform 180ms cubic-bezier(.22,.61,.36,1); }
.sim-breakage__lead,
.sim-breakage__cap { transition-duration: 300ms; }
.sim-breakage__svgreveal { transition: opacity 120ms cubic-bezier(.22,.61,.36,1); }

html.js-on .sim-breakage__reveal:not(.is-on) { opacity: 0; transform: translateY(4px); }
html.js-on .sim-breakage__svgreveal:not(.is-on) { opacity: 0; }
html.js-on .sim-breakage__node:not(.is-on) { opacity: .22; }
html.js-on .sim-breakage__err:not(.is-on) { opacity: .22; }

/* sauts d'état sans transition (armement, rembobinage, mouvement réduit) */
.sim-breakage.is-instant .sim-breakage__reveal,
.sim-breakage.is-instant .sim-breakage__svgreveal,
.sim-breakage.is-instant .sim-breakage__node,
.sim-breakage.is-instant .sim-breakage__err,
.sim-breakage.is-instant .sim-breakage__err-bad,
.sim-breakage.is-instant .sim-breakage__err-ok,
.sim-breakage.is-instant .sim-breakage__prog,
.sim-breakage.is-instant .sim-breakage__dot,
.sim-breakage.is-instant .sim-breakage__orbit,
.sim-breakage.is-instant .sim-breakage__arrow,
.sim-breakage.is-instant .sim-breakage__sigpath,
.sim-breakage.is-instant .sim-breakage__lane::after { transition: none; }

/* --------------------------------------------------------------- une colonne */
@media (max-width: 560px) {
    .sim-breakage__trace { font-size: 12.5px; line-height: 1.45; }
    .sim-breakage__lanes { grid-template-columns: 1fr; }
    .sim-breakage__lane { padding: .6rem .65rem .65rem; }
    .sim-breakage__head { min-height: 0; }
    .sim-breakage__btn { font-size: .85rem; }
}

/* ------------------------------------------------------- mode présentation */
/* [I] The 15.5 px the author wrote here NEVER applied: this selector is (0,2,1) and
   `html[data-teaching="true"] .sim pre` (ai-era.css l.98-100) is (0,2,2), so the trace
   came out at 18.4 px. Measured: below 1400 px the panel is 889 px wide and the trace
   is 931 px — the stack stops on "task_queues:105" and the ":5)" is gone, with no
   ellipsis and no visible bar. At 1400 px and above the column is 1240 px ([D]) and
   18.4 px fits with 278 px to spare, so the real trace stays BIGGER on a projector:
   the rule is raised in specificity but scoped to the widths where it is needed.
   No `white-space: pre-wrap` (a folded Node trace is no longer the real trace) and no
   extra `overflow-x` (l.66 already has it, inside the panel). */
@media (max-width: 1399px) {
    html[data-teaching="true"] .sim pre.sim-breakage__trace { font-size: 16px; }
}
html[data-teaching="true"] .sim-breakage__lvl,
html[data-teaching="true"] .sim-breakage__end,
html[data-teaching="true"] .sim-breakage__cap { font-size: 1.05rem; }
