/* ==========================================================================
   Window Portal Network — feuille de style du site public

   Les couleurs, polices et espacements viennent tous de tokens.css.
   Aucune valeur de marque n'est écrite en dur ici.

   RÈGLE DE KARL : les effets liés au défilement suivent le geste 1:1.
   Aucun `scroll-behavior: smooth` imposé, aucune inertie. Les transitions
   ci-dessous ne concernent que le survol et les apparitions — jamais la
   position de la page.
   ========================================================================== */

* { box-sizing: border-box; }

/* `overscroll-behavior-y: none` neutralise le rebond élastique de macOS.
   Sans lui, le geste qui pilote le logo en haut de page ferait aussi rebondir
   la page, et les deux effets se marcheraient dessus. */
/* `overflow-x: hidden` est proscrit ici : il force `overflow-y: auto`, ce qui
   fait de la racine un conteneur de défilement et neutralise TOUS les
   `position: sticky` de la page — la pile des trois onglets s'effondre.
   Le débordement horizontal est contenu autrement, bloc par bloc. */
html {
  overscroll-behavior-y: none;
  /* La couleur qui se voit HORS du document — c'est-à-dire sous le pied de
     page, dans l'instant où la barre du navigateur mobile se replie ou se
     déploie et où la fenêtre visible est momentanément plus haute que le
     contenu.

     Sans elle, c'est le fond clair du `body` qui apparaissait là : une bande
     beige surgissait au bas de l'écran le temps d'une fraction de seconde
     quand on repartait vers le haut depuis le bas de la page. Karl : « il y a
     une petite barre au bas de l'écran qui apparaît pendant une fraction de
     seconde. »

     On lui donne donc la couleur du pied, qui est ce qui borde le bas du
     document. Le fond du `body` reste inchangé : il couvre toute la hauteur
     réelle du document, si bien que cette couleur ne se voit jamais ailleurs.

     `background-color` seulement, et non `background` : le raccourci
     écraserait la texture de papier posée sur le `body` si l'un des deux
     venait à changer. */
  background-color: var(--wpn-ancre);
}

body {
  margin: 0;
  max-width: 100vw;
  background: var(--wpn-fond) url("../../images/texture-papier.jpg") repeat;
  background-size: 600px auto;
  background-blend-mode: multiply;
  color: var(--wpn-ancre);
  font-family: var(--police-texte);
  font-size: 17px;
  line-height: var(--interligne-normal);
  -webkit-font-smoothing: antialiased;
  /* Pas d'`overflow-x` ici non plus : même effet que sur `html`, il ferait du
     corps un conteneur de défilement et casserait la pile des onglets.
     Chaque bloc contient son propre débordement. */
}

.enveloppe {
  width: 100%;
  max-width: var(--largeur-contenu);
  margin: 0 auto;
  padding: 0 var(--espace-6);
}

a { color: inherit; }

/* La trame de points interactive. Elle couvrait autrefois toute la page ;
   elle est maintenant confinée au bloc de contact final, où sa réaction au
   curseur devient une invitation à jouer juste avant l'appel à écrire. */
#fond {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0; pointer-events: none;
}
#talk { position: relative; overflow: hidden; }
#talk > .enveloppe { position: relative; z-index: 1; }

/* Le contenu passe au-dessus des canvas de fond. Trois exclusions : le banc
   d'essai, qui garde son `position: fixed` pour recouvrir la page ; la pile,
   dont le `z-index` propre serait écrasé par cette règle — plus spécifique
   que `.pile` malgré son apparence ; et le champ de losanges du décor.

   Les blocs et le calculateur ne figurent plus dans les exceptions : ils
   vivent désormais À L'INTÉRIEUR de `.pile`, et non plus directement dans
   `body`, donc la règle ne les atteint pas.

   Le symptôme, quand on l'oublie, est trompeur : la hauteur et le décalage
   s'appliquent bien, seule la position reste `relative`, si bien que
   l'élément a l'air correctement réglé mais continue de défiler. */
body > *:not(.banc):not(.pile):not(.champ-losanges):not(.modale) { position: relative; z-index: 1; }


/* ==========================================================================
   Navigation
   ========================================================================== */

.nav {
  position: sticky; top: 0; z-index: 50;
  background: rgba(245, 245, 244, 0.82);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(27, 31, 42, 0.07);
}
.nav-contenu {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--espace-6);
  padding: var(--espace-4) var(--espace-6);
  max-width: var(--largeur-contenu); margin: 0 auto;
}
.nav-marque { display: flex; align-items: center; gap: var(--espace-3); text-decoration: none; }
.nav-marque svg { width: 34px; height: auto; display: block; }

/* --- L'ÉCHANGE DES DEUX LOSANGES ----------------------------------------
   Au survol, le bleu passe à droite et le jaune à gauche. Le violet reste
   au milieu : les deux formes se croisent, et la troisième couleur naît de
   leur recouvrement comme dans le logo lui-même.

   Idée de Karl, préférée à la rotation de 90° qui était là avant. Elle est
   plus juste : une rotation ne dit rien de la marque, alors que le
   croisement EST le concept — deux fenêtres qui se superposent et créent
   l'espace partagé.

   Les deux losanges ont leurs centres à x=70 et x=130, soit 60 unités
   d'écart. Chacun parcourt donc 60 dans le sens opposé pour prendre la
   place de l'autre. Les unités sont celles du viewBox, pas des pixels :
   `transform` sur un élément SVG s'exprime dans le repère local.

   LA RÈGLE DU VIOLET, à ne pas oublier si l'on retouche ceci : il reçoit le
   décalage du JAUNE (c'est le jaune qu'il redessine), et sa découpe reçoit
   celui du BLEU (c'est la silhouette du bleu qui le taille). Les deux
   doivent donc bouger en sens contraire, exactement comme leurs formes
   d'origine. Sans cela, la zone violette resterait taillée dans une
   silhouette immobile et se décrocherait des losanges. */
.nav-bleu, .nav-jaune, .nav-violet, .nav-coupe {
  transition: transform .55s cubic-bezier(.65,0,.35,1);
}

.nav-marque:hover .nav-bleu   { transform: translateX(60px); }
.nav-marque:hover .nav-jaune  { transform: translateX(-60px); }
/* `.nav-coupe` porte le déplacement du BLEU : il emmène la silhouette de
   découpe avec lui. */
.nav-marque:hover .nav-coupe  { transform: translateX(60px); }
/* `.nav-violet` porte celui du JAUNE, mais DANS le repère déjà décalé de
   +60 : il faut donc retrancher ce décalage, d'où −120 et non −60. */
.nav-marque:hover .nav-violet { transform: translateX(-120px); }
.nav-marque span {
  font-weight: var(--graisse-noire); font-size: 11px; line-height: 1.1;
  letter-spacing: 0.04em; text-transform: uppercase;
}

.nav-liens { display: flex; align-items: center; gap: var(--espace-6); }
.nav-liens a {
  position: relative;
  text-decoration: none; font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne); color: var(--wpn-neutre-500);
  padding: 4px 0;
}
/* Le soulignement se déploie depuis la gauche. */
.nav-liens a:not(.nav-bouton)::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 2px; background: var(--wpn-ancre);
  transform: scaleX(0); transform-origin: left;
  transition: transform .28s cubic-bezier(.2,.8,.2,1);
}
.nav-liens a:not(.nav-bouton):hover { color: var(--wpn-ancre); }
.nav-liens a:not(.nav-bouton):hover::after { transform: scaleX(1); }

.nav-bouton {
  background: var(--wpn-ancre); color: #fff !important;
  padding: 10px 18px; border-radius: var(--rayon-md);
  font-size: var(--texte-sm); font-weight: var(--graisse-moyenne);
  text-decoration: none; white-space: nowrap;
  transition: background .2s ease;
}
.nav-bouton:hover { background: var(--wpn-bleu-700); }
@media (max-width: 860px) { .nav-liens a:not(.nav-bouton) { display: none; } }

/* ==========================================================================
   Le prolongement du header
   --------------------------------------------------------------------------
   Ce bloc n'est pas une section à part : c'est la suite du header. Aucun
   fond propre, aucune bordure, aucune ombre — le fond de la page continue
   simplement, et son propre champ de losanges prend le relais de celui du
   header. Rien ne doit signaler qu'on change de section.
   ========================================================================== */

.suite {
  position: relative;
  /* Aucun padding : tout vide sous le bloc figé retarderait d'autant
     l'arrivée du bloc Window, qui part du bas de la section.
     Pas d'`overflow` non plus : `clip` comme `hidden` désactiverait le
     `position: sticky` de l'enfant figé. */
  padding: 0;
  background: transparent;
  /* La hauteur totale donne la COURSE du figement : c'est la différence
     entre cette hauteur et celle du bloc figé (88vh).

     Cette course doit dépasser UNE HAUTEUR D'ÉCRAN, sinon le figement lâche
     avant que le bloc Window ait fini de monter, et la fin du mouvement se
     transforme en défilement ordinaire — ce que Karl ne veut pas : « je ne
     veux pas que ça devienne un scroll, je veux vraiment que l'onglet
     embarque all the way. »

     100vh (bloc figé) + 100vh (la montée complète de Window) = 200vh.

     Le bloc Window remonte d'autant par sa marge négative (voir `.bloc-window`)
     pour partir du bas de l'ÉCRAN et non du bas de la section : sans cela il
     serait deux écrans plus bas et sa montée déborderait de la course. */
  /* `svh` — « small viewport height » — et non `vh`. Sur mobile, `100vh` vaut
     la hauteur de l'écran BARRE D'ADRESSE RÉTRACTÉE ; tant qu'elle est
     visible, tout ce qui en dépend est trop haut, et au premier geste de
     défilement la barre se replie, la valeur change et la mise en page saute.

     Karl : « sur téléphone, il y a un petit bug au tout début du bloc un
     quand on commence à scroller, il y a un petit jump des deux côtés. »

     `svh` est la hauteur barres VISIBLES : elle ne bouge pas quand la barre
     se replie, donc plus rien ne saute. La ligne `vh` qui précède sert de
     repli pour les navigateurs qui ne connaissent pas encore `svh`. */
  min-height: 200vh;
  min-height: 200svh;
}

/* Le contenu du prolongement se fige à l'écran pendant que Window monte
   par-dessus. C'est ce qui produit l'effet demandé : le carrousel ne monte
   plus, c'est l'onglet bleu qui vient le recouvrir.

   Sans cela, le carrousel défilait vers le haut et Window arrivait par le
   bas — on descendait VERS lui au lieu qu'il soit tiré par-dessus. */
.suite-fige {
  position: sticky;
  top: 0;
  /* La hauteur reste sous celle de l'écran : un élément collant aussi haut
     que la fenêtre n'a aucune marge pour se figer visiblement. */
  /* Le bloc figé occupe une hauteur d'écran. Le reste de la section — le
     surplus de `min-height` — est la course pendant laquelle il reste collé
     et le bloc Window monte par-dessus. */
  height: 100vh;
  height: 100svh;
  box-sizing: border-box;
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--espace-4) 0;
}

/* --- Le carrousel figé ---------------------------------------------------
   Il reste collé en haut pendant que le premier bloc monte par-dessus lui.
   C'est du `position: sticky` pur : le navigateur le décolle de lui-même
   quand sa section quitte le champ, et le recolle en remontant. Aucun
   JavaScript n'intervient.

   Une longue série de correctifs a existé ici — le carrousel repassait en
   `position: fixed` pendant le retour pour « se croiser » avec une pile
   elle-même en `fixed`. Tout cela a été retiré le 26 août 2026 : la pile ne
   quitte plus jamais le `sticky`, donc il n'y a plus deux repères à
   réconcilier, et le problème que ces correctifs traitaient n'existe plus. */

.suite-contenu { max-width: 760px; position: relative; z-index: 2; }

.suite-titre {
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  font-size: clamp(1.9rem, 4vw, 3rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
  margin: 0 0 var(--espace-4);
  color: var(--wpn-ancre);
}

.suite-chapo {
  font-size: clamp(1rem, 1.7vw, 1.15rem);
  color: var(--wpn-neutre-500);
  max-width: 56ch;
  margin: 0 0 var(--espace-10);
}

/* --- Le carrousel ---------------------------------------------------------
   Un seul élément à la fois, qui prend toute la place. Il reste en position
   un moment, puis cède la place au suivant. Aucun cadre, aucune boîte : le
   visuel flotte sur le fond, et seule sa propre ombre le détache.

   Les éléments sont superposés dans la même case de grille : celui qui est
   actif est visible, les autres attendent. C'est ce qui permet un fondu sans
   que la hauteur du bloc ne saute. */

.carrousel { position: relative; z-index: 2; }

.carrousel-scene {
  display: grid;
  place-items: center;
  min-height: clamp(300px, 36vw, 480px);
}

.carrousel-item {
  grid-area: 1 / 1;
  margin: 0;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--espace-6);
  opacity: 0;
  transform: translateY(14px) scale(.985);
  transition: opacity .55s ease, transform .55s cubic-bezier(.16,.84,.28,1);
  pointer-events: none;
}
.carrousel-item.est-actif {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.carrousel-item img {
  display: block;
  width: auto;
  max-width: min(100%, 880px);
  max-height: clamp(240px, 30vw, 400px);
  height: auto;
}

.carrousel-item figcaption {
  text-align: center;
  display: flex; flex-direction: column; gap: 4px;
}
.carrousel-item figcaption strong {
  font-family: var(--police-titre);
  font-size: clamp(1.1rem, 2vw, 1.4rem);
  font-weight: var(--graisse-noire);
  letter-spacing: -0.02em;
  color: var(--wpn-ancre);
}
.carrousel-item figcaption span {
  font-size: var(--texte-sm);
  color: var(--wpn-neutre-500);
}

/* --- Les commandes : flèches et points ---------------------------------- */

.carrousel-commandes {
  display: flex; align-items: center; justify-content: center;
  gap: var(--espace-5);
  margin-top: var(--espace-8);
}

.carrousel-fleche {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid rgba(27,31,42,.16);
  border-radius: 50%;
  background: transparent;
  color: var(--wpn-ancre);
  cursor: pointer;
  transition: background .2s ease, border-color .2s ease, transform .2s ease;
}
.carrousel-fleche svg { width: 20px; height: 20px; }
.carrousel-fleche:hover {
  background: var(--wpn-ancre); color: #fff;
  border-color: var(--wpn-ancre);
}
.carrousel-fleche:active { transform: scale(.94); }

.carrousel-points { display: flex; gap: 10px; }

.carrousel-point {
  width: 9px; height: 9px; padding: 0;
  border: 0; border-radius: 2px;
  background: rgba(27,31,42,.2);
  transform: rotate(45deg);
  cursor: pointer;
  transition: background .25s ease, width .25s ease;
}
.carrousel-point[aria-current="true"] { background: var(--wpn-bleu-700); }
.carrousel-point:hover { background: rgba(27,31,42,.45); }

@media (prefers-reduced-motion: reduce) {
  .carrousel-item { transition: none; }
}

/* ==========================================================================
   Héros
   ========================================================================== */

.heros {
  /* Le header défile normalement avec la page. Il a été rendu collant un
     temps, à tort : Karl veut que « tout le bloc un soit scrollable ». Ce
     sont les trois onglets qui montent par-dessus, pas le header qui reste. */
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  /* Le contenu est poussé vers le bas : cela laisse plus de course au-dessus
     de lui avant que le header ne sorte du champ, donc plus de temps pour
     voir le logo s'assembler. */
  padding: clamp(90px, 15svh, 190px) 0 clamp(34px, 5svh, 64px);
  /* Jamais `overflow: hidden` ici : cela rognerait les losanges écartés du
     logo pendant la convergence, et désactiverait `position: sticky`. */
  overflow: clip visible;
}

@media (max-width: 620px) {
  .heros-marque { font-size: clamp(2.2rem, 11vw, 3.2rem); }
  /* Sur petit écran, le lockup passe en colonne : le nom et le logo ne
     tiennent pas côte à côte sans devenir illisibles. */
  .heros-lockup { flex-direction: column; align-items: flex-start; }
  .losange-volant { height: auto; width: min(300px, 72vw); margin: 0 0 0 -8%; }
  .btn { padding: 14px 22px; font-size: var(--texte-sm); }
  .bloc, .section { border-radius: 22px 22px 0 0; }
  .bloc-mot { font-size: 5.5rem; }
}

/* --- Le logo qui se construit au défilement -------------------------------
   Le logo est collé à droite du nom : sa hauteur se cale sur celle du bloc de
   texte, pas sur une largeur arbitraire. Le viewBox porte du vide autour du
   dessin (300 × 200 pour un logo de 200 × 140), d'où les marges négatives qui
   rattrapent ce vide et rapprochent vraiment les deux. */

.losange-volant {
  display: block;
  /* Le logo doit faire la hauteur des trois lignes du nom : celui-ci fait
     3 lignes à line-height 0.78, soit 2,34 fois sa taille de police. Le
     viewBox (300 × 200) porte du vide autour du dessin, qu'il faut compenser
     — d'où le facteur 1,45. */
  height: calc(2.34em * 1.36);
  font-size: clamp(2.4rem, 7.2vw, 5.6rem);   /* la même échelle que le nom */
  width: auto;
  flex: none;
  margin: 0 -4% 0 -6%;
  overflow: visible;
  pointer-events: none;
  will-change: transform;
}

/* Les losanges écartés sortent du cadre du logo pendant la convergence :
   le SVG ne doit donc jamais rogner, et le header non plus. */
#lv-g-bleu, #lv-g-jaune, #lv-g-violet, #lv-coupe-rect {
  will-change: transform;
}

/* --- Le champ de losanges : le fond du header ---------------------------
   Des losanges jaunes et bleus de tailles variées qui dérivent en
   parallaxe. Aucun violet n'y est peint : il naît des croisements, calculé
   par comptage de couches (voir losanges.js). */

.champ-losanges {
  /* Le canvas couvre la zone d'accueil entière — le header ET son
     prolongement — en un seul morceau : sa hauteur est posée en JavaScript.
     C'est ce qui garantit un décor continu, sans répétition ni coupure.

     Il est enfant du header et en position absolue : posé en `fixed` comme
     enfant direct du corps, il se peignait au-dessus de tout le contenu, qui
     devenait invisible. */
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  z-index: 0; pointer-events: none;
}


.heros-contenu { position: relative; z-index: 2; max-width: 940px; }

/* --- Le bloc de marque ---------------------------------------------------
   Le nom et le logo forment un seul ensemble, aligné à gauche. Le logo est
   collé au nom : c'est une signature, pas un titre avec une illustration
   posée à côté. */

.heros-lockup {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 0 0 var(--espace-7);
  max-width: none;   /* le lockup déborde volontairement la colonne de texte */
}


.heros-etiquette {
  display: inline-flex; align-items: center; gap: var(--espace-3);
  font-size: var(--texte-xs); font-weight: var(--graisse-grasse);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--wpn-neutre-500);
  margin: 0 0 var(--espace-4);
}
.heros-etiquette::before {
  content: ""; width: 10px; height: 10px;
  background: var(--wpn-jaune-400); border-radius: 2px;
  transform: rotate(45deg);
}

/* --- Le nom de la marque -------------------------------------------------
   Sous le logo, collé à gauche : c'est le titre de la page et la pièce
   maîtresse du header. Une ligne par mot, comme dans le lettrage du logo. */

.heros-marque {
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  /* Le nom est la pièce maîtresse du header : il prend toute la place que
     la ligne lui laisse une fois le logo posé à sa droite. */
  font-size: clamp(2.4rem, 7.2vw, 5.6rem);
  line-height: 0.78;
  letter-spacing: -0.055em;
  margin: 0;
  color: var(--wpn-ancre);
  flex: 0 1 auto;
  will-change: transform;
}
.heros-marque span { display: block; }

/* Les trois promesses « Your window / Your portal / Your network » ont été
   retirées du header le 25 août 2026 : Karl les réserve pour plus loin dans la
   page, et leur départ raccourcit un bloc qu'il jugeait trop long. Les règles
   ci-dessous restent parce qu'elles servent aux blocs de la page. */

@keyframes monter {
  from { opacity: 0; transform: translateY(105%); }
  to   { opacity: 1; transform: none; }
}

.mot { color: inherit; transition: color .5s ease; }
:root.ouvert .mot-window  { transition-delay: .55s; color: var(--wpn-bleu-700); }
:root.ouvert .mot-portal  { transition-delay: .70s; color: var(--wpn-violet-800); }
:root.ouvert .mot-network { transition-delay: .85s; color: var(--wpn-jaune-700); }

.heros-chapo {
  font-size: clamp(1rem, 1.7vw, 1.15rem);
  color: var(--wpn-neutre-500);
  /* Le résumé s'arrête avant la zone du logo, à droite. */
  max-width: min(58%, 50ch);
  margin: 0 0 var(--espace-6);
}
.heros-chapo strong { color: var(--wpn-ancre); font-weight: var(--graisse-moyenne); }

:root:not(.ouvert) .heros-etiquette,
:root:not(.ouvert) .heros-chapo,
:root:not(.ouvert) .heros-actions { opacity: 0; }
:root.ouvert .heros-etiquette { animation: paraitre .7s ease .05s both; }
:root.ouvert .heros-chapo     { animation: paraitre .7s ease .62s both; }
:root.ouvert .heros-actions   { animation: paraitre .7s ease .78s both; }

@keyframes paraitre {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* --- Le signal de défilement --- */

.heros-signal {
  position: absolute; left: 50%; bottom: 30px;
  transform: translateX(-50%);
  width: 1px; height: 54px;
  background: rgba(27,31,42,.14);
  overflow: hidden;
}
.heros-signal span {
  display: block; width: 100%; height: 40%;
  background: var(--wpn-ancre);
  animation: descendre 2.1s cubic-bezier(.6,0,.4,1) infinite;
}
@keyframes descendre {
  0%   { transform: translateY(-100%); }
  100% { transform: translateY(250%); }
}
:root:not(.sequence-finie) .heros-signal { opacity: 0; }
.heros-signal { transition: opacity .6s ease; }

/* ==========================================================================
   Les trois blocs
   ========================================================================== */

/* --- Les trois onglets qu'on tire ----------------------------------------
   Les blocs Window, Portal et Network ne défilent pas : ils se collent en
   haut de l'écran, et le suivant monte par-dessus. On sent qu'on tire un
   document après l'autre hors d'une pile.

   Chaque bloc refermé laisse dépasser une bande en haut — son onglet, qui
   reste cliquable pour y revenir. Les onglets se décalent donc les uns par
   rapport aux autres, comme des intercalaires. `--onglet` gouverne cette
   hauteur, et `--rang` le numéro du bloc dans la pile : c'est le produit des
   deux qui donne le décalage.

   Le mécanisme est du `position: sticky` pur, sans JavaScript : le
   défilement reste 1:1 avec le geste, conformément à la règle du projet. */

:root {
  --onglet: 62px;
  /* Le rayon des coins hauts de chaque feuille de la pile. Une seule source :
     le `border-radius` des blocs, la découpe du calage de couleur et le
     masque des échancrures s'y réfèrent tous, donc ils ne peuvent pas
     diverger. */
  --rayon-feuille: 34px;
}

.bloc {
  position: sticky;
  /* Chaque bloc se colle en haut de l'écran, décalé des onglets de ceux qui
     le précèdent. Le premier arrive quand le carrousel a fini de défiler ;
     les suivants se tirent ensuite chacun leur tour. */
  /* ⚠️ LA PILE REMONTE À MESURE QU'ELLE SE CONSTITUE.

     Karl, 28 août 2026 : « à chaque fois qu'un onglet de couleur se colle au
     précédent, ensemble ils pourraient se pousser et glisser un peu plus vers
     le haut, d'une façon fluide, parce que lorsque les quatre onglets se
     connectent ensemble, ils mangent beaucoup d'espace d'écran. Il faudrait un
     mouvement de glissement vers l'extérieur du haut de la fenêtre, subtil, au
     fur et à mesure que les onglets se collent l'un à l'autre. »

     Sa remarque est juste : à quatre feuilles, la pile occupait 186 px du haut
     de l'écran en permanence, et le calculateur d'autant moins de place.

     `--remontee` est posé par `pile.js` et vaut le nombre de bandes déjà
     empilées. Chaque nouvelle arrivée tire l'ensemble d'un tiers d'onglet vers
     le haut : le premier bloc reste où il est, et quand les quatre sont
     réunis, la pile a gagné une bande entière — soit 62 px rendus au
     calculateur. Le mouvement est doux (`transition` ci-dessous) et se lit
     comme un tassement, pas comme un saut. */
  top: calc(var(--rang) * var(--onglet)
            - var(--remontee, 0) * var(--onglet) / 3);
  transition: top .5s cubic-bezier(.33, 0, .2, 1);
  padding: clamp(58px, 7vw, 88px) 0 clamp(74px, 9vw, 110px);
  color: #fff;
  /* Le bloc doit être OPAQUE : il recouvre le canvas fixe du décor, qui
     couvre toute la fenêtre. Sans fond propre, on voyait le décor à travers
     et le contenu du bloc semblait avoir disparu. */
  isolation: isolate;
  /* La marge laisse l'échancrure (`::before`) atteindre les coins arrondis —
     voir sa note. */
  overflow-clip-margin: var(--rayon-feuille);
  /* Jamais `overflow: hidden` : cela désactiverait `position: sticky` et
     toute la pile s'effondrerait. Même piège qu'au header. */
  overflow: clip visible;
  /* ⚠️ CETTE VALEUR DOIT SUIVRE `--rayon-feuille`, ET ELLE ÉTAIT EN DUR.

     Karl, 30 août 2026, sur téléphone : « il n'y a pas de continuité de
     l'onglet. Les coins sont gris, ils ne sont pas de la couleur de l'onglet
     au-dessus. » C'est le même défaut qu'il avait signalé le 28 août sur grand
     écran, revenu par une autre porte.

     La note de `--rayon-feuille` promet que « le border-radius des blocs, la
     découpe du calage de couleur et le masque des échancrures s'y réfèrent
     tous, donc ils ne peuvent pas diverger ». Celui-ci ne s'y référait pas : il
     valait 34px en dur, tandis que `overflow-clip-margin` suivait bien la
     variable — qui tombe à 22px sous 620px de large.

     Résultat mesuré sur téléphone : rayon 34px, marge de découpe 22px. La
     couche `::before` qui peint la couleur du bloc du dessous était découpée
     12px trop tôt, et ces 12px de coin restaient au fond de page. Sur grand
     écran les deux valaient 34, d'où un défaut invisible là où je le
     cherchais. */
  border-radius: var(--rayon-feuille) var(--rayon-feuille) 0 0;
  /* L'ombre vers le haut donne l'épaisseur de la feuille.

     Elle est volontairement COURTE et peu opaque. Dans sa version précédente
     (`0 -22px 50px` à 22 %), elle s'étalait si loin vers le haut qu'elle
     retombait sur l'onglet du bloc précédent et y noyait le texte : mesuré,
     le libellé « YOUR WINDOW » n'était lisible que sur 2 lignes de pixels sur
     11, ce qui le faisait paraître coupé en deux sur les captures de Karl.
     Le contraste du texte est passé de 128 à 189 avec ce réglage. */
  /* ⚠️ L'ÉPAISSEUR TIENT À DEUX TRAITS, PAS À L'OMBRE SEULE.

     Karl, 28 août 2026 : « j'aimerais que les onglets de couleur aient la même
     profondeur dans leur design, dans leurs ombrages et leur profondeur. »

     Sa lecture est juste, et la mesure l'a confirmée : l'ombre portée vers le
     haut ne se voyait QUE sur le jaune. Mesuré à 1512×850, l'assombrissement au
     bas de chaque bande valait 10,1 % sur le jaune et **0,0 % sur le bleu comme
     sur le violet**. C'est arithmétique : une ombre sombre posée sur une
     surface sombre ne produit aucun contraste.

     Une feuille de papier ne montre pas son épaisseur par sa seule ombre : elle
     la montre AUSSI par la tranche qui capte la lumière. On ajoute donc une
     arête claire en haut de chaque feuille. Elle se lit sur n'importe quelle
     couleur — d'autant mieux que le fond est sombre, là où l'ombre échoue — et
     les deux effets se relaient au lieu de se concurrencer.

     ⚠️ L'ombre reste COURTE (10 px, diffusion 26). Dans une version
     précédente (`0 -22px 50px` à 22 %), elle s'étalait si loin qu'elle
     retombait sur l'onglet du bloc précédent et y noyait le texte : le libellé
     « YOUR WINDOW » n'était lisible que sur 2 lignes de pixels sur 11. Toute
     augmentation de sa portée doit être revérifiée sur le contraste des
     libellés. */
  /* ⚠️ DEUX TRANCHES, PAS UNE — chacune porte une famille de couleurs.

     L'arête claire du haut se lit d'autant mieux que le fond est sombre :
     mesuré, +25 de luminance sur le bleu et +29 sur le violet, mais seulement
     +4 sur le jaune, où du blanc sur du clair n'ajoute presque rien.

     La tranche sombre du bas fait l'inverse : invisible sur le sombre,
     franche sur le clair. Les deux ensemble donnent à chaque feuille la même
     épaisseur perçue, quelle que soit sa couleur — ce que Karl demande.

     ⚠️ L'ombre portée vers le haut (`0 -10px 26px`) ne se voit PAS quand la
     pile est pliée : le `clip-path` qui réduit chaque bloc à sa bande découpe
     aussi ce qu'il peint au-dessus de lui. Elle ne sert donc que dans l'état
     déployé, et l'épaisseur des bandes repose entièrement sur les deux
     tranches. */
  box-shadow:
    inset 0 1px 0 var(--arete-haut, rgba(255,255,255,.17)),
    inset 0 -1px 0 var(--tranche-bas, rgba(0,0,0,.30)),
    0 -10px 26px rgba(12,14,20,.28);
  /* Le dernier arrivé passe devant : c'est ce qui fait la pile. Les valeurs
     partent de 10 pour rester au-dessus du z-index 1 des sections qui
     suivent la pile — sans quoi elles recouvriraient les onglets et le
     premier deviendrait incliquable. */
  z-index: calc(var(--rang) + 10);

  /* **La hauteur est calculée pour que les quatre feuilles se décollent
     EXACTEMENT AU MÊME INSTANT.**

     Un élément collant se décolle quand le bas de son rectangle atteint le bas
     de son conteneur. Ce bas vaut `top + hauteur` : avec une hauteur identique
     pour tous, les blocs se décollaient donc à 62 px d'intervalle, et pendant
     ces quelques dizaines de pixels ils se séparaient — le violet
     réapparaissait sous le calculateur, ce que Karl a vu.

     En donnant à chacun `100vh − son décalage`, tous les rectangles se
     terminent sur le bas de l'écran : `top + hauteur` vaut `100vh` pour les
     quatre, ils partent donc du même pixel de défilement, solidaires.

     Le calculateur suit déjà cette règle : posé à `3 × --onglet`, il mesure
     `100vh − 3 × --onglet`. */
  height: calc(100vh - var(--rang) * var(--onglet));
  height: calc(100svh - var(--rang) * var(--onglet));
  /* **Le RYTHME de la séquence doit être régulier.** Karl : « le deuxième nous
     donne le même espace quasiment, mais l'onglet suivant, jaune, il arrive un
     peu trop vite. J'aimerais qu'on donne un peu d'air dans le rythme. Et même
     chose pour la fin de l'onglet jaune. »

     La hauteur ci-dessus décroît d'une bande à chaque rang — c'est ce qui fait
     que les quatre feuilles se décollent ensemble. Mais c'est aussi elle qui
     fixe la DISTANCE DE DÉFILEMENT entre deux arrivées : mesuré, 750 px du
     bleu au violet, 700 du violet au jaune, 650 du jaune au calculateur. La
     séquence accélérait donc, et le dernier tiers paraissait précipité.

     La marge inférieure rend à chaque bloc ce que son rang lui retire : les
     intervalles redeviennent égaux, sans toucher à la hauteur de la boîte —
     donc sans rien changer au moment où les feuilles se décollent. */
  margin-bottom: calc(var(--rang) * var(--onglet));
}

/* Le premier bloc remonte d'une hauteur d'écran : il part ainsi du bas de la
   fenêtre au moment où le carrousel se fige, et sa montée tient entièrement
   dans la course du figement. Sans cela, la fin du mouvement se transformait
   en défilement ordinaire. */
.bloc-window  { --rang: 0; margin-top: -100vh; margin-top: -100svh; }

/* Le conteneur borne le `sticky` : au bas de la pile, les blocs se décollent
   et laissent passer la section suivante.

   Le `z-index` est indispensable et le piège est retors : `position:
   relative` sans `z-index` laisse le conteneur au niveau 0 du document, si
   bien que les sections QUI SUIVENT — pourtant en `z-index: 1` seulement —
   se peignent par-dessus la pile, parce qu'elles viennent après dans l'ordre
   du document. Le symptôme observé était la disparition du seul onglet
   jaune, recouvert par la section sombre qui le suit, alors que sa position
   mesurée était juste. */
.pile {
  position: relative;
  z-index: 30;
  /* La COURSE DE LECTURE du calculateur. Une fois qu'il est venu se loger
     sous le troisième onglet, il y reste tant que le conteneur le permet ;
     c'est ce padding qui mesure ce temps d'arrêt.

     Passé cette réserve, le conteneur se termine et les quatre feuilles se
     décollent — le `position: sticky` s'en charge tout seul, dans les deux
     sens et sans le moindre JavaScript. */
}

/* La course de lecture du calculateur : ce conteneur est plus haut que la
   boîte du calculateur, et la différence est le temps d'arrêt sous le
   troisième onglet.

   Un `padding-bottom` sur la pile ne conviendrait pas : le `position: sticky`
   est borné par la boîte de CONTENU de son conteneur, jamais par son padding.
   Le padding resterait donc en vide visible une fois le calculateur décollé —
   c'était le « gros espace beige » signalé par Karl. */
.calculateur-course {
  position: relative;
  /* **AUCUNE RÉSERVE : la course vaut exactement la boîte du calculateur.**

     Elle a d'abord valu `80svh` de plus, puis 200 px. Les deux étaient des
     erreurs de même nature, et Karl a signalé les deux symptômes :

     — le surplus est du défilement pendant lequel RIEN NE BOUGE, puisque le
       calculateur est à sa butée et que les blocs y sont déjà ;
     — pire, ce surplus fait que le calculateur se décolle AVANT les trois
       blocs. Il monte alors seul par-dessus les onglets restés figés, ce que
       Karl décrit ainsi : « le calculateur embarque par-dessus les onglets.
       Ce serait mieux que tout reste collé ensemble, on fait juste descendre
       en bas, c'est tout qui bouge ensemble et disparaît dans le haut. »

     La course vaut donc la hauteur de la boîte, PLUS une bande.

     Cette bande n'est pas une réserve de lecture — c'est ce qu'il faut au
     calculateur pour ATTEINDRE sa butée. Sans elle, la course se termine au
     pixel exact où il y arriverait : il se décolle donc avant, `pile.js` ne
     voit jamais la butée, le pliage ne se déclenche pas au bon moment, et la
     pile disparaissait alors d'un seul bond de 1 700 px. Karl : « d'un coup ça
     a coupé. » Une bande suffit, et elle ne produit aucun temps mort
     mesurable : le calculateur y est encore en mouvement. */
  /* **La course doit permettre au calculateur d'ATTEINDRE sa butée.**

     Karl, 27 août 2026 : « la fenêtre du calculateur est carrément embarquée
     par-dessus l'onglet jaune dès le départ. »

     Mesuré : le calculateur se bloquait à 259 px, sans jamais atteindre sa
     butée de 186 px, parce que la page se terminait avant. Le pliage — qui
     attend cette butée — ne se déclenchait donc pas, et le calculateur restait
     figé en travers du bloc jaune, dont il ne laissait voir qu'une bande.

     Il manquait exactement 73 px de course. La rallonge vaut donc 124 px.

     **Elle est écrite en DUR, et non en `var(--onglet)`, volontairement.**
     Sur écran court, une requête média fait passer `--onglet` de 62 à 52 px,
     puis à 46 px sous 700 px de haut. Une rallonge exprimée en bandes
     rétrécirait donc au moment précis où le calculateur — proportionnellement
     plus haut dans un écran plus court — en a le plus besoin. Mesuré à
     1280 × 720 : le cadre du calculateur débordait de 31 px sur la bande
     jaune, alors que tous les écrans hauts étaient corrects. La rallonge doit
     rester constante pour que la butée soit atteignable à toutes les
     hauteurs. */
  height: calc(100vh - 3 * var(--onglet) + 124px);
  height: calc(100svh - 3 * var(--onglet) + 124px);

  /* **La course s'allonge de ce que le calculateur déroulé dépasse.**

     Ajouté le 27 août 2026, quand le calculateur a cessé d'avoir une hauteur
     fixe pour grandir au déroulé. Sans cette rallonge, la course gardait sa
     taille d'origine (838 px mesurés) pendant que la section en atteignait
     1 270 : le calculateur débordait de son conteneur et passait sous le bloc
     suivant.

     `--depassement` est posé par `estime.js` au moment du déroulé, et remis à
     zéro au repli. Il vaut 0 par défaut, donc la course reste exactement ce
     qu'elle était tant que l'outil n'est pas déroulé — tout le calibrage
     ci-dessus est préservé. */
  /* La rallonge de 124 px et le dépassement S'ADDITIONNENT.

     Un essai les a rendus exclusifs, par `max()`, en croyant que la rallonge
     devenait du vide dès que l'outil débordait. C'était faux : la rallonge est
     ce qu'il faut au calculateur pour ATTEINDRE sa butée, quelle que soit sa
     hauteur, et la retirer le faisait se décoller avant la fin de son
     contenu. */
  /* ⚠️ LA COURSE EST LE TRAJET DU CADRE, DE SON ENTRÉE À SA SORTIE.

     La formule ci-dessus — un écran moins les onglets, plus 124 px de
     rallonge, plus le dépassement — était une accumulation de correctifs. Elle
     produisait deux défauts opposés, tous deux signalés par Karl le 28 août
     2026 :

     — **du défilement dans le vide à l'entrée.** Mesuré à 1512×850 : la course
       faisait 1008 px pour une section de 783. Les 225 px d'écart étaient du
       défilement pendant lequel le cadre, collé à sa butée, ne bougeait pas.
       « Il y a ce moment de scroll dans le vide avant de descendre vers le
       calculateur. »

     — **une bande beige sous le calculateur.** La course s'arrêtait au bas du
       cadre (1027 contre 930), donc la section suivante commençait pendant
       qu'il était encore à l'écran.

     Le trajet réel se calcule : le cadre entre par le bas, monte jusqu'à sa
     butée, y reste le temps qu'on le lise, puis sort par le haut. Cela vaut sa
     propre hauteur — la section, qui porte l'en-tête ET le cadre — plus une
     hauteur d'écran pour la traversée. Ni plus, ni moins.

     ⚠️ NE PAS AJOUTER DE RALLONGE FIXE. Les 124 px avaient été posés pour que
     la butée reste atteignable sur les écrans courts ; depuis que la course
     vaut la hauteur réelle de la section, elle l'est par construction. Toute
     marge en trop redevient du défilement mort. */
  /* La course vaut la BOÎTE de la feuille, plus ce que le calculateur dépasse
     quand il est déroulé. Rien d'autre.

     ⚠️ Une hauteur d'écran supplémentaire a été essayée ici pour « laisser le
     temps de la sortie » : elle a doublé le défilement mort à l'entrée, qui
     est passé de 200 à 800 px. La sortie du calculateur est portée par
     l'enveloppe de la pile, pas par la course d'une feuille. */
  /* ⚠️ + LA HAUTEUR DE L'EN-TÊTE.

     La course valait la boîte de la feuille — un écran moins les trois
     onglets. Mais la SECTION porte l'en-tête (sur-titre, titre, description,
     mascotte) EN PLUS du cadre : elle est donc plus haute que sa course, et
     dépassait sa butée avant la fin. Le pliage la remettait alors en place
     d'un coup, ce qui se voyait comme un saut. Mesuré à 1512×850, état
     replié : la section passait de 79 à 186 px, soit 107 px d'un coup.

     `--entete` est mesuré et posé par `estime.js`. Il ne change qu'au
     redimensionnement de la fenêtre, jamais pendant le défilement. */
  /* ⚠️ `--depassement` PORTE DÉJÀ L'EN-TÊTE.

     Il est calculé dans `estime.js` sur la hauteur RÉELLE de la section —
     en-tête compris — moins la boîte disponible. Ajouter `--entete` en plus
     comptait celui-ci deux fois : mesuré à 1512×850, la course excédait la
     section de 222 px pour un `--entete` de 231, et ces pixels étaient du
     défilement pendant lequel le cadre, à sa butée, ne bougeait plus. */
  /* ⚠️ MOINS L'EN-TÊTE : il défile AVANT que le cadre n'atteigne sa butée.

     La course valait la boîte entière de la feuille. Mais la section porte
     l'en-tête (sur-titre, titre, description, mascotte) au-dessus du cadre :
     cette hauteur-là est parcourue pendant l'approche, pas pendant le collage.
     La compter dans la course la prolongeait d'autant APRÈS la butée — du
     défilement pendant lequel le cadre ne bouge plus et son bas reste hors
     écran. Mesuré à 1512×850, état replié : 180 px de course morte, le cadre
     figé à 446→930 dans une fenêtre de 850.

     `--entete` est mesuré et posé par `estime.js`. Il ne change qu'au
     redimensionnement, jamais pendant le défilement. */
  /* ⚠️ + LA BUTÉE. La feuille se colle à `3 × --onglet` du haut de l'écran :
     sa course doit contenir ces pixels-là, sinon elle atteint la fin de son
     conteneur AVANT d'avoir fini de coller et se met à défiler librement.

     Mesuré à 1512×850 : la section descendait jusqu'à 69 px alors que sa
     butée est à 186, soit 117 px de dépassement. Au moment du pliage elle
     était ramenée à sa butée, et le calculateur REMONTAIT de 116 px en pleine
     descente. Karl : « il n'arrête pas, il continue de se rouler par-dessus
     les trois onglets de couleur. » */
  /* ⚠️ LA COURSE DOIT ÊTRE PLUS HAUTE QUE SA FEUILLE, SINON RIEN NE COLLE.

     C'est la règle fondamentale de `position: sticky` : une feuille se colle
     tant qu'il reste de la place dans son conteneur, et se décolle quand il se
     termine. Si la course vaut EXACTEMENT la hauteur de la section, il n'y a
     aucune marge de manœuvre et la feuille défile librement, sans jamais
     s'accrocher.

     C'est ce qui arrivait : mesuré à 1512×850, course de 796 px pour une
     section de 796. Le calculateur atteignait sa butée de 186 px puis
     CONTINUAIT — 159, 119, 79 — passant par-dessus les trois onglets au lieu
     de s'y souder. Karl : « lors de l'entrée du bloc avec le calculateur, il
     ne se colle pas aux onglets de couleur, il passe par-dessus. Je veux que
     le collage se fasse dès le moment où le bloc atteint sa limite avec
     l'onglet jaune. »

     Le pliage, qui survenait plus tard, le ramenait alors d'un coup à sa
     butée — d'où l'impression qu'il ne se soude qu'après être descendu puis
     remonté.

     La course vaut donc la SECTION plus le TRAJET DE COLLAGE : la hauteur
     pendant laquelle le calculateur reste accroché sous les trois onglets
     pendant qu'on le lit. Une hauteur d'écran moins la butée est la mesure
     naturelle de ce trajet — c'est exactement ce qu'il faut pour que le bas du
     calculateur traverse la fenêtre.

     ⚠️ `min-height: min-content` reste nécessaire (voir plus bas), mais il ne
     suffit PAS : il garantit que la course contient sa feuille, pas qu'elle
     est plus grande. C'est cette confusion qui a fait perdre le collage. */
  /* ⚠️ + LE PADDING DU BAS. La section porte un `padding-bottom` (l'air entre
     le calculateur et « Try it yourself »), et la course doit le contenir : il
     manquait 46 px, si bien que la course se terminait juste AVANT que le bas
     du cadre n'entre dans la fenêtre — or c'est précisément la condition du
     pliage. La section quittait donc sa butée sans que le pliage la relaie, et
     le calculateur repartait de 281 px d'un coup au moment où il survenait
     enfin. */
  /* ⚠️ + `--depassement` : LE TRAJET QUI AMÈNE LE BAS DANS L'ÉCRAN.

     Sans lui, le calculateur reste bien collé sous les onglets — le collage
     est acquis — mais la course se termine avant que son bas ait pu entrer
     dans la fenêtre. Mesuré aux états déroulés : le bas devenait
     inatteignable.

     `--depassement`, posé par `estime.js`, vaut ce que la section dépasse de
     la place disponible. Ce sont exactement les pixels qu'il faut parcourir,
     feuille collée, pour que la fin du calculateur traverse l'écran. */
  /* ⚠️ LE TRAJET DE COLLAGE N'EST PAS UNE HAUTEUR D'ÉCRAN.

     Il valait `100svh - 3 × --onglet`, soit 664 px à 1512×850 : le calculateur
     restait donc figé sous les onglets pendant tout ce temps, bas hors écran.
     Karl : « après, en dessous, il y a du scroll dans le vide où rien ne se
     passe. » Mesuré : 700 px de course morte.

     Ce qu'il faut réellement parcourir, feuille collée, c'est ce que la section
     DÉPASSE de la place disponible — ni plus ni moins. C'est exactement
     `--depassement`, déjà présent dans la formule. On ajoute seulement une
     marge courte pour que le bas du cadre franchisse le bord de l'écran au
     lieu de l'effleurer.

     ⚠️ NE PAS y remettre une hauteur d'écran : chaque pixel au-delà du
     dépassement est du défilement pendant lequel rien ne bouge. */
  /* ⚠️ LA BUTÉE FAIT PARTIE DE LA COURSE.

     La feuille se colle à `3 × --onglet` du haut : pour que son bas atteigne
     le bord de l'écran, elle doit parcourir cette distance EN PLUS de ce
     qu'elle porte. Sans elle, la course retombait à la hauteur exacte de la
     section — 796 px pour 796 — et `min-height: min-content` l'y maintenait :
     aucune marge, donc aucun collage. C'est l'erreur qui avait déjà fait
     passer le calculateur par-dessus les onglets. */
  /* ⚠️ `--pied` N'EST PAS RÉPÉTÉ ICI : `--trajet` le contient déjà (voir son
     calcul dans `estime.js`). Le compter deux fois allongeait la course
     d'autant, et ces pixels devenaient du défilement pendant lequel le cadre
     restait figé — mesuré à 1512×850, 148 px de course au-delà du point où le
     bas du cadre atteint le bord de l'écran. */
  /* ⚠️ LA COURSE = LA SECTION + LE TRAJET. RIEN À RECONSTRUIRE.

     La formule reconstituait la hauteur de la section à partir de ses parties
     (`--entete + --hauteur-cadre + 3 × --onglet`). Elle tombait toujours un peu
     court — les marges internes qu'aucune somme ne voit — et `min-height:
     min-content` ramenait alors la course à la hauteur exacte de la section :
     marge nulle, donc AUCUN COLLAGE. Mesuré : course 796 px pour une section
     de 796, alors que `--trajet` valait bien 80 px.

     `--section` est mesuré directement par `estime.js`. Additionné au trajet,
     il donne une course qui dépasse toujours sa feuille de la bonne quantité,
     sans hypothèse sur sa composition interne.

     ⚠️ C'est la troisième fois que la course perd son collage pour avoir été
     calculée par reconstruction. Ne pas y revenir. */
  /* ⚠️ + LA BUTÉE : elle se parcourt AUSSI, feuille collée.

     `--trajet` ne couvre que ce qui manque au bas du cadre pour entrer dans
     l'écran. Mais la feuille doit rester accrochée pendant TOUT le temps où
     elle est à sa butée — sinon elle repart avant que le pliage ne prenne le
     relais, et le calculateur mord sur les onglets : mesuré à 1512×850, il
     descendait à 160, 120 puis 80 px pour une butée de 186.

     La butée est la hauteur des trois onglets empilés : c'est exactement la
     distance dont la feuille doit disposer en plus. */
  /* ⚠️ LA MÊME RÈGLE QUE LES TROIS ONGLETS : `top + hauteur = 100vh`.

     Karl, 28 août 2026 : « je veux que lorsqu'il touche à l'onglet jaune, il
     s'y colle et que ce bloc reste fusionné aux trois onglets de couleur, de
     la même façon dont les onglets de couleur sont collés l'un à l'autre. »

     C'est la règle qui régit déjà `.bloc` : chaque feuille mesure
     `100vh − rang × --onglet`, si bien que `top + hauteur` vaut `100vh` pour
     toutes les quatre. Elles se décollent alors AU MÊME PIXEL de défilement —
     c'est précisément ce qui les rend solidaires.

     Le calculateur est la quatrième feuille, posée à `3 × --onglet` : sa
     course vaut donc `100vh − 3 × --onglet`, exactement comme les autres.

     ⚠️ J'AI ÉCARTÉ CETTE FORMULE PLUSIEURS FOIS en tentant de supprimer le
     défilement mort, et chaque fois le calculateur a cessé d'être solidaire :
     il continuait sa course et passait par-dessus les onglets. Le défilement
     qui reste pendant que la feuille est collée n'est PAS mort — c'est le
     temps où le calculateur est à sa place, en haut de l'écran, et où on le
     lit. NE PAS LE RETIRER.

     `--trajet` s'y ajoute quand le calculateur déroulé dépasse la fenêtre :
     ces pixels-là amènent son bas dans l'écran, sans quoi on ne verrait jamais
     sa fin. */
  /* ⚠️ + `--pied` : l'air sous le calculateur fait partie de la feuille.

     La section porte un `padding-bottom` (l'espace avant « Try it yourself »).
     Sans le compter, la feuille dépassait sa course de ces 52 px, atteignait
     la fin de son conteneur avant d'avoir fini de coller, et le calculateur
     repartait par-dessus les onglets. */
  /* ⚠️ + LA BUTÉE : c'est la marge sans laquelle rien ne colle.

     `position: sticky` a besoin que le conteneur soit PLUS GRAND que la
     feuille. Quand la course vaut exactement la hauteur de la section, la
     marge est nulle et la feuille défile librement : c'est le piège rencontré
     quatre fois, et c'est lui qui faisait passer le calculateur par-dessus les
     onglets.

     La feuille se colle à `3 × --onglet` du haut : elle doit donc disposer de
     cette distance EN PLUS de ce qu'elle porte, pour parcourir l'écran de sa
     butée jusqu'au bas. C'est exactement ce que fait la règle `.bloc` pour les
     trois onglets, où `top + hauteur = 100vh`. */
  height: calc(100vh - 3 * var(--onglet) + var(--trajet, 0px)
               + var(--pied, 0px) + 3 * var(--onglet));
  /* ⚠️ REPLI POUR SAFARI iOS, comme partout ailleurs dans ce fichier. Sur iOS,
     `100vh` vaut la hauteur BARRE D'ADRESSE MASQUÉE : tant qu'elle est
     affichée, la valeur dépasse la zone visible, et elle change quand la barre
     se rétracte au défilement. `svh` est la hauteur stable, celle qu'il faut.
     Les deux autres hauteurs de la pile (`.bloc` et `.calculateur-course`)
     portaient déjà ce repli ; celle-ci l'avait perdu.

     ⚠️ La déclaration tient sur DEUX lignes : insérer le repli après la
     première la couperait en deux et casserait la règle. */
  height: calc(100svh - 3 * var(--onglet) + var(--trajet, 0px)
               + var(--pied, 0px) + 3 * var(--onglet));
  height: calc(100svh - 3 * var(--onglet) + var(--trajet, 0px)
               + var(--pied, 0px) + 3 * var(--onglet));

  /* ⚠️ LA COURSE NE PEUT PAS ÊTRE PLUS COURTE QUE CE QU'ELLE PORTE.

     Mesuré à 1512×850, état replié : la course faisait 774 px pour une section
     de 783. Ces 9 px manquants — des marges que le calcul théorique ne voit
     pas — suffisaient à ce que la section dépasse sa butée avant la fin de sa
     course, et le pliage la remettait alors en place d'un coup : un saut de
     106 px.

     `min-content` règle cela sans mesure JavaScript : la course prend au moins
     la hauteur de son contenu, quelles que soient les marges. Une correction
     équivalente a été tentée dans `estime.js` et ne fonctionnait pas — le
     calcul s'y fait avant que la course ait sa taille définitive. */
  /* ⚠️ PLUS DE `min-height: min-content`.

     Il garantissait que la course contienne sa feuille. Mais il l'emportait
     sur la formule ci-dessus : mesuré à 1512×850, la course était forcée à
     796 px là où la règle des quatre feuilles en demande 744. La feuille
     devenait alors plus haute que sa course, elle atteignait la fin de son
     conteneur avant d'avoir fini de coller, et le calculateur passait
     par-dessus les onglets — le défaut que Karl a signalé six fois.

     La solidarité prime : `top + hauteur = 100vh` pour les quatre feuilles,
     sans exception. C'est le choix de Karl : « je veux que ce bloc reste
     fusionné aux trois onglets de couleur, de la même façon dont les onglets
     de couleur sont collés l'un à l'autre. »

     Ce que la course ne peut pas contenir déborde vers le bas et se lit en
     défilant, comme n'importe quelle section plus haute que l'écran. */
}

/* --- Le calculateur, dernière feuille de la pile -------------------------
   Il monte par-dessus le bloc jaune exactement comme les onglets sont montés
   les uns par-dessus les autres — le même geste, une quatrième fois — puis
   s'arrête sous le troisième onglet. Les trois bandes de couleur restent
   visibles et cliquables au-dessus de lui.

   Karl : « je veux que ce soit le bloc de calculation de prix qui embarque
   directement sur le dernier onglet, et il s'arrête de monter lorsque le
   restant qu'on voit du bloc jaune est la même portion que les deux autres
   blocs de couleur avant. »

   **C'est du `position: sticky`, comme les blocs, et c'est tout.** Le
   mécanisme précédent le pilotait en `position: fixed` depuis JavaScript, avec
   une variable de montée, une cale invisible qui rallongeait le document et
   une chorégraphie de classes. Tout cela reproduisait à la main ce que trois
   lignes de `sticky` font mieux : la montée est en 1:1 par construction, elle
   se défait à l'identique en remontant, et rien ne peut se désynchroniser
   puisqu'il n'y a plus qu'un seul repère. */
/* Le calculateur est posé HAUT dans son bloc.

   Karl, 27 août 2026 : « il y a un énorme espace vide en haut du bloc avec le
   calculateur, la fenêtre pourrait être vraiment plus en haut. »

   Mesuré : 107 px au-dessus du cadre, dont 41 de padding de section. Les
   sections ordinaires du site respirent davantage, mais le calculateur est un
   objet posé, pas un texte à lire : il gagne à toucher le haut de sa boîte. */
.pile .section-estime > .enveloppe {
  padding-top: 0;
  /* Un élément de grille a `min-height: auto`, qui vaut la hauteur du contenu
     — sauf en cas de conflit, où il cède. On le fige explicitement. */
  min-height: min-content;
}

/* ⚠️ LE CONTENU NE SE COMPRIME JAMAIS.

   La section est en `display: grid` avec `align-items: center` — un héritage
   de `.section`, qui centre son contenu. Un élément de grille se COMPRIME pour
   tenir dans sa piste : le cadre du calculateur, dont le contenu demande
   566 px, était écrasé à 545 et son bas se trouvait tronqué à l'intérieur
   d'un cadre pourtant entier.

   Karl : « le calculateur est encore coupé en bas ». Ce n'était pas un
   rognage — c'était un écrasement, ce qui explique que les mesures de boîte
   n'y voyaient rien.

   `align-items: start` et `min-height: min-content` sur les éléments : la
   piste prend la hauteur du contenu, jamais l'inverse. */
.pile .section-estime {
  align-items: start;
  padding-top: 12px;
  /* Le sélecteur est volontairement plus spécifique que `.section`, qui pose
     `position: relative` plus bas dans ce fichier : à spécificité égale, la
     dernière règle l'emporterait et le calculateur cesserait d'être collant. */
  position: sticky;
  /* ⚠️ LE POINT DE COLLAGE NE BOUGE PAS.

     Il a reculé un moment (`--recul`), pour qu'un calculateur plus haut que la
     fenêtre voie son bas revenir dans l'écran. Retiré le 27 août 2026 : le
     remède coupait le HAUT — mesuré, le cadre partait à −212 px dès
     l'ouverture, et Karl voyait un calculateur tronqué qu'il ne pouvait pas
     parcourir.

     Le calculateur grandit VERS LE BAS et pousse la page. Son haut reste où il
     est ; ce qui dépasse se lit en défilant, comme n'importe quelle section
     plus grande que l'écran. C'est exactement ce que Karl demandait : « pour
     se promener dans la fenêtre du calculateur, ça nous promène partout dans
     le site web. »

     Trois autres pistes ont été essayées et écartées le même jour, et ne
     doivent pas être reprises : sortir le calculateur du flux collant
     (`position: static`) désorganise toute la pile ; réorganiser le panneau en
     deux colonnes casse la mise en page ; et un défilement automatique à
     l'ouverture se bat avec le `sticky` et atterrit au mauvais endroit. */
  /* Le calculateur suit le même tassement que les trois onglets — voir
     `--remontee` dans la règle `.bloc`. */
  top: calc(3 * var(--onglet) - var(--remontee, 0) * var(--onglet) / 3);
  transition: top .5s cubic-bezier(.33, 0, .2, 1);

  /* ⚠️ HAUTEUR MINIMALE, PAS FIXE — changement du 27 août 2026.

     Elle valait `100svh − 3 × --onglet`, une hauteur FIXE qui donnait au
     calculateur sa butée nette dans la pile. Deux défauts en découlaient :

       · un grand vide au-dessus de l'outil, qui ne remplissait pas sa boîte ;
       · l'outil devait défiler à l'intérieur d'une fenêtre bornée, ce qui
         revenait à comprimer le contenu plutôt qu'à lui donner de la place.

     Karl : « plutôt que de scroller dans le calculateur, il prend tout son
     espace dans le bloc, il pousse le bloc vers le bas. Pour se promener dans
     la fenêtre du calculateur, ça nous promène partout dans le site web. »

     C'est une meilleure réponse au problème du défilement capturé que celle
     qu'elle remplace : il n'y a plus qu'UN SEUL défilement, celui de la page.
     Rien ne peut plus capturer la molette, puisqu'il n'existe aucune zone
     défilante à l'intérieur de l'outil.

     `min-height` conserve la butée quand le calculateur est compressé — il
     remplit alors exactement sa boîte, comme avant. Déroulé, il dépasse et
     pousse la page. Un élément `sticky` plus haut que la fenêtre se colle
     naturellement par son haut puis défile avec la page : c'est exactement le
     comportement voulu. */
  min-height: calc(100vh - 3 * var(--onglet));
  min-height: calc(100svh - 3 * var(--onglet));
  /* Au-dessus des trois blocs (z-index 10 à 12) : c'est la dernière feuille
     tirée, elle se pose PAR-DESSUS le bloc jaune. */
  z-index: 13;
  /* Elle a la même silhouette de feuille que les onglets — même rayon, même
     ombre portée vers le haut — pour que sa montée se lise comme le même
     geste. */
  border-radius: var(--rayon-feuille) var(--rayon-feuille) 0 0;
  /* ⚠️ L'ÉPAISSEUR TIENT À DEUX TRAITS, PAS À L'OMBRE SEULE.

     Karl, 28 août 2026 : « j'aimerais que les onglets de couleur aient la même
     profondeur dans leur design, dans leurs ombrages et leur profondeur. »

     Sa lecture est juste, et la mesure l'a confirmée : l'ombre portée vers le
     haut ne se voyait QUE sur le jaune. Mesuré à 1512×850, l'assombrissement au
     bas de chaque bande valait 10,1 % sur le jaune et **0,0 % sur le bleu comme
     sur le violet**. C'est arithmétique : une ombre sombre posée sur une
     surface sombre ne produit aucun contraste.

     Une feuille de papier ne montre pas son épaisseur par sa seule ombre : elle
     la montre AUSSI par la tranche qui capte la lumière. On ajoute donc une
     arête claire en haut de chaque feuille. Elle se lit sur n'importe quelle
     couleur — d'autant mieux que le fond est sombre, là où l'ombre échoue — et
     les deux effets se relaient au lieu de se concurrencer.

     ⚠️ L'ombre reste COURTE (10 px, diffusion 26). Dans une version
     précédente (`0 -22px 50px` à 22 %), elle s'étalait si loin qu'elle
     retombait sur l'onglet du bloc précédent et y noyait le texte : le libellé
     « YOUR WINDOW » n'était lisible que sur 2 lignes de pixels sur 11. Toute
     augmentation de sa portée doit être revérifiée sur le contraste des
     libellés. */
  /* ⚠️ DEUX TRANCHES, PAS UNE — chacune porte une famille de couleurs.

     L'arête claire du haut se lit d'autant mieux que le fond est sombre :
     mesuré, +25 de luminance sur le bleu et +29 sur le violet, mais seulement
     +4 sur le jaune, où du blanc sur du clair n'ajoute presque rien.

     La tranche sombre du bas fait l'inverse : invisible sur le sombre,
     franche sur le clair. Les deux ensemble donnent à chaque feuille la même
     épaisseur perçue, quelle que soit sa couleur — ce que Karl demande.

     ⚠️ L'ombre portée vers le haut (`0 -10px 26px`) ne se voit PAS quand la
     pile est pliée : le `clip-path` qui réduit chaque bloc à sa bande découpe
     aussi ce qu'il peint au-dessus de lui. Elle ne sert donc que dans l'état
     déployé, et l'épaisseur des bandes repose entièrement sur les deux
     tranches. */
  box-shadow:
    inset 0 1px 0 var(--arete-haut, rgba(255,255,255,.17)),
    inset 0 -1px 0 var(--tranche-bas, rgba(0,0,0,.30)),
    0 -10px 26px rgba(12,14,20,.28);
  margin-top: 0;
}

/* ---- LE BAS DE LA FEUILLE S'ARRONDIT UNE FOIS DÉROULÉE ----------------

   La section est une FEUILLE qui monte dans la pile : son bas est droit par
   construction, puisqu'il est censé disparaître sous ce qui suit. Une fois le
   calculateur déroulé et posé, ce bord droit se lit comme une coupure — Karl :
   « le bas de la fenêtre est encore coupé, cette boîte-là est coupée en deux
   d'une ligne droite. Je veux qu'il ait les mêmes beaux coins arrondis en bas
   qu'en haut. »

   La classe est posée par `estime.js` en même temps que le déroulé. */
.pile .section-estime.est-pose {
  border-radius: var(--rayon-feuille);
  padding-bottom: 22px;
}


/* Les blocs GARDENT LEUR PLEINE HAUTEUR, et c'est la clé de la simplicité
   retrouvée le 26 août 2026.

   Il a longtemps fallu les réduire à leur bande de 62 px, avec toute une
   mécanique de classes posées au bon moment (`pile-plies`, `pile-calculateur`,
   `pile-retour`). C'était nécessaire tant que la pile passait en
   `position: fixed` : les blocs quittaient alors le flux, ne se recouvraient
   plus les uns les autres, et le reste de leur hauteur ressortait sous le
   calculateur.

   En `position: sticky`, ils SE RECOUVRENT NATURELLEMENT : chacun se colle
   62 px plus bas que le précédent et le cache, si bien qu'on ne voit jamais
   que la bande du haut de chacun. Le calculateur, dernière feuille de la même
   pile, recouvre le bloc jaune de la même façon.

   La règle de Karl — « une fois que les onglets se sont fermés et sont rangés
   l'un à côté de l'autre, peu importe où on est dans le site web, les onglets
   restent fermés et la seule façon de les ouvrir, c'est en cliquant dessus » —
   est donc satisfaite par construction : un bloc recouvert ne peut pas
   s'ouvrir tout seul, il n'y a plus d'état à maintenir ni à lever. */

.bloc-portal  { --rang: 1; }
.bloc-network { --rang: 2; }

/* Chaque feuille porte SA couleur et celle de ce qui la précède dans la pile.
   L'ordre est celui que Karl a dicté : le carrousel gris, puis bleu, violet,
   jaune, et le calculateur qui referme la pile.

   `--wpn-couleur` sert de fond au bloc ; `--couleur-dessous` remplit
   l'échancrure de ses coins hauts (voir le calage de couleur plus bas). */
.bloc { background: var(--wpn-couleur, transparent); }

.bloc-window   { --wpn-couleur: var(--wpn-bleu-700);   --couleur-dessous: var(--wpn-fond); }
.bloc-portal   { --wpn-couleur: var(--wpn-violet-800); --couleur-dessous: var(--wpn-bleu-700); }
.bloc-network  { --wpn-couleur: var(--wpn-jaune-400);  --couleur-dessous: var(--wpn-violet-800);
  /* ⚠️ LE JAUNE EST CLAIR : LE DOSAGE S'INVERSE.

     Avec le réglage commun, son relief mesurait 82,5 contre 44 pour le bleu et
     43,5 pour le violet — presque le double. C'est arithmétique : une tranche
     noire sur du jaune clair crée un contraste énorme, une arête blanche
     presque aucun.

     On rééquilibre donc : arête blanche plus franche, tranche sombre plus
     discrète. Les trois feuilles montrent alors la même épaisseur, ce que Karl
     demande — « la même profondeur dans leur design, dans leurs ombrages et
     leur profondeur ». */
  --arete-haut:  rgba(255,255,255,.50);
  --tranche-bas: rgba(0,0,0,.12);
}
.section-estime{ --couleur-dessous: var(--wpn-jaune-400); }

/* --- Le calage de couleur derrière les coins arrondis ---------------------
   Karl : « je n'aime pas l'espèce de coin arrondi gris. Cette partie devrait
   être la même couleur que l'onglet qui est au-dessus. Ça brise l'illusion que
   plusieurs onglets sont glissés l'un par-dessus l'autre. »

   Chaque feuille a le haut arrondi. Là où le rayon creuse la silhouette, on
   voyait le fond de la page — un gris qui n'a rien à faire là, puisque ce qui
   se trouve derrière est la feuille PRÉCÉDENTE. L'ordre dicté par Karl : le
   bloc bleu porte le gris du carrousel, le violet porte le bleu, le jaune
   porte le violet, et le calculateur porte le jaune (`--couleur-dessous`,
   posée juste au-dessus).

   ## La technique

   Un pseudo-élément de la hauteur du rayon, posé DERRIÈRE la feuille, rempli
   de la couleur du dessous, puis découpé par un masque qui ne laisse passer
   que les deux échancrures — le négatif exact du `border-radius`. Chaque
   `radial-gradient` est transparent dans le disque du rayon et opaque au
   dehors : ce qui reste est précisément le petit triangle courbe entre le
   coin de la boîte et l'arrondi.

   **Les propriétés du masque doivent être écrites en toutes lettres.** La
   forme courte `left top / 34px 34px` ne pose PAS la taille : les dégradés
   couvrent alors toute la largeur et le pseudo-élément peint une bande pleine
   qui donne aux onglets un aspect rectangulaire. Ce détail a déjà coûté du
   temps une fois.

   ## Deux techniques qui ont échoué avant elle — ne pas les réessayer

   — Une ombre portée (`box-shadow: 0 0 0 34px`) : elle se peint bien hors de
     la silhouette arrondie, mais SUR LES QUATRE CÔTÉS. Elle débordait sous
     chaque bloc en une bande pleine largeur, et recouvrait le haut du
     calculateur, dont les coins paraissaient violets.
   — Deux couches de fond en `padding-box` / `border-box` : sans bordure les
     deux surfaces sont identiques et la couche du dessous ne se voit jamais ;
     créer une bordure de 34 px pour les séparer déplace le bloc. */
.bloc::before,
.pile .section-estime::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--rayon-feuille);
  background: var(--couleur-dessous);

  /* ⚠️ L'ÉCHANCRURE DOIT POUVOIR PEINDRE HORS DE LA SILHOUETTE ARRONDIE.

     La feuille porte `overflow: clip` et `border-radius: 34px 34px 0 0` : ses
     enfants — dont cette couche — sont donc découpés SUIVANT LES COINS
     ARRONDIS. L'échancrure ne pouvait pas remplir les deux triangles laissés
     libres en haut, et on y voyait le fond de la page.

     Karl, 28 août 2026 : « une fois qu'on a atteint le calculateur, si je
     reviens en haut et que je déroule l'onglet violet, les coins sont gris.
     Ils ne sont pas bleus. Ils devraient être bleus pour simuler que l'onglet
     bleu en dessous fait sa continuité. » Mesuré : rgb(227,223,214) — le beige
     de la page — là où on attend le bleu rgb(10,76,139).

     `overflow-clip-margin` élargit la zone de découpe de la hauteur de
     l'échancrure, juste assez pour qu'elle atteigne les coins. Le `clip-path`
     du pliage continue d'agir par-dessus, donc rien ne déborde quand la
     feuille est réduite à sa bande. */

  /* **Le bloc BLEU est un cas à part : derrière lui, il n'y a pas une couleur
     unie mais le fond texturé de la page.** Karl, 27 août 2026 : « un bug ici
     avec les coins blancs. »

     Les échancrures du bleu étaient remplies avec `--wpn-fond` seul, soit
     #F5F5F4 — un blanc froid. Or le `body` peint cette couleur AVEC la texture
     papier en `multiply` par-dessus, ce qui donne un beige chaud : mesuré,
     rgb(233,230,221) sur la page contre rgb(245,245,244) dans les coins. La
     lisière paraissait donc blanche et plus claire que la page autour.

     Les blocs violet et jaune n'ont pas ce défaut : derrière eux il y a le
     bloc précédent, qui est bien un aplat de couleur unie. */
  /* Derrière la feuille elle-même, qui est opaque : seules les échancrures,
     que la feuille ne couvre pas, laissent voir cette couche. */
  z-index: -1;
  pointer-events: none;
  -webkit-mask-image:
    radial-gradient(circle var(--rayon-feuille) at var(--rayon-feuille) var(--rayon-feuille), transparent 100%, #000 100%),
    radial-gradient(circle var(--rayon-feuille) at 0 var(--rayon-feuille), transparent 100%, #000 100%);
  -webkit-mask-position: left top, right top;
  -webkit-mask-size: var(--rayon-feuille) var(--rayon-feuille);
  -webkit-mask-repeat: no-repeat;
  mask-image:
    radial-gradient(circle var(--rayon-feuille) at var(--rayon-feuille) var(--rayon-feuille), transparent 100%, #000 100%),
    radial-gradient(circle var(--rayon-feuille) at 0 var(--rayon-feuille), transparent 100%, #000 100%);
  mask-position: left top, right top;
  mask-size: var(--rayon-feuille) var(--rayon-feuille);
  mask-repeat: no-repeat;
}

/* **Seul le bloc BLEU a la page derrière lui, et la page est TEXTURÉE.**
   Sa couche de remplissage doit donc reprendre la texture du `body`, sinon ses
   échancrures montrent `--wpn-fond` nu — un blanc froid #F5F5F4 — là où la
   page voisine, elle, est à rgb(236,232,221) parce que la texture papier y est
   appliquée en `multiply`. C'est ce qui donnait la lisière claire que Karl a
   signalée le 27 août 2026 : « un bug ici avec les coins blancs. »

   **Cette règle doit rester APRÈS la règle groupée ci-dessus.** Les deux ont
   la même spécificité (0-1-1) : placée avant, elle est écrasée et le défaut
   revient — c'est exactement ce qui s'est produit au premier essai, où la
   règle avait été posée près de la définition des couleurs, 46 lignes plus
   haut, et n'avait aucun effet. */
.bloc-window::before {
  background: var(--wpn-fond) url("../../images/texture-papier.jpg") repeat;
  background-size: 600px auto;
  background-blend-mode: multiply;
}

/* L'onglet lui-même : la bande cliquable qui dépasse en haut du bloc une
   fois celui-ci recouvert. Elle porte le nom du bloc, aligné à gauche
   comme le reste du site. */
.bloc-onglet {
  position: absolute; top: 0; left: 0; right: 0;
  height: var(--onglet);
  display: flex; align-items: center;
  padding: 0 clamp(20px, 5vw, 64px);
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Blanc plein, et non transparent : le libellé se lit par-dessus l'ombre
     d'épaisseur de la feuille qui le recouvre. */
  color: #fff;
  text-decoration: none;
  z-index: 3;
}
.bloc-onglet:hover { color: #fff; }
.bloc-onglet::before {
  content: ""; width: 9px; height: 9px; margin-right: var(--espace-3);
  background: currentColor; border-radius: 2px;
  transform: rotate(45deg);
}

/* Le contenu commence sous l'onglet, pour ne jamais passer dessous. */
.bloc > .enveloppe { padding-top: var(--onglet); }

/* ⚠️⚠️ LA PILE EST DÉSORMAIS ACTIVE SUR TÉLÉPHONE — 29 août 2026.

   Ce bloc désactivait toute la mécanique sous 620 px, pour la raison écrite
   alors : « trois onglets mangeraient la moitié de la hauteur ». Karl a
   parcouru le site sur son téléphone et a demandé l'inverse :

   « Le premier onglet bleu, il ne glisse pas par-dessus, il faut qu'on scrolle
   vers l'onglet — cette animation-là n'existe pas. Même chose avec le violet,
   même chose avec le jaune. C'est comme un énorme scroll qui ne finit jamais. »

   CE QUI A CHANGÉ DEPUIS LA DÉCISION D'ORIGINE, et qui la rend caduque : le
   tassement complet, posé le même jour. Les bandes ne s'accumulent plus — chaque
   feuille qui arrive pousse la précédente hors de l'écran. L'objection « trois
   onglets mangeraient la moitié de la hauteur » ne tient donc plus : une seule
   bande est visible à la fois.

   CE QUI RESTE DE CE BLOC : les deux variables. `--onglet: 44px` au lieu de 62
   et `--rayon-feuille: 22px` au lieu de 34 — les proportions du téléphone, qui
   valaient déjà quand la pile était désactivée et qui valent encore plus
   maintenant qu'elle joue.

   TOUT LE RESTE A ÉTÉ RETIRÉ : la position relative, les hauteurs annulées,
   les échancrures masquées, le pliage neutralisé. Chacune de ces lignes
   empêchait la pile de fonctionner. Les remettre la casserait de nouveau.

   Le balisage d'origine est conservé dans
   `siteweb/sauvegardes/pile-desactivee-mobile.css.avant-retrait`. */
@media (max-width: 620px) {
  :root { --onglet: 44px; --rayon-feuille: 22px; }
}
/* Les couleurs propres des blocs sont posées plus bas, par `--wpn-couleur`,
   en même temps que celle de leur échancrure : les deux forment un seul fond
   à deux couches (voir « Le calage de couleur derrière les coins arrondis »).
   Une déclaration `background` ici écraserait ces couches. */
.bloc-network { color: var(--wpn-ancre); }

/* Le grain d'encre sérigraphiée, sur les aplats. */
.bloc::after {
  content: "";
  position: absolute; inset: 0;
  background: url("../../images/grain-riso.jpg") repeat;
  background-size: 420px;
  mix-blend-mode: multiply;
  opacity: 0.10;
  pointer-events: none;
}
.bloc-network::after { opacity: 0.06; }

/* --- Le mot géant qui déborde --- */
.bloc-mot {
  position: absolute;
  right: 3%; bottom: 10px;
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  /* Le mot doit se lire en entier : il est posé sur le bord inférieur du
     bloc, pas coupé par lui. */
  font-size: clamp(3.4rem, 9.5vw, 8rem);
  line-height: .82; letter-spacing: -0.045em;
  color: #fff; opacity: .10;
  pointer-events: none; user-select: none;
  white-space: nowrap;
}
.bloc-network .bloc-mot { color: var(--wpn-ancre); opacity: .13; }

.bloc-grille {
  position: relative; z-index: 1;
  display: grid;
  grid-template-columns: 1fr minmax(230px, 340px);
  gap: clamp(32px, 5vw, 68px);
  align-items: center;
}
.bloc-network .bloc-grille { direction: rtl; }
.bloc-network .bloc-grille > * { direction: ltr; }
@media (max-width: 880px) {
  .bloc-grille { grid-template-columns: 1fr; }
  .bloc-network .bloc-grille { direction: ltr; }
}

.bloc-numero {
  display: flex; align-items: center; gap: var(--espace-3);
  font-family: var(--police-mono, monospace);
  font-size: var(--texte-xs); font-weight: var(--graisse-grasse);
  letter-spacing: 0.18em; text-transform: uppercase;
  opacity: .62; margin: 0 0 var(--espace-5);
}
.bloc-numero span {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border: 1.5px solid currentColor; border-radius: 5px;
  transform: rotate(45deg); font-size: 10px; letter-spacing: 0;
}
.bloc-numero span::before { content: attr(data-x); }

.bloc h2 {
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  font-size: clamp(2rem, 4.4vw, 3rem);
  line-height: 1.04; letter-spacing: -0.032em;
  margin: 0 0 var(--espace-5);
}
.bloc-chapo {
  font-size: clamp(1rem, 1.6vw, 1.14rem);
  margin: 0 0 var(--espace-6);
  max-width: 50ch; opacity: .9;
}
.bloc-liste { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--espace-3); }
.bloc-liste li {
  display: flex; gap: var(--espace-3); align-items: flex-start;
  font-size: var(--texte-sm); opacity: .88;
}
.bloc-liste li::before {
  content: ""; flex-shrink: 0;
  width: 9px; height: 9px; margin-top: 9px;
  background: currentColor; border-radius: 2px;
  transform: rotate(45deg); opacity: .7;
}

/* --- Le lien de bloc, avec remplissage au survol --- */
.bloc-lien {
  position: relative; overflow: hidden;
  display: inline-block; margin-top: var(--espace-6);
  padding: 12px 24px; border-radius: var(--rayon-md);
  color: #fff; text-decoration: none;
  font-weight: var(--graisse-moyenne); font-size: var(--texte-base);
  border: 1.5px solid rgba(255,255,255,.34);
}
.bloc-lien span { position: relative; z-index: 1; transition: color .3s ease; }
.bloc-lien::before {
  content: ""; position: absolute; inset: 0;
  background: #fff; transform: scaleX(0); transform-origin: left;
  transition: transform .38s cubic-bezier(.2,.8,.2,1);
}
.bloc-lien:hover::before { transform: scaleX(1); }
.bloc-window .bloc-lien:hover span { color: var(--wpn-bleu-700); }
.bloc-portal .bloc-lien:hover span { color: var(--wpn-violet-800); }
.bloc-network .bloc-lien {
  color: var(--wpn-ancre); border-color: rgba(27,31,42,.3);
}
.bloc-network .bloc-lien::before { background: var(--wpn-ancre); }
.bloc-network .bloc-lien:hover span { color: var(--wpn-jaune-400); }

/* --- Médias du bloc --- */

.bloc-media { display: flex; justify-content: center; align-items: center; }

.objet-image {
  display: block; width: min(270px, 62vw); height: auto;
  filter: brightness(1.22);
}
.bloc-network .objet-image { filter: brightness(1.08); }

.telephone {
  width: 100%; max-width: 246px;
  border-radius: 32px; padding: 9px;
  background: #111318;
  box-shadow: 0 30px 70px rgba(0,0,0,.32);
}
.telephone video {
  display: block; width: 100%; height: auto;
  border-radius: 27px; background: #000;
}

/* --------------------------------------------------------------------------
   Le sélecteur de métier — blocs Window et Portal
   --------------------------------------------------------------------------
   Les trois images du jeu sont empilées AU MÊME ENDROIT et se fondent l'une
   dans l'autre. Le personnage occupe la même position dans les trois photos,
   donc le fondu ne se lit pas comme un changement d'image mais comme le métier
   qui se transforme autour d'un homme immobile.

   La première image reste DANS LE FLUX et donne sa hauteur à la pile ; les
   suivantes sont posées par-dessus en absolu. Sans cela la pile n'aurait
   aucune hauteur propre et s'effondrerait.
   -------------------------------------------------------------------------- */

.zone-metiers { position: relative; }

.masc-pile { position: relative; width: 100%; }
.masc-pile img {
  display: block; width: 100%; height: auto;
  border-radius: var(--rayon-lg, 18px);
  box-shadow: 0 30px 70px rgba(0,0,0,.34);
  opacity: 0; transition: opacity .5s ease;
}
.masc-pile img + img { position: absolute; inset: 0; }
.masc-pile img.actif { opacity: 1; }

/* Le téléphone du bloc Portal se pose PAR-DESSUS la mascotte, dans le coin
   bas-droit. On voit le commerçant et l'écran qu'il manipule dans une seule
   scène — c'est le sens même de Portal. Il déborde volontairement du cadre de
   l'image pour créer de la profondeur. */
.bloc-portal .zone-metiers .telephone {
  position: absolute; right: -54px; bottom: -48px; z-index: 3;
  max-width: 156px;
}

/* --- Les boutons de métier --- */
.masc-choix {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-top: var(--espace-6);
}
.masc-choix button {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 10px 16px; border-radius: 999px;
  background: rgba(255,255,255,.10);
  border: 1.5px solid rgba(255,255,255,.26);
  color: #fff; font-family: inherit; font-size: var(--texte-sm);
  cursor: pointer;
  transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.masc-choix button:hover { background: rgba(255,255,255,.18); }
.masc-choix svg { width: 18px; height: 18px; flex-shrink: 0; }

.bloc-window .masc-choix button[aria-pressed="true"] {
  background: #fff; color: var(--wpn-bleu-700); border-color: #fff;
  font-weight: var(--graisse-moyenne);
}
.bloc-portal .masc-choix button[aria-pressed="true"] {
  background: #fff; color: var(--wpn-violet-800); border-color: #fff;
  font-weight: var(--graisse-moyenne);
}

/* La liste change de contenu au clic. Une hauteur minimale évite que le bloc
   sursaute quand un métier a des libellés plus courts qu'un autre. */
.bloc-liste[data-puces-metier] { min-height: 108px; }

/* La colonne média doit être plus large que pour une simple forme
   décorative : une photographie de commerce y perd tout son sens si elle est
   réduite à 270 px. */
.bloc-window .bloc-grille,
.bloc-portal .bloc-grille { grid-template-columns: 1fr minmax(320px, 460px); }

/* Le jeu « fenêtre » est en paysage (16:8,7) : à largeur égale il est bien
   moins haut que le jeu « métier » en portrait, et le bloc bleu se retrouvait
   avec un grand vide sous l'image. On lui donne donc plus de largeur. */
.bloc-window .bloc-grille { grid-template-columns: 1fr minmax(380px, 560px); }

/* L'élargissement de la colonne média a rapproché le titre du chapô : la
   marge sous le h2 est rétablie explicitement pour ces deux blocs. */
.bloc-window h2, .bloc-portal h2 { margin-bottom: var(--espace-5); }
.bloc-window .bloc-chapo, .bloc-portal .bloc-chapo { margin-top: 0; }

/* **Le contenu du bloc Portal est remonté.** Karl : « le bloc violet perd de
   l'espace en haut, et le téléphone est un peu trop coupé, donc on pourrait
   remonter tout le contenu un peu plus haut. »

   Les deux vont ensemble : ce qu'on récupère en haut, le téléphone le gagne en
   bas. La grille est alignée sur le HAUT plutôt que centrée — c'est ce qui
   libère vraiment l'espace, un contenu centré le répartissant à parts égales
   en haut et en bas. */
.bloc-portal .bloc-grille { align-items: start; }
.bloc-portal .enveloppe { padding-top: 0; }

/* La colonne média descend légèrement pour rester en regard du texte, sans
   quoi l'image collerait au bord haut du bloc. */
.bloc-portal .bloc-media { padding-top: var(--espace-4); }

@media (max-width: 880px) {
  .bloc-window .bloc-grille,
  .bloc-portal .bloc-grille { grid-template-columns: 1fr; }
  .bloc-portal .zone-metiers .telephone {
    right: 0; bottom: -18px; max-width: 118px;
  }
  .bloc-liste[data-puces-metier] { min-height: 0; }
}

/* ==========================================================================
   Sections claires
   ========================================================================== */

.section {
  position: relative;
  padding: clamp(90px, 12vw, 150px) 0;
  margin-top: -34px;
  border-radius: 34px 34px 0 0;
  background: var(--wpn-fond);
  box-shadow: 0 -22px 50px rgba(27,31,42,.10);
}
.section-voies { background: #fff; }

/* --- Un bloc déroulé passe devant le calculateur -------------------------
   Au repos, le calculateur est la dernière feuille de la pile : il se pose
   PAR-DESSUS le bloc jaune, donc son `z-index` domine celui du conteneur.

   Mais dès qu'on déroule un onglet, le rapport s'inverse : c'est le bloc
   qu'on vient d'ouvrir qui doit être lu, et le calculateur redevient le
   fond. Sans cette bascule, cliquer sur un onglet n'affichait qu'une bande
   de couleur vide — le contenu du bloc était bien déplié, mais entièrement
   masqué par le calculateur posé devant.

   `:has()` permet de faire remonter le conteneur quand l'un de ses enfants
   est ouvert, ce qu'aucun sélecteur descendant ne saurait exprimer. */
/* --- La pile pliée : les onglets deviennent solidaires --------------------
   Une fois la pile constituée — c'est-à-dire quand le calculateur est venu se
   loger sous le troisième onglet — les trois blocs se PLIENT. Ils ne sont plus
   trois pages qu'on retraverse en remontant, mais un jeu de trois bandes qui
   descendent ensemble.

   Karl : « lorsqu'on a passé les trois onglets de couleur, ils se referment et
   deviennent petits comme des onglets. Si on revient vers le haut, il faut
   qu'ils restent collés ensemble pour que ce soit plus rapide de remonter en
   haut de la page. »

   ## Deux exigences contradictoires, et comment elles se concilient

   Un élément collant se décolle quand le bas de sa BOÎTE atteint le bas du
   conteneur ; mais c'est sa hauteur DANS LE FLUX qui fixe la position du
   suivant, donc le moment où celui-ci le rattrape en remontant.

   — Pour que les trois bandes remontent SOLIDAIRES, il faut que leurs
     positions dans le flux soient espacées de 62 px, comme leurs `top`. Elles
     se décollent alors toutes au même pixel.
   — Pour qu'elles s'en aillent EN MÊME TEMPS que le calculateur quand la pile
     sort par le haut, il faut que `top + hauteur de boîte` vaille `100svh`
     pour les quatre.

   Ces deux hauteurs semblent s'exclure. Elles se séparent grâce à une MARGE
   NÉGATIVE : la boîte garde sa pleine hauteur — donc la sortie reste
   synchronisée — pendant que la marge ramène l'avance dans le flux à 62 px —
   donc la remontée est solidaire.

   ## Et le découpage visuel

   La boîte reste haute, mais on n'en montre que la bande : `clip-path` découpe
   le rendu à 62 px sans toucher ni à la boîte ni au flux. Sans lui, on verrait
   redescendre un aplat de couleur plein écran au lieu d'un onglet.

   Le pliage est INVISIBLE au moment où il se produit : les blocs étaient déjà
   recouverts au-delà de leur bande par le bloc suivant. Seule la hauteur du
   flux change, et `pile.js` compense le défilement dans la même image. */
/* Chrome pratique le « scroll anchoring » : quand du contenu situé au-dessus
   de la vue change de taille, il ajuste le défilement de lui-même pour garder
   l'image stable. C'est utile en général, mais ici cela s'ajoutait à notre
   propre compensation et la page partait deux fois trop haut — mesuré, elle
   remontait jusqu'à zéro au lieu de ne pas bouger.

   On le désactive donc sur la pile : la compensation est faite explicitement
   par `pile.js`, ce qui donne le même résultat dans tous les navigateurs —
   Safari, lui, ne pratique pas l'ancrage. */
/* L'ancrage doit être désactivé sur le CONTENEUR DE DÉFILEMENT, pas seulement
   sur la pile. C'est le document qui défile, donc c'est lui qui arbitre
   l'ancrage : `overflow-anchor: none` posé sur `.pile` seule laissait le
   navigateur repositionner la page de son côté, AVANT que `pile.js` n'ait pu
   compenser. La page « coupait » alors et remontait de plus de 2 000 px —
   Karl : « d'un coup ça a coupé et je suis revenu en haut de la page. » */
html, body { overflow-anchor: none; }
.pile { overflow-anchor: none; }

.pile-plies .bloc {
  /* **Les trois bandes restent collées AU-DESSUS du calculateur et partent
     avec lui.** Karl : « lorsque le bloc du calculateur a fini de rouler le
     bloc jaune, je ne veux pas que le bloc du calculateur s'enroule sur le
     reste des onglets quand on défile vers le bas. Je veux que les trois
     onglets restent collés après le haut du bloc du calculateur. »

     La marge ne ramène donc PAS la boîte à une bande : cela ferait terminer
     les blocs bien avant la course du calculateur, ils se décolleraient les
     premiers, et le calculateur monterait par-dessus eux. Elle est calculée
     pour que les quatre boîtes se terminent au même point — la marge annule
     l'écart de hauteur entre le bloc et la course, rien de plus.

     Le `clip-path` continue de ne montrer que la bande : c'est lui qui donne
     l'aspect « onglet », sans toucher ni à la boîte ni au flux. */
  /* **La boîte de chaque bloc se termine au MÊME POINT que celle du
     calculateur** — mesuré, à 720 px dans le flux du conteneur.

     Avec l'ancienne marge, les boîtes finissaient à 393 et 497, c'est-à-dire
     AU-DELÀ de la fin du conteneur : les blocs restaient donc collés jusqu'au
     bout pendant que le calculateur, lui, se décollait et montait par-dessus
     eux. Karl : « je ne veux pas que le bloc du calculateur s'enroule sur le
     reste des onglets. Je veux que les trois onglets restent collés après le
     haut du bloc du calculateur. »

     Chaque bloc est décalé de son rang dans le flux (il commence une bande
     plus bas que le précédent) et sa hauteur est réduite d'autant : la marge
     annule ce décalage pour que les quatre boîtes se terminent ensemble. Les
     quatre feuilles se décollent alors au même pixel et sortent solidaires.

     Le `clip-path` continue de ne montrer que la bande : c'est lui qui donne
     l'aspect « onglet », sans toucher ni à la boîte ni au flux. */
  margin-bottom: 0;
  clip-path: inset(0 0 calc(100% - var(--onglet)) 0);
}

/* ==========================================================================
   LE PLIAGE STRUCTUREL : un seul élément colle, donc un seul se décolle
   ==========================================================================

   Tant que les quatre feuilles restent collantes chacune de son côté, elles se
   décollent l'une après l'autre en remontant — mesuré, le calculateur à
   y = 3350, le bloc jaune à 2750, le violet à 2150. Entre deux décollages, un
   intervalle se creuse et laisse voir le fond de la page. Karl : « l'onglet
   jaune, ça reste juste une barre jaune qui descend jusqu'en bas, c'est tout
   gris partout. Même chose avec le violet. »

   Cinq tentatives de corriger cela par des marges, des hauteurs ou des butées
   ont toutes cassé la descente : la position de décollage à la remontée et
   l'écart des onglets à la descente sont gouvernés par la même géométrie, on
   ne peut pas régler l'une sans dérégler l'autre.

   La solution est structurelle. Une fois la pile constituée, c'est `.pile`
   ELLE-MÊME qui devient collante, et les quatre feuilles repassent en
   `relative` à l'intérieur. Il n'y a plus qu'un seul élément collant, donc un
   seul point de décollage : les quatre partent forcément ensemble, sans aucun
   calcul. Karl : « quand on revient en haut, les trois onglets et le
   calculateur descendent ensemble, pour dérouler le carrousel pendant qu'on
   revient. »

   Les blocs restent PLIÉS pendant tout ce trajet — le `clip-path` ne les lâche
   pas, et seul un clic sur un onglet rouvre son bloc. */

/* L'enveloppe garde la hauteur que la pile avait AVANT d'être pliée. Sans
   cela, le document raccourcissait brutalement au moment du pliage — la pile
   passe de quatre hauteurs d'écran à une seule — et la page bondissait de
   1 866 px, ce qui reproduisait exactement la coupure signalée par Karl.

   La hauteur est donc figée ici, et c'est elle qui porte toute la course : la
   descente des quatre feuilles, puis le temps où la pile pliée reste collée.
   Le pliage ne change plus alors que l'apparence, jamais la mise en page. */
.pile-enveloppe {
  position: relative;
  /* ⚠️ LE DÉPASSEMENT DU CALCULATEUR S'AJOUTE ICI AUSSI.

     La hauteur est calculée pour quatre feuilles d'UNE HAUTEUR D'ÉCRAN. Le
     calculateur en fait maintenant davantage — en-tête, cadre déroulé, liste
     dépliée — et `.calculateur-course` s'allonge en conséquence. Mais
     l'enveloppe, elle, gardait sa taille : la pile débordait de son conteneur
     de près de 600 px, et le rendu s'arrêtait net à la fin de l'enveloppe.

     C'est ce qui donnait un calculateur « coupé en bas » que les mesures ne
     voyaient pas : le navigateur MESURAIT le cadre entier (606 px, de 244 à
     849) mais n'en PEIGNAIT que le haut, jusqu'à 283 px. Un décalage entre la
     mise en page et le rendu, invisible à toute mesure de boîte.

     `--depassement` est posé sur `.calculateur-course` par `estime.js` ; il
     est repris ici parce que la variable est héritée par les descendants, pas
     par les ancêtres — d'où la duplication du calcul dans `estime.js`. */
  /* ⚠️ LA HAUTEUR EST LE TRAJET RÉELLEMENT NÉCESSAIRE, RIEN DE PLUS.

     Elle valait quatre hauteurs d'écran, puis cinq. C'était une réserve
     confortable, et c'est ce qui produisait les deux zones mortes que Karl a
     signalées : « il y a ce moment de scroll dans le vide avant de descendre
     vers le calculateur, et même chose à l'inverse lorsqu'on essaie d'en
     sortir, on scrolle dans le vide pendant un certain temps. »

     Mesuré à 1512×850 : 900 px de défilement pendant lesquels le cadre restait
     figé à 446→930 à l'entrée, et 2 400 px après sa sortie de l'écran.

     Le trajet réel est simple et se calcule : les TROIS BLOCS défilent chacun
     sur une hauteur d'écran moins leur onglet — c'est ce qui les empile — puis
     le CALCULATEUR traverse l'écran de bas en haut, soit sa propre hauteur
     plus une hauteur d'écran. `--depassement-pile` porte sa croissance quand
     il est déroulé.

     ⚠️ NE PAS AJOUTER DE MARGE « au cas où » : chaque pixel en trop est un
     pixel de défilement pendant lequel rien ne bouge à l'écran. */
  /* ⚠️ + LA HAUTEUR DU CALCULATEUR, pour qu'il SORTE avant que la section
     suivante n'apparaisse. Une hauteur d'écran seule le fait quitter l'écran
     par le haut, mais la pile finit alors au ras de son bas : mesuré à
     1512×850, le cadre finissait à 63 et `#demo` commençait à 203 — 140 px de
     bande claire sous le calculateur, la « barre beige » de Karl.
     `--depassement-pile` porte la croissance du cadre déroulé. */
  /* ⚠️ `--hauteur-calc` SEUL. Il vaut la hauteur RÉELLE du cadre, déroulé ou
     non : il porte donc déjà la croissance, et lui ajouter
     `--depassement-pile` comptait celle-ci deux fois. Mesuré à 1512×850, le
     cumul faisait empirer les trois défauts avec le déroulé — 240 px de vide
     replié contre 440 la liste dépliée, et 140 px de bande claire contre 487. */
  /* ⚠️ L'ENVELOPPE CONTIENT LA COURSE DU CALCULATEUR, INTÉGRALEMENT.

     Les deux hauteurs étaient calculées séparément et ont fini par diverger :
     mesuré à 1512×850, la section du calculateur descendait jusqu'à 982 px
     alors que l'enveloppe s'arrêtait à 767. Le calculateur débordait donc de
     215 px hors de son conteneur, et la section suivante — « Try it
     yourself » — commençait en plein dedans.

     Karl : « la fenêtre Try it yourself commence à glisser au-dessus du
     calculateur jusqu'à ce qu'elle atteigne le top du calculateur et
     soudainement elle disparaît. » Ce qu'il voyait : l'invite défilait
     normalement pendant que le calculateur était collé, donc elle lui passait
     devant ; puis le pliage la renvoyait d'un coup en bas de l'écran.

     L'enveloppe reprend donc EXACTEMENT les mêmes termes que
     `.calculateur-course`, plus les trois blocs qui la précèdent. Toute
     modification de l'une doit être répercutée sur l'autre — d'où la
     duplication assumée de la formule, faute de pouvoir référencer une
     hauteur calculée en CSS. */
  /* ⚠️ `--hauteur-calc` A ÉTÉ RETIRÉ : il n'est plus posé nulle part et valait
     donc la chaîne vide, ce qui faussait tout le calcul. La hauteur du cadre
     est portée par `--hauteur-cadre`, qui le remplace.

     L'enveloppe vaut EXACTEMENT : les trois blocs qui défilent, plus la course
     du calculateur — les mêmes termes que `.calculateur-course`, ni plus ni
     moins. Toute hauteur en trop ici devient du défilement pendant lequel rien
     ne bouge à l'écran : mesuré à 1512×850, 2 930 px de course morte quand
     l'enveloppe dépassait sa pile de 300 px. */
  /* ⚠️ LE `- 3 * var(--onglet)` DE LA COURSE NE SE REPORTE PAS ICI.

     Dans `.calculateur-course`, cette soustraction représente la butée : la
     feuille se colle à 186 px du haut, donc son trajet utile est d'autant plus
     court. Mais l'enveloppe, elle, contient la pile ENTIÈRE, et cette butée
     est justement l'espace qu'occupent les trois onglets au-dessus du
     calculateur. La retirer laissait l'enveloppe 186 px plus courte que sa
     pile. */
  height: calc(3 * (100svh - var(--onglet))
               + 100svh - 3 * var(--onglet) + var(--trajet, 0px)
               + var(--pied, 0px) + 6 * var(--onglet));
}

/* **L'enveloppe NE CHANGE PAS de hauteur au pliage**, et c'est un arbitrage,
   pas un oubli.

   La réduire supprimerait le temps mort de la remontée — mesuré, un palier de
   525 px, ramené à 175 px avec une enveloppe plus courte encore. Mais toute
   réduction raccourcit le document AU-DESSUS du point de lecture, et la page
   bondit alors de plus de 1 600 px au moment du pliage : c'est très exactement
   la coupure que Karl a signalée (« d'un coup ça a coupé et je suis revenu en
   haut de la page »).

   Entre une pause de défilement et une coupure, la pause est de loin le moindre
   mal : elle se remarque à peine, alors que la coupure rend la page
   inutilisable. On garde donc la hauteur constante.

   Deux valeurs intermédiaires ont été mesurées, et TOUTES DEUX coupent :
   `200svh − 3 bandes` fait bondir la page de 1 302 px, et `100svh + 4 bandes`
   de 1 658 px. Il n'existe pas de réglage moyen — dès que l'enveloppe
   raccourcit, la coupure apparaît. La hauteur reste donc constante.

   La réduction est donc faite, MAIS accompagnée d'une compensation de
   défilement dans `pile.js` : celle-ci retranche à la position exactement ce
   que le document perd, dans la même image, si bien que la vue ne bouge pas.
   C'est cette combinaison — enveloppe réduite ET compensation — qui supprime
   le temps mort sans ramener la coupure. */
/* ⚠️ UNE HAUTEUR MINIMALE, PAS UNE HAUTEUR FIXE.

   La règle imposait `height: calc(100svh + 4 * var(--onglet))` — une valeur
   écrite d'avance, qui ignore le calculateur alors que celui-ci grandit au
   déroulé et au dépliage de la liste. La pile débordait donc son enveloppe :
   mesuré à 1512×850, 1192 px de pile pour 1098 px d'enveloppe. La section
   suivante (`#demo`, sur fond clair) remontait dans le calculateur, et selon
   l'état on voyait soit une barre beige, soit le calculateur passer sous le
   bloc « Try it yourself ».

   `min-height` garde le trajet voulu — la pile dure au moins une hauteur
   d'écran plus les quatre onglets, ce qui laisse le temps de la remontée
   solidaire — mais la laisse s'allonger quand son contenu l'exige.

   ⚠️ NE PAS remplacer par une hauteur mesurée en JavaScript : cela a été
   essayé le 28 août et produisait un saut pendant le défilement, la valeur
   étant recalculée en pleine descente. Voir la note dans `plier()` de
   `pile.js`. */
.pile-plies .pile-enveloppe {
  /* ⚠️ LA PILE DURE JUSQU'À CE QUE LE CALCULATEUR SOIT SORTI DE L'ÉCRAN.

     Contenir la pile ne suffit pas. Quand l'enveloppe s'arrête au ras de son
     contenu, la section suivante (`#demo`, sur fond clair) commence pendant
     que le calculateur est ENCORE à l'écran : on voit alors une bande claire
     sous lui, d'autant plus haute que le calculateur est déroulé. Mesuré à
     1512×850 : 162 px à l'état replié, 330 px déroulé, 462 px la liste
     dépliée. C'est la « grosse barre beige entre le bloc du calculateur et le
     bloc Try it yourself » signalée par Karl, et son « le calculateur passe en
     dessous du bloc Try it yourself » quand elle devenait insuffisante.

     On ajoute donc une hauteur d'écran : la pile se poursuit le temps que le
     calculateur en sorte par le haut. `--depassement-pile`, posé par
     `estime.js`, suit déjà la croissance du cadre ; c'est la seule variable et
     elle est écrite une fois, pas pendant le défilement. */
  /* ⚠️ L'ENVELOPPE NE RÉTRÉCIT PAS AU PLIAGE.

     Elle passait de quatre hauteurs d'écran à une seule — c'était voulu, mais
     c'est la source du saut. Mesuré à 1512×850, calculateur déroulé : le
     document perdait 1 266 px d'un coup et le navigateur ramenait la page de
     4908 à 3317. Karl : « il y a un petit saut, une petite coupure pendant
     qu'on descend sur le calculateur et il repart en haut de la page, et il
     faut recommencer la descente. »

     Aucune compensation ne rattrape proprement un document qui raccourcit sous
     le point de lecture : elles ont été essayées (sur la hauteur perdue, puis
     sur la position du cadre, avec lecture forcée) et le saut demeurait. La
     seule solution robuste est de ne pas raccourcir.

     La hauteur est donc la MÊME qu'avant le pliage, plus une hauteur d'écran
     pour que le calculateur ait le temps de sortir avant que la section
     suivante n'apparaisse. Le pliage continue de faire son travail — rendre
     les trois onglets solidaires — sans toucher à la longueur du document. */
  /* ⚠️ EXACTEMENT LA MÊME HAUTEUR QU'AVANT LE PLIAGE.

     C'est ce qui supprime le saut : si le document ne change pas de longueur,
     le navigateur n'a rien à recadrer et la page ne bouge pas d'un pixel.
     Trois compensations JavaScript ont été essayées avant d'en arriver là, et
     aucune ne rattrapait proprement un document qui raccourcit sous le point
     de lecture. Voir la note de la règle non pliée pour le détail du calcul. */
  /* ⚠️ + LA HAUTEUR DU CALCULATEUR, pour qu'il SORTE avant que la section
     suivante n'apparaisse. Une hauteur d'écran seule le fait quitter l'écran
     par le haut, mais la pile finit alors au ras de son bas : mesuré à
     1512×850, le cadre finissait à 63 et `#demo` commençait à 203 — 140 px de
     bande claire sous le calculateur, la « barre beige » de Karl.
     `--depassement-pile` porte la croissance du cadre déroulé. */
  /* ⚠️ `--hauteur-calc` SEUL. Il vaut la hauteur RÉELLE du cadre, déroulé ou
     non : il porte donc déjà la croissance, et lui ajouter
     `--depassement-pile` comptait celle-ci deux fois. Mesuré à 1512×850, le
     cumul faisait empirer les trois défauts avec le déroulé — 240 px de vide
     replié contre 440 la liste dépliée, et 140 px de bande claire contre 487. */
  /* ⚠️ L'ENVELOPPE CONTIENT LA COURSE DU CALCULATEUR, INTÉGRALEMENT.

     Les deux hauteurs étaient calculées séparément et ont fini par diverger :
     mesuré à 1512×850, la section du calculateur descendait jusqu'à 982 px
     alors que l'enveloppe s'arrêtait à 767. Le calculateur débordait donc de
     215 px hors de son conteneur, et la section suivante — « Try it
     yourself » — commençait en plein dedans.

     Karl : « la fenêtre Try it yourself commence à glisser au-dessus du
     calculateur jusqu'à ce qu'elle atteigne le top du calculateur et
     soudainement elle disparaît. » Ce qu'il voyait : l'invite défilait
     normalement pendant que le calculateur était collé, donc elle lui passait
     devant ; puis le pliage la renvoyait d'un coup en bas de l'écran.

     L'enveloppe reprend donc EXACTEMENT les mêmes termes que
     `.calculateur-course`, plus les trois blocs qui la précèdent. Toute
     modification de l'une doit être répercutée sur l'autre — d'où la
     duplication assumée de la formule, faute de pouvoir référencer une
     hauteur calculée en CSS. */
  /* ⚠️ `--hauteur-calc` A ÉTÉ RETIRÉ : il n'est plus posé nulle part et valait
     donc la chaîne vide, ce qui faussait tout le calcul. La hauteur du cadre
     est portée par `--hauteur-cadre`, qui le remplace.

     L'enveloppe vaut EXACTEMENT : les trois blocs qui défilent, plus la course
     du calculateur — les mêmes termes que `.calculateur-course`, ni plus ni
     moins. Toute hauteur en trop ici devient du défilement pendant lequel rien
     ne bouge à l'écran : mesuré à 1512×850, 2 930 px de course morte quand
     l'enveloppe dépassait sa pile de 300 px. */
  /* ⚠️ LE `- 3 * var(--onglet)` DE LA COURSE NE SE REPORTE PAS ICI.

     Dans `.calculateur-course`, cette soustraction représente la butée : la
     feuille se colle à 186 px du haut, donc son trajet utile est d'autant plus
     court. Mais l'enveloppe, elle, contient la pile ENTIÈRE, et cette butée
     est justement l'espace qu'occupent les trois onglets au-dessus du
     calculateur. La retirer laissait l'enveloppe 186 px plus courte que sa
     pile. */
  height: calc(3 * (100svh - var(--onglet))
               + 100svh - 3 * var(--onglet) + var(--trajet, 0px)
               + var(--pied, 0px) + 6 * var(--onglet));
}

/* ⚠️ LA PILE PLIÉE SE POSE À LA FIN DE SON ENVELOPPE.

   L'enveloppe garde sa pleine hauteur au pliage — c'est délibéré, et c'est ce
   qui empêche le document de raccourcir sous le point de lecture (voir sa
   note). Mais la pile pliée, elle, ne mesure plus que trois bandes plus le
   calculateur : mesuré à 1512×850, 960 px dans une enveloppe de 3698.

   Avec `top: 0`, elle restait collée en haut pendant les 2 738 px de
   différence. Karl : « je remonte l'onglet jaune... et là il y a une coupure.
   Au-dessus de l'onglet du calculateur, il y a une barre blanche et les trois
   onglets de couleur. » En redescendant comme en remontant, on parcourait des
   milliers de pixels sans que rien ne bouge, les trois bandes figées en haut
   de l'écran.

   `margin-top: auto` dans une enveloppe en `flex` colonne pousse la pile à la
   FIN de son conteneur. Le vide se retrouve alors AVANT elle, là où il est
   parcouru pendant l'approche — donc invisible — au lieu d'être après, où il
   se lisait comme un blocage.

   ⚠️ Ne pas remplacer par une réduction de la hauteur de l'enveloppe : cela
   raccourcit le document au moment du pliage et rejoue le saut, ce qui a été
   mesuré à 1 611 px. */
.pile-plies .pile {
  position: sticky;
  top: 0;

  /* Pas de hauteur imposée : elle vient de son contenu — trois bandes plus le
     calculateur. Lui en donner une l'étirait, et les trois bandes se
     partageaient l'espace en trois parts de 198 px au lieu de faire 52. */
  height: auto;
}

/* Les feuilles perdent leur collage : c'est le conteneur qui colle désormais.
   Leur position dans la pile vient de leur `top`, qui devient un simple
   décalage relatif — donc exactement le même étagement qu'avant. */
/* ⚠️ LE CALCULATEUR RESTE COLLANT APRÈS LE PLIAGE.

   Les trois blocs perdent leur collage — c'est le conteneur qui colle
   désormais, et leur `top` devient un simple décalage relatif. Mais la section
   du calculateur, elle, doit le GARDER.

   En la passant en `position: relative`, son haut retrouvait d'un coup sa
   place dans le flux : mesuré à 1512×850, elle était à -133 px, déjà engagée
   vers le haut, et le pliage la ramenait à +186. Le cadre bondissait de 128 à
   446 — le « petit saut, une petite coupure pendant qu'on descend sur le
   calculateur » signalé par Karl le 28 août 2026.

   Ce n'était pas un saut de défilement : aucune ligne du site ne déplace la
   page à cet instant, vérifié en instrumentant `scrollTo` et `scrollBy`.
   C'était un saut de mise en page, et aucune compensation JavaScript ne le
   rattrapait — le pliage a lieu dans un observateur de défilement, où toute
   correction déclenche une nouvelle passe qui l'annule.

   En gardant le collage, il n'y a plus rien à rattraper : la position du
   calculateur ne change pas au moment du pliage. */
/* ⚠️ LE TASSEMENT SURVIT AU PLIAGE.

   `top: auto` rend les blocs au flux — c'est nécessaire, le conteneur colle
   désormais à leur place. Mais cela annulait du même coup le tassement
   accumulé (`--remontee`) : mesuré à 1512×850, les trois onglets reculaient de
   −41/21/83 à 0/62/124 et le calculateur de 169 à 186, d'un seul coup, à
   l'instant du pliage. Karl : « au moment où il se colle, il y a un genre de
   glitch. »

   La pile est donc remontée d'autant par une translation, qui ne dépend pas du
   flux et survit au changement de positionnement. La transition la rend aussi
   douce que le tassement lui-même. */
.pile-plies .pile .bloc {
  position: relative;
  top: auto;
}
/* ⚠️ PAS DE `transform` SUR LA PILE : il casse le `position: sticky` de ses
   enfants — essayé, sans effet visible, le navigateur l'ignorant ou annulant
   le collage. Le tassement est repris par une marge négative sur la pile
   elle-même, qui ne crée pas de contexte de superposition. */
/* ⚠️ NI `transform` NI `margin-top` : c'est le POINT DE COLLAGE qui recule.

   Les deux ont été essayés sans effet — le `transform` casse le
   `position: sticky` des enfants, et la marge est absorbée par le collage de
   la pile elle-même.

   La pile pliée colle à `top: 0`. On lui donne la même remontée que les
   feuilles avaient avant le pliage : le tassement acquis est ainsi conservé,
   et rien ne recule à l'instant de la bascule. */
.pile-plies .pile {
  top: calc(-1 * var(--remontee, 0) * var(--onglet) / 3);
  transition: top .5s cubic-bezier(.33, 0, .2, 1);
}

/* Chaque bande est posée à la suite de la précédente : la hauteur d'un bloc
   plié EST sa bande. C'est ce qui remplace l'étagement que le `top` assurait
   du temps où chacun collait pour son compte. */
.pile-plies .pile .bloc:not(.est-ouvert),
.pile-plies .pile .bloc.bloc-window:not(.est-ouvert),
.pile-plies .pile .bloc.bloc-portal:not(.est-ouvert),
.pile-plies .pile .bloc.bloc-network:not(.est-ouvert) {
  height: var(--onglet);
  min-height: 0;
  /* **Le padding doit être annulé**, sinon il fixe un plancher que la hauteur
     ne peut pas franchir. Les blocs portent `padding: 88px 0 110px` en
     `box-sizing: border-box` : leur boîte ne descendait donc jamais sous
     198 px, et les trois bandes gardaient cette épaisseur au lieu des 52 px
     demandés — même avec `!important`, ce qui a longtemps rendu le défaut
     incompréhensible.

     Le contenu du bloc mesure encore une hauteur d'écran ; `overflow: hidden`
     l'empêche de repousser la boîte. Le `clip-path` ne suffit pas : il découpe
     le RENDU, il ne contraint pas la mise en page. */
  /* **Le contenu garde sa place, mais SANS padding.** Le padding haut posait
     bien le contenu à sa place définitive — il ne bougeait plus d'un pixel
     pendant l'animation — mais il fixait du même coup un plancher de hauteur :
     le bloc plié faisait 88 px au lieu de 52.

     La position est donc donnée au CONTENU lui-même (voir la règle qui suit),
     et plus à la boîte. Celle-ci peut alors descendre jusqu'à la bande, et le
     contenu reste ancré là où il doit être — « comme s'il était imprimé
     dedans ». */
  padding: 0;
  /* **`overflow: clip` et non `hidden`.** Les deux empêchent le contenu de
     repousser la boîte, mais `hidden` rogne aussi le pseudo-élément du calage
     de couleur, qui déborde volontairement dans les échancrures des coins :
     celles-ci redevenaient beiges, et la pile perdait son aspect de feuilles
     glissées les unes sur les autres. `clip` avec `overflow-clip-margin`
     laisse ce débordement précis passer. */
  overflow: clip;
  overflow-clip-margin: var(--rayon-feuille);
}

/* Le calculateur occupe tout ce qui reste sous les trois bandes. */
.pile-plies .calculateur-course {
  /* Le dépassement du calculateur déroulé s'ajoute ici aussi : sans lui, la
     course garde sa taille d'écran pendant que la section en fait le double,
     et le calculateur passe sous le bloc suivant. Voir la règle de base de
     `.calculateur-course`. */
  /* ⚠️ `--trajet`, PAS `--depassement` : ce dernier n'est plus tenu à jour.

     La course pliée gardait donc une longueur figée pendant que le calculateur
     grandissait. Résultat : à l'ouverture, tout ce qui suit restait en place
     puis était poussé d'un coup à la fin de l'animation. Karl : « le
     calculateur se développe d'un coup et ensuite le bloc du bas est poussé
     vers le bas. » */
  height: calc(100svh - 3 * var(--onglet) + var(--trajet, 0px));
  margin-top: 0;
}

/* **LA COURSE DU CALCULATEUR SE PLIE AUSSI.**

   Les trois blocs reçoivent ci-dessus une marge négative qui ramène leur
   avance dans le flux à une bande : mesuré, leurs boîtes se terminent alors
   à 195, 247 et 299 px. Mais `.calculateur-course`, elle, gardait sa pleine
   hauteur et s'étendait jusqu'à 920 px — c'est donc ELLE qui fixait la fin du
   conteneur `.pile`.

   Conséquence : les trois blocs, dont la boîte finit à 299, restaient collés
   jusqu'à 920, soit 621 px de plus. Pendant tout ce temps, les onglets étaient
   figés en haut de l'écran pendant que le calculateur, lui, montait par-dessus
   eux. Karl : « le calculateur embarque par-dessus les onglets. Ce serait
   mieux que tout reste collé ensemble, on fait juste descendre en bas, c'est
   tout qui bouge ensemble et disparaît dans le haut de la page. »

   Une fois la pile pliée, la course n'a plus de raison d'être : le calculateur
   a fini sa montée, et ce qui reste à faire est de sortir. Elle se réduit donc
   à la hauteur d'une bande, comme les blocs, et les quatre feuilles se
   décollent au même pixel.

   Attention au piège qui a coûté un essai : une marge négative calculée sur
   `100svh` ne convient pas ici, parce que `--onglet` change de valeur dans les
   paliers d'écran court et que le résultat se retrouve écrêté à zéro. On pose
   directement la hauteur, qui ne dépend d'aucune soustraction. */
.pile-plies .calculateur-course {
  /* **La course vaut EXACTEMENT la boîte du calculateur, une fois plié.**

     Tant qu'elle est plus longue, le calculateur continue de monter après
     avoir atteint sa butée : il s'enroule alors par-dessus les trois onglets,
     qui restent figés en haut de l'écran. Mesuré, il passait de 156 à −210
     pendant que les bandes ne bougeaient pas. Karl : « je ne veux pas que le
     bloc du calculateur s'enroule sur le reste des onglets quand on défile
     vers le bas. Je veux que les trois onglets restent collés après le haut du
     bloc du calculateur. »

     En alignant la course sur la boîte, le calculateur s'arrête net à sa
     butée : les quatre feuilles forment alors un ensemble solidaire, et la
     descente les emporte toutes ensemble.

     Il ne faut PAS la raccourcir en deçà : la fin de la pile remonterait
     au-dessus du point de lecture et tout le contenu glisserait de plus de
     2 000 px sous les yeux du lecteur — la « grosse coupure » signalée par
     Karl, qu'aucune compensation de défilement ne rattrape proprement. */
  /* Le dépassement du calculateur déroulé s'ajoute ici aussi.

     ⚠️ ET LE RECUL AVEC LUI. Le recul (voir `.estime-deborde`) ne joue
     qu'une fois la pile pliée, et c'est PENDANT cette course qu'il doit se
     parcourir : c'est le trajet qui amène le bas du cadre dans l'écran. Sans
     ces pixels, la pile repart avant que le recul ait produit son effet, et
     le bas redevient inatteignable — les deux exigences de Karl (« le bas
     toujours visible » et « rien qui réapparaisse sous le calculateur ») se
     contredisent alors, alors qu'elles tiennent ensemble ici. */
  /* ⚠️ `--trajet`, PAS `--depassement` : ce dernier n'est plus tenu à jour.

     La course pliée gardait donc une longueur figée pendant que le calculateur
     grandissait. Résultat : à l'ouverture, tout ce qui suit restait en place
     puis était poussé d'un coup à la fin de l'animation. Karl : « le
     calculateur se développe d'un coup et ensuite le bloc du bas est poussé
     vers le bas. » */
  height: calc(100svh - 3 * var(--onglet) + var(--trajet, 0px));
}

/* **Le contenu est IMPRIMÉ dans le bloc.** Karl : « je veux que tous les
   éléments et le texte soient vraiment incrustés dans le bloc. L'information ne
   bouge pas dans l'onglet, c'est vraiment comme si elle était imprimée dedans.
   Parce que là il y a des bugs, il y a des trucs qui apparaissent un peu
   n'importe où, un peu par-dessus l'autre. Et lorsqu'on ferme l'onglet, les
   écrits, pouf, ils disparaissent. »

   Deux défauts venaient de la même cause : l'opacité était une bascule
   INSTANTANÉE pendant que la hauteur, elle, glissait sur 720 ms.

   — à l'ouverture, l'opacité passait à 1 dès 80 ms alors que le contenu se
     trouvait encore à −69 px, au-dessus d'un bloc haut de 57 px : il
     apparaissait hors de sa boîte avant de glisser à sa place ;
   — à la fermeture, elle tombait à 0 dès 80 ms alors que le bloc faisait
     encore 689 px : le texte s'évanouissait d'un coup — le « pouf ».

   L'opacité ne sert donc plus à rien pendant l'animation : la boîte du bloc
   est bornée par `overflow: clip`, elle rogne déjà tout ce qui dépasse, et de
   façon CONTINUE puisqu'elle suit la hauteur. Le contenu reste simplement
   ancré en haut du bloc et se dévoile au fur et à mesure que celui-ci
   s'ouvre — comme une image imprimée qu'on découvre en déroulant la feuille.

   `visibility` reste posée sur le bloc REFERMÉ, une fois l'animation finie :
   un contenu rogné demeure sinon focalisable au clavier et lisible par les
   lecteurs d'écran. Elle ne s'anime pas, mais à ce stade la hauteur ne bouge
   plus, donc la bascule est invisible. */
.pile-plies .bloc:not(.est-ouvert):not(.est-ferme) > .enveloppe,
.pile-plies .bloc:not(.est-ouvert):not(.est-ferme) > .bloc-mot {
  pointer-events: none;
}

/* **Le mot géant est FIGÉ à sa place, comme le reste du contenu.**

   Karl : « plutôt que le gros mot Window, Portal ou Network glisse de haut en
   bas comme ça, il devrait juste avoir un fondu et il disparaît, et il
   réapparaît à sa position finale. Il est déjà là. Donc il n'y a rien qui
   bouge, absolument aucun mot ne bouge. Même chose avec le téléphone, avec la
   vidéo dans Portal : elle est par-dessus le mot Portal et Portal passe en
   dessous. Je ne veux pas que ça se passe. Tout est figé en place. »

   Le mot était ancré par `bottom: 10px`. Comme la hauteur du bloc s'anime, ce
   bas se déplace en continu et le mot suivait le bord inférieur : mesuré, il
   glissait de 646 px dans le document pendant l'ouverture, et croisait le
   téléphone au passage — d'où l'impression que l'un passe sous l'autre. Le
   téléphone, lui, ne bougeait déjà pas (6 px).

   On l'ancre donc par le HAUT, à la position qu'il occupe une fois le bloc
   ouvert. Il ne dépend plus de la hauteur courante, donc il ne bouge plus du
   tout : le bloc s'ouvre autour de lui.

   La valeur est celle du bloc déroulé moins la place du mot et sa marge basse
   d'origine — soit `100svh − 1.5 bande − 10px`, moins sa propre hauteur, que
   `translateY(-100%)` retranche sans qu'on ait à la connaître. */
.pile-plies .pile .bloc > .bloc-mot {
  top: calc(100svh - 1.5 * var(--onglet) - 10px);
  bottom: auto;
  transform: translateY(-100%);
}

/* **Le contenu est ancré au bloc, pas à sa boîte.** En `position: absolute`,
   il ne participe plus au calcul de hauteur : le bloc peut se réduire à sa
   bande sans le déplacer, et il reste exactement là où il était — le bloc
   s'ouvre et se referme autour de lui.

   Les valeurs reprennent le rembourrage d'origine (`88px` en haut) pour que la
   position soit rigoureusement la même qu'au premier déroulement, quand le
   bloc est une section ordinaire. */
.pile-plies .pile .bloc > .enveloppe {
  position: absolute;
  top: 88px;
  left: 0;
  right: 0;
}

/* Hors accordéon — donc au repos, l'animation terminée — le contenu d'un bloc
   plié est retiré du parcours clavier et des lecteurs d'écran. */
:root:not(.pile-accordeon) .pile-plies .bloc:not(.est-ouvert) > .enveloppe,
:root:not(.pile-accordeon) .pile-plies .bloc:not(.est-ouvert) > .bloc-mot {
  visibility: hidden;
}

/* Un bloc qu'on DÉROULE d'un clic reprend sa pleine hauteur visible : le
   découpage saute, le contenu revient. C'est la seule façon de revoir un bloc
   de couleur — Karl : « si on veut revoir les onglets de couleur, la seule
   façon possible, c'est en cliquant sur un, il se déroule. » */
.pile-plies .bloc.est-ouvert {
  clip-path: none;
  /* Le bloc déroulé reprend la pleine hauteur et le rembourrage que le pliage
     lui avait retirés : sans cela il restait haut d'une bande, et le clic
     n'ouvrait rien de visible.

     **La hauteur est un compromis assumé.** Karl : « le premier déroulement
     des blocs de couleur nous donne beaucoup plus d'espace que lorsqu'on ouvre
     la deuxième fois au clic. J'aimerais qu'on ait toujours le même espace
     partout. »

     Les deux situations ne peuvent pas être strictement identiques : au
     défilement, le bloc occupe l'écran entier (800 px sur un écran de 800) ;
     au clic, il doit laisser voir les trois bandes de la pile, donc
     `100svh − 3 × 52`, soit 644 px. Aligner l'un sur l'autre casserait soit la
     pile, soit le plein écran.

     On retire donc une bande et demie au lieu de trois : le bloc ouvert gagne
     78 px et passe à 722 px, à mi-chemin des 800 px du premier déroulement.
     Les bandes voisines restent visibles — elles se chevauchent simplement un
     peu plus, ce qui ne change rien à leur lisibilité puisque seul leur titre
     y figure. */
  height: calc(100svh - 1.5 * var(--onglet));
  padding: 88px 0 110px;

  /* **Le rognage est CONSERVÉ pendant l'animation, et libéré seulement
     après.** C'est ce qui répare le défaut que Karl décrivait : « à partir du
     moment où on commence l'animation de déroulement, tous les éléments
     apparaissent par-dessus tout, donc il n'y a rien qui est révélé. »

     La cause était un `overflow: visible` posé ici sans condition. Il avait
     été ajouté pour laisser le téléphone du bloc Portal déborder de son cadre,
     mais il supprimait du même coup le rognage qui devait dévoiler le contenu
     au fur et à mesure que le bloc grandit. Le contenu n'étant plus borné par
     rien, il s'affichait en entier dès la première image de l'animation.

     `overflow: clip` avec une marge laisse le téléphone dépasser SANS rendre
     le rognage inopérant : la boîte rogne toujours, simplement un peu plus
     loin que son bord. Le contenu se dévoile donc bien avec la hauteur, et le
     téléphone garde sa place. */
  overflow: clip;
  /* **Aucune marge de rognage PENDANT l'animation.** Une marge de 72 px avait
     été posée ici pour le téléphone, mais elle laissait fuir tout le contenu
     du bloc sur la même épaisseur : au premier dixième de seconde, le titre et
     l'image dépassaient sous l'onglet suivant. La marge n'est donc ouverte
     qu'une fois l'animation terminée, dans la règle ci-dessous. */
  overflow-clip-margin: 0;
  /* **Pas de marge compensatoire :** le bloc ouvert occupe RÉELLEMENT sa
     hauteur dans le flux, et ce qui le suit descend d'autant. C'est ce qui
     donne la continuité — les onglets restants se rangent juste sous lui, et
     le calculateur derrière eux.

     Une marge inférieure négative avait été posée ici pour que le bloc ne
     déborde pas vers le haut. Elle annulait sa croissance dans le flux : les
     onglets suivants restaient collés à 0 et 52 px, donc DERRIÈRE le bloc
     déroulé, et il ne restait rien de visible sous lui. */
}
/* Une fois l'animation FINIE (la classe `pile-accordeon` est retirée au bout
   de 760 ms), le rognage est levé complètement : le téléphone du bloc Portal
   déborde alors sans aucune borne. Pendant l'animation, la règle ci-dessus
   garde le rognage — c'est lui qui révèle le contenu progressivement. */
:root:not(.pile-accordeon) .pile-plies .bloc.est-ouvert {
  overflow: visible;
}
/* Le bloc Portal garde son rognage même au repos, avec une marge juste
   suffisante pour le téléphone : sans borne du tout, l'image et le téléphone
   pourraient dépasser sur les onglets voisins. */
:root:not(.pile-accordeon) .pile-plies .bloc-portal.est-ouvert {
  overflow: clip;
  overflow-clip-margin: 72px;
}

/* **Le contenu d'un bloc ouvert est PLEINEMENT opaque.** Une correction de
   l'opacité du mot géant avait été écrite dans une règle qui visait AUSSI
   l'enveloppe : le `.10` destiné au filigrane s'appliquait donc à tout le
   contenu, titre et texte compris, et le bloc déroulé paraissait délavé.
   Karl : « lorsqu'on clique sur un onglet et qu'il se déroule, son opacité est
   vraiment très basse. »

   Les deux sont désormais séparés — ils n'ont rien à voir : l'un est du texte à
   lire, l'autre un filigrane décoratif. */
.pile-plies .bloc.est-ouvert > .enveloppe {
  opacity: 1;
  pointer-events: auto;
}

/* Le mot déroulé retrouve son opacité de FILIGRANE — celle qu'il a partout
   ailleurs sur le site (.10, et .13 sur le jaune). Il valait 1, ce qui en
   faisait un aplat blanc opaque au lieu d'un mot imprimé dans la couleur. */
.pile-plies .bloc.est-ouvert > .bloc-mot {
  opacity: .10;
  pointer-events: none;
}
.pile-plies .bloc-network.est-ouvert > .bloc-mot { opacity: .13; }

/* **Le fondu, à la place du glissement.** Karl : « il devrait juste avoir un
   fondu, il disparaît et il réapparaît à sa position finale. » Le mot ne
   bougeant plus, l'opacité est le seul changement qui reste — et elle
   s'anime, comme la hauteur du bloc. */
.pile-accordeon .pile .bloc > .bloc-mot {
  transition: opacity .72s cubic-bezier(.33,0,.2,1);
}

/* --- L'accordéon : le clic déroule un bloc -------------------------------
   Les onglets restent fermés d'eux-mêmes — un bloc recouvert par le suivant ne
   peut pas s'ouvrir tout seul. Le clic est donc le seul moyen d'en dérouler
   un, et un seul à la fois. C'est `pile.js` qui pose `est-ouvert` et
   `est-ferme`.

   **Rien ici ne touche à la HAUTEUR des blocs, et c'est essentiel.** La
   première version donnait au bloc ouvert une hauteur d'écran : cela
   raccourcissait la pile, donc le document, et le navigateur rattrapait le
   défilement d'un bond — mesuré, la page sautait de 325 px au moment du clic,
   ce qui déclenchait aussitôt la fermeture par défilement. Le bloc s'ouvrait
   et se refermait dans la même image.

   Un bloc n'a de toute façon pas besoin de grandir pour s'ouvrir : il occupe
   déjà toute la hauteur sous son onglet, il est simplement recouvert par les
   suivants. L'ouvrir revient donc à le faire passer DEVANT, et à envoyer les
   onglets qui le suivent se ranger en bas de l'écran.

   Ce déplacement se fait en `transform`, qui ne touche pas au flux : la
   hauteur du document reste rigoureusement la même, et aucun saut n'est
   possible. */
.bloc.est-ouvert {
  z-index: 20;
}

/* Les blocs qui SUIVENT celui qu'on a ouvert descendent d'un bloc, ce qui pose
   leur bande en bas de l'écran, les unes sous les autres.

   La distance est la même pour tous — `100vh − 3 × --onglet` — parce qu'ils
   sont déjà espacés de 62 px les uns des autres et conservent cet écart. */
.bloc.est-ouvert ~ .bloc.est-ferme {
  /* **Le décalage tient compte du RANG, et c'est indispensable.**

     Ces blocs sont collés à des `top` différents (0, puis une bande, puis
     deux). Une translation IDENTIQUE pour tous conserve cet écart tant que la
     pile est immobile — mais dès qu'on défile, le bloc ouvert se décolle et
     remonte pendant que les autres restent collés : les positions se croisent
     et les onglets finissent superposés. Mesuré : l'onglet violet et l'onglet
     jaune se retrouvaient tous deux à 485 px, l'un masquant complètement
     l'autre. Karl : « il y a juste un bout d'onglet jaune et l'onglet continue
     en bas en dessous. Même chose pour les deux autres onglets. »

     En retranchant le décalage de collage propre à chaque bloc, tous
     atterrissent au même point de départ en bas de l'écran, puis se réétagent
     de leur bande — l'écart est reconstruit explicitement au lieu d'être
     hérité d'un empilement qui bouge. */
  transform: translateY(calc(100svh - 3 * var(--onglet) + var(--rang) * var(--onglet)));
  z-index: 21;
}

/* **Une fois la pile PLIÉE, aucun décalage n'est nécessaire.**

   La translation ci-dessus date d'avant le pliage structurel : les blocs
   étaient alors collants chacun pour son compte, tous à `top: 0` ou presque,
   et il fallait pousser les fermés hors du chemin du bloc ouvert.

   Depuis que les quatre feuilles sont en `position: relative` dans une pile
   elle-même collante, elles se suivent naturellement dans le flux : le bloc
   ouvert grandit, et ce qui vient après descend d'autant, tout seul. La
   translation, elle, continuait de les pousser nnn'à près d'une hauteur d'écran
   — les onglets se retrouvaient à 756 et 860 px sur un écran de 860, donc hors
   du cadre, et il ne restait plus rien sous le bloc ouvert. Karl : « quand ils
   sont ouverts, il n'y a rien en bas, il y a juste la fenêtre de l'onglet de la
   couleur qu'on a ouvert, et en dessous il n'y a plus rien. Il faudrait
   poursuivre la continuité-là et que les trucs restent ensemble. »

   On l'annule donc : les onglets restants se rangent d'eux-mêmes juste sous le
   bloc déroulé, et la pile garde sa continuité. */
.pile-plies .bloc.est-ouvert ~ .bloc.est-ferme {
  transform: none;
}

/* **Un bloc ouvert ancre la pile en haut de l'écran.**

   En s'ouvrant, le bloc rend la pile plus haute que la fenêtre. Un élément
   collant plus grand que l'écran glisse alors vers le haut au lieu de rester
   posé : la pile remontait à −70 px, et l'onglet qu'on venait de cliquer
   sortait du cadre par le haut.

   `top: 0` avec une hauteur libre suffit à la maintenir : elle reste ancrée,
   le bloc ouvert se lit depuis son onglet, et ce qui le suit descend dans le
   flux. Le débordement sous l'écran se parcourt au défilement, comme n'importe
   quel contenu long. */
.pile-plies .pile:has(.bloc.est-ouvert) {
  position: relative;
  top: auto;
}

/* **L'enveloppe suit la pile quand un bloc est ouvert.** Elle porte une
   hauteur fixe — c'est elle qui réserve la course du défilement et qui évite
   la coupure au pliage. Mais un bloc déroulé rend la pile bien plus haute
   qu'elle : mesuré, la pile passait de 704 à 1486 px pendant que l'enveloppe
   restait à 704, si bien que la section suivante venait se poser PAR-DESSUS le
   calculateur — un chevauchement de 522 px.

   Tant qu'un bloc est ouvert, la hauteur est donc rendue au contenu : la pile
   occupe la place qu'il lui faut, et ce qui suit se range dessous. La course
   réservée n'a plus lieu d'être à ce moment-là, puisque la pile ne colle
   plus. */
.pile-plies:has(.bloc.est-ouvert) .pile-enveloppe {
  height: auto;
}

/* Pendant l'accordéon SEULEMENT, le déplacement s'anime. Le reste du temps la
   transition est nulle, pour que la pile suive le geste au pixel près : une
   transition permanente ferait traîner les blocs derrière le doigt. */
/* **Le découpage est levé pendant toute l'animation.**

   Le `clip-path` du pliage n'est pas interpolable : à la fermeture, il
   revenait à la bande DÈS LA PREMIÈRE IMAGE, alors que la hauteur, elle,
   s'animait encore. Mesuré : le bloc était découpé à 52 px quand il en faisait
   encore 578. On voyait donc une barre de couleur figée en haut, le reste du
   bloc disparu, et le fond de la page au travers. Karl : « lorsqu'on essaie de
   rouler à nouveau, tout le fond disparaît, et on voit du gris au travers. Il
   reste une barre de couleur au sommet, et en dessous il y a juste l'info. »

   Le découper n'est de toute façon plus nécessaire : depuis que la hauteur du
   bloc plié est réellement réduite et bornée par `overflow: clip`, c'est elle
   qui produit la bande. Le `clip-path` ne servait qu'à l'époque où la boîte
   gardait une hauteur d'écran. On le lève donc le temps de l'animation, et la
   hauteur seule fait le travail — de façon continue, elle. */
.pile-accordeon .pile .bloc {
  clip-path: none;
}

.pile-accordeon .bloc {
  /* **Le déroulage et le roulage s'animent.** Karl : « ajouter une animation
     de déroulage et de roulage des onglets colorés après qu'ils se soient
     assemblés. »

     Ce qui change à l'ouverture, c'est la HAUTEUR du bloc — d'une bande à une
     pleine feuille — et son rembourrage. Les deux valeurs sont calculées mais
     fixes (`52px` et `calc(100svh − 3 × 52px)`), donc interpolables : la
     transition suffit, sans aucune animation en JavaScript.

     `transform` reste dans la liste pour l'ancien mécanisme de mise à l'écart,
     encore actif tant que la pile n'est pas pliée.

     **La transition n'est ACTIVE QUE PENDANT L'ACCORDÉON**, posée puis retirée
     par `pile.js`. En permanence, elle ferait traîner les blocs derrière le
     doigt à chaque défilement et briserait la règle du geste 1:1. */
  /* **Durée et courbe : un glissement, pas un déclic.** Karl : « le
     déroulement est un peu trop rapide et un peu trop violent, j'aimerais que
     ce soit plus doux comme mouvement, un beau glissement. »

     720 ms au lieu de 420, et surtout une courbe qui démarre en douceur au
     lieu de partir sec. `cubic-bezier(.22,.61,.36,1)` attaque à pleine
     vitesse — c'est ce qui donnait le côté brusque ; `(.33,0,.2,1)` prend son
     élan puis s'arrête longuement, ce qui se lit comme une feuille qu'on tire
     plutôt que comme un panneau qui claque. */
  transition:
    height .72s cubic-bezier(.33,0,.2,1),
    padding .72s cubic-bezier(.33,0,.2,1),
    transform .72s cubic-bezier(.33,0,.2,1);
}

/* Le chevron de l'onglet : vers le bas au repos, vers le haut quand le bloc
   est déroulé. */
.bloc-onglet { cursor: pointer; }
.bloc-onglet::after {
  content: "";
  margin-left: auto;
  width: 8px; height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .3s ease;
}
.bloc.est-ouvert .bloc-onglet::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.pile:has(.bloc.est-ouvert) { z-index: 50; }

.section-entete { max-width: 60ch; margin-bottom: var(--espace-12); }
.section-entete.centre { margin-left: auto; margin-right: auto; text-align: center; }

.section-etiquette {
  font-size: var(--texte-xs); font-weight: var(--graisse-grasse);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--wpn-neutre-400); margin: 0 0 var(--espace-4);
}
.section h2 {
  font-family: var(--police-titre); font-weight: var(--graisse-noire);
  font-size: clamp(2.1rem, 4.8vw, 3.3rem); line-height: 1.04;
  letter-spacing: -0.035em; margin: 0 0 var(--espace-5);
}
.section-chapo { font-size: var(--texte-lg); color: var(--wpn-neutre-500); margin: 0; }

/* ==========================================================================
   L'invitation
   ========================================================================== */

/* **Le calculateur et l'invitation ne font qu'UN SEUL BLOC.**

   Karl : « après l'onglet jaune, il y a le bloc avec le calculateur qui
   embarque. J'aimerais que le fond ne soit plus gris et qu'il soit exactement
   le même noir foncé que le bloc en dessous. Et qu'en fait ces deux blocs-là,
   ça soit un bloc. […] Le calculateur, le bloc d'en dessous se roule un petit
   peu par-dessus et je ne veux pas ça. Je veux qu'ils soient bien collés
   ensemble. On va juste en faire un gros bloc avec deux fenêtres dedans, le
   calculateur et le bouton "try it yourself". »

   Trois choses à réunir pour que la couture disparaisse :

   1. le MÊME fond sur les deux — le noir de la marque, pas le gris clair ;
   2. AUCUN chevauchement : `.section` porte `margin-top: -34px` et un rayon
      haut, qui font justement « embarquer » chaque section sur la précédente.
      C'est la signature du reste du site, mais ici les deux moitiés doivent se
      lire comme une seule surface continue ;
   3. aucune ombre portée entre elles, sans quoi une ligne sombre trahit la
      jonction.

   Le calculateur garde son cadre : dans un bloc foncé, il devient l'une des
   deux fenêtres — l'autre étant l'invitation. */
.section-invite {
  /* Les jetons de l'instrument sont déclarés sur `.section-estime` : l'invite
     les reprend pour partager sa matière — voir la note de `.invite`. */
  --chassis:      #171B24;
  --chassis-haut: #1E232E;
  --creux:        #12151C;
  --arete-vive:   rgba(255,255,255,.13);
  --ombre-douce:  rgba(0,0,0,.45);

  background: var(--wpn-ancre);
  /* Collé au calculateur : ni remontée, ni coins, ni ombre. */
  margin-top: 0;
  border-radius: 0;
  box-shadow: none;
}

.invite {
  display: grid;
  grid-template-columns: 190px 1fr;
  gap: clamp(28px, 5vw, 60px);
  align-items: center;
  width: 100%;
  padding: clamp(30px, 4vw, 52px);
  /* ⚠️ LA MÊME MATIÈRE QUE LE CALCULATEUR.

     Karl : « j'aimerais que la fenêtre Try it yourself ressemble plus au
     calculateur en termes de design de couleur. Je la trouve un peu trop
     claire, et il y a un manque de dimension. »

     Elle était un simple voile blanc à 4,5 % sur fond sombre, bordé d'un trait
     clair : une surface plate, plus pâle que l'instrument qui la précède
     immédiatement. Elle reprend maintenant le châssis — dégradé du haut vers
     le creux, arête vive en haut, ombre portée dessous — de sorte que les deux
     objets se lisent comme taillés dans la même pièce. */
  background:
    linear-gradient(180deg, var(--chassis-haut) 0%, var(--chassis) 46%, var(--creux) 100%);
  border: 1px solid rgba(0,0,0,.55);
  border-radius: 28px;
  box-shadow:
    inset 0 1px 0 var(--arete-vive),
    inset 0 -1px 0 rgba(0,0,0,.45),
    0 1px 0 rgba(0,0,0,.6),
    0 22px 44px -18px var(--ombre-douce);
  color: #fff; text-align: left;
  font-family: var(--police-texte);
  cursor: pointer;
  transition: box-shadow .3s ease, border-color .3s ease, transform .3s cubic-bezier(.2,.8,.2,1);
}
.invite:hover {
  background: rgba(255,255,255,.075);
  border-color: rgba(232,178,60,.5);
  transform: translateY(-3px);
}
.invite:focus-visible { outline: 2px solid var(--wpn-jaune-400); outline-offset: 4px; }
@media (max-width: 780px) {
  .invite { grid-template-columns: 1fr; text-align: center; justify-items: center; }
}

/* Le logo, en grand, dans l'invitation. Deux carrés et leur intersection. */
.invite-decor {
  position: relative; display: block;
  width: 170px; height: 119px;   /* le viewBox du logo est en 200 × 140 */
  flex-shrink: 0;
}
.invite-decor svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* --- L'ÉCHANGE DES DEUX LOSANGES ------------------------------------------

   **C'est EXACTEMENT l'animation du logo de l'en-tête** (`.nav-marque`
   ci-dessus, et le même SVG dans la page d'attente). Karl, 27 août 2026 :
   « tu devrais te référer à l'animation du logo dans la landing page, celle
   qui est en tête de page où il y a la barre de menu. Animation in quand on
   met le curseur sur le bouton, animation out quand on l'enlève. »

   Au survol, le bleu passe à droite et le jaune à gauche : les deux formes se
   croisent, et le violet naît de leur recouvrement comme dans le logo. Au
   retrait du curseur, tout revient en sens inverse — c'est une `transition`,
   pas une `animation`, donc l'aller et le retour sont symétriques par
   construction, sans qu'on ait rien à écrire pour le retour.

   **CE QUI ÉTAIT LÀ AVANT, et qu'il ne faut pas réintroduire.** Deux versions
   ont échoué et Karl a corrigé les deux :

   1. Les deux losanges s'ÉCARTAIENT de 6 %, et le violet — un simple carré
      posé au centre — n'était visé par aucune transition. « Une vieille
      animation qui ne fonctionnait pas, avec le carré violet qui ne bouge
      pas. » Les écarter défait le logo au lieu de le construire.
   2. Une réécriture en `@keyframes` avec `translateZ` et un violet qu'on
      faisait rétrécir à la main. Elle croisait bien les losanges, mais le
      violet restait une forme approchée qu'il fallait animer séparément.

   La bonne réponse était de reprendre le SVG du header tel quel : le violet y
   est le losange JAUNE redessiné, DÉCOUPÉ par la silhouette du bleu. Il n'y a
   donc rien à animer sur lui — l'intersection se déforme d'elle-même.

   LA RÈGLE DU VIOLET, à ne pas oublier si l'on retouche ceci : il reçoit le
   décalage du JAUNE (c'est le jaune qu'il redessine), et sa découpe reçoit
   celui du BLEU (c'est la silhouette du bleu qui le taille). Les deux doivent
   donc bouger en sens contraire, exactement comme leurs formes d'origine.

   Les unités sont celles du viewBox (200 × 140), pas des pixels : `transform`
   sur un élément SVG s'exprime dans le repère local. Les centres des deux
   losanges sont à x=70 et x=130, soit 60 unités d'écart. */
.invite-bleu, .invite-jaune, .invite-violet, .invite-coupe {
  transition: transform .55s cubic-bezier(.65,0,.35,1);
}

.invite:hover .invite-bleu,
.invite:focus-visible .invite-bleu   { transform: translateX(60px); }
.invite:hover .invite-jaune,
.invite:focus-visible .invite-jaune  { transform: translateX(-60px); }
/* `.invite-coupe` porte le déplacement du BLEU : il emmène la silhouette de
   découpe avec lui. */
.invite:hover .invite-coupe,
.invite:focus-visible .invite-coupe  { transform: translateX(60px); }
/* `.invite-violet` porte celui du JAUNE, mais DANS le repère déjà décalé de
   +60 : il faut donc retrancher ce décalage, d'où −120 et non −60. */
.invite:hover .invite-violet,
.invite:focus-visible .invite-violet { transform: translateX(-120px); }

/* Un visiteur qui a demandé moins d'animation garde le logo, sans mouvement. */
@media (prefers-reduced-motion: reduce) {
  .invite-bleu, .invite-jaune, .invite-violet, .invite-coupe {
    transition: none;
  }
  .invite:hover .invite-bleu,   .invite:focus-visible .invite-bleu,
  .invite:hover .invite-jaune,  .invite:focus-visible .invite-jaune,
  .invite:hover .invite-coupe,  .invite:focus-visible .invite-coupe,
  .invite:hover .invite-violet, .invite:focus-visible .invite-violet {
    transform: none;
  }
}

.invite-texte { display: block; }
.invite-etiquette {
  display: block;
  font-size: var(--texte-xs); font-weight: var(--graisse-grasse);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--wpn-jaune-400); margin-bottom: var(--espace-3);
}
.invite-titre {
  display: block;
  font-family: var(--police-titre); font-weight: var(--graisse-noire);
  font-size: clamp(1.8rem, 4vw, 2.8rem); line-height: 1.05;
  letter-spacing: -.03em; margin-bottom: var(--espace-4);
}
.invite-sous {
  display: block; max-width: 54ch;
  color: var(--wpn-neutre-200); font-size: var(--texte-base);
  margin-bottom: var(--espace-6);
}
.invite-appel {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 26px; border-radius: var(--rayon-md);
  background: var(--wpn-jaune-400); color: var(--wpn-ancre);
  font-weight: var(--graisse-grasse); font-size: var(--texte-base);
}
.invite-appel em {
  font-style: normal;
  transition: transform .3s cubic-bezier(.2,.8,.2,1);
}
.invite:hover .invite-appel em { transform: translateX(5px); }

/* ==========================================================================
   Le banc d'essai — deux fenêtres côte à côte
   ========================================================================== */

.banc[hidden] { display: none; }
.banc {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(14px, 2.5vw, 34px);
}
.banc-fond {
  position: absolute; inset: 0;
  background: rgba(12, 14, 20, .82);
  backdrop-filter: blur(6px);
}

.banc-scene {
  position: relative;
  width: 100%; max-width: 1560px; height: 100%;
  display: flex; flex-direction: column; gap: var(--espace-5);
}

.banc-entete {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--espace-6); flex-wrap: wrap;
  color: #fff; flex-shrink: 0;
}
.banc-etiquette {
  font-size: var(--texte-xs); font-weight: var(--graisse-grasse);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--wpn-jaune-400); margin: 0 0 var(--espace-2);
}
.banc-entete h2 {
  font-family: var(--police-titre); font-weight: var(--graisse-noire);
  font-size: clamp(1.4rem, 2.6vw, 2.1rem); line-height: 1.08;
  letter-spacing: -.03em; margin: 0; color: #fff;
}
.banc-fermer {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 22px; border-radius: var(--rayon-md);
  background: rgba(255,255,255,.1); color: #fff;
  border: 1.5px solid rgba(255,255,255,.2);
  font-family: var(--police-ui); font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne); cursor: pointer;
  transition: background .2s ease;
}
.banc-fermer:hover { background: rgba(255,255,255,.2); }

/* --- Les deux fenêtres --- */

.banc-duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(14px, 2vw, 26px);
  flex: 1; min-height: 0;
}
@media (max-width: 980px) {
  .banc-duo { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .fenetre-portail { max-height: 46svh; }
}

.fenetre {
  display: flex; flex-direction: column;
  min-height: 0; overflow: hidden;
  border-radius: 26px;
  box-shadow: 0 34px 90px rgba(0,0,0,.55);
}
.fenetre-portail { background: #171310; }
.fenetre-site { background: #171310; }

.fenetre-tete {
  display: flex; align-items: center; gap: var(--espace-4);
  padding: 13px 18px; flex-shrink: 0;
  background: rgba(0,0,0,.22);
  border-bottom: 1px solid rgba(255,255,255,.09);
  font-size: var(--texte-xs); color: rgba(255,255,255,.7);
}
.fenetre-pastilles { display: flex; gap: 6px; flex-shrink: 0; }
.fenetre-pastilles i {
  width: 10px; height: 10px; border-radius: 50%;
  background: rgba(255,255,255,.22);
}
.fenetre-nom {
  font-weight: var(--graisse-grasse); letter-spacing: .1em;
  text-transform: uppercase;
}
.fenetre-url {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 14px; border-radius: 999px;
  background: rgba(255,255,255,.08);
  font-family: var(--police-mono, monospace); font-size: 11px;
}
.fenetre-cadenas {
  width: 8px; height: 10px; border-radius: 2px;
  border: 1.5px solid currentColor; border-top-width: 4px;
  opacity: .6;
}
.fenetre-aide {
  margin-left: auto; font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--wpn-jaune-400);
}

/* Le voyant bat brièvement quand le site vient de recevoir un changement. */
.fenetre-direct.bat { animation: battre .9s ease; }
@keyframes battre {
  0%   { color: #fff; transform: scale(1.12); }
  100% { color: var(--wpn-jaune-400); transform: none; }
}

.fenetre-direct {
  margin-left: auto; display: inline-flex; align-items: center; gap: 7px;
  font-size: 10px; font-weight: var(--graisse-grasse);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--wpn-jaune-400);
}
.fenetre-direct::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor;
}
.fenetre-direct.attente { color: rgba(255,255,255,.45); }

.fenetre-corps { flex: 1; min-height: 0; overflow-y: auto; padding: var(--espace-6); }
.fenetre-corps-plein { padding: 0; overflow: hidden; }
.fenetre-corps-plein iframe {
  width: 100%; height: 100%; border: 0; display: block; background: #171310;
}

/* --- Le portail dans sa fenêtre --- */

.banc-onglets {
  display: flex; gap: 4px; margin-bottom: var(--espace-5);
  padding-bottom: var(--espace-4);
  border-bottom: 1px solid rgba(255,255,255,.12);
  flex-wrap: wrap;
}
.banc-onglet {
  padding: 8px 15px; border: 0; border-radius: var(--rayon-md);
  background: transparent; color: rgba(255,255,255,.65);
  font-family: var(--police-ui); font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne); cursor: pointer;
}
.banc-onglet:hover { background: rgba(255,255,255,.09); color: #fff; }
.banc-onglet.actif { background: rgba(255,255,255,.17); color: #fff; }

.banc-panneau { display: none; }
.banc-panneau.actif { display: block; }

.banc-item {
  display: grid; grid-template-columns: 1fr 96px 36px;
  gap: 10px; align-items: end;
  padding: 12px; margin-bottom: 10px;
  background: rgba(255,255,255,.05); border-radius: var(--rayon-md);
}
.banc-champ { display: block; }
.banc-champ span {
  display: block; font-size: 10px; font-weight: var(--graisse-grasse);
  letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255,255,255,.5); margin-bottom: 5px;
}
.banc-champ input,
.banc-heure input {
  width: 100%; padding: 9px 12px;
  border: 1.5px solid rgba(255,255,255,.16); border-radius: 8px;
  background: rgba(255,255,255,.07); color: #fff;
  font-family: var(--police-texte); font-size: var(--texte-sm);
}
.banc-champ input:focus,
.banc-heure input:focus {
  outline: none; border-color: var(--wpn-jaune-400);
  background: rgba(255,255,255,.12);
}
.banc-panneau > .banc-champ { margin-bottom: var(--espace-4); }

.banc-retirer {
  width: 36px; height: 36px; border: 0; border-radius: 8px;
  background: rgba(255,255,255,.07); color: rgba(255,255,255,.55);
  cursor: pointer; font-size: 13px;
}
.banc-retirer:hover { background: rgba(179,38,30,.75); color: #fff; }

.banc-ajouter {
  width: 100%; padding: 11px; margin-top: 6px;
  border: 1.5px dashed rgba(255,255,255,.24); border-radius: var(--rayon-md);
  background: transparent; color: rgba(255,255,255,.65);
  font-family: var(--police-ui); font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne); cursor: pointer;
}
.banc-ajouter:hover {
  border-color: var(--wpn-jaune-400); color: var(--wpn-jaune-400);
  border-style: solid;
}

.banc-heure {
  display: grid; grid-template-columns: 96px 1fr;
  gap: 10px; align-items: center; margin-bottom: 8px;
}
.banc-heure > span { font-size: var(--texte-sm); color: rgba(255,255,255,.8); }

.banc-depot {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: var(--espace-8) var(--espace-4); text-align: center;
  border: 1.5px dashed rgba(255,255,255,.22); border-radius: var(--rayon-md);
  color: rgba(255,255,255,.55); font-size: var(--texte-sm);
  cursor: pointer;
}
.banc-depot em {
  font-style: normal; font-family: var(--police-mono, monospace);
  font-size: 10px; color: rgba(255,255,255,.4);
}

.banc-pied {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--espace-4); flex-wrap: wrap;
  margin-top: var(--espace-6); padding-top: var(--espace-5);
  border-top: 1px solid rgba(255,255,255,.12);
}
.banc-etat { font-size: var(--texte-xs); color: rgba(255,255,255,.55); }
.banc-publier {
  padding: 11px 22px; border: 0; border-radius: var(--rayon-md);
  background: rgba(255,255,255,.16); color: #fff;
  font-family: var(--police-ui); font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne); cursor: pointer;
  transition: background .22s ease, color .22s ease;
}
.banc-publier.prete { background: var(--wpn-jaune-400); color: var(--wpn-ancre); }
.banc-publier:hover { background: #fff; color: var(--wpn-violet-800); }
.banc-publier:disabled { opacity: .6; cursor: default; }

.banc.publie .fenetre-site { box-shadow: 0 0 0 3px var(--wpn-jaune-400), 0 34px 90px rgba(0,0,0,.55); }
.fenetre-site { transition: box-shadow .35s ease; }

/* ==========================================================================
   Chiffres
   ========================================================================== */

.chiffres {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--espace-8);
  margin-bottom: var(--espace-16);
  padding-bottom: var(--espace-12);
  border-bottom: 1px solid var(--wpn-neutre-100);
}
.chiffre b {
  display: block;
  font-family: var(--police-titre); font-weight: var(--graisse-noire);
  font-size: clamp(2.8rem, 6vw, 4.4rem); line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--wpn-bleu-700);
}
.chiffre:nth-child(2) b { color: var(--wpn-violet-800); }
.chiffre:nth-child(3) b { color: var(--wpn-jaune-700); }
.chiffre span {
  display: block; margin-top: var(--espace-2);
  font-size: var(--texte-sm); color: var(--wpn-neutre-500);
}

/* ==========================================================================
   Réalisations
   ========================================================================== */

.travaux { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: var(--espace-6); }
.travail {
  background: #fff; border: 1px solid var(--wpn-neutre-100);
  border-radius: var(--rayon-lg); overflow: hidden;
  text-decoration: none; display: flex; flex-direction: column;
  transition: transform .3s cubic-bezier(.2,.8,.2,1), box-shadow .3s ease;
}
.travail:hover { transform: translateY(-5px); box-shadow: var(--ombre-lg); }
.travail-image {
  aspect-ratio: 16 / 10; background: var(--wpn-neutre-100);
  display: flex; align-items: center; justify-content: center;
  color: var(--wpn-neutre-300); font-size: var(--texte-sm);
  overflow: hidden;
  transition: transform .45s cubic-bezier(.2,.8,.2,1);
}
.travail:hover .travail-image { transform: scale(1.04); }
.travail-texte { padding: var(--espace-6); flex: 1; }
.travail h3 { margin: 0 0 var(--espace-2); font-size: var(--texte-lg); }
.travail p { margin: 0; font-size: var(--texte-sm); color: var(--wpn-neutre-500); }
.travail-meta { display: flex; gap: var(--espace-2); flex-wrap: wrap; margin-top: var(--espace-4); }

.puce {
  font-size: 11px; font-weight: var(--graisse-grasse);
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 4px 9px; border-radius: 4px;
  transform: translateY(0);
  transition: transform .3s cubic-bezier(.2,.8,.2,1);
}
/* Les trois puces s'élèvent en cascade au survol de la carte. */
.travail:hover .puce:nth-child(1) { transform: translateY(-3px); transition-delay: 0s; }
.travail:hover .puce:nth-child(2) { transform: translateY(-3px); transition-delay: .06s; }
.travail:hover .puce:nth-child(3) { transform: translateY(-3px); transition-delay: .12s; }
.puce-w { background: var(--wpn-bleu-50);   color: var(--wpn-bleu-700); }
.puce-p { background: var(--wpn-violet-50); color: var(--wpn-violet-800); }
.puce-n { background: var(--wpn-jaune-100); color: var(--wpn-jaune-800); }

/* ==========================================================================
   Les trois voies
   ========================================================================== */

.voies { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); gap: var(--espace-6); }
.voie {
  position: relative; overflow: hidden;
  background: var(--wpn-fond); border: 1px solid var(--wpn-neutre-100);
  border-radius: var(--rayon-lg); padding: var(--espace-8);
  display: flex; flex-direction: column;
  transition: transform .3s cubic-bezier(.2,.8,.2,1), border-color .3s ease;
}
.voie:hover { transform: translateY(-4px); }
.voie-1:hover { border-color: var(--wpn-bleu-700); }
.voie-2:hover { border-color: var(--wpn-violet-800); }
.voie-3:hover { border-color: var(--wpn-jaune-400); }

/* Au survol, un second losange rejoint le premier : la géométrie du logo
   se complète, et l'idée de l'échange devient visible. */
.voie-marque {
  position: relative;
  width: 26px; height: 26px; border-radius: 5px;
  transform: rotate(45deg); margin-bottom: var(--espace-6);
  transition: transform .45s cubic-bezier(.2,.8,.2,1);
}
.voie-marque::after {
  content: ""; position: absolute; inset: 0;
  border-radius: 5px; background: currentColor;
  opacity: 0; transform: translate(9px, 9px);
  transition: opacity .35s ease, transform .45s cubic-bezier(.2,.8,.2,1);
}
.voie:hover .voie-marque::after { opacity: .34; transform: translate(13px, 13px); }
.voie-1 .voie-marque { background: var(--wpn-bleu-700);   color: var(--wpn-jaune-400); }
.voie-2 .voie-marque { background: var(--wpn-violet-800); color: var(--wpn-jaune-400); }
.voie-3 .voie-marque { background: var(--wpn-jaune-400);  color: var(--wpn-bleu-700); }

.voie h3 { margin: 0 0 var(--espace-3); font-size: var(--texte-xl); }
.voie p { margin: 0 0 var(--espace-6); color: var(--wpn-neutre-500); font-size: var(--texte-sm); flex: 1; }
.voie-echange {
  font-size: var(--texte-sm); padding-top: var(--espace-4);
  border-top: 1px solid var(--wpn-neutre-100);
}
.voie-echange b { display: block; margin-bottom: 3px; }

/* ==========================================================================
   Boutons
   ========================================================================== */

.heros-actions { display: flex; gap: var(--espace-4); flex-wrap: wrap; align-items: center; }

.btn {
  position: relative; overflow: hidden;
  display: inline-block; text-decoration: none;
  font-family: var(--police-ui); font-weight: var(--graisse-moyenne);
  font-size: var(--texte-base);
  padding: 16px 32px; border-radius: var(--rayon-md);
  border: 1.5px solid transparent;
}
.btn span { position: relative; z-index: 1; transition: color .3s ease; }
.btn::before {
  content: ""; position: absolute; inset: 0;
  transform: scaleX(0); transform-origin: left;
  transition: transform .4s cubic-bezier(.2,.8,.2,1);
}
.btn:hover::before { transform: scaleX(1); }

.btn-primaire { background: var(--wpn-ancre); color: #fff; }
.btn-primaire::before { background: var(--wpn-bleu-700); }
.btn-fantome { border-color: rgba(27,31,42,.2); color: var(--wpn-ancre); }
.btn-fantome::before { background: var(--wpn-ancre); }
.btn-fantome:hover span { color: #fff; }

/* ==========================================================================
   Appel final et pied
   ========================================================================== */

.final { background: var(--wpn-ancre); color: #fff; }
.final h2 { color: #fff; }
.final .section-chapo { color: var(--wpn-neutre-200); }
.final .btn-primaire { background: var(--wpn-jaune-400); color: var(--wpn-ancre); }
.final .btn-primaire::before { background: #fff; }
.final .btn-fantome { border-color: rgba(255,255,255,.3); color: #fff; }
.final .btn-fantome::before { background: #fff; }
.final .btn-fantome:hover span { color: var(--wpn-ancre); }

.pied {
  background: var(--wpn-ancre); color: var(--wpn-neutre-300);
  padding: var(--espace-12) 0; font-size: var(--texte-sm);
  border-top: 1px solid rgba(255,255,255,.08);
}
.pied-contenu { display: flex; justify-content: space-between; gap: var(--espace-8); flex-wrap: wrap; }
.pied strong { color: #fff; }
.pied a { color: var(--wpn-neutre-200); text-decoration: none; }
.pied a:hover { color: #fff; }
.pied-liens { display: flex; gap: var(--espace-6); flex-wrap: wrap; }

/* ==========================================================================
   Apparition au défilement
   Aucune inertie, aucun retard : l'élément se révèle dès qu'il entre.
   ========================================================================== */

.apparait { opacity: 0; transform: translateY(18px); }
.apparait.vu {
  opacity: 1; transform: none;
  transition: opacity .5s linear, transform .5s cubic-bezier(.2,.8,.2,1);
}

/* ⚠️ LE CALCULATEUR EST VISIBLE D'EMBLÉE, LUI AUSSI.

   Karl, 27 août 2026 : « quand on arrive sur le bloc avec le calculateur, il y
   a comme une espèce de flash blanc dans tout l'écran ».

   La cause : `.apparait` pose `opacity: 0`, et la règle qui l'annule ne visait
   que `.bloc` — or la section du calculateur n'en est pas un. Le cadre restait
   donc invisible jusqu'à ce que l'observateur de défilement se déclenche, puis
   surgissait d'un coup. Dans une pile collante, cet observateur se déclenche
   mal : l'apparition ne pouvait pas être douce.

   Le calculateur ne se révèle donc pas — il est là. */
.section-estime .apparait { opacity: 1; transform: none; }

/* Dans la pile des onglets, le contenu est visible d'emblée : le bloc étant
   collant, l'observateur d'apparition se déclenche mal et le texte pouvait
   rester à demi transparent, voire invisible. Ailleurs sur la page,
   l'apparition joue normalement. */
.bloc .apparait { opacity: 1; transform: none; }

/* ==========================================================================
   Mouvement réduit — tout s'affiche à l'état final, rien ne bouge.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
  .apparait { opacity: 1; transform: none; }
  /* Le logo reste assemblé, dans sa position finale : c'est déjà l'état
     d'arrivée de l'animation, donc rien à figer de plus. */
  .heros-etiquette, .heros-chapo, .heros-actions { opacity: 1; }
  .mot-window  { color: var(--wpn-bleu-700); }
  .mot-portal  { color: var(--wpn-violet-800); }
  .mot-network { color: var(--wpn-jaune-700); }
  #fond { display: none; }
}


/* ==========================================================================
   Le calculateur d'estimation
   --------------------------------------------------------------------------
   Deux onglets : ce qu'on construit, ce qu'on échange. Le résultat reste
   visible dans les deux, pour qu'on voie l'effet d'un réglage sans changer
   de vue.
   ========================================================================== */

/* Le titre reste sur deux lignes courtes plutôt que de déborder, et l'espace
   sous l'introduction sépare franchement le discours des réglages. */
/* --- Le cadre du calculateur ---------------------------------------------
   Karl a écarté la mise en page pleine largeur : « fais comme si ce dernier
   onglet blanc était vide, il monte, il cache. Là on a un espace vide avant
   le bloc suivant. Dans cet espace vide, je veux le calculateur dans un
   cadre aux coins très arrondis. »

   Le dernier onglet de la pile monte donc SANS contenu propre. Ce qu'on voit
   dedans est un objet posé sur un fond neutre — un panneau qu'on manipule,
   pas une section de page. D'où le fond blanc sur fond crème, l'ombre douce
   et le très grand rayon. */

.section-estime {
  /* Elle est le VIDE dans lequel la fenêtre du calculateur est posée, et rien
     d'autre : ni titre, ni chapô. Le cadre est centré dedans. */
  display: grid;
  place-items: center;
  align-content: center;
  /* Le même noir que l'invitation qui suit : les deux moitiés forment une
     seule surface, et la jonction devient invisible. */
  background: var(--wpn-ancre);

  /* De l'air tout autour de la fenêtre. Karl : « je veux que le calculateur
     soit une fenêtre dans le bloc » — une fenêtre a besoin d'une marge, sans
     quoi elle se lit comme un panneau collé aux bords. */
  /* **Le même rebord tout autour du cadre.** Karl : « la fenêtre du calculateur
     est trop collée en haut. Il faudrait aussi lui donner un peu d'espace et
     qu'il y ait le même rebord gris foncé au-dessus du cadre, la même chose
     que sur les côtés. »

     Le padding vertical valait `clamp(16px, 2.2svh, 30px)` contre
     `clamp(16px, 2.4vw, 34px)` à l'horizontale — mesuré, 17,6 px en haut pour
     33,6 px sur les côtés. La même valeur est désormais appliquée aux quatre
     bords, si bien que le cadre est posé dans le bloc avec un rebord régulier
     plutôt que plaqué contre le haut. */
  /* Le rebord vertical est plus généreux que l'horizontal, et c'est
     volontaire : le cadre est large (jusqu'à 1180 px) mais sa hauteur suit
     celle du bloc, si bien qu'à padding égal il paraît plaqué contre le haut.
     Karl l'a signalé deux fois — « la fenêtre du calculateur est trop collée
     sur le top du bloc, il faut ajouter de l'air ». On double donc le rebord
     haut et bas. */
  /* **Troisième passe sur ce rebord** (27 août 2026). Karl : « le calculateur
     est trop collé au top du bloc gris foncé dans lequel il se positionne, il
     faut qu'il ait une bande qui passe au-dessus comme sur les côtés. »

     Mesuré, le cadre ÉTAIT déjà centré : 67 px au-dessus, 67 px en dessous.
     Ce n'est donc pas un défaut de centrage. Ce qui trompe l'œil, c'est que la
     section qui SUIT le calculateur porte exactement le même noir : la
     jonction est invisible, et le rebord de 67 px se compare à un vide de
     plusieurs centaines de pixels sous le cadre. Le rebord paraît maigre alors
     qu'il est symétrique.

     La réponse est donc d'épaissir réellement la bande haute, pas de corriger
     un centrage qui n'a rien de faux. */
  padding: clamp(30px, 4.6vh, 56px) clamp(16px, 2.4vw, 34px);

  /* **La bande au-dessus du cadre — troisième passe, 27 août 2026.**
     Karl : « le calculateur est trop collé au top du bloc gris foncé dans
     lequel il se positionne, il faut qu'il ait une bande qui passe au-dessus
     comme sur les côtés. »

     Ce qui NE marche pas, et pourquoi — pour qu'une session future ne le
     réessaie pas : une fois la pile pliée, `.calculateur-course` impose à
     cette section une hauteur fixe (`100svh − 3 × --onglet`, mesuré 764 px).
     Épaissir le padding ne pousse donc rien, le cadre se comprimant d'autant ;
     et borner la `max-height` du cadre ne fait rien non plus, parce que sa
     hauteur vient de son CONTENU (la grille `.estime`, 600 px de colonnes) et
     non du conteneur. Les deux ont été essayés et mesurés : l'écart restait
     bloqué à 67 px.

     Ce qui marche : aligner le contenu de la section sur le BAS plutôt qu'au
     centre. Le cadre garde sa hauteur naturelle, le rebord bas reste celui du
     padding, et tout le reste de la hauteur disponible devient la bande du
     haut.

     **`safe` est indispensable, et son absence a causé un vrai défaut.**
     Karl, 27 août 2026 : « la fenêtre du calculateur embarque toujours sur
     l'onglet jaune ; la petite fenêtre du calculateur, elle sort de son
     bloc. » Sur un écran court (mesuré à 1280 × 720), le cadre est plus haut
     que la place que la section lui laisse — 562 px de cadre pour 564 px de
     section dont 33 px de rembourrage haut. Un alignement `end` pousse alors
     le débordement du côté OPPOSÉ, c'est-à-dire vers le haut : le cadre
     sortait de 31 px par-dessus la bande jaune.

     `safe end` bascule automatiquement sur `start` dès que le contenu déborde,
     ce qui garde le débordement du côté bas, où il est rogné sans rien
     recouvrir. Sur les écrans où la place suffit, le comportement est
     identique à `end` — la bande du haut reste. */
  align-content: safe end;
  box-sizing: border-box;
}

/* ==========================================================================
   LE CALCULATEUR — un instrument posé dans la page

   Direction visuelle décidée avec Karl le 27 août 2026, documentée dans
   `CALCULATEUR.md` § 5 : « un petit logiciel incrusté dans le site web »,
   « un peu d'un jouet, avec plus de textures et de reliefs », et surtout
   « qu'on ait envie d'aller jouer dans cet espace-là ».

   L'équilibre entre « logiciel » et « jouet » se trouve dans l'INSTRUMENT :
   une console de mixage, un vieux boîtier de photo. Des objets sérieux,
   auxquels on fait confiance pour mesurer, et qu'on a pourtant envie de
   manipuler. Quatre principes, appliqués partout dans cette feuille :

     1. LES COMMANDES ONT DU VOLUME, LE CHÂSSIS N'EN A PAS. Le fond est mat et
        calme ; ce qui dépasse, ce sont les boutons et les curseurs. C'est le
        contraste qui dit « touche ici, pas là ». Un objet texturé partout
        devient bruyant et cesse d'inviter.

     2. LES CHOSES BOUGENT VRAIMENT QUAND ON LES TOUCHE. Un bouton s'enfonce,
        un curseur a de la prise, un onglet se relève. C'est là qu'est le
        « jouet » — pas dans les couleurs, dans la RÉPONSE.

     3. LES COMMANDES SONT CE QU'ELLES IMITENT. Un curseur qui a l'air d'un
        vrai potentiomètre se comprend sans mode d'emploi.

     4. UNE SEULE CHOSE S'ALLUME : LE PRIX. C'est l'afficheur de l'appareil, et
        c'est ce qui donne envie de continuer à jouer — on veut voir le nombre
        changer.

   ⚠️ LA TEXTURE NE DOIT JAMAIS GÊNER LA LECTURE. Un instrument reste lisible
   dans une salle sombre : ses contrastes sont plus forts qu'ailleurs, pas plus
   faibles. C'est ce qui sépare un bel objet d'un objet décoré.
   ========================================================================== */

.section-estime {
  /* Les jetons de l'instrument. Ils ne servent qu'ici : le reste du site garde
     sa sobriété typographique, et le relief est confiné à l'objet. */
  --chassis:      #171B24;
  --chassis-haut: #1E232E;
  --creux:        #12151C;
  --arete:        rgba(255,255,255,.07);
  --arete-vive:   rgba(255,255,255,.13);
  --ombre-douce:  rgba(0,0,0,.45);
  --ombre-dure:   rgba(0,0,0,.7);
  --luire:        var(--wpn-jaune-400);
}

/* --------------------------------------------------------------------------
   L'EN-TÊTE DU BLOC
   -------------------------------------------------------------------------- */

/* Les mots à gauche, la mascotte à droite. Elle est posée en BAS de sa
   colonne et déborde légèrement sous l'en-tête, de sorte qu'elle paraisse se
   tenir DEVANT le calculateur plutôt qu'à côté — c'est ce qui la relie à
   l'objet au lieu d'en faire une illustration décorative. */
.estime-entete {
  display: grid;
  /* Une seule colonne : la mascotte est absolue et ne prend pas de place.
     La réserve à droite empêche le texte de passer dessous. */
  grid-template-columns: minmax(0, 1fr);
  /* Le texte est borné par sa propre largeur (voir `.entete-titre` et
     `.entete-texte`), pas par une réserve : la mascotte se pose juste après
     lui, à `left: 52%`. */
  padding-right: 0;
  align-items: end;
  max-width: 1180px;
  margin: 0 auto clamp(10px, 1.4vw, 18px);
  padding-inline: clamp(4px, 1vw, 14px);
}

.entete-mots {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding-bottom: clamp(4px, 1vw, 12px);
}

.entete-sur {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--wpn-jaune-400);
}

.entete-titre {
  margin: 0;
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  font-size: clamp(1.5rem, 3.4vw, 2.5rem);
  line-height: 1.08;
  letter-spacing: -.02em;
  color: #F4F4F2;
  text-wrap: balance;
  max-width: 24ch;
}

.entete-texte {
  grid-column: 1;
  grid-row: 2;
  margin: 8px 0 0;
  font-size: var(--texte-base, 1rem);
  line-height: 1.55;
  color: rgba(255,255,255,.6);
  max-width: 46ch;
}
.entete-mots { grid-column: 1; grid-row: 1; }

/* ⚠️ LA MASCOTTE EST HORS DU FLUX DE LA GRILLE.

   Elle mesure 1 461 px de haut à sa taille native et occupait deux rangées :
   la grille étirait donc la rangée du titre à sa hauteur, et 200 px de vide
   s'ouvraient entre le titre et la description. `align-content`,
   `grid-template-rows`, `align-self` — rien n'y faisait, parce que le
   problème n'est pas l'alignement mais la CONTRIBUTION de l'image à la
   hauteur des rangées.

   En `position: absolute`, elle ne contribue plus à rien : les rangées se
   dimensionnent sur le seul texte, et elle se pose par-dessus, alignée sur le
   bas de l'en-tête d'où elle déborde sur le calculateur. */
.estime-entete { position: relative; }

/* ⚠️ LA MASCOTTE NE DÉPASSE JAMAIS LE HAUT DU TEXTE.

   Karl : « il ne faudrait pas que le personnage soit plus haut que le haut du
   titre, le texte. »

   Elle est donc dimensionnée par sa HAUTEUR, pas par sa largeur : `top: 0` la
   cale sur le sur-titre, `bottom` la fait déborder sur le calculateur, et sa
   largeur suit d'elle-même (`width: auto` avec un rapport de 620/1461).

   C'est aussi ce qui l'empêche de grandir indéfiniment sur grand écran : sa
   taille est celle du bloc de texte, quelle que soit la largeur disponible. */
/* ⚠️ ELLE SE COLLE AU TEXTE, PAS AU BORD DU BLOC.

   Karl : « tu pourrais mettre la mascotte plus à côté du texte ». Elle était
   plaquée au bord droit, avec un vide de plusieurs centaines de pixels entre
   elle et le titre — elle ne semblait plus appartenir au même ensemble.

   `left` la pose juste après la colonne de texte ; `right: auto` la libère du
   bord. Elle reste à DROITE, et c'est délibéré : on lit de gauche à droite,
   donc le texte vient d'abord ; elle regarde vers sa calculatrice, donc vers
   la gauche, et son regard ramène dans la page au lieu d'en sortir ; et le
   panneau de prix du calculateur est lui aussi à droite, si bien qu'elle
   annonce ce qu'elle surplombe. */
.entete-image {
  position: absolute;
  /* Après le texte. Le titre porte un « ? » final qui dépasse la largeur du
     corps de texte : la mascotte se pose donc après le plus large des deux,
     avec une marge qui l'empêche de mordre dessus.

     `top` la descend d'un cran : calée à 0, sa tête se faisait couper par le
     haut de la section, qui commence au-dessus du sur-titre. */
  left: min(58ch, calc(100% - 150px));
  right: auto;
  top: 10px;
  /* ⚠️ LA MASCOTTE FLOTTE AU-DESSUS DU CADRE, ELLE NE S'Y APPUIE PAS.

     Karl, 28 août 2026 : « le personnage, je trouve qu'il est un peu trop
     par-dessus le calculateur. Je crois qu'il devrait plutôt être juste un peu
     au-dessus, peut-être dix à quinze pixels, pour donner l'impression qu'il
     flotte un peu sur un plancher qu'on ne voit pas. »

     La valeur était négative — de 14 à 40 px selon la largeur — ce qui la
     faisait mordre DANS le boîtier : elle avait l'air posée dessus, voire
     avachie. Un écart positif de 12 px lui donne au contraire une assise :
     l'œil suppose un sol sous ses pieds, et l'objet garde sa netteté.

     Sa TAILLE ne change pas : Karl l'a demandé explicitement, et c'est
     `top`/`bottom` qui la dimensionnent (voir la note plus haut), donc toute
     retouche ici doit rester de l'ordre de la dizaine de pixels. */
  bottom: 12px;
  /* ⚠️ `display: flex` est nécessaire : un conteneur absolu sans largeur
     définie s'effondre à zéro, et l'image avec lui — mesuré, elle faisait
     0 × 0. En flex, il prend la largeur de son contenu. */
  display: flex;
  align-items: stretch;
}
.entete-image img {
  width: auto;
  height: 100%;
  max-width: none;
  object-fit: contain;
}

/* Plus de `min-height` : la mascotte prend la hauteur du texte, et non
   l'inverse. C'est ce qui garantit qu'elle ne dépasse jamais le sur-titre,
   sur aucune taille d'écran. */
.estime-entete { row-gap: 0; }

/* La mascotte déborde vers le bas : le calculateur commence sous elle, et
   elle a l'air de le présenter. `margin-bottom` négatif plutôt qu'une
   position absolue — la grille garde ainsi la maîtrise de la hauteur. */
.entete-image { pointer-events: none; }
.entete-image img {
  display: block;
  width: 100%;
  height: auto;
  /* Une ombre portée douce, comme si elle se tenait sur le bloc. */
  filter: drop-shadow(0 18px 22px rgba(0,0,0,.45));
}

@media (max-width: 780px) {
  /* Sur petit écran, la mascotte se met à côté du SEUL TITRE, et la
     description passe dessous sur toute la largeur.

     Mesuré : en gardant les deux colonnes pour tout le texte, la colonne
     tombait à 190 px — le titre se cassait sur cinq lignes et la description
     devenait illisible. En pleine largeur, elle respire ; le titre reste
     court par nature et supporte la colonne étroite. */
  .estime-entete {
    /* La réserve ne vaut que pour le titre : la description passe dessous,
       en pleine largeur, car c'est elle qui souffrait le plus d'une colonne
       étroite. */
    padding-right: 0;
    gap: 10px;
  }
  /* ⚠️ LA RÉSERVE VAUT POUR TOUT LE TEXTE, DESCRIPTION COMPRISE.

     Elle ne portait que sur `.entete-mots`, et `.entete-texte` reprenait
     ensuite `max-width: none` : sur téléphone, mesuré à 390 px, la
     description passait donc SOUS la mascotte et devenait illisible — « tick
     what your place has » disparaissait derrière le personnage.

     Titre et description gardent maintenant la même réserve. La description
     est plus longue que le titre : c'est elle qui a le plus besoin de la
     protection, pas l'inverse. */
  .entete-mots { padding-right: clamp(100px, 28vw, 140px); }
  .entete-titre { max-width: none; }
  .entete-texte {
    grid-column: 1;
    grid-row: 2;
    max-width: none;
    padding-right: clamp(100px, 28vw, 140px);
  }
  .entete-image {
    /* Sur petit écran, elle revient au bord : il n'y a pas la place de la
       décoller du texte. */
    left: auto;
    right: 0;
    top: 0;
    bottom: -8px;
  }
}

/* --------------------------------------------------------------------------
   LE CHÂSSIS
   -------------------------------------------------------------------------- */

.estime-cadre {
  width: 100%;
  max-width: 1180px;
  margin-inline: auto;
  box-sizing: border-box;

  /* Le châssis d'un appareil : mat, légèrement plus clair en haut comme si la
     lumière de la pièce tombait dessus. Pas de dégradé spectaculaire — c'est
     un boîtier, pas une vitrine. */
  background:
    linear-gradient(180deg, var(--chassis-haut) 0%, var(--chassis) 42%, var(--creux) 100%);
  border-radius: clamp(18px, 2.2vw, 26px);

  /* Le relief tient à trois traits, pas à une grosse ombre : une arête claire
     en haut (la lumière), une ombre portée sous l'objet (il est posé), et un
     liseré sombre tout autour (il a une épaisseur). */
  box-shadow:
    inset 0 1px 0 var(--arete-vive),
    inset 0 -1px 0 rgba(0,0,0,.5),
    0 1px 0 rgba(0,0,0,.6),
    0 22px 44px -18px var(--ombre-douce);
  border: 1px solid rgba(0,0,0,.55);

  padding: clamp(14px, 2vw, 22px);
  /* ⚠️ LE CHÂSSIS SE RESSERRE EN GLISSANT, LUI AUSSI.

     À l'ouverture, le padding du cadre passe de ~22 px à ~14 px et la tirette
     se réduit : ces changements étaient INSTANTANÉS alors que la hauteur du
     corps, elle, s'animait. Mesuré, le cadre décrochait de 483 à 434 px sur la
     toute première image, avant de reprendre sa croissance douce — un à-coup
     au départ du mouvement. Karl : « l'animation se fait de façon saccadée. »

     Même durée et même courbe que la hauteur : tout le châssis bouge d'un seul
     geste. */
  transition:
    height .52s cubic-bezier(.22, .61, .36, 1),
    padding .52s cubic-bezier(.22, .61, .36, 1),
    gap .52s cubic-bezier(.22, .61, .36, 1);
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.6vw, 18px);

  /* ⚠️ AUCUN `overflow` ET AUCUNE HAUTEUR BORNÉE ICI, JAMAIS.

     C'est le cœur de la refonte, et la règle vaut dans les DEUX états. Il n'y
     a aucune zone défilante à l'intérieur de l'instrument : la molette du
     visiteur fait toujours défiler la page, jamais l'outil.

     Déroulé, le cadre grandit et pousse le bloc vers le bas. « Se promener
     dans le calculateur » revient donc à se promener dans le site, ce qui est
     le comportement de tout le reste de la page.

     Remettre ici un `overflow: auto` ou un `max-height` ferait revenir le
     défilement capturé qui a motivé toute cette refonte. */

  /* L'inversion des jetons de texte, pour que le contenu écrit pour un fond
     clair reste lisible sur le châssis. */
  --wpn-ancre: #F4F4F2;
  --wpn-neutre-500: var(--wpn-neutre-200);
  --wpn-neutre-400: var(--wpn-neutre-200);
  --wpn-neutre-100: #3A404C;
  color: #F4F4F2;
}

.estime { display: flex; flex-direction: column; gap: inherit; }

/* --------------------------------------------------------------------------
   LA FAÇADE — ce qu'on voit sans rien demander
   -------------------------------------------------------------------------- */

.estime-facade {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-5);
  flex-wrap: wrap;
}


/* Une plaque gravée : le nom de l'appareil, comme sérigraphié sur le boîtier.
   Le relief vient d'une ombre claire sous le texte, pas d'un effet. */

/* L'AFFICHEUR de la façade. Encastré dans le châssis — un creux, pas une
   carte posée dessus : l'ombre est INTÉRIEURE et l'arête claire est en BAS,
   l'inverse d'un objet en relief. C'est ce qui fait qu'on lit un enfoncement. */
.facade-afficheur {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  padding: 10px 16px;
  border-radius: 10px;
  background: var(--creux);
  box-shadow:
    inset 0 2px 5px rgba(0,0,0,.75),
    inset 0 -1px 0 var(--arete);
}
.afficheur-etiq {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255,255,255,.4);
}
/* Le seul élément lumineux de l'objet. */
.afficheur-valeur {
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  font-size: clamp(1.15rem, 2.2vw, 1.6rem);
  letter-spacing: -.02em;
  color: var(--luire);
  text-shadow: 0 0 18px rgba(228,169,60,.28);
  font-variant-numeric: tabular-nums;
}
.afficheur-note {
  font-size: var(--texte-xs);
  color: rgba(255,255,255,.42);
}

/* --------------------------------------------------------------------------
   LA TIRETTE — la seule porte d'entrée
   -------------------------------------------------------------------------- */

.estime-tirette {
  transition:
    max-width .52s cubic-bezier(.22, .61, .36, 1),
    padding .52s cubic-bezier(.22, .61, .36, 1);

  appearance: none;
  border: 0;
  width: 100%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--espace-3);
  padding: 11px;
  border-radius: 10px;
  /* La largeur bascule selon l'état, voir plus bas : pleine pour ouvrir,
     discrète pour fermer. */
  transition: transform .12s ease, box-shadow .12s ease, color .12s ease,
              max-width .3s cubic-bezier(.4,0,.2,1);
  font: inherit;
  font-size: var(--texte-sm);
  font-weight: var(--graisse-moyenne);
  color: rgba(255,255,255,.72);

  /* Un vrai bouton : bombé au repos, enfoncé au clic. */
  background: linear-gradient(180deg, #262C38 0%, #1B202A 100%);
  box-shadow:
    inset 0 1px 0 var(--arete-vive),
    0 1px 0 rgba(0,0,0,.55),
    0 3px 6px -2px rgba(0,0,0,.5);
  transition: transform .12s ease, box-shadow .12s ease, color .12s ease;
}
.estime-tirette:hover { color: #fff; }
.estime-tirette:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 2px 5px rgba(0,0,0,.6),
    0 0 0 rgba(0,0,0,0);
}
.estime-tirette:focus-visible {
  outline: 2px solid var(--luire);
  outline-offset: 2px;
}

.tirette-fleche { display: flex; }
.tirette-fleche svg { width: 18px; height: 11px; }
.estime-cadre:not(.est-replie) .tirette-fleche { transform: rotate(180deg); }
.tirette-fleche { transition: transform .22s ease; }

.estime-cadre.est-replie .tirette-fermer,
.estime-cadre:not(.est-replie) .tirette-ouvrir { display: none; }

/* ---- OUVRIR EST ÉVIDENT, FERMER EST DISCRET ---------------------------

   Karl : « le bouton close, il est beaucoup trop gros, pas besoin d'être aussi
   large. J'aimerais que ça soit plus esthétique et plus discret. »

   La dissymétrie est délibérée. « Work out a price » est le seul appel à
   l'action de l'aperçu et c'est lui qui fait entrer les gens dans l'outil : il
   garde toute sa largeur. Fermer est un geste de sortie, qu'on cherche quand
   on en a besoin — il n'a pas à occuper la même place. */
.estime-cadre:not(.est-replie) .estime-tirette {
  max-width: 132px;
  margin-inline: auto;
  padding: 7px 14px;
  font-size: .82rem;
  background: transparent;
  /* La tirette se resserre au même rythme — voir la note du châssis. */
  box-shadow: inset 0 0 0 1px var(--arete);
  color: rgba(255,255,255,.5);
}
.estime-cadre:not(.est-replie) .estime-tirette:hover {
  background: linear-gradient(180deg, #262C38 0%, #1B202A 100%);
  box-shadow: inset 0 1px 0 var(--arete-vive), 0 1px 0 rgba(0,0,0,.55);
}
.estime-cadre:not(.est-replie) .tirette-fleche svg { width: 14px; height: 9px; }

/* --------------------------------------------------------------------------
   REPLIÉ / DÉROULÉ
   -------------------------------------------------------------------------- */

/* ---- COMPRESSÉ / DÉROULÉ -------------------------------------------------

   ⚠️ CE QUI A CHANGÉ LE 27 AOÛT 2026, dans la même journée.

   La première version cachait TOUT le corps de l'instrument à l'état replié :
   on ne voyait qu'une plaque, un prix et un bouton. Karl : « l'aperçu est
   beaucoup trop vide, il n'y a pratiquement rien ». Il avait raison — un objet
   qui ne montre rien ne donne pas envie d'être touché, et c'était pourtant
   toute l'intention.

   Désormais l'état COMPRESSÉ montre déjà l'essentiel : les trois onglets, les
   commandes du module ouvert, et l'afficheur avec son prix. Assez pour
   comprendre l'outil et commencer à jouer.

   DÉROULER ne fait qu'une chose : lever la limite de hauteur, pour que rien ne
   soit coupé. Le bloc grandit et pousse la page. Il n'y a AUCUN défilement
   interne, ni avant ni après — c'est ce qui distingue cette version de celle
   qu'elle remplace. */

/* ⚠️ DÉROULÉ, L'OUTIL DOIT TENIR DANS LA BOÎTE DE LA PILE.

   La pile lui laisse `100svh − 3 × --onglet` : sur un écran de 850 px, cela
   fait 664 px utilisables, les trois bandes d'onglets occupant le reste. Le
   cadre déroulé en demandait 808 — il ne pouvait donc JAMAIS être vu en
   entier, et son bas restait sous la fenêtre quoi qu'on fasse.

   Karl : « le calculateur est encore coupé en bas, je ne peux pas commenter
   davantage parce que je ne vois pas le bas de l'image. »

   On resserre donc l'outil déroulé — et lui seul, l'aperçu replié garde son
   confort. Chaque valeur ci-dessous a été mesurée pour rendre les 144 px
   manquants sans rien retirer. */
.estime-cadre:not(.est-replie) {
  padding: clamp(10px, 1.2vw, 14px);
  gap: clamp(8px, 1vw, 12px);
}
.estime-cadre:not(.est-replie) .estime-facade { display: none; }
.estime-cadre:not(.est-replie) .panneau { padding: clamp(9px, 1vw, 12px); gap: 7px; }
.estime-cadre:not(.est-replie) .bloc-commande { padding: 7px 10px; gap: 4px; }
.estime-cadre:not(.est-replie) .commande-aide { min-height: 1.6em; font-size: .74rem; }
.estime-cadre:not(.est-replie) .bloc-rangee { gap: 8px; }
.estime-cadre:not(.est-replie) .onglet { padding: 7px 11px; }
.estime-cadre:not(.est-replie) .estime-plan { gap: clamp(9px, 1vw, 12px); }

/* Compressé : le corps est là, mais borné en hauteur et estompé en pied, ce
   qui dit qu'il y a une suite. `overflow: hidden` est ici SANS DANGER — il
   coupe, il ne fait pas défiler : la molette continue d'aller à la page. */
.estime-corps {
  /* La hauteur s'anime, ce qui donne le geste de dépliage. Sans transition,
     le contenu apparaissait d'un coup — Karl : « l'animation est très
     saccadée, il fait juste réapparaître, ouvert ».

     `max-height` plutôt que `height` : on ne connaît pas la hauteur du contenu
     à l'avance, elle dépend du module ouvert et des faits cochés. La valeur
     déroulée est donc volontairement plus grande que tout contenu plausible ;
     l'animation ralentit un peu en fin de course, ce qui passe inaperçu. */
  /* ⚠️ L'AGRANDISSEMENT EST PILOTÉ PAR `estime.js`, PAS PAR CETTE RÈGLE.

     `max-height` passait de 357 px à 2600 px à l'ouverture. Comme le contenu
     n'en fait que 624, la boîte atteignait sa taille finale dans les premières
     millisecondes de la transition : l'animation existait sur le papier mais
     ne se voyait pas. Karl : « il y a juste une coupure sèche ».

     On ne peut pas écrire la hauteur d'arrivée en CSS — elle dépend du module
     ouvert et des cases cochées. `estime.js` la MESURE et anime `height` entre
     les deux valeurs réelles, ce qui donne un vrai mouvement.

     La transition reste déclarée ici pour que `height` s'anime quand le script
     l'écrit ; `max-height` ne sert plus qu'à borner l'état replié. */
  max-height: none;
  transition: height .52s cubic-bezier(.22, .61, .36, 1);
}
.estime-cadre.est-replie .estime-corps {
  max-height: clamp(300px, 42svh, 430px);
  overflow: hidden;
  /* ⚠️ L'ESTOMPAGE DOIT ÊTRE FRANC.

     Il valait 60 px et se lisait comme une coupure nette — Karl a signalé
     plusieurs fois « le calculateur est coupé en bas » alors qu'il regardait
     l'aperçu replié, qui fonctionnait normalement. Un dégradé trop court ne
     dit pas « il y a une suite », il dit « c'est tronqué ».

     À 130 px, la dernière rangée s'efface progressivement et l'intention
     devient lisible : ce qu'on voit continue plus bas. */
  mask-image: linear-gradient(to bottom, #000 calc(100% - 130px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 130px), transparent);
}

/* ⚠️ L'AFFICHEUR DE FAÇADE NE SERT PLUS ET RESTE MASQUÉ.

   Il portait le prix quand l'aperçu ne montrait qu'une plaque et un bouton.
   Depuis que l'aperçu montre déjà les commandes ET l'encadré du bas, il
   affichait le même chiffre deux fois à quelques centimètres d'écart.

   Le balisage est conservé : il redeviendrait utile si l'aperçu devait un jour
   se réduire à nouveau. */
.facade-afficheur { display: none; }

.estime-corps {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.6vw, 18px);
}

/* --------------------------------------------------------------------------
   LES ONGLETS DE MODULE
   -------------------------------------------------------------------------- */

.estime-onglets {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

/* Un onglet est une touche : bombée, elle s'enfonce quand elle est ouverte.
   L'onglet ouvert est CREUX, les autres sont en relief — c'est le contraire
   d'un onglet de navigateur, et c'est voulu : sur un appareil, la touche
   enfoncée est celle qui est active. */
.onglet {
  position: relative;
  border-radius: 11px;
  background: linear-gradient(180deg, #252B37 0%, #1A1F29 100%);
  box-shadow:
    inset 0 1px 0 var(--arete-vive),
    0 1px 0 rgba(0,0,0,.5),
    0 4px 8px -3px rgba(0,0,0,.45);
  transition: box-shadow .16s ease, transform .16s ease, opacity .16s ease;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
}
.onglet.est-ouvert {
  transform: translateY(1px);
  background: linear-gradient(180deg, #12161E 0%, #191E28 100%);
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,.6),
    inset 0 -1px 0 var(--arete);
}

/* Un module décoché s'assombrit mais reste ouvrable : le client voit ce qu'il
   n'a pas pris. Karl : « il devient sombre, on peut l'ouvrir, mais tout est
   sombre et pas cliquable ». */
.onglet:not(.est-actif) { opacity: .5; }

/* ---- UNE ICÔNE PAR MODULE, DANS SA COULEUR ----------------------------

   Karl, 27 août 2026 : « je verrais vraiment sur chacun des onglets une icône
   bleue, violette et jaune — comme une fenêtre bleue, un genre de portail
   violet et une icône de réseau en jaune, qui remplacerait le point de couleur
   qu'on a présentement. »

   Remplacer le point plutôt que s'ajouter à côté est la bonne façon : l'icône
   porte l'identité ET l'état, sans encombrer. Elle s'allume quand le module
   est actif et s'éteint quand il ne l'est pas — même rôle que la diode, en
   plus parlant.

   Les dessins reprennent le vocabulaire de la marque : une fenêtre à croisée
   pour Window, un passage traversé pour Portal, trois points reliés pour
   Network. Trait fin, pas d'aplat : ils vivent à côté de commandes en relief,
   et un aplat de couleur y ferait tache. */
.onglet-icone {
  flex: none;
  display: flex;
  /* Éteinte, l'icône garde une trace de sa couleur : elle porte l'IDENTITÉ du
     module autant que son état. Un gris neutre effacerait ce qui distingue
     Network de Portal quand les deux sont décochés. */
  color: rgba(255,255,255,.2);
  transition: color .2s ease, filter .2s ease;
}
.onglet.est-window .onglet-icone { color: color-mix(in srgb, var(--wpn-bleu-400) 34%, transparent); }
.onglet.est-portal .onglet-icone { color: color-mix(in srgb, var(--wpn-violet-400) 34%, transparent); }
.onglet.est-network .onglet-icone { color: color-mix(in srgb, var(--wpn-jaune-400) 34%, transparent); }
/* ⚠️ LES ICÔNES SONT CELLES DE KARL, PEINTES AU MASQUE.

   Elles vivent dans `design/icons/` (une fenêtre à rideaux, un portail sur
   pied, un réseau de nœuds) et sont préparées en masques alpha de 96×96 dans
   `site/images/icones/`.

   `mask` plutôt qu'`<img>` : le masque prend la couleur de `background`, donc
   `currentColor` de l'onglet continue de s'appliquer — bleu, violet, jaune,
   avec l'atténuation quand l'onglet est inactif et le halo quand il est
   ouvert. Un `<img>` aurait figé la couleur du fichier.

   ⚠️ Les PNG d'origine sont en RGBA et portent leur dessin dans la couche
   ALPHA, pas dans la luminosité. Les convertir en niveaux de gris produit un
   masque INVERSÉ — fond plein, trait creux. Voir la note dans le script de
   préparation. */
.onglet-icone {
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
  background: currentColor;
  width: 19px;
  height: 19px;
}
.onglet.est-window .onglet-icone {
  -webkit-mask-image: url("../../images/icones/window.png");
  mask-image: url("../../images/icones/window.png");
}
.onglet.est-portal .onglet-icone {
  -webkit-mask-image: url("../../images/icones/portal.png");
  mask-image: url("../../images/icones/portal.png");
}
.onglet.est-network .onglet-icone {
  -webkit-mask-image: url("../../images/icones/network.png");
  mask-image: url("../../images/icones/network.png");
}

.onglet.est-actif.est-window .onglet-icone {
  color: var(--wpn-bleu-400);
  filter: drop-shadow(0 0 5px rgba(77,143,255,.45));
}
.onglet.est-actif.est-portal .onglet-icone {
  color: var(--wpn-violet-400);
  filter: drop-shadow(0 0 5px rgba(158,120,255,.45));
}
.onglet.est-actif.est-network .onglet-icone {
  color: var(--wpn-jaune-400);
  filter: drop-shadow(0 0 5px rgba(228,169,60,.45));
}

/* L'onglet OUVERT — celui dont on voit le panneau — pousse un peu plus loin :
   c'est là que le regard doit revenir. */
.onglet.est-ouvert .onglet-icone { filter: drop-shadow(0 0 8px currentColor); }

.onglet-corps {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  text-align: left;
  flex: 1 1 auto;
  min-width: 0;
  /* L'icône à gauche, le nom et la description empilés à droite. */
  display: flex;
  align-items: center;
  gap: 9px;
  color: inherit;
}
.onglet-corps .onglet-texte {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.onglet-corps:focus-visible { outline: 2px solid var(--luire); outline-offset: 3px; }

.onglet-nom {
  font-size: var(--texte-sm);
  font-weight: var(--graisse-grasse);
  color: rgba(255,255,255,.9);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.onglet-quoi {
  font-size: var(--texte-xs);
  color: rgba(255,255,255,.45);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.onglet-etat {
  font-size: var(--texte-xs);
  color: rgba(255,255,255,.35);
  flex: none;
}

/* La bascule : un vrai interrupteur, avec sa glissière creusée et son bouton
   en relief qui coulisse. */
.onglet-bascule { flex: none; cursor: pointer; display: flex; }
.onglet-bascule input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
}
.bascule-piste {
  display: block;
  width: 34px; height: 19px;
  border-radius: 999px;
  background: var(--creux);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.7);
  position: relative;
  transition: background .18s ease;
}
.bascule-piste::after {
  content: "";
  position: absolute;
  top: 2.5px; left: 2.5px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: linear-gradient(180deg, #4A5364 0%, #333B49 100%);
  box-shadow: 0 1px 2px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.18);
  transition: transform .18s cubic-bezier(.4,0,.2,1), background .18s ease;
}
.onglet-bascule input:checked + .bascule-piste::after {
  transform: translateX(15px);
  background: linear-gradient(180deg, #F0C368 0%, var(--luire) 100%);
}
.onglet-bascule input:focus-visible + .bascule-piste {
  outline: 2px solid var(--luire);
  outline-offset: 2px;
}
.bascule-petite .bascule-piste { width: 30px; height: 17px; }
.bascule-petite .bascule-piste::after { width: 12px; height: 12px; }
.bascule-petite input:checked + .bascule-piste::after { transform: translateX(13px); }

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

/* --------------------------------------------------------------------------
   LE PLAN DE TRAVAIL
   -------------------------------------------------------------------------- */

.estime-plan {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(230px, 300px);
  gap: clamp(12px, 1.4vw, 18px);
  align-items: start;
}

/* Le panneau du module ouvert : une plaque encastrée dans le châssis.
   Ses coins sont généreux — c'est la plaque qu'on voit se terminer en bas du
   calculateur, et un bord droit y trancherait la silhouette. */
.panneau {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: clamp(12px, 1.4vw, 16px);
  border-radius: 14px;
  background: rgba(0,0,0,.22);
  box-shadow:
    inset 0 1px 3px rgba(0,0,0,.5),
    inset 0 -1px 0 var(--arete);
}
.panneau[hidden] { display: none; }

/* ⚠️ LE PANNEAU NE DÉFILE PAS. Il a eu un `max-height` avec `overflow: auto`
   pendant quelques heures le 27 août 2026 ; retiré le jour même. Karl voulait
   que l'outil GRANDISSE plutôt que de comprimer son contenu — voir la note sur
   `min-height` dans `.pile .section-estime`.

   Il n'existe désormais AUCUNE zone défilante dans l'instrument. */

/* Un module décoché : visible, inerte. */
.panneau.est-eteint { opacity: .38; }
.panneau.est-eteint .glissiere,
.panneau.est-eteint .ecran { pointer-events: none; }

.panneau-intro {
  margin: 0;
  font-size: var(--texte-sm);
  line-height: 1.55;
  color: rgba(255,255,255,.55);
}

.bloc-rangee {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

/* --------------------------------------------------------------------------
   LES COMMANDES
   -------------------------------------------------------------------------- */

.bloc-commande {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(255,255,255,.035) 0%, rgba(255,255,255,0) 100%);
  box-shadow: inset 0 1px 0 var(--arete);
}

.commande-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--espace-3);
}
.commande-titre {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255,255,255,.5);
}
/* La lecture de la commande : où elle est posée. Sur un instrument, une
   commande dit toujours sa valeur. */
.commande-lecture {
  font-family: var(--police-titre);
  font-size: var(--texte-sm);
  font-weight: var(--graisse-grasse);
  color: var(--luire);
  white-space: nowrap;
}
.commande-lecture.est-rempli { color: var(--luire); }
.commande-aide {
  margin: 0;
  font-size: .78rem;
  line-height: 1.45;
  color: rgba(255,255,255,.42);
  min-height: 2.1em;
}

/* --------------------------------------------------------------------------
   LES GLISSIÈRES — de vrais potentiomètres
   -------------------------------------------------------------------------- */

/* ---- LES CRANS ---------------------------------------------------------

   Karl : « les curseurs sont divisés sur soit deux, trois, quatre points
   d'arrêt, j'aimerais une marque dans le curseur qui montre combien d'options
   il y a. »

   C'est exactement ce que fait un vrai potentiomètre : ses graduations sont
   gravées sur le boîtier, et on sait d'un coup d'œil combien de positions la
   commande a.

   Le dessin est un dégradé répété posé DERRIÈRE la piste : `--pas-cran` est
   calculé par `estime.js` depuis le `max` du curseur, donc un réglage qui
   gagne un palier gagne son cran tout seul. Le fond est décalé d'un demi-cran
   pour que les traits tombent exactement là où le bouton s'arrête. */
.glissiere {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 22px;
  cursor: pointer;
  margin: 0;

  background: transparent;
}

/* La piste est CREUSÉE dans la plaque : ombre intérieure, arête claire en bas. */
.glissiere::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  /* Les encoches sont peintes DANS la piste : c'est là qu'elles ont un sens,
     et c'est ainsi que les porte un vrai potentiomètre.

     `--pas-cran` est calculé par `estime.js` depuis le `max` du curseur, donc
     un réglage qui gagne un palier gagne son cran tout seul. Le fond est
     resserré de 10 px de chaque côté pour que les traits tombent là où le
     bouton s'arrête réellement — sa moitié de largeur. */
  /* ⚠️ LE CRAN DE LA POSITION D'ORIGINE.

     Le motif répété peint un trait à la FIN de chaque intervalle : les crans
     tombaient donc sur les positions 2, 3, 4… et la première n'en avait pas.
     Karl, 28 août 2026 : « les onglets qui sont séparés par des lignes n'ont
     pas tous une ligne à la position numéro un. Probablement parce que c'est la
     position originale du curseur. Mais lorsqu'on bouge le curseur, il faut
     qu'il y ait aussi une ligne blanche à la position initiale. »

     Une couche supplémentaire pose ce premier trait à l'origine. Elle est
     placée AVANT le motif répété pour que, sur un curseur à deux positions,
     les deux traits se peignent bien aux deux extrémités. */
  background:
    linear-gradient(
      to right,
      rgba(255,255,255,.3) 0 1px,
      transparent 1px
    ) 10px 0 / calc(100% - 20px) 100% no-repeat,
    repeating-linear-gradient(
      to right,
      transparent 0 calc(var(--pas-cran, 25%) - 1px),
      rgba(255,255,255,.3) calc(var(--pas-cran, 25%) - 1px) var(--pas-cran, 25%)
    ) 10px 0 / calc(100% - 20px) 100% no-repeat,
    var(--creux);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.8), 0 1px 0 var(--arete);
}
.glissiere::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  /* ⚠️ LE CRAN DE LA POSITION D'ORIGINE.

     Le motif répété peint un trait à la FIN de chaque intervalle : les crans
     tombaient donc sur les positions 2, 3, 4… et la première n'en avait pas.
     Karl, 28 août 2026 : « les onglets qui sont séparés par des lignes n'ont
     pas tous une ligne à la position numéro un. Probablement parce que c'est la
     position originale du curseur. Mais lorsqu'on bouge le curseur, il faut
     qu'il y ait aussi une ligne blanche à la position initiale. »

     Une couche supplémentaire pose ce premier trait à l'origine. Elle est
     placée AVANT le motif répété pour que, sur un curseur à deux positions,
     les deux traits se peignent bien aux deux extrémités. */
  background:
    linear-gradient(
      to right,
      rgba(255,255,255,.3) 0 1px,
      transparent 1px
    ) 10px 0 / calc(100% - 20px) 100% no-repeat,
    repeating-linear-gradient(
      to right,
      transparent 0 calc(var(--pas-cran, 25%) - 1px),
      rgba(255,255,255,.3) calc(var(--pas-cran, 25%) - 1px) var(--pas-cran, 25%)
    ) 10px 0 / calc(100% - 20px) 100% no-repeat,
    var(--creux);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.8), 0 1px 0 var(--arete);
}

/* Le bouton du potentiomètre : bombé, avec une nervure centrale comme sur les
   vraies commandes. C'est le détail qui fait qu'on a envie de le prendre. */
.glissiere::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 20px; height: 20px;
  margin-top: -7px;
  border-radius: 6px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.22) 0%, rgba(255,255,255,0) 45%),
    linear-gradient(180deg, #5A6373 0%, #363E4C 100%);
  box-shadow:
    0 1px 2px rgba(0,0,0,.7),
    0 3px 6px -2px rgba(0,0,0,.5),
    inset 0 -1px 0 rgba(0,0,0,.4);
  border: 1px solid rgba(0,0,0,.5);
  transition: transform .1s ease, background .16s ease;
}
.glissiere::-moz-range-thumb {
  width: 20px; height: 20px;
  border-radius: 6px;
  background: linear-gradient(180deg, #5A6373 0%, #363E4C 100%);
  box-shadow: 0 1px 2px rgba(0,0,0,.7), 0 3px 6px -2px rgba(0,0,0,.5);
  border: 1px solid rgba(0,0,0,.5);
}
.glissiere:hover::-webkit-slider-thumb {
  background:
    linear-gradient(180deg, rgba(255,255,255,.28) 0%, rgba(255,255,255,0) 45%),
    linear-gradient(180deg, #66707F 0%, #3E4655 100%);
}
.glissiere:active::-webkit-slider-thumb { transform: scale(.94); }
.glissiere:focus-visible { outline: 2px solid var(--luire); outline-offset: 4px; }

/* --------------------------------------------------------------------------
   LES BOUTONS D'ÉCRAN
   -------------------------------------------------------------------------- */

.ecrans { display: flex; gap: 6px; }

/* Trois touches. Un clic choisit, point — pas de double sens : Karl a écarté
   le double clic, « les gens ne vont probablement pas cliquer une deuxième
   fois ». La touche choisie s'enfonce et s'allume. */
.ecran {
  appearance: none;
  border: 1px solid rgba(0,0,0,.45);
  flex: 1 1 0;
  min-width: 0;
  cursor: pointer;
  font: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 4px 6px;
  border-radius: 9px;
  color: rgba(255,255,255,.45);
  background: linear-gradient(180deg, #2A303C 0%, #1D222C 100%);
  box-shadow:
    inset 0 1px 0 var(--arete-vive),
    0 1px 0 rgba(0,0,0,.5),
    0 3px 5px -2px rgba(0,0,0,.45);
  transition: transform .12s ease, box-shadow .12s ease, color .16s ease;
}
.ecran:hover { color: rgba(255,255,255,.75); }
.ecran:active { transform: translateY(1px); }
.ecran:focus-visible { outline: 2px solid var(--luire); outline-offset: 2px; }

.ecran.est-choisi {
  color: var(--luire);
  transform: translateY(1px);
  background: linear-gradient(180deg, #13171F 0%, #1A1F29 100%);
  box-shadow:
    inset 0 2px 5px rgba(0,0,0,.65),
    inset 0 0 12px rgba(228,169,60,.12);
  border-color: rgba(228,169,60,.3);
}

.ecran-dessin { display: flex; }
.ecran-dessin svg { width: auto; height: 26px; }
.ecran-nom {
  font-size: .68rem;
  font-weight: var(--graisse-moyenne);
  letter-spacing: .04em;
}

/* --------------------------------------------------------------------------
   LES PARTIES DU COMMERCE
   -------------------------------------------------------------------------- */

.bloc-parties { gap: 8px; }

/* La liste défile pour son propre compte, bornée en hauteur.

   ⚠️ CE DÉFILEMENT-CI EST ACCEPTABLE, contrairement à celui qu'on a supprimé.
   La différence : il est DANS un outil qu'on a demandé à ouvrir, sur une zone
   petite et clairement délimitée. Celui qu'on a supprimé capturait le
   défilement de la page entière sur quelqu'un qui passait simplement devant.

   Mesuré : douze faits dépliés font 590 px et repoussent l'afficheur hors de
   l'écran, ce qui casse le principe même de l'instrument — voir le chiffre
   bouger pendant qu'on manipule. */
/* Quatre colonnes. Les libellés ont été raccourcis pour tenir sur une ligne
   à cette largeur — la formulation complète reste au survol, dans le `title`
   posé par `estime.js`. */
.parties-grille {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2px 10px;
}
/* ⚠️ LES PALIERS SE MESURENT SUR LE CONTENEUR, PAS SUR LA FENÊTRE.

   Une requête média regarde la largeur de l'ÉCRAN. Or la liste vit dans un
   panneau bien plus étroit que lui — sur un écran de 1 440 px, elle n'en
   occupe que 900. Les paliers écrits en `max-width` se déclenchaient donc trop
   tôt : mesuré, la grille tombait à deux colonnes sur un grand écran.

   `container-type: inline-size` fait mesurer le conteneur lui-même. */
.bloc-parties { container-type: inline-size; }

@container (max-width: 720px) {
  .parties-grille { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@container (max-width: 520px) {
  .parties-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (max-width: 330px) {
  .parties-grille { grid-template-columns: minmax(0, 1fr); }
}

/* ---- LA LISTE SE REPLIE DERRIÈRE UN « VOIR PLUS » ----------------------

   Karl : « il faudrait compresser ça encore un petit peu plus, l'écriture
   pourrait être un peu plus petite, et je mettrais seulement une rangée ou
   deux d'éléments avec un bouton voir plus pour pouvoir dérouler la liste
   entière. »

   Deux rangées visibles suffisent à comprendre ce qu'on attend ; le bouton
   ouvre le reste. Cela règle du même coup le problème de hauteur qui a occupé
   toute la journée : la liste était le seul poste vraiment long. */
/* ⚠️ UNE RANGÉE ET DEMIE, PAS DEUX.

   Karl : « j'aimerais qu'elles soient un peu plus petites pour qu'il y ait
   quatre éléments par rangée, et que lorsque c'est roulé, qu'on voie juste une
   rangée et demie. Donc le début de la rangée suivante, et que ça ne prenne
   pas plus de place que ça. »

   Montrer le HAUT de la rangée suivante dit qu'il y a une suite mieux qu'un
   dégradé : on voit les éléments commencer, donc on sait qu'ils continuent.

   `overflow: hidden` est SANS DANGER ici : il coupe, il ne fait pas défiler,
   donc la molette continue d'aller à la page. */
.bloc-parties:not(.est-tout) .parties-grille {
  /* Une rangée complète plus la moitié de la suivante : on voit les éléments
     COMMENCER, ce qui dit qu'il y a une suite mieux qu'un dégradé. Karl :
     « qu'on voie juste une rangée et demie, donc le début de la rangée
     suivante ». Mesuré, une rangée fait 29 px. */
  /* ⚠️ LA COUPE SE FOND, ELLE NE TRANCHE PAS.

     Karl, 28 août 2026 : « je n'aime pas comment l'espace What your place has
     est découpé, parce qu'on voit une partie de la deuxième rangée mais elle
     est coupée avec la même couleur de fond. Donc ça ressemble plus à un bug.
     Je ne sais pas s'il faudrait qu'on ajoute un encadré ou simplement qu'on
     enlève l'aperçu de la deuxième rangée. »

     Sa lecture est juste : une ligne de texte tranchée net, sur un fond
     identique de part et d'autre, ne se lit pas comme un aperçu — elle se lit
     comme un défaut d'affichage.

     Plutôt que de supprimer l'aperçu (qui dit utilement qu'il y a une suite)
     ou d'ajouter un encadré (une ligne de plus dans un panneau déjà dense), on
     fait DISPARAÎTRE la seconde rangée en fondu. L'œil comprend alors que le
     contenu continue au-delà, sans qu'aucun bord dur ne suggère une erreur.

     Le masque laisse la première rangée intacte et éteint progressivement la
     seconde. */
  max-height: 46px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom,
    #000 0, #000 26px, rgba(0,0,0,.35) 36px, transparent 46px);
  mask-image: linear-gradient(to bottom,
    #000 0, #000 26px, rgba(0,0,0,.35) 36px, transparent 46px);
}

.parties-plus {
  appearance: none;
  border: 0;
  background: none;
  padding: 5px 0 0;
  font: inherit;
  font-size: .76rem;
  font-weight: var(--graisse-moyenne);
  color: var(--luire);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  align-self: flex-start;
  opacity: .85;
  transition: opacity .14s ease;
}
.parties-plus:hover { opacity: 1; }
.parties-plus:focus-visible { outline: 2px solid var(--luire); outline-offset: 3px; }
.parties-plus::after {
  content: "";
  width: 8px; height: 8px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .2s ease;
}
.bloc-parties.est-tout .parties-plus::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
.bloc-parties.est-tout .parties-plus .mot-plus { display: none; }
.bloc-parties:not(.est-tout) .parties-plus .mot-moins { display: none; }

/* L'écriture des faits est resserrée : ils sont nombreux et courts. */
.partie-nom { font-size: .78rem; line-height: 1.3; }
.partie-note { font-size: .7rem; }

/* Les notes n'apparaissent qu'une fois la liste dépliée : en quatre colonnes
   repliées, elles ne tiendraient pas et la rangée et demie deviendrait
   illisible. Le `title` les rend accessibles au survol dans les deux états. */
.bloc-parties:not(.est-tout) .partie-note { display: none; }
.partie { padding: 6px 6px; gap: 7px; }

/* ---- DÉROULÉ : la liste passe en TROIS colonnes ------------------------

   Le calculateur déroulé faisait 1 188 px sur un écran de 900, dont 590 pour
   la seule liste des douze faits. Or un élément `sticky` plus haut que la
   fenêtre se colle par son haut : son bas reste hors de vue et on ne peut
   jamais l'atteindre. Karl : « je vais en bas et il est coupé au bas, je ne
   peux même pas aller jusqu'en bas ».

   Deux autres pistes ont été essayées et écartées le même jour :

     · Sortir le calculateur du flux collant (`position: static`) : la pile se
       désorganisait entièrement, les trois blocs perdant la feuille sur
       laquelle ils s'appuient.
     · Réorganiser tout le panneau en deux colonnes : la grille cassait la mise
       en page et le cadre passait à 1 975 px.

   La liste est le seul poste vraiment long, et elle se prête à la largeur :
   trois colonnes au lieu de deux la raccourcissent d'un tiers sans rien
   changer d'autre. */
/* La liste est déjà en quatre colonnes dans les deux états : il n'y a plus de
   règle particulière au déroulé. */
/* La logique s'est inversée le 27 août 2026 : les notes étaient masquées au
   DÉROULÉ pour gagner de la hauteur. Elles sont maintenant masquées quand la
   liste est REPLIÉE — où la place manque vraiment — et visibles une fois
   « See everything » ouvert, où il y a de quoi les lire. */

/* Sous 1100 px, trois colonnes deviennent trop étroites et le contenu déborde
   horizontalement — mesuré sur une tablette de 900 px. */


/* Chaque fait est une petite touche à bascule. Le carré à gauche est creusé
   quand il est vide et s'allume quand il est coché — même logique que partout
   ailleurs dans l'objet. */
.partie {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 7px 8px;
  border-radius: 8px;
  cursor: pointer;
  transition: background .14s ease;
}
.partie:hover { background: rgba(255,255,255,.04); }
.partie input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
}
.partie-marque {
  flex: none;
  width: 16px; height: 16px;
  margin-top: 2px;
  border-radius: 5px;
  background: var(--creux);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.8);
  position: relative;
  transition: background .16s ease, box-shadow .16s ease;
}
.partie-marque::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: no-repeat center / 10px 8px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 8'%3E%3Cpath d='M1 4l2.6 2.6L9 1.2' fill='none' stroke='%23151A22' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  opacity: 0;
  transition: opacity .14s ease;
}
.partie.est-coche .partie-marque {
  background: linear-gradient(180deg, #F0C368 0%, var(--luire) 100%);
  box-shadow: 0 1px 2px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.3);
}
.partie.est-coche .partie-marque::after { opacity: 1; }
.partie input:focus-visible + .partie-marque {
  outline: 2px solid var(--luire);
  outline-offset: 2px;
}

.partie-corps { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.partie-nom {
  font-size: var(--texte-sm);
  color: rgba(255,255,255,.86);
  line-height: 1.35;
}
.partie-note {
  font-size: .74rem;
  line-height: 1.4;
  color: rgba(255,255,255,.38);
}

/* --------------------------------------------------------------------------
   LES PHOTOS
   -------------------------------------------------------------------------- */

.bloc-photos[hidden] { display: none; }
/* Le volume ne se règle que si les photos sont demandées : régler un nombre
   d'images qu'on n'a pas commandées ne veut rien dire. */
.bloc-photos .photos-volume {
  opacity: .35;
  pointer-events: none;
  transition: opacity .18s ease;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.bloc-photos.est-actif .photos-volume { opacity: 1; pointer-events: auto; }

/* --------------------------------------------------------------------------
   L'AFFICHEUR — en bas à droite, comme sur un appareil
   -------------------------------------------------------------------------- */

.estime-resultat {
  grid-column: 2;
  grid-row: 1;
  /* ⚠️ L'AFFICHEUR NE COLLE PAS. Il a été `position: sticky` quelques heures
     le 27 août 2026, du temps où le panneau du module défilait à l'intérieur
     de l'outil.

     Depuis que le calculateur grandit au lieu de défiler, tout doit rester
     verrouillé ensemble. Karl : « toute la page bouge, mais il y a la fenêtre
     de prix qui, elle, reste en place et se promène dans la fenêtre du
     calculateur. Ça aussi, ça ne serait pas supposé se passer. Le tout est
     supposé rester verrouillé en place. »

     Il est aligné en haut de sa colonne, et il y reste. */
  align-self: start;

  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: clamp(14px, 1.6vw, 18px);
  border-radius: 12px;

  /* Un creux profond dans le châssis, avec la lueur du chiffre dedans. */
  background: var(--creux);
  box-shadow:
    inset 0 2px 8px rgba(0,0,0,.8),
    inset 0 -1px 0 var(--arete),
    0 0 30px -8px rgba(228,169,60,.12);
}

.resultat-avant {
  margin: 0 0 2px;
  display: flex;
  align-items: baseline;
  gap: var(--espace-3);
  flex-wrap: wrap;
  font-size: var(--texte-sm);
  color: rgba(255,255,255,.38);
}
.resultat-avant .avant-etiquette {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .12em;
  text-transform: uppercase;
}
.resultat-avant s { text-decoration-color: rgba(255,255,255,.3); }

.resultat-etiquette {
  margin: 0;
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255,255,255,.45);
}

/* Le chiffre qui bouge. C'est lui qu'on regarde en manipulant les commandes,
   et c'est pour ça qu'on continue de jouer. */
.resultat-fourchette {
  margin: 0 0 var(--espace-3);
  font-family: var(--police-titre);
  font-weight: var(--graisse-noire);
  font-size: clamp(1.3rem, 2.3vw, 1.85rem);
  line-height: 1.1;
  letter-spacing: -.03em;
  color: var(--luire);
  text-shadow: 0 0 22px rgba(228,169,60,.3);
  font-variant-numeric: tabular-nums;
  text-wrap: balance;
}

.resultat-lancement {
  margin: 0 0 var(--espace-3);
  padding-top: var(--espace-3);
  border-top: 1px solid rgba(255,255,255,.1);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.lancement-titre {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--luire);
}
.lancement-note {
  font-size: .78rem;
  line-height: 1.5;
  color: rgba(255,255,255,.48);
}

.resultat-note {
  margin: 0 0 var(--espace-4);
  font-size: .78rem;
  line-height: 1.5;
  color: rgba(255,255,255,.42);
}

/* L'objectif de hauteur : le calculateur déroulé doit TENIR DANS LA FENÊTRE.
   Un élément collant plus haut qu'elle se colle par le haut, et son bas
   devient inatteignable. Il manquait 33 px — pris sur l'afficheur, dont la
   note explicative est déjà lue dans l'aperçu. */
.estime-cadre:not(.est-replie) .resultat-note { display: none; }
.estime-cadre:not(.est-replie) .resultat-lancement { margin-bottom: var(--espace-4); }

.estime-resultat .btn-primaire {
  background: var(--luire);
  color: var(--wpn-ancre-fixe, #151A22);
  width: 100%;
  justify-content: center;
  box-shadow: 0 1px 0 rgba(0,0,0,.4), 0 4px 12px -4px rgba(228,169,60,.5);
}
.estime-resultat .btn-primaire:hover { filter: brightness(1.06); }

/* --------------------------------------------------------------------------
   LE FORMULAIRE D'ENVOI
   -------------------------------------------------------------------------- */

/* Il vit DANS l'instrument, replié tant qu'on ne l'a pas demandé : c'est la
   dernière commande de l'appareil, pas une fenêtre par-dessus. Le prix reste
   visible pendant qu'on le remplit — on sait ce qu'on envoie. */
.estime-envoi {
  border-radius: 12px;
  padding: clamp(12px, 1.4vw, 16px);
  background: rgba(0,0,0,.22);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.5), inset 0 -1px 0 var(--arete);
}
.estime-envoi[hidden] { display: none; }

.envoi-intro {
  margin: 0 0 var(--espace-4);
  font-size: var(--texte-sm);
  line-height: 1.5;
  color: rgba(255,255,255,.6);
  max-width: 54ch;
}

.envoi-champs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px 12px;
}
.champ-large { grid-column: 1 / -1; }

.champ { display: flex; flex-direction: column; gap: 4px; }
.champ-nom {
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255,255,255,.5);
}
.champ-nom em {
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(255,255,255,.32);
}

/* Les champs sont CREUSÉS comme le reste des commandes : même logique
   d'ombre intérieure, pour qu'ils appartiennent au même objet. */
.champ input,
.champ textarea {
  font: inherit;
  font-size: var(--texte-sm);
  color: #F4F4F2;
  background: var(--creux);
  border: 1px solid rgba(0,0,0,.5);
  border-radius: 7px;
  padding: 8px 10px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,.6);
  transition: box-shadow .14s ease, border-color .14s ease;
  width: 100%;
  box-sizing: border-box;
}
.champ textarea { resize: vertical; min-height: 54px; }
.champ input:focus,
.champ textarea:focus {
  outline: none;
  border-color: rgba(228,169,60,.4);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.6), 0 0 0 2px rgba(228,169,60,.22);
}

/* Le piège à robots : hors écran, jamais masqué par `display: none` — les
   remplisseurs automatiques ignorent ce qui n'existe pas. */
.champ-piege {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.envoi-pied {
  display: flex;
  align-items: center;
  gap: var(--espace-4);
  margin-top: var(--espace-4);
  flex-wrap: wrap;
}
.envoi-pied .btn-primaire {
  background: var(--luire);
  color: #151A22;
  box-shadow: 0 1px 0 rgba(0,0,0,.4), 0 4px 12px -4px rgba(228,169,60,.5);
}
.envoi-pied .btn-primaire:disabled { opacity: .55; cursor: default; }

.envoi-annuler {
  appearance: none;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--texte-sm);
  color: rgba(255,255,255,.45);
  cursor: pointer;
  padding: 4px 2px;
}
.envoi-annuler:hover { color: rgba(255,255,255,.75); }
.envoi-annuler:focus-visible { outline: 2px solid var(--luire); outline-offset: 3px; }

.envoi-message {
  margin: var(--espace-3) 0 0;
  font-size: var(--texte-sm);
  min-height: 1.4em;
  color: rgba(255,255,255,.55);
}
.envoi-message.est-ok { color: var(--wpn-vert-400, #5FB98D); }
.envoi-message.est-erreur { color: #E08A7A; }

/* --------------------------------------------------------------------------
   LA VISITE GUIDÉE
   -------------------------------------------------------------------------- */

/* ⚠️ EN SUPERPOSITION, JAMAIS DANS LE FLUX.

   Karl : « je ne veux pas que ça fasse partie de l'interface, je veux que ce
   soit de petites fenêtres qui s'ajoutent par-dessus, dans la section où on
   doit porter l'attention. »

   Une première version insérait la bulle entre les onglets et le contenu :
   elle poussait tout vers le bas et faisait partie de la page. Celle-ci est en
   `position: absolute` dans la section — elle se pose PAR-DESSUS et ne
   déplace rien. */

.section-estime { position: relative; }

/* ⚠️ PLUS DE VOILE SOMBRE.

   Karl : « le tutoriel, j'ai l'impression qu'il nous prend un peu otage. C'est
   comme tout arrête, l'image s'assombrit, on doit répondre aux questions.
   J'aimerais que ça soit un peu moins lourd et que ça accompagne la navigation
   plutôt que la stopper complètement. »

   Le voile a été retiré : les fenêtres sont maintenant des repères posés à
   côté de ce dont elles parlent, qu'on peut lire ou ignorer. On continue de
   cliquer dans l'outil pendant qu'elles sont là — rien n'est bloqué.

   La classe est conservée pour ne pas casser le JavaScript qui la crée ; elle
   ne peint plus rien. */
.visite-voile { display: none; }

.visite {
  position: absolute;
  z-index: 41;
  /* Plus étroite qu'avant : sans voile, une grande fenêtre écraserait ce
     qu'elle est censée désigner. */
  /* ⚠️ PLUS PETITE ENCORE. Karl, 28 août 2026 : « l'indication est simple, et
     la fenêtre pourrait être plus petite. » Les textes ont été resserrés en
     conséquence — une phrase pour ce que c'est, une pour ce qu'on y décide. */
  width: min(252px, calc(100% - 32px));
  padding: 11px 13px 10px;
  border-radius: 12px;
  background: linear-gradient(180deg, #2C3342 0%, #1F2530 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    0 14px 34px -10px rgba(0,0,0,.75),
    0 0 0 1px rgba(228,169,60,.28);
  display: flex;
  flex-direction: column;
  gap: 5px;
  /* ⚠️ UN FONDU, ET RIEN D'AUTRE.

     Karl, 29 août 2026 : « la première fenêtre apparaît et glisse un peu de la
     droite vers la gauche. Ce mouvement-là n'est pas nécessaire, ça pourrait
     seulement être un fading de la fenêtre. »

     Le glissement n'était pas voulu : il venait d'une INTERPOLATION ENTRE DEUX
     `transform`. La fenêtre centrée passe de `translateY(-6px)` à
     `translate(-50%, -50%)` — le navigateur anime donc un déplacement
     horizontal de la moitié de sa largeur, de la droite vers la gauche.

     ⚠️ NE PAS remettre `transform` dans la transition : le centrage se fait par
     `translate(-50%, -50%)`, et toute transition sur cette propriété rejouera
     le glissement. L'opacité seule suffit. */
  opacity: 0;
  transition: opacity .22s ease;
}
.visite.est-la { opacity: 1; }

/* Au centre du cadre pour la première étape, qui ne pointe rien de précis. */
.visite.est-centre {
  left: 50%;
  top: 42%;
  /* Le centrage est FIXE : il ne s'anime jamais, sans quoi le fondu
     s'accompagnerait d'un glissement — voir la note de `.visite`. */
  transform: translate(-50%, -50%);
  width: min(400px, calc(100% - 40px));
}

/* La flèche vers l'onglet dont la fenêtre parle : sans elle, on ne sait pas
   de quoi il s'agit quand plusieurs onglets sont proches. */
.visite.vise-bas::before {
  content: "";
  position: absolute;
  top: -7px;
  left: 26px;
  width: 13px; height: 13px;
  background: #2C3342;
  border-left: 1px solid rgba(228,169,60,.28);
  border-top: 1px solid rgba(228,169,60,.28);
  transform: rotate(45deg);
  border-radius: 2px 0 0 0;
}

.visite-rang {
  font-family: var(--police-titre);
  font-size: .7rem;
  font-weight: var(--graisse-grasse);
  letter-spacing: .1em;
  color: rgba(255,255,255,.35);
}
.visite-titre {
  margin: 0;
  font-family: var(--police-titre);
  font-size: 1rem;
  font-weight: var(--graisse-grasse);
  color: var(--luire);
  line-height: 1.25;
}
.visite-texte {
  margin: 0;
  font-size: var(--texte-sm);
  line-height: 1.5;
  color: rgba(255,255,255,.7);
}
.visite-pied {
  display: flex;
  align-items: center;
  gap: var(--espace-4);
  margin-top: 6px;
}
.visite-ok {
  appearance: none;
  border: 0;
  padding: 6px 18px;
  border-radius: 7px;
  font: inherit;
  font-size: .84rem;
  font-weight: var(--graisse-moyenne);
  color: #151A22;
  background: var(--luire);
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(0,0,0,.4);
}
.visite-ok:hover { filter: brightness(1.06); }
.visite-ok:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Une sortie visible : une visite qu'on ne peut pas quitter agace, et celui
   qui la quitte n'en avait pas besoin. */
.visite-passer {
  appearance: none;
  border: 0;
  background: none;
  font: inherit;
  font-size: .82rem;
  color: rgba(255,255,255,.4);
  cursor: pointer;
  padding: 4px 2px;
}
.visite-passer:hover { color: rgba(255,255,255,.7); }
.visite-passer:focus-visible { outline: 2px solid var(--luire); outline-offset: 3px; }

/* L'onglet dont la fenêtre parle reste au-dessus du voile : c'est lui qu'on
   doit regarder. */
.visite-voile ~ .visite { pointer-events: auto; }
.section-estime:has(.visite) .onglet[data-module] { position: relative; }

@media (max-width: 700px) {
  .visite { width: calc(100% - 24px); left: 12px !important; }
  .visite.vise-bas::before { left: 24px; }
}

/* Une entrée acquise — le compte, dans la liste du réseau : cochée, inerte,
   et visiblement pas un choix. */
.partie.est-acquis { opacity: .62; cursor: default; }
.partie.est-acquis:hover { background: none; }

/* --------------------------------------------------------------------------
   PETIT ÉCRAN
   -------------------------------------------------------------------------- */

@media (max-width: 880px) {
  .estime-plan { grid-template-columns: minmax(0, 1fr); }

  /* En une seule colonne, l'afficheur passe AVANT les commandes et se colle
     en haut : c'est ce qui remplace sa position « en bas à droite », qui n'a
     plus de sens sans deuxième colonne. Le principe est le même — voir le
     chiffre bouger pendant qu'on manipule. */
  .estime-resultat {
    grid-column: 1;
    grid-row: auto;
    order: -1;
    /* ⚠️ PAS DE `sticky` : plus rien ne défile à l'intérieur de l'outil, donc
       tout doit rester verrouillé ensemble. Karl : « le tout est supposé rester
       verrouillé en place ». L'afficheur passe simplement en premier. */
    /* Condensé : mesuré, l'afficheur complet occupait tout l'écran et le
       visiteur ne voyait plus une seule commande — ce qui vide de son sens le
       fait de le coller. */
    padding-block: 12px;
  }
  .estime-resultat .lancement-note,
  .estime-resultat .resultat-note { display: none; }
  .resultat-lancement { margin-bottom: 8px; padding-top: 8px; }
  .resultat-fourchette { margin-bottom: 8px; }

  /* Sur petit écran non plus, rien ne défile à l'intérieur : le bloc grandit
     et pousse la page, exactement comme sur grand écran. */
  .bloc-rangee { grid-template-columns: minmax(0, 1fr); }
  /* Les onglets restent sur UNE LIGNE, en trois colonnes étroites.

     Empilés, ils faisaient 390 px à eux seuls — soit la moitié de l'écran
     avant d'atteindre la moindre commande, ce qui donne l'impression d'un
     formulaire interminable. Sur un instrument, les touches sont côte à côte :
     c'est vrai sur un petit boîtier comme sur un grand. */
  .estime-onglets {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 5px;
  }
  .onglet {
    flex-direction: column;
    align-items: flex-start;
    gap: 5px;
    padding: 8px 9px;
  }
  /* La description disparaît : trois mots dans une colonne de 110 px se
     cassent sur quatre lignes et n'aident personne. Le nom suffit. */
  .onglet-quoi { display: none; }
  /* « Your Window » ne tient pas dans une colonne de 110 px : le mot « Your »
     est le même sur les trois et n'apprend rien. Il est masqué, et seul le nom
     du module reste — c'est lui qui distingue. */
  .onglet-nom {
    font-size: .78rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* « Your » est le même sur les trois onglets et n'apprend rien : il
     disparaît sur petit écran, où chaque pixel de largeur compte. */
  .onglet-nom .mot-your { display: none; }
  .onglet-etat { display: none; }
  .onglet-bascule { align-self: flex-start; }
  .parties-grille { grid-template-columns: minmax(0, 1fr); }
  .envoi-champs { grid-template-columns: minmax(0, 1fr); }
  .facade-afficheur { align-items: flex-start; }
  .estime-facade { gap: var(--espace-4); }
}

@media (prefers-reduced-motion: reduce) {
  .estime-cadre *, .estime-cadre *::before, .estime-cadre *::after {
    transition: none !important;
  }
}

/* --- Les échancrures d'un bloc OUVERT ------------------------------------
   Karl, 27 août 2026 : « j'ai déroulé l'onglet violet et là, les petits coins,
   ils sont gris. Ce qui découpe mal avec l'onglet bleu au-dessus. »

   La cause n'est pas le calage de couleur, qui est correct : c'est
   `.bloc.est-ouvert { z-index: 20 }`. Un `z-index` numérique crée un CONTEXTE
   D'EMPILEMENT, et un enfant en `z-index: -1` ne peut plus en sortir. Le
   pseudo-élément de remplissage, prévu pour se peindre DERRIÈRE la feuille et
   sur la page, se retrouve piégé sous le fond du bloc lui-même : les
   échancrures redeviennent grises, et l'illusion des feuilles glissées l'une
   sur l'autre se brise à l'endroit précis où on la regarde.

   Le remède est de le remonter AU-DESSUS du fond du bloc quand celui-ci est
   ouvert. C'est sans risque parce que le masque ne laisse subsister que les
   deux petits triangles courbes des coins, qui sont par construction HORS de
   la silhouette arrondie : la couche ne peut donc rien recouvrir du bloc.

   Ne pas « corriger » cela en retirant le `z-index: 20` du bloc ouvert : il
   est ce qui pose le bloc déroulé au-dessus de ses voisins fermés. */
.bloc.est-ouvert::before,
.pile .section-estime.est-ouvert::before {
  z-index: 0;
}

/* --- Les puces de métier sur DEUX colonnes --------------------------------
   Karl, 27 août 2026 : « par métier, on voit comme trois types d'utilité.
   J'ai l'impression qu'il y aurait de la place pour six en mettant une autre
   colonne à côté. »

   Six puces en une seule colonne allongeraient le bloc de moitié et
   pousseraient les boutons de métier hors de l'écran. En deux colonnes de
   trois, la hauteur reste EXACTEMENT celle d'avant — c'est ce qui permet
   d'en montrer deux fois plus sans toucher à la mise en page validée.

   `grid-auto-flow: column` avec deux rangées imposées remplit la première
   colonne de haut en bas avant de passer à la seconde : l'ordre de lecture
   suit donc l'ordre du tableau dans `metiers.js`, ce qui n'est pas le cas
   d'un simple `grid-template-columns: 1fr 1fr`, qui remplirait en zigzag.

   Le `min-height` qui réservait la place de trois puces reste valable : deux
   colonnes de trois ont la même hauteur qu'une colonne de trois, donc rien ne
   saute quand on change de métier. */
.bloc-liste[data-puces-metier] {
  grid-template-rows: repeat(3, auto);
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  column-gap: var(--espace-6);
}

/* Sous cette largeur la colonne texte devient trop étroite pour deux
   colonnes de puces : les libellés se couperaient sur trois lignes chacun.
   On revient à une seule colonne, et la liste s'allonge — c'est acceptable
   ici, puisque la mise en page est déjà verticale. */
@media (max-width: 980px) {
  .bloc-liste[data-puces-metier] {
    grid-auto-flow: row;
    grid-template-rows: none;
  }
}

/* --- Les points sous l'image de métier ------------------------------------
   Karl, 27 août 2026 : « en dessous de l'image, je mettrai trois petits
   points pour montrer qu'il y a trois photos et qu'on peut les changer. »

   Ils sont posés sous la pile d'images, centrés sur elle. Volontairement
   discrets : ce sont des repères, pas des commandes principales — les boutons
   de métier, eux, restent nommés et explicites.

   Le point actif s'élargit en pastille plutôt que de simplement changer
   d'opacité : la différence se voit d'un coup d'œil, y compris pour qui
   distingue mal les contrastes proches. */
.masc-points {
  display: flex; justify-content: center; align-items: center;
  gap: 9px;
  margin-top: var(--espace-4);
}
.masc-point {
  appearance: none; border: 0; padding: 0;
  width: 8px; height: 8px; border-radius: 999px;
  background: currentColor; opacity: .3;
  cursor: pointer;
  transition: opacity .3s ease, width .3s cubic-bezier(.33,0,.2,1);
}
.masc-point:hover { opacity: .6; }
.masc-point[aria-pressed="true"] { width: 22px; opacity: .95; }
/* La cible tactile fait 44 px sans que le point grossisse : sur un téléphone,
   viser un rond de 8 px est un exercice d'adresse. */
.masc-point::before {
  content: ""; position: absolute;
  inset: -18px -7px;
}
.masc-point { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .masc-point { transition: none; }
}

/* ==========================================================================
   LES COMMANDES DU CALCULATEUR — poignées et cases RONDES
   --------------------------------------------------------------------------
   Karl, 27 août 2026, deux consignes distinctes :

     1. « où il est supposé avoir des curseurs, on ne voit pas les curseurs.
        Donc il devrait y avoir une ligne. »
     2. « je ne veux pas qu'on utilise le losange, je ne trouve pas que ça
        fonctionne. J'utiliserai un point de curseur rond comme les curseurs
        normales. » Et pour les cases : « je ne mettrai pas des losanges, je
        mettrai des checkboxes rondes » avec un check mark.

   ## Pourquoi on ne voyait rien

   Le cadre du calculateur est passé sur fond SOMBRE, et il inverse ses jetons
   de couleur pour que le contenu suive. Mais la piste des curseurs et la
   bordure des cases étaient écrites en dur — `rgba(27,31,42,.16)`, soit de
   l'encre presque noire. Sur le fond sombre, elles disparaissaient purement et
   simplement : la ligne existait, on ne pouvait pas la voir.

   Les valeurs ci-dessous sont donc en BLANC translucide, qui se lit sur le
   fond sombre du cadre et resterait visible si le cadre revenait au clair.

   ## Le losange s'en va

   Karl : « ce n'est pas parce que ton losange, c'est ton logo, qu'il faut
   mettre des losanges partout. » Un curseur porte une poignée ronde et une
   case à cocher est une case à cocher, parce que ce sont les formes que tout
   le monde reconnaît déjà. Ne pas réintroduire le motif de la marque ici au
   nom de la cohérence visuelle : l'argument a été écarté explicitement.
   ========================================================================== */

/* --- La piste et la poignée ---------------------------------------------- */

.reglage input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 34px;
  background: transparent; cursor: pointer;
}

/* La piste est plus épaisse qu'avant (5 px contre 3) : une ligne d'un pixel
   et demi sur un fond sombre se devine plus qu'elle ne se voit. */
.reglage input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: 999px;
  background: rgba(255,255,255,.22);
}
.reglage input[type="range"]::-moz-range-track {
  height: 5px; border-radius: 999px;
  background: rgba(255,255,255,.22);
}

/* La poignée : un rond franc, blanc, avec un halo sombre qui le détache de
   la piste. La marge négative vaut la moitié de l'écart entre la poignée et
   la piste — c'est ce qui la centre verticalement sur la ligne. */
.reglage input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px;
  margin-top: -7.5px;
  background: #fff;
  border: 0; border-radius: 50%;
  box-shadow: 0 1px 4px rgba(0,0,0,.45);
  /* Seule la TAILLE s'anime au survol. La position, elle, ne doit jamais
     être animée : une transition sur le déplacement ferait traîner la
     poignée derrière le doigt, et c'est précisément le « saccadé » que Karl
     ne veut pas. Le glissement d'un `range` suit l'entrée au pixel près
     tant qu'on ne l'anime pas. */
  transition: transform .18s ease, box-shadow .18s ease;
}
.reglage input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px;
  background: #fff;
  border: 0; border-radius: 50%;
  box-shadow: 0 1px 4px rgba(0,0,0,.45);
  transition: transform .18s ease, box-shadow .18s ease;
}

.reglage input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.12); }
.reglage input[type="range"]:hover::-moz-range-thumb { transform: scale(1.12); }
/* Pendant qu'on tire, la poignée reste franche : elle ne grossit plus, pour
   que l'attention aille au chiffre qui change, pas au bouton qu'on tient. */
.reglage input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.04); }
.reglage input[type="range"]:active::-moz-range-thumb { transform: scale(1.04); }

.reglage input[type="range"]:focus-visible::-webkit-slider-thumb {
  outline: 2px solid #fff; outline-offset: 3px;
}
.reglage input[type="range"]:focus-visible::-moz-range-thumb {
  outline: 2px solid #fff; outline-offset: 3px;
}

/* Les curseurs de l'échange publicitaire gardent leur distinction — céder de
   la place n'est pas le même geste que construire — mais elle passe par un
   ANNEAU jaune autour d'une poignée qui reste blanche, plutôt que par une
   poignée entièrement jaune : sur fond sombre, le jaune plein perdait le
   contraste franc qui rend la poignée repérable. */
#curseur-principale::-webkit-slider-thumb,
#curseur-secondaire::-webkit-slider-thumb {
  background: #fff;
  box-shadow: 0 0 0 3px var(--wpn-jaune-400), 0 1px 4px rgba(0,0,0,.45);
}
#curseur-principale::-moz-range-thumb,
#curseur-secondaire::-moz-range-thumb {
  background: #fff;
  box-shadow: 0 0 0 3px var(--wpn-jaune-400), 0 1px 4px rgba(0,0,0,.45);
}

@media (prefers-reduced-motion: reduce) {
  .reglage input[type="range"]::-webkit-slider-thumb,
  .reglage input[type="range"]::-moz-range-thumb { transition: none; }
}

/* --- Les cases à cocher, rondes et cochées ------------------------------- */

.module input[type="checkbox"] {
  -webkit-appearance: none; appearance: none;
  flex: none;
  width: 22px; height: 22px; margin: 1px 0 0;
  border: 2px solid rgba(255,255,255,.34);
  border-radius: 50%;
  /* Le losange partait d'ici : la rotation est explicitement annulée, sans
     quoi la règle plus haut dans le fichier continuerait de s'appliquer. */
  transform: none;
  cursor: inherit;
  position: relative;
  transition: background .18s ease, border-color .18s ease;
}

/* Le crochet est dessiné en CSS plutôt qu'en caractère : une coche
   typographique change de forme d'une police à l'autre et se centre mal.
   Deux bords d'une boîte tournée à 45°, c'est la même coche partout. */
.module input[type="checkbox"]::after {
  content: "";
  position: absolute;
  left: 50%; top: 46%;
  width: 5px; height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -50%) rotate(45deg) scale(.4);
  opacity: 0;
  transition: opacity .16s ease, transform .16s cubic-bezier(.3,1.4,.5,1);
}
.module input[type="checkbox"]:checked {
  background: var(--wpn-bleu-500, #2E6FD6);
  border-color: var(--wpn-bleu-500, #2E6FD6);
}
.module input[type="checkbox"]:checked::after {
  opacity: 1;
  transform: translate(-50%, -50%) rotate(45deg) scale(1);
}
.module input[type="checkbox"]:focus-visible {
  outline: 2px solid #fff; outline-offset: 3px;
}
.module-banniere input[type="checkbox"]:checked {
  background: var(--wpn-jaune-400);
  border-color: var(--wpn-jaune-400);
}
/* Sur le jaune, un crochet blanc ne se lit pas : il passe à l'encre. */
.module-banniere input[type="checkbox"]:checked::after { border-color: var(--wpn-ancre-vrai, #1B1F2A); }

@media (prefers-reduced-motion: reduce) {
  .module input[type="checkbox"],
  .module input[type="checkbox"]::after { transition: none; }
}

/* --- « Ce que votre commerce contient » ------------------------------------
   La liste de faits à cocher, qui mesure la charge réelle. Elle est plus
   longue que les trois modules : sur deux colonnes, elle tient dans le cadre
   sans le faire défiler, et les libellés restent courts.

   Les lignes sont plus SERRÉES que celles des modules : ce sont des faits à
   parcourir vite, pas trois choix structurants à peser un par un. */
/* Le volet replié : une ligne cliquable qui ne coûte qu'une ligne de hauteur
   tant qu'on ne l'ouvre pas. */
.parties {
  margin-top: var(--espace-5);
  border-top: 1px solid rgba(255,255,255,.10);
  border-bottom: 1px solid rgba(255,255,255,.10);
}
.parties-titre {
  display: flex; align-items: center; gap: var(--espace-3);
  padding: 13px 0;
  cursor: pointer;
  list-style: none;
  font-size: var(--texte-xs);
  font-weight: var(--graisse-grasse);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--wpn-neutre-500);
}
.parties-titre::-webkit-details-marker { display: none; }
/* Le chevron tourne à l'ouverture : c'est le seul signal qu'il y a quelque
   chose dessous, et il doit rester lisible sans couleur. */
.parties-titre::after {
  content: ""; flex: none;
  width: 8px; height: 8px; margin-left: auto;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .25s ease;
}
.parties[open] .parties-titre::after { transform: rotate(-135deg) translate(-2px, -2px); }
.parties-titre:hover { color: #F4F4F2; }

/* Le compte de ce qui est coché, pour qu'un volet fermé ne cache pas ce qu'on
   y a mis. En jaune quand il y a quelque chose : c'est la couleur que le
   calculateur donne déjà à ce qui pèse sur le prix. */
.parties-compte {
  margin-left: auto; margin-right: var(--espace-3);
  /* Nettement plus petit que le titre du volet : c'est un état, pas un second
     titre. À taille égale les deux se lisaient comme deux libellés côte à
     côte, et le regard ne savait plus lequel nommait la section. */
  font-size: .66rem; font-weight: var(--graisse-moyenne);
  letter-spacing: .06em; text-transform: none;
  color: var(--wpn-jaune-400);
}
/* ⚠️ UN BLOC DE L'ANCIEN CALCULATEUR A ÉTÉ RETIRÉ ICI le 27 août 2026.

   Il redéfinissait `.parties-grille` en DEUX colonnes, plus bas dans le
   fichier que la règle voulue — et l'écrasait donc silencieusement. Mesuré :
   la grille restait à deux colonnes sur un écran de 1 440 px alors que quatre
   étaient demandées.

   Les styles de la liste vivent maintenant à un seul endroit, avec le reste
   des commandes de l'instrument. */


/* ==========================================================================
   LE MOT GÉANT — EN HAUT À GAUCHE, ET JAMAIS EN DOUBLE
   --------------------------------------------------------------------------
   Karl, 27 août 2026 : « je veux le même gros mot à basse opacité, en haut à
   gauche, mais je ne veux pas qu'il y ait deux fois le même mot, un à côté de
   l'autre. Donc le petit, en haut à gauche, lui, doit disparaître quand on
   est déroulé. Il reste juste le gros en haut à gauche. Et ce n'est pas juste
   "portal", ce sera "your portal". »

   ## Le problème que ça règle

   Le mot était en bas à droite. Dans le bloc Portal, l'image et le téléphone
   descendent jusque-là et le recouvraient entièrement : il n'y avait tout
   simplement pas la place. En haut à gauche, il occupe une zone que rien ne
   dispute — et il devient l'indicateur d'état de l'onglet au lieu d'être un
   ornement de coin.

   ## Un seul mot à la fois, et le décalage n'est pas un détail

     • ROULÉ   : le libellé « ◆ Your Portal » de l'onglet, seul.
     • DÉROULÉ : le grand « Your Portal » en filigrane, seul.

   Le croisement des opacités est DÉCALÉ dans le temps : le petit s'efface
   d'abord (.30s), le grand entre ensuite (.22s d'attente). En les faisant se
   croiser au même instant, on lit brièvement les deux mots superposés — c'est
   précisément ce que Karl ne veut pas. Vérifié à mi-transition : un seul mot
   est lisible à tout moment.

   Le texte est passé à « Your Window / Your Portal / Your Network » dans
   `index.html`, pour que le grand mot dise la même chose que l'onglet qu'il
   remplace.
   ========================================================================== */

/* Le mot quitte le bas-droite pour le haut-gauche. Cette règle vient APRÈS
   celle qui posait `right: 3%; bottom: 10px` : `right` et `bottom` doivent
   être explicitement relâchés, sans quoi le mot serait tiré aux quatre coins
   et s'étirerait sur toute la largeur du bloc. */
.bloc > .bloc-mot {
  left: clamp(20px, 5vw, 64px);
  right: auto;
  top: calc(var(--onglet) - 0.34em);
  bottom: auto;
  /* Il doit tenir sur UNE ligne : « Your Portal » replié en deux lignes
     ferait sauter la hauteur du bloc au moment du déroulement. La taille se
     borne donc sur la largeur disponible. */
  white-space: nowrap;
  font-size: clamp(2.6rem, 7.4vw, 6.4rem);
}

/* Au repos — pile non pliée, tous les blocs pleinement lisibles — le mot est
   visible et l'onglet aussi : il n'y a pas de doublon, puisque la bande
   d'onglet n'existe pas encore comme libellé isolé. */

/* Une fois la pile PLIÉE, les deux se relaient. */
.pile-plies .bloc > .bloc-mot { opacity: 0; }
.pile-plies .bloc.est-ouvert > .bloc-mot {
  opacity: .10;
  transition: opacity .5s ease .22s;
}
.pile-plies .bloc-network.est-ouvert > .bloc-mot { opacity: .13; }

/* Le libellé de l'onglet s'efface dès que son bloc est ouvert : c'est le
   grand mot qui prend le relais, et les deux ne doivent jamais coexister.
   `pointer-events` suit, sans quoi un libellé invisible garde sa cible et
   attrape les clics destinés au contenu. */
.pile-plies .bloc.est-ouvert > .bloc-onglet {
  opacity: 0;
  pointer-events: none;
  transition: opacity .30s ease;
}
/* Le chevron, lui, RESTE : c'est le seul moyen de refermer le bloc. Il est
   remonté au-dessus du libellé transparent qui l'accompagnait. */
.pile-plies .bloc.est-ouvert > .bloc-onglet::after {
  opacity: 1;
}

/* Le mot ne doit pas passer sous le contenu : le titre du bloc commence plus
   bas quand le bloc est ouvert et que le mot occupe le haut. */
.pile-plies .bloc.est-ouvert > .enveloppe {
  /* Juste ce qu'il faut pour passer sous le mot, et pas davantage. Une marge
     plus généreuse ouvrait un vide franc entre le mot et le titre, et le bloc
     paraissait commencer deux fois. La valeur suit la taille du mot, qui est
     elle-même bornée sur la largeur. */
  padding-top: calc(var(--onglet) + clamp(28px, 4.4vw, 54px));
}

/* Le sur-titre du bloc (« 02 · YOUR PORTAL ») ferait un TROISIÈME rappel du
   même texte, juste sous le mot géant qui le dit déjà en grand. On ne garde
   que le numéro, qui lui apporte une information que le mot ne porte pas. */
.pile-plies .bloc.est-ouvert .bloc-numero { font-size: 0; }
.pile-plies .bloc.est-ouvert .bloc-numero span { font-size: var(--texte-xs); }

@media (max-width: 620px) {
  /* Sur petit écran la pile n'existe plus : les blocs sont de simples
     sections, l'onglet garde son libellé, et le mot reprend une taille qui
     tient dans la largeur. */
  .bloc > .bloc-mot { font-size: clamp(2rem, 11vw, 3.4rem); }
}

@media (prefers-reduced-motion: reduce) {
  .pile-plies .bloc.est-ouvert > .bloc-mot,
  .pile-plies .bloc.est-ouvert > .bloc-onglet { transition: none; }
}

/* **La règle de placement de la pile doit être neutralisée pour le mot.**

   Une fois la pile pliée, `.pile-plies .pile .bloc > .bloc-mot` posait le mot
   à `top: calc(100svh − 1.5 × --onglet − 10px)` avec un `translateY(-100%)` —
   c'est-à-dire calé sur le BAS du bloc, là où il se trouvait avant. Elle est
   plus spécifique que le placement en haut à gauche posé plus haut (0-3-1
   contre 0-2-1), et le mot restait donc en bas : mesuré, il s'affichait sous
   les boutons de métier au lieu du haut du bloc.

   On la neutralise ici plutôt que de la modifier sur place, pour que la
   raison du changement reste avec la consigne de Karl qui l'a motivée. */
.pile-plies .pile .bloc > .bloc-mot {
  top: calc(var(--onglet) - 0.34em);
  bottom: auto;
  transform: none;
}

/* ====================================================================
   ⚠️ LE CALCULATEUR GRANDIT VERS LE BAS. JAMAIS VERS LE HAUT.

   C'est la règle qui prime sur toutes les autres dans cette section, et deux
   corrections l'ont déjà enfreinte.

   Karl, 28 août 2026 : « si je clique sur "Work out a price" et que je déroule
   le calculateur, il prend de l'expansion vers le haut, ce qui le fait
   embarquer par-dessus les onglets de couleur. Je vois juste un restant
   d'onglet bleu en haut. Et si je clique sur "See everything", il embarque
   par-dessus tout, et il est même par-dessus le carrousel. Je veux que lorsqu'on
   le déroule, ça pousse la fenêtre du calculateur vers le BAS plutôt que le
   haut. »

   Mesuré à 1512×850 : le haut du cadre passait de 290 à 247 puis à 46 aux
   trois états (replié, déroulé, liste dépliée). Il devait rester à 290.

   LA CAUSE ÉTAIT UN `--recul` appliqué au point de collage, introduit le même
   jour pour rendre le bas visible sur écran court. Il faisait remonter la
   section d'autant qu'elle débordait — donc précisément l'inverse de ce qui
   est demandé. Il est retiré.

   ⚠️ UN `--recul` A DÉJÀ ÉTÉ INTRODUIT PUIS RETIRÉ DEUX FOIS, les 27 et 28
   août, chaque fois pour régler le bas coupé, chaque fois en cassant le haut.
   NE PAS LE REPRENDRE UNE TROISIÈME FOIS. Le bas se règle par la COURSE — les
   pixels de défilement pendant lesquels le calculateur reste collé — jamais en
   déplaçant son point d'accroche.
   ==================================================================== */


/* ====================================================================
   SUR TÉLÉPHONE, L'ENVELOPPE DE LA PILE LÂCHE SA HAUTEUR

   ⚠️ CETTE RÈGLE DOIT RESTER EN FIN DE FICHIER. Trois autres définitions de
   `.pile-enveloppe` la suivraient sinon et l'emporteraient à spécificité
   égale — c'est précisément ce qui est arrivé quand elle était placée dans le
   palier `max-width: 620px`, où elle n'avait aucun effet.

   Sous 620 px, la pile n'existe plus : les feuilles redeviennent de simples
   sections empilées en `height: auto`. Mais l'enveloppe, elle, gardait
   `calc(4 * 100svh - 3 * var(--onglet))`, taillée pour quatre feuilles
   collantes d'une hauteur d'écran.

   Conséquence mesurée à 390 px : la pile dépassait son enveloppe, et le rendu
   s'arrêtait à la fin de celle-ci. Le calculateur était MESURÉ entier jusqu'à
   841 px mais PEINT coupé net vers 555, en plein milieu du prix — ni son coin
   arrondi ni son bouton de déroulé n'étaient visibles. Aucun `overflow`
   n'était en cause : tous les ancêtres étaient `visible`. C'est le flux qui
   reprenait la main trop tôt, l'enveloppe déclarant une fin que son contenu
   avait dépassée.

   C'est la même nature de défaut que sur grand écran, où l'enveloppe devait
   apprendre `--depassement-pile` : une hauteur écrite d'avance contre un
   contenu qui, lui, varie.
   ==================================================================== */
/* ⚠️ CES TROIS ANNULATIONS ONT ÉTÉ RETIRÉES le 29 août 2026, avec la
   réactivation de la pile sur téléphone. L'enveloppe doit garder sa hauteur
   calculée : c'est elle qui donne sa course au collage. À `height: auto`, la
   pile n'a plus de quoi défiler et le calculateur se coupe en bas — le
   symptôme exact que Karl a décrit sur son téléphone.
   Conservé dans `siteweb/sauvegardes/pile-desactivee-mobile.css.avant-retrait`. */


/* ==========================================================================
   ⚠️ LA SECTION QUI SUIT LA PILE COLLE AU CALCULATEUR

   Karl, 28 août 2026 : « il y a toujours un gros espace beige, blanc, en
   dessous du bloc du calculateur. »

   L'espace n'était pas une erreur de calcul de la pile — il venait de deux
   marges parfaitement ordinaires qui s'additionnaient :

     — 39 px de padding sous `.section-estime` ;
     — 150 px de padding en haut de `.section`, l'espacement standard entre
       deux sections du site.

   Soit 189 px de vide entre le bas du cadre et le bloc « Try it yourself ».
   Ailleurs sur le site cet espacement est juste ; ici il ne l'est pas, parce
   que le calculateur est la DERNIÈRE FEUILLE D'UNE PILE : il glisse sous ce
   qui suit, et ce qui suit doit donc arriver au ras de lui, sans respiration.

   Le sélecteur vise la section qui suit immédiatement l'enveloppe de la pile,
   quelle qu'elle soit : il continuera de fonctionner si l'ordre des sections
   change.
   ========================================================================== */
/* ⚠️ UN PEU D'AIR, MAIS PAS L'ESPACEMENT STANDARD.

   Deux réglages successifs ont été faux, tous deux corrigés par Karl :

   — d'abord 189 px (39 px sous la section + les 150 px d'espacement standard
     entre sections) : « il y a toujours un gros espace beige, blanc, en
     dessous du bloc du calculateur » ;
   — puis zéro : « il est complètement collé avec le bouton Try it yourself,
     j'aimerais qu'on mette un peu d'air, un peu de distance ici. »

   Le juste milieu est un espacement franc mais nettement plus court que celui
   qui sépare deux sections ordinaires : le calculateur et « Try it yourself »
   forment une suite — l'un donne le prix, l'autre invite à essayer — et
   doivent rester visuellement liés.

   ⚠️ Ce n'est PAS `padding-top` sur la section suivante : le calculateur est
   la dernière feuille d'une pile et glisse SOUS ce qui suit. Un padding en
   haut de `#demo` créerait une bande de sa couleur, pas un vide. L'espace doit
   venir d'en dessous du calculateur, à l'intérieur de la pile. */
/* ⚠️ UN PEU D'AIR, MAIS PAS L'ESPACEMENT STANDARD.

   Le padding valait 0 : le calculateur et « Try it yourself » se touchaient
   exactement — mesuré, 0 px entre le bas du cadre et le haut de la fenêtre.
   Karl : « je vois simplement un manque de distance entre le calculateur et la
   fenêtre, elles sont collées l'une à l'autre. »

   L'espacement reste nettement plus court que celui qui sépare deux sections
   ordinaires (150 px) : les deux forment une suite — l'un donne le prix,
   l'autre invite à essayer — et doivent rester liés.

   ⚠️ C'est bien un `padding-top` sur la section SUIVANTE : le calculateur
   glisse sous elle, donc l'espace doit appartenir à celle qui arrive. */
.pile-enveloppe + .section {
  padding-top: clamp(44px, 5vw, 84px);
}
.section-estime {
  padding-bottom: clamp(28px, 3.5vw, 52px);
}





/* ==========================================================================
   ⚠️ L'ÉCHANCRURE DU BAS — sous les coins arrondis du calculateur

   Karl, 28 août 2026 : « entre le calculateur et le bloc, il y a encore ces
   coins arrondis dans le bas du bloc du calculateur. L'extérieur de ces
   coins-là sont blancs. »

   Une fois posé (`est-pose`), le calculateur arrondit ses quatre coins. Les
   deux du BAS laissent voir ce qui se trouve derrière — et derrière, il n'y a
   rien d'autre que le fond de la page. Mesuré à 1512×850 : rgb(225,221,212),
   le beige du papier, sur 16 px de hauteur, alors que la section suivante
   juste dessous est rgb(27,31,42).

   C'est le même mécanisme que l'échancrure du HAUT, retourné : on peint la
   couleur de ce qui SUIT derrière les coins du bas, pour que la continuité se
   lise. `--wpn-ancre` est le fond de `.section-invite`, la section qui suit
   toujours la pile.

   `overflow-clip-margin` est nécessaire ici pour la même raison qu'en haut :
   sans lui, la couche serait découpée suivant les coins arrondis et ne
   pourrait jamais les atteindre.
   ========================================================================== */
.pile .section-estime.est-pose {
  overflow-clip-margin: var(--rayon-feuille);
}
.pile .section-estime.est-pose::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--rayon-feuille);
  background: var(--wpn-ancre);
  z-index: -1;
  pointer-events: none;
}


/* ⚠️ LE NAVIGATEUR NE RECALE PAS LA PAGE QUAND LA PILE CHANGE DE TAILLE.

   L'« ancrage au défilement » de Chrome ajuste automatiquement la position
   quand du contenu change de hauteur au-dessus du point de lecture. C'est utile
   en général, mais ici la pile change de taille à chaque image de l'animation
   d'ouverture ou de fermeture : l'ancrage réagissait à chacune et déplaçait la
   page de 50 px à la fermeture. Karl veut que l'écran reste strictement
   immobile pendant que le calculateur s'étire. */
.pile-enveloppe,
.calculateur-course {
  overflow-anchor: none;
}


/* ==========================================================================
   ⚠️ LE BOUTON « GET A REAL NUMBER » EST UNE TOUCHE DE CONSOLE

   Karl, 28 août 2026 : « pour le bouton Get a real number, j'aimerais qu'on ait
   le même sentiment de dimension que dans tous les autres boutons. J'ai
   l'impression que celui-là est plus flat, alors que les autres, on a vraiment
   l'impression d'être dans une console. »

   Il portait `.btn-primaire`, le bouton générique du site : un aplat jaune,
   juste. Ailleurs dans l'instrument, chaque commande est bombée au repos et
   s'enfonce au clic — une arête vive en haut, une ombre sous la touche.

   Il garde sa couleur, qui le désigne comme l'action principale ; il gagne le
   relief. Le dégradé va du jaune clair au jaune profond, l'arête blanche marque
   le dessus, et l'ombre portée le décolle du panneau.
   ========================================================================== */
.section-estime #ouvrir-envoi {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--wpn-jaune-400) 88%, #fff) 0%,
      var(--wpn-jaune-400) 44%,
      color-mix(in srgb, var(--wpn-jaune-400) 84%, #000) 100%);
  border: 1px solid rgba(0,0,0,.42);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.55),
    inset 0 -1px 0 rgba(0,0,0,.22),
    0 1px 0 rgba(0,0,0,.5),
    0 4px 10px -3px rgba(0,0,0,.55);
  transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}
.section-estime #ouvrir-envoi:hover {
  filter: brightness(1.05);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.62),
    inset 0 -1px 0 rgba(0,0,0,.22),
    0 1px 0 rgba(0,0,0,.5),
    0 6px 14px -4px rgba(0,0,0,.6);
}
/* Enfoncée : la touche descend d'un pixel et son ombre se referme. */
.section-estime #ouvrir-envoi:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 2px 4px rgba(0,0,0,.32),
    0 1px 0 rgba(0,0,0,.4);
}

/* ==========================================================================
   ZONE — LE PIED DE PAGE : LOCKUP DE MARQUE ET RÉSEAUX
   Ajouté le 29 août 2026, en même temps que le portail d'administration et
   l'espace client.

   Ces règles sont reprises TELLES QUELLES de la page d'attente
   (`landing/site/assets/css/style.css` et `landing.css`), pour que le pied
   des deux sites soit rigoureusement le même. Les modifier ici sans les
   reporter là-bas ferait diverger les deux pieds — c'est exactement ce que
   la reprise à l'identique cherche à éviter.

   ⚠️ Ce bloc est ajouté EN FIN DE FICHIER à dessein : `style.css` est partagé
   avec la session qui travaille sur le calculateur. On y ajoute, on ne le
   réécrit jamais.
   ========================================================================== */

/* Les liens du pied et les pastilles sociales se centrent sur une même
   ligne : sans `align-items`, le lien texte se cale en haut et les pastilles
   en bas. */
.pied-liens { align-items: center; }

/* --- Le lockup monochrome ------------------------------------------------
   Le pied est sur fond sombre et le SVG est en `currentColor` : il s'affiche
   donc en BLANC, ce qui correspond à la case « monochrome négatif » de la
   planche de marque. Le même balisage sert dans les deux sens, sans variante
   de fichier. */
.pied-lockup {
  display: inline-flex; align-items: center; gap: var(--espace-3);
  text-decoration: none; color: #fff;
}
.pied-lockup svg { width: 40px; height: auto; display: block; }
.pied-lockup span {
  font-weight: var(--graisse-noire); font-size: 12px; line-height: 1.1;
  letter-spacing: 0.04em; text-transform: uppercase; color: #fff;
}
/* Le lockup ne change pas de couleur au survol : c'est une marque, pas un
   lien ordinaire. Seule une légère atténuation signale qu'il est cliquable. */
.pied-lockup:hover { opacity: .78; }

.pied-baseline { margin: var(--espace-4) 0 0; }

/* La ligne de droits est la mention la plus discrète du pied : elle doit être
   lisible sans jamais attirer l'oeil avant le reste. */
.pied-droits {
  margin: var(--espace-2) 0 0;
  font-size: 12px;
  color: var(--wpn-neutre-500);
}

/* --- Les deux réseaux ------------------------------------------------------ */

.pied-sociaux { display: inline-flex; gap: var(--espace-3); align-items: center; }

.pied-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* La cible tactile fait 36 px : une icône de 18 px seule serait trop petite
     à toucher sur téléphone. */
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--rayon-md);
  color: var(--wpn-neutre-200);
  background: rgba(255, 255, 255, .06);
}
.pied-social svg { width: 1.05rem; height: 1.05rem; display: block; }
.pied-social:hover { background: rgba(255, 255, 255, .14); color: #fff; }

/* ==========================================================================
   ZONE — DEUX CIBLES DE CLIC QUI MANQUAIENT
   Ajouté le 29 août 2026, à la demande de Karl. Deux défauts distincts, tous
   deux réparables en CSS seul : ni `pile.js` ni `estime.js` n'ont été touchés.

   ⚠️ Ajouté EN FIN DE FICHIER à dessein — `style.css` est partagé avec la
   session du calculateur. On y ajoute, on ne le réécrit jamais. Les règles
   ci-dessous ANNULENT volontairement des déclarations plus haut dans le
   fichier : à spécificité égale, c'est la dernière qui gagne.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. REFERMER UN BLOC DE LA PILE

   Karl : « une fois la pile collée avec le calculateur, si on déroule un
   onglet, il n'y a pas l'option de simplement le rouler à nouveau. »

   `pile.js` savait déjà refermer — son clic fait `ouvrir(bloc === ouvert ?
   null : bloc)`. Ce qui manquait était uniquement la cible : la règle plus
   haut posait `opacity: 0` sur la bande pour effacer son libellé, et une
   règle voisine tentait de rattraper le chevron avec `opacity: 1`.

   Cela ne pouvait pas marcher. **L'opacité d'un parent s'applique à tout son
   sous-arbre** : un enfant à `opacity: 1` dans un parent à `opacity: 0` reste
   invisible, il n'y a pas de « rattrapage » possible. Mesuré dans Chrome —
   opacité effective du chevron : 0. Et `pointer-events: none` achevait le
   travail : le clic tombait sur `section.bloc`, jamais sur l'onglet.

   La correction efface le libellé par la COULEUR et non par l'opacité. Le
   chevron, lui, reçoit une couleur explicite, parce qu'il est dessiné en
   `currentColor` — devenu transparent avec le reste.
   -------------------------------------------------------------------------- */

.pile-plies .bloc.est-ouvert > .bloc-onglet {
  /* Annule le `opacity: 0` posé plus haut : c'était lui qui emportait le
     chevron avec le libellé. */
  opacity: 1;
  /* Le libellé disparaît, ainsi que le petit losange qui le précède — il est
     peint en `background: currentColor` et suit donc la couleur. */
  color: transparent;
  /* La bande redevient cliquable. Toute la bande, et pas seulement le chevron
     de 8 px : une cible de 8 px se rate, une bande de 62 px ne se rate pas.
     C'est sans risque pour le contenu, qui commence SOUS elle — `.bloc >
     .enveloppe` porte un `padding-top` d'au moins la hauteur d'onglet, et le
     bloc ouvert en ajoute encore. Mesuré : bande 0→62 px, contenu à 88 px. */
  pointer-events: auto;
  transition: color .30s ease;
}

/* Le chevron reçoit sa couleur en propre : `currentColor` vient d'être rendu
   transparent juste au-dessus, et sans cette ligne il disparaîtrait avec le
   libellé — le défaut d'origine, sous une autre forme. */
.pile-plies .bloc.est-ouvert > .bloc-onglet::after {
  opacity: 1;
  border-right-color: #fff;
  border-bottom-color: #fff;
}

/* Le seul signe visible que la bande est cliquable étant le chevron, il
   s'anime un peu au survol : sans cela, une bande sans libellé n'annonce plus
   rien du tout. Un léger glissement vers le bas, dans le sens du geste
   qu'elle produit — refermer. */
.pile-plies .bloc.est-ouvert > .bloc-onglet:hover::after {
  transform: rotate(-135deg) translate(-5px, -5px);
}

/* --------------------------------------------------------------------------
   2. TOUTE LA SURFACE D'UN ONGLET DU CALCULATEUR

   Karl : « si je veux cliquer dans l'onglet, il y a l'espace où c'est écrit,
   il ne déclenche pas. J'aimerais que tout le bouton soit activable. »

   Le `<button class="onglet-corps">` ne remplit pas la boîte `.onglet` : à sa
   droite vivent `.onglet-etat` (le « Always included » de Your Window) et
   l'interrupteur des deux autres. Sondé dans Chrome sur toute la largeur, la
   zone morte allait de 74 % à 92 % sur Your Window — c'est-à-dire exactement
   « l'espace où c'est écrit » — et de 86 % à 98 % sur les deux autres.

   On étend donc la cible du bouton à toute la boîte de l'onglet par un
   pseudo-élément transparent. `.onglet` est déjà `position: relative`, ce qui
   sert d'ancrage. `estime.js` n'a pas besoin de changer : il écoute
   `.onglet-corps`, et le pseudo-élément EN FAIT PARTIE.
   -------------------------------------------------------------------------- */

.onglet-corps::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Rien à voir : cette surface ne sert qu'à recevoir le clic. */
}

/* L'interrupteur doit rester AU-DESSUS de cette surface, sinon il devient
   lui-même inatteignable et cocher un module serait impossible. Un élément
   positionné passe devant un élément qui ne l'est pas : il faut donc le
   positionner explicitement. */
.onglet-bascule {
  position: relative;
  z-index: 1;
}

/* --------------------------------------------------------------------------
   3. LA FENÊTRE DE VISITE GUIDÉE NE CAPTURE PLUS LES CLICS

   Trouvé en cherchant la cause du point 2 : sur l'onglet « Your Portal » du
   calculateur, le clic n'atteignait toujours pas le bouton après correction.
   `elementFromPoint` désignait `p.visite-texte` — la fenêtre de la visite
   guidée, posée là en `z-index: 41`.

   Elle est pourtant INVISIBLE : `.visite` vit en permanence dans le document
   avec `opacity: 0`, et ne s'allume qu'avec la classe `est-la`. Mais une
   opacité nulle n'enlève pas la cible du clic — c'est exactement le défaut
   qui empêchait aussi de refermer un bloc de la pile, plus haut.

   La règle ci-dessous ne fait qu'appliquer l'intention DÉJÀ ÉCRITE au-dessus
   de `.visite-voile` : « on continue de cliquer dans l'outil pendant qu'elles
   sont là — rien n'est bloqué ». Ses deux boutons redeviennent cliquables
   explicitement, sans quoi on ne pourrait plus passer ni terminer la visite.

   ⚠️ Correction en CSS seul : `estime.js`, qui construit ces fenêtres, n'a pas
   été touché.
   -------------------------------------------------------------------------- */

/* Le sélecteur reprend celui de la règle qu'il annule — `.visite-voile ~
   .visite`, deux classes — parce qu'un simple `.visite` d'une seule classe
   perdrait la comparaison malgré sa position en fin de fichier. À spécificité
   ÉGALE, c'est la dernière qui l'emporte ; à spécificité inférieure, la
   position ne compte pas.

   La règle annulée datait du voile : « l'onglet dont la fenêtre parle reste
   au-dessus du voile ». Le voile est `display: none` depuis, mais elle a
   survécu et continuait de faire capter les clics à la fenêtre. */
.visite-voile ~ .visite,
.visite { pointer-events: none; }

/* Les deux boutons de la fenêtre redeviennent cliquables : sans cette ligne,
   « Got it » et « Skip » hériteraient du `none` de leur parent et la visite
   deviendrait impossible à terminer. */
.visite-ok, .visite-passer { pointer-events: auto; }

/* ==========================================================================
   ZONE — REFONTE DU BAS DE PAGE (29 août 2026)

   Quatre demandes de Karl, dans l'ordre où elles apparaissent en descendant :
   des aperçus à la place des fonds gris, le compteur déplacé en
   avant-dernier, le logo en filigrane dans le bloc final, et l'entrée des
   espaces privés dans la barre du haut.

   ⚠️ Ajouté EN FIN DE FICHIER — `style.css` est partagé. On y ajoute, on ne
   le réécrit jamais.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. LES APERÇUS DES TROIS RÉALISATIONS

   Karl : « est-ce qu'on pourrait mettre juste comme des aperçus pour les trois
   exemples, au lieu d'un fond gris ? »

   Les captures ont été faites sur les sites eux-mêmes, servis depuis le disque
   — les deux versions Netlify sont hors ligne (503 et 404) depuis le 25 août.
   Elles vivent dans `images/realisations/`, en 1000 × 625 (16:10, le ratio de
   la vignette), et l'adresse de chacune est posée en style dans le HTML.

   Le cadrage est ancré EN HAUT (`center top`) et non au centre : ces captures
   sont des héros de page, et c'est le titre du site qu'il faut voir, pas le
   milieu de sa photo de fond.
   -------------------------------------------------------------------------- */

.travail-image {
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
  /* Le gris reste comme couleur d'attente : le temps que l'image arrive, la
     vignette garde sa forme au lieu de s'effondrer. */
  background-color: var(--wpn-neutre-100);
}

/* --------------------------------------------------------------------------
   2. LE COMPTEUR, EN AVANT-DERNIER

   Karl : « le compteur, je le mettrai au bas vraiment avant-dernier. »

   Il vivait à l'intérieur de la section des réalisations, où `.chiffres`
   portait une bordure basse et une grande marge : elles servaient à le séparer
   des vignettes qui le suivaient. Devenu une section à lui, il n'a plus rien à
   séparer — la bordure et la marge sont retirées, sinon il traînerait un trait
   sous lui-même.
   -------------------------------------------------------------------------- */

.section-compte {
  /* Le fond clair du site, entre les vignettes blanches et le bloc final
     sombre : le compteur est le dernier moment clair de la page. */
  padding-top: var(--espace-16);
  padding-bottom: var(--espace-16);
}
.section-compte .chiffres {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}

/* --------------------------------------------------------------------------
   3. LE LOGO EN FILIGRANE DU BLOC FINAL

   Repris de `.affaires-filigrane` sur la page d'attente, à la demande de Karl.
   Les trois décisions de là-bas tiennent ici aussi, et il ne faut pas les
   défaire :

   1. IL EST COUPÉ par le bord droit. Une forme entière se lit comme une
      illustration posée ; une forme coupée se lit comme un motif dont la page
      n'est qu'une fenêtre. C'est la coupure qui fait la texture.

   2. IL EST TRÈS PÂLE. Le texte blanc passe devant. On le sent, on ne le lit
      pas — et il ne concurrence pas les deux boutons, qui sont la seule chose
      à faire dans ce bloc.

   3. IL N'INTERCEPTE RIEN (`pointer-events: none`) et il est `aria-hidden` :
      c'est du décor. La trame de points qui s'écarte sous le curseur reste
      pilotable à travers lui.

   Le débordement est en PIXELS et non en pourcentage : un `right: -18%` grandit
   avec l'écran, dépasse de plus de 250 px sur une large fenêtre et allonge le
   document. `overflow: hidden` masque l'affichage mais n'empêche pas la boîte
   de compter dans la taille de la page. `#talk` porte déjà cet `overflow`.
   -------------------------------------------------------------------------- */

.final-filigrane {
  position: absolute;
  top: 50%;
  right: -70px;
  transform: translateY(-50%);
  width: min(46rem, 52vw);
  /* Bornée à la hauteur du bloc : un SVG plus haut dépasserait en haut ET en
     bas, ce que l'`overflow` masque sans empêcher la page de s'allonger. */
  max-height: 100%;
  height: auto;
  /* Plus haut qu'un vrai filigrane, parce que les trois couleurs doivent
     rester DISTINCTES : en dessous, l'intersection violette se confond avec le
     jaune et la construction de la marque devient illisible. */
  opacity: .2;
  pointer-events: none;
  /* Au-dessus de la trame de points, sous le texte. Le canvas est en
     `z-index: 0` et le contenu en 1 : le filigrane se glisse entre les deux
     en restant à 0, puisqu'il vient après le canvas dans le document. */
  z-index: 0;
}

/* La révélation. Le décalage de 60 unités n'est pas choisi : c'est exactement
   l'écart entre les deux centres du logo (70 et 130). La durée est plus longue
   que celle de l'animation officielle du header — un geste rapide sur une
   forme de cette taille se lirait comme un sursaut — mais la courbe est la
   même. TOUT est conditionné à `est-arme`, que `filigrane.js` pose : sans
   script, le filigrane est le logo normal. */
.final-filigrane .filigrane-jaune {
  transition: transform 1.1s cubic-bezier(.65, 0, .35, 1);
}
.final-filigrane.est-arme .filigrane-jaune { transform: translateX(-60px); }
.final-filigrane.est-arme.est-revele .filigrane-jaune { transform: none; }

/* Sur écran étroit, le texte occupe toute la largeur et passerait par-dessus
   le filigrane. On le recule dans le coin bas-droit, où il ne rencontre plus
   que le vide sous les boutons. */
@media (max-width: 900px) {
  .final-filigrane {
    top: auto;
    bottom: -12%;
    right: -80px;
    transform: none;
    width: min(26rem, 80vw);
    opacity: .13;
  }
}

/* --------------------------------------------------------------------------
   4. DEUX ENTRÉES, DEUX ESPACES

   Karl : « c'est comme dans les autres sites web qu'on a faits. En haut, c'est
   le login pour l'espace client. En bas, c'est le portail de gestion. Ces deux
   interfaces différentes, ça va être deux espaces différents. »

   EN HAUT — l'espace client, dans la barre de navigation. Il hérite du style
   des autres liens, avec le soulignement qui se déploie depuis la gauche. Ce
   qui le distingue est un petit losange de marque : il n'appartient pas à la
   même famille que « What you get » ou « Work », qui mènent plus bas dans la
   page — celui-ci fait sortir du site public.

   EN BAS — le portail de gestion, à la dernière ligne du pied, dans la mention
   de droits. C'est l'entrée du propriétaire : elle doit exister sans jamais se
   proposer à un visiteur. Même place et même discrétion que sur les sites
   livrés (voir `Do Aldo/siteweb/site/assets/js/site.js`, où « Manage » suit le
   téléphone dans `.pied__bas`).

   ⚠️ Une première version faisait CHANGER le lien du haut selon qui était
   connecté — « Your portal » pour Karl. Ce n'était pas ce qu'il voulait : deux
   espaces distincts appellent deux entrées distinctes, pas une entrée qui se
   déguise. Le script `acces-nav.js` a été supprimé avec cette version.
   -------------------------------------------------------------------------- */

/* Le losange qui précédait ce lien a été RETIRÉ le 29 août 2026. Karl doutait
   de lui — « je ne suis pas certain du losange dans client area » — et son
   doute suit sa propre règle, posée en session 15 : « ce n'est pas parce que
   ton losange, c'est ton logo, qu'il faut mettre des losanges partout. »
   La division verticale qui précède le lien fait déjà le travail de le
   distinguer des liens de section. */
.nav-acces { display: inline-flex; align-items: center; }

/* Le lien de gestion vit DANS la ligne de droits, la mention la plus discrète
   du pied. Il en garde la taille et s'en distingue par le seul soulignement :
   quelque chose d'assez visible pour qu'on le trouve quand on le cherche,
   assez effacé pour qu'on ne le remarque pas quand on ne le cherche pas. */
.pied-gestion { color: var(--wpn-neutre-500); text-decoration: underline; }
.pied-gestion:hover { color: #fff; }

/* La colonne de texte du bloc final, alignée à gauche comme sur la page
   d'attente. La largeur est portée par ce bloc INTÉRIEUR et jamais par
   `.enveloppe` : celle-ci porte la largeur de colonne du site (1200 px) et son
   centrage, et mélanger les deux recentrerait le texte au milieu de la page.
   C'est l'erreur que le bloc d'attente de la landing avait déjà faite. */
.final-entete { max-width: 46rem; margin-left: 0; margin-right: auto; }
.final-actions { justify-content: flex-start; }

/* Sous 900 px, le filigrane part dans le coin bas-droit (voir plus haut) et le
   texte reprend toute la largeur : il n'a plus rien à éviter. */
@media (max-width: 900px) {
  .final-entete { max-width: none; }
}

/* ==========================================================================
   ZONE — RECALIBRAGE DES BLOCS DÉROULÉS DE LA PILE (29 août 2026)

   Karl, en naviguant le site : « lorsque je déroule le bloc violet, il y a un
   bouton qui est encore caché en dessous de l'onglet jaune, le pied de la
   photo est caché, plus de la moitié du téléphone est aussi caché. Il y a un
   gros vide en haut et le texte est un peu trop collé vers le bas. » Et pour
   le bleu : « tout est collé trop au bas de la page, l'image pourrait être
   plus haut, et ça nous donnerait de l'espace au bas pour ajouter de
   l'information. »

   CE QUE LA MESURE A MONTRÉ — le défaut est UN SEUL, pas deux
   -------------------------------------------------------------------------
   Relevé dans Chrome sur le bloc Portal ouvert, à 1512 px de large :

       le grand mot « Your Portal »  27 → 111 px
       l'enveloppe du contenu        posée à `top: 88px`
       son propre rembourrage        + 116 px
       ⇒ le contenu commence à       204 px

   Il y avait donc **93 px de vide pur** entre le bas du mot et la première
   ligne — deux placements qui s'additionnaient sans le savoir, l'un pour
   décaler l'enveloppe, l'autre pour la faire passer sous le mot.

   Ce vide ne se voit pas sur un grand écran : à 1512 × 900 le bloc fait 807 px
   et tout tient. Il devient un défaut dès que l'écran raccourcit, parce que la
   hauteur du bloc suit la fenêtre (`100svh − 1.5 × --onglet`) alors que le
   contenu, lui, ne bouge pas. Mesuré :

       1512 × 850   rien de caché
       1512 × 800   le téléphone dépasse de 68 px
       1512 × 750   le BOUTON de 3 px, la mascotte de 45, le téléphone de 117
       1512 × 700   le bouton de 53 px, le téléphone de 165

   D'où les trois symptômes de Karl : le bouton, le pied de la photo et la
   moitié du téléphone passaient sous l'onglet jaune. Le « gros vide en haut »
   et le « texte collé en bas » sont les deux faces du même écart.

   ⚠️ Ces règles valent pour LES TROIS BLOCS, le jaune compris. Karl a dit ne
   pas vouloir qu'on travaille sur Network — on n'y touche donc pas
   spécifiquement, mais il profite du recalibrage comme les deux autres,
   puisque c'est le même mécanisme qui les porte tous.
   ========================================================================== */

/* --- 1. Le contenu remonte de 69 px ---------------------------------------
   L'enveloppe repart du haut du bloc, et c'est son seul rembourrage qui la
   fait passer sous le mot. Le mot n'est pas concerné : il est posé en absolu à
   `top: calc(var(--onglet) - 0.34em)`, indépendamment de tout cela.

   135 px = les 111 px du bas du mot, plus 24 px de respiration. En dessous, le
   titre viendrait toucher le filigrane ; au-dessus, on recreuse le vide qu'on
   vient de combler. */
.pile-plies .bloc.est-ouvert > .enveloppe {
  top: 0;
  padding-top: 135px;
}

/* --- 2. Le rembourrage du bas se resserre ---------------------------------
   110 px sous le contenu d'un bloc qui manque déjà de place, c'était de
   l'espace pris à ce qu'on veut voir. 56 px suffisent à ne pas coller à
   l'onglet suivant. Le haut passe à 0 : il ne servait plus à rien depuis que
   l'enveloppe part du haut, et il faussait le calcul de la hauteur utile. */
.pile-plies .bloc.est-ouvert {
  padding-top: 0;
  padding-bottom: 56px;
}

/* --- 3. L'image se borne à la hauteur disponible --------------------------
   Elle mesurait 489 px quelle que soit la fenêtre : c'est elle qui débordait
   le plus, et le téléphone du bloc Portal, posé en absolu sous son coin
   bas-droit, dépassait encore de 48 px de plus.

   Le calcul suit la hauteur réelle du bloc : `100svh − 1.5 × --onglet` est sa
   hauteur, moins 250 px pour le rembourrage haut, le rembourrage bas et le
   débordement du téléphone. Sur un écran haut, la borne est plus grande que
   l'image et ne change donc rien — le rendu que Karl a validé reste intact
   au-dessus de 850 px de hauteur. */
.pile-plies .bloc.est-ouvert .masc-pile img {
  max-height: calc(100svh - 1.5 * var(--onglet) - 250px);
  width: auto;
  /* ⚠️ CETTE LIGNE N'EST PAS FACULTATIVE, et son absence a causé une
     régression franche. `width: auto` rend à l'image sa largeur NATURELLE dès
     que la borne de hauteur ne mord pas — c'est-à-dire sur les écrans hauts,
     et sur le bloc Window dont l'image est moins haute que celle du Portal.
     Elle sortait alors de sa colonne et passait par-dessus le titre :
     « The room you built, seen from the street » était coupé en deux.
     `max-width: 100%` la garde dans sa colonne quoi qu'il arrive. */
  max-width: 100%;
  margin-inline: auto;
}
/* Le conteneur suit la largeur réduite de l'image, sinon celle-ci flotterait à
   gauche d'une colonne restée pleine largeur — mais il ne dépasse jamais la
   sienne, pour la même raison que ci-dessus. Les images empilées par-dessus
   (`inset: 0`) gardent leur proportion au lieu de s'étirer. */
.pile-plies .bloc.est-ouvert .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }
.pile-plies .bloc.est-ouvert .masc-pile img + img { object-fit: contain; }

/* --- 4. Le contenu d'un bloc déroulé s'aligne EN HAUT ----------------------
   Karl, sur le bloc bleu : « tout est collé trop au bas de la page, l'image
   pourrait être plus haut, plus dans le haut, et ça nous donnerait de l'espace
   au bas pour ajouter de l'information. »

   `.bloc-grille` porte `align-items: center` : les deux colonnes se centrent
   l'une sur l'autre. Quand l'image est plus haute que le texte — c'est le cas
   du bloc Window, 507 px contre 381 — le texte descend pour se centrer sur
   elle. Mesuré : il commençait à 210 px au lieu des 135 px du bloc Portal, où
   c'est le texte qui est le plus haut.

   D'où l'impression, juste, que « tout est collé au bas » : le texte n'était
   pas poussé vers le bas, il était centré sur une image qui, elle, occupait
   toute la hauteur.

   Aligné en haut, chaque colonne part du même trait, et ce qui reste se libère
   en bas — l'espace que Karl veut pour y ajouter de l'information.

   ⚠️ Ne vaut que pour un bloc DÉROULÉ dans la pile. Au premier défilement,
   avant que la pile ne se plie, les blocs sont des sections ordinaires et le
   centrage reste le bon choix : il n'y a pas de hauteur contrainte à
   respecter, et deux colonnes centrées s'y lisent mieux. */
.pile-plies .bloc.est-ouvert .bloc-grille { align-items: start; }

/* ==========================================================================
   ZONE — LA BARRE DU HAUT, EN SOMBRE ET EN RELIEF (29 août 2026)

   Karl : « la bande complètement en haut avec les menus, je trouve ça un peu
   plate. Peut-être qu'on devrait y aller plus dans le foncé, ça encadrerait
   mieux le header. J'aimerais une bande qui a un peu de relief, comme si elle
   était surélevée par rapport au header. »

   Elle était en `rgba(245,245,244,.82)` — le fond de la page, à peine
   assombri : posée sur un héros beige clair, elle ne se distinguait de rien.
   « Plate » était le mot juste : aucune valeur ne la séparait de ce qu'elle
   surmontait.

   ⚠️ LE PIÈGE DU LOGO SUR FOND SOMBRE, déjà rencontré sur le portail
   d'administration : le losange VIOLET du logo vaut #4A1661, très proche des
   noirs. Sur un fond trop sombre, l'intersection disparaît et le logo se lit
   comme deux losanges séparés — le concept de la marque, effacé.

   L'ancre (#1B1F2A) est un bleu-noir, pas un violet : le contraste avec
   #4A1661 reste lisible, et le logo garde ses trois couleurs. C'est pourquoi
   on s'arrête à l'ancre et qu'on ne descend pas vers le violet-900. Vérifié à
   l'écran avant de valider.
   ========================================================================== */

.nav {
  /* Presque opaque, et non plus translucide : la barre doit tenir sa valeur
     au-dessus de tout ce qu'elle survole — le héros beige, puis les trois
     blocs bleu, violet et jaune. Un fond trop transparent prendrait la
     couleur de chacun et la barre changerait d'aspect au défilement. */
  background: linear-gradient(180deg,
              rgba(33, 38, 51, .94) 0%,
              rgba(23, 27, 36, .94) 100%);
  border-bottom: 1px solid rgba(0, 0, 0, .5);
  /* LE RELIEF, en trois traits superposés :
       — une arête vive en haut, qui attrape la lumière ;
       — un liseré sombre en bas, qui creuse ;
       — une ombre portée, qui décolle la bande de la page.
     C'est cette dernière qui donne l'impression de surélévation demandée :
     sans elle, un simple fond foncé reste posé à plat. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .10),
    inset 0 -1px 0 rgba(0, 0, 0, .35),
    0 10px 28px -12px rgba(0, 0, 0, .55);
}

/* Le lettrage de la marque hérite de `currentColor` : sur fond sombre il
   deviendrait illisible sans cette déclaration explicite. C'est le piège des
   couleurs héritées noté dans ENTREPRISE.md, appliqué à l'envers. */
.nav-marque, .nav-marque span { color: #fff; }

/* Les liens s'éclaircissent au lieu de s'assombrir. Le soulignement qui se
   déploie depuis la gauche est conservé — c'est un geste de la maison — mais
   il passe au blanc, sans quoi il resterait invisible. */
.nav-liens a:not(.nav-bouton) { color: var(--wpn-neutre-200); }
.nav-liens a:not(.nav-bouton):hover { color: #fff; }
.nav-liens a:not(.nav-bouton)::after { background: #fff; }

/* --- Les trois groupes, et le trait qui les divise -----------------------
   `.nav-groupe` tient les liens qui mènent plus bas dans la PAGE ; après le
   trait viennent le lien qui fait SORTIR du site public, puis l'action
   principale. Trois natures différentes, trois places distinctes. */
.nav-groupe { display: inline-flex; align-items: center; gap: var(--espace-6); }

.nav-division {
  width: 1px;
  /* Plus court que la ligne de texte : un trait pleine hauteur ferait une
     cloison. Celui-ci ne fait que marquer une respiration. */
  height: 18px;
  /* Deux valeurs superposées — une sombre, une claire d'un pixel — pour que
     le trait soit gravé dans la bande plutôt que posé dessus. C'est le même
     relief que celui de la barre, en miniature. */
  background: rgba(0, 0, 0, .45);
  box-shadow: 1px 0 0 rgba(255, 255, 255, .07);
  flex: none;
}

/* --- Le lien d'accès ------------------------------------------------------
   Il n'a plus de losange (voir le commentaire dans index.html). Ce qui le
   distingue maintenant est sa position — après la division — et le fait qu'il
   soit rendu en blanc plein, contre le gris des liens de section : il sort du
   site, il n'y navigue pas. */
.nav-acces { color: #fff !important; font-weight: var(--graisse-grasse); }

/* --- Le bouton ------------------------------------------------------------
   Karl : « il y a juste le bouton qui est bizarre et pourrait être mieux
   fait. Et le [texte] aussi, il est trop compact à l'intérieur du bouton. »

   Deux défauts, et le second explique en partie le premier.

   1. LE TEXTE ÉTOUFFAIT. Le rembourrage venait de la version claire de la
      barre — 10 px sur 18 — et n'avait pas été revu quand le bouton est passé
      en blanc plein sur fond sombre. Un aplat clair a besoin de plus d'air
      autour de son texte qu'un aplat sombre : le blanc « avance » vers l'oeil
      et resserre visuellement ce qu'il contient. Passé à 12 px sur 24.

   2. IL NE PARLAIT PAS LA MÊME LANGUE QUE LA BANDE. La barre a désormais une
      arête vive en haut, un liseré sombre en bas et une ombre portée. Le
      bouton, lui, était un rectangle blanc parfaitement plat posé dessus — un
      autocollant sur un objet en relief. Il reprend donc le même vocabulaire,
      en plus petit : un dégradé à peine perceptible qui lui donne un galbe,
      une arête blanche au sommet, et une ombre qui le décolle de la bande.

   Il reste la seule surface claire de la barre, donc le point le plus fort —
   ce qui convient à la seule action qu'on y propose. */
/* ⚠️ LE SÉLECTEUR PORTE `.nav-liens` À DESSEIN, ET C'EST LA CLÉ DU DÉFAUT.

   `.nav-liens a { padding: 4px 0 }` vaut (0,1,1) et bat `.nav-bouton`, qui ne
   vaut que (0,1,0) — quelle que soit sa position dans le fichier. Le bouton
   n'a donc JAMAIS eu de rembourrage horizontal : mesuré, `padding: 4px 0px`,
   et le texte touchait les deux bords. C'est très exactement ce que Karl
   décrivait — « le texte est trop compact à l'intérieur du bouton » — et ce
   n'était pas une affaire de goût, mais une règle qui n'avait jamais pris.

   `.nav-liens a.nav-bouton` vaut (0,2,1) et l'emporte. Ne pas simplifier ce
   sélecteur en revenant à `.nav-bouton` : le rembourrage disparaîtrait de
   nouveau, en silence. */
.nav-liens a.nav-bouton {
  padding: 12px 24px;
  background: linear-gradient(180deg, #FFFFFF 0%, #EDECE9 100%);
  color: var(--wpn-ancre) !important;
  font-weight: var(--graisse-grasse);
  /* Un cheveu plus doux que le `--rayon-md` des boutons du corps de page :
     dans une bande de 69 px, un angle vif paraît coupant. */
  border-radius: 10px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 -1px 0 rgba(0, 0, 0, .10),
    0 2px 7px -2px rgba(0, 0, 0, .55);
  /* Le texte respire aussi à l'intérieur de lui-même : une graisse noire
     serrée sur 14 px se lit comme un bloc. */
  letter-spacing: .01em;
  transition: background .2s ease, transform .12s ease, box-shadow .2s ease;
}
/* Le jaune de la marque au survol — la couleur d'action, la même que le
   bouton principal du bloc final. Le bouton s'enfonce d'un pixel : c'est le
   seul objet en relief de la bande, il doit réagir comme tel. */
.nav-liens a.nav-bouton:hover {
  background: linear-gradient(180deg, var(--wpn-jaune-300) 0%, var(--wpn-jaune-400) 100%);
}
.nav-liens a.nav-bouton:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, .25),
    0 1px 2px -1px rgba(0, 0, 0, .4);
}

/* --- Petits écrans --------------------------------------------------------
   ⚠️ La règle d'origine masque TOUS les liens sauf le bouton sous 860 px.
   « Log in » disparaissait donc du téléphone, alors que c'est justement là
   qu'un commerçant ouvre son espace entre deux services. Il reste, avec sa
   division ; seuls les liens de section s'effacent, eux qu'on retrouve en
   défilant. */
@media (max-width: 860px) {
  .nav-groupe { display: none; }
  .nav-liens a.nav-acces { display: inline-block; }
  .nav-liens { gap: var(--espace-4); }
  /* La division tombe AVEC le groupe qu'elle sépare, et non plus tard :
     mesuré à 700 px, elle flottait seule devant « Log in », un trait qui ne
     divisait plus rien. Les deux règles doivent partager la même coupure —
     si l'une bouge un jour, l'autre bouge avec elle. */
  .nav-division { display: none; }
}

/* ==========================================================================
   ZONE — LA BARRE DU HAUT RESTE À L'ÉCRAN (29 août 2026)
   ==========================================================================
   Karl a comparé les deux versions côte à côte, sur une branche d'essai, et
   a tranché : « j'aime mieux avec la barre collante ».

   ⚠️ CE N'ÉTAIT PAS QU'UN CHANGEMENT DE `position`, ET C'EST TOUT L'ENJEU.
   Toute la page se colle en haut de l'écran — le bloc figé du héros, les
   trois blocs de la pile, la pile elle-même une fois pliée, le calculateur.
   Chacun le fait à une hauteur calculée. Une barre qui occupe 69 px devant
   eux doit être retranchée de CHAQUE calcul, sans quoi elle recouvre ce qui
   se colle dessous.

   LES SEPT DÉCALAGES CI-DESSOUS SONT SOLIDAIRES. En retirer un seul casse
   l'ensemble, et l'erreur ne se voit pas toujours tout de suite. Mesures du
   premier essai, quand il en manquait deux :

       sans le décalage de la pile pliée   69 px du bloc bleu sous la barre,
                                            et 7 px du chevron de fermeture
       sans le décalage de l'image          21 px de contenu sous l'onglet
                                            suivant, à 1512 × 850

   Le piège principal : après le pliage, `.pile-plies .pile .bloc` retire leur
   collage aux blocs et c'est le CONTENEUR `.pile` qui colle à leur place.
   Compenser `.bloc` seul ne sert donc qu'avant le pliage.

   TOUTE RETOUCHE FUTURE À LA PILE doit tenir compte de `--barre`. Les 15 cas
   de `verifier-calculateur.mjs` passent avec ces règles — les relancer après
   toute modification d'une des sept. */

/* La valeur de REPLI, pour le cas où `barre.js` ne s'exécute pas. Elle est la
   hauteur réelle mesurée le 29 août 2026 (79 px, identique de 360 à 1512 px de
   large). Le script la remplace par la mesure du moment dès qu'il tourne — et
   c'est lui qui fait autorité, parce que cette valeur-ci se démentira au
   prochain changement dans la barre. Voir l'en-tête de `barre.js`. */
:root { --barre: 79px; }

/* 1. LA BARRE COLLE POUR DE VRAI.
   La règle `body > *:not(.banc):not(.pile):not(.champ-losanges):not(.modale)`
   de la ligne 93 vaut (0,4,1) et remplace `sticky` par `relative`. On reprend
   son sélecteur en y ajoutant l'élément et la classe de la barre — (0,5,2) —
   plutôt qu'un `!important` qui masquerait la vraie raison du conflit. */
body > nav.nav:not(.banc):not(.pile):not(.champ-losanges):not(.modale) {
  position: sticky;
  top: 0;
  z-index: 60;
}

/* 2. LE BLOC FIGÉ DU HÉROS se colle sous la barre au lieu de dessous elle. */
.suite-fige { top: var(--barre); height: calc(100vh - var(--barre)); }

/* ==========================================================================
   ⚠️ LE TASSEMENT COMPLET — 29 août 2026, demande de Karl
   ==========================================================================
   « J'adore l'animation, c'est parfait comme c'est là. Le seul problème que ça
   cause, c'est qu'au fur et à mesure qu'ils s'attachent l'un à l'autre, ça
   mange de l'espace dans l'écran. Une fois qu'on est rendu au calculateur, la
   moitié de l'écran est prise par les trois onglets et la bande du haut. Il
   faudrait maintenir la même mécanique, mais libérer de l'espace pendant que
   l'animation se produit. Quand vient l'onglet violet, il embarque de la même
   façon, il arrête au même moment, mais à la fin il sort le bleu de l'écran en
   le poussant à l'extérieur. Même chose avec le jaune, et avec le calculateur.
   Mais si on scrolle de retour vers le haut, tout est rattaché de la même
   façon qu'en ce moment. »

   LE MÉCANISME EXISTAIT DÉJÀ, IL N'EN APPLIQUAIT QU'UN TIERS.

   `pile.js` compte les feuilles arrivées à leur butée et pose `--remontee` sur
   la racine. Le CSS s'en servait pour tasser l'ensemble de `--onglet / 3`, soit
   une vingtaine de pixels par cran — le « glissement subtil » que Karl avait
   demandé en session 20. Il en veut maintenant la version complète : une
   hauteur d'onglet entière par cran.

   `pile.js` N'A PAS BOUGÉ. Il posait déjà le bon compteur.

   POURQUOI LE COMPTE TOMBE JUSTE. Un bloc de rang N se colle à
   `barre + N × onglet − remontee × onglet`. Quand il vient d'arriver,
   `remontee` vaut N — la feuille qui arrive est comptée, moins la première qui
   ne tasse rien. Le calcul donne donc `barre` : **chaque bloc qui arrive se
   retrouve exactement sous la barre, et pousse tous les précédents dehors.**
   Le calculateur, à `3 × onglet`, fait de même quand les trois sont passés.

   LE RETOUR EST GRATUIT. `--remontee` diminue quand les feuilles quittent leur
   butée en remontant : la pile se ré-étage d'elle-même, sans code de retour à
   écrire. C'est la seconde moitié de la demande de Karl.

   ⚠️ CE TASSEMENT DÉPEND D'UNE CORRECTION DANS `pile.js`, FAITE LE MÊME JOUR.

   `majRemontee()` cherchait les feuilles arrivées à `i × onglet` du HAUT DE LA
   FENÊTRE. Depuis que la barre est collante, elles se collent à
   `--barre + i × onglet`, 79 px plus bas : le test n'était plus jamais vrai et
   `--remontee` restait à 0.

   Le tassement ne se déclenchait donc PLUS DU TOUT — ni le complet, ni celui
   d'un tiers d'origine. Karl l'a vu depuis son écran : « les onglets de
   couleur s'accumulent encore, et c'est seulement quand le calculateur arrive
   qu'on peut les remonter ». Le compteur ne basculait qu'en toute fin de
   course, par accident.

   `pile.js` lit maintenant `--barre` et l'ajoute à ses deux références.
   **Sans cette correction, les trois `calc()` ci-dessous ne servent à rien** :
   ils multiplient une valeur qui reste nulle.
   ========================================================================== */

/* 3. LES TROIS BLOCS DE LA PILE, tassement COMPLET.
   ⚠️ Les trois `calc()` de cette zone vont ensemble — celui-ci, celui du
   calculateur juste dessous, et celui de `.pile-plies .pile` plus bas. En
   modifier un seul désaligne la pile de son propre contenu. */
.bloc {
  top: calc(var(--barre) + var(--rang) * var(--onglet)
            - var(--remontee, 0) * var(--onglet));
}

/* 4. LE CALCULATEUR suit le même tassement que les onglets. */
.pile .section-estime {
  top: calc(var(--barre) + 3 * var(--onglet)
            - var(--remontee, 0) * var(--onglet));
}

/* 4 bis. LA PILE PLIÉE, qui est le VRAI point de collage une fois les trois
   onglets réunis. C'est le décalage qui manquait au premier essai : après le
   pliage, `.pile-plies .pile .bloc { position: relative; top: auto }` retire
   leur collage aux blocs, et c'est le conteneur `.pile` qui colle à leur
   place. Compenser `.bloc` seul ne servait donc qu'AVANT le pliage — mesuré :
   69 px du bloc bleu passaient sous la barre, et 7 px du chevron de
   fermeture avec. */
.pile-plies .pile {
  top: calc(var(--barre) - var(--remontee, 0) * var(--onglet));
}

/* 6. LA HAUTEUR D'UN BLOC DÉROULÉ perd la barre, sinon son bas passe sous
   l'onglet suivant — le défaut qu'on vient justement de réparer. */
.pile-plies .bloc.est-ouvert {
  height: calc(100svh - var(--barre) - 1.5 * var(--onglet));
}

/* 7. L'IMAGE se borne sur la hauteur ainsi réduite. Sans ce septième
   décalage, mesuré à 1512 × 850 : 21 px de contenu passaient encore sous
   l'onglet suivant. C'est le prix de chaque pixel pris par la barre — il se
   répercute jusque dans la taille des photographies. */
.pile-plies .bloc.est-ouvert .masc-pile img {
  max-height: calc(100svh - var(--barre) - 1.5 * var(--onglet) - 250px);
}

/* Il y avait ici une valeur distincte sous 860 px, posée en supposant que le
   logo sur trois lignes ferait grandir la barre. Mesuré : elle vaut 79 px de
   360 à 1512 px, sans exception. La supposition était fausse, et une seconde
   valeur en dur n'aurait fait que doubler les occasions de se tromper. */

/* ============ FIN DES SEPT DÉCALAGES DE LA BARRE COLLANTE ============ */

/* ==========================================================================
   ZONE — LA FOURCHETTE DE PRIX SUR UNE SEULE LIGNE (29 août 2026)

   Karl : « je n'aime pas que la tranche de prix soit divisée sur deux
   rangées. J'aimerais que ce soit sur la même rangée. »

   La coupure ne venait pas du JavaScript — `estime.js` écrit un simple
   `textContent`, « R$3 200 – R$4 300 », sans retour à la ligne. Elle venait
   de `text-wrap: balance` sur `.resultat-fourchette` : cette propriété
   ÉQUILIBRE les lignes d'un texte qui se coupe, et sur deux nombres séparés
   par un tiret, elle trouve toujours que la coupure la plus équilibrée est au
   milieu — exactement au tiret. Un réglage pensé pour les titres, appliqué à
   ce qui est en réalité une seule valeur.

   `estime.js` n'a pas eu à changer.
   ========================================================================== */

.resultat-fourchette {
  /* La fourchette est UNE valeur, pas une phrase : elle ne se coupe pas. */
  white-space: nowrap;
  /* `balance` est neutralisé explicitement : le laisser avec `nowrap` n'a
     plus d'effet, mais un lecteur futur croirait qu'il agit encore. */
  text-wrap: nowrap;

  /* ⚠️ LA TAILLE SE MESURE SUR LE CONTENEUR, PAS SUR LA FENÊTRE.

     Le premier réglage bornait le corps sur `vw` — la largeur de la FENÊTRE.
     Il tenait tant que le calculateur plafonnait à « Let's talk » : les prix
     affichés restaient courts. Le plafond retiré le même jour, les nombres
     ont grandi — « R$21 900 – R$29 400 » au lieu de « R$3 200 – R$4 300 » —
     et la ligne, devenue insécable, s'est mise à sortir de sa colonne : le
     dernier zéro était coupé par le bord.

     `cqi` mesure la largeur du CONTENEUR de l'afficheur, qui est ce qui borne
     réellement le texte. La colonne du résultat est étroite même sur grand
     écran : c'est elle qui décide, pas la fenêtre.

     Deux choses vont ensemble et ne doivent pas être séparées : le
     `container-type` posé sur `.estime-resultat` juste en dessous, et cette
     unité. Retirer l'un rend l'autre inopérant — `cqi` retombe alors sur la
     fenêtre, et le débordement revient. */
  font-size: clamp(0.95rem, 8.2cqi, 1.85rem);
}

/* Le conteneur de référence pour le `cqi` ci-dessus. `inline-size` ne borne
   que la largeur : la hauteur reste libre, sans quoi l'afficheur cesserait de
   s'adapter à son contenu. */
.estime-resultat { container-type: inline-size; }

/* Le prix normal barré suit la même logique, en plus petit. */
.resultat-avant s { font-size: clamp(0.75rem, 4.6cqi, 1rem); }

/* Le prix normal barré, juste au-dessus, se coupait pour la même raison. Deux
   fourchettes alignées dont l'une seule tient sur une ligne se liraient mal. */
.resultat-avant s { white-space: nowrap; }

/* ==========================================================================
   ZONE — LE DÉTAIL PAR SECTION DANS L'AFFICHEUR (29 août 2026)

   Karl : « j'aimerais qu'au-dessus du prix total, il y ait un prix relié à
   chaque section, et le prix total, c'est juste le cumulatif de chaque
   section. Ça va permettre au client potentiel, s'il veut couper, de voir
   exactement laquelle des portions lui coûte le plus cher. »

   Trois lignes, au-dessus du total, dans l'afficheur. Elles doivent se lire
   comme un décompte qu'on totalise — d'où l'alignement des nombres à droite
   et le filet au-dessus du total, qui fait le geste de la barre d'addition.

   ⚠️ LE CHIFFRE DE CHAQUE LIGNE EST DISCRET, PAS LUMINEUX. Le total garde
   seul la couleur d'affichage et son halo : c'est lui le résultat. Si les
   lignes brillaient autant, l'oeil ne saurait plus où se poser, et
   l'afficheur — le seul élément lumineux de l'objet — perdrait ce qui lui
   donne sa force.
   ========================================================================== */

.resultat-detail {
  margin: 0 0 var(--espace-4);
  padding-bottom: var(--espace-3);
  /* Le filet de l'addition : ce qui est au-dessus se totalise en dessous. */
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}

.detail-ligne {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--espace-4);
  margin: 0 0 5px;
  font-size: var(--texte-xs);
  line-height: 1.35;
}
.detail-ligne:last-child { margin-bottom: 0; }

.detail-nom { color: rgba(255, 255, 255, .55); }

.detail-prix {
  color: rgba(255, 255, 255, .82);
  font-weight: var(--graisse-grasse);
  /* Chiffres de largeur fixe : les trois nombres s'alignent colonne par
     colonne, ce qui est la condition pour les comparer d'un coup d'oeil —
     l'usage même que Karl décrit. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Un module laissé de côté garde sa ligne, estompée, avec un tiret. C'est ce
   qui permet de voir ce qu'on économise en ne le prenant pas : une ligne qui
   disparaîtrait ne dirait rien du tout. */
.detail-ligne.est-hors .detail-nom { color: rgba(255, 255, 255, .28); }
.detail-ligne.est-hors .detail-prix { color: rgba(255, 255, 255, .28); font-weight: var(--graisse-normale); }

/* ==========================================================================
   ZONE — LE PRIX QUI RENTRE, ET LA DEVISE PLUS DISCRÈTE (29 août 2026)

   Karl : « le prix est trop gros, le total sort de la fenêtre. Tu pourrais
   simplement rapetisser les chiffres pour que ça entre. Et même le R et le
   signe de dollar, il n'est pas obligé que ce soit la même grandeur que les
   chiffres. Fais juste en sorte que ça puisse rentrer. »

   Les deux réglages travaillent ensemble : la devise réduite rend au nombre
   la place qu'elle prenait, et le corps réduit finit le travail. C'est aussi
   de la meilleure typographie — dans un prix, on lit le NOMBRE ; la devise
   n'est qu'une étiquette.

   ⚠️ CE QUI A RENDU CE RÉGLAGE NÉCESSAIRE, et qu'il faut garder en tête si
   les prix changent encore : le plafond du calculateur a été retiré le même
   jour. Les nombres ne sont plus bornés — « R$21 900 – R$29 400 » là où le
   plafond donnait « R$3 200 – R$4 300 » — et la ligne, rendue insécable pour
   tenir sur une rangée, sort du cadre dès qu'elle s'allonge. Toute future
   hausse des prix affichés se heurtera à la même limite.
   ========================================================================== */

/* Le symbole monétaire : plus petit, légèrement remonté, et un cheveu détaché
   du nombre. `0.62em` le lie au corps du prix — il rétrécit avec lui. */
.resultat-fourchette .devise {
  font-size: 0.62em;
  /* Aligné sur le haut des chiffres plutôt que sur leur pied : un symbole
     réduit posé sur la ligne de base paraît tomber. */
  vertical-align: 0.28em;
  margin-right: 0.08em;
  /* La devise ne porte pas le poids du nombre : elle informe, elle n'annonce
     pas. Une graisse pleine sur deux caractères réduits fait une tache. */
  font-weight: var(--graisse-grasse);
  opacity: .78;
}

/* Le tiret de la fourchette respire moins que les nombres : c'est un
   séparateur, pas une valeur. Le resserrer récupère de la largeur là où ça ne
   coûte rien à la lecture. */
.resultat-fourchette .tiret {
  font-weight: var(--graisse-grasse);
  opacity: .55;
  margin: 0 -0.08em;
}

/* Le corps descend d'un cran. La borne haute passe de 1.85rem à 1.6rem, et le
   coefficient de conteneur de 8.2 à 7.4 : mesuré, c'est ce qu'il faut pour que
   le plus long prix atteignable tienne dans la colonne sur toutes les largeurs
   essayées, de 390 à 1512 px. */
.resultat-fourchette {
  font-size: clamp(0.9rem, 7.4cqi, 1.6rem);
}

/* Le prix barré suit : il est désormais collé au total, et deux échelles trop
   différentes casseraient la comparaison qu'il sert à faire. */
.resultat-avant s .devise { font-size: 0.68em; vertical-align: 0.22em; opacity: .8; }
.resultat-avant s .tiret { opacity: .6; margin: 0 -0.06em; }

/* Le prix normal, maintenant juste au-dessus du total : il s'en rapproche
   visuellement, sous le filet d'addition. */
.resultat-avant {
  margin-top: 0;
  margin-bottom: var(--espace-1);
}

/* ==========================================================================
   ZONE — LE MESSAGE DE DÉPENDANCE AU PORTAIL (29 août 2026)

   Karl : « certaines fonctionnalités cochées dans la fenêtre publique
   devraient activer une nécessité dans le portail. Je ne veux pas imposer
   l'idée d'un portail pour quelqu'un qui veut seulement avoir un site web. »

   Le message a deux visages, et le style les distingue sans les opposer :

     PORTAIL ALLUMÉ    un constat calme, dans le violet du module Portal —
                       « voilà ce que ça vous permet de tenir à jour ».
     PORTAIL ÉTEINT    un avertissement, dans le jaune d'attention — « voilà
                       ce que ça vous coûte ».

   Ni l'un ni l'autre n'est une erreur : décocher le portail est un choix
   légitime, que Karl tient à laisser ouvert. Le second n'est donc PAS rouge —
   un rouge dirait au client qu'il a mal fait, alors qu'il a seulement fait
   autrement.
   ========================================================================== */

.dependance {
  margin: var(--espace-4) 0 0;
  padding: 10px 13px;
  border-radius: var(--rayon-md);
  font-size: var(--texte-xs);
  line-height: 1.45;
  /* Le violet du module Portal : le message parle de lui, il en porte la
     couleur. Assez pâle pour rester un commentaire, pas une alarme. */
  background: rgba(154, 67, 193, .12);
  border: 1px solid rgba(154, 67, 193, .28);
  color: rgba(255, 255, 255, .78);
}
.dependance b { color: #fff; font-weight: var(--graisse-grasse); }

/* Le portail est éteint alors que des cases l'exigent : on passe au jaune
   d'attention. Le jaune AVERTIT, le rouge REPROCHE — et il n'y a rien à
   reprocher à quelqu'un qui a fait un choix qu'on lui a laissé. */
.dependance.est-manquant {
  background: rgba(228, 169, 60, .12);
  border-color: rgba(228, 169, 60, .34);
}
.dependance.est-manquant b { color: var(--luire, #E4A93C); }

/* ==========================================================================
   ZONE — CE QUI EST INCLUS D'OFFICE DANS LE PORTAIL (29 août 2026)

   Karl : « à partir du moment où tu écris un portail, j'aimerais que l'espace
   du haut soit occupé par juste des points qui sont écrits, de choses qui sont
   déjà incluses. Plutôt que des checkbox, que ce soit des points. Je les
   verrais dans les couleurs néons. »

   ⚠️ POURQUOI DES POINTS ET NON DES CASES À COCHER — ce n'est pas décoratif.
   Une case à cocher dit « à vous de choisir ». Ces cinq lignes ne se
   choisissent pas : quatre sont les principes non négociables de l'entreprise
   (ENTREPRISE.md § 2), la cinquième est une propriété que Karl veut donner à
   tout portail. Leur montrer une case, même cochée d'avance, laisserait croire
   qu'on peut les retirer — et donc qu'elles se paient.

   Le point lumineux dit l'inverse : c'est un voyant, pas un interrupteur.
   L'objet est un instrument ; sur un instrument, un voyant allumé signale un
   état acquis, et personne n'essaie de l'éteindre.

   LE NÉON, ET SA MESURE. Chaque point porte sa teinte dans `--neon` et
   s'allume par deux ombres portées de la même couleur, l'une serrée, l'autre
   large. C'est ce qui fait la lueur d'un vrai néon : un cœur saturé et un halo
   qui déborde. Les cinq teintes ne sont pas les couleurs de marque
   — celles-ci désignent les trois MODULES et les confondre brouillerait la
   lecture ; ce sont cinq lumières distinctes, assez espacées dans le spectre
   pour qu'aucune ne se confonde avec sa voisine.
   ========================================================================== */

.inclus {
  margin: 0 0 var(--espace-6);
  padding: 14px 16px 15px;
  border-radius: var(--rayon-md);
  /* Un renfoncement dans la façade de l'instrument : plus sombre que le
     panneau, avec une arête vive en bas — l'inverse d'un bouton, qui bombe. */
  background: rgba(0, 0, 0, .22);
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, .5),
    inset 0 -1px 0 rgba(255, 255, 255, .05);
}

.inclus-titre {
  margin: 0 0 11px;
  font-size: var(--texte-xs);
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: var(--graisse-grasse);
  color: rgba(255, 255, 255, .38);
}

.inclus-liste {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  /* Deux colonnes dès qu'il y a la place : cinq lignes en colonne unique
     pousseraient les commandes trop bas dans un panneau déjà chargé. */
  grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
  gap: 9px 22px;
}

.inclus-point {
  position: relative;
  padding-left: 17px;
  font-size: var(--texte-xs);
  line-height: 1.45;
  color: rgba(255, 255, 255, .52);
}

/* Le voyant. Posé sur la première ligne du texte et non centré sur le bloc :
   sur une entrée de trois lignes, un point centré flotte au milieu de nulle
   part. */
.inclus-point::before {
  content: "";
  position: absolute;
  left: 0;
  top: .48em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--neon);
  box-shadow:
    0 0 5px var(--neon),
    0 0 11px color-mix(in srgb, var(--neon) 55%, transparent);
}

.inclus-point b {
  display: block;
  color: #fff;
  font-weight: var(--graisse-grasse);
  margin-bottom: 1px;
}

/* Les cinq lumières. Ajouter une entrée demande une teinte de plus ici. */
.inclus-point.teinte-1 { --neon: #4CE0B3; }   /* vert d'eau */
.inclus-point.teinte-2 { --neon: #E4A93C; }   /* ambre, la lumière de l'afficheur */
.inclus-point.teinte-3 { --neon: #6FB8FF; }   /* bleu clair */
.inclus-point.teinte-4 { --neon: #C58CFF; }   /* violet clair */
.inclus-point.teinte-5 { --neon: #FF8FA3; }   /* rose */

/* Le panneau éteint — module décoché — estompe tout, y compris les voyants :
   un néon allumé sur un module qu'on n'a pas pris se lirait comme une
   promesse. Le halo tombe, la couleur reste. */
.panneau.est-eteint .inclus-point::before { box-shadow: none; opacity: .45; }

@media (prefers-reduced-motion: no-preference) {
  /* Aucune animation : un voyant qui clignote demande qu'on le regarde, or
     ces lignes se lisent une fois et s'oublient. La lueur suffit. */
}

/* Le « ≈ » des sections. Plus petit et plus effacé que le nombre : il qualifie
   la valeur, il n'en fait pas partie. Karl : « à côté de Window et Portal, un
   petit symbole à peu près » — pour dire de ces lignes ce que la fourchette dit
   déjà du total, qu'elles situent sans facturer. */
.detail-environ {
  font-size: .85em;
  opacity: .5;
  font-weight: var(--graisse-normale);
  margin-right: .1em;
}

/* ==========================================================================
   ZONE — PASSE 1 DE L'ADAPTATION TÉLÉPHONE (29 août 2026)

   Karl a parcouru le site sur son téléphone et décrit ce qu'il voit. Cette
   première passe traite ce qui se règle sans toucher à la mécanique : le
   texte du héros, les boutons, le compteur, les réalisations. La pile et le
   calculateur suivent dans deux passes séparées — ils demandent de la
   structure, pas du réglage.

   RELEVÉ AVANT TRAVAUX, à 390 × 844 (fait sur la production) :

       hauteur totale          10 048 px, soit 11,9 écrans
       débordement horizontal  aucun
       vide réel                8 % seulement

   ⚠️ LA LONGUEUR EST LE PROBLÈME, PAS LES TROUS. Karl parlait « d'espaces
   blancs entre chaque onglet » ; la mesure montre que la page n'a presque pas
   de vide — elle est simplement trop longue, chaque section faisant un écran
   ou plus. C'est en réduisant les sections, pas en comblant des trous, qu'on
   règle son impression.
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. LE SUR-TITRE DU HÉROS, SUR UNE LIGNE -------------------------
     Karl : « le premier texte dans le header, je le mettrai pleine ligne pour
     qu'il prenne moins d'espace. »

     « EVERYTHING A SMALL BUSINESS NEEDS ONLINE » passait sur deux lignes.
     Trois réglages ensemble le ramènent sur une : un corps plus petit, un
     interlettrage resserré (0,14 em sur 41 caractères coûtent à eux seuls
     près de six caractères de largeur), et un losange plus petit avec moins
     d'écart. `white-space: nowrap` garantit le résultat, et le corps s'adapte
     à la largeur réelle plutôt qu'à une valeur devinée. */
  .heros-etiquette {
    white-space: nowrap;
    font-size: clamp(8px, 2.55vw, 11px);
    letter-spacing: .07em;
    gap: 6px;
    margin-bottom: var(--espace-3);
  }
  .heros-etiquette::before { width: 7px; height: 7px; }

  /* --- 2. LES BOUTONS, EN PILULES ---------------------------------------
     Karl : « j'adapterais les boutons carrés et je les mettrai vraiment plus
     au rond aux extrémités, plus petit, vraiment plus adapté pour un format
     de téléphone. »

     Le rayon passe à 999 px — la pilule franche, pas l'angle adouci. C'est
     déjà le parti pris du formulaire de la page d'attente, où Karl avait
     demandé « rien de carré ».

     La hauteur descend à 44 px de cible réelle (12 px de rembourrage vertical
     sur un corps de 14) : c'est le minimum tactile admis, et pas moins. Ils
     cessent d'occuper toute la largeur — un bouton pleine largeur sur
     téléphone se lit comme une barre, pas comme un bouton. */
  .btn {
    padding: 12px 24px;
    border-radius: 999px;
    font-size: var(--texte-sm);
  }
  .btn::before { border-radius: 999px; }

  /* Les boutons se rangent côte à côte tant qu'ils tiennent, et passent l'un
     sous l'autre sinon — sans jamais s'étirer sur toute la largeur. */
  .heros-actions, .final-actions {
    display: flex; flex-wrap: wrap; gap: 10px;
  }
  .heros-actions .btn, .final-actions .btn { flex: 0 1 auto; }

  /* --- 3. LE COMPTEUR, LES TROIS CHIFFRES CÔTE À CÔTE -------------------
     Karl : « le compteur aussi avec les chiffres en dessous, je mettrai les
     chiffres un à côté de l'autre. »

     La grille passe de `minmax(160px, 1fr)` — qui ne laisse tenir qu'une
     colonne sur 390 px — à trois colonnes franches. Le corps du chiffre suit
     la largeur disponible pour que « 0 monthly software fees » ne déborde
     pas de son tiers. */
  .section-compte .chiffres {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--espace-4);
    text-align: center;
  }
  .section-compte .chiffre b { font-size: clamp(1.9rem, 9vw, 2.8rem); }
  .section-compte .chiffre span { font-size: 11px; line-height: 1.3; }

  /* --- 4. LES RÉALISATIONS, UNE LIGNE CHACUNE ---------------------------
     Karl : « au lieu de mettre trois grandes fenêtres, une en dessous de
     l'autre, il faudrait qu'on condense ça et que ce soit rapetissé. »

     Mesuré : chaque carte faisait environ 570 px, soit près d'un écran
     entier ; les trois occupaient deux écrans. En ligne — aperçu carré à
     gauche, texte à droite — chacune tombe sous 150 px.

     DEUX COLONNES ONT ÉTÉ ÉCARTÉES, et c'est un vrai choix : à 390 px de
     large, une vignette sur deux colonnes fait 175 px, et l'aperçu d'un site
     entier y devient illisible — or c'est précisément ce que ces images
     doivent montrer. La ligne garde une image lisible ET divise la hauteur. */
  .travaux { gap: var(--espace-4); }
  .travail {
    display: grid;
    grid-template-columns: 108px 1fr;
    align-items: center;
    gap: var(--espace-4);
    padding: 10px;
  }
  .travail-image {
    /* Carrée plutôt qu'en 16:10 : à cette taille, le format large ne montre
       plus qu'une bande. Le cadrage reste ancré en haut, sur le titre du site. */
    aspect-ratio: 1 / 1;
    border-radius: var(--rayon-md);
  }
  .travail-texte { padding: 0; }
  .travail h3 { font-size: var(--texte-base); margin-bottom: 4px; }
  /* La description disparaît : sur une ligne de 108 px de haut, elle ne tient
     pas, et le nom du commerce plus les trois puces disent déjà ce qu'il faut.
     Elle reste dans le document pour les lecteurs d'écran. */
  .travail p { display: none; }
  .travail-meta { margin-top: 6px; gap: 5px; }
  .travail .puce { font-size: 9px; padding: 3px 6px; }
  /* Le zoom au survol n'a aucun sens sur un écran tactile, et il déforme la
     vignette pendant le défilement. */
  .travail:hover { transform: none; }
  .travail:hover .travail-image { transform: none; }
}

/* ==========================================================================
   ZONE — PASSE 3 : LE CALCULATEUR SUR TÉLÉPHONE (29 août 2026)

   Karl, en le parcourant sur son téléphone : « le calculateur est coupé au
   bas. Le texte au haut du bloc, il faudrait réarranger la disposition de
   cette partie. L'image aussi, le personnage pourrait être trois fois plus
   petit — présentement, le problème, c'est qu'il embarque par-dessus le
   texte. Je ne peux pas vraiment regarder le calculateur. »

   MESURÉ à 390 × 844 avant correction :

       mascotte            146 × 345 px, et elle chevauche le titre
       cadre du calcul     coupé de 137 px sous le bas de l'écran
       réserve de texte    109 px pour une mascotte de 146 — d'où le
                           chevauchement de 37 px

   ⚠️ LA RÉSERVE ÉTAIT PLUS ÉTROITE QUE LA MASCOTTE. Une règle existante
   protégeait déjà le texte avec `clamp(100px, 28vw, 140px)`, mais l'image ne
   se réduisait pas en dessous de 146 px : la protection était calculée sur une
   largeur que l'image ne respectait pas. Réduire la mascotte ET la réserve
   ensemble est la seule façon de faire tenir les deux — corriger l'une sans
   l'autre laisse le défaut.
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. LA MASCOTTE, TROIS FOIS PLUS PETITE ET HORS DU TEXTE ---------
     De 146 px de large à 52 : c'est le « trois fois plus petit » de Karl,
     mesuré et non estimé. À cette taille elle reste reconnaissable — elle
     tient un calculateur, c'est tout ce qu'on lui demande de dire — et elle
     cesse d'être un obstacle.

     Elle est ancrée au bord DROIT plutôt qu'à une position calculée sur la
     largeur du texte : sur 390 px, `min(58ch, …)` la plaçait au-delà du bord,
     et c'est le repli à 150 px qui décidait — une valeur pensée pour un écran
     large. Au bord droit, sa place ne dépend plus de la longueur du titre. */
  .entete-image {
    left: auto;
    right: 4px;
    top: 4px;
    width: 52px;
    /* ⚠️ `align-items: stretch` est le défaut du conteneur, et il ÉTIRE
       l'image sur toute la hauteur du bloc : mesuré, 52 × 247 au lieu des
       52 × 123 de ses proportions. Le personnage devenait un fil. */
    align-items: flex-start;
  }
  .entete-image img {
    width: 52px;
    /* `height: 100%` est posé plus haut dans le fichier : il faut le défaire
       explicitement, sans quoi `width` ne décide de rien. */
    height: auto;
    /* L'ombre portée passe à l'échelle : à 52 px, une ombre de 18 px de
       décalage fait une tache plus grande que le personnage. */
    filter: drop-shadow(0 6px 8px rgba(0, 0, 0, .45));
  }

  /* La réserve suit la nouvelle largeur : 52 px d'image plus 10 px d'air.
     Elle valait 109 px pour rien — c'est 47 px de largeur de texte rendus au
     titre et à la description, sur un écran qui en compte 390. */
  .entete-mots,
  .entete-texte { padding-right: 66px; }

  /* --- 2. LA DISPOSITION DU HAUT, RESSERRÉE ----------------------------
     Karl : « le texte au haut du bloc du calculateur, il n'est pas trop mal,
     il faudrait réarranger la disposition de cette partie. »

     Le titre en trois lignes puis la description en cinq occupaient plus du
     tiers de l'écran avant qu'on voie le premier réglage. Sur téléphone, ce
     qui compte est l'instrument, pas son annonce : le titre garde sa présence,
     la description se resserre. */
  /* ⚠️ C'EST L'EN-TÊTE QUI COUPAIT LE CADRE, PAS LA HAUTEUR DU CADRE.

     Le diagnostic a d'abord porté à faux : on a borné la hauteur du cadre, et
     il l'a bien respectée — 452 px sur 543 autorisés — tout en dépassant
     quand même de 43 px. Mesuré ensuite : il COMMENÇAIT à 435 px du haut,
     alors que la section commence à 169. L'en-tête occupait donc 266 px avant
     que le premier réglage apparaisse.

     Borner une hauteur ne sert à rien quand c'est la position qui est en
     cause. Ce sont le sur-titre, le titre et la description qu'il faut
     resserrer — chaque pixel rendu ici est un pixel gagné par l'instrument. */
  .section-estime .section-etiquette {
    font-size: 10px;
    letter-spacing: .1em;
    margin-bottom: 6px;
  }
  .section-estime .entete-titre {
    font-size: clamp(1.35rem, 6.6vw, 1.8rem);
    line-height: 1.05;
  }
  .section-estime .entete-texte {
    font-size: 13px;
    line-height: 1.4;
    margin-top: 6px;
  }
  .estime-entete { margin-bottom: 2px; }
  /* Le rembourrage haut de la section : il valait pour un écran large où
     l'espace ne manque pas. Chaque pixel rendu ici remonte le cadre d'autant,
     et c'est sa position — non sa hauteur — qui décidait du débordement. */
  .pile .section-estime > .enveloppe { padding-top: 4px; padding-bottom: 4px; }

  /* --- 3. LE CADRE NE SE COUPE PLUS ------------------------------------
     Il dépassait de 137 px sous le bas de l'écran. La section est collée à sa
     butée : ce qui déborde n'est plus jamais atteignable, puisque la page
     défile derrière elle.

     La hauteur du cadre se borne donc à ce qui reste réellement — la fenêtre,
     moins la barre du haut, moins ce que la pile occupe encore au-dessus. Ce
     qui ne tient pas défile DANS le cadre plutôt que hors de l'écran. */
  .pile .section-estime .estime-cadre {
    /* Mesuré : avec 24 px de marge, le cadre débordait encore de 43 px sous
       le bas de l'écran. La section ne se colle pas exactement sous la barre —
       il reste des bandes au-dessus d'elle selon l'état de la pile — et le
       calcul théorique sous-estimait donc ce qui est pris. 90 px couvrent
       l'écart mesuré avec de la marge, plutôt que de viser au plus juste et
       de recouper au premier changement. */
    max-height: calc(100svh - var(--barre, 79px) - 3 * var(--onglet) - 105px);
    overflow-y: auto;
    /* Le défilement interne doit rester fluide au doigt, et ne pas entraîner
       la page quand on arrive au bout du cadre. */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  /* --- 4. L'INSTRUMENT LUI-MÊME, PLUS COMPACT --------------------------
     Les trois onglets de module passent en pleine largeur : côte à côte sur
     390 px, « Your Network » se cassait en deux lignes et l'interrupteur se
     collait au texte. */
  .estime-onglets { grid-template-columns: 1fr; gap: 6px; }
  .onglet { padding: 8px 10px; }
  .onglet-quoi { display: none; }   /* la description tient dans le titre */
  .onglet-nom { font-size: var(--texte-sm); }

  /* Les commandes les unes sous les autres : deux colonnes sur 390 px
     donnaient des curseurs de 160 px, trop courts pour se régler au doigt. */
  .bloc-rangee { grid-template-columns: 1fr; gap: 12px; }
  .estime-corps { padding: 12px; gap: 12px; }

  /* La zone des acquis se resserre : cinq entrées de trois lignes chacune
     repoussaient les commandes hors de vue. */
  .inclus { padding: 11px 12px 12px; margin-bottom: var(--espace-4); }
  .inclus-liste { gap: 7px; }
  .inclus-point { font-size: 11px; line-height: 1.35; }
}

/* ==========================================================================
   ZONE — PASSE 4 DE L'ADAPTATION TÉLÉPHONE (29 août 2026)

   Trois corrections après un second passage de Karl sur son téléphone. Les
   deux premières corrigent des erreurs de la passe précédente.
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. LE CHAPÔ DU HÉROS, EN PLEINE LARGEUR ------------------------
     ⚠️ J'AVAIS TRAITÉ LE MAUVAIS TEXTE. Karl : « le texte AU-DESSUS DU
     PREMIER BOUTON "change something, watch it publish" — ce texte-là pourrait
     prendre toute la largeur de l'écran et compter uniquement quatre à cinq
     lignes. » Il parlait du paragraphe ; la passe 3 a resserré le sur-titre.

     La cause : `max-width: min(58%, 50ch)`, posée pour que le résumé s'arrête
     avant la zone du logo — ce qui n'a de sens que sur un écran large où le
     logo est à droite. Sur téléphone il est au-dessus, et la réserve ne
     protégeait plus rien : mesuré, 198 px de large sur 390, et **neuf lignes**.

     En pleine largeur, le même texte tient en cinq. */
  .heros-chapo {
    max-width: none;
    font-size: 15px;
    line-height: 1.5;
  }

  /* --- 2. LE CALCULATEUR EN PLEINE LARGEUR ----------------------------
     Karl : « est-ce qu'on pourrait mettre la largeur du calculateur plein
     écran pour aller chercher plus d'espace ? Je crois que ça peut être
     beaucoup plus haut parce qu'on a besoin justement d'espace. »

     Mesuré : le cadre faisait 310 px sur un écran de 390 — **80 px perdus en
     marges**, soit un quart de sa largeur. Sur un instrument déjà à l'étroit,
     c'est la première chose à rendre.

     L'enveloppe perd son rembourrage latéral pour cette section seulement, et
     le cadre va d'un bord à l'autre. Il garde ses coins arrondis en haut : à
     angles vifs, il cesserait de se lire comme un objet posé sur la page. */
  .pile .section-estime > .enveloppe {
    padding-left: 0;
    padding-right: 0;
    max-width: none;
  }
  /* Le texte de l'en-tête, lui, garde ses marges : un titre collé au bord se
     lit mal. Seul le cadre s'étend. */
  .section-estime .estime-entete { padding-inline: 14px; }
  .pile .section-estime .estime-cadre {
    border-radius: var(--rayon-lg) var(--rayon-lg) 0 0;
    /* Le cadre peut monter plus haut, comme Karl le demande : la borne passe
       de 105 px de réserve à 60. Ce qui ne tient toujours pas défile dedans. */
    max-height: calc(100svh - var(--barre, 79px) - 3 * var(--onglet) - 60px);
  }

  /* --- 3. LES TROIS ONGLETS CÔTE À CÔTE ------------------------------
     ⚠️ LA PASSE 3 LES AVAIT EMPILÉS, ET C'ÉTAIT UNE ERREUR. Karl : « je
     mettrais les trois onglets un à côté de l'autre plutôt. Ils peuvent être
     vraiment plus petits. Si on élargit, ça va nous donner la place de mettre
     les trois onglets un à côté de l'autre de façon ergonomique. »

     Il a raison, et c'est la largeur retrouvée qui le permet : à 310 px, trois
     onglets faisaient 95 px chacun et « Your Network » se cassait en deux
     lignes — d'où l'empilement. À 390 px pleine largeur, ils font 126 px.

     Et le « Your » disparaît : côte à côte, les trois se lisent comme une
     série, le mot est redondant sur chacun. « Window · Portal · Network »
     tient largement, et c'est 5 caractères rendus à chaque onglet. */
  .estime-onglets { grid-template-columns: repeat(3, 1fr); gap: 4px; }
  .onglet { padding: 7px 6px; flex-direction: column; align-items: stretch; gap: 4px; }
  .onglet-corps { gap: 5px; }
  .onglet-icone { width: 16px; height: 16px; flex: none; }
  .onglet-nom { font-size: 11px; letter-spacing: -.01em; }
  .mot-your { display: none; }
  .onglet-etat { font-size: 9px; }
  .onglet-bascule { align-self: flex-start; }
  .bascule-piste { transform: scale(.82); transform-origin: left center; }

  /* --- 4. LA FENÊTRE DE VISITE RESTE DANS L'ÉCRAN --------------------
     Karl : « à partir du moment où on clique quelque part dans le calculateur,
     le tutoriel commence, mais on voit juste une partie de la fenêtre et
     l'endroit cliquable pour poursuivre est à l'extérieur du champ. »

     La fenêtre est posée par `estime.js` À CÔTÉ de ce qu'elle désigne, avec un
     `top` calculé en pixels — juste sur un grand écran, intenable sur 390 ×
     844 où l'élément visé est souvent en bas. Ses boutons se retrouvaient
     hors champ, et la visite devenait impossible à poursuivre.

     Sur téléphone, elle s'ancre donc EN BAS DE L'ÉCRAN, comme le font les
     visites guidées des applications : elle ne désigne plus par sa position —
     l'écran est trop petit pour cela — mais elle reste toujours entièrement
     lisible et ses deux boutons toujours atteignables.

     ⚠️ `!important` est nécessaire : `estime.js` pose `top` et `left` en style
     en ligne, qui l'emporte sur toute règle de feuille. */
  .visite {
    position: fixed !important;
    top: auto !important;
    bottom: 14px !important;
    left: 12px !important;
    right: 12px !important;
    width: auto !important;
    max-width: none;
    transform: none !important;
    /* Au-dessus de la barre du haut, qui est en z-index 60. */
    z-index: 90;
    max-height: 46svh;
    overflow-y: auto;
  }
  /* La flèche qui pointait vers l'élément désigné n'a plus de sens quand la
     fenêtre est ancrée en bas : elle pointerait vers le vide. */
  .visite.vise-bas::before { display: none; }
  /* Les boutons prennent de la place au doigt et restent côte à côte. */
  .visite-pied { display: flex; gap: 10px; align-items: center; }
  .visite-ok { padding: 11px 20px; border-radius: 999px; }
  .visite-passer { padding: 11px 14px; }
}

/* ==========================================================================
   ZONE — PASSE 5 : DONNER SA HAUTEUR AU CALCULATEUR (29 août 2026)

   Karl : « Je crois que ça peut être beaucoup plus haut parce qu'on a besoin
   justement d'espace. […] Je veux qu'il y ait quand même une bonne
   compréhension au premier œil, ce qu'il n'y a pas présentement. »
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. LE CADRE PREND EXACTEMENT LA PLACE QUI RESTE ------------------
     ⚠️ CORRECTION D'UNE MÉTHODE FAUSSE, PAS D'UNE VALEUR. La passe 4 bornait
     le cadre par `max-height: calc(100svh - …)`. Mesuré une fois le panneau
     des commandes déplié : le cadre allait de 297 à 870 sur un écran de 844 —
     **26 px sous le bord**, avec 1015 px de contenu à faire défiler dedans.

     La raison est structurelle : un calcul en `svh` mesure l'écran, mais
     ignore ce que le cadre a AU-DESSUS de lui — la barre, le sur-titre, le
     titre, le chapô — dont la hauteur change avec la largeur de l'écran et la
     langue. Aucune constante ne peut représenter ça ; j'en avais choisi une
     (60 px), et elle était fausse dès que le titre passait à trois lignes.

     La répartition en flex n'a pas ce défaut : l'enveloppe reçoit la hauteur
     de l'écran, l'en-tête prend ce qu'il lui faut, et le cadre reçoit **le
     reste, quel qu'il soit**. Le résultat est juste sur tous les téléphones,
     et il le restera si l'en-tête change. */
  .pile .section-estime > .enveloppe {
    display: flex;
    flex-direction: column;
    /* ⚠️ `min-height`, JAMAIS `height`. Mesuré avec une hauteur fixe : replié
       le cadre était juste, mais déplié il montait à 1936 px pour 780 px
       d'écran — il débordait de son enveloppe bornée au lieu de la pousser.

       La cause est un choix de conception du calculateur, inscrit plus haut
       dans ce fichier : « rien ne défile à l'intérieur : le bloc grandit et
       pousse la page ». C'est le bon choix sur téléphone — deux défilements
       imbriqués font perdre le doigt du visiteur — et une hauteur fixe le
       contredisait frontalement.

       Avec un plancher, les deux états sont servis par la même règle : replié,
       le cadre s'étire pour remplir l'écran (c'est la hauteur que Karl
       demande) ; déplié, il dépasse ce plancher et pousse la page comme
       prévu. La réserve du bas laisse voir les onglets de la pile, sans quoi
       le visiteur ne saurait pas qu'il y a autre chose sous le calculateur. */
    min-height: calc(100svh - var(--barre, 79px) - 2 * var(--onglet, 46px));
    max-height: none;
  }
  .pile .section-estime .estime-entete { flex: 0 0 auto; }
  .pile .section-estime .estime-cadre {
    /* Grandit pour remplir l'écran quand il reste de la place, mais ne rétrécit
       jamais sous sa taille naturelle : c'est `0` en flex-shrink qui garantit
       que les commandes dépliées ne se font pas écraser. */
    flex: 1 0 auto;
    max-height: none;
  }

  /* --- 2. L'EN-TÊTE REND DE LA HAUTEUR AU CADRE -----------------------
     Mesuré : l'en-tête occupait 200 px des 765 disponibles, dont 81 pour un
     titre étalé sur trois lignes à 25,7 px. Tout ce qu'il rend, le cadre le
     prend — c'est maintenant vrai automatiquement, grâce au flex ci-dessus.

     Le texte n'est pas raccourci, seulement resserré : c'est lui qui explique
     à quoi sert l'outil, et Karl demande justement plus de compréhension au
     premier coup d'œil, pas moins. */
  .entete-titre { font-size: 22px; line-height: 1.16; }
  .estime-entete .estime-description,
  .estime-entete > p { font-size: 13.5px; line-height: 1.45; }
  .entete-sur { font-size: 10px; }

  /* --- 3. « LAUNCH PRICE » N'EST PLUS UN TITRE ORPHELIN ----------------
     ⚠️ DÉFAUT INTRODUIT PAR MOI. Pour gagner de la place, j'avais masqué
     `.lancement-note` sur téléphone — la phrase qui explique que les dix
     premiers clients ont ce tarif. Il restait à l'écran « Launch price » seul,
     suivi de rien : une étiquette qui n'apprend rien et qui fait douter que
     quelque chose se soit mal affiché.

     La phrase courte ajoutée dans le HTML dit l'essentiel sur une ligne. Elle
     n'existe que pour le téléphone : sur grand écran, la note complète est
     lisible et cette version brève serait un doublon. */
  /* ⚠️ Sélecteur en DEUX classes, et c'est délibéré. La règle par défaut
     `.lancement-bref { display: none }` est écrite plus bas dans le fichier :
     à spécificité égale, c'est la dernière qui gagne, y compris contre un
     média. Il faut donc la battre en spécificité (0,2,0 contre 0,1,0), pas
     compter sur l'ordre. */
  .resultat-lancement .lancement-bref {
    display: inline;
    font-size: 11.5px;
    color: var(--gris-doux, #9aa0aa);
    margin-left: 6px;
  }

  /* --- 4. LES COMMANDES SE DEVINENT DEPUIS L'ÉTAT REPLIÉ ---------------
     Karl : « une bonne compréhension au premier œil ». Replié, l'outil ne
     montre qu'un prix : rien ne dit qu'il se calcule, ni qu'on peut le faire
     bouger. La tirette « Work out a price » est ce qui ouvre les commandes —
     elle mérite de se lire comme une invitation, pas comme un pied de bloc. */
  .estime-tirette {
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .14);
    font-weight: 600;
  }
}

/* Hors téléphone, la mention brève n'a pas lieu d'être : la note complète est
   affichée juste à côté. Déclarée ici pour que la règle par défaut ne dépende
   pas de l'ordre des médias. */
.lancement-bref { display: none; }

/* Sur les écrans les plus étroits (360 px et moins), le chapô du héros
   retombait à six lignes malgré la pleine largeur. Karl en veut « quatre à
   cinq » : un demi-point de police de moins suffit à le ramener à cinq, sans
   descendre sous une taille confortable à lire. */
@media (max-width: 375px) {
  .heros-chapo { font-size: 14px; }
}

/* ==========================================================================
   ZONE — PASSE 6 : LE BOUTON D'APPEL N'EST PLUS RECOUVERT (29 août 2026)

   Défaut trouvé en regardant le rendu, pas signalé : sur téléphone, le bouton
   « Get a real number » passait SOUS la tirette « Work out a price » — mesuré,
   19 px de recouvrement. Il restait cliquable, mais coupé en deux à l'écran,
   ce qui le fait lire comme un défaut d'affichage plutôt que comme le bouton
   principal du bloc. C'est l'unique geste que l'outil cherche à provoquer.

   Il ne vient pas des passes précédentes : il est visible sur les captures
   d'avant. Le bloc de résultat demande simplement plus de hauteur que le cadre
   n'en offre, et déborde par le bas.

   La correction resserre ses espacements internes plutôt que d'agrandir le
   cadre : agrandir le cadre reprendrait la place réservée aux onglets de la
   pile, dont Karl a besoin pour savoir qu'il y a autre chose en dessous.
   ========================================================================== */

@media (max-width: 620px) {
  .estime-resultat { padding-block: 6px; }
  /* Les lignes par module (« Your Window ≈ R$2 400 ») se suffisent d'un
     interligne serré : ce sont des chiffres alignés, pas de la prose. */
  .estime-resultat .resultat-lignes { line-height: 1.32; }
  .resultat-etiquette { margin-bottom: 1px; letter-spacing: .08em; }
  .estime-cadre:not(.est-replie) .resultat-lancement { margin-bottom: 6px; }
  .resultat-lancement { margin-bottom: 4px; padding-top: 4px; }
  .resultat-fourchette { margin-bottom: 4px; }
  /* Une réserve sous le bouton garantit qu'il ne touche jamais la tirette,
     même si le contenu du résultat grandit d'une ligne dans une autre langue. */
  .estime-resultat .resultat-action,
  .estime-resultat > a:last-child,
  .estime-resultat > button:last-child { margin-bottom: 4px; }
}

/* --------------------------------------------------------------------------
   PASSE 6b — LES TÉLÉPHONES COURTS (29 août 2026)

   Mesuré sur les trois formats après la passe 6 : à 390 et 430 px de large le
   bouton est dégagé, mais sur un 360 × 780 il restait recouvert — et cette
   fois `elementFromPoint` le donnait **non cliquable**, donc réellement perdu
   pour le visiteur, pas seulement mal cadré.

   Le critère est la HAUTEUR, pas la largeur : c'est elle qui manque. Cibler
   `max-width` aurait pénalisé un téléphone étroit mais long, qui n'a pas le
   problème. Deux gestes complémentaires, aucun suffisant seul :
   la réserve du bas se resserre (les onglets de la pile restent visibles,
   simplement moins hauts), et le grand chiffre rend quelques pixels.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) and (max-height: 820px) {
  .pile .section-estime > .enveloppe {
    min-height: calc(100svh - var(--barre, 79px) - 1.25 * var(--onglet, 46px));
  }
  .resultat-fourchette { font-size: 26px; }
  .entete-titre { font-size: 21px; }
  .estime-entete .estime-description,
  .estime-entete > p { font-size: 13px; }
}

/* --------------------------------------------------------------------------
   PASSE 6c — PLUS AUCUN CHEVAUCHEMENT DANS LE CADRE (29 août 2026)

   Diagnostic à 360 × 780, après avoir mesuré au lieu de deviner : le bloc de
   résultat occupe 394→687 pendant que la tirette occupe 655→701. Les deux se
   recouvrent sur 32 px, alors qu'ils sont l'un après l'autre dans le flux.

   La cause n'est ni une marge ni un `position`, mais la répartition flex : le
   cadre distribue une piste plus courte que la hauteur naturelle du résultat,
   et le contenu déborde par-dessus le voisin au lieu de le pousser. Resserrer
   les espacements (passe 6) traitait le symptôme et ne pouvait pas suffire —
   à chaque pixel gagné, la répartition en reprenait un autre.

   `flex: 0 0 auto` sur chaque enfant supprime la répartition elle-même :
   chacun garde exactement sa hauteur naturelle et pousse le suivant. Si la
   somme dépasse le cadre, celui-ci grandit et la page défile — le
   comportement déjà retenu pour le calculateur, et de loin préférable à un
   bouton d'appel que le doigt ne peut pas atteindre.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  /* ⚠️ LE BON NIVEAU EST `.estime-corps`, ET J'AVAIS VISÉ LE MAUVAIS. Un
     premier essai posait `flex: 0 0 auto` sur les enfants de `.estime-cadre` :
     sans effet, parce que le cadre n'a qu'un seul enfant, `.estime`. La chaîne
     réelle, relevée dans le DOM :

       .estime-cadre (flex) > .estime (flex) > .estime-corps > .estime-plan
       (grid) > .estime-resultat

     La tirette est frère de `.estime-corps`, pas du résultat. C'est donc
     `.estime-corps` que la répartition comprime, et son contenu — le résultat,
     avec le bouton d'appel au bout — qui déborde par-dessus la tirette. */
  .estime > .estime-corps { flex: 0 0 auto; }
  .estime-cadre .estime-resultat { min-height: 0; }
}

/* --------------------------------------------------------------------------
   PASSE 6d — L'APERÇU REPLIÉ NE COUPE PLUS LE BOUTON D'APPEL (29 août 2026)

   LA CAUSE, enfin trouvée en sondant toute la chaîne au lieu de supposer :

     .estime-cadre.est-replie .estime-corps {
       max-height: clamp(300px, 42svh, 430px);  overflow: hidden;
     }

   Sur un écran de 780 px, 42svh vaut 327,6 px — exactement la hauteur mesurée.
   Or le bloc de résultat commence 79 px après le haut du corps et en fait 293 :
   il lui faut 372 px. Les 44 px manquants sont coupés par `overflow: hidden`,
   et c'est précisément la moitié basse du bouton « Get a real number » qui
   tombe dedans. `elementFromPoint` le confirmait : non cliquable.

   Cette règle n'a jamais été fautive sur grand écran, où le résultat est dans
   la colonne de droite et n'est donc jamais coupé. C'est le passage en une
   colonne sur téléphone, avec `order: -1` qui remonte le résultat en tête, qui
   le fait entrer dans la zone tronquée. Le défaut ne pouvait apparaître que là.

   Les trois passes précédentes s'étaient trompées de niveau — répartition
   flex, espacements, hauteur du cadre — parce que je traitais un symptôme
   (« ça se chevauche ») sans avoir mesuré le conteneur qui coupait vraiment.

   Ici, le plancher devient assez haut pour contenir le résultat ENTIER plus la
   zone d'estompage, et l'estompage se raccourcit à 70 px, faute de place pour
   130 sur un téléphone. L'aperçu garde son sens — on voit que ça continue —
   sans amputer le seul bouton que le bloc cherche à faire cliquer.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  .estime-cadre.est-replie .estime-corps {
    max-height: clamp(400px, 58svh, 520px);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 70px), transparent);
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 70px), transparent);
  }
}

/* --------------------------------------------------------------------------
   PASSE 6e — LE COMPROMIS SUR LES ÉCRANS COURTS (29 août 2026)

   La passe 6d rend le bouton cliquable partout, mais le cadre dépasse alors le
   bas de l'écran de 61 px sur un 360 × 780 et de 40 px sur un 390 × 844 : la
   tirette qui ouvre les commandes sortait à son tour du champ. Corriger un
   défaut en déplaçant le problème de dix pixels plus bas n'est pas corriger.

   La hauteur nécessaire ne se négocie pas — le résultat mesure ce qu'il
   mesure. Ce qui se négocie, c'est ce qu'on met AUTOUR : l'estompage tombe à
   45 px (il reste lisible comme un fondu, pas comme une coupure) et l'en-tête
   se resserre encore, ce qui fait remonter le cadre d'autant. Deux économies
   modestes qui, ensemble, ramènent le cadre dans l'écran.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) and (max-height: 880px) {
  .estime-cadre.est-replie .estime-corps {
    mask-image: linear-gradient(to bottom, #000 calc(100% - 45px), transparent);
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 45px), transparent);
  }
  .estime-corps { padding: 9px; gap: 9px; }
  .section-estime .estime-entete { padding-bottom: 0; }
  .estime-entete .estime-description,
  .estime-entete > p { font-size: 12.5px; line-height: 1.4; }
  .entete-titre { margin-bottom: 4px; }
}

/* ==========================================================================
   ZONE — PASSE 7 : LES SURFACES QUE LE DOIGT DOIT ATTEINDRE (29 août 2026)

   ⚠️ RÉGRESSION INTRODUITE PAR MA PASSE 4, trouvée en mesurant les zones
   tactiles de toute la page et non en la regardant.

   En passant les onglets en colonne pour les faire tenir à trois de front,
   `.onglet-corps` — le bouton qui SÉLECTIONNE le module — a cessé de remplir
   la hauteur de son onglet : mesuré, **18 px de haut** pour une languette qui
   en fait 55. Le reste de la surface ne réagissait plus.

   Karl avait demandé explicitement, en travaillant l'interface : « l'entière
   surface de l'onglet doit être cliquable ». C'était vrai sur grand écran et
   ma réorganisation l'avait défait sur téléphone — précisément là où viser est
   le plus difficile.

   Les repères tactiles usuels sont 44 px (Apple) et 48 px (Android). En
   dessous, on vise, on manque, et on croit que le site ne répond pas.

   Ce qui a été vérifié et ne demande rien : les languettes de la pile
   (`a.bloc-onglet`, « Your Portal », « Your Network ») font déjà 44 px, et les
   liens du pied de page mesurent 63 à 70 px de zone sensible grâce à leur
   parent. Le seul SVG qui sort de l'écran est le filigrane du bloc final —
   décoratif, `pointer-events: none`, parent en `overflow: hidden`.
   ========================================================================== */

@media (max-width: 620px) {
  /* Le bouton de sélection remplit toute la hauteur libre de sa languette :
     tout ce qui n'est pas la bascule sélectionne le module. */
  .onglet-corps {
    flex: 1 1 auto;
    align-items: center;
    min-height: 34px;
  }
  /* La bascule garde sa propre zone, assez grande pour être visée sans
     toucher le corps par erreur. */
  .onglet-bascule { min-height: 26px; display: flex; align-items: center; }

  /* « See everything / Show less » ouvre la liste complète des équipements :
     24 px de haut, c'est un lien de bas de page, pas une commande d'outil. */
  .parties-plus {
    min-height: 44px;
    padding-block: 10px;
    align-items: center;
  }
}

/* ==========================================================================
   ZONE — PASSE 8 : LE DÉFILEMENT MORT AU CALCULATEUR (30 août 2026)

   ⚠️ DÉFAUT BLOQUANT, ET C'EST MOI QUI L'AI INTRODUIT.

   Karl : « une bande jaune qui apparaît au bas de l'écran et qui reste là tout
   le long. Elle ne s'en va plus après. » Elle ne s'en allait pas parce que
   **la page ne bougeait plus du tout**.

   Mesuré à la molette, comme le journal l'exige : le défilement se fige à
   y = 4080 alors qu'il reste **2787 px de document**. Cent vingt mille pixels
   de molette n'ont pas déplacé la page d'un pixel. Tout ce qui suit le
   calculateur — la démonstration, le compteur, le bloc final, le pied de page —
   était inatteignable sur un téléphone.

   LA CAUSE : `.estime-cadre` portait, sur téléphone,

       max-height: calc(…);      ← retirée par ma passe 5
       overflow-y: auto;         ← restée
       overscroll-behavior: contain;  ← restée

   Ces trois lignes formaient un ensemble cohérent quand le cadre était borné
   en hauteur : il défilait à l'intérieur, et `contain` empêchait le geste
   d'entraîner la page une fois arrivé au bout. En supprimant la borne, la
   passe 5 a laissé les deux autres orphelines — et `overscroll-behavior:
   contain` fait exactement ce que son nom dit : il **empêche le défilement de
   remonter à la page**. Un conteneur qui ne peut plus défiler (contenu 588 px
   pour 588 px de hauteur) mais qui refuse de passer la main devient un trou
   noir : le geste entre et rien n'en sort.

   La preuve par l'écart : molette au centre (195, 500), au-dessus du cadre —
   la page reste figée ; molette au bord gauche (6, 500), hors du cadre — elle
   repart aussitôt. Sur un téléphone, le doigt est toujours au centre.

   ⚠️ FAUSSE PISTE À NE PAS REFAIRE : j'ai d'abord accusé `.estime-corps`, qui
   porte `overflow: hidden` avec 1526 px de contenu pour 490 px de hauteur, en
   me fiant à un commentaire du fichier affirmant que « `overflow: hidden` est
   ici SANS DANGER — la molette continue d'aller à la page ». Le passage en
   `overflow: clip` a bien été appliqué (vérifié en style calculé) et n'a rien
   débloqué. Le coupable était le voisin.

   La correction rend le cadre transparent au geste : il ne défile plus en
   interne — c'est le choix assumé du calculateur, « rien ne défile à
   l'intérieur » — donc il n'a aucune raison de retenir le défilement.
   ========================================================================== */

@media (max-width: 620px) {
  .pile .section-estime .estime-cadre,
  .estime-cadre {
    /* Plus de boîte de défilement : le contenu déborde vers la page, qui a
       toujours été celle qui défile. */
    overflow: visible;
    /* ⚠️ LA LIGNE QUI COMPTE. Sans elle, `contain` continue de couper le
       chaînage même quand `overflow` est `visible` dans certaines
       combinaisons — on la remet explicitement à sa valeur par défaut. */
    overscroll-behavior: auto;
  }
}

/* ==========================================================================
   ZONE — PASSE 9 : LES TROIS AUTRES POINTS DU 30 AOÛT 2026
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. LES IMAGES DES BLOCS DE COULEUR ENTRENT DANS L'ÉCRAN ---------
     Karl : « dans les onglets de couleur, il n'y a pas les images. J'aimerais
     qu'on intègre les images — je crois qu'il y a de la place. »

     Les images étaient bien là et bien chargées : elles tombaient simplement
     SOUS LE PLI. Mesuré, part visible de l'image à l'ouverture du bloc :
     window 83 % (coupée de 31 px), network 68 % (76 px), et **portal 27 %,
     coupée de 264 px**. Karl ne voyait qu'un bandeau de pixels.

     Il a raison sur la place : le texte au-dessus en prend beaucoup pour ce
     qu'il dit. La liste de puces occupait 194 px pour six lignes très courtes
     — « Opening hours », « Service area » — qui tiennent à deux par rangée, et
     les boutons de métier 90 px sur deux lignes.

     Rien n'est retiré : les six puces sont toutes là, les trois métiers aussi.
     Le texte est simplement disposé pour ce qu'il est. */
  .bloc-liste {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 10px;
    row-gap: 2px;
  }
  .bloc-liste li { font-size: 13.5px; }
  /* Les boutons de métier sur une seule ligne : trois mots courts. Ils
     gardent 34 px de haut, au-dessus du seuil de ce que le doigt vise bien. */
  /* ⚠️ `nowrap` A ÉTÉ UNE ERREUR, corrigée après l'avoir vue à l'écran : les
     trois métiers du bloc Portal — Mover, Creamery, **Landscaper** — ne tiennent
     pas sur 390 px, et le troisième sortait de l'écran par la droite. Le mot le
     plus long décide, et c'est toujours le dernier bouton qu'on oublie de
     regarder.

     Le retour à la ligne reste donc autorisé ; ce sont les boutons qui
     rapetissent, ce qui suffit à les faire tenir sur une seule ligne dans la
     plupart des cas et garantit qu'aucun ne déborde dans les autres. */
  .masc-choix { gap: 4px; flex-wrap: wrap; }
  /* Serré pour que les trois métiers tiennent sur UNE ligne : à deux lignes,
     Portal reprend la hauteur qu'on venait de rendre à son image et retombe à
     82 % de visible. Le mot qui décide est « Landscaper ». */
  .masc-choix > * { padding: 5px 8px; font-size: 11px; gap: 4px; }
  .masc-choix svg, .masc-choix img { width: 12px; height: 12px; }
  .bloc-chapo { font-size: 13.5px; line-height: 1.42; }
  .bloc .enveloppe > .apparait > h2 { font-size: 26px; line-height: 1.14; }

  /* ⚠️ ET SURTOUT LE PORTAL. Les gains ci-dessus rendent une centaine de
     pixels, ce qui suffit pour window et network mais pas pour portal, dont
     l'image fait 364 px de haut à elle seule — presque un carré. On la borne
     en HAUTEUR, en laissant la largeur suivre : l'image reste entière et
     proportionnée, simplement plus petite. La recadrer aurait coupé le
     personnage, qui est tout le sujet de l'image. */
  /* 42svh a été mesuré insuffisant : l'image de Portal descendait encore
     65 px sous le bord. 33svh la fait entrer entièrement, et c'est la
     valeur la plus haute qui y parvienne — mesurée, pas estimée. */
  .bloc-portal .masc-pile img { max-height: 33svh; width: auto; margin-inline: auto; }
  .bloc-portal .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }
  .bloc-network .bloc-media img { max-height: 30svh; }

  /* --- 3. LES COMMANDES D'ABORD, LE PRIX ENSUITE ----------------------
     Karl : « une fois qu'on déroule le calculateur, le prix est en haut et les
     curseurs et les boutons sont en dessous. Ça devrait être inversé. »

     Il a raison, et c'est l'ordre du grand écran : là, les commandes sont dans
     la colonne de gauche et le résultat à droite — on agit, puis on lit. Sur
     téléphone, `order: -1` remontait le résultat en tête et inversait ce
     rapport : on lisait un prix avant de savoir d'où il venait.

     ⚠️ SEULEMENT À L'ÉTAT DÉROULÉ. Replié, le cadre ne montre qu'un aperçu, et
     cet aperçu N'A DE SENS QUE S'IL MONTRE LE PRIX : c'est ce que le visiteur
     est venu chercher, et c'est ce qui lui donne envie d'ouvrir. Y mettre les
     commandes tronquées ne dirait rien. L'ordre reste donc inversé au repos et
     ne se remet à l'endroit qu'une fois l'outil ouvert. */
  .estime-cadre:not(.est-replie) .estime-resultat { order: 0; }

  /* Le prix reste sous les yeux pendant qu'on manipule. Sans cela, le
     déplacer en bas d'une colonne de commandes longue de 1100 px reviendrait à
     le faire disparaître — or la promesse du bloc est « tick what your place
     has and watch the number settle » : le chiffre doit bouger À VUE.
     `sticky` ne fait pas défiler l'intérieur du cadre, il ne fait que retenir
     le bloc à l'écran, ce qui reste compatible avec la règle « rien ne défile
     à l'intérieur ». */
  .estime-cadre:not(.est-replie) .estime-resultat {
    position: sticky;
    bottom: 0;
    z-index: 3;
    /* Un bloc collant à fond opaque recouvre ce qui passe dessous. Sans
       marque, la superposition se lit comme un défaut d'affichage ; avec une
       ombre portée vers le HAUT, elle se lit comme ce qu'elle est — un
       bandeau qui flotte au-dessus du contenu qui défile. */
    box-shadow: 0 -14px 24px -8px rgba(0, 0, 0, .55);
  }

  /* --- 4. LA FENÊTRE DU TUTORIEL, PLUS DISCRÈTE -----------------------
     Karl : « les fenêtres du tutoriel sont trop grosses, on pourrait mettre ça
     plus petit, pas besoin que ce soit aussi gros. »

     Mesuré : 366 × 165, soit 18 % de l'écran, en corps 16 à 17 px — la taille
     d'un paragraphe de lecture pour une bulle d'aide de deux phrases. Elle
     couvrait le calculateur qu'elle est censée montrer. */
  /* ⚠️ RÉDUIRE LES POLICES NE SUFFISAIT PAS : la fenêtre est ancrée par
     `left: 12px; right: 12px`, donc sa LARGEUR ne bougeait pas — elle est
     passée de 18 % à 17 % de l'écran, ce que Karl n'aurait pas vu. C'est la
     largeur qu'il faut borner, et la fenêtre se centre alors d'elle-même.

     Une bulle d'aide de deux phrases n'a pas besoin de toute la largeur : plus
     étroite, elle laisse voir le calculateur qu'elle est censée désigner. */
  .visite {
    padding: 9px 11px;
    max-height: 30svh;
    max-width: 300px;
    margin-inline: auto;
  }
  .visite-titre, .visite h3 { font-size: 13.5px; margin-bottom: 3px; }
  .visite p, .visite-texte { font-size: 12.5px; line-height: 1.38; }
  .visite-rang, .visite-compteur { font-size: 10px; }
  /* Les boutons rapetissent avec le reste, mais pas sous le seuil du doigt :
     ils gardent 38 px de haut, contre 44 avant. */
  .visite-ok { padding: 9px 16px; font-size: 12.5px; }
  .visite-passer { padding: 9px 12px; font-size: 12.5px; }
  .visite-pied { gap: 8px; margin-top: 8px; }
}

/* --------------------------------------------------------------------------
   PASSE 9b — LES IMAGES SUR LES TÉLÉPHONES COURTS (30 août 2026)

   Compacter les boutons de métier n'a pas suffi : « Landscaper » et « Snack
   bar » sont trop longs pour tenir à trois sur une ligne de 360 px, quelle que
   soit la taille de police raisonnable. Les faire passer sur deux lignes reste
   la seule sortie, et cette deuxième ligne reprend la hauteur qu'on venait de
   rendre aux images.

   Ce qui se règle, alors, c'est la taille des images elles-mêmes. Mesuré sur
   360 × 780 avant cette règle : portal 65 % visible, network 74 %. Les bornes
   ci-dessous sont ajustées sur ce format, le plus contraint des trois.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) and (max-height: 880px) {
  /* ⚠️ `max-width: 100%` N'EST PAS FACULTATIF, et le journal le disait déjà :
     « `width: auto` rend à l'image sa largeur NATURELLE dès que la borne de
     hauteur ne mord pas — elle sortait alors de sa colonne et passait par-
     dessus le titre. » Je l'ai omis et le bloc Window a débordé de 7 px à
     droite, entraînant avec lui son sur-titre, son h2 et son chapô. */
  .bloc-portal .masc-pile img { max-height: 22svh; max-width: 100%; }
  .bloc-network .bloc-media img { max-height: 21svh; max-width: 100%; }
  .bloc-window .masc-pile img { max-height: 24svh; width: auto; max-width: 100%; margin-inline: auto; }
  .bloc-window .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }
}

/* ==========================================================================
   ZONE — PASSE 10 : LE PRIX EN BAS, COLLÉ AU RESTE (30 août 2026)

   Karl, sur son iPhone : « il y a comme un glissement qui se fait entre
   l'espace du prix et le reste du calculateur. J'aimerais que ce soit collé
   ensemble et qu'il n'y ait pas cette espèce de double scroll. […] Lorsqu'on
   est en mode aperçu, on voit encore le prix en haut. Je ne veux pas que le
   prix soit la première chose qu'on voit. Mets-le en bas et colle-le au reste
   du calculateur. »

   Deux corrections de MES choix, et il a raison sur les deux.
   ========================================================================== */

@media (max-width: 620px) {

  /* --- 1. PLUS DE BLOC COLLANT ----------------------------------------
     ⚠️ LE « DOUBLE SCROLL » VENAIT DE MOI. J'avais rendu le bloc de résultat
     `position: sticky; bottom: 0` pour qu'il reste sous les yeux pendant qu'on
     manipule les commandes — la promesse « watch the number settle ».

     L'intention était juste, l'effet ne l'est pas : un bloc collant se
     désolidarise visuellement de ce qui défile derrière lui, et c'est
     exactement le glissement que Karl décrit. Sur un téléphone, où toute la
     surface est sous le pouce, deux plans qui bougent à des vitesses
     différentes se lisent comme deux zones de défilement imbriquées.

     Le calculateur a une règle, écrite plus haut dans ce fichier : « le tout
     est supposé rester verrouillé ensemble ». Le sticky la contredisait. */
  .estime-cadre .estime-resultat,
  .estime-cadre:not(.est-replie) .estime-resultat {
    position: static;
    bottom: auto;
    box-shadow: none;
  }

  /* --- 2. LE PRIX EN BAS DANS LES DEUX ÉTATS --------------------------
     ⚠️ J'AVAIS GARDÉ L'INVERSION AU REPLIÉ, ET C'ÉTAIT UNE ERREUR DE
     RAISONNEMENT. J'avais écrit que l'aperçu « n'a de sens que s'il montre le
     prix, c'est ce que le visiteur est venu chercher ». Karl tranche
     autrement, et c'est son site : il ne veut pas que le prix soit la première
     chose qu'on voit.

     Sa raison se tient mieux que la mienne : un prix affiché avant qu'on ait
     rien dit de son commerce est un chiffre qui ne veut rien dire, et qui peut
     faire fuir. Les commandes d'abord, le résultat au bout — c'est l'ordre du
     grand écran, et désormais celui du téléphone dans les deux états. */
  .estime-resultat,
  .estime-cadre .estime-resultat,
  .estime-cadre:not(.est-replie) .estime-resultat { order: 0; }
}

/* --------------------------------------------------------------------------
   PASSE 10b — RENDRE AUX IMAGES LEUR TAILLE (30 août 2026)

   Karl : « dans l'onglet bleu et violet, il n'y a toujours pas les images
   qu'on a dans la version desktop. »

   Elles y étaient — mais rétrécies au point de ne plus compter. Mesuré :
   l'image de Portal faisait **175 px de large sur un écran de 390**, contre 460
   sur grand écran. En bornant leur hauteur (passes 9 et 9b) pour les faire
   tenir entières dans l'écran, je les avais réduites à des vignettes. Une
   image qu'on ne distingue plus n'est pas une image présente : Karl avait
   raison de dire qu'elles n'y sont pas.

   ⚠️ CE QUI MANQUAIT N'ÉTAIT PAS DE LA PLACE, MAIS DU VIDE MAL PLACÉ. Mesuré
   sur le bloc Portal : 101 px de vide entre les boutons de métier et l'image,
   et 76 px sous l'image — près de 180 px perdus, exactement ce qu'il fallait
   pour la montrer en pleine largeur. Ils viennent de `gap: clamp(32px, 5vw,
   68px)` sur la grille du bloc, pensé pour séparer DEUX COLONNES sur grand
   écran, et devenu un espacement vertical quand la grille passe sur une seule
   colonne ; et de `align-items: center`, qui centre l'image dans une cellule
   plus haute qu'elle.

   Les images retrouvent donc la largeur du bloc, et c'est le vide qui cède.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  /* L'écart entre le texte et l'image n'a plus à valoir celui de deux colonnes
     côte à côte : elles sont l'une sous l'autre. */
  .bloc-grille { gap: 14px; }
  .bloc-portal .bloc-media { padding-top: 0; }
  .bloc-media { align-items: flex-start; }

  /* ⚠️ ON ANNULE LES BORNES DES PASSES 9 ET 9b. Elles répondaient à la bonne
     question — faire entrer l'image dans l'écran — par le mauvais moyen. La
     largeur du bloc est la bonne mesure : l'image occupe ce qui lui revient,
     et sa hauteur suit ses proportions. */
  .bloc-window .masc-pile img,
  .bloc-portal .masc-pile img,
  .bloc-network .bloc-media img {
    max-height: none;
    width: 100%;
    max-width: 100%;
    height: auto;
  }
  .bloc-window .masc-pile,
  .bloc-portal .masc-pile { width: 100%; max-width: 100%; }
}

/* Sur les téléphones courts, l'image reprend une borne — mais une borne LARGE,
   qui la garde lisible au lieu de la réduire à une vignette. */
@media (max-width: 620px) and (max-height: 800px) {
  .bloc-window .masc-pile img,
  .bloc-portal .masc-pile img,
  .bloc-network .bloc-media img { max-height: 40svh; width: auto; margin-inline: auto; }
}

/* --------------------------------------------------------------------------
   PASSE 10c — LE TEXTE CÈDE ENCORE, POUR QUE L'IMAGE SE VOIE (30 août 2026)

   L'image de Portal est rendue à sa pleine largeur (342 px au lieu de 175),
   mais on n'en voyait que le tiers haut : le personnage était coupé au torse.
   Le bloc suivant arrive à hauteur fixe — c'est le mécanisme de la pile, on n'y
   touche pas — donc la seule marge de manœuvre est ce qui précède l'image.

   Rien n'est retiré : le titre, le chapô, les six puces et les trois métiers
   restent tous là. Ils se resserrent, dans l'ordre de ce qui coûte le plus de
   hauteur pour ce qu'il apporte.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  .bloc .enveloppe > .apparait > h2 { font-size: 23px; line-height: 1.1; margin-bottom: 6px; }
  .bloc-chapo { font-size: 12.5px; line-height: 1.38; margin-bottom: 10px; }
  .bloc-numero { font-size: 10px; margin-bottom: 4px; }
  .bloc-liste { row-gap: 0; margin-bottom: 10px; }
  .bloc-liste li { font-size: 12.5px; line-height: 1.35; }
  .masc-choix { margin-bottom: 8px; }
  /* Le lien d'action garde sa taille de doigt, mais pas sa marge de grand
     écran : il est juste au-dessus de l'image, pas au bout d'une colonne. */
  .bloc-lien { margin-block: 6px; padding: 9px 14px; font-size: 13px; }
  /* ⚠️ NE PAS MASQUER `.bloc-mot`. Je l'avais fait en croyant gagner de la
     hauteur — il est en `position: absolute` et ne prend AUCUNE place dans le
     flux. Le masquer ne rendait pas un pixel et supprimait un élément de
     design que Karl n'a pas demandé de retirer : le mot en filigrane derrière
     le titre, qui donne sa profondeur au bloc. Vérifier la position d'un
     élément avant de le supprimer pour gagner de la place. */
}

/* ==========================================================================
   ZONE — PASSE 11 : LA PHOTO EN HAUT DANS LES BLOCS DE COULEUR (30 août 2026)

   Karl, sur son iPhone : « le premier onglet bleu, on voit juste vingt pour
   cent de la photo et l'onglet violet commence à se rouler par-dessus. Je crois
   qu'il faudrait mettre la photo en haut et l'information en dessous. Les
   boutons garage / hôtel / snack bar en dessous de la photo. »

   ⚠️ MA SONDE ME MENTAIT, ET C'EST POURQUOI JE CROYAIS LE PROBLÈME RÉGLÉ. Elle
   mesurait la part de la photo présente DANS L'ÉCRAN, sans compter que le bloc
   suivant monte par-dessus. Mesuré correctement sur le bloc Portal : la photo
   occupe 512→876, mais le bloc jaune commence à 635 — **34 % de visible, pas
   les 91 % que j'annonçais**. Toute vérification de visibilité dans cette pile
   doit tenir compte du recouvrement, pas seulement du bord de l'écran.

   La photo était en dernier : dernière à apparaître en descendant, première à
   être recouverte. La mettre en tête inverse exactement cela — elle est vue
   pleinement, et c'est le texte, qu'on peut lire plus vite, qui cède au bloc
   suivant.

   Les boutons de métier la suivent immédiatement : ce sont eux qui CHANGENT la
   photo. Les laisser au bas d'une colonne de texte, loin de ce qu'ils
   commandent, rendait leur effet invisible.
   ========================================================================== */

@media (max-width: 620px) {
  /* ⚠️ `display: contents` fait disparaître la BOÎTE du conteneur, pas son
     contenu : ses enfants deviennent directement des éléments de la grille du
     bloc, et peuvent donc être ordonnés un par un. Sans cela, les boutons
     resteraient prisonniers du bloc de texte, sous le chapô et la liste.

     Le sélecteur exclut `.bloc-media`, qui porte la même classe `apparait` et
     doit garder sa boîte. */
  .bloc-grille > .apparait:not(.bloc-media) {
    display: contents;
  }

  /* ⚠️ CONSÉQUENCE ASSUMÉE : `.apparait` pose `opacity: 0` jusqu'à ce que le
     script ajoute `.vu`. Un élément en `display: contents` n'a plus de boîte,
     donc plus d'opacité propre — le contenu s'affiche d'emblée, sans le
     fondu d'entrée. C'est acceptable ici, et même souhaitable : le fichier note
     plus haut que le calculateur a dû lui aussi être rendu « visible d'emblée »
     parce que l'apparition retardée produisait « une espèce de flash blanc ».
     On applique le même remède au même mal. */
  .bloc-grille > .apparait:not(.bloc-media) > * { opacity: 1; transform: none; }

  /* L'ordre voulu : la photo, ce qui la commande, puis le texte. */
  .bloc-media   { order: 1; }
  .masc-choix   { order: 2; }
  .bloc-numero  { order: 3; }
  .bloc-grille h2 { order: 4; }
  .bloc-chapo   { order: 5; }
  .bloc-liste   { order: 6; }
  .bloc-lien    { order: 7; }

  /* Les boutons collent à la photo qu'ils changent, et s'en séparent du texte. */
  .masc-choix { margin-top: 10px; margin-bottom: 14px; }
  /* La photo n'a plus de réserve au-dessus : elle ouvre le bloc. */
  .bloc-portal .bloc-media,
  .bloc-media { padding-top: 0; margin-top: 0; }
}

/* --------------------------------------------------------------------------
   PASSE 11b — FAIRE ENTRER TOUT LE BLOC (30 août 2026)

   Karl : « si on peut les rapetisser encore plus pour se donner de l'espace et
   faire tout entrer ». Mesuré après avoir remonté la photo :

     window   fenetre utile 732 px → tout visible
     portal   fenetre utile 496 px → photo 100 %, TOUT LE RESTE À 0 %
     network  fenetre utile 765 px → tout visible sauf le lien du bas

   Portal est le bloc étranglé : le bloc jaune monte plus tôt sur lui, et sa
   photo est la plus haute des trois — presque carrée, 364 px pour 342 de large,
   là où celle de Window n'en fait que 186.

   ⚠️ LE RÉGLAGE NE PEUT PAS ÊTRE LE MÊME POUR LES TROIS. Une borne unique
   sacrifie soit la photo de Portal, soit le texte des deux autres. Chacun reçoit
   donc la sienne, calculée sur la place dont il dispose réellement.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  /* Portal : la photo cède ce qu'il faut pour que son texte existe. À 232 px
     elle reste une vraie image — un tiers de plus que les 175 px auxquels je
     l'avais réduite avant — et le téléphone posé dessus reste lisible. */
  .bloc-portal .masc-pile img { max-height: 232px; width: auto; margin-inline: auto; }
  .bloc-portal .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }

  /* Window a de la place : sa photo garde toute la largeur. */
  .bloc-window .masc-pile img { max-height: none; width: 100%; }

  /* Network en a aussi, mais son lien du bas passait juste dessous. */
  .bloc-network .bloc-media img { max-height: 300px; }

  /* Le texte se resserre pour les trois, sans rien perdre de son contenu. */
  .bloc-grille h2 { font-size: 21px; line-height: 1.1; margin-bottom: 4px; }
  .bloc-chapo { font-size: 12px; line-height: 1.35; margin-bottom: 8px; }
  .bloc-liste { row-gap: 0; margin-bottom: 8px; }
  .bloc-liste li { font-size: 12px; line-height: 1.3; }
  .bloc-numero { font-size: 9.5px; margin-bottom: 3px; }
  .masc-choix { margin-top: 8px; margin-bottom: 10px; }
  .bloc-lien { margin-block: 4px; padding: 8px 13px; font-size: 12.5px; }
  .bloc-grille { gap: 8px; }
}

/* --------------------------------------------------------------------------
   PASSE 11c — DONNER SA COURSE AU BLOC VIOLET (30 août 2026)

   Karl : « si tu as besoin de grossir l'onglet un peu, tu peux le faire. »

   C'est nécessaire pour Portal, et pour lui seul. Mesuré, la fenêtre utile de
   chaque bloc — de la barre du haut jusqu'à l'arrivée du bloc suivant :

     window   732 px
     portal   496 px   ← étranglé
     network  765 px

   Portal dispose de 240 px de moins que ses voisins alors qu'il porte la photo
   la plus haute des trois. Aucune réduction raisonnable ne fait entrer 640 px de
   contenu dans 496 : à force de rogner, la photo redevient la vignette que Karl
   a déjà refusée.

   La course d'un bloc se règle par sa marge basse — c'est elle qui décide du
   moment où le suivant arrive. On l'allonge pour Portal uniquement, ce qui lui
   rend le rythme de ses voisins.

   ⚠️ NE PAS TOUCHER À `height` NI À `--rang` : la hauteur des quatre feuilles
   est solidaire (`top + hauteur = 100svh`), et le journal note que rompre cette
   solidarité désaligne toute la pile. La marge, elle, ne fait qu'espacer.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  /* 190 px laissaient encore le lien du bas coupé à moitié ; 240 px le font
     entrer entièrement. Valeur mesurée sur 390 × 844, vérifiée sur 360 et 430. */
  .bloc-portal { margin-bottom: calc(1 * var(--onglet) + 240px); }
}

/* --------------------------------------------------------------------------
   PASSE 11d — LES TÉLÉPHONES COURTS (30 août 2026)

   Sur 390 × 844 et 430 × 932, les trois blocs entrent entièrement. Sur un
   360 × 780, il ne manque plus que le LIEN DU BAS de Portal et de Network —
   « Open a real portal », « See a customer's side », c'est-à-dire l'appel à
   l'action de chaque bloc, la partie qu'on peut le moins se permettre de
   perdre.

   Il manque une soixantaine de pixels. Les photos les rendent : elles restent
   nettement plus grandes que les vignettes d'avant, et un lien qu'on peut
   toucher vaut mieux que soixante pixels d'image en plus.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) and (max-height: 800px) {
  .bloc-portal .masc-pile img { max-height: 176px; }
  .bloc-network .bloc-media img { max-height: 238px; }
  .bloc-window .masc-pile img { max-height: 160px; width: auto; margin-inline: auto; }
  .bloc-window .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }
}

/* --------------------------------------------------------------------------
   PASSE 11e — LE TÉLÉPHONE SUIT LA PHOTO (30 août 2026)

   Le téléphone du bloc Portal se pose par-dessus la photo : on voit le
   commerçant et l'écran qu'il manipule dans une seule scène, et c'est le sens
   même du bloc. Sa taille est fixée en pixels (`max-width: 156px`), pensée pour
   une photo de 460 px de large.

   Sur téléphone, la photo est descendue à 218 px pour laisser entrer le texte —
   mais le téléphone, lui, ne bougeait pas : il occupait 54 % de la scène au lieu
   de 34 %, et masquait le personnage qu'il est censé accompagner.

   ⚠️ QUAND ON RÉDUIT UNE IMAGE, CE QUI SE POSE DESSUS DOIT SUIVRE. Une taille
   en pixels est une taille absolue : elle ne sait rien de ce qu'elle recouvre.
   Ici le téléphone est exprimé en pourcentage de la photo, donc la proportion
   tient quelle que soit la taille retenue.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) {
  .bloc-portal .zone-metiers .telephone {
    max-width: 36%;
    right: -14px;
    bottom: -16px;
  }
}

/* ==========================================================================
   ZONE — PASSE 12 : AUCUNE BANDE SOUS LES BLOCS (30 août 2026)

   Karl, sur iPhone : « la barre apparaît d'abord comme une barre BLANCHE quand
   je commence à dérouler l'onglet bleu. J'ai fini de dérouler l'onglet bleu, la
   barre est toujours blanche, et c'est quand je commence à dérouler l'onglet
   jaune qu'elle devient JAUNE. Et elle reste là indéfiniment. »

   ⚠️ CE QU'IL DÉCRIT N'EST PAS UNE BARRE, C'EST UN TROU. Une bande qui prend
   successivement la couleur du fond de page (beige, qu'on lit comme blanc) puis
   celle du bloc jaune ne peut être qu'une zone où l'on voit CE QUI EST DERRIÈRE
   la feuille du dessus. Autrement dit : le bloc collé ne descend pas jusqu'au
   bas de l'écran.

   LA CAUSE, propre à Safari iOS. Les blocs mesurent `100svh − rang × --onglet`.
   `svh` est la hauteur de l'écran BARRE D'ADRESSE VISIBLE — la plus petite.
   Quand la barre se rétracte au défilement, l'écran grandit de 60 à 90 px, mais
   `svh` ne bouge pas, par définition. Le bloc garde donc sa taille et il manque
   la différence en bas : d'abord le fond de la page, puis le bloc jaune une fois
   qu'il est monté derrière. Sur Chrome de bureau, où rien ne se rétracte,
   `svh == lvh` et le défaut est invisible — c'est pourquoi je ne le
   reproduisais pas.

   ⚠️ ON NE REMPLACE PAS `svh` PAR `lvh` : la hauteur en `svh` est ce qui rend le
   tassement stable, et le journal du calculateur rappelle qu'une hauteur qui
   change pendant le défilement fait sauter la page. On AJOUTE un plancher en
   `lvh`, qui ne joue que dans le cas fautif — écran agrandi — et reste sans
   effet partout ailleurs, `svh` et `lvh` étant égaux dès que rien ne se
   rétracte.
   ========================================================================== */

.bloc {
  /* Le bloc ne peut plus être plus court que le plus grand écran possible. */
  min-height: calc(100lvh - var(--rang) * var(--onglet));
}

/* La section du calculateur suit la même règle : elle est la dernière feuille
   de la pile et laisserait le même trou. */
.pile .section-estime,
.calculateur-course {
  min-height: calc(100lvh - 3 * var(--onglet));
}

/* ==========================================================================
   ZONE — PASSE 13 : LE BLOC BLEU, RESSERRÉ (30 août 2026)

   Karl : « pour le bloc bleu, on a un problème avec l'espace pour le texte,
   parce qu'on voit juste le début du texte. Si on peut pousser l'image un peu
   plus en haut, faire un peu plus de place, rapetisser un peu le texte. »

   ⚠️ MES MESURES DISAIENT « TOUT EST VISIBLE » — SUR 844 PX DE HAUT. Karl est
   sur un iPhone dont l'écran fait bien 844 px, mais Safari en réserve environ
   90 pour sa barre d'adresse : il lui en reste près de 750. Toutes mes
   vérifications se faisaient donc sur un écran plus grand que le sien, et
   déclaraient bon ce qu'il voyait coupé.

   Deux gisements mesurés dans le bloc bleu, avant de toucher au texte :
   **111 px de vide au-dessus de la photo** et **130 px sous le dernier
   paragraphe**. C'est de l'espace, pas du contenu — on le prend d'abord.
   ========================================================================== */

@media (max-width: 620px) {
  /* --- 1. LA PHOTO REMONTE ---------------------------------------------
     Le rembourrage du bloc était pensé pour un grand écran, où l'air fait
     partie du propos. Sur téléphone, il repousse la photo de 111 px sous la
     languette, et ces 111 px sont pris au texte. */
  .bloc .enveloppe { padding-top: 10px; padding-bottom: 10px; }
  .bloc-grille { padding-top: 0; }
  .bloc-media { margin-top: 0; padding-top: 0; }

  /* --- 2. LE TEXTE SE RESSERRE ---------------------------------------
     Rien n'est retiré : ni une puce, ni une ligne du chapô. Ce sont les
     interlignes et les corps qui cèdent, dans l'ordre de ce qui coûte le plus
     de hauteur pour ce qu'il apporte. */
  /* ⚠️ SPÉCIFICITÉ : une règle de ma passe 10c cible
     `.bloc .enveloppe > .apparait > h2` (0,3,1) et gagnait contre
     `.bloc-grille h2` (0,1,1) — mesuré, le titre restait à 23 px alors que
     j'avais posé 19,5. On reprend donc le même chemin, en plus long. */
  .bloc .enveloppe > .apparait > h2,
  .bloc-grille h2 { font-size: 19.5px; line-height: 1.08; margin: 0 0 4px; }
  .bloc-chapo { font-size: 11.5px; line-height: 1.32; margin: 0 0 7px; }
  .bloc-numero { font-size: 9px; letter-spacing: .12em; margin: 0 0 2px; }
  .bloc-liste { row-gap: 0; column-gap: 8px; margin: 0 0 7px; }
  .bloc-liste li { font-size: 11.5px; line-height: 1.28; }
  .masc-choix { margin: 7px 0 8px; }
  .bloc-lien { margin: 2px 0 0; padding: 7px 12px; font-size: 12px; }
  .bloc-grille { gap: 6px; }
}

/* --------------------------------------------------------------------------
   PASSE 14 — L'IPHONE DE KARL : 375 × 667 (30 août 2026)

   ⚠️ JE TESTAIS SUR LE MAUVAIS ÉCRAN DEPUIS LE DÉBUT. L'enregistrement qu'il a
   envoyé fait 750 × 1334 pixels physiques, soit **375 × 667 points** : un
   iPhone SE ou 8, pas un iPhone 12 et plus. Avec la barre de Safari, il ne lui
   reste qu'environ 570 px de haut — contre les 732 sur lesquels je déclarais
   « tout est visible ».

   À cette taille, tout entre sauf le LIEN DU BAS du bloc violet, celui qui
   ouvre le portail de démonstration. La photo lui rend les quelques pixels
   nécessaires : elle reste à 150 px de haut, ce qui la garde lisible.
   -------------------------------------------------------------------------- */

@media (max-width: 620px) and (max-height: 700px) {
  .bloc-portal .masc-pile img { max-height: 150px; }
  .bloc-network .bloc-media img { max-height: 210px; }
  .bloc-window .masc-pile img { max-height: 140px; width: auto; margin-inline: auto; }
  .bloc-window .masc-pile { width: auto; max-width: 100%; margin-inline: auto; }
  /* Le bloc violet reçoit un peu plus de course : ses trois métiers tiennent
     sur deux lignes à cette largeur, ce que ses voisins n'ont pas à porter, et
     son lien du bas restait coupé à 38 %. */
  .bloc-portal { margin-bottom: calc(1 * var(--onglet) + 300px); }
  /* Le texte se resserre d'un dernier cran sur ces écrans courts. */
  .bloc .enveloppe > .apparait > h2,
  .bloc-grille h2 { font-size: 18px; }
  .bloc-chapo { font-size: 11px; }
  .bloc-liste li { font-size: 11px; }
}
