/* See for yourself — one stylesheet for the catalogue and the lab.
   The look is a bench, not a website: quiet paper, a rule under everything,
   and every number set in mono because instruments read in mono.

   ── L'IDENTITÉ, EN QUATRE RÈGLES ──────────────────────────────────────────
   Elles sont écrites une fois, ici et dans lib/brand.js, et le reste du
   fichier ne fait que les appliquer.

   1. ENCRE ET PAPIER D'ABORD. Le fond est un papier chaud, l'encre un
      bleu-noir. Tout ce qui n'est pas une donnée se tient dans ces deux-là.
   2. UNE SEULE COULEUR VIVE À L'ÉCRAN — celle de la matière ouverte, servie
      par --sub. Elle a deux valeurs, une par thème (--sub-l / --sub-d) : une
      teinte unique pour les deux fonds est toujours terne sur l'un des deux.
      Le vermillon --live est la seule autre couleur, et il ne dit qu'une
      chose : une mesure tourne en ce moment.
   3. TROIS FAMILLES, TROIS RÔLES, JAMAIS MÉLANGÉS. La sérif AFFIRME (le nom,
      les titres, la phrase d'accueil) ; la sans EXPLIQUE (le texte courant) ;
      la mono MESURE (étiquettes, compteurs, unités, codes). Si l'on hésite
      sur la famille, c'est que l'on hésite sur le rôle du texte.
   4. UN SEUL ORNEMENT : la réglure graduée (.tickrule). Pas d'ombre portée
      décorative, pas de dégradé, pas d'icône qui ne dise rien. */

:root {
  --paper:    #f6f4ef;
  --paper-2:  #efece4;
  --paper-3:  #e7e3d8;
  --ink:      #16202b;
  --ink-soft: #4a5866;
  --ink-mute: #7d8896;
  --rule:     #ddd7c9;
  --rule-2:   #c9c2b1;
  /* la matière ouverte : deux valeurs, le thème choisit */
  --sub-l:    #10707f;
  --sub-d:    #4ec5d6;
  --sub:      var(--sub-l);
  /* le vermillon d'instrument : une mesure est en train de tourner */
  --live:     #c8452c;
  --shadow:   0 1px 2px rgba(20,26,34,.06), 0 8px 24px rgba(20,26,34,.06);
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --paper:    #0f151c;
  --paper-2:  #151d26;
  --paper-3:  #1c2631;
  --ink:      #e9eef4;
  --ink-soft: #a9b6c4;
  --ink-mute: #6f7f8f;
  --rule:     #24303c;
  --rule-2:   #34424f;
  --sub:      var(--sub-d);
  --live:     #ff6a52;
  --shadow:   0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
  color-scheme: dark;
}

/* Tout ce qui porte une matière porte ses DEUX teintes, posées en ligne par le
   catalogue ou le serveur, et c'est le thème qui tranche. Un seul endroit
   décide, donc une carte, une en-tête et un curseur ne peuvent pas diverger. */
[data-sub] { --sub: var(--sub-l); }
:root[data-theme="dark"] [data-sub] { --sub: var(--sub-d, var(--sub-l)); }

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font-family: var(--sans); font-size: 15px; line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.012em; }
a { color: inherit; }
button { font: inherit; color: inherit; }
.grow { flex: 1; }
[hidden] { display: none !important; }

/* ── the masthead ─────────────────────────────────────────────────────── */
.top {
  display: flex; align-items: center; gap: 14px;
  padding: 0 26px; height: 60px;
  border-bottom: 1px solid var(--rule); background: var(--paper);
  position: sticky; top: 0; z-index: 20;
}
/* ── le bloc-marque ─────────────────────────────────────────────────────────
   La marque, le nom en sérif, le descripteur en mono derrière un filet. Les
   trois familles de caractères sont là, dans leurs trois rôles : c'est le
   système entier en vingt millimètres, et c'est pour ça qu'on ne le recompose
   jamais à la main. Il est produit par blocMarque() dans lib/brand.js. */
.marque-bloc { display: flex; align-items: center; gap: 11px; text-decoration: none; color: var(--ink); }
.marque-bloc .marque { display: block; flex: none; }
/* Le foyer — le point où le faisceau se referme — prend la couleur de la matière
   ouverte, partout où la marque apparaît. La marque reste à l'encre : seul le
   résultat est coloré, et l'enseigne change donc de couleur selon l'étagère où
   l'on se trouve. C'est le seul endroit où la marque bouge. */
.marque circle { fill: var(--sub); }
/* Sur les pages sans matière, le foyer les passe en revue — les images sont
   fabriquées depuis la liste des matières par cycleDuFoyer(), dans app.js.
   L'enseigne et la signature seulement : sur la planche d'identité, un
   spécimen doit rester immobile pour qu'on puisse le juger. */
body.sans-sujet .top .marque circle,
body.sans-sujet .pied .marque circle { animation: foyer-jour 13s infinite; }
:root[data-theme="dark"] body.sans-sujet .top .marque circle,
:root[data-theme="dark"] body.sans-sujet .pied .marque circle { animation-name: foyer-nuit; }
@media (prefers-reduced-motion: reduce) {
  body.sans-sujet .top .marque circle,
  body.sans-sujet .pied .marque circle { animation: none; }
}
.marque-bloc b {
  font-family: var(--serif); font-size: 19px; font-weight: 500;
  letter-spacing: -.014em; line-height: 1;
}
.marque-bloc i {
  font-style: normal; font-family: var(--mono); font-size: 9.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-mute); padding-left: 11px;
  border-left: 1px solid var(--rule-2); line-height: 1;
}
/* ── la mise au point ───────────────────────────────────────────────────────
   Au survol — et au clavier, ce qui donne enfin un état de focus visible à
   l'enseigne — les rayons s'allongent vers le foyer et le point se resserre.
   La marque fait sous le doigt ce qu'elle raconte : elle met au point. C'est
   un mouvement de trois dixièmes, sur deux propriétés composées : rien à
   repeindre, et il s'efface si l'on demande moins d'animation. */
.marque .rai, .marque circle {
  transform-box: view-box;
  transition: transform .3s cubic-bezier(.2, .8, .2, 1);
}
.marque .rai { transform-origin: 8.5px 16px; }   /* la lentille, d'où partent les rayons */
.marque circle { transform-origin: 27.6px 16px; }
a.marque-bloc:hover .marque, a.marque-bloc:focus-visible .marque { color: var(--sub); }
a.marque-bloc:hover .marque .rai,
a.marque-bloc:focus-visible .marque .rai { transform: scale(1.11); }
a.marque-bloc:hover .marque circle,
a.marque-bloc:focus-visible .marque circle { transform: scale(1.26); }
/* La mise au point accompagne le focus clavier, elle ne le remplace pas : un
   changement d'échelle de onze pour cent n'est pas un indicateur de focus
   suffisant. L'anneau reste, dans la couleur de la matière ouverte. */
a.marque-bloc:focus-visible { outline: 2px solid var(--sub); outline-offset: 5px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  .marque .rai, .marque circle { transition: none; }
}

/* ── la réglure graduée ─────────────────────────────────────────────────────
   Le seul ornement de la maison : un filet et ses graduations, la tranche d'une
   règle. Il dit « ici on mesure » sans écrire un mot, il coûte trois dégradés,
   et il prend la couleur de ce qui l'entoure. */
/* Le filet s'efface par sa COULEUR, pas par l'opacité de l'élément : `opacity`
   s'applique à toute la descendance, et les graduations qui viennent s'y loger
   au survol se retrouvaient délavées avec lui. */
.tickrule {
  height: 9px; color: color-mix(in srgb, var(--ink) 34%, transparent);
  background-repeat: repeat-x, repeat-x, no-repeat;
  background-image:
    repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px 48px),
    repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px 8px),
    linear-gradient(currentColor, currentColor);
  background-size: 100% 9px, 100% 4px, 100% 1.5px;
}
.tickrule.sub { color: color-mix(in srgb, var(--sub) 62%, transparent); }

/* ── la réglure qui se laisse lire ──────────────────────────────────────────
   Au survol, des valeurs de la matière montent sous les crans. Le bloc est en
   position absolue : il ne pousse rien, la page ne bouge pas d'un pixel quand
   on approche le curseur. */
.tickrule { position: relative; transition: color .25s; }
.tickrule.ouvert { color: color-mix(in srgb, var(--ink) 66%, transparent); }
.tickrule.sub.ouvert { color: color-mix(in srgb, var(--sub) 90%, transparent); }
/* Un filet de neuf pixels de haut est une cible impossible à viser, et l'on
   perdrait les graduations en descendant vers elles. La zone sensible déborde
   donc jusque sous les étiquettes — invisible, et sans rien recouvrir. */
.tickrule::after { content: ""; position: absolute; inset: -9px 0 -42px; }
.grad { position: absolute; inset: 100% 0 auto 0; height: 46px; pointer-events: none; }
/* --p vaut 1 sous le curseur et tombe à 0 à la limite de sa portée : toute
   l'apparition — le cran qui s'allonge, l'étiquette qui monte et s'éclaircit —
   se lit dans cette seule variable, posée par app.js une fois par image. */
.grad .g {
  position: absolute; top: 0; transform: translateX(-50%); text-align: center;
  --p: 0;
}
.grad .g i {
  display: block; width: 1px; margin: 0 auto; background: currentColor;
  height: calc(5px + var(--p) * 4px);
  opacity: calc(.35 + var(--p) * .65);
  transition: height .16s, opacity .16s;
}
/* une étiquette sur deux descend d'un cran : elles peuvent alors se serrer deux
   fois plus sans jamais se toucher, et la règle en porte le double */
.grad .g.bas i { height: calc(23px + var(--p) * 4px); }
.grad .g b {
  display: block; margin-top: 4px; white-space: nowrap;
  font: 400 10px/1 var(--mono); letter-spacing: .05em; color: var(--ink-soft);
  opacity: var(--p);
  transform: translateY(calc((1 - var(--p)) * 5px)) scale(calc(.96 + var(--p) * .04));
  transition: opacity .17s linear, transform .17s cubic-bezier(.2, .8, .2, 1);
}
/* la tête de lecture : le trait que suit l'œil, dans la couleur de la matière */
.tete {
  position: absolute; top: -9px; width: 1px; height: 30px; margin-left: -.5px;
  background: linear-gradient(var(--sub), transparent);
  opacity: 0; transition: opacity .2s;
}
.tete::before {
  content: ""; position: absolute; top: 6px; left: -2.5px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--sub);
}
.tickrule.ouvert .tete { opacity: .85; }
@media (prefers-reduced-motion: reduce) {
  .grad .g i, .grad .g b, .tete { transition: none; }
}

/* La réglure qui COMPTE. Le pas est constant et il y a un cran par expérience :
   la longueur du filet est donc la mesure elle-même, et deux étagères se
   comparent d'un coup d'œil sans lire un chiffre. C'est l'ornement de la maison
   qui se met à dire quelque chose de vrai.

   Elle n'est posée QUE là où le compte tient dans la largeur (voir le garde-fou
   dans app.js) : une règle tronquée compterait faux, et une page qui promet de
   ne pas mentir ne peut pas se le permettre. */
/* Et elle se pose : en arrivant sur une étagère, la réglure s'étend de zéro à
   sa longueur, un cran après l'autre — l'étagère se mesure devant vous au lieu
   d'afficher son résultat tout fait. La graduation étant à pas fixe, les crans
   sortent un par un tout seuls. */
.tickrule.compte {
  --pas: 16px;
  width: 0;
  transition: width .6s cubic-bezier(.22, .85, .25, 1);
  background-image:
    repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px calc(var(--pas) * 5)),
    repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px var(--pas)),
    linear-gradient(currentColor, currentColor);
}
.tickrule.compte.posee { width: calc(var(--n) * var(--pas)); }
@media (prefers-reduced-motion: reduce) {
  .tickrule.compte { transition: none; }
}
/* sur une carte de matière, le même filet en plus petit — on ne compare que des
   longueurs, le pas peut donc être plus serré tant qu'il est le même partout */
.subject .tickrule.compte { --pas: 11px; height: 7px; margin: 13px 0 7px;
  color: color-mix(in srgb, var(--ink) 42%, transparent); }
.icon-btn {
  background: none; border: 1px solid var(--rule); border-radius: 8px;
  width: 34px; height: 34px; cursor: pointer; color: var(--ink-soft);
  display: grid; place-items: center; font-size: 15px; transition: .14s;
}
.icon-btn:hover { border-color: var(--sub); color: var(--sub); }

/* FR / EN — même encre et même bordure que le bouton de thème, pour que
   l'en-tête garde une seule famille de commandes. */
.langsel { display: flex; gap: 2px; }
.lang-btn {
  background: none; border: 1px solid transparent; border-radius: 6px;
  padding: 7px 8px; cursor: pointer; color: var(--ink-soft);
  font: 600 11px/1 var(--mono, ui-monospace, monospace); letter-spacing: .1em;
  transition: .14s;
}
.lang-btn:hover { color: var(--sub); }
.lang-btn[aria-pressed="true"] { color: var(--sub); border-color: var(--rule); }

/* the search field, which is also the only chrome on the home page */
.find { position: relative; }
.find input {
  font: inherit; font-size: 14px; width: 230px; padding: 7px 12px 7px 32px;
  border: 1px solid var(--rule); border-radius: 9px;
  background: var(--paper-2); color: var(--ink);
}
.find input:focus { outline: none; border-color: var(--sub); background: var(--paper); }
.find svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 15px; height: 15px; color: var(--ink-mute); pointer-events: none; }

/* ── the hero ─────────────────────────────────────────────────────────── */
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 26px 72px; }
.hero { padding: 54px 0 34px; max-width: 660px; }
.hero h1 {
  font-family: var(--serif); font-size: clamp(34px, 5vw, 50px); line-height: 1.08;
  letter-spacing: -.025em; font-weight: 500;
}
.hero p { margin: 16px 0 0; font-size: 17.5px; line-height: 1.62; color: var(--ink-soft); }
/* La largeur fixe ne vaut QUE pour la réglure muette : celle qui compte tire sa
   longueur du nombre d'expériences, et cette règle-ci l'écrasait — même
   spécificité, déclarée plus bas. Le filet comptait juste et se dessinait faux. */
.hero .tickrule { margin: 28px 0 0; }
.hero .tickrule:not(.compte) { width: 232px; }

/* ── subject shelves ──────────────────────────────────────────────────── */
.shelf-h {
  display: flex; align-items: baseline; gap: 12px; margin: 40px 0 16px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .17em;
  text-transform: uppercase; color: var(--ink-mute);
}
.shelf-h::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

.subjects { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(248px, 1fr)); }
/* En colonne, avec la phrase qui pousse : les réglures et les compteurs des
   quatre cartes tombent alors sur la MÊME ligne, quel que soit le nombre de
   lignes du titre. « Sciences de la vie et de la Terre » tient sur deux lignes
   et décalait toute la rangée. */
.subject {
  --sub: currentColor;
  display: flex; flex-direction: column; text-align: left; text-decoration: none; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 14px; padding: 20px 20px 18px;
  background: var(--paper-2); position: relative; overflow: hidden; transition: .16s;
}
.subject p { flex: 1; }
.subject::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--sub);
  opacity: .85;
}
.subject:hover, .subject:focus-visible { border-color: var(--sub); transform: translateY(-2px); box-shadow: var(--shadow); }
.subject-top { display: flex; align-items: center; gap: 11px; }
.subject svg { width: 22px; height: 22px; color: var(--sub); flex: none; }
.subject h3 { font-family: var(--serif); font-size: 19px; font-weight: 500; letter-spacing: -.012em; }
.subject p { margin: 10px 0 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }
.subject .count {
  margin-top: 14px; font-family: var(--mono); font-size: 11px; letter-spacing: .07em;
  color: var(--ink-mute);
}
.subject .count b { color: var(--sub); font-weight: 600; }

/* ── simulation cards ─────────────────────────────────────────────────── */
.sims { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(266px, 1fr)); }
.sim {
  display: flex; flex-direction: column; text-decoration: none; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 13px; padding: 16px 17px 15px;
  background: var(--paper-2); transition: .16s;
}
.sim:hover, .sim:focus-visible { border-color: var(--sub); transform: translateY(-2px); box-shadow: var(--shadow); }
/* Une carte est un lien : au clavier elle doit se voir. L'anneau est celui de
   l'enseigne — même geste, même couleur, partout où le focus se pose. */
.sim:focus-visible, .subject:focus-visible, .pied .fine a:focus-visible {
  outline: 2px solid var(--sub); outline-offset: 3px;
}
.sim-kicker {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .15em; text-transform: uppercase;
  color: var(--sub); display: flex; align-items: center; gap: 7px;
}
.sim-kicker i { width: 7px; height: 7px; border-radius: 50%; background: var(--sub); font-style: normal; }
/* Le titre d'une expérience est de la même voix que la phrase d'accueil : c'est
   une affirmation, elle se compose en sérif. */
/* Deux lignes réservées au titre, même s'il n'en tient qu'une : sans cela, les
   résumés d'une même rangée démarrent à des hauteurs différentes et la planche
   part en escalier. */
.sim h3 { margin: 9px 0 0; font-family: var(--serif); font-size: 18.5px;
  font-weight: 500; line-height: 1.26; letter-spacing: -.014em; min-height: 2.52em; }
.sim p { margin: 8px 0 0; font-size: 13.5px; line-height: 1.6; color: var(--ink-soft); flex: 1; }
/* « Ce qu'il faut voir » est le champ le plus important du manifeste — sans lui
   on a une chose qui bouge, avec lui on a une leçon. Il était composé en italique
   grise de 12,5 px, c'est-à-dire comme la chose la moins importante de la carte.
   Il devient une citation : un filet dans la couleur de la matière, du romain, et
   la couleur de l'encre. Une italique de 12,5 px dans une sans ne se lit pas. */
.sim-look {
  margin-top: 13px; padding: 1px 0 1px 12px;
  border-left: 2px solid color-mix(in srgb, var(--sub) 60%, transparent);
  font-size: 13px; line-height: 1.55; color: var(--ink);
}
.chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 13px; }
.chip {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 20px; border: 1px solid var(--rule-2); color: var(--ink-mute);
}
/* Le niveau et la durée sont une mention, pas une étiquette à décoller : un
   aplat plein en bas de chaque carte finissait par peser plus que le titre.
   Un filet suffit — et sur une planche de vingt-neuf cartes, ce sont
   vingt-neuf taches grises en moins. */
.chip.min { border-color: var(--rule); background: none; }

/* ── the empty shelf, which is a real state and not an accident ───────── */
.empty {
  border: 1px dashed var(--rule-2); border-radius: 14px; padding: 40px 30px;
  text-align: center; color: var(--ink-soft); background: var(--paper-2);
}
.empty h3 { font-size: 17px; margin-bottom: 8px; color: var(--ink); }
.empty p { margin: 0 auto; max-width: 460px; font-size: 14px; line-height: 1.62; }
.empty code {
  font-family: var(--mono); font-size: 12.5px; background: var(--paper-3);
  border: 1px solid var(--rule); border-radius: 6px; padding: 2px 7px;
}
.empty .steps { text-align: left; max-width: 460px; margin: 18px auto 0; font-size: 13.5px; }
.empty .steps li { margin: 7px 0; }

/* problems found while scanning sims/ — surfaced, never swallowed */
.problems {
  margin-top: 22px; border: 1px solid #c0714f; border-radius: 12px;
  background: color-mix(in srgb, #c0714f 9%, transparent); padding: 14px 16px;
}
.problems h4 { font-family: var(--mono); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: #c0714f; margin-bottom: 8px; }
.problems li { font-size: 13px; margin: 4px 0; }
.problems code { font-family: var(--mono); font-size: 12px; }

/* ── the lab ──────────────────────────────────────────────────────────── */
body.is-lab { display: block; }
.lab-head {
  display: flex; align-items: center; gap: 13px; padding: 0 18px; height: 56px;
  border-bottom: 1px solid var(--rule); background: var(--paper);
  position: sticky; top: 0; z-index: 10;
}
.lab-back {
  text-decoration: none; font-size: 22px; line-height: 1; color: var(--ink-soft);
  width: 32px; height: 32px; display: grid; place-items: center;
  border: 1px solid var(--rule); border-radius: 8px; transition: .14s;
}
.lab-back:hover { border-color: var(--sub); color: var(--sub); }
.lab-sub {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .15em; text-transform: uppercase;
  color: var(--sub); border: 1px solid currentColor; border-radius: 20px; padding: 3px 9px;
}
.lab-head h1 { font-family: var(--serif); font-size: 19px; font-weight: 500; letter-spacing: -.014em; }
/* Sur le banc, l'enseigne passe au second plan et perd son descripteur : on est
   bien chez « See for yourself », mais ce qui compte ici est l'expérience. Elle
   se compose donc PLUS PETIT que le titre de la simulation — à taille égale, les
   deux se disputaient l'œil et c'est la marque qui gagnait, ce qui est le
   contraire de ce qu'on veut. */
.lab-head .marque-bloc { color: var(--ink-mute); gap: 8px; }
.lab-head .marque-bloc b { font-size: 15px; }
.lab-head .marque-bloc:hover { color: var(--sub); }
@media (max-width: 860px) { .lab-head .marque-bloc b { display: none; } }
.lab-btn {
  background: none; border: 1px solid var(--rule); border-radius: 8px;
  width: 32px; height: 32px; cursor: pointer; color: var(--ink-soft);
  display: grid; place-items: center; font-size: 14px; transition: .14s;
}
.lab-btn:hover { border-color: var(--sub); color: var(--sub); }

/* a fixed working height: tall enough to read two stacked graphs, short
   enough that the note underneath is visible on a laptop */
.lab { display: grid; grid-template-columns: minmax(0, 1fr) 296px; gap: 0;
  height: min(68vh, 640px); min-height: 400px; }
.lab-stagewrap { min-width: 0; display: flex; background: var(--paper-2); overflow: hidden; }
.lab-stage { flex: 1; min-width: 0; position: relative; overflow: hidden; }
.lab-stage > svg, .lab-stage > canvas { display: block; width: 100%; height: 100%; }
.lab-side {
  border-left: 1px solid var(--rule); overflow-y: auto; overflow-x: hidden; background: var(--paper);
  display: flex; flex-direction: column;
}
.lab-panel { padding: 16px 17px; border-bottom: 1px solid var(--rule); }
.lab-panel h2 {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-mute); margin-bottom: 13px;
}

/* controls — one look for every experiment on the platform */
.lab-fields { display: flex; flex-direction: column; gap: 15px; }
.f-group {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--sub); margin-top: 4px;
}
.f { display: flex; flex-direction: column; gap: 6px; }
.f-top { display: flex; align-items: baseline; gap: 8px; }
.f-top label { font-size: 13px; color: var(--ink-soft); flex: 1; }
.f-val { font-family: var(--mono); font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.f-val em { font-style: normal; color: var(--ink-mute); margin-left: 2px; }
.f input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 18px; background: none; cursor: pointer; }
.f input[type=range]::-webkit-slider-runnable-track { height: 3px; border-radius: 2px; background: var(--rule-2); }
.f input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 15px; height: 15px; border-radius: 50%;
  background: var(--sub); border: 2px solid var(--paper); margin-top: -6px; box-shadow: 0 1px 3px rgba(0,0,0,.28);
}
.f input[type=range]::-moz-range-track { height: 3px; border-radius: 2px; background: var(--rule-2); }
.f input[type=range]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%; background: var(--sub);
  border: 2px solid var(--paper); box-shadow: 0 1px 3px rgba(0,0,0,.28);
}
.f select {
  font: inherit; font-size: 13px; padding: 7px 9px; border: 1px solid var(--rule);
  border-radius: 8px; background: var(--paper-2); color: var(--ink); width: 100%;
}
.f select:focus { outline: none; border-color: var(--sub); }
.f-check { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--ink-soft); cursor: pointer; }
.f-check input { accent-color: var(--sub); width: 15px; height: 15px; }
.f-btns { display: flex; flex-wrap: wrap; gap: 7px; }
.f-btn {
  font-family: var(--mono); font-size: 11.5px; padding: 7px 12px; cursor: pointer;
  border: 1px solid var(--rule-2); border-radius: 8px; background: var(--paper-2);
  color: var(--ink-soft); transition: .14s;
}
.f-btn:hover { border-color: var(--sub); color: var(--sub); }
.f-btn.on { background: var(--sub); border-color: var(--sub); color: #fff; }

/* the transport bar every animated experiment gets for free */
.lab-transport { display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  padding: 12px 17px; border-bottom: 1px solid var(--rule); }
.lab-transport .f-btn { min-width: 76px; text-align: center; }
.lab-time { font-family: var(--mono); font-size: 11.5px; color: var(--ink-mute); margin-left: auto;
  font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 7px; }
/* Le témoin de mesure. C'est le seul endroit de la plateforme où le vermillon
   a le droit d'apparaître, et il ne bat que pendant que l'horloge tourne : un
   voyant allumé en permanence ne dit plus rien. */
.lab-transport:has(.f-btn.on) .lab-time::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--live);
  animation: battement 1.6s ease-in-out infinite;
}
@keyframes battement { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .lab-transport:has(.f-btn.on) .lab-time::before { animation: none; }
}

/* measurements */
.lab-rows { display: flex; flex-direction: column; gap: 2px; }
.r { display: flex; align-items: baseline; gap: 10px; padding: 6px 0; border-bottom: 1px dashed var(--rule); }
.r:last-child { border-bottom: 0; }
.r-label { font-size: 12.5px; color: var(--ink-soft); flex: 1; }
.r-val { font-family: var(--mono); font-size: 14.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.r-val em { font-style: normal; font-size: 11.5px; color: var(--ink-mute); margin-left: 3px; }
.r.hi .r-val { color: var(--sub); font-weight: 600; }

/* text drawn INSIDE a simulation, styled here so every experiment labels its
   axes the same way and picks up the theme without knowing about it */
.lab-stage text { font-family: var(--mono); fill: var(--ink-mute); }
.lab-stage text.ax   { font-size: 10.5px; }
.lab-stage text.lab  { font-size: 10px; letter-spacing: .13em; text-transform: uppercase; fill: var(--ink-mute); }
.lab-stage text.pt   { font-size: 12px; font-weight: 600; fill: var(--ink); }
.lab-stage text.sub  { fill: var(--sub); }
.lab-stage text.tau  { font-size: 13px; font-weight: 600; fill: var(--sub); }

/* the teaching note under the bench */
.lab-look {
  margin: 0; padding: 13px 22px; border-top: 1px solid var(--rule);
  background: var(--paper-2); font-size: 14px; line-height: 1.6; color: var(--ink-soft);
}
.lab-look b {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--sub); margin-right: 10px;
}
.lab-notes { padding: 18px 22px 26px; border-top: 1px solid var(--rule); max-width: 720px; }
.lab-notes h3, .lab-notes h4, .lab-notes h5 { margin: 16px 0 6px; font-size: 15px; }
.lab-notes p { margin: 0 0 10px; font-size: 14px; line-height: 1.68; color: var(--ink-soft); }
.lab-notes ul, .lab-notes ol { margin: 0 0 12px; padding-left: 22px; }
.lab-notes li { font-size: 14px; line-height: 1.6; color: var(--ink-soft); margin: 0 0 5px; }
/* Une énumération numérotée porte son ordre dans le chiffre : on le garde bien
   visible, puisque c'est justement ce qui la distingue d'une liste à puces. */
.lab-notes ol > li::marker { color: var(--sub); font-family: var(--mono); font-size: 12.5px; }
.lab-notes code { font-family: var(--mono); font-size: 13px; background: var(--paper-3); padding: 1px 5px; border-radius: 5px; }
/* Un tableau large défile dans sa propre boîte : jamais la page de côté. */
.lab-notes table { width: 100%; border-collapse: collapse; margin: 12px 0 16px;
  font-size: 14px; display: block; overflow-x: auto; }
.lab-notes thead th { text-align: left; font-weight: 600; color: var(--ink);
  border-bottom: 1px solid var(--sub); padding: 6px 14px 6px 0; white-space: nowrap; }
.lab-notes tbody td { padding: 6px 14px 6px 0; color: var(--ink-soft);
  border-bottom: 1px solid var(--rule); }
.lab-notes tbody tr:last-child td { border-bottom: 0; }

.lab-err {
  margin: 18px 22px; padding: 14px 16px; border: 1px solid #c0714f; border-radius: 11px;
  background: color-mix(in srgb, #c0714f 10%, transparent); font-size: 13.5px;
}
.lab-err h4 { color: #c0714f; font-family: var(--mono); font-size: 10px; letter-spacing: .13em;
  text-transform: uppercase; margin-bottom: 7px; }
.lab-err pre { font-family: var(--mono); font-size: 12px; white-space: pre-wrap; margin: 0; color: var(--ink-soft); }

@media (max-width: 860px) {
  .lab { grid-template-columns: minmax(0, 1fr); height: auto; }
  .lab-stagewrap { min-height: 52vh; }
  .lab-side { border-left: 0; border-top: 1px solid var(--rule); }
  .find input { width: 150px; }
  .hero { padding: 34px 0 22px; }
}

/* Sur un téléphone, le bandeau doit loger l'enseigne, la recherche et le bouton
   de thème. Quelque chose doit céder, et l'ordre est écrit : d'abord le
   descripteur, ensuite la taille du nom — jamais la marque elle-même, qui est
   ce qui reste reconnaissable quand il ne reste plus rien. */
@media (max-width: 620px) {
  .top { padding: 0 16px; gap: 10px; }
  .marque-bloc i { display: none; }
  /* Le nom ne se coupe pas en deux lignes : c'est le champ de recherche qui
     rétrécit. Une enseigne pliée est une enseigne cassée. */
  .marque-bloc b { font-size: 17px; white-space: nowrap; }
  .find { flex: 1 1 84px; min-width: 0; }
  .find input { width: 100%; }
}

/* ── L'habillage selon l'âge ────────────────────────────────────────────────
   Une même simulation, trois présentations. Rien ici ne change ce que la
   simulation calcule ni son contrat avec le banc : seuls la taille des prises
   en main et la densité des chiffres bougent. Un curseur de quatre pixels et
   un tableau de mesures à quatre décimales sont justes en terminale ; en
   primaire ils sont l'obstacle. */

/* La largeur du panneau vit dans `grid-template-columns` de `.lab`, pas dans une
   variable — et la règle qui passe en une seule colonne sous 860 px se trouve
   PLUS HAUT dans ce fichier. Élargir le panneau sans condition l'emporterait
   donc sur elle, et le téléphone retrouverait deux colonnes. D'où le
   `min-width` : les prises grossissent, la mise en page étroite est
   préservée. */
@media (min-width: 861px) {
  [data-niveau="college"] .lab { grid-template-columns: minmax(0, 1fr) 318px; }
  [data-niveau="primaire"] .lab { grid-template-columns: minmax(0, 1fr) 340px; }
}

/* — collège : tout un peu plus gros, un peu plus d'air — */
[data-niveau="college"] .f label { font-size: 14.5px; }
[data-niveau="college"] .f-val { font-size: 16px; }
[data-niveau="college"] input[type="range"] { height: 26px; }
[data-niveau="college"] .f-btn { font-size: 14px; padding: 9px 14px; }
[data-niveau="college"] .r-label { font-size: 13.5px; }
[data-niveau="college"] .r-val { font-size: 17px; }
[data-niveau="college"] .f-check span { font-size: 14.5px; }

/* — primaire : de grosses prises, peu de chiffres, et des chiffres lisibles
     de loin, parce que l'écran est souvent projeté au tableau — */
[data-niveau="primaire"] .f { margin-bottom: 20px; }
[data-niveau="primaire"] .f label { font-size: 16px; font-weight: 600; }
[data-niveau="primaire"] .f-val { font-size: 20px; font-weight: 600; }
[data-niveau="primaire"] input[type="range"] { height: 34px; }
[data-niveau="primaire"] input[type="range"]::-webkit-slider-thumb {
  width: 28px; height: 28px;
}
[data-niveau="primaire"] input[type="range"]::-moz-range-thumb {
  width: 28px; height: 28px;
}
[data-niveau="primaire"] .f-btn { font-size: 16px; padding: 12px 18px; border-radius: 12px; }
[data-niveau="primaire"] .f-btns { gap: 10px; }
[data-niveau="primaire"] .f-check span { font-size: 16px; }
[data-niveau="primaire"] .f-check input { width: 22px; height: 22px; }
[data-niveau="primaire"] .r-label { font-size: 15px; }
[data-niveau="primaire"] .r-val { font-size: 22px; font-weight: 600; }
[data-niveau="primaire"] .f-group { font-size: 12px; letter-spacing: .1em; }

/* — la note repliée, pour les deux niveaux jeunes — */
.lab-notes-plie { padding: 14px 22px 18px; }
.lab-notes-plie > summary {
  cursor: pointer; font-size: 14px; font-weight: 600; color: var(--sub);
  padding: 8px 0; list-style: none;
}
.lab-notes-plie > summary::-webkit-details-marker { display: none; }
.lab-notes-plie > summary::before { content: "▸  "; }
.lab-notes-plie[open] > summary::before { content: "▾  "; }
.lab-notes-plie[open] > summary { margin-bottom: 8px; }

/* ── la vignette de chaque carte ────────────────────────────────────────────
   Une figure réduite à deux ou trois traits, dans la couleur de la matière, sur
   un fond à peine teinté. Elle sert à reconnaître une simulation d'un coup
   d'œil sur une planche de vingt-huit cartes — pas à décorer, d'où l'absence de
   remplissage, de dégradé et d'ombre. */
.sim-vig-band {
  height: 76px; margin: 0 0 13px; border-radius: 9px;
  background: color-mix(in srgb, var(--sub) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--sub) 16%, transparent);
  display: grid; place-items: center; overflow: hidden; transition: .16s;
}
.sim-vig { width: 128px; height: 64px; display: block; color: var(--sub); opacity: .92; }
.sim:hover .sim-vig-band { background: color-mix(in srgb, var(--sub) 12%, transparent); }
.sim:hover .sim-vig { opacity: 1; }

/* ── la vignette se trace ───────────────────────────────────────────────────
   Au survol, la figure se dessine au lieu d'apparaître : c'est un tracé, pas
   une image, et l'ordre du geste dit quelque chose de la figure. Chaque tracé
   porte pathLength="1" (posé par app.js), ce qui rend la même durée à tous les
   traits quelle que soit leur longueur réelle — sans quoi un grand arc mettrait
   trois fois plus de temps qu'un petit segment. */
.sim-vig *:not([fill="currentColor"]) { stroke-dasharray: 1; stroke-dashoffset: 0; }
.sim:hover .sim-vig *:not([fill="currentColor"]) { animation: tracer .62s ease forwards; }
.sim:hover .sim-vig [fill="currentColor"] { animation: paraitre .28s .34s backwards; }
@keyframes tracer { from { stroke-dashoffset: 1; } }
@keyframes paraitre { from { opacity: 0; } }

/* ── l'arrivée des cartes ───────────────────────────────────────────────────
   Une planche de vingt-neuf cartes qui apparaît d'un bloc ne se regarde pas :
   l'œil ne sait pas où se poser. Elles montent l'une après l'autre, très vite,
   et le décalage est plafonné — au-delà d'une dizaine, on attend au lieu de
   lire. `backwards` et non `forwards` : une animation qui retient sa valeur
   finale gèlerait la transformation, et la carte ne se soulèverait plus au
   survol. */
.sim, .subject { animation: monter .42s cubic-bezier(.2, .8, .2, 1) backwards; }
@keyframes monter { from { opacity: 0; transform: translateY(9px); } }

@media (prefers-reduced-motion: reduce) {
  .sim, .subject { animation: none; }
  .sim:hover .sim-vig *, .sim:hover .sim-vig [fill="currentColor"] { animation: none; }
}

/* ── le pied de page ────────────────────────────────────────────────────────
   Il ne contient pas de liens : il redit la promesse et signe. C'est la
   dernière chose qu'on lit, et c'est là que la marque a le droit d'être
   grande. */
.pied { margin-top: 64px; padding-top: 22px; border-top: 1px solid var(--rule); }
.pied-l { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.pied .promesse {
  font-family: var(--serif); font-size: 17px; font-weight: 500; color: var(--ink-soft);
  letter-spacing: -.012em;
}
.pied .tickrule { margin-top: 16px; color: color-mix(in srgb, var(--ink) 24%, transparent); }
.pied .tickrule.ouvert { color: color-mix(in srgb, var(--ink) 62%, transparent); }
/* la place des graduations est réservée en permanence : la ligne du bas ne doit
   pas sauter quand on approche le curseur de la réglure */
.pied .fine {
  margin-top: 56px; font-family: var(--mono); font-size: 10px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-mute); display: flex; gap: 14px; flex-wrap: wrap;
}
.pied .fine a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--rule-2); }
.pied .fine a:hover { color: var(--sub); border-color: currentColor; }

/* ── la planche d'identité (#/marque) ───────────────────────────────────────
   Une page qui montre le système au lieu de le décrire : la marque à ses
   tailles réelles, les quatre teintes sur les deux papiers, les trois familles
   dans leurs trois rôles. Elle existe pour qu'on n'ait plus jamais à deviner. */
.planche { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.pl {
  border: 1px solid var(--rule); border-radius: 14px; padding: 20px;
  background: var(--paper-2);
}
.pl h4 {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-mute); margin-bottom: 14px;
}
.pl p { margin: 12px 0 0; font-size: 13px; line-height: 1.6; color: var(--ink-soft); }
.pl.large { grid-column: 1 / -1; }
/* la marque à ses tailles d'emploi, alignées sur la même ligne de base */
.tailles { display: flex; align-items: flex-end; gap: 26px; flex-wrap: wrap; }
.tailles figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 9px; }
.tailles figcaption {
  font-family: var(--mono); font-size: 9px; letter-spacing: .1em; color: var(--ink-mute);
}
/* les deux papiers côte à côte, pour juger une teinte là où elle vivra */
.papiers { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.papier { border: 1px solid var(--rule); border-radius: 10px; padding: 13px; }
.papier.clair { background: #f6f4ef; }
.papier.sombre { background: #0f151c; }
.pastilles { display: flex; flex-direction: column; gap: 8px; }
.pastille { display: flex; align-items: center; gap: 9px; font-family: var(--mono); font-size: 10.5px; }
.pastille i { width: 15px; height: 15px; border-radius: 4px; flex: none; }
.papier.clair .pastille { color: #4a5866; }
.papier.sombre .pastille { color: #a9b6c4; }
/* les trois familles, chacune avec sa charge */
.voix { display: flex; flex-direction: column; gap: 16px; }
.voix > div { display: grid; grid-template-columns: 82px 1fr; gap: 14px; align-items: baseline; }
.voix dt {
  font-family: var(--mono); font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-mute);
}
.voix .ex-serif { font-family: var(--serif); font-size: 22px; font-weight: 500; letter-spacing: -.02em; }
.voix .ex-sans { font-size: 14.5px; color: var(--ink-soft); line-height: 1.55; }
.voix .ex-mono { font-family: var(--mono); font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
/* les règles, numérotées comme dans le code */
.regles { counter-reset: r; display: flex; flex-direction: column; gap: 13px; }
.regles li { list-style: none; display: grid; grid-template-columns: 26px 1fr; gap: 12px; }
.regles li::before {
  counter-increment: r; content: counter(r);
  font-family: var(--mono); font-size: 11px; color: var(--sub);
  border: 1px solid var(--rule-2); border-radius: 6px; height: 22px;
  display: grid; place-items: center;
}
.regles b { font-weight: 600; }
.regles span { font-size: 13.5px; line-height: 1.6; color: var(--ink-soft); }

/* Le compte de Fable, dans l’en-tête. Vide, il ne laisse pas de trou. */
.compte-hote:empty{display:none}
.compte-hote button{font:inherit;font-size:12px;cursor:pointer;color:var(--mut);
  background:var(--card,transparent);border:1px solid var(--bord,currentColor);
  border-radius:100px;padding:5px 11px;opacity:.85}
.compte-hote button:hover{opacity:1;color:var(--ink)}

/* ── LA PREUVE : le miroir qu'on tourne (app/miroir.js) ──────────────────
   Venue de la page d'accueil de Fable, où elle était une bande sombre. Ici
   elle suit les règles du banc : papier et encre, une réglure, pas d'ombre
   décorative. L'encre pour le miroir, --sub (la teinte du foyer) pour la
   normale et les arcs, le vermillon --live pour les rayons — une mesure tourne. */
.preuve {
  display: grid; grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
  gap: 40px; align-items: center;
  margin: 8px 0 44px; padding: 30px 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.preuve-eye {
  margin: 0; font: 600 11px/1.2 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--sub);
}
.preuve-h { margin: 10px 0 12px; font-family: var(--serif); font-size: clamp(26px, 3.4vw, 36px); line-height: 1.12; }
.preuve-p { margin: 0; color: var(--ink-soft); font-size: 15.5px; line-height: 1.65; max-width: 34rem; }
.preuve-fig {
  margin: 0; background: var(--paper-2); border: 1px solid var(--rule-2); border-radius: 10px; overflow: hidden;
}
.optique { display: block; width: 100%; height: auto; touch-action: none; cursor: grab; }
.optique:active { cursor: grabbing; }
.m-regle { stroke: var(--rule); stroke-width: 1; }
.m-rayon { stroke: var(--live); stroke-width: 2.2; fill: none; stroke-linecap: round; }
.m-sortant { opacity: .92; }
/* la bille dit le SENS de la lumière ; en CSS, pas en JavaScript : aucune
   boucle permanente sur le fil principal */
.m-bille { stroke: var(--ink); stroke-width: 3.4; fill: none; stroke-linecap: round;
  stroke-dasharray: 3 97; animation: m-flux 2.6s linear infinite; }
@keyframes m-flux { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -100; } }
.m-miroir { stroke: var(--ink); stroke-width: 5; stroke-linecap: round; }
.m-dos { stroke: var(--ink-mute); stroke-width: 1; opacity: .6; }
.m-normale { stroke: var(--sub); stroke-width: 1.2; stroke-dasharray: 4 4; fill: none; }
.m-arc { stroke: var(--sub); stroke-width: 1.2; fill: none; }
.m-lbl { font: 600 11px/1 var(--mono); fill: var(--ink-mute); letter-spacing: .06em; }
.m-impact { fill: var(--live); }
.m-poignee { fill: var(--paper); stroke: var(--live); stroke-width: 2; cursor: grab; }
.m-poignee:hover, .m-poignee:focus-visible { fill: var(--paper-3); outline: none; }
.m-pouls { fill: none; stroke: var(--live); stroke-width: 2; opacity: 0; pointer-events: none;
  transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: no-preference) {
  .preuve-fig.invite .m-pouls { animation: m-onde 1.9s ease-out 3; }
}
@media (prefers-reduced-motion: reduce) { .m-bille { animation: none; opacity: 0; } }
@keyframes m-onde { 0% { transform: scale(1); opacity: .6; } 70% { opacity: 0; } 100% { transform: scale(2.9); opacity: 0; } }
.preuve-cap {
  display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline;
  padding: 10px 16px; border-top: 1px solid var(--rule); font-size: 13px; color: var(--ink-mute);
}
.preuve-val { margin-inline-start: auto; display: flex; gap: 16px; flex-wrap: wrap; }
.preuve-cap .mesure { display: flex; align-items: baseline; gap: .35rem; }
.preuve-cap .mesure b { font: 500 13px var(--mono); color: var(--live); font-variant-numeric: tabular-nums; }
.preuve-cap .mesure i { font-style: normal; font: 600 10px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
/* l'écart est le fait établi, pas la valeur vivante : il n'est pas vermillon */
.preuve-cap .ecart b { color: var(--ink); }
@media (max-width: 760px) {
  .preuve { grid-template-columns: 1fr; gap: 22px; }
}
