/*
 * Beluga Build — vitrine. Sobre, en grilles. Deux ambiances : noir (sombre) et sable (clair).
 *
 * UNE TRAME QU'ON DÉBORDE EXPRÈS. Le texte tient dans « .enveloppe » (1200 px, deux traits
 * verticaux), et chaque section est une GRILLE dont le titre est une cellule à part entière ; mais
 * la démonstration, la bande des personnages et le personnage d'accueil SORTENT de cette colonne.
 * Rien n'est coupé par l'enveloppe : c'est la page qui coupe, avec « overflow-x: clip ».
 *
 * RIEN DE COLORÉ NE DÉBORDE DANS LES MARGES. Les traits de fin de section s'arrêtent aux bords de
 * la colonne, et le seul dégradé teinté qu'on voit hors des cellules est celui posé derrière la
 * démonstration, à GAUCHE. Aucune nappe ne sort sur les côtés ni ne descend du haut de la page.
 *
 * LES TRAITS QUI NE REJOIGNENT RIEN SE TERMINENT EN DÉGRADÉ. Les traits de grille séparent deux
 * cellules et vont donc d'un bord à l'autre ; ceux qui sont décoratifs (sous une étiquette, le
 * long du rail des états, en fin de section) s'éteignent au lieu de s'arrêter net.
 *
 * AUCUN BLOC N'ENTRE EN GLISSANT. Ce qui n'est pas encore lu est un SQUELETTE — des barres grises
 * qui se remplissent quand le bloc entre dans l'écran. Voir « .apparait », plus bas.
 */

:root {
  color-scheme: dark;
  --bg: #0a0a0a;
  --surface: #101010;
  --surface-2: #151515;
  --line: #232323;
  --line-soft: #191919;
  --text: #ededed;
  --muted: #8d8d8d;
  --accent: #5ad1c8;
  --accent-ink: #04211f;
  --chrome: #161616;
  --ombre: rgba(0, 0, 0, 0.55);
  --lueur: 12%;
  --radius: 2px;
  --col: 1200px;
  --marge: 28px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
}

html[data-theme='sable'] {
  color-scheme: light;
  --bg: #efe8db;
  --surface: #f5efe4;
  --surface-2: #eae1d0;
  --line: #d8cbb4;
  --line-soft: #e2d8c5;
  --text: #191510;
  --muted: #6d6355;
  --accent: #0f6f66;
  --accent-ink: #f5efe4;
  --chrome: #e4dac6;
  --ombre: rgba(60, 45, 20, 0.22);
  --lueur: 14%;
}

* { box-sizing: border-box; }

/*
 * « overflow-x: clip » et NON « hidden » : « hidden » ferait de la page un conteneur de
 * défilement, et « position: sticky » cesserait de tenir (la barre du haut, la démonstration
 * collée des fonctionnalités). « clip » coupe ce qui dépasse sans rien casser.
 */
/*
 * UN LIEN DE SECTION S'ARRÊTE SOUS LA BARRE, PAS DERRIÈRE. La barre du haut est collée : sans
 * cette réserve, « Installer » amenait le titre de la section EXACTEMENT sous la barre, qui le
 * recouvrait. La réserve vaut la hauteur de la barre plus une respiration.
 */
html { scroll-behavior: smooth; overflow-x: clip; scroll-padding-top: 78px; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ---------- La trame ---------- */

/*
 * LA COLONNE NE RETIENT PLUS RIEN. L'enveloppe garde sa largeur et ses deux traits, mais elle ne
 * COUPE plus : la démonstration, la bande des personnages et le personnage d'accueil sortent
 * volontairement de la colonne. C'est « html { overflow-x: clip } » qui empêche la page de
 * s'élargir, pas l'enveloppe.
 */
.enveloppe {
  max-width: var(--col);
  margin: 0 auto;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  overflow: visible;
}

/*
 * LA FIN D'UNE SECTION EST UN TRAIT, RIEN DE PLUS. Le trait reste DANS la colonne — il s'arrête
 * aux deux traits verticaux de l'enveloppe — et s'éteint avant ses extrémités au lieu de buter
 * dessus. Plus de halo teinté débordant dans les marges : les nappes qui sortaient de part et
 * d'autre de la page ont été retirées, le seul dégradé coloré de la page d'accueil est celui
 * posé derrière la démonstration.
 *
 * ET IL NE SE RÉPÈTE PAS À L'IDENTIQUE : une section sur deux penche son point dense d'un côté,
 * une sur trois le teinte de l'accent. Quatre sections d'affilée ne montrent jamais le même trait.
 */
.section { position: relative; }

.section::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--line) 40%, transparent) 22%,
    var(--line) 50%,
    color-mix(in srgb, var(--line) 40%, transparent) 78%,
    transparent 100%
  );
}

/* Le point dense se déplace : à gauche pour l'une, à droite pour la suivante. */
.section:nth-of-type(even)::after {
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--line) 35%, transparent) 14%,
    var(--line) 36%,
    color-mix(in srgb, var(--line) 50%, transparent) 70%,
    transparent 96%
  );
}

/* Une section sur trois teinte son trait de l'accent, sans jamais le durcir. */
.section:nth-of-type(3n)::after {
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--line) 30%, transparent) 18%,
    color-mix(in srgb, var(--accent) 45%, var(--line)) 58%,
    color-mix(in srgb, var(--line) 35%, transparent) 84%,
    transparent 100%
  );
}

.grille {
  display: grid;
  gap: 1px;
  background: var(--line-soft);
}

.grille > * { background: var(--bg); min-width: 0; }

/* Un bloc de code plus large que sa cellule pousserait la page entière. */
.grille pre { max-width: 100%; }

.g2 { grid-template-columns: repeat(2, 1fr); }
.g3 { grid-template-columns: repeat(3, 1fr); }
.g4 { grid-template-columns: repeat(4, 1fr); }

.cellule { padding: 28px; }

/*
 * LE TITRE EST UNE CELLULE, PAS UN BLOC POSÉ AU-DESSUS. Avant, une bande vide séparait le titre
 * de la grille, et les traits verticaux la traversaient sans rien y séparer. En faisant du titre
 * une rangée pleine de la même grille, son trait du bas EST le trait du haut des cellules.
 */
.pleine { grid-column: 1 / -1; }

.entete { padding: 62px 28px 34px; }

.entete.mince { padding: 34px 28px 26px; }

/*
 * UN TITRE CENTRÉ POUR CE QUI EST CENTRÉ. La démonstration déborde la colonne des deux côtés :
 * ce qui l'annonce se lit au-dessus d'elle, dans son axe, pas collé au bord gauche de la colonne.
 */
.entete.centree { text-align: center; }

.entete.centree p { margin-left: auto; margin-right: auto; }

.entete.centree .etiquette-soulignee::after { left: -40px; }

.entete p { color: var(--muted); max-width: 62ch; margin: 16px 0 0; }

.etiquette {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-block;
}

/* Un trait qui ne rejoint rien : il s'éteint au lieu de s'arrêter net. */
.etiquette-soulignee { position: relative; padding-bottom: 9px; }

.etiquette-soulignee::after {
  content: '';
  position: absolute;
  left: 0;
  right: -40px;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 25%, transparent) 45%, transparent);
}

.mini-etiquette {
  display: block;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 16px;
}

.bande {
  padding: 20px 28px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
}

.bande-note { color: var(--muted); font-size: 14px; }

/* ---------- En-tête ---------- */

header.barre {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.barre-inner {
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 28px;
  height: 62px;
  display: flex;
  align-items: center;
  gap: 24px;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
}

.marque {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.marque img { width: 26px; height: 26px; border-radius: 6px; display: block; }

nav.liens { display: flex; gap: 22px; margin-left: auto; }

nav.liens a { text-decoration: none; color: var(--muted); font-size: 14px; }

nav.liens a:hover { color: var(--text); }

.actions { display: flex; align-items: center; gap: 10px; }

.bouton {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 14px;
  text-decoration: none;
  color: var(--text);
  background: transparent;
  cursor: pointer;
  font-family: inherit;
}

.bouton:hover { border-color: var(--accent); color: var(--accent); }

.bouton.plein {
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 72%, var(--text)));
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

.bouton.plein:hover { opacity: 0.88; color: var(--accent-ink); }

/* Sur ordinateur, le bouton du dépôt porte son mot ; la marque ne sert que sur téléphone. */
.icone-github { width: 16px; height: 16px; display: none; }

.bascule { width: 38px; height: 38px; display: inline-grid; place-items: center; padding: 0; }

.bascule svg { width: 17px; height: 17px; }

html[data-theme='sable'] .icone-lune { display: none; }
html:not([data-theme='sable']) .icone-soleil { display: none; }

/* ---------- Le choix de la langue ---------- */

.langue {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding-left: 10px;
}

.langue:focus-within { border-color: var(--accent); }

.langue-globe { display: inline-grid; place-items: center; color: var(--muted); }

.langue-globe svg { width: 15px; height: 15px; display: block; }

.langue select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  padding: 0 8px 0 7px;
  height: 100%;
  cursor: pointer;
}

.langue select:focus { outline: none; }

/* Le menu déroulant natif s'ouvre avec les couleurs du système : on le remet dans l'ambiance. */
.langue select option { background: var(--surface); color: var(--text); }

/* ---------- Zone d'accueil ---------- */

/*
 * UNE SEULE COLONNE, SUR L'AXE. Pastille, titre, chapô et boutons sont centrés ; le dégradé chaud
 * descend du haut, au milieu, et non plus du coin gauche. Les quatre temps d'une carte se lisent
 * plus bas, sur le rail des personnages.
 */
.hero {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.hero-texte {
  padding: 96px 28px 84px;
  text-align: center;
  background-image: radial-gradient(72% 130% at 50% -12%, color-mix(in srgb, var(--accent) var(--lueur), transparent), transparent 64%);
}

.hero-texte h1,
.hero-texte .chapo { margin-left: auto; margin-right: auto; }

.hero-texte h1 { max-width: 19ch; }

.hero-texte .chapo { max-width: 64ch; }

.pastille {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 14px;
}

.point {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

h1 {
  font-size: clamp(2.5rem, 5.6vw, 4.2rem);
  line-height: 1.03;
  letter-spacing: -0.035em;
  font-weight: 600;
  margin: 26px 0 0;
  max-width: 15ch;
}

/* Le mot mis en avant : un dégradé, pas une couleur plate. */
h1 em {
  font-style: normal;
  background: linear-gradient(100deg, var(--accent), color-mix(in srgb, var(--accent) 45%, var(--text)));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.chapo { margin: 24px 0 0; max-width: 58ch; font-size: 1.125rem; color: var(--muted); }

.hero-actions { display: flex; justify-content: center; gap: 12px; margin-top: 34px; flex-wrap: wrap; }

/*
 * Les temps d'une carte, portés par les personnages des colonnes — et, dans le coin bas-droit, le
 * personnage « En cours » EN GRAND, qui sort de la colonne. C'est le seul mouvement de la page.
 */
.hero-etats {
  position: relative;
  padding: 40px 28px 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  background-image: radial-gradient(90% 80% at 100% 100%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 62%);
}

.hero-mascotte {
  margin: auto 0 0;
  padding: 0;
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 4px;
  right: -34px;
}

.hero-mascotte img {
  width: clamp(180px, 21vw, 290px);
  height: auto;
  display: block;
  filter: drop-shadow(0 26px 34px var(--ombre));
  order: 2;
}

.hero-mascotte figcaption {
  order: 1;
  padding-bottom: 26px;
  text-align: right;
  flex: none;
}

.hero-mascotte figcaption b { display: block; font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em; }

.hero-mascotte figcaption span { display: block; font-size: 13px; color: var(--muted); line-height: 1.45; max-width: 18ch; }

.etats { list-style: none; margin: 22px 0 0; padding: 0; position: relative; }

/* Le fil qui relie les quatre temps : il naît et meurt en dégradé, il ne bute sur rien. */
.etats::before {
  content: '';
  position: absolute;
  left: 21px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--line) 12%, var(--line) 88%, transparent);
}

.etat { display: flex; align-items: center; gap: 14px; position: relative; }

.etat + .etat { margin-top: 18px; }

.etat img {
  width: 44px;
  height: auto;
  flex: none;
  filter: drop-shadow(0 8px 12px var(--ombre));
  /* Comme sur le rail : un halo qui s'éteint, jamais une pastille ronde. */
  background: radial-gradient(closest-side, var(--bg) 58%, transparent 100%);
}

.etat b { display: block; font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em; }

.etat span { display: block; font-size: 13px; color: var(--muted); line-height: 1.45; }

/* ---------- La démonstration ---------- */

/*
 * LA DÉMONSTRATION DÉBORDE LA COLONNE SANS PRENDRE L'ÉCRAN. Sa scène est plus large que
 * l'enveloppe (1560 px contre 1200), jamais large comme la fenêtre : elle se détache du texte
 * tout en gardant une marge de chaque côté. Elle penche vers l'arrière au premier regard, puis se
 * redresse EN UNE FOIS quand elle entre dans l'écran : app.js pose la classe « redressee », la
 * transition fait le reste. Sans JavaScript, elle reste penchée sans jamais gêner la lecture ;
 * en mouvement réduit, elle est droite d'emblée.
 */
.section-demo { padding-bottom: 8px; }

/*
 * LES DEUX BASCULES SE POSENT DANS L'AXE DE LA DÉMONSTRATION, pas à ses deux bouts : c'est la
 * commande de l'outil qu'on regarde juste dessous, elle se lit d'un seul coup d'œil. Elles ne
 * portent AUCUNE étiquette : « Clair / Sombre » et « Ordinateur / Téléphone » se comprennent
 * seuls, un mot au-dessus n'ajoutait rien.
 */
.demo-reglages {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  flex-wrap: wrap;
  padding: 22px 28px;
  border-bottom: 1px solid var(--line-soft);
}


.segments { display: flex; gap: 10px; flex-wrap: wrap; }

/* Une bascule : deux choix collés, celui qui tient l'état est allumé. */
.segment {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
  background: var(--surface);
}

.segment-choix {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  letter-spacing: -0.01em;
  padding: 6px 15px;
  border-radius: 999px;
  cursor: pointer;
}

.segment-choix:hover { color: var(--text); }

.segment-choix[aria-pressed='true'] {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

/*
 * PLUS LARGE QUE LA COLONNE, PAS LARGE COMME L'ÉCRAN. 1560 px au plus, et jamais plus de 94 % de
 * la fenêtre : la scène sort de l'enveloppe de 180 px de chaque côté et garde une marge sur les
 * bords. Le décalage se calcule depuis le centre du parent, la scène reste donc centrée quelle
 * que soit la largeur.
 *
 * LE SEUL DÉGRADÉ DE LA SECTION EST ICI, ET IL MONTE DU BAS. Une lueur posée à gauche tirait le
 * regard sur le côté, loin de l'écran qu'on vient regarder ; du bas, elle pousse la scène vers le
 * haut, dans le sens de la lecture, et accompagne la mise à plat. Il tient dans la scène — il ne
 * déborde ni sur les côtés de la page ni vers le haut — et la fenêtre étant opaque, on ne le voit
 * que tout autour d'elle : c'est bien une lueur DERRIÈRE la démonstration, montant du bas.
 */
.demo-large {
  position: relative;
  width: min(1560px, 94vw);
  margin-left: calc(50% - min(780px, 47vw));
  padding: 46px 0 88px;
  perspective: 1500px;
  background-image: radial-gradient(86% 70% at 50% 100%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 74%);
}

/*
 * LA SCÈNE ARRIVE EN 3D ET SE MET À PLAT AU FIL DU DÉFILEMENT. On la découvre penchée vers
 * l'arrière, vue de dessus comme une maquette posée sur une table ; à mesure qu'on descend, elle
 * se relève et devient un écran plat, bien en face.
 *
 * DEUX MÉCANIQUES, ET LA PREMIÈRE DISPONIBLE GAGNE :
 *
 *   1. « animation-timeline: view() » — le navigateur lie lui-même l'avancement de l'animation à
 *      la position de la scène dans l'écran. Rien ne passe par JavaScript, rien n'est mesuré à
 *      chaque image : le mouvement est confié au compositeur, exactement comme une transition.
 *      C'est ce qui permet de suivre le défilement SANS la saccade d'autrefois, quand un script
 *      posait la transformation image par image et forçait le recalcul de la page entière.
 *   2. À défaut (navigateur sans ligne de temps de défilement), app.js pose « .redressee » quand
 *      la scène entre dans l'écran et la transition fait le redressement d'un seul mouvement.
 *
 * Les valeurs de départ sont posées ici une fois pour toutes : les deux mécaniques partent du
 * même état penché et finissent sur le même état plat.
 */
.demo-plan {
  position: relative;
  z-index: 1;
  --inclinaison: 22deg;
  --recul: 0.9;
  --montee: 38px;
  opacity: 0.78;
  transform: translateY(var(--montee)) rotateX(var(--inclinaison)) scale(var(--recul));
  transform-origin: 50% 0%;
  transition: transform 1.2s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.9s ease-out;
}

/* « will-change » ne vaut que pendant le mouvement : le garder ensuite retient une image de la
   taille de l'écran en mémoire pour rien. */
.demo-plan:not(.redressee) { will-change: transform; }

/*
 * LE REPLI SUIT LE DÉFILEMENT, LUI AUSSI. Sur un navigateur sans ligne de temps de défilement,
 * app.js pose les trois valeurs image par image d'après la position de la scène : aucune
 * transition ne doit alors les lisser, elle traînerait derrière le doigt.
 */
.demo-plan.suivie { transition: none; }

.demo-plan.redressee { --inclinaison: 0deg; --recul: 1; --montee: 0px; opacity: 1; }

/* Sans JavaScript, aucun voile ne doit rester : la scène est droite et pleinement lisible. */
html.sans-observateur .demo-plan { --inclinaison: 0deg; --recul: 1; --montee: 0px; opacity: 1; }

/*
 * LA MISE À PLAT, CRAN PAR CRAN. L'animation court pendant que la scène traverse le bas de
 * l'écran : penchée quand son haut apparaît, entièrement plate quand elle occupe le milieu. Elle
 * est FIGÉE aux deux bouts (« both ») — au-dessus elle reste penchée, en dessous elle reste
 * plate — et ne repart donc jamais en arrière une fois la démonstration dépassée… sauf en
 * remontant la page, ce qui est bien le mouvement inverse et se lit comme tel.
 *
 * Les variables ne s'animent pas d'un navigateur à l'autre : l'animation écrit donc la
 * transformation ENTIÈRE à chaque étape. Elle prend le pas sur la déclaration de « .demo-plan »
 * comme sur « .redressee », qu'app.js ne pose de toute façon pas quand cette mécanique existe.
 */
@keyframes demo-mise-a-plat {
  from { transform: translateY(38px) rotateX(22deg) scale(0.9); opacity: 0.78; }
  to { transform: translateY(0) rotateX(0deg) scale(1); opacity: 1; }
}

@supports (animation-timeline: view()) {
  /* Le mouvement réduit est refusé ICI, pas plus bas : une animation l'emporte sur une simple
     déclaration, la règle de « mouvement réduit » ne pourrait pas l'annuler après coup. */
  @media (prefers-reduced-motion: no-preference) {
    html:not(.sans-observateur) .demo-plan {
      animation: demo-mise-a-plat linear both;
      animation-timeline: view();
      /*
       * ELLE N'EST PLATE QU'AU MILIEU DE L'ÉCRAN. Le mouvement commence dès que le haut de la
       * scène touche le bas de la fenêtre (« entry 0% ») et s'achève quand son centre rejoint le
       * centre de la fenêtre — « cover 50% », le point exact où l'élément et le scrollport
       * partagent leur milieu. Avec l'ancienne fin (« cover 38% »), la scène était déjà à plat
       * alors qu'elle occupait encore le bas de l'écran : sur un grand écran, on ne voyait plus
       * la perspective du tout.
       */
      animation-range: entry 0% cover 50%;
      transition: none;
      /* Le repli pose « will-change » tant que la scène n'est pas redressée ; ici elle ne l'est
         jamais, et l'indice resterait à vie. Une animation liée au défilement est déjà confiée au
         compositeur : le navigateur n'a pas besoin qu'on le lui dise. */
      will-change: auto;
    }
  }
}

/*
 * En mode téléphone, la fenêtre s'efface et le cadre de poche prend sa place, centré. LA RÈGLE
 * VAUT AUX DEUX ENDROITS où la démonstration se montre : la scène large, et le cadre collé des
 * fonctionnalités. Un seul réglage, deux emplacements, jamais deux états qui se contredisent.
 */
html[data-appareil='mobile'] .demo-plan > .fenetre,
html[data-appareil='mobile'] .fonctions-cadre > .fenetre { display: none; }
html[data-appareil='bureau'] .demo-plan > .telephone,
html[data-appareil='bureau'] .fonctions-cadre > .telephone { display: none; }

/* Dans la scène, le téléphone est la vedette : il y est bien plus grand qu'en colonne. */
.demo-plan > .telephone { --tel-l: 340px; --tel-h: 690px; margin: 0 auto; }

/* La scène ne touche plus les bords de l'écran : le cadre redevient une vraie fenêtre, avec ses
   quatre côtés et son ombre portée. */
.fenetre {
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 30px 60px -30px var(--ombre);
}

.fenetre-barre {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
}

.feux { display: inline-flex; gap: 7px; }

.feux i { width: 10px; height: 10px; border-radius: 50%; background: var(--line); display: block; }

.fenetre-adresse {
  flex: 1;
  text-align: center;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * L'application occupe TOUTE la largeur de la fenêtre de démonstration : au-delà de sa largeur
 * naturelle, app.js lui donne la place plutôt que de la grossir ; en dessous, il la réduit et la
 * hauteur du cadre suit, sinon la page garderait un trou.
 */
.fenetre-ecran {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  display: grid;
  place-items: center;
  min-height: 200px;
  /* Ce que l'application redessine à l'intérieur ne peut plus salir le reste de la page : le
     navigateur n'a pas à remonter jusqu'au corps du document pour repeindre. */
  contain: paint;
}

.fenetre-ecran iframe { border: 0; display: block; transform-origin: top left; }

.demo-attente { color: var(--muted); font-size: 14px; margin: 0; padding: 40px 20px; }

/* ---------- Le cadre de téléphone ---------- */

/*
 * PETIT FORMAT, MAIS UN VRAI TÉLÉPHONE : coque, île, boutons de tranche. L'écran fait sa taille
 * en dur — c'est app.js qui y met l'application à l'échelle.
 */
.telephone {
  --tel-l: 250px;
  --tel-h: 495px;
  --tel-barre: 26px;
  position: relative;
  width: calc(var(--tel-l) + 20px);
  height: calc(var(--tel-h) + 20px);
  padding: 10px;
  border-radius: 42px;
  background: linear-gradient(150deg, #4a4a4f, #17171a 42%, #0d0d0f 62%, #35353a);
  box-shadow: 0 26px 50px -22px var(--ombre), inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  flex: none;
}

.telephone-ecran {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 33px;
  overflow: hidden;
  background: var(--surface-2);
  contain: paint;
}

/*
 * La barre d'état du téléphone : une bande noire en haut de l'écran, sous laquelle l'application
 * commence. Sans elle, l'île de la caméra se poserait au milieu de la barre de l'application.
 */
.telephone-ecran::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--tel-barre);
  background: #08080a;
  z-index: 1;
}

.telephone-ecran iframe { border: 0; display: block; transform-origin: top left; }

.telephone-ile {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: 60px;
  height: 15px;
  border-radius: 999px;
  background: #08080a;
  z-index: 2;
}

.telephone-bouton {
  position: absolute;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, #56565c, #2a2a2e);
}

.telephone-bouton-1 { left: -2px; top: 96px; height: 26px; }
.telephone-bouton-2 { left: -2px; top: 134px; height: 44px; }
.telephone-bouton-3 { right: -2px; top: 120px; height: 62px; }

/* ---------- Chiffres ---------- */

.chiffre {
  padding: 26px 28px;
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--accent) 5%, transparent), transparent 60%);
}

.chiffre b { display: block; font-size: 1.9rem; font-weight: 600; letter-spacing: -0.03em; }

.chiffre span { font-size: 13px; color: var(--muted); }

/* ---------- Titres ---------- */

h2 {
  font-size: clamp(1.7rem, 3vw, 2.3rem);
  line-height: 1.12;
  letter-spacing: -0.03em;
  font-weight: 600;
  margin: 14px 0 0;
  max-width: 22ch;
}

h3 { font-size: 1.02rem; font-weight: 600; margin: 0 0 8px; letter-spacing: -0.01em; }

.cellule p { margin: 0; color: var(--muted); font-size: 14.5px; }

.cellule p.second { margin-top: 16px; }

.num {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--accent);
  letter-spacing: 0.12em;
  display: block;
  margin-bottom: 14px;
}

code { font-family: var(--mono); font-size: 0.92em; }

/* ---------- Code ---------- */

pre {
  margin: 0;
  padding: 24px 28px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.75;
  color: var(--text);
}

pre .c { color: var(--muted); }
pre .p { color: var(--accent); }

/* ---------- Le tableau et sa bande de personnages ---------- */

.duo { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }

.duo-texte { padding: 62px 28px; }

.duo-texte p { color: var(--muted); max-width: 56ch; margin: 16px 0 0; }

/*
 * LES PERSONNAGES SONT EN LIGNE, ET LA LIGNE SORT DE LA COLONNE. Les quatre veilleurs traversent
 * l'écran de bord à bord, comme les colonnes du tableau qu'ils gardent ; leur fil s'éteint aux deux
 * bouts au lieu de buter sur les bords de l'enveloppe.
 */
/*
 * NI HALO NI PASTILLE DERRIÈRE LES VEILLEURS. Le fond dégradé et les ronds pleins sous les
 * personnages leur donnaient l'air de vignettes détourées ; ils se posent maintenant à même la
 * page, et c'est leur NOM en gras suivi de leur rôle qui porte l'information.
 */
.rail-large {
  width: min(1560px, 94vw);
  margin-left: calc(50% - min(780px, 47vw));
  padding: 34px 0 48px;
}

.rail-large .mini-etiquette { text-align: center; }

.rail {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
  width: 100%;
}

/* Le fil du rail : dégradé aux deux bouts, il ne s'arrête pas net dans le vide. Il passe DERRIÈRE
   les personnages, qui l'effacent par un halo — plus de pastille ronde à leur pied. */
.rail::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 34px;
  height: 1px;
  z-index: 0;
  background: linear-gradient(90deg, transparent, var(--line) 12%, var(--accent) 50%, var(--line) 88%, transparent);
}

.rail-etape img,
.rail-etape b,
.rail-etape span { position: relative; z-index: 1; }

/*
 * LE FOND DU PERSONNAGE S'ÉTEINT AU LIEU DE DESSINER UN ROND. Un aplat rond détourait la
 * silhouette ; un halo qui se perd cache le fil derrière elle sans qu'on voie où il s'arrête.
 */
.rail-etape img { background: radial-gradient(closest-side, var(--bg) 58%, transparent 100%); }

.rail-etape {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  position: relative;
  text-align: center;
  padding: 0 10px;
}

.rail-etape img {
  width: 68px;
  height: auto;
  flex: none;
  margin-bottom: 6px;
  filter: drop-shadow(0 10px 14px var(--ombre));
}

.rail-etape b { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }

.rail-etape span { font-size: 13.5px; color: var(--muted); line-height: 1.5; max-width: 24ch; }

/* ---------- Fonctionnalités : quatre panneaux, un cadre qui suit ---------- */

/*
 * LES TEXTES DÉFILENT À GAUCHE, L'APPLICATION RESTE COLLÉE À DROITE. La colonne de droite tient
 * par « position: sticky » : elle ne peut donc avoir AUCUN ancêtre en « overflow: hidden » —
 * c'est pourquoi l'enveloppe et la page coupent en « clip », jamais en « hidden ».
 *
 * LA LARGEUR SUIT L'APPAREIL CHOISI. Sur ordinateur, l'application a besoin de place : la liste
 * se resserre à un tiers. Sur téléphone, le cadre est étroit : la liste reprend les deux tiers.
 * La bascule est la MÊME que celle de la démonstration, et elle se manie à tout moment.
 */
.fonctions {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 1px;
  background: var(--line-soft);
  transition: grid-template-columns 0.5s ease;
}

html[data-appareil='bureau'] .fonctions { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
html[data-appareil='mobile'] .fonctions { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

.fonctions > * { background: var(--bg); min-width: 0; }

.fonctions-liste { display: flex; flex-direction: column; }

/*
 * UNE ÉTAPE FAIT LA MOITIÉ DU CADRE. Les treize cas d'usage sont rangés en quatre panneaux, mais
 * c'est l'ÉTAPE qui est l'unité de lecture : chacune commande l'écran d'en face, et il lui faut
 * assez de hauteur pour que le geste ait le temps de se jouer avant qu'on passe à la suivante. La
 * mesure vient de « --h-cadre », posée par app.js d'après le cadre réellement affiché.
 */
.fonctions-panneau {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 40px 0;
  border-bottom: 1px solid var(--line-soft);
}

.fonctions-panneau:last-child { border-bottom: none; }

.fonctions-titre {
  margin: 0 0 6px;
  padding: 0 28px;
  font-size: 13px;
  font-family: var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--muted);
  transition: color 0.4s ease;
}

/* Le panneau qu'on lit s'allume : c'est lui qui commande la vue du cadre d'en face. */
.fonctions-panneau.lu .fonctions-titre { color: var(--accent); }

.fonctions-items { list-style: none; margin: 0; padding: 0; }

.fonction {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 26px 28px;
  min-height: calc(var(--h-cadre, 620px) * 0.5);
  transition: background-image 0.5s ease, box-shadow 0.5s ease;
}

.fonction + .fonction { border-top: 1px solid var(--line-soft); }

/*
 * UN CAS D'USAGE SE CLIQUE. Le curseur et l'éclaircissement au survol sont le seul indice qu'on
 * peut lancer sa démonstration soi-même, sans attendre que le défilement l'atteigne. Le contour
 * de focus est celui du site : la liste se parcourt entièrement au clavier.
 */
.fonction.cliquable { cursor: pointer; }
.fonction.cliquable:hover { background-color: color-mix(in srgb, var(--accent) 5%, transparent); }
.fonction.cliquable:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/*
 * L'ÉTAPE QU'ON LIT PORTE UN CADRE. C'est le seul repère qui dit quel cas d'usage commande
 * l'écran d'en face : sans lui, treize paragraphes se ressemblent et l'écran change sans qu'on
 * sache pourquoi. Le trait est posé à gauche EN OMBRE INTÉRIEURE, jamais en bordure — la bordure
 * du haut sépare déjà deux étapes, et deux bordures se disputeraient le même pixel.
 */
.fonction.lu {
  background-image: linear-gradient(100deg, color-mix(in srgb, var(--accent) 9%, transparent), transparent 62%);
  box-shadow: inset 2px 0 0 var(--accent);
}

.fonction h4,
.fonction .fonction-icone,
.fonction-numero { transition: color 0.4s ease, border-color 0.4s ease; }

.fonction.lu h4 { color: var(--accent); }
.fonction.lu .fonction-icone { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
.fonction.lu .fonction-numero { color: var(--accent); }

/* Le numéro du cas d'usage : treize étapes, on sait toujours où l'on en est. */
.fonction-numero {
  flex: none;
  align-self: flex-start;
  margin-top: 9px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  color: var(--faint, var(--muted));
}

.fonction-icone {
  flex: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--accent);
  background-image: linear-gradient(150deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%);
}

.fonction-icone svg { width: 19px; height: 19px; display: block; }

.fonction h4 { margin: 6px 0 8px; font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; }

.fonction p { margin: 0; color: var(--muted); font-size: 14.5px; }

.fonctions-colle { padding: 0; }

/*
 * LE CADRE COLLÉ SE TIENT AU MILIEU DE LA FENÊTRE, pas sous la barre du haut : le texte défile à
 * gauche, l'application reste en face du regard. Le décalage est CALCULÉ par app.js d'après la
 * hauteur réellement occupée par le bloc collé — « top » n'accepte pas de pourcentage relatif à
 * l'élément lui-même, et un « 50% » se rapporterait à la rangée de grille, donc à rien d'utile.
 * La valeur de repli (92 px) est celle d'avant : sans JavaScript, le cadre reste collé en haut.
 */
.fonctions-colle-inner {
  position: sticky;
  top: var(--colle-haut, 92px);
  padding: 30px 28px 34px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-image: radial-gradient(90% 70% at 50% 10%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 70%);
}

.fonctions-reglages {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.fonctions-reglages .mini-etiquette { margin: 0; }

/*
 * LA SCÈNE COUPE CE QUI SORT DU CADRE. Elle est portée par l'élément collé lui-même, jamais par
 * un de ses ancêtres : un « overflow » posé plus haut décollerait la colonne.
 */
.fonctions-scene {
  align-self: stretch;
  display: flex;
  justify-content: center;
  overflow: hidden;
}

/*
 * LE CADRE OUVRE L'ÉCRAN DONT ON PARLE, PUIS S'EN APPROCHE. Le grossissement et le décalage ne
 * sont plus écrits ici, une valeur par vue : app.js MESURE la zone visée dans l'application et
 * pose la transformation en ligne. Une valeur écrite à la main se démentait au premier changement
 * de mise en page de l'application, qui est reconstruite chaque nuit.
 *
 * Chaque panneau porte sa vue : app.js ouvre dans le cadre l'écran correspondant — une carte et
 * son volet, la pile des agents, le coffre-fort, le tableau de bord — et le recadrage vient se
 * poser dessus. Le zoom est franc, pas décoratif.
 */
/* La fenêtre prend TOUTE la largeur de la colonne : c'est elle qui décide de l'échelle à laquelle
   l'application est rendue, et une fenêtre étriquée rendrait la démonstration illisible. */
.fonctions-cadre {
  width: 100%;
  display: flex;
  justify-content: center;
  /* Le centre, et rien d'autre : c'est l'hypothèse du calcul fait dans app.js. */
  transform-origin: 50% 50%;
  transition: transform 0.9s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

.fonctions-cadre > .fenetre { width: 100%; }

/*
 * AUCUN CADRAGE NE DOIT LAISSER UN BORD VIDE. Le point d'ancrage est à 50 % 24 % : pour un
 * agrandissement « s », le décalage horizontal reste entre ±(0,5 − 0,5/s) et le vertical entre
 * 0,76/s − 0,76 et 0,24 − 0,24/s. Au-delà, le cadre se décolle d'un côté et l'on voit le fond
 * de la page à travers la fenêtre.
 */
/* La carte ouverte tient le milieu du cadre : on s'en approche sans la décaler. */
[data-vue='carte'] .fonctions-cadre { transform: scale(1.34) translate(0, 1%); }
/* La pile des agents s'ouvre tout en bas de la colonne de gauche : on descend l'y chercher. */
[data-vue='agents'] .fonctions-cadre { transform: scale(1.7) translate(19%, -30%); }
/* Le coffre-fort se lit par la gauche, la barre du navigateur passée. */
[data-vue='coffre'] .fonctions-cadre { transform: scale(1.45) translate(13%, 0); }
/* Le tableau de bord se lit par le haut : ses compteurs et son activité jour après jour. */
[data-vue='bord'] .fonctions-cadre { transform: scale(1.4) translate(12%, -2%); }

/*
 * SUR TÉLÉPHONE, AUCUN RECADRAGE. Le cadre de poche est déjà à la taille d'un vrai téléphone dans
 * une colonne à peine plus large : l'agrandir ne rapproche de rien, il coupe la coque et l'écran
 * cesse de se lire comme un téléphone. Ce qui rend la fonctionnalité claire ici, c'est l'écran
 * OUVERT par app.js — le tiroir déplié, la carte affichée —, pas le zoom.
 */
html[data-appareil='mobile'] .fonctions-cadre { transform: none; }

.fonctions-vue {
  margin: 18px 0 0;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

/*
 * LE CHOIX DE LA FONCTIONNALITÉ, SOUS LE CADRE. Treize pastilles et deux flèches : on change
 * d'écran sans remonter la liste. La rangée se replie sur plusieurs lignes quand la colonne est
 * étroite — jamais de barre qui déborde de la fenêtre du navigateur dessinée au-dessus.
 */
.fonctions-nav {
  margin: 14px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

.fonctions-pastilles {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex-wrap: wrap;
}

.fonctions-pastille,
.fonctions-fleche {
  appearance: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--muted);
  font-family: var(--mono);
  transition: color 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
}

.fonctions-pastille {
  width: 26px;
  height: 26px;
  font-size: 11px;
  padding: 0;
}

.fonctions-fleche { width: 28px; height: 28px; padding: 0; }
.fonctions-fleche svg { width: 15px; height: 15px; display: block; }

.fonctions-pastille:hover,
.fonctions-fleche:hover:not(:disabled) {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--line));
}

.fonctions-pastille:focus-visible,
.fonctions-fleche:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* La pastille de la fonctionnalité montrée : c'est le seul repère de « où l'on en est » dans le
   cadre, quand la liste de gauche est passée hors de l'écran. */
.fonctions-pastille.lu {
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

.fonctions-fleche:disabled { opacity: 0.35; cursor: default; }

/* ---------- Changelog ---------- */

.journal { display: grid; grid-template-columns: 200px 1fr; }

.journal + .journal { border-top: 1px solid var(--line-soft); }

.journal-date {
  padding: 26px 28px;
  border-right: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-transform: uppercase;
}

.journal-liste { padding: 26px 28px; margin: 0; list-style: none; }

.journal-liste li {
  position: relative;
  padding-left: 20px;
  font-size: 14.5px;
  color: var(--text);
  /* Un titre de carte sans espace ne doit pas élargir la page sur téléphone. */
  overflow-wrap: anywhere;
}

.journal-liste li + li { margin-top: 9px; }

.journal-liste li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 10px;
  width: 7px;
  height: 1px;
  background: var(--accent);
}

#journal-corps { border-top: 1px solid var(--line-soft); }

/* ---------- Studio ---------- */

.studio { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }

.studio-texte { padding: 62px 28px; }

.studio-texte p { color: var(--muted); max-width: 52ch; }

.fiche { padding: 0; display: flex; flex-direction: column; justify-content: center; }

.fiche-ligne {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 18px 28px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px;
}

.fiche-ligne:last-child { border-bottom: none; }

.fiche-ligne span:first-child { color: var(--muted); }

/* ---------- Appel final ---------- */

.final {
  padding: 72px 28px;
  background-image: radial-gradient(70% 130% at 50% 120%, color-mix(in srgb, var(--accent) var(--lueur), transparent), transparent 65%);
}

.final h2 { max-width: 24ch; margin-top: 0; }

.final p { color: var(--muted); max-width: 58ch; margin: 16px 0 0; }

/* ---------- Pied ---------- */

footer { padding: 44px 28px; }

.pied-haut { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; }

.pied-liens { display: flex; gap: 20px; flex-wrap: wrap; font-size: 14px; }

.pied-liens a { color: var(--muted); text-decoration: none; }

.pied-liens a:hover { color: var(--text); }

.mention {
  margin-top: 32px;
  font-size: 12.5px;
  color: var(--muted);
  display: flex;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}

/* ---------- Squelettes ---------- */

/*
 * PLUS AUCUN BLOC N'ENTRE EN GLISSANT. Tant qu'un bloc n'a pas été lu, il montre un SQUELETTE :
 * des barres grises posées par-dessus, qui balaient doucement. Dès qu'il entre dans l'écran,
 * app.js pose la classe « vu », le squelette s'efface et le contenu se révèle sur place — sans
 * mouvement, sans saut de mise en page.
 *
 * Le squelette est un CALQUE, pas une mise en forme des textes : il se pose sur n'importe quel
 * bloc, quel que soit son contenu, et ne change jamais sa hauteur.
 */
.apparait { position: relative; }

.apparait > * { transition: opacity 0.45s ease; }

.apparait:not(.vu) > * { opacity: 0; }

.apparait::before {
  content: '';
  position: absolute;
  inset: 14px;
  z-index: 1;
  pointer-events: none;
  border-radius: var(--radius);
  opacity: 1;
  transition: opacity 0.5s ease;
  /* Trois barres de largeurs différentes : un titre, puis deux lignes de texte. */
  background-image:
    linear-gradient(90deg, var(--surface-2) 0 42%, transparent 42%),
    linear-gradient(90deg, var(--surface-2) 0 88%, transparent 88%),
    linear-gradient(90deg, var(--surface-2) 0 64%, transparent 64%);
  background-repeat: no-repeat;
  background-size: 100% 20px, 100% 12px, 100% 12px;
  background-position: 0 6px, 0 44px, 0 66px;
}

/* Le balayage : la seule chose qui bouge, et elle s'arrête dès que le bloc est lu. */
.apparait::after {
  content: '';
  position: absolute;
  inset: 14px;
  z-index: 2;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.5s ease;
  background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text) 6%, transparent) 50%, transparent 70%);
  background-size: 240% 100%;
  animation: balayage 1.9s linear infinite;
}

@keyframes balayage {
  from { background-position: 140% 0; }
  to { background-position: -60% 0; }
}

.apparait.vu > * { opacity: 1; }

.apparait.vu::before,
.apparait.vu::after { opacity: 0; animation: none; }

/* Sans JavaScript, rien ne doit rester caché derrière un squelette. */
html.sans-observateur .apparait > * { opacity: 1; }
html.sans-observateur .apparait::before,
html.sans-observateur .apparait::after { display: none; }

@media (prefers-reduced-motion: reduce) {
  .apparait > * { opacity: 1; transition: none; }
  .apparait::before, .apparait::after { display: none; }
  .demo-plan { --inclinaison: 0deg; --recul: 1; --montee: 0px; opacity: 1; transition: none; }
  .fonctions-cadre { transition: none; }
  html { scroll-behavior: auto; }
}

/*
 * ---------- L'ADAPTATION AUX ÉCRANS ----------
 *
 * QUATRE PALIERS, ET LE MÊME SITE À CHACUN. Ce qui change d'un palier à l'autre, ce sont les
 * COLONNES, les RESPIRATIONS et l'ÉCHELLE DES TITRES — jamais le contenu : aucune section, aucun
 * bouton et aucun chemin vers le dépôt ne disparaît sur téléphone. Une commande qui n'a plus la
 * place de son mot se réduit à son icône ; elle ne s'efface pas.
 *
 * LES RESPIRATIONS SONT PROPORTIONNELLES, PAS FIXES. Les 62 px qui aèrent un titre sur 1200 px
 * de large étirent la page sur 390 : chaque palier resserre les marges d'un cran, sinon la page
 * du téléphone n'est qu'une colonne de blancs qu'on fait défiler.
 */

/* ---------- Sous la largeur de la colonne ---------- */

/*
 * En dessous de 1200 px, l'enveloppe n'a plus sa largeur de repos : une cellule sur deux tombe
 * sous 460 px, et la ligne « git clone https://github.com/… » n'y tient plus. Les deux colonnes
 * de l'installation s'empilent dès ce moment.
 */
@media (max-width: 1199px) {
  .g2 { grid-template-columns: 1fr; }
}

/* ---------- Écrans moyens ---------- */

@media (max-width: 1080px) {
  .telephone { --tel-l: 214px; --tel-h: 424px; }
  .demo-plan > .telephone { --tel-l: 290px; --tel-h: 590px; }
  .entete { padding: 54px 26px 30px; }
  .hero-texte { padding: 84px 26px 72px; }
}

@media (max-width: 900px) {
  .g4 { grid-template-columns: repeat(2, 1fr); }
  .g3 { grid-template-columns: repeat(2, 1fr); }
  .duo, .studio { grid-template-columns: 1fr; }
  .hero-texte { padding: 68px 28px 56px; }
  nav.liens { display: none; }
  /* Les liens partis, plus rien ne pousse les commandes vers la droite : on le fait ici. */
  .actions { margin-left: auto; }
  .entete { padding-top: 48px; }
  .studio-texte, .duo-texte { padding: 48px 28px; }
  .rail-large { padding: 28px 0 40px; }
  .demo-large { padding: 34px 0 42px; }
  /*
   * Sur écran étroit, la démonstration collée n'a plus de place à côté du texte : elle repasse
   * au-dessus de la liste et reste collée en haut de l'écran pendant la lecture.
   */
  html[data-appareil] .fonctions { grid-template-columns: 1fr; }
  .fonctions-colle { order: -1; }
  /*
   * LE CADRE NE COLLE PLUS : il ouvre la section, puis la liste se lit seule. Un élément collé ne
   * tient que dans SA rangée de grille ; sur une colonne unique, cette rangée fait la hauteur du
   * cadre, et il disparaîtrait au premier panneau. La bascule reste à portée : elle ouvre la
   * section, et la démonstration la porte aussi, plus haut dans la page.
   */
  .fonctions-colle-inner { position: static; padding-bottom: 30px; }
  /* Le cadre passe au-dessus : un panneau n'a plus à faire sa hauteur. */
  .fonctions-panneau { min-height: 0; padding: 24px 0; }
  /* Sans colonne collée en face, l'étape lue ne commande plus rien : ni hauteur, ni cadre. */
  .fonction { min-height: 0; box-shadow: none; background-image: none; }
  /*
   * ET IL NE SE RECADRE PLUS. Le zoom sur une partie de l'application n'a de sens que quand le
   * cadre est EN FACE du panneau qu'on lit ; posé en tête de section, il agrandirait une vue
   * sans rapport avec le texte qu'on a sous les yeux.
   */
  .fonctions-cadre { transform: none !important; }
}

/* ---------- Grand téléphone et petite tablette ---------- */

@media (max-width: 720px) {
  .journal { grid-template-columns: 150px 1fr; }
  .entete { padding: 46px 24px 28px; }
  .entete.mince { padding: 28px 24px 22px; }
  .cellule { padding: 24px; }
  .fonction { padding: 22px 24px; }
  .final { padding: 60px 24px; }
  footer { padding: 36px 24px; }
}

/* ---------- Téléphone ---------- */

@media (max-width: 620px) {
  .g4, .g3, .g2 { grid-template-columns: 1fr; }
  /*
   * LES CHIFFRES RESTENT À DEUX DE FRONT. Quatre nombres empilés sur toute la largeur faisaient
   * quatre bandes presque vides et rallongeaient la page de plus d'un écran ; à deux, la rangée
   * se lit d'un coup d'œil, comme sur ordinateur.
   */
  .grille.g4:has(> .chiffre) { grid-template-columns: repeat(2, 1fr); }

  /*
   * LES TITRES SUIVENT L'ÉCRAN. « clamp » plafonnait vers le haut mais gardait un plancher de
   * 2,5 rem : sur 390 px, le titre d'accueil tenait sur cinq lignes et poussait tout le reste
   * hors de la première vue.
   */
  h1 { font-size: clamp(1.95rem, 8.4vw, 2.45rem); letter-spacing: -0.03em; margin-top: 20px; }
  h2 { font-size: 1.5rem; }
  .chapo { font-size: 1rem; margin-top: 18px; }
  .pastille { font-size: 10.5px; padding: 5px 12px; }

  /* Les respirations d'un écran large, resserrées d'un tiers. */
  .hero-texte { padding: 44px 20px 38px; }
  .hero-actions { margin-top: 26px; gap: 10px; }
  .entete { padding: 40px 20px 24px; }
  .entete.mince { padding: 24px 20px 18px; }
  .cellule { padding: 22px 20px; }
  .fonction { padding: 20px; gap: 14px; }
  .final { padding: 48px 20px; }
  .studio-texte, .duo-texte { padding: 40px 20px; }
  .fiche-ligne { padding: 15px 20px; }
  .journal-date, .journal-liste { padding: 18px 20px; }
  .demo-reglages { padding: 18px 20px; }
  footer { padding: 32px 20px; }
  .mention { margin-top: 24px; }
  .chiffre { padding: 20px; }
  .chiffre b { font-size: 1.65rem; }
  .chiffre span { font-size: 12.5px; }
  .fonctions-titre { padding: 0 20px; }
  .fonctions-colle-inner { padding: 24px 20px 26px; }
  .fonctions-panneau { padding: 18px 0; }
  .barre-inner { padding: 0 20px; }

  /*
   * LE BLOC DE COMMANDES TIENT DANS L'ÉCRAN. À 13 px, « git clone https://… » débordait de la
   * cellule et ne se lisait qu'en faisant glisser la ligne à l'aveugle.
   */
  pre {
    font-size: 11.5px;
    padding: 18px 20px;
    line-height: 1.7;
    /* La ligne se PLIE au lieu de sortir : un « git clone » qu'il faut faire glisser à l'aveugle
       pour lire la fin ne se recopie pas. Le repli suit le mot, et coupe l'adresse s'il le faut. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  .telephone { --tel-l: 232px; --tel-h: 460px; }
  .demo-plan > .telephone { --tel-l: 244px; --tel-h: 495px; }
  .demo-large { padding: 24px 0 30px; }
  .rail-large { padding: 22px 0 32px; }

  /* Les quatre personnages passent sur deux rangées : leur fil ne les relierait plus, il sort. */
  .rail { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 12px; }
  .rail::before { display: none; }
  .rail-etape { padding: 0 4px; }
  .rail-etape img { width: 58px; margin-bottom: 2px; }
  .rail-etape b { font-size: 14px; }
  .rail-etape span { font-size: 12.5px; }

  .journal { grid-template-columns: 1fr; }
  .journal-date { border-right: none; border-bottom: 1px solid var(--line-soft); padding-bottom: 0; }
  .enveloppe, .barre-inner { border-left: none; border-right: none; }

  /*
   * LA BARRE DU HAUT GARDE SES TROIS COMMANDES. Elle en perdait une — le dépôt — faute de place ;
   * le bouton se réduit maintenant à la marque GitHub, carré comme la bascule d'ambiance, et les
   * trois tiennent à l'aise. Les liens de section, eux, restent au pied de page.
   */
  html { scroll-padding-top: 68px; }
  .barre-inner { height: 54px; gap: 10px; }
  .marque { font-size: 15px; }
  .marque img { width: 24px; height: 24px; }
  .actions { gap: 8px; margin-left: auto; }
  .langue { height: 34px; padding-left: 8px; }
  .langue select { font-size: 12.5px; padding: 0 6px 0 5px; }
  .bascule { width: 34px; height: 34px; }
  .bouton-github { width: 34px; height: 34px; padding: 0; justify-content: center; }
  .bouton-github .bouton-texte { display: none; }
  .bouton-github .icone-github { display: block; }

  /* Le squelette de chargement se serre avec les blocs qu'il recouvre. */
  .apparait::before, .apparait::after { inset: 10px; }
}

/* ---------- Petit téléphone ---------- */

@media (max-width: 380px) {
  h1 { font-size: 1.8rem; }
  h2 { font-size: 1.38rem; }
  .segments { gap: 8px; }
  .segment-choix { padding: 6px 11px; font-size: 12.5px; }
  .demo-plan > .telephone { --tel-l: 218px; --tel-h: 442px; }
  .rail-etape img { width: 52px; }
  .rail-etape span { max-width: 18ch; }
  .marque span { display: none; }
  pre { font-size: 10.5px; padding: 16px; }
}
