/* Les trois polices de la marque, auto-hébergées depuis le 15 octobre 2026 (liste
   « Avant la mise en ligne » du README). Elles étaient appelées chez Google Fonts,
   ce qui transmettait l'adresse IP du visiteur à Google avant tout consentement.
   Mêmes fichiers que ceux que servait Google — polices variables, sous-ensembles
   latin et latin étendu, les seuls dont un texte français a besoin —, mêmes
   « unicode-range », même « font-display: swap » : le rendu ne change pas d'un
   pixel. Les trois familles sont sous licence libre (SIL Open Font License). */
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(polices/azeret-mono-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(polices/azeret-mono-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(polices/azeret-mono-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(polices/azeret-mono-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(polices/azeret-mono-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Azeret Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(polices/azeret-mono-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(polices/bricolage-grotesque-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(polices/bricolage-grotesque-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(polices/libre-franklin-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(polices/libre-franklin-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(polices/libre-franklin-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(polices/libre-franklin-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(polices/libre-franklin-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(polices/libre-franklin-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(polices/libre-franklin-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(polices/libre-franklin-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Les jetons de couleur et de typographie sont repris tels quels du prototype :
   la page d'accueil et l'application doivent se ressembler assez pour qu'on ne
   doute pas d'être au bon endroit après avoir cliqué. */
:root{
  /* Les contrôles que le navigateur dessine lui-même — barres de défilement,
     menus déroulants, champs, calendriers — ne lisent pas nos jetons. Ils suivent
     ceci, et rien d'autre. Sans la ligne, un visiteur réglé en sombre aurait des
     contrôles nuit sur une page jour ; et comme celui qui relit la page est en
     clair, le défaut ne se verrait jamais de ce côté-ci.
     La balise « color-scheme » dans l'en-tête des pages fait la même chose, et les
     deux servent : la balise vaut dès l'analyse du document, avant que cette
     feuille ne soit arrivée, ce qui évite l'éclair de contrôles sombres au premier
     rendu ; la propriété, elle, est la valeur qui fait foi ensuite. La balise
     seule laisse « color-scheme » calculé à « normal », ce qui se vérifie mal —
     on ne saurait pas, en relisant, si elle a bien été prise en compte. */
  color-scheme:light;

  --texte:'Libre Franklin', system-ui, -apple-system, sans-serif;
  --display:'Bricolage Grotesque', system-ui, sans-serif;
  --mono:'Azeret Mono', ui-monospace, monospace;

  /* Le quatrième n'est pas une police de la marque, et c'est tout son intérêt.
     Il ne sert qu'aux deux appareils de la démonstration — le nom dans la barre de
     titre du Mac, l'heure dans la barre d'état de l'iPhone — c'est-à-dire aux seuls
     textes de la page qui ne sont pas écrits par Kinôme mais par le système.

     Ils étaient jusqu'ici en « --texte » et en « --mono », autrement dit en Libre
     Franklin et en Azeret Mono. Géométriquement personne ne s'en plaignait, et c'était
     pourtant le défaut qui faisait que ces deux châssis sentaient le dessin. Une
     barre de titre macOS est en San Francisco, l'heure d'un iPhone aussi, et cette
     police n'est pas un choix de l'application : c'est une propriété de l'appareil,
     comme l'aluminium du capot ou le rayon des angles. La mettre à la fonte de la
     marque revenait à peindre les pastilles de fenêtre à l'accent — on l'a refusé là,
     il n'y avait pas de raison de l'accepter ici.

     Pas de guillemets sur « -apple-system » ni sur « BlinkMacSystemFont » : ce ne sont
     pas des noms de familles mais des mots-clés que le moteur résout vers la police du
     système. Les trois suivantes sont la retombée pour qui ne lit pas la page sur un
     Mac, et elle n'a presque pas d'importance : ces deux textes-là ne se voient que
     sur une capture d'appareil Apple, où le premier mot-clé répond toujours. */
  --systeme:-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Helvetica, Arial, sans-serif;

  --page-bg:#E9E9E1;
  --bg:#F1F2ED;
  --surface:#FFFFFF;
  --text:#16233F;
  --text-muted:#3C4A66;
  --border:#E6E5DD;
  --accent:#E8963C;
  --accent-ink:#8A5A1E;
  --accent-soft:#FBEEDC;
  --on-accent:#16233F;
  --ok:#4F7A63;
  --ok-soft:#E4EDE7;
  --select-soft:#E7EAF0;
  --grille:#E4E3DA;

  --radius-sm:12px;
  --radius-md:16px;
  --radius-lg:26px;
  --shadow:0 10px 30px rgba(22,35,63,.08);
  --shadow-lg:0 28px 70px rgba(22,35,63,.16);
  /* La largeur utile de la page. Elle était à 1120 px, ce qui laissait près de deux
     cents pixels de vide de chaque côté sur un écran courant — la page avait l'air
     posée au milieu d'une table plutôt que d'occuper son écran. Le texte long n'en
     souffre pas : les blocs de lecture ont leur propre plafond en « ch » ou en pixels
     (« .article » à 730, « .chapeau » à 62ch), et ce sont eux qui tiennent la longueur
     de ligne. Ce réglage-ci ne commande que les grilles et les cartes, qui s'élargissent
     sans se déformer. */
  --largeur:1280px;

  /* ---------- Le grain et les lueurs du fond ----------
     Le fond n'était qu'une couleur, posée d'un bord à l'autre sans une variation : à
     l'écran, un aplat parfait ne ressemble à rien de regardé, il ressemble à un calque.
     Deux corrections, et aucune des deux n'ajoute d'élément au balisage.
     Le grain d'abord. C'est un bruit fractal tracé par le navigateur lui-même
     (« feTurbulence »), désaturé, en tuile de 160 px : trois cents octets, contre les
     dizaines de kilo-octets qu'aurait coûté la même texture en image. Il est posé en
     « overlay », ce qui veut dire qu'un gris moyen ne change rien et que seules les
     variations comptent — le fond garde donc exactement sa couleur, il perd seulement
     sa planéité. L'alternative, du gris à faible opacité, aurait éclairci le thème
     sombre d'un voile uniforme : du bruit en plus et la bonne couleur en moins.
     Les lueurs ensuite : trois taches très larges et très diluées, une chaude en haut
     à droite derrière la démonstration, une froide à gauche derrière le titre, une
     chaude au pied de la fenêtre. Elles sont fixées au cadre et non à la page, de sorte
     qu'elles se comportent comme l'éclairage de la pièce et non comme un décor qui
     défile : on ne les voit pas passer, on voit que le fond n'est pas éteint.
     Le nuage enfin. Le grain seul, monté assez fort pour se voir, donne un fond sableux
     mais toujours uniforme : la texture est partout la même, et c'est encore une sorte
     d'aplat. Ce qui manque est la variation lente, celle qui fait qu'un mur peint n'a pas
     tout à fait la même valeur à deux mètres d'intervalle. C'est le même « feTurbulence »
     avec une fréquence deux cents fois plus basse, en tuile de 900 px : des taches douces,
     sans contour, qu'on ne remarque pas une à une mais qui empêchent l'œil de trouver
     deux endroits identiques. Grain fin et nuage large se complètent — l'un donne la
     matière, l'autre donne la profondeur, et il en faut peu de chacun. */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
  --nuage:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='900'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.004' numOctaves='5' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='900' height='900' filter='url(%23n)'/%3E%3C/svg%3E");
  /* Monté de .55 à .85 le 15 octobre 2026, à la demande du propriétaire du site.
     Le réglage ne concerne que le fond — « --grain-surface » et « --grain-final »
     ne bougent pas. Ce sont trois surfaces de natures différentes, et la raison
     pour laquelle elles n'ont pas le même chiffre n'a pas changé : le fond est un
     grand aplat libre, où la matière se lit comme de la matière ; les bandeaux
     sont des bandes basses et bordées, où la même valeur se lirait comme de la
     salissure. Monter les trois ensemble aurait été confondre un rapport avec une
     règle de trois. Le nuage reste à .3 : c'est l'autre variation, celle qui
     donne la profondeur, et elle n'a rien demandé. */
  --grain-force:.85;
  --nuage-force:.3;
  /* Les bandeaux prennent un grain plus discret que le fond. Ce sont des bandes basses et
     larges, bordées, posées au milieu de la page : ce qui passe pour de la matière sur une
     grande surface libre y passe pour de la salissure. */
  --grain-surface:.4;
  /* Le pavé final est peint dans la couleur du texte : il est donc toujours l'inverse
     du thème — sombre quand la page est claire, clair quand elle est sombre. Son grain
     prend par conséquent le réglage de l'autre thème, et les deux valeurs sont croisées
     d'un bloc à l'autre. */
  --grain-final:.8;
  --lueur-chaude:color-mix(in srgb, var(--accent) 11%, transparent);
  --lueur-froide:color-mix(in srgb, var(--select-soft) 62%, transparent);
  /* Le voile de survol de la barre et du sélecteur du parcours. Il était tiré de
     la couleur du texte, donc bleu-gris, donc froid au milieu d'une page qui ne
     l'est nulle part ailleurs : tous les autres états — bouton, badge, lien du
     menu déroulant — passent déjà par l'ambre. Deux intensités, parce qu'un
     menu ouvert doit se distinguer d'un simple survol. */
  --survol:color-mix(in srgb, var(--accent) 16%, transparent);
  --survol-appuye:color-mix(in srgb, var(--accent) 26%, transparent);
}
/* ----------------------------------------------------------------------------
   Les couleurs de nuit, en réserve
   ----------------------------------------------------------------------------
   Ce bloc s'activait tout seul par « @media (prefers-color-scheme: dark) », et
   c'était une erreur de cadrage plutôt qu'une erreur de code : le site n'avait
   pas la même tête selon le réglage de l'ordinateur du visiteur. Deux personnes
   regardant la même adresse au même moment n'y voyaient pas la même marque, et
   celui qui relit la page ne voit jamais la version que voit l'autre. Un site
   d'entreprise choisit son thème ; une application le laisse choisir.

   Rien n'est perdu pour autant : le sélecteur devient explicite. Les jetons
   restent entiers, à leurs valeurs exactes, et il suffira de poser
   « data-theme="dark" » sur « <html> » pour les rallumer — le jour où un
   sélecteur de thème sera demandé, le travail de colorimétrie sera déjà fait.
   Les supprimer aurait obligé à le refaire.

   Trois choses vont avec, et sans elles le thème clair serait incomplet :
     — « color-scheme:light » sur « :root » plus haut, et la balise du même nom
       dans l'en-tête de chaque page, pour les contrôles que le navigateur peint
       lui-même ;
     — la même coupe dans « app.css », qui n'est pas écrite à la main mais
       produite par « outils/assembler-demo.py » (voir « est_bascule_sombre »
       là-bas) : corriger le fichier n'aurait tenu qu'une capture ;
     — « outils/verifier-site.py », qui refuse désormais toute requête de media
       sur le thème du système et toute page sans la balise. C'est ce qui empêche
       la bascule de revenir par mégarde, par exemple en recopiant un gabarit.
   ---------------------------------------------------------------------------- */
:root[data-theme="dark"]{
  /* Le pendant de la ligne du thème clair : si la réserve est un jour rallumée,
     les contrôles du navigateur doivent basculer avec elle, sans quoi on aurait
     le défaut à l'envers — des champs et des barres de jour sur une page de nuit. */
  color-scheme:dark;

  --page-bg:#0E1526;
  --bg:#16233F;
  --surface:#1E2C4C;
  --text:#F1F2ED;
  --text-muted:#A9B3C9;
  --border:#263248;
  --accent:#F0A855;
  --accent-ink:#F7CE9C;
  --accent-soft:#3A2C18;
  --on-accent:#16233F;
  --ok:#7FA98F;
  --ok-soft:#1E3328;
  --select-soft:#26344F;
  --grille:#2A3A5A;
  --shadow:0 10px 30px rgba(0,0,0,.45);
  --shadow-lg:0 25px 60px rgba(0,0,0,.6);

  /* Le grain est poussé en thème sombre et les lueurs aussi, pour une raison
     d'optique et non de goût : en « overlay », un bleu nuit à 8 % de luminosité
     rend la variation huit fois plus faible que la crème du thème clair. Le même
     réglage des deux côtés donnerait un fond texturé d'un côté et lisse de l'autre.
     Suit le thème clair le 15 octobre 2026, de .8 à 1 : l'écart entre les deux
     réglages est ce qui fait que les deux thèmes ont la même matière, et laisser
     la nuit en arrière aurait rouvert le défaut que ce commentaire décrit. */
  --grain-force:1;
  --nuage-force:.35;
  --grain-surface:.55;
  --grain-final:.55;
  --lueur-chaude:color-mix(in srgb, var(--accent) 12%, transparent);
  --lueur-froide:color-mix(in srgb, var(--grille) 26%, transparent);
  /* Plus appuyé que de jour : un voile de 16 % posé sur un fond sombre ne se voit
     pas, là où le même voile sur un fond clair suffit largement. */
  --survol:color-mix(in srgb, var(--accent) 22%, transparent);
  --survol-appuye:color-mix(in srgb, var(--accent) 32%, transparent);
}

*{box-sizing:border-box;}
/* « text-size-adjust:100% » (18 octobre 2026) : sans cette ligne, Safari sur iPhone et
   Chrome sur Android grossissent d'eux-mêmes les paragraphes qu'ils jugent trop petits.
   Les écrans de l'application posés dans les téléphones de démonstration sont réduits
   par « zoom » : leurs textes passaient pour minuscules, étaient regonflés, et la fiche
   du fil débordait de l'écran du téléphone — plus large que lui, décalée, la barre du
   bas remontée. On garde la taille écrite, partout. */
html{scroll-behavior:smooth;scroll-padding-top:84px;-webkit-text-size-adjust:100%;text-size-adjust:100%;}
body{
  margin:0;
  background:var(--page-bg);
  color:var(--text);
  font-family:var(--texte);
  font-size:16.5px;
  line-height:1.62;
  -webkit-font-smoothing:antialiased;
}

/* Les deux couches du fond. Elles sont au rang « -1 » : cela les place au-dessus du
   fond de la page et en dessous de tout ce qui s'écrit par-dessus. C'est le point
   important de ce bloc — une nappe posée par-dessus le contenu aurait texturé aussi
   les lettres et les écrans de la démonstration, c'est-à-dire sali ce qu'on vient
   justement de rendre net. Le grain appartient au fond, il reste dans le fond.
   « ::before » porte les lueurs, « ::after » le grain, dans cet ordre : le grain passe
   sur les lueurs, sans quoi les lueurs seraient les seules surfaces lisses de la page. */
/* Le nuage est porté par la racine et non par le corps, pour une raison d'ordre : à rang
   égal, les couches se peignent dans l'ordre de l'arbre, et « html » vient avant « body ».
   Le nuage passe donc sous les lueurs, qui sont une lumière et doivent rester au-dessus
   de ce qu'elles éclairent, et sous le grain, qui est la matière de surface. */
html::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background-image:var(--nuage);
  mix-blend-mode:overlay;
  opacity:var(--nuage-force);
}
body::before,
body::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
}
body::before{
  background:
    radial-gradient(1100px 760px at 82% -8%,  var(--lueur-chaude), transparent 68%),
    radial-gradient( 900px 700px at  2% 34%,  var(--lueur-froide), transparent 70%),
    radial-gradient(1200px 820px at 56% 112%, var(--lueur-chaude), transparent 72%);
}
body::after{
  background-image:var(--grain);
  mix-blend-mode:overlay;
  opacity:var(--grain-force);
}

/* Les surfaces opaques recouvrent la nappe fixe et retomberaient donc à plat au
   milieu d'une page qui ne l'est plus. On leur rend le grain, et « background-attachment:
   fixed » le prend sur la même trame que le reste : sans cela chaque case du bandeau
   repartirait de son propre coin de tuile, et les raccords se verraient là où il y a
   justement quatre bordures pour attirer l'œil. « isolation » est ce qui autorise le
   rang « -1 » à l'intérieur : il faut un contexte d'empilement pour que la nappe
   s'arrête au-dessus du fond de sa case au lieu de glisser derrière.
   Les cartes, elles, restent lisses : du papier posé sur une table a du grain sous lui,
   pas sur lui. */
.bandeau,.fait,.final{position:relative;isolation:isolate;}
.bandeau::before,
.fait::before,
.final::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  border-radius:inherit;
  background-image:var(--grain);
  background-attachment:fixed;
  mix-blend-mode:overlay;
  opacity:var(--grain-surface);
}
/* Le nuage s'arrête au fond et n'entre pas dans les bandeaux : essayé, et c'est un défaut.
   Une variation dont la période fait neuf cents pixels n'a pas la place de se déployer dans
   une bande haute de cent vingt — on n'en voit qu'un morceau, c'est-à-dire une tache, et
   les quatre bordures de la grille découpent cette tache en lui donnant un bord net.
   Le nuage a besoin de surface pour se lire comme de la profondeur ; sur une bande, il se
   lit comme un défaut d'impression. */
/* Le pavé final est la seule de ces surfaces qui soit assez grande pour porter le nuage,
   et il en a besoin plus que les autres : c'est le plus large aplat de la page. */
.final::before{
  opacity:var(--grain-final);
  background-image:var(--grain), var(--nuage);
  background-attachment:fixed, fixed;
  background-blend-mode:overlay;
}
h1,h2,h3{font-family:var(--display);font-weight:700;letter-spacing:-0.02em;line-height:1.12;margin:0;}
h1{font-size:clamp(38px,6.2vw,66px);}
h2{font-size:clamp(28px,3.7vw,42px);}
h3{font-size:19px;letter-spacing:-0.01em;}
p{margin:0;}
a{color:inherit;}
ul{margin:0;padding:0;list-style:none;}

/* La gouttière est le dernier recours quand la fenêtre est plus étroite que la largeur
   utile : elle ne se voit que là, et c'est pourquoi elle reste à 20 px. Sur un grand
   écran, la marge visible n'est pas elle mais le reste de « --largeur ». */
.enveloppe{width:100%;max-width:var(--largeur);margin:0 auto;padding:0 20px;}
.section{padding:clamp(64px,9vw,116px) 0;}
.surtitre{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--accent-ink);
  margin-bottom:14px;
}
.chapeau{color:var(--text-muted);font-size:17.5px;max-width:62ch;margin-top:18px;}

/* ---------- Boutons ----------
   Le rayon est une pilule, et il l'est pour tout le site depuis le 15 octobre 2026.
   Il a été rectangulaire (« --radius-sm », 12 px), puis pilule sur la seule page
   d'accueil — la règle vivait dans « accueil.css », que les autres pages ne chargent
   pas —, et ce demi-pas a tenu une demi-journée.

   La raison du premier pas : la barre du haut de l'accueil est une pilule, et un
   rectangle à 12 px posé dans un objet à bord circulaire laisse entre les deux
   courbes un croissant qu'on voit. Puis l'argument a débordé de la barre, parce que
   « Créer mon profil » se lit quatre fois en descendant l'accueil et que le même
   bouton au même mot près, arrondi en haut et anguleux trois fois plus bas, se lit
   comme un défaut et non comme une variante.

   Ce qui a manqué ce jour-là, c'est que « le même bouton au même mot près » ne
   s'arrête pas à l'accueil : « Créer mon profil » est aussi dans la barre du blog,
   dans celle des articles, et dans le pavé bleu qui ferme chacune de ces pages. La
   portée « page d'accueil » était défendable en soi ; elle ne l'était plus dès qu'on
   la regardait depuis une autre page du site, et c'est de là que le propriétaire l'a
   regardée — « les boutons de ce bandeau doivent être de la même forme que les
   autres boutons ». Une forme qui dépend de la page où l'on se trouve n'est pas une
   forme, c'est un accident.

   La règle est donc ici, au seul endroit qui sert toutes les pages, et il n'y a plus
   de second endroit à tenir d'accord. « 999px » et non « 50% » : un pourcentage sur
   un bouton large dessine une ellipse, dont la courbure change avec la longueur du
   libellé. « --radius-sm » continue de servir ailleurs — champs, pastilles, petites
   cartes —, il n'est pas remplacé, il n'habille simplement plus les boutons. */
.bouton{
  display:inline-flex;align-items:center;gap:9px;
  padding:14px 24px;border-radius:999px;
  font-family:var(--texte);font-size:15px;font-weight:700;
  text-decoration:none;border:1.5px solid transparent;cursor:pointer;
  transition:transform .16s ease, box-shadow .16s ease, background .16s ease;
}
.bouton-plein{background:var(--accent);color:var(--on-accent);box-shadow:var(--shadow);}
.bouton-plein:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg);}
/* Le contour ne peut pas être --border : posé sur --page-bg (et non sur une carte
   blanche comme dans l'application), il disparaît. On le dérive du texte. */
.bouton-ligne{background:transparent;color:var(--text);border-color:color-mix(in srgb, var(--text) 26%, transparent);}
.bouton-ligne:hover{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-ink);}

/* ---------- En-tête ---------- */
/* Le voile de l'en-tête était de la couleur du fond, et c'était juste tant que le fond
   n'avait qu'une couleur. Depuis les lueurs, il n'en a plus : poser 88 % de la couleur
   de référence par-dessus un fond localement éclairci éteint cet éclaircissement, et
   l'en-tête se découpait en bande sombre sur la page — une couture horizontale en haut
   de l'écran, là où il n'y a rien à séparer tant que rien n'a défilé.
   L'en-tête est donc à nu tant qu'on est en haut. Le voile et le flou n'arrivent qu'avec
   « décollée », c'est-à-dire au moment où du contenu passe réellement dessous et où il
   faut bien quelque chose pour le retenir. La bordure arrive en même temps, et la bande
   se lit alors comme un en-tête, non comme un défaut. */
.entete{
  position:sticky;top:0;z-index:40;
  background:transparent;
  border-bottom:1px solid transparent;
  transition:border-color .2s ease;
}
/* Le voile du défilement est passé sur un pseudo-élément le 5 octobre 2026, et ce
   n'est pas une question de style : c'est la seule façon de garder le menu déroulant
   lisible depuis qu'il est lui-même translucide.

   Un élément qui porte « backdrop-filter » devient une *backdrop root*. Pour ses
   descendants, l'arrière-plan à filtrer n'est plus la page mais ce que cet élément a
   peint — c'est-à-dire presque rien. Le menu « Vous êtes » est un descendant de
   l'en-tête et retombe en dessous, en plein sur l'article : page défilée, son flou
   s'appliquait au vide et le titre du billet se lisait au travers du panneau, par-dessus
   « Un cabinet ». Deux textes superposés, dont aucun lisible.

   Le filtre descend donc sur un pseudo-élément, qui n'a pas de descendant à priver de
   quoi que ce soit. L'en-tête cesse d'être une backdrop root et le menu retrouve la page
   comme arrière-plan. À l'écran, la barre est identique — même couleur, même flou, même
   géométrie.

   « z-index:-1 » plutôt que de relever le contenu : l'en-tête porte déjà « z-index:40 »
   et forme donc un contexte d'empilement, dont un enfant négatif ne peut pas sortir. Le
   voile se peint au-dessus du fond de la barre et sous tout ce qu'elle contient, sans
   qu'aucun de ses enfants n'ait à être touché — ce qui importe, « .entete-int » étant le
   cadre de référence du panneau du téléphone.

   Rien n'avertit de ce piège : la valeur calculée du menu vaut bien « blur(22px) » dans
   les deux cas, et la console est muette. Cela ne se constate qu'à l'œil, page défilée et
   menu ouvert. */
.entete::before{
  content:"";position:absolute;inset:0;z-index:-1;
  background:transparent;
  transition:background-color .2s ease;
}
.entete.decollee{border-bottom-color:var(--border);}
.entete.decollee::before{
  background:color-mix(in srgb, var(--page-bg) 94%, transparent);
  backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
}
.entete-int{display:flex;align-items:center;justify-content:space-between;gap:24px;height:72px;}
.marque{
  display:flex;align-items:center;gap:10px;
  font-family:var(--display);font-weight:700;font-size:21px;letter-spacing:-0.02em;
  text-decoration:none;
}
.marque .o{color:var(--accent);}
/* ---------- Navigation ----------
   La barre ne liste plus les fonctions du produit mais les entrées du visiteur : le
   projet, ce qu'il est lui-même, les partenaires, le blog, la maison. La seule entrée
   qui se déplie est « Vous êtes », parce que c'est la seule qui pose une question dont
   la réponse change la suite — et qu'il valait mieux la poser dans la barre que laisser
   le visiteur deviner laquelle des deux moitiés de la page le concerne. */
/* L'écart tombe de 30 px à 4, et ce n'est pas un resserrement : les rubriques ont reçu
   un « padding » pour que la surbrillance de survol ait de la place où exister (voir
   plus bas). Vingt-huit pixels de remplissage plus quatre d'écart laissent les mêmes
   trente-deux pixels apparents entre deux libellés — l'œil ne voit pas la barre
   changer, seul le survol change. Sur téléphone le panneau remet « gap:0 » et son
   propre « padding », il n'est donc pas concerné. */
.nav{display:flex;align-items:center;gap:4px;}
/* « :not(.bouton) » parce que la barre contient aussi un bouton plein, celui que le
   téléphone récupère quand l'en-tête range le sien. Sans cette exclusion, la règle de
   couleur de la barre l'emporterait sur celle du bouton et le rendrait gris sur orange. */
.nav > a:not(.bouton),
.nav-tete{
  font-family:var(--texte);font-size:14.5px;font-weight:600;
  text-decoration:none;color:var(--text-muted);
  transition:color .16s ease, background-color .16s ease;
}
.nav > a:not(.bouton):hover,
.nav-tete:hover{color:var(--text);}

/* Le repli est un vrai bouton et non un lien : il n'emmène nulle part, il ouvre. Un
   lecteur d'écran annonce donc « bouton, replié », ce qui est exactement ce qui se
   passe, et la touche Entrée fait la même chose que le clic sans qu'on l'écrive. */
.nav-groupe{position:relative;display:flex;align-items:center;}
.nav-tete{
  display:inline-flex;align-items:center;gap:6px;
  background:none;border:0;padding:0;cursor:pointer;line-height:inherit;
}
.nav-tete svg{transition:transform .18s ease;}
.nav-groupe.ouvert .nav-tete{color:var(--text);}
.nav-groupe.ouvert .nav-tete svg{transform:rotate(180deg);}

/* ---------- La surbrillance de survol ----------
   Jusqu'au 5 octobre 2026, survoler une rubrique ne faisait que foncer son libellé, de
   « --text-muted » à « --text ». C'est un signal juste mais très faible — un gris qui
   devient un bleu sombre, sur une barre devenue elle-même à moitié transparente — et
   rien n'indiquait la **surface** cliquable : on devinait qu'un mot réagissait, pas
   jusqu'où il fallait viser. La surbrillance dit les deux à la fois.

   Elle a un prix : une surface de survol a besoin d'exister avant d'être peinte. Les
   rubriques n'avaient aucun « padding » — c'était du texte posé dans un « flex » à
   30 px d'écart — et un fond collé au plus près des lettres se lit comme un
   surlignage de traitement de texte, pas comme une cible. D'où les 9 × 14 px ici et
   la reprise de l'écart plus haut, les deux étant une seule décision.

   Le voile est de l'encre de la charte à 8 %, et non de l'ambre comme dans le menu
   déroulant. L'ambre dirait « sélectionné » ; on veut dire « sous le curseur ». Et
   surtout, l'encre neutre marche sur les deux barres du site — la pilule translucide
   de l'accueil, où un voile teinté prendrait la couleur de ce qui défile dessous, et
   la barre blanche opaque du blog.

   « 999px » pour suivre la pilule et ses boutons. Sur le blog, dont la barre est
   rectangulaire, la surbrillance reste ronde : c'est une cible, pas une pièce de la
   barre, et elle a la forme des autres cibles du site.

   Le survol n'est posé que sur un vrai pointeur. Sur un écran tactile « :hover » se
   déclenche au premier doigt et ne se relâche plus : la rubrique touchée resterait
   allumée pendant toute la visite. Le « padding » et le rayon, eux, sont hors de la
   requête — ils servent aussi à l'état « ouvert » et au focus clavier. */
.nav > a:not(.bouton),
.nav-tete{padding:9px 14px;border-radius:999px;}
@media (hover:hover){
  .nav > a:not(.bouton):hover,
  .nav-tete:hover{background:var(--survol);}
}
/* Le groupe dont le menu est déployé garde sa surbrillance : sans elle, le panneau
   ouvert ne serait plus rattaché visuellement à l'entrée qui l'a ouvert — le seul lien
   entre les deux étant l'arête gauche commune, qui ne suffit pas quand le curseur est
   descendu dans le menu et que le libellé, lui, est redevenu gris.
   Un peu plus dense que le survol simple, parce que les deux états se superposent :
   survoler le menu déployé ne doit pas éteindre son en-tête. */
.nav-groupe.ouvert .nav-tete,
.nav-groupe:focus-within .nav-tete{background:var(--survol-appuye);}

/* Le menu reste dans le flux de l'onglet (il n'est ni « display:none » ni « hidden »
   quand il s'ouvre) et on l'escamote par visibilité : c'est ce qui permet d'animer son
   arrivée. « visibility » est bien là, sans quoi un menu fermé mais transparent
   garderait ses liens cliquables au-dessus de la page. */
/* Le menu est calé sur le bord gauche du bouton, et non centré sous lui. Centré, il
   débordait des deux côtés et passait sous « Le projet » et sous « Partenaires » : on
   ne voyait plus de quelle entrée il descendait. Aligné à gauche, l'arête commune dit
   la filiation sans qu'on ait à dessiner quoi que ce soit. */
/* Le panneau est translucide depuis le 5 octobre 2026, pour la même raison que la
   pilule qui le porte : il posait un rectangle de blanc pur sur une page crème, et
   c'était devenu le seul objet entièrement opaque en haut de l'écran.

   Cinquante-cinq pour cent de blanc, plus un flou de 22 px. Le flou n'est pas
   facultatif, et c'est là toute la difficulté d'un menu transparent par rapport à une
   barre transparente : la barre ne couvre qu'un bandeau de page, le panneau tombe en
   plein sur du contenu — le titre du hero, la photographie, les appareils. Sans flou,
   on lirait « Un cabinet » par-dessus un mot du titre, et deux textes superposés ne
   se lisent ni l'un ni l'autre. Le flou réduit le dessous en plages de couleur, sur
   lesquelles les 55 % suffisent à poser un texte de 14,5 px.

   Les 55 % sont d'ailleurs plus hauts que les 52 de la barre, et ce n'est pas une
   incohérence : la barre ne contient que des libellés gras et courts, le panneau
   porte un sous-titre de 12,5 px en « --text-muted ». C'est ce sous-titre-là qui fixe
   le plancher.

   « saturate » pour la même raison que sur la barre — un flou seul délave le crème et
   le fait virer au gris. L'ombre s'allège de moitié : elle disait l'élévation d'une
   dalle opaque, elle doit maintenant la suggérer sous un objet qu'on voit à
   travers. */
.nav-menu{
  position:absolute;top:calc(100% + 14px);left:0;transform:translateY(-6px);
  min-width:242px;padding:7px;
  background:color-mix(in srgb, var(--surface) 55%, transparent);
  backdrop-filter:blur(22px) saturate(1.18);
  -webkit-backdrop-filter:blur(22px) saturate(1.18);
  border:1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius:var(--radius-md);
  box-shadow:0 14px 34px rgba(22,35,63,.09);
  display:flex;flex-direction:column;gap:3px;
  opacity:0;visibility:hidden;
  transition:opacity .16s ease, transform .16s ease, visibility .16s;
}
/* Le pont. Entre le bouton et le menu il y a quatorze pixels de vide : la souris les
   traverse, le survol s'interrompt, le menu se referme sous le curseur. Ce pseudo-élément
   comble l'intervalle sans rien dessiner — il n'existe que pour que le geste tienne. */
.nav-menu::before{content:"";position:absolute;left:0;right:0;top:-14px;height:14px;}
.nav-groupe.ouvert .nav-menu{opacity:1;visibility:visible;transform:translateY(0);}
/* Le soulignement vient de la règle générale des liens, où il est utile : dans un
   paragraphe, c'est lui qui distingue un lien d'un mot en gras. Ici l'entrée occupe
   déjà une ligne à elle, tient dans une pastille qui s'allume au survol, et porte un
   sous-titre — souligner les deux lignes ne dirait rien de plus et hacherait le menu.

   L'interligne, lui, doit être repris à la main. Le corps de la page est réglé à 1,62,
   ce qu'il faut pour lire un paragraphe de huit lignes ; appliqué à un libellé de deux
   mots, il posait 23 px de hauteur sous 14,5 px de caractères et faisait de chaque
   entrée un bloc de 65 px. Le menu mesurait 149 px de haut pour deux choix, et se
   lisait comme un paragraphe aéré plutôt que comme une liste où l'on désigne. */
.nav-menu a{
  display:block;padding:8px 11px;border-radius:9px;
  text-decoration:none;line-height:1.3;
  transition:background .14s ease, color .14s ease;
}
.nav-menu a b{display:block;color:var(--text);font-size:14.5px;font-weight:700;line-height:1.25;}
.nav-menu a span{display:block;font-size:12.5px;font-weight:500;color:var(--text-muted);line-height:1.3;margin-top:2px;}
/* Le voile de survol suit le panneau : « --accent-soft » plein sur un fond devenu
   translucide redevenait la seule surface opaque du menu, et l'entrée survolée se
   détachait plus fort qu'avant alors que tout le reste s'était allégé. */
.nav-menu a:hover,
.nav-menu a:focus-visible{background:color-mix(in srgb, var(--accent-soft) 72%, transparent);}
.nav-menu a:hover b,
.nav-menu a:focus-visible b{color:var(--accent-ink);}

/* ---------- La bascule du téléphone ----------
   La barre disparaissait sous 900 px et rien ne la remplaçait : sur un téléphone, le site
   n'avait pas de navigation du tout. C'était déjà discutable avec six liens de rubrique ;
   ce ne l'est plus du tout depuis que la barre dit au visiteur de quel côté il se trouve.
   La bascule n'apparaît donc qu'en dessous de ce seuil, et c'est le même balisage qui
   sert aux deux formats — la barre ne change pas, elle change de mise en page. */
.bascule{
  display:none;align-items:center;justify-content:center;
  width:42px;height:42px;flex:none;
  background:none;border:1.5px solid color-mix(in srgb, var(--text) 22%, transparent);
  border-radius:var(--radius-sm);color:var(--text);cursor:pointer;
}
.bascule:hover{border-color:var(--accent);}
.bascule .fermer{display:none;}
.entete.ouverte .bascule .ouvrir{display:none;}
.entete.ouverte .bascule .fermer{display:block;}
.entete .bouton{padding:11px 19px;font-size:14px;}
.nav-action{display:none;}
/* « Se connecter » à côté de « Créer mon profil » (17 octobre 2026) : les deux portes
   d'entrée de l'application, côte à côte à droite, et non plus le dernier lien de la
   barre centrée, loin du bouton. Il ne sert qu'à l'ordinateur : au téléphone, c'est le
   bouton « Connexion » collé à la bascule qui prend le relais (chapitre suivant). La marge
   négative ramène l'écart de l'en-tête (24 px) à 6 px entre les deux ; « margin-left:auto »
   le pousse contre le bouton quand la barre ne prend pas toute la place (blog). */
.entete-connexion{
  margin-left:auto;margin-right:-18px;padding:9px 14px;border-radius:999px;white-space:nowrap;
  font-family:var(--texte);font-size:14.5px;font-weight:600;
  text-decoration:none;color:var(--text-muted);
  transition:color .16s ease, background-color .16s ease;
}
.entete-connexion:hover{color:var(--text);}
@media (hover:hover){ .entete-connexion:hover{background:var(--survol);} }

/* ---------- Connexion ----------
   Demandé le 18 octobre 2026 : « ajouter un bouton connexion dans la barre en haut à
   gauche du burger ». Le libellé est écrit deux fois dans chaque page — l'un avant la
   bascule pour le téléphone, l'autre en queue de barre pour le grand écran — et chacun
   s'efface là où l'autre sert ; le raisonnement est dans le balisage, au-dessus de la
   bascule d'index.html.

   « .bouton-ligne » au départ, et non « .bouton-plein » : la barre contient déjà « Créer
   mon profil » en orange plein, et deux boutons de même poids côte à côte ne disent plus
   lequel est l'action principale. Le contour disait « c'est ici si vous avez déjà un
   compte » sans disputer la place au seul appel que la page défend.

   Arbitré autrement le 18 octobre 2026, le jour même : « met le en jaune (comme le bouton
   créer mon profil) ». Les deux exemplaires portent donc « .bouton-plein ». Le
   raisonnement ci-dessus n'était pas faux, il était secondaire : au téléphone, un contour
   clair posé à côté d'une bascule elle-même en contour faisait deux formes creuses
   voisines, et « Connexion » se lisait comme un deuxième bouton de menu plutôt que comme
   une entrée dans l'application. L'orange le sort de la barre d'outils.

   Ce qu'il coûte est au grand écran, et il faut le savoir : « Connexion » et « Créer mon
   profil » y sont à 24 px l'un de l'autre, maintenant de même poids, et la hiérarchie
   entre « revenir » et « s'inscrire » ne tient plus qu'à l'ordre de lecture. Si elle doit
   revenir, c'est l'exemplaire du grand écran qu'on repasse en « .bouton-ligne » : il a sa
   propre balise dans chacune des quatre pages, précisément pour que cet arbitrage puisse
   se refaire d'un côté sans toucher l'autre. */
.connexion-barre{display:none;}

@media (max-width:900px){
  .bascule{display:inline-flex;}
  /* ---------- La barre à trois occupants ----------
     Elle en avait deux au téléphone, la marque et la bascule, et elle était large.
     « Connexion » en fait un troisième, et à 320 px le compte ne tombe plus juste :
     posé tel quel, avec le remplissage commun des boutons de barre, le contenu réclamait
     293,9 px pour 260 disponibles, et la bascule finissait 33,9 px au-delà du bord
     intérieur de la pilule — elle sortait de la gouttière pour aller se coller au bord de
     l'écran, ce qu'aucune autre ligne de la page ne fait. La page, elle, ne débordait
     pas : la pilule de l'en-tête masque ce qui dépasse, et le défaut ne se voyait donc
     qu'en mesurant, pas en comparant « scrollWidth » et « innerWidth ».

     Ces 33,9 px sont pris sur quatre postes, comme pour l'interrupteur du profil quelques
     jours plus tôt, et pour la même raison : aucun ne les rendait seul sans se voir.
     Toutes les colonnes ci-dessous sont mesurées dans la page, après coup.

                            posé tel quel   320 px   375 px   390 px   plafond
       corps de la marque ..    21 px        18       21       21        21
       la marque entière ...   111,2 px      99,7    111,2    111,2     111,2
       écart du logo au mot     10 px         8,96    10       10        10
       corps de « Connexion »   14 px        12,64    13,5     13,5      13,5
       côtés du bouton .....    19 px        10,88    12,75    13,26     16
       le bouton entier ....   112,7 px      89,5     97,6     98,6     104,1
       écart de la barre ...    14 px        10,24    12       12,48     14
       — demandé ...........   293,9        251,6    274,8    276,8       —
       — disponible ........   260          260      315      330         —
       — libre .............   -33,9          8,4     40,2     53,2       —

     La marque à 18 px est le plus gros poste et le moins coûteux : un logotype qui
     respire moins sur un écran de 320 px reste un logotype, là où un bouton rogné de
     23 px commence à ressembler à une étiquette.

     La colonne « plafond » n'est pas atteinte partout au même endroit, et le tableau le
     dit mieux que la phrase qu'il remplace : le corps de la marque est entier dès 375 px,
     son écart dès 357, le corps du bouton dès 342, l'écart de la barre dès 438, mais les
     côtés du bouton ne cessent de grandir qu'à 471 px. Un téléphone courant voit donc
     encore un bouton très légèrement resserré — 98,6 px au lieu de 104,1 à 390 px — et
     c'est sans conséquence : les 5,5 px manquants sont du vide à gauche et à droite du
     mot, pas du mot.

     Deux rédactions de ce tableau ont été fausses avant celle-ci, et c'est noté parce que
     c'est deux fois la même erreur. La première annonçait 280,3 px demandés pour 262
     disponibles : 262 oubliait les 1 px de bordure de chaque côté de la pilule, et 280,3
     était une addition faite à la main sur des valeurs supposées. La seconde prévoyait
     247,4 px demandés et 14,6 de libre à 320 px, avec des pentes de « clamp » qui
     n'atteignaient jamais leur plancher — « clamp(10px,4.3vw,16px) » vaut 13,76 à 320 px,
     pas 10 — et la mesure donnait 258,2 pour 3,8 de libre. Les pentes ont été refaites
     (4,3vw devient 3,4vw, 4vw devient 3,95vw) pour que le plancher serve vraiment. Rien
     dans ce fichier ne doit être écrit avant d'avoir été lu dans la page. */
  .entete-int{gap:clamp(8px,3.2vw,14px);}
  .marque{font-size:clamp(18px,5.6vw,21px);gap:clamp(7px,2.8vw,10px);}
  /* Hauteur et remplissage repris à la main : le bouton de barre fait 47,7 px avec le
     remplissage commun (« .entete .bouton », 11 px de haut, 19 de côté, corps 14), la
     bascule en fait 42, et deux commandes voisines de hauteurs différentes se lisent
     comme un défaut d'alignement et non comme deux formes. Les 42 px les mettent sur
     la même ligne de base ; les côtés et le corps resserrés rendent les pixels qui
     manquaient à 320. Mesuré après coup, le bouton passe de 112,7 à 104 px de large.

     Quarante-deux pixels, c'est sous les 44 recommandés pour une cible tactile. C'est
     assumé : la bascule les fait déjà et depuis plus longtemps, et un bouton de 48 px
     à côté d'une bascule de 42 réglerait la norme en cassant la barre. Les deux sont
     à reprendre ensemble, avec le reste des cibles trop petites de la page.

     Écrit « .entete .connexion-barre » et non « .connexion-barre » : « .entete .bouton »
     pose déjà le remplissage et le corps de tous les boutons de la barre, avec deux
     classes contre une. La première rédaction n'a donc changé que la hauteur, pour
     laquelle rien ne la contredisait — le bouton est passé à 42 px de haut en restant
     à 112,7 de large, et le gain attendu n'existait pas. Deux classes ici aussi, plus
     bas dans le fichier : la règle passe devant.

     « margin-left:auto » depuis le 18 octobre 2026 : « le bouton connexion doit être plus
     à droite juste à côté du burger ». La barre répartit ses trois occupants par
     « space-between », et les 8,4 px libres de 320 px se coupaient donc en deux : 4,2
     avant le bouton, 4,2 après. Le bouton flottait entre la marque et la bascule sans
     appartenir ni à l'une ni à l'autre, et au-delà de 375 px, où le libre monte à 40 puis
     53 px, les deux moitiés devenaient franchement visibles — vingt pixels de vide de
     chaque côté. Une marge automatique est servie avant que « space-between » n'ait quoi
     que ce soit à répartir : tout le vide passe à gauche, et les deux commandes se
     retrouvent à l'écart de la barre l'une de l'autre, quelle que soit la largeur.

     Écrit ici plutôt que sur « .entete-int » en « justify-content:flex-start » : la barre
     sert aussi le grand écran, où « space-between » tient les quatre occupants, et une
     marge posée sur l'enfant ne vaut que dans la requête qui la porte. */
  .entete .connexion-barre{
    display:inline-flex;height:42px;margin-left:auto;
    padding:0 clamp(10px,3.4vw,16px);
    font-size:clamp(12.6px,3.95vw,13.5px);
  }
  /* L'en-tête range son bouton et le panneau le reprend en bas, après les rubriques.
     Ce n'est pas qu'une question de place : le bouton est le seul élément de l'en-tête
     qui vienne après la barre dans le balisage, et le laisser là mettait le panneau
     ouvert avant lui dans l'ordre de tabulation — on ouvrait la navigation au clavier
     pour atterrir aussitôt après elle. En le déplaçant dans le panneau, l'ordre des
     balises, l'ordre à l'écran et l'ordre du clavier redeviennent le même.

     « :not(.connexion-barre) » depuis le 18 octobre 2026. La règle disait « le bouton »
     au singulier parce qu'il n'y en avait qu'un ; la barre en compte trois depuis que
     « Connexion » s'y est posé en deux exemplaires, et « .entete-int > .bouton »
     l'emportait sur « .connexion-barre » par sa spécificité — le bouton neuf était
     écrit, estampillé, et invisible. L'exception est posée ici plutôt que compensée
     par une règle plus forte ailleurs : c'est à l'endroit qui range les boutons de la
     barre qu'on doit lire lequel reste.

     Deux partent donc, et un seul reste. Celui qui reste est « Connexion » version
     téléphone ; ceux qui partent sont « Créer mon profil », que le panneau reprend
     plus bas, et « Connexion » version grand écran, qui n'a pas d'autre règle que
     celle-ci — la ligne ci-dessous est tout ce qui le cache au téléphone. */
  .entete-int > .bouton:not(.connexion-barre){display:none;}
  /* « Se connecter » du grand écran laisse la place au bouton « Connexion » de la barre :
     le panneau n'en reprend pas de troisième. */
  .entete-connexion{display:none;}
  .nav-action{display:inline-flex;justify-content:center;margin-top:16px;}
  /* Le panneau est accroché à l'en-tête, pas à la page : il suit la barre collante et ne
     laisse jamais le visiteur à côté du bouton qui l'a ouvert. */
  .nav{
    position:absolute;top:100%;left:0;right:0;
    flex-direction:column;align-items:stretch;gap:0;
    padding:10px 20px 18px;
    background:var(--page-bg);border-bottom:1px solid var(--border);
    box-shadow:var(--shadow-lg);
    display:none;
  }
  .entete.ouverte .nav{display:flex;}
  .nav > a:not(.bouton),
  .nav-tete{padding:13px 2px;font-size:16px;}
  .nav > a:not(.bouton){border-bottom:1px solid var(--border);}
  .nav-groupe{display:block;border-bottom:1px solid var(--border);}
  /* Sur un téléphone, le repli n'a plus de raison d'être : il n'y a pas de survol pour
     l'ouvrir, et la place manque bien moins en hauteur qu'en largeur. Les deux réponses
     sont donc posées à plat sous la question, et le chevron s'efface. */
  .nav-tete{width:100%;cursor:default;}
  .nav-tete svg{display:none;}
  /* La surbrillance ne vaut rien ici et nuirait : les rubriques sont des lignes
     pleine largeur séparées par des filets, le panneau est déjà une surface à part,
     et « :focus-within » allumerait l'en-tête d'un groupe parce qu'on a touché un de
     ses deux liens — un retour visuel qui désigne le mauvais élément. */
  .nav > a:not(.bouton),
  .nav-tete{border-radius:0;}
  .nav-groupe.ouvert .nav-tete,
  .nav-groupe:focus-within .nav-tete{background:none;}
  .nav-menu{
    position:static;transform:none;min-width:0;padding:0 0 10px 14px;
    background:none;border:0;box-shadow:none;
    opacity:1;visibility:visible;gap:4px;
  }
  .nav-menu::before{display:none;}
  .nav-menu a{padding:8px 10px;}
  /* La page ne doit pas défiler derrière le panneau ouvert. */
  body.panneau-ouvert{overflow:hidden;}
}
/* Au-dessus du seuil, le menu ne s'ouvre au survol que si le pointeur en est un : sur un
   écran tactile, « :hover » se déclenche au premier doigt et ne se relâche jamais. */
@media (min-width:901px) and (hover:hover){
  .nav-groupe:hover .nav-menu,
  .nav-groupe:focus-within .nav-menu{opacity:1;visibility:visible;transform:translateY(0);}
  /* Le chevron dit l'état du menu. Ouvert au survol sans qu'il bouge, il pointerait
     encore vers le bas devant un menu déjà déplié. */
  .nav-groupe:hover .nav-tete,
  .nav-groupe:focus-within .nav-tete{color:var(--text);}
  /* Le survol du groupe, et non du seul bouton : le menu s'ouvre au survol et le
     curseur y descend aussitôt. Accroché à « .nav-tete:hover », la surbrillance
     s'éteindrait au moment même où le visiteur se sert du menu qu'elle annonce. */
  .nav-groupe:hover .nav-tete{background:var(--survol-appuye);}
  .nav-groupe:hover .nav-tete svg,
  .nav-groupe:focus-within .nav-tete svg{transform:rotate(180deg);}
}
@media (min-width:901px){
  .nav-groupe:focus-within .nav-menu{opacity:1;visibility:visible;transform:translateY(0);}
  .nav-groupe:focus-within .nav-tete svg{transform:rotate(180deg);}
}

/* Le goniomètre de la marque : l'aiguille se repose sur son axe. */
.aiguille{
  transform-box:view-box;transform-origin:60px 86px;
  transition:transform 1.5s cubic-bezier(.34,1.28,.42,1);
}
@media (prefers-reduced-motion: reduce){ .aiguille{transition:none;} }

/* ---------- Les deux restes du hero ----------
   Ce qui subsiste de l'ancien hero en deux colonnes, retiré le 4 octobre 2026 avec
   la page qui le portait. Ces deux classes-là servent encore, et sur toutes les
   pages : la rangée de boutons se retrouve au bas de chaque chapitre de l'accueil
   et dans les quatre articles du blog, la mention juste sous un titre.

   Les quatre autres règles du bloc — « .hero », « .hero-grille », « .hero-texte »
   et « .hero h1 .lueur » — ne visaient plus aucun élément d'aucune page : la
   nouvelle page d'accueil compose son hero avec « .accueil-hero », dans sa propre
   feuille. Elles sont parties avec le côte-à-côte ; le détail est dans
   « Une seule page d'accueil », dans « outils/capturer-demo.md ».
   Le dégradé du titre n'est pas perdu pour autant : « .accueil-hero-texte h1 .lueur »
   le reprend dans « accueil.css ». C'est la seule des quatre qui avait un effet
   visible, et c'est la raison pour laquelle elle a été vérifiée avant la coupe. */
.hero-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:32px;}
.hero-note{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--text-muted);margin-top:22px;
}

/* ---------- Bandeau de faits ---------- */
.bandeau{border-top:1px solid var(--border);border-bottom:1px solid var(--border);background:var(--bg);}
.bandeau-grille{
  display:grid;grid-template-columns:repeat(4,1fr);
  gap:1px;background:var(--border);
}
.fait{background:var(--bg);padding:26px 20px;text-align:center;}
.fait b{display:block;font-family:var(--display);font-size:30px;line-height:1;margin-bottom:7px;}
.fait span{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);}
@media (max-width:760px){ .bandeau-grille{grid-template-columns:repeat(2,1fr);} }

/* ---------- Cartes ---------- */
/* « .cartes.trois » — une grille de trois colonnes qui repassait à une seule
   sous 980 px — est retirée le 15 octobre 2026 avec la section « À propos »,
   qui en était la dernière et désormais la seule utilisatrice : ses trois
   cartes (« ce que Kinôme fait », « ne fera pas », « qui le porte ») étaient le
   seul endroit du site à demander trois colonnes. Comptage fait sur l'accueil
   et sur les cinq pages du blog avant de retirer les deux lignes.
   « .cartes » et « .cartes.deux » restent : la section 5 s'en sert toujours.
   Une règle sans sélecteur correspondant ne coûte pas un pixel, mais elle coûte
   une hésitation au prochain qui la lira et cherchera ce qu'elle habille. */
.cartes{display:grid;gap:22px;margin-top:44px;}
.cartes.deux{grid-template-columns:repeat(2,1fr);}
@media (max-width:880px){ .cartes.deux{grid-template-columns:1fr;} }
.carte{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:32px;
  transition:transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.carte:hover{transform:translateY(-3px);box-shadow:var(--shadow);border-color:var(--accent);}
.carte-icone{
  width:44px;height:44px;border-radius:13px;
  display:flex;align-items:center;justify-content:center;
  background:var(--accent-soft);color:var(--accent-ink);margin-bottom:18px;
}
.carte p{color:var(--text-muted);font-size:15.5px;margin-top:10px;}
.carte-liste{margin-top:18px;display:flex;flex-direction:column;gap:9px;}
.carte-liste li{display:flex;gap:10px;font-size:14.5px;color:var(--text-muted);}
.carte-liste li::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--accent);margin-top:9px;}

/* ---------- Étapes ---------- */
.etapes{display:grid;grid-template-columns:repeat(4,1fr);gap:24px;margin-top:48px;counter-reset:etape;}
@media (max-width:880px){ .etapes{grid-template-columns:1fr;} }
.etape{position:relative;padding-top:26px;border-top:2px solid var(--grille);counter-increment:etape;}
.etape::before{
  content:counter(etape,decimal-leading-zero);
  position:absolute;top:-13px;left:0;
  font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.1em;
  /* Le numéro chevauche le filet du haut : il doit peindre le fond de SA section
     (--bg), pas celui de la page, sinon on voit un rectangle plus foncé. */
  background:var(--bg);color:var(--accent-ink);padding-right:12px;
}
.etape h3{margin-bottom:8px;}
.etape p{color:var(--text-muted);font-size:15px;}

/* ---------- Compatibilité ---------- */
.compat{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;}
@media (max-width:900px){ .compat{grid-template-columns:1fr;gap:36px;} }
/* Le tableau des poids — cinq critères, cinq barres qui se remplissaient — a quitté
   le premier temps du parcours, remplacé par l'écran du fil. Ses règles sont parties
   avec lui, et « .poids » tout court avec elles : la classe existe encore, mais dans
   la démonstration, où elle nomme le poids d'une pièce jointe. Les deux se sont
   croisées sans qu'on le voie — « .poids » posait ici une carte de trente pixels de
   marge intérieure, avec bordure et ombre, sur chaque « 180 Ko » du prototype, que
   « .appli .msg-piece .poids » repeignait sans jamais défaire la boîte. Rien dans
   l'application n'a cette carte ; c'est la vitrine qui la lui ajoutait. */

/* ---------- Dossier ----------
   Vide depuis le 14 octobre 2026. Les sept classes qui tenaient ici dessinaient le
   tableau des cinq étapes du recrutement — numéro, titre, description, qui doit agir
   — dans le troisième temps de l'accueil. Ce tableau énumérait en mots un parcours que
   les deux premiers temps montrent maintenant en écrans relevés du produit ; il est
   parti avec le texte qu'il accompagnait. Son identifiant « #dossier », lui, reste sur
   le volet : la barre du haut et le pied de page y mènent. */

/* ---------- Confiance ----------
   Il ne reste rien ici, et c'est la fin d'une démolition en deux temps. « .badges » et
   sa pastille, « .regle » et son pavé orange sont partis le 13 octobre 2026 avec le
   contenu qu'ils habillaient : quatre vérifications et une règle de confidentialité,
   remplacées par l'application montrée sur un ordinateur. Le fond, les deux filets et
   le grain de surface les ont suivis le 14 : c'était l'habillage d'une bande pleine
   largeur, et « .confiance » n'est plus une bande — c'est un volet du parcours, posé
   dans le même encart que les trois autres. Ses trois frères n'ont ni fond ni filets ;
   lui seul en avait, et cela se voyait comme une couture au milieu du sélecteur.

   Le grain partait avec, nécessairement : il était là pour rendre au fond opaque la
   trame que le corps de page porte derrière lui. Sans fond, il la redouble.

   Le nom survit dans la feuille d'accueil, qui s'en sert pour la mise en colonnes —
   c'est pourquoi la classe reste dans le balisage. Et ce que les règles parties
   disaient n'est pas perdu : la vérification du SIRET est dans les arguments du
   cabinet, la règle d'anonymat dans la FAQ. */

/* ---------- FAQ ---------- */
.faq{margin-top:40px;border-top:1px solid var(--border);}
.faq details{border-bottom:1px solid var(--border);}
.faq summary{
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding:22px 4px;cursor:pointer;list-style:none;
  font-family:var(--display);font-weight:700;font-size:17px;
}
.faq summary::-webkit-details-marker{display:none;}
.faq summary::after{
  content:"+";font-family:var(--mono);font-size:20px;color:var(--accent-ink);
  flex:none;transition:transform .2s ease;
}
.faq details[open] summary::after{transform:rotate(45deg);}
.faq p{padding:0 4px 24px;color:var(--text-muted);font-size:15.5px;max-width:78ch;}

/* ---------- Appel final ---------- */
.final{
  margin:clamp(64px,9vw,110px) auto;
  background:var(--text);color:var(--bg);
  border-radius:var(--radius-lg);
  padding:clamp(44px,6vw,76px);text-align:center;
  box-shadow:var(--shadow-lg);
}
.final h2{color:var(--bg);}
.final p{color:color-mix(in srgb, var(--bg) 72%, transparent);margin:18px auto 0;max-width:52ch;}
.final .hero-actions{justify-content:center;}
.final .bouton-ligne{color:var(--bg);border-color:color-mix(in srgb, var(--bg) 34%, transparent);}
.final .bouton-ligne:hover{background:color-mix(in srgb, var(--bg) 12%, transparent);color:var(--bg);border-color:var(--bg);}
/* Sur un petit téléphone, la carte finale gardait 44 px de marge intérieure de chaque
   côté : il restait 192 px pour les boutons à 320 px de large, et « Voir comment ça
   marche » (228 px) se cassait sur deux lignes — une pilule à deux étages. Constaté
   le 16 octobre 2026, sur le blog et l'article. Sous 360 px — et seulement là : au-
   dessus, le bouton tient et la page reste exactement celle du prototype —, la carte
   (et l'encadré d'appel de l'article, plus bas avec ses règles) rendent de la marge
   aux côtés, et les boutons d'action un peu
   de rembourrage : le libellé tient sur une ligne dès 320 px. En deçà (280 px, un
   pliable fermé), il passe à la ligne, centré, plutôt que de déborder. */
@media (max-width:359px){
  .final{padding-left:22px;padding-right:22px;}
  .hero-actions .bouton{padding-left:16px;padding-right:16px;justify-content:center;text-align:center;}
}

/* ---------- Pied ---------- */
.pied{border-top:1px solid var(--border);padding:38px 0 46px;}
.pied-int{display:flex;flex-wrap:wrap;gap:20px;align-items:center;justify-content:space-between;}
.pied-liens{display:flex;flex-wrap:wrap;gap:22px;}
.pied a{font-size:14px;color:var(--text-muted);text-decoration:none;}
.pied a:hover{color:var(--text);}
.pied small{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--text-muted);}

/* Apparition au défilement — désactivée si l'utilisateur préfère le calme. */
/* L'état masqué est conditionné à « .js », posée par un script en ligne dans le
   <head> de chaque page. Sans cette garde, « opacity:0 » s'applique toujours et
   c'est JavaScript qui doit venir dévoiler le contenu : si le script échoue, est
   bloqué, ou si IntersectionObserver ne se déclenche pas, la page reste vide.
   La garde inverse la dépendance — sans JavaScript le contenu s'affiche
   normalement, et l'animation n'est qu'un supplément. Le script est en ligne et
   non différé pour que la classe soit posée avant le premier rendu : autrement le
   contenu apparaîtrait puis disparaîtrait avant d'être ré-animé. */
.js .apparait{opacity:0;transform:translateY(18px);transition:opacity .6s ease, transform .6s ease;}
.js .apparait.visible{opacity:1;transform:none;}
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  .js .apparait{opacity:1;transform:none;transition:none;}
}

/* ==========================================================================
   Démonstration — l'application elle-même, dans ses deux formats
   ==========================================================================
   Rien n'est redessiné ici. Le balisage posé dans la page et la feuille
   assets/app.css sont relevés sur le prototype par outils/assembler-demo.py :
   ce sont les classes, les profils et les écrans réels de Kinôme. La seule
   chose que ce fichier ajoute, c'est le mouvement — l'application n'a pas de
   raison d'enchaîner les profils toute seule, une vitrine si.

   Une imitation dessinée à la main, c'est ce qu'il y avait avant : elle
   vieillissait à chaque retouche du produit et ne montrait que ce que son
   auteur avait retenu. Corriger la démonstration se fait donc dans
   l'application, puis on recapture.

   La boucle dure 9 s : trois fiches déphasées de 3 s, donc un jugement toutes
   les 3 s — à 2,0 s, 5,0 s et 8,0 s. Les impulsions des deux boutons et le
   balayage des aiguilles sont calés sur ces instants ; changer la durée oblige
   à recalculer tous les pourcentages ci-dessous.
   ========================================================================== */

/* La démonstration est un décor : rien n'y est cliquable. Sans cela, les
   boutons de l'application capturée invitent à un geste qui ne mène nulle part,
   et le curseur change de forme sur une maquette. */
.demo{pointer-events:none;user-select:none;}
/* La démonstration est posée dans la page par le script d'assemblage, qui ne connaît
   rien de la mise en page de la vitrine. Elle n'a plus pour autant à porter l'enveloppe
   elle-même : « .hero-grille » l'entoure désormais et tient déjà la largeur maximale, le
   centrage et la gouttière. Les lui redonner ici en faisait une seconde enveloppe à
   l'intérieur de la première — quarante-huit pixels de gouttière en double, qui ne se
   voyaient pas puisque la scène est centrée, mais qui manquaient là où ils comptent :
   à 901 px, juste avant que le cadre d'ordinateur ne disparaisse, c'est la largeur où
   la composition est le plus à l'étroit. */
.demo{width:100%;}
/* La scène se serre sur le cadre de l'ordinateur, qui se serre lui-même sur la
   fenêtre réduite de l'application. Sans cela le cadre s'étire jusqu'aux bords de
   l'enveloppe et l'application flotte dans une fenêtre trop grande pour elle — et
   le téléphone, calé sur la droite de la scène, se pose à côté de l'écran au lieu
   de se poser dessus. */
.scene{position:relative;width:fit-content;margin:clamp(40px,6vw,66px) auto 0;}

/* ---------- Habillage : le cadre du navigateur et celui du téléphone ----------
   Ces deux cadres appartiennent à la vitrine, pas au produit : ils situent
   l'application sans prétendre en faire partie. Tout ce qui est à l'intérieur,
   en revanche, vient du prototype. */
/* L'ordinateur est une machine, pas une fenêtre. La première version posait une
   simple barre de navigateur au-dessus de l'application et s'arrêtait là : à côté
   d'un téléphone dessiné avec son corps, sa lunette et ses angles, l'écran
   d'ordinateur flottait sans rien autour. Les deux objets n'étaient pas du même
   monde — l'un était un appareil posé sur un bureau, l'autre une capture d'écran.

   Le capot et le socle sont donc dessinés, et dans le même matériau que le
   téléphone : « var(--text) », c'est-à-dire l'encre de la page. On ne cherche pas
   l'imitation d'une marque — ni reflets, ni dégradé de métal, ni logo — mais la
   silhouette, qui suffit à dire « ordinateur » et reste muette sur le reste.

   Dessiner la silhouette ne suffisait pas : la version suivante avait son capot, sa
   caméra, son socle et son encoche, et ne ressemblait toujours pas à un ordinateur.
   La raison tient en un chiffre. Sa dalle mesurait 645 × 584, soit un rapport de
   1,10 — un carré. Aucune machine n'a jamais eu un écran carré : l'œil ne lit pas
   une lunette, il lit une proportion, et celle-là disait « encadré », pas
   « portable ». Tous les détails du monde ne rattrapent pas un format faux.

   La dalle est donc en 16/10 : 1512 × 945, pour une fenêtre d'application de 1512 × 893.
   Ces nombres ne sont pas choisis pour tomber juste — ils se déduisent, dans cet ordre, du
   plancher de hauteur de la page et de la hauteur du chrome. Voir « La proportion de la
   dalle » plus bas pour la déduction et pour la façon dont elle se tient à toutes les
   échelles. */
.cadre-ordi{display:flex;flex-direction:column;align-items:center;}

/* ---------- Une seule variable pour tout le châssis ----------
   Toutes les pièces de la machine — lunette, menton, caméra, socle, encoche, barre de
   titre — se mesurent en multiples de « --chassis », et c'est la correction de
   fond de cette version.

   Elles étaient auparavant en pixels fixes, ce qui marche tant qu'on ne touche pas à
   l'échelle et se dégrade silencieusement dès qu'on y touche. Chaque fois que la
   machine rétrécissait, la dalle suivait et les pièces, non : à force, le menton
   pesait huit pour cent de la hauteur d'écran et la barre d'adresse six. C'est très
   exactement ce qu'on appelle un dessin grossier — non pas des pièces mal dessinées,
   mais des pièces à la mauvaise échelle les unes par rapport aux autres. Le défaut ne
   se corrige pas en retouchant une valeur, puisqu'il revient au palier suivant.

   La règle est donc : aucune longueur de châssis en dur. Mieux qu'un facteur abstrait,
   « --chassis » porte **l'échelle elle-même**, et chaque pièce est écrite dans le
   repère de la machine — celui des 1512 × 945 de sa dalle. Une lunette de 18 px, un
   menton de 52, une barre de titre de 52 : ce sont les cotes qu'aurait la pièce sur
   l'écran réel, et il n'y a plus qu'à les multiplier par l'échelle à laquelle on regarde
   la machine. C'est aussi ce qui a permis de remplacer tout le chrome sans rien
   recalculer : des cotes de plus dans le même repère.

   Deux choses en découlent, et la seconde est la plus utile.

   Les cotes deviennent lisibles, donc vérifiables : « 52 px de chrome sur une dalle de
   945 » se compare à la barre de titre d'une fenêtre d'application web — même ordre, 5,5 %
   de la hauteur — là où « 22 px » ne se comparait à rien. C'est ce qui avait permis de voir
   que la toute première barre était deux fois trop fine ; tant que les cotes étaient en
   pixels d'écran, le défaut était invisible.

   Et le 16/10 devient **exact par construction**. La dalle mesure 1512·z de large et
   893·z + 52·z = 945·z de haut : le rapport ne dépend plus de l'échelle, il vaut 1,600
   à tous les paliers. C'est ce que la version précédente n'arrivait pas à tenir — sa
   barre figée à 22 px faisait dériver le rapport de 1,576 à 1,615 selon le palier, et
   il fallait un garde-fou dans la vérification pour surveiller cette dérive. Le
   garde-fou reste, mais il n'a plus rien à rattraper.

   Deux exceptions assumées, et une seule famille de raisons. Les deux textes de système —
   le nom dans la barre de titre du Mac, l'heure dans celle de l'iPhone — ont un plancher
   en « max() », parce qu'on vient les lire. Le filet sous le chrome reste à 1 px, parce
   qu'une arête mise à l'échelle disparaît. Aucune des trois n'entre dans la hauteur de la
   dalle, et c'est la condition pour que « exact par construction » reste vrai. */

/* Le capot porte la lunette. Elle est plus épaisse en bas qu'en haut — trois fois —
   comme sur une machine réelle, et c'est cette asymétrie qui fait lire un objet
   plutôt qu'un rectangle : une lunette régulière donne un cadre photo.
   Les angles du bas sont moins ronds que ceux du haut : c'est le côté par lequel le
   capot rejoint le socle, et un coin franc y dit la charnière. */
/* Les teintes du matériel, et elles ne sont pas prises dans la palette de la marque.
   Les deux appareils étaient bleu nuit, c'est-à-dire de la couleur du texte : cohérent
   sur le papier, mais un ordinateur couleur de marque se lit comme un pictogramme, pas
   comme un objet. En aluminium, il devient un appareil posé là — et la page entière
   s'éclaircit, ce qui était la demande.

   Ces quatre valeurs sont donc des couleurs de matière et non des jetons de thème :
   elles ne varient pas, parce qu'un MacBook ne change pas de couleur avec le site.
   C'est aussi pourquoi elles vivent ici, sur « .scene », et non dans « :root » — les
   mettre dans la palette inviterait à s'en servir ailleurs, où elles ne voudraient
   plus rien dire.

   « .accueil-tel » est le second porteur depuis que le premier temps du parcours
   montre lui aussi un téléphone. Il est nommé à côté de « .scene » et non servi
   par une copie des quatre valeurs dans « accueil.css » : ce sont les mêmes
   matières sur le même appareil, et deux listes de couleurs finissent toujours
   par diverger d'un ton. La règle reste fermée — on y ajoute un porteur quand il
   y a vraiment un châssis de plus, pas quand une couleur grise manque ailleurs.
   « .accueil-ordi » est le troisième, et il remplit la condition au sens strict :
   le deuxième temps du parcours porte une machine entière — capot, lunette, socle,
   encoche —, c'est-à-dire exactement les pièces que ces quatre valeurs peignent. */
.scene,
.accueil-tel,
.accueil-ordi{
  --alu:#d7d9dd;        /* le corps, en aluminium brossé */
  --alu-haut:#eceef1;   /* l'arête du haut, qui prend la lumière */
  --alu-bas:#b4b8bf;    /* le bas, dans son ombre propre */
  --lunette:#0c0d10;    /* la lunette noire qui entoure la dalle */
}
.ordi-capot{
  position:relative;
  padding:calc(18px * var(--chassis)) calc(18px * var(--chassis)) calc(52px * var(--chassis));
  border-radius:calc(24px * var(--chassis)) calc(24px * var(--chassis)) calc(13px * var(--chassis)) calc(13px * var(--chassis));
  background:linear-gradient(180deg, var(--alu-haut) 0%, var(--alu) 18%, var(--alu-bas) 100%);
  box-shadow:var(--shadow-lg);
}
/* La lunette noire, et c'est un calque et non une bordure — la distinction est la
   seule chose à retenir de cette règle.

   Sur l'appareil réel, l'aluminium du capot ne se voit de face que par un liseré de
   deux ou trois millimètres : tout le reste du plan avant, menton compris, est noir.
   Le réflexe serait donc de poser « border:9px solid var(--lunette) » sur la dalle.
   Il ne faut pas : la dalle est tenue en 16/10 exact par construction — 1512 × 945,
   voir le calcul de la barre de titre plus bas — et une bordure entre dans sa
   boîte. Neuf pixels de chaque côté feraient dériver le rapport, et la vérification
   ne saurait plus ce qu'elle surveille.

   Un calque posé en absolu ne prend aucun pixel de boîte. Il occupe le capot entier,
   rentré du liseré d'aluminium, et la dalle se contente de passer par-dessus : d'où le
   « position:relative » qui la suit, qui ne sert qu'à la soulever dans l'ordre de
   peinture. Les angles sont ceux du capot moins le liseré, sans quoi on verrait du
   noir dépasser dans les coins. */
.ordi-capot::before{
  content:"";position:absolute;inset:calc(3.5px * var(--chassis));
  border-radius:calc(21px * var(--chassis)) calc(21px * var(--chassis)) calc(10px * var(--chassis)) calc(10px * var(--chassis));
  background:var(--lunette);
}
/* La caméra, au milieu du bandeau du haut — c'est peu, et c'est précisément ce qui
   manque à un rectangle pour devenir un écran. Au point où elle en est, elle ne se
   lit plus comme un objectif mais comme une ponctuation : c'est tout ce qu'on lui
   demande, et une caméra qu'on remarque serait déjà trop grosse. */
.ordi-camera{
  position:absolute;top:calc(6.75px * var(--chassis));left:50%;transform:translateX(-50%);
  width:calc(8px * var(--chassis));height:calc(8px * var(--chassis));border-radius:50%;
  /* Elle se centre désormais sur la lunette, qui va de 3,5 à 18 : son milieu est à
     10,75, d'où ce « top » une fois retirée la moitié des 8 px du point. Elle était
     à 5 quand le bandeau du haut était tout l'aluminium du capot ; sur du noir, un
     objectif clair à cette place aurait l'air posé sur l'arête.
     Elle vient après le calque de lunette dans l'ordre de peinture — les deux sont
     en absolu, et c'est l'ordre du document qui tranche — donc elle reste visible
     sans avoir besoin d'un « z-index » à surveiller. */
  background:#3a3d44;
}
/* La dalle rogne l'application : c'est elle qui porte les angles de l'écran, le
   capot n'ayant qu'à l'entourer. Presque droits — un écran n'est pas une vignette.
   « position:relative » ne la déplace pas d'un pixel : il la sort du flux non
   positionné pour qu'elle passe au-dessus du calque de lunette. */
.ordi-dalle{position:relative;border-radius:calc(5px * var(--chassis));overflow:hidden;background:var(--bg);}

/* Le socle, plus large que le capot — un portable ouvert est toujours plus large à
   sa base qu'à son écran, et c'est ce débord qui donne l'assise. Il est posé en
   « débordement négatif » plutôt qu'en largeur fixe pour suivre l'échelle du cadre
   sans qu'on ait à le recalculer à chaque palier.

   Il ne déborde que de très peu. La première version lui donnait 68 px pour un capot
   de 671, soit dix pour cent : un socle qui dépasse d'un dixième n'est plus une base,
   c'est une étagère. Sur une machine réelle l'écart se compte en millimètres ; on en
   garde juste assez pour que l'œil voie une assise, et pas une seconde pièce.

   Le débord reste une variable, parce qu'il entre en concurrence avec la gouttière :
   c'est la seule pièce qui sorte de sa propre boîte, donc la seule que le balayage
   des largeurs puisse trouver dehors alors que le capot tient. */
.scene{--socle-debord:calc(42px * var(--chassis));}
.ordi-socle{
  position:relative;
  width:calc(100% + var(--socle-debord));height:calc(29px * var(--chassis));
  /* Même aluminium que le capot, mais pris à l'envers : le socle est vu de dessus,
     sa face avant est donc dans l'ombre vers le bas et prend la lumière vers le
     haut, où elle rejoint le plan du clavier. Le dégradé s'arrête à l'alu neutre
     plutôt qu'à l'arête claire — deux pièces d'un même métal n'ont pas le même
     reflet, et c'est cette petite différence qui les sépare à l'œil. */
  background:linear-gradient(180deg, var(--alu) 0%, var(--alu-bas) 100%);
  border-radius:0 0 calc(21px * var(--chassis)) calc(21px * var(--chassis));
  /* Le liseré clair du haut est le plan du clavier qui prend la lumière. Une barre
     sans lui est un trait ; avec lui, elle a une épaisseur. Il reste à un pixel : un
     liseré qui s'épaissit cesse d'être une arête et devient une seconde pièce.
     Il est passé du blanc au noir en changeant de métal : sur du bleu nuit une
     arête s'éclaire, sur de l'aluminium elle s'ombre. La valeur change, le rôle
     est le même — dire que la barre a une épaisseur. */
  box-shadow:var(--shadow-lg), inset 0 1px 0 rgba(0,0,0,.10);
}
/* L'encoche par laquelle on soulève le capot. Elle creuse le bord avant, et c'est
   le seul détail qui distingue un socle d'une barre d'aluminium. Elle s'est
   assombrie pour la même raison que le liseré : un creux est toujours plus sombre
   que la surface qui l'entoure, et l'ancienne version l'éclaircissait parce qu'elle
   creusait du bleu nuit. */
.ordi-encoche{
  position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:calc(200px * var(--chassis));height:calc(11px * var(--chassis));
  background:rgba(12,13,16,.16);
  border-radius:0 0 calc(11px * var(--chassis)) calc(11px * var(--chassis));
}

/* ---------- La fenêtre : une application web, pas un navigateur ----------
   Il y avait ici deux rangées de navigateur : une languette d'onglet titrée, deux
   chevrons de navigation, une pilule d'adresse portant « kinomeapp.fr ». C'était bien
   dessiné et c'était le mauvais registre.

   Un onglet dit « cette page est ouverte parmi d'autres pages ». Une barre d'adresse dit
   « on arrive ici en tapant quelque chose ». Les deux racontent une visite. Or ce que la
   page vend n'est pas une visite : c'est l'outil qu'un cabinet garde ouvert à côté de sa
   messagerie et de son agenda, et qu'il rouvre le lendemain. Sur un Mac cela a une forme,
   et une seule — la fenêtre d'application web, celle que Safari crée quand on ajoute un
   site au Dock : pas d'onglet, pas d'adresse, une barre de titre qui porte le **nom de
   l'application** et rien d'autre. Retirer l'adresse n'enlève donc pas une information,
   ça change ce que la fenêtre affirme.

   Ce qui reste suffit à dire macOS, et il n'en faut pas plus : les trois pastilles à
   gauche, le nom centré, un bouton de menu à droite. Les pastilles sont la seule pièce de
   l'ancienne barre à traverser intacte, et c'est logique — c'était déjà la seule qui
   disait le système plutôt que le navigateur.

   La géométrie, elle, ne bouge pas d'un pixel, et c'est ce qui rend l'échange gratuit. La
   dalle est tenue en 16/10 exact par construction : 1,6 × 945 = 1512. Les 945 px se
   partagent entre le chrome et la page, et **seule leur somme** entre dans le rapport. Le
   chrome passe de 83 à 52, la page de 862 à 893 ; 52 + 893 = 945, la machine se dessine à
   l'identique, et la capture — qui ne connaît ni l'une ni l'autre de ces hauteurs — n'a
   pas eu à être rejouée pour autant.

   52 est en revanche un arbitrage, et autant le dire. La page a un plancher mesuré à
   857 px (voir plus bas), ce qui ne laisse au chrome que 88 px au plus ; il restait une
   plage, entre la barre de titre d'une fenêtre ordinaire — basse — et la barre d'outils
   d'un navigateur — haute, les 83 px qu'on retire. Une fenêtre d'application web est
   entre les deux : plus épaisse qu'un simple titre parce qu'elle loge un bouton, bien
   plus fine qu'un navigateur parce qu'elle ne loge que lui. 52 tombe dans cette plage et
   laisse la page à 893, soit 36 px au-dessus de son plancher. */
/* Le fond est « --page-bg » et le contenu de l'application « --bg » : un ton d'écart, le
   plus petit que la palette permette. C'est ce qui distingue une barre de titre macOS
   d'un bandeau — elle n'est pas d'une autre couleur, elle est de la même matière prise
   un cran plus sombre. Les deux jetons sont ceux de l'application elle-même, relevés
   dans sa feuille de style : la barre est donc teintée par la page qu'elle porte, ce qui
   est exactement ce que fait une fenêtre d'application web.

   Le filet du bas est une ombre portée vers l'intérieur, et non une bordure, parce qu'une
   bordure de 1 px serait la seule longueur du châssis à ne pas suivre l'échelle : elle
   s'ajouterait à la hauteur et ferait dériver le rapport de la dalle de 1,600 à 1,598.
   C'est peu, et ce n'est pas le point — le point est que « exact par construction » doit
   rester vrai sans exception, sinon la vérification ne sait plus ce qu'elle surveille.
   Une ombre interne occupe zéro pixel de boîte et se voit exactement pareil. Elle reste à
   1 px à toutes les échelles, et c'est voulu : un filet est une arête, pas une pièce — le
   mettre à l'échelle le ferait disparaître en bas de plage et épaissir en haut.

   « position:relative » n'est pas décoratif : c'est le repère dans lequel le nom va se
   centrer, et la raison en est au commentaire de « .ordi-nom ». */
.ordi-titre{
  position:relative;
  flex:none;display:flex;align-items:center;
  height:calc(52px * var(--chassis));
  padding:0 calc(20px * var(--chassis));
  background:var(--page-bg);
  box-shadow:inset 0 -1px 0 var(--border);
}

/* Les trois pastilles de fenêtre. Elles n'ont plus de hauteur propre : il n'y a plus
   qu'une rangée, et elles se centrent donc dessus comme n'importe quoi d'autre. La
   version d'avant devait leur donner les 34 px de la languette d'onglet pour qu'elles ne
   tombent pas trop bas dans leur rangée de 40 — une contrainte qui disparaît avec la
   pièce qui la créait. */
.ordi-boutons{
  display:flex;align-items:center;gap:calc(8px * var(--chassis));flex:none;
}
/* Rouge, orange, vert, et c'est le marqueur le plus rentable de toute la machine.
   Trois pastilles grises en haut à gauche, c'est une fenêtre quelconque ; les mêmes
   trois pastilles colorées dans cet ordre-là, c'est macOS et rien d'autre. Personne
   ne les lit — on les reconnaît, exactement comme les chevrons de navigation.

   Les teintes sont celles du système et pas celles de la marque, pour la raison qui
   vaut déjà pour l'aluminium : ce sont des couleurs d'appareil. Les mettre à l'accent
   ferait trois points orange, c'est-à-dire plus aucun signal.
   « nth-child » plutôt que trois classes : le gabarit de l'assembleur pose trois
   « .ordi-pt » identiques, et leur ordre est précisément ce qui porte le sens. */
.ordi-pt{
  width:calc(12px * var(--chassis));height:calc(12px * var(--chassis));
  border-radius:50%;background:var(--grille);flex:none;
}
.ordi-pt:nth-child(1){background:#ff5f57;}
.ordi-pt:nth-child(2){background:#febc2e;}
.ordi-pt:nth-child(3){background:#28c840;}

/* Le nom de l'application, et c'est tout ce que porte la barre.

   Il est posé en absolu, et c'est le seul moyen de le centrer **sur la fenêtre**. La
   version précédente avait appris la leçon à ses dépens sur la pilule d'adresse : des
   marges automatiques ne centrent un élément que dans la place qui reste, si bien qu'un
   titre flanqué de trois pastilles à gauche et d'un bouton à droite se décale de la
   moitié de leur différence. La barre d'adresse s'en était sortie en inventant un
   contrepoids de droite — deux carrés dont le seul rôle était d'avoir la largeur des
   chevrons d'en face. C'était une pièce de décor au service d'un calcul, et on n'en a
   plus besoin : hors du flux, le centrage ne dépend plus de ce qui l'entoure.

   La graisse est à 600 et non à 400. Un titre de fenêtre macOS est en demi-gras, et
   c'est ce qui le sépare d'une légende : léger, il flotterait au milieu de la barre
   comme une étiquette posée là.

   Le corps a un plancher, et c'est l'exception assumée du châssis — la même que celle
   des deux textes du navigateur qu'on retire, pour la même raison. Un titre de fenêtre
   réel fait 13 px dans le repère de la machine ; à l'échelle 0,50 où on la regarde, cela
   ferait 6,5 px à l'écran, soit un texte qu'on devine. On l'écrit donc à 21 px de
   machine — 1,6 fois sa cote vraie — avec un plancher à 9 px pour les paliers réduits.
   C'est une entorse à l'échelle, elle est visible, et elle est préférable à l'alternative :
   une barre géométriquement juste dont on ne peut pas lire le seul mot. */
.ordi-nom{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:flex;align-items:center;gap:calc(8px * var(--chassis));
  font-family:var(--systeme);
  font-size:max(9px, calc(21px * var(--chassis)));
  font-weight:600;line-height:1;letter-spacing:-.01em;
  color:var(--text);white-space:nowrap;
}
/* Le favicon : un point de couleur de marque. À l'échelle où on le regarde il ne peut
   pas porter de dessin, et il n'en a pas besoin — ce qu'on lit à cette taille n'est pas
   le logo, c'est qu'il y en a un. Il a grandi de 15 à 20 px de machine en changeant de
   place : dans une languette d'onglet il accompagnait un titre, dans une barre de titre
   il forme un couple avec lui, et un couple veut deux pièces de poids comparable. */
.ordi-favicon{
  width:calc(20px * var(--chassis));height:calc(20px * var(--chassis));
  border-radius:calc(5px * var(--chassis));
  background:var(--accent);flex:none;
}

/* Le bouton de menu, à droite. C'est la troisième et dernière pièce, et elle est
   nécessaire : une barre qui ne porte qu'un nom centré et trois pastilles se lit comme
   une maquette, parce qu'aucune fenêtre réelle n'est aussi vide. Une fenêtre
   d'application web en a un, et il est à cette place.

   Trois points et non une flèche ou un engrenage : à la taille où on le voit, un
   pictogramme devient une tache dont la forme ne se lit plus, alors que trois points
   alignés restent trois points. Ils ne suivent pas de plancher — on ne les lit pas, on
   les reconnaît — et le fond de pilule est là pour qu'ils se lisent comme un bouton
   plutôt que comme une poussière. */
.ordi-options{
  display:flex;align-items:center;justify-content:center;
  gap:calc(4px * var(--chassis));
  margin-left:auto;flex:none;
  width:calc(34px * var(--chassis));height:calc(24px * var(--chassis));
  border-radius:calc(7px * var(--chassis));
  background:color-mix(in srgb, var(--text) 7%, transparent);
}
.ordi-option-pt{
  width:calc(4px * var(--chassis));height:calc(4px * var(--chassis));
  border-radius:50%;background:var(--text-muted);opacity:.75;flex:none;
}
/* Le téléphone est posé sur le coin de l'écran, comme on le pose sur un bureau.
   Il déborde volontairement du cadre : c'est ce chevauchement qui dit que les
   deux montrent la même chose au même moment. */
.cadre-tel{
  /* Décalé hors du cadre, et pas posé dessus : le format « bureau » centre son
     écran dans la fenêtre, si bien qu'un téléphone calé à l'intérieur vient
     recouvrir la fiche et sa jauge — c'est-à-dire ce qu'on veut montrer. À cheval
     sur le bord, il ne mange que la gouttière, qui est vide. */
  position:absolute;right:calc(-1 * var(--debord));bottom:calc(-72px * var(--tel));z-index:2;
  /* Pas de largeur : « zoom » a déjà réduit la boîte de l'application, le cadre
     se serre donc dessus tout seul et suit toute correction d'échelle. */
  /* Même principe que le châssis de l'ordinateur, et pour la même raison : les cotes
     sont celles de l'appareil — 14 px de lunette, 54 px d'angle sur un écran de 412 —
     multipliées par l'échelle. Figées, elles faisaient du téléphone réduit un objet à
     la bordure trop épaisse, exactement comme le menton de l'ordinateur. */
  border-radius:calc(54px * var(--tel));padding:calc(14px * var(--tel));
  /* La lunette est noire, et la bande d'aluminium est **autour** et non dedans :
     c'est l'ordre des matières sur l'appareil, et c'est aussi ce qui évite de
     reprendre la géométrie. Un téléphone se tient par une tranche de métal qui
     fait le tour de la dalle ; cette tranche est donc une ombre portée de rayon
     nul, qui ne coûte aucun pixel de boîte — même raisonnement que le filet de la
     barre de navigateur, et même bénéfice : la largeur du cadre reste exactement
     celle de l'application réduite, et le calcul du débord n'a pas bougé.
     Trois anneaux plutôt qu'un : le métal, puis une arête claire qui l'allume, puis
     l'ombre de l'objet. Un seul anneau donne un contour ; trois donnent une tranche. */
  background:var(--lunette);
  box-shadow:
    0 0 0 calc(3px * var(--tel)) var(--alu),
    0 0 0 calc(4px * var(--tel)) var(--alu-haut),
    var(--shadow-lg);
}
/* L'écran est désormais une pièce à lui : la barre d'état, l'application et la barre
   d'accueil s'empilent dedans, et c'est lui qui porte l'arrondi et le rognage que
   l'application portait seule. Le fond est celui de l'application, pour que les deux
   bandes ne se lisent pas comme des bordures mais comme la dalle qui continue. */
.cadre-tel .tel-ecran{
  border-radius:calc(42px * var(--tel));overflow:hidden;
  background:var(--bg);
}

/* ---------- La barre d'état, et ce qui s'y lit vraiment ----------
   Toutes les cotes qui suivent sont celles de l'appareil, divisées par rien : la
   barre d'état d'un iPhone mesure 54 points sur un écran large de 412, l'île 126 sur
   37, la barre d'accueil 140 sur 5. Ce sont les nombres écrits ici, multipliés par
   « --tel » comme le reste du châssis. On pourrait les régler à l'œil et obtenir le
   même dessin ; les garder réels fait qu'au prochain changement d'échelle il n'y a
   rien à rejuger.

   Les trois indicateurs de droite sont dessinés en fonds et non en icônes, et ils
   sont abstraits. À .43, le groupe entier fait trente pixels : c'est le même
   raisonnement que pour le favicon de l'onglet — ce qu'on lit à cette taille n'est
   pas le dessin, c'est qu'il y en a un. Les dessiner en SVG détaillé coûterait du
   balisage pour un détail que personne ne verra, et mentirait sur l'intention. */
.tel-barre-etat{
  position:relative;
  display:flex;align-items:center;justify-content:space-between;
  height:calc(54px * var(--tel));
  padding:0 calc(26px * var(--tel));
}
/* L'heure, et elle est en « --systeme » depuis qu'on a regardé de près les deux
   typographies des châssis. Elle était en « --texte », c'est-à-dire en Libre Franklin :
   la police de la marque dans la barre d'état du système, autrement dit à la seule place
   de la page où la marque n'écrit rien. Voir le commentaire du jeton, en tête de cette
   feuille, pour le raisonnement complet — il vaut à l'identique pour le nom de la fenêtre
   du Mac, qui était en « --mono » et souffrait du même défaut.

   Le « letter-spacing » disparaît avec elle. Il était à +0,01em pour aérer une graisse de
   Libre Franklin ; San Francisco est déjà réglée pour cette taille-là, et toute correction
   d'approche qu'on lui ajoute ne fait que défaire ce que sa fonderie a fait exprès. */
.tel-heure{
  font-family:var(--systeme);
  font-size:max(7.5px, calc(17px * var(--tel)));line-height:1;
  font-weight:600;
  color:var(--lunette);
}
/* « 9:41 », et ce n'est pas une heure prise au hasard : c'est celle que montrent
   toutes les captures d'Apple depuis la première présentation de l'iPhone. Mettre
   l'heure réelle demanderait du script pour un détail que personne ne vérifie ;
   mettre une heure quelconque donnerait une capture qui a l'air fausse sans qu'on
   sache dire pourquoi. */

/* L'île. Elle est en absolu parce qu'elle est dans la dalle et non dans une rangée :
   intercalée entre l'heure et les indicateurs, elle les repousserait vers les bords
   dès que l'échelle change, alors que sur l'appareil ces trois éléments ne se
   poussent jamais — ils cohabitent. */
.tel-ile{
  position:absolute;top:calc(11px * var(--tel));left:50%;transform:translateX(-50%);
  width:calc(126px * var(--tel));height:calc(37px * var(--tel));
  border-radius:calc(19px * var(--tel));
  background:var(--lunette);
}

.tel-indicateurs{
  display:flex;align-items:flex-end;gap:calc(5px * var(--tel));
}
/* Les quatre barres du réseau, en quatre fonds superposés plutôt qu'en quatre
   éléments : c'est la seule façon d'obtenir des hauteurs croissantes sans ajouter
   quatre balises au gabarit. Chaque fond est un aplat, dimensionné et placé
   indépendamment — « background-size » porte la hauteur, « background-position »
   l'abscisse, et l'ancrage en bas fait que les barres montent depuis la même ligne. */
.tel-reseau{
  width:calc(17px * var(--tel));height:calc(11px * var(--tel));
  background-image:
    linear-gradient(var(--lunette),var(--lunette)),
    linear-gradient(var(--lunette),var(--lunette)),
    linear-gradient(var(--lunette),var(--lunette)),
    linear-gradient(var(--lunette),var(--lunette));
  background-repeat:no-repeat;
  background-size:18% 40%, 18% 62%, 18% 81%, 18% 100%;
  background-position:0 100%, 27% 100%, 55% 100%, 100% 100%;
}
/* Le wifi : trois arcs concentriques, puis un coin de tarte pour n'en garder que la
   pointe. Les arcs viennent d'un dégradé radial centré sur le bas de la boîte, le
   coin d'un « clip-path » — autrement dit deux propriétés pour un pictogramme, là où
   un SVG en demanderait huit lignes de balisage. */
.tel-wifi{
  width:calc(14px * var(--tel));height:calc(11px * var(--tel));
  background:radial-gradient(circle at 50% 100%,
    var(--lunette) 0 26%, transparent 26% 46%,
    var(--lunette) 46% 66%, transparent 66% 84%,
    var(--lunette) 84% 100%);
  clip-path:polygon(50% 100%, 2% 20%, 98% 20%);
}
/* La pile. Le contour est une bordure, la charge un fond rogné sur la boîte de
   contenu : « background-clip:content-box » fait que le remplissage s'arrête au
   bord intérieur du padding, et c'est ce liseré vide entre le contour et la charge
   qui fait lire une pile plutôt qu'un rectangle noir. Elle est à 72 % — une pile
   pleine ou vide raconte quelque chose, une pile aux trois quarts ne raconte rien,
   ce qui est exactement ce qu'on veut d'un indicateur de décor. */
.tel-batterie{
  position:relative;
  width:calc(25px * var(--tel));height:calc(12px * var(--tel));
  border:calc(1.5px * var(--tel)) solid var(--lunette);
  border-radius:calc(3.5px * var(--tel));
  padding:calc(1.5px * var(--tel));
  background-image:linear-gradient(90deg, var(--lunette) 0 72%, transparent 72%);
  background-clip:content-box;background-repeat:no-repeat;
  opacity:.9;
}
.tel-batterie::after{
  content:"";position:absolute;left:100%;top:50%;transform:translateY(-50%);
  width:calc(2px * var(--tel));height:calc(4px * var(--tel));
  border-radius:0 calc(2px * var(--tel)) calc(2px * var(--tel)) 0;
  background:var(--lunette);opacity:.5;
}

/* La barre d'accueil. C'est, avec l'île, le marqueur le plus court du lot : un trait
   arrondi centré au bas d'un écran sans bouton ne se trouve que sur un iPhone. */
.tel-accueil{
  display:flex;align-items:center;justify-content:center;
  height:calc(28px * var(--tel));
}
.tel-trait{
  width:calc(140px * var(--tel));height:calc(5px * var(--tel));
  border-radius:calc(5px * var(--tel));
  background:var(--lunette);opacity:.85;
}
/* La scène réserve la hauteur du débord, faute de quoi le téléphone recouvre ce qui
   suit. C'est une **marge**, et le mot compte : la version précédente écrivait un
   remplissage, et ce remplissage ne réservait rien du tout.

   Le téléphone est posé en absolu, « bottom » compté — comme tout décalage absolu —
   depuis la boîte de remplissage de son bloc conteneur, qui est la scène. Un
   « padding-bottom » déplace donc vers le bas l'arête même à laquelle le téléphone
   s'accroche : la scène descend de 31 px, le téléphone descend avec elle, et le débord
   vaut toujours 31 px. La réserve se déplaçait en même temps que ce qu'elle était
   censée rattraper.

   Mesuré dans le navigateur, page d'accueil à 1512 px, en basculant la seule
   déclaration :

   | déclaration              | bas de « .scene » | bas du téléphone | débord |
   |--------------------------|-------------------|------------------|--------|
   | padding-bottom: 30,96 px | 1101              | 1132             | 31     |
   | padding-bottom: 0        | 1070              | 1101             | 31     |

   Trente et un pixels de hauteur payés pour rien, et le bandeau des types de poste qui
   coupait l'appareil juste sous sa barre d'accueil — un téléphone tranché net, qu'on
   lit comme un bogue et non comme un objet qui sort du cadre.

   Une marge est en dehors de la boîte de remplissage : l'arête d'ancrage ne bouge pas,
   le téléphone reste où il est, et la place apparaît en dessous de lui. C'est la seule
   façon de réserver la hauteur d'un débord, et la raison est dans le mot — un débord
   est ce qui sort de la boîte, donc ce qu'aucune propriété interne à la boîte ne peut
   compter.

   Ce qu'on gagne en justesse se paie en fragilité : une marge basse se fusionne avec
   celle du parent, un remplissage non. La page d'accueil s'en charge de son côté, au
   commentaire de « .accueil-scene ». */
.scene{margin-bottom:calc(72px * var(--tel));}

/* En largeur, le débord ne peut pas se réserver de la même façon : « width:fit-content »
   serre la scène sur le cadre, et le téléphone est posé en absolu — il sort donc de la
   boîte sans que rien ne s'en aperçoive. Or « margin:auto » centre cette boîte, c'est-à-dire
   le cadre seul, alors que la composition qu'on regarde va jusqu'au bord droit du téléphone.
   Le centrage était donc faux de la moitié du débord, et sur les écrans où la place est
   juste cette moitié passait le bord de la page : à 960 px le téléphone finissait sept
   pixels dehors, et la page prenait une barre de défilement horizontale.

   Décaler la scène de la moitié du débord recentre la composition entière sans toucher à
   aucune boîte — une transformation ne déplace pas le bloc de référence du téléphone,
   puisque celui-ci est déjà « .scene » lui-même (elle est en « position:relative »).
   Le même « --debord » sert aux deux usages, de sorte qu'une correction d'échelle ne peut
   pas décaler l'un en oubliant l'autre.

   Une seule valeur, et plus une par palier — c'est une simplification qu'on n'a pas
   décidée mais constatée, et elle a une cause précise. Tant que la largeur de la fenêtre
   variait avec le palier, le bord droit du contenu tombait à une fraction différente de
   la dalle à chaque palier (81,0 % à 1280, 77,6 % à 1440…), et il fallait donc un débord
   par palier pour l'y rattraper. Depuis que la fenêtre est fixée à 1512 px partout — seul
   le zoom change — cette fraction vaut 76,32 % à tous les paliers, et le réglage devient
   commun. Les trois valeurs d'avant (19, 18, 16) n'étaient pas trois réglages esthétiques
   mais trois rattrapages du même défaut.

   La valeur se mesure, et 7 px est le plus petit entier qui tienne les trois paliers avec
   de la réserve : le jeu entre le bord du contenu et le flanc du téléphone vaut alors
   5,8 px à l'échelle .50, 5,3 à .44 et 4,7 à .38. On ne descend pas à 4 px, qui ne
   laisserait que 1,7 px au palier le plus serré : c'est juste géométriquement et sans
   réserve contre un rendu sous-pixel ou une police de rechange, et le prix d'une erreur
   ici est une fiche rognée. On ne monte pas non plus — chaque pixel de débord coûte un
   demi-pixel de place là où la composition touche déjà les deux bords, et laisse en prime
   une bande de fond nu entre l'application et le téléphone. */
.scene{--debord:7px;transform:translateX(calc(var(--debord) / -2));}

/* ---------- L'application capturée, réduite et non redimensionnée ----------
   « .phone » se dimensionne, dans le prototype, sur la fenêtre entière : c'est la
   seule chose qu'il faut lui reprendre, puisqu'ici il n'a plus la fenêtre pour lui.

   On lui donne donc une fenêtre, une vraie, et c'est le cadre qui la réduit. La
   première version faisait l'inverse — une hauteur choisie pour la vitrine — et
   l'application s'y mettait en page autrement : sa zone défilante se contractait,
   la fiche était tranchée en plein milieu d'une phrase et la démonstration montrait
   un écran que personne ne voit. Mettre à l'échelle, au contraire, ne change aucune
   proportion : ce qui est à l'intérieur reste exactement ce qu'un utilisateur a
   sous les yeux, en plus petit.

   « zoom » plutôt que « transform:scale() » : zoom réduit aussi la boîte, le cadre
   se dimensionne donc tout seul. Une mise à l'échelle par transformation laisserait
   la place de l'application non réduite et demanderait un enrobage à mesurer. */
.appli .phone{max-width:none;max-height:none;border-radius:0;box-shadow:none;}

/* Les deux seuls endroits où l'application se mesure sur la fenêtre du navigateur
   (« height: calc(100dvh - 150px) » en téléphone, « - 116px » en bureau). C'est la
   seule chose qu'elle ne peut pas emporter ici, où sa fenêtre est le cadre et non
   l'onglet : gardée telle quelle, elle prenait la hauteur de la page d'accueil, et
   sa zone défilante changeait de taille selon l'écran du visiteur — sur un portable
   la fiche se retrouvait tranchée, sur un grand écran elle flottait.
   On lui rend donc la hauteur de son cadre, qui est sa fenêtre dans la vitrine.

   « [data-format] » et « .rig » depuis le 18 octobre 2026 : les écrans relevés sur
   l'application réelle ont apporté deux règles « height:100dvh » plus précises que
   celle-ci (« .appli[data-format] .phone » et « .appli[data-format] .rig »), qui
   l'emportaient. Invisible sur un ordinateur, où la fenêtre est plus haute que
   l'application ; sur un iPhone, dont la fenêtre utile est plus basse, l'écran du
   téléphone se raccourcissait, la barre du bas remontait et la fiche du fil
   débordait sur le côté. Même précision ici, et cette feuille passe après app.css. */
.appli .rig,
.appli[data-format] .rig,
.appli .phone,
.appli[data-format] .phone,
.appli[data-format="bureau"] .phone{height:100%;}

/* ---------- Les deux plafonds de largeur du format bureau ----------
   C'est le seul endroit où la vitrine s'écarte sciemment de l'application, et il vaut
   mieux le dire ici que le laisser deviner.

   Le format bureau plafonne deux largeurs : la colonne de lecture à 720 px et la carte
   de découverte à 470 px. Les deux plafonds sont justes dans l'application, et pour la
   même raison qu'elle écrit elle-même — « un écran de 1200 px n'est pas une raison pour
   étirer une ligne de texte sur 1200 px ». Mais on lit l'application à 100 % et on
   regarde la vitrine à 38 % : ce que le visiteur a sous les yeux n'est pas un texte,
   c'est une forme, et la forme d'une colonne de 720 px dans une fenêtre de 1512 px est
   une bande étroite flanquée d'un vide.

   La fenêtre ne peut pas rétrécir pour rattraper la colonne : sa largeur découle du
   plancher de hauteur de la carte de découverte et du 16/10 de la dalle — 1,6 × (857 + 52)
   vaut 1454, et 1512 est donc à cinquante-huit pixels du minimum. Il ne reste, pour
   l'essentiel, que l'autre bout. (Du temps du navigateur à deux rangées le chrome faisait
   83 px et ce minimum valait 1504 : la fenêtre était alors à huit pixels près la plus
   étroite possible. La barre de titre a desserré l'étau, sans que ça change la conclusion
   — 58 px de marge ne rattrapent pas l'écart entre 720 et 1512.)

   Les deux plafonds sont multipliés par le même facteur, 1,278 : 720 → 920 et 470 → 600.
   Le rapport de la carte à sa colonne (0,65) est ce qui fait qu'elle reste une carte au
   milieu d'elle ; le changer aurait été un second réglage, et on n'en veut qu'un.

   La colonne pousse vers la gauche seulement. « margin-right » reprend la gouttière
   qu'avait la colonne de 720 px centrée — d'où l'écriture en « calc », qui la recalcule
   au lieu de la constanter — de sorte que le bord droit du contenu ne bouge pas d'un
   pixel : il reste à 76,32 % de la dalle, vérifié sur les trois volets. Toute la chaîne
   du débord, du jeu au flanc du téléphone et du centrage de la scène tient donc sans
   qu'on y touche. Et l'asymétrie ne se voit pas : la gouttière de droite est couverte
   par le téléphone, qui est toujours là quand l'ordinateur l'est — en dessous de 900 px
   c'est l'ordinateur qui disparaît, jamais lui. */
.appli[data-format="bureau"] .screen:not(.large){
  max-width:920px;
  margin-left:auto;
  margin-right:calc((100% - 720px) / 2);
}
/* « !important » parce que la règle de l'application en porte un : la carte est centrée
   par le remplissage de sa zone défilante, et non par une largeur à elle. */
.appli[data-format="bureau"] .swipe-zone .scroll{
  padding-inline:max(20px, calc((100% - 600px) / 2)) !important;
}

/* 1512 × 893 pour l'ordinateur, et ces deux nombres ne se règlent pas séparément.

   La hauteur part du bas : la carte de découverte a une hauteur fixe que la largeur de
   la fenêtre ne change pas. Son plancher est à 857 px — mesuré, pas estimé : en dessous,
   ses deux boutons de jugement sortent du bas de la fenêtre. C'est donc une contrainte,
   pas une préférence. (La version précédente annonçait 880 comme « la hauteur que
   l'application réclame pour ce profil, mesurée » : c'était vrai du temps où le rang 0
   était le tableau de suivi, et faux depuis. La contrainte ne vient pas du profil, qui
   défile, mais de la carte, qui ne défile pas.)

   La largeur en découle, et elle découle d'un seul autre nombre : la hauteur du chrome.
   La dalle montre la page *plus* le chrome, et c'est cet ensemble qui doit faire 16/10.
   Le chrome fait 52 px — la barre de titre de l'application web, voir plus haut :
   893 + 52 = 945, et 1,6 × 945 = 1512. La dalle fait donc 1512 × 945 et la fenêtre
   1512 × 893.

   Le partage a déjà changé une fois sans que rien bouge, et c'est ce qui montre où est la
   vraie contrainte. Du temps du navigateur à deux rangées, le chrome faisait 83 px et la
   page 862 ; en passant à la barre de titre, le chrome tombe à 52 et la page remonte à
   893. La somme reste 945, la dalle reste 1512 × 945, la machine se dessine à l'identique
   et la capture — qui ignore ces deux hauteurs — n'a pas eu à être rejouée. Seule la
   somme est contrainte ; le partage est un choix de dessin.

   Rien n'a été arrondi pour que ça tombe juste, et pourtant 1512 est la largeur logique
   d'un MacBook Pro 14 pouces : la fenêtre a bien une taille que des gens ont vraiment.
   La version d'avant arrivait à 1440 × 900 par le même raisonnement avec un chrome de
   38 px — cette dalle-là était une définition d'écran réelle, mais son chrome ne faisait
   que 4,2 % de sa hauteur, soit la moitié de ce qu'occupe une barre d'outils de
   navigateur. On a échangé une définition de catalogue contre une proportion de chrome
   juste, et c'est le bon sens de l'échange : personne ne reconnaît 1440 × 900 à l'œil,
   tout le monde reconnaît un chrome à la mauvaise épaisseur.

   Ce que ça coûte : une dalle correctement proportionnée est plus basse qu'un carré de
   même largeur, donc la fiche rend moins. Il n'y a pas de troisième possibilité.

   L'échelle, elle, est une question de composition et non de géométrie, et elle a été
   descendue d'un cran après coup : une machine juste mais trop grande remplissait sa
   colonne d'un bord à l'autre et ne laissait d'air nulle part. Les deux objets se
   lisent mieux petits et groupés que grands et à l'étroit — c'est la taille relative
   qui fait la scène, pas la taille absolue.

   Le téléphone garde 880 px de page : il n'a pas de chrome à loger — sa barre d'état et
   sa barre d'accueil sont des bandes à part — et c'est la hauteur déjà vérifiée pour ses
   écrans. Son échelle suit celle de l'ordinateur à
   87 %, de sorte qu'il en fait toujours ~78 % de la hauteur quel que soit le palier.
   Il en faisait 124 % — une composition où le téléphone écrase la machine ne se lit
   pas comme un bureau. */
.scene{--chassis:.50;--tel:.43;}
.cadre-ordi .appli{width:1512px;height:893px;--appli-l:1512px;--appli-h:893px;--appli-z:.50;}
.cadre-tel  .appli{width:412px;height:880px;--appli-l:412px;--appli-h:880px;--appli-z:.43;}
/* L'échelle s'écrit dans « --appli-z » (avec la taille d'origine dans « --appli-l » et
   « --appli-h ») et s'applique ici, une seule fois.

   Par « zoom », partout, comme depuis le premier jour. Sur iPhone (Safari et Chrome),
   les textes de l'application sortaient pourtant à leur taille réelle dans un écran
   réduit de moitié — fiche tronquée, titres sur trois lignes. Relevé sur un appareil
   le 18 octobre 2026 ; aucun simulateur ne le montre. Le réglage de taille des textes
   d'iOS en est le suspect : en « auto » il regonfle les textes devenus minuscules, et
   avec un pourcentage il recalcule la taille sans tenir compte du « zoom ». « none »
   le coupe tout à fait, sur les maquettes seulement.

   Filet de sécurité, si un appareil n'applique toujours pas le « zoom » aux textes :
   kinome.js le mesure au chargement et pose « echelle-transform » sur « html ».
   L'écran est alors mis en page à sa taille d'origine et réduit d'un bloc par
   « transform:scale() », textes compris ; les marges négatives rendent la place qu'il
   laisse dans la page. Pas par défaut, à cause de la mémoire : chaque écran animé est
   alors peint en pleine taille avant d'être réduit — 1,2 Go mesurés dans WebKit au
   format iPhone, de quoi faire fermer l'onglet. « content-visibility:auto » ne peint
   donc que les maquettes proches de l'écran (près de 1 Go encore tant que le haut de
   page est visible, 300 Mo plus bas) ; la place réservée est celle de la boîte
   d'origine, que les marges ramènent à l'échelle, donc rien ne bouge quand une
   maquette s'allume ou s'éteint. */
.cadre-tel .appli,
.cadre-ordi .appli{zoom:var(--appli-z);-webkit-text-size-adjust:none;text-size-adjust:none;}
html.echelle-transform .cadre-tel .appli,
html.echelle-transform .cadre-ordi .appli{
  zoom:1;transform:scale(var(--appli-z));transform-origin:0 0;
  margin-right:calc(var(--appli-l) * (var(--appli-z) - 1));
  margin-bottom:calc(var(--appli-h) * (var(--appli-z) - 1));
  content-visibility:auto;contain-intrinsic-size:var(--appli-l) var(--appli-h);
}
/* Réduire l'échelle du cadre rapproche son bord droit de la fiche, puisque la
   gouttière vide qui les sépare se réduit d'autant ; le téléphone, lui, se réduit
   moins vite. Sans la rallonge de débord qui suit, il vient recouvrir le bord droit de
   la fiche.
   Les valeurs se déduisent d'une seule mesure. Le format « bureau » plafonne sa colonne
   de lecture, de sorte que le bord droit du contenu tombe à une fraction constante de la
   largeur de la dalle : **76,32 %**. Mesuré sur les trois écrans de l'ordinateur — profil,
   découverte, discussions — qui donnent tous les trois la même valeur au centième. C'est
   ce qui rend le réglage sûr : il n'y a pas un écran à surveiller, il y en a un seul à
   mesurer.

   Cette fraction ne dépend que de la largeur CSS de la fenêtre, et plus du tout du palier,
   puisque la fenêtre est fixée à 1512 px à tous les paliers — seul le zoom change. C'est
   une simplification que la version précédente n'avait pas : elle devait tenir une table
   (1280 → 81,0 %, 1350 → 79,4 %, 1440 → 77,6 %, 1600 → 74,8 %) parce qu'elle avait cru un
   moment faire varier la largeur de la fenêtre avec le palier.

   Le débord juste pose le flanc gauche du téléphone juste à droite de ce bord : il couvre
   la gouttière, qui est vide, et rien de plus. Ne pas arrondir vers le bas — c'est la
   fiche qui est rognée — ni trop vers le haut : chaque pixel de débord coûte un demi-pixel
   de place sur les écrans où la composition touche déjà les deux bords. Comme le débord
   est en pixels rendus et la fraction en proportion de la dalle, le même débord ne vaut
   pas la même chose à deux paliers : c'est pourquoi il y en a un par palier, et pourquoi
   le balayage des largeurs les vérifie tous plutôt que de les déduire.

   Il ne reste qu'un palier empilé, là où il y en avait deux. La machine étant plus petite
   qu'à l'origine, elle tient d'un bout à l'autre de la plage empilée et n'a plus besoin
   d'un cran intermédiaire. Le seul qui reste est celui qui sert vraiment — en dessous de
   1020 px, la gouttière de la page finit par mordre. */
@media (max-width:1020px){ .cadre-ordi .appli{--appli-z:.44;} .cadre-tel .appli{--appli-z:.38;} .scene{--chassis:.44;--tel:.38;} }

/* ---------- La remise à zéro de la marge haute, au-delà de 1360 px ----------
   Il ne reste qu'une déclaration de ce qui fut tout le côte-à-côte : le texte à
   gauche, la démonstration à droite. Cette mise en page est partie le 4 octobre 2026
   avec la page qui la portait ; le nouveau hero est centré et dispose de toute la
   largeur. Les règles qui la tenaient — « .hero-grille » en deux colonnes,
   « .hero{overflow:hidden} » contre le débord du téléphone, le titre plafonné à
   50 px parce qu'il partageait sa ligne avec la machine — ne visaient plus aucun
   élément. Le raisonnement qui les justifiait, avec ses mesures, est conservé en
   entier dans « ~/Documents/kinome-archives/kinome-2026-10-04.css ».

   Trois autres règles étaient devenues pires qu'inutiles : elles ramenaient la
   machine à .38/.33 pour la faire tenir dans une colonne qui n'existe plus, et
   « accueil.css » les rattrapait aussitôt en la remettant à .50/.43 — c'est-à-dire
   à la valeur de base, celle qui s'applique de toute façon en l'absence des deux.
   Trois couches qui se disputaient une seule valeur pour retomber sur le point de
   départ. Les deux premières sont retirées ; la valeur calculée ne change pas d'un
   centième, ce qui a été vérifié dans le navigateur avant et après.

   Restait « margin:0 auto », qui n'était pas redondante : la règle de base donne à
   « .scene » une marge haute de « clamp(40px,6vw,66px) », que ce palier annulait. Elle
   l'est devenue le 5 octobre 2026, et par les deux bouts.

   Par le haut d'abord. Le commentaire d'alors notait le défaut sans le corriger — « en
   dessous de 1360 px cette marge s'ajoute à celle de .accueil-scene, ce qui fait un
   écart de 116 px contre 54 au-delà », un saut jugé relever de la composition et non du
   nettoyage. Il a été corrigé depuis, et à sa place : la page d'accueil est la seule où
   « .scene » ait une boîte autour d'elle, donc la seule qui puisse décider que cette
   boîte porte la marge haute toute seule. « .accueil-scene .scene{margin-top:0} » le
   dit, sans condition de largeur, et ce palier n'annulait donc plus que ce qui l'était
   déjà. Deux déclarations se disputaient la même valeur pour retomber sur zéro.

   Par le bas ensuite, et c'est ce qui a rendu le retrait nécessaire plutôt que
   seulement propre : « margin:0 auto » est un raccourci, il remet donc aussi la marge
   basse à zéro — y compris celle que la scène vient de recevoir pour réserver le débord
   du téléphone. Au-delà de 1360 px, et là seulement, le téléphone aurait recommencé à
   mordre sur ce qui suit. Un raccourci écrit pour une propriété en efface trois autres,
   et il les efface en silence.

   Le raisonnement complet, avec ses mesures, reste dans
   « ~/Documents/kinome-archives/kinome-2026-10-04.css ». */

/* ---------- Le parcours : une bande de trois écrans, qui va et qui revient ------
   Chaque cadre porte trois volets, et les deux cadres n'ont pas les mêmes : du côté
   de l'ordinateur la fiche complète d'un cabinet, le fil, et les discussions ; du
   côté du téléphone les discussions, le fil, un kinôme. Ce sont des écrans entiers de
   l'application, relevés tels quels : on ne navigue pas vraiment ici, on rejoue une
   navigation qui a eu lieu dans le prototype au moment de la capture.
   Le partage des écrans se décide dans « assembler-demo.py », qui l'explique ; cette
   feuille n'en connaît que les rangs.

   « Les discussions » est un volet qui en contient deux — la liste, puis le fil ouvert.
   Il a sa propre chorégraphie, un cran plus bas, juste après celle de la bande.

   Les trois volets ne sont pas superposés : ils sont posés côte à côte, et c'est le
   cadre qui coulisse devant eux. La première version les empilait et les croisait
   en fondu, ce qui avait un défaut qu'aucun réglage de durée ne corrige : arrivé au
   bout du parcours, il faut revenir au début, et le retour est alors une coupe. On
   ouvrait une discussion, puis on se retrouvait sans transition devant le fil. Le
   fondu adoucissait le pixel, pas le saut : rien ne disait *comment* on était
   revenu.

   Une bande qui va et qui revient — 0, 1, 2, 1, 0 — n'a pas ce point de rupture,
   parce qu'elle n'a pas de fin : son dernier état est son premier. Le retour n'est
   plus une coupe à masquer, c'est un mouvement qu'on montre, et c'est d'ailleurs
   celui que le produit fait vraiment — on entre dans une discussion, puis on
   revient en arrière.

   Reste l'infidélité, et elle change de nature plutôt que de disparaître :
   l'application, elle, ne fait pas glisser ses écrans, elle masque son conteneur,
   le repeint et le rallume. Mais là où le fondu croisé inventait une surimpression
   que l'application ne produit jamais — deux écrans visibles à la fois —, le
   glissement n'invente qu'un déplacement : à chaque instant on voit un écran de
   l'application, entier, et jamais deux l'un sur l'autre.

   Les volets sont hors du flux, ce que « .appli » peut se permettre parce qu'elle
   porte déjà une largeur et une hauteur explicites : la bande ne se dimensionne
   donc pas sur son contenu. « top:0; bottom:0 » n'est pas ici un simple centrage :
   c'est lui qui rend aux volets la hauteur que « .appli » leur donne. L'application
   mesure sa zone défilante en pourcentage (« .phone{height:100%} » plus haut), et un
   volet de hauteur automatique laisserait ce pourcentage sans référence — la fiche
   s'affaissait alors sur la hauteur de son propre texte.

   « overflow:hidden » n'est pas décoratif : c'est lui qui fait du cadre une fenêtre
   sur la bande, donc lui qui cache les deux écrans qui attendent leur tour. */
.appli{position:relative;overflow:hidden;}
.appli .volet{
  position:absolute;top:0;bottom:0;width:100%;
  animation:bande 36s cubic-bezier(.76,0,.24,1) var(--decalage,0s) infinite;
}
/* Le rang dans la bande est posé par « left », pas par la transformation : la
   transformation est animée et commune aux trois volets, elle ne peut donc pas
   porter en plus un décalage propre à chacun. Les deux se composent — « left »
   place, « translateX » déplace.

   On range par le rang, et non par le nom de l'écran. Les deux cadres ne montrent
   plus la même chose : l'ordinateur ouvre la fiche d'un cabinet, balaye des profils
   et lit une discussion ; le téléphone lit ses discussions, balaye et reçoit un
   kinôme. Viser les noms obligerait donc à écrire cinq lignes au lieu de trois, dont deux
   qui se disputeraient la même place, et surtout à retoucher cette feuille chaque
   fois que la bande change d'écran — alors que la bande, elle, est décidée dans
   « assembler-demo.py », qui écrit le rang à côté du nom. Le rang est tout ce dont
   le rangement a besoin, et c'est la seule chose qui soit commune aux deux cadres. */
.appli .volet-rang-0{left:0;}
.appli .volet-rang-1{left:100%;}
.appli .volet-rang-2{left:200%;}

/* Le cycle fait 36 s : quatre temps de 9 s, et 9 s est la durée du fil. Ce n'est
   pas un nombre choisi pour lui-même. Il faut que le volet de découverte retrouve
   le fil à son instant zéro chaque fois qu'il revient, sans quoi le visiteur
   tomberait sur une fiche à moitié partie et le geste n'aurait plus de début.
   Les deux instants où ce volet apparaît sont 9 s et 27 s, l'un et l'autre
   multiples de 9 : l'écran du fil arrive donc toujours sur une fiche entière, et le
   visiteur voit deux balayages complets avant que la bande reparte — le troisième
   accompagne le départ, ce qui se lit comme une conséquence.

   La découverte est au rang 1, c'est-à-dire au milieu de la bande, et c'est ce qui
   fait qu'un cadre balaye en permanence. Sur un palindrome, l'écran du milieu est
   visité deux fois par tour : il tient 16 s sur 36 dans chaque cadre, et comme le
   téléphone est décalé d'un temps ou presque, les deux fenêtres de l'un comblent
   presque exactement les deux creux de l'autre. « Presque » vaut 1,24 s par tour
   depuis que le décalage est passé de 9 s à 8 — c'est mesuré, et c'est raconté plus
   bas, au commentaire de « --decalage ».

   Chaque temps vaut 8 s d'arrêt puis 1 s de glissement, d'où les pourcentages :
   8/36 = 22,222 %, 9/36 = 25 %, et ainsi de suite. La courbe est appliquée à
   l'animation entière, mais elle n'a d'effet qu'entre deux étapes de valeurs
   différentes — les paliers, eux, ne bougent pas. Elle est volontairement raide
   aux deux bouts : un glissement qui démarre et s'arrête mollement a l'air d'un
   décor qui dérive, un glissement qui part net et se pose a l'air d'un geste. */
@keyframes bande{
  0%,     22.222% {transform:translateX(0);}
  25%,    47.222% {transform:translateX(-100%);}
  50%,    72.222% {transform:translateX(-200%);}
  75%,    97.222% {transform:translateX(-100%);}
  100%            {transform:translateX(0);}
}

/* « Ne montre jamais la même chose sur l'ordi et le téléphone. »
   Les deux cadres ne portent déjà pas le même contenu — l'un montre des cabinets,
   l'autre des assistant(e)s, ce sont les deux rôles du produit. Mais ils
   pourraient être sur le même écran au même instant, et la vitrine aurait alors
   l'air de montrer deux fois la même chose.

   Un décalage voisin d'un quart de cycle l'empêche, et le voisinage est étroit. Sur
   une bande qui va et revient, l'écran du milieu est visité deux fois par tour : il
   occupe 16 s sur 36 dans chaque cadre, et il ne reste que 18 s où l'autre cadre
   n'y est pas. Les décalages possibles sont donc rares — autour d'un quart de cycle,
   et nulle part ailleurs : un demi-cycle remettrait les deux cadres sur le même écran
   du milieu. La valeur retenue est 8 s et non 9, pour un motif qui n'a rien à voir
   avec les rangs ; le commentaire de « --decalage », plus bas, dit lequel.

   Le quart de cycle fait par ailleurs tout le reste, et 8 s en garde l'essentiel. Les
   quatre couples de rangs sont (0,1), (1,2), (2,1) et (1,0) : le rang 1 est dans
   chacun, et jamais face à lui-même. C'est ce qui donne les quatre temps que l'on voit —
     · l'ordinateur ouvre une fiche        / le téléphone balaye ;
     · l'ordinateur balaye                 / le téléphone reçoit un kinôme ;
     · l'ordinateur entre dans un échange  / le téléphone balaye ;
     · l'ordinateur balaye                 / le téléphone entre dans un échange.
   Soit, à tout instant, un cadre et un seul en train de balayer des fiches, et
   jamais deux fois le même écran. Rien de cela n'est écrit nulle part : tout vient
   de la place de la découverte dans la bande, décidée dans « assembler-demo.py ».

   Le même calcul donne au passage le droit de mettre la navigation des discussions des
   deux côtés. Le rang 2 ne croise que le rang 1, et le rang 0 non plus : les deux volets
   de navigation ne sont donc jamais à l'écran en même temps, et la vitrine ne se répète
   pas pour autant qu'elle montre deux fois le même geste à deux moments différents — sur
   deux rôles différents, avec des cabinets d'un côté et des assistant(e)s de l'autre.

   Le décalage est négatif parce que « animation-delay » négatif veut dire « démarre
   déjà entamée » : le téléphone est en avance d'un temps, pas en retard de 27 s.

   « Faire en sorte que les pages des 2 supports ne changent pas en même temps. »
   Le quart de cycle valait 9 s, et un temps vaut aussi 9 s — 8 s d'arrêt, 1 s de
   glissement. Décaler d'exactement un temps revient donc à décaler des contenus
   sans décaler l'horloge : les deux bandes glissaient aux mêmes instants, à 8, 17,
   26 et 35 s. Le défaut était invisible au calcul des rangs, qui était juste, et
   bien visible à l'œil, qui voyait les deux appareils tressauter ensemble. C'est
   une leçon à garder : vérifier *ce que l'on montre* ne dit rien de *quand on en
   change*.

   8 s au lieu de 9 séparent les deux horloges, et ce gain-là est exact, pas mesuré :
   chaque cadre glisse pendant quatre fenêtres de son temps propre — [8,9], [17,18],
   [26,27], [35,36] —, ce que disent les paliers de « bande » (22,222 % de 36 s valent
   8 s). Décaler d'un temps entier superpose ces fenêtres une à une : 4 s de glissement
   commun par tour, c'est-à-dire *tous* les glissements. Décaler de 8 s les déplace à
   [0,1], [9,10], [18,19], [27,28] ; elles ne se touchent plus, et les mouvements
   alternent strictement — téléphone à 0, ordinateur à 8, téléphone à 9, ordinateur
   à 17. Un mouvement toutes les 4 à 5 s au lieu de deux toutes les 9.

   Ce que l'on paye en échange est plus lourd que ce que disait la première version de
   ce commentaire, et le corriger vaut mieux que le garder flatteur. Le glissement dure
   1 s : tout décalage qui n'est pas un multiple de 9 fait donc coïncider les rangs
   pendant 1 s à chaque temps. Les quatre couples (0,1), (1,2), (2,1), (1,0) ne sont
   plus seuls — s'y ajoutent des queues de raccord (0,0), (1,1) et (2,2). Sur 1800
   instants du cycle, un cadre étant tenu pour posé quand un de ses volets couvre 90 %
   de la fenêtre :

     décalage    rangs égaux   écrans égaux   glissement commun
       −7 s         18 %            9 %             0 s
     **−8 s**     **6,89 %**     **3,44 %**       **0 s**
       −9 s          0 %             0 %             4 s
      −10 s        3,44 %          5,17 %           0 s
      −11 s          9 %           13,5 %           0 s

   −10 s fait mieux sur les rangs et moins bien sur les écrans, qui sont la seule
   colonne que l'œil voie : voilà pourquoi on mesure au lieu de raisonner, et pourquoi
   on garde 8. Les 3,44 % d'écrans égaux valent 1,24 s par tour, et ils sont entièrement
   de la double découverte, aux deux raccords de 9 s et de 27 s — un cadre finit
   d'arriver sur la découverte quand l'autre s'apprête à la quitter. Les couples (0,0)
   et (2,2) ne se voient pas, eux : ils mettent face à face des écrans différents, le
   profil contre une conversation, les discussions contre le kinôme.

   Ces 1,24 s ne sont pas rattrapables : tout décalage qui les effacerait est un
   multiple de 9, et remettrait les deux bandes en marche ensemble.

   Le prix est ailleurs, et il est payé juste en dessous : 8 n'étant pas un multiple
   de 9, l'écran de découverte du téléphone n'arrive plus sur un début de fiche. Les
   animations du fil doivent recevoir l'horloge de leur cadre pour que la pile, elle,
   retombe juste. */
.cadre-tel .appli{--decalage:-8s;}

/* « --phase-fil » décale le fil **dans** son cadre, sans toucher à la bande.
   « --decalage » cale l'horloge d'un cadre sur celle de l'autre ; celui-ci cale les
   départs de fiches à l'intérieur d'un cadre, et les deux se composent sans se mêler.

   Mesuré, le besoin est précis. Le volet du balayage de l'ordinateur tient l'écran de
   27 s à 35 s, et les trois fiches partaient à 29, 32 et 35 s. La deuxième — celle dont
   naît le kinôme — s'achevait donc à 32,7 s, et il ne restait que 2,3 s pour glisser le
   volet du kinôme (0,8 s), jouer le logo du produit (1,63 s) et laisser voir l'écran.
   La somme passe mal : au relevé, l'arc du logo finissait de se tracer à 33,4 s quand le
   volet ne se posait qu'à 33,5 s. Autrement dit le geste signature du logo se jouait
   **en entier pendant le glissement**, et l'écran du kinôme à nu ne durait qu'une
   demi-seconde. « On n'a pas le temps de la voir » : c'était exact, et c'était mesurable.

   Une seconde de décalage du fil rend une seconde au kinôme — les départs tombent
   maintenant à 28, 31 et 34 s, le deuxième s'achève à 31,7 s, et le budget passe de
   2,3 s à 3,3 s. Le troisième départ, à 34 s, se produit derrière le volet du kinôme,
   donc hors du regard : on ne perd rien de visible à ce décalage.

   Pourquoi le fil et non la bande : allonger le séjour du volet demanderait de sortir
   des quatre segments de 9 s qui composent les 36 s, c'est-à-dire de casser le
   palindrome et de recalculer toute la table des couples. Décaler le fil ne coûte rien
   de tout cela.

   Les quatre animations de l'horloge du fil — « pile », « balayage » et les deux éclats
   des boutons — doivent recevoir cette phase **ensemble**. L'oubli d'une seule allumerait
   un éclat sur le départ d'une autre fiche, ou laisserait l'aiguille repartir à contretemps. */
.cadre-ordi .appli{--phase-fil:-1s;}

/* ---------- La navigation : un volet qui contient plusieurs écrans ----------
   « Quand c'est les écrans de discussion, effectuer de la navigation en passant de mes
   discussions puis en entrant dans une discussion et montre un échange de message. »

   Un volet montrait un écran. Celui des discussions en montre deux sur l'ordinateur, et
   quatre sur le téléphone : la liste, le fil ouvert, le suivi du recrutement qu'on ouvre
   depuis son en-tête, le CV qu'on y dépose. Et ce n'est pas un rang de plus dans la
   bande — la bande garde ses trois rangs — c'est le même volet qui se déplie. Une
   discussion n'est pas un écran qu'on regarde, c'est un endroit où l'on va : posée là,
   déjà ouverte, elle ne disait pas d'où elle venait.

   Le moyen est celui de la bande, un cran plus bas : deux vues côte à côte, et un cadre
   qui coulisse devant elles. Rien n'est inventé que le déplacement, et c'est déjà le
   compromis assumé plus haut — l'application, elle, repeint son conteneur.

   « overflow:hidden » est ici une nécessité, pas une précaution. La vue de rang 1 est
   posée à « left:100% » de son volet ; dans le volet de rang 0 du téléphone, cela tombe
   exactement sur le volet de rang 1 — la découverte. Sans cette coupe, la conversation
   du téléphone s'afficherait par-dessus son propre fil de profils. */
.appli .navigation{position:absolute;inset:0;overflow:hidden;--nav-retard:0s;}
/* Le même décalage que la bande, plus un retard propre au rang du volet. Le volet de
   rang 2 est à l'écran de 18 s à 26 s, celui de rang 0 de 0 s à 8 s : dix-huit secondes
   d'écart, et une seule chorégraphie pour les deux. Le reste du cycle, la navigation est
   hors du cadre — c'est là qu'elle se remet à la liste, sans que ça se voie. */
.appli .volet-rang-2 .navigation{--nav-retard:-18s;}
.appli .navigation > .nav-vue{
  position:absolute;top:0;bottom:0;width:100%;
  animation:navigation 36s cubic-bezier(.76,0,.24,1)
            calc(var(--decalage, 0s) + var(--nav-retard)) infinite;
}
.appli .navigation > .nav-rang-0{left:0;}
.appli .navigation > .nav-rang-1{left:100%;}
/* Le téléphone déroule quatre vues par volet. Le rail est le même, il est seulement plus
   long : chaque vue est posée un cadre plus loin, et le glissement se compte en crans de
   100 %. Deux vues non voisines ne peuvent donc pas se succéder sans qu'on voie passer
   celles du milieu — c'est cette contrainte qui fixe l'ordre d'écriture des vues dans
   l'assembleur, et non une préférence de lecture. */
.appli .navigation > .nav-rang-2{left:200%;}
.appli .navigation > .nav-rang-3{left:300%;}

/* Trois secondes et demie sur la liste — le temps de lire deux intitulés et de voir la
   pastille de non-lus —, une seconde de poussée, puis la discussion jusqu'à la fin du
   temps. Le retour à la liste est une marche, et non un glissement : il tombe à 20 s,
   alors que le volet est hors du cadre depuis onze secondes. Le faire glisser serait
   dépenser une animation pour un mouvement que personne ne verra, et surtout risquer
   qu'on l'aperçoive au moment où le volet revient.

   Cette chorégraphie-ci est celle des volets à deux vues : les rangs 0 et 2 de
   l'ordinateur, et le rang 0 du téléphone. Les deux volets à quatre vues du téléphone ont
   la leur, plus bas. */
@keyframes navigation{
  0%,      9.722% {transform:translateX(0);}
  12.5%,  55.555% {transform:translateX(-100%);}
  55.583%,  100%  {transform:translateX(0);}
}

/* ---------- Les deux volets à quatre vues du téléphone ----------
   Dix écrans en trente-six secondes, c'est la densité qu'on a acceptée pour que la bande
   raconte le parcours entier d'un(e) assistant(e) : on regarde une fiche en entier, on
   répond à une discussion, on y ouvre le suivi, on dépose son CV, on retourne balayer, on
   fait un kinôme, on lui écrit — et la notification du contrat rouvre le tour.

   Les deux chorégraphies qui suivent sont écrites sur l'horloge du cadre et non sur celle
   du volet : « --nav-retard » est remis à zéro pour le rang 2 du téléphone (il valait
   -18 s, ce qui convenait tant que les deux rangs partageaient une même chorégraphie), de
   sorte que 0 % vaut le même instant pour les deux. On peut alors lire les deux tables à
   la suite et vérifier qu'elles ne se chevauchent pas : le rang 1 est à l'écran de 9 s à
   18 s puis de 27 s à 36 s, le rang 2 de 18 s à 27 s.

   Pour mémoire, la conversion : une seconde vaut 2,7778 % de trente-six. Les instants
   ci-dessous sont donnés en secondes de l'horloge du cadre, qui est en avance de huit
   secondes sur l'horloge de la page — « --decalage:-8s ». */
.cadre-tel .appli .volet-rang-2 .navigation{--nav-retard:0s;}

/* ---- rang 1 : quatre vues, deux passes ----
   Le rang 1 est le seul des trois à passer deux fois par tour, et c'est ce qui permet au
   téléphone de tenir dix écrans : son volet de rang 1 joue deux histoires différentes sur
   le même rail.

     première passe, 9 s → 18 s : la découverte, puis la fiche complète. On s'arrête sur
       un cabinet et on le lit en entier. Le rail recule d'un cran — la découverte est
       écrite en seconde position exprès, voir « construire_cadre ».
     seconde passe, 27 s → 36 s : la découverte, le kinôme, l'échange. Le rail avance de
       deux crans, de voisine en voisine.

   Les deux remises en place — le retour à la découverte à 19 s, et le retour depuis
   l'échange à 1 s — sont des marches et non des glissements : elles tombent pendant que le
   volet est hors du cadre, et un glissement n'y serait qu'une dépense qu'on risque
   d'apercevoir au moment où le volet revient.

   L'arrivée du kinôme à 29,9 s n'est pas un chiffre rond, et c'est le voile animé qui le
   fixe : il doit achever de monter à l'instant où la vue se pose. Voir
   « --kinome-retard » plus bas, où le même raisonnement est tenu pour l'ordinateur. */
.cadre-tel .appli .volet-rang-1 .navigation > .nav-vue{animation-name:navigation-tel-fil;}
@keyframes navigation-tel-fil{
  0%,      2.778% {transform:translateX(-300%);}  /* l'échange, hors cadre */
  2.806%, 36.111% {transform:translateX(-100%);}  /* la découverte, 1 s → 13 s */
  38.889%,52.778% {transform:translateX(0);}      /* la fiche complète, 14 s → 19 s */
  52.806%,81.111% {transform:translateX(-100%);}  /* la découverte, 19 s → 29,2 s */
  83.056%,90.556% {transform:translateX(-200%);}  /* le kinôme, 29,9 s → 32,6 s */
  92.5%,    100%  {transform:translateX(-300%);}  /* l'échange, 33,3 s → 36 s */
}

/* ---- rang 2 : quatre vues, une passe ----
   Le geste entier d'un(e) assistant(e) qui répond : la liste, le fil, le suivi du
   recrutement qu'on ouvre depuis l'en-tête du fil, le CV qu'on y dépose. Neuf secondes
   pour quatre vues, dont trois glissements de sept dixièmes.

   La conversation a la part la plus large — deux secondes sept — parce que c'est la seule
   des quatre où quelque chose arrive pendant qu'on la regarde : les deux derniers messages
   s'y posent l'un après l'autre. Voir « arrivee-message-tel ». Les trois autres sont des
   écrans qu'on lit, et un écran qu'on lit n'a pas besoin d'être montré plus longtemps
   qu'il ne faut pour en saisir la forme. */
.cadre-tel .appli .volet-rang-2 .navigation > .nav-vue{animation-name:navigation-tel-fil-2;}
@keyframes navigation-tel-fil-2{
  0%,     54.444% {transform:translateX(0);}      /* la liste, 18 s → 19,6 s */
  56.389%,63.889% {transform:translateX(-100%);}  /* la conversation, 20,3 s → 23 s */
  65.833%,69.444% {transform:translateX(-200%);}  /* le suivi, 23,7 s → 25 s */
  71.389%,76.389% {transform:translateX(-300%);}  /* le CV déposé, 25,7 s → 27,5 s */
  76.667%,  100%  {transform:translateX(0);}      /* retour à la liste, hors cadre */
}

/* ---------- Le kinôme de l'ordinateur : une fois par tour, et pas deux ----------
   Le volet de rang 1 est le seul des six à passer **deux fois** par tour. La bande est
   un palindrome — rangs 0, 1, 2, 1, 0 — si bien que le rang 1 est à l'écran de 9 s à
   18 s, puis de nouveau de 27 s à 36 s. C'est ce qui en fait la bonne place pour le
   balayage, qui gagne à être vu longtemps, et c'est ce qui complique le kinôme qu'on y
   déplie maintenant.

   Car il ne peut pas se déplier aux deux passages, et la raison a deux fois changé de
   nature. Elle était d'évitement : le téléphone montrait son propre kinôme au rang 2, le
   premier passage du rang 1 de l'ordinateur tombait dessus presque exactement, et deux
   écrans « Nouveau kinôme ! » côte à côte au même instant sont la seule chose que la
   démonstration s'interdit depuis le début. Le funnel a ensuite pris le rang 2 du
   téléphone, et la collision a disparu avec lui. Le kinôme du téléphone est aujourd'hui
   revenu — à son rang 1, déplié derrière son balayage comme ici — et il ne se joue pas
   davantage aux deux passages, pour la raison qui suit et qui vaut des deux côtés. Les
   deux célébrations se croisent donc à dix secondes d'écart et jamais ensemble.

   La raison qui vaut est celle du produit, et elle est plus solide : « like un premier
   profil, like un second puis kinôme créé ». Le kinôme est ce qui **suit** deux
   validations, pas ce qui les accompagne. Or les trois fiches partent à 2 s, 5 s et 8 s
   du cycle de 9 s du fil : au premier passage du rang 1, la seconde carte retenue
   s'envole à 14 s, et il reste une carte à juger derrière elle. Déplier le kinôme là
   l'annoncerait au milieu du balayage, avant que le second like ait eu lieu. Au second
   passage, elle s'envole à 31,7 s et le fil est allé au bout : le kinôme arrive à sa
   place, qui est la fin.

   Ce qui n'a pas changé, c'est le coût : une chorégraphie à deux visages pour une seule
   pièce de balisage, le reste du tour la navigation restant sur la découverte. C'est le
   prix du palindrome, et il se paie une fois.

   Le compte des secondes, maintenant, parce qu'il tombe juste à la demi-seconde près et
   que ce n'est pas un hasard — c'est ce qui a fixé 31,8 s.

   Le fil dure 9 s, les trois fiches sont déphasées de 3 s, et le départ d'une fiche
   occupe 22 % à 30 % de son cycle. Les trois partent donc à 2 s (rang 0, écartée), 5 s
   (rang 2, retenue) et 8 s (rang 1, retenue) modulo 9. La **deuxième** carte à partir est
   celle de rang 2, et dans la fenêtre de 27 s à 36 s — « --phase-fil » comprise — elle
   s'envole de 31,0 s à 31,7 s. Le volet glisse à 31,8 s, c'est-à-dire à l'image près où
   elle achève de sortir du cadre : l'écran du kinôme arrive derrière la carte qui vient
   d'être retenue, et non par-dessus. La demande était « un kinôme au bout de la deuxième
   fiche » ; c'est littéralement ce que disent ces deux nombres.

   Le rang 2 est la mieux notée des trois, l'assembleur triant par score croissant, et
   c'est pour cela que le pilote élit deux cartes d'accompagnement moins compatibles que
   le kinôme. Voir « empiler_les_fiches » dans assembler-demo.py.

   Le retour à la découverte est une marche, à 1 s, soit dix secondes avant que le volet
   ne revienne — même procédé que « navigation » plus haut, même raison.

   Le sélecteur nomme le cadre, et il doit le nommer. Les deux cadres ont un volet de
   rang 1 qui se déplie derrière un balayage, et ils n'y mettent ni le même nombre de vues
   ni les mêmes instants : écrite sur « .appli », cette règle imposait au téléphone la
   chorégraphie de l'ordinateur — deux vues là où il en a quatre, donc le kinôme et
   l'échange laissés hors du cadre pour tout le tour. */
.cadre-ordi .appli .volet-rang-1 .navigation > .nav-vue{animation-name:navigation-kinome;}
@keyframes navigation-kinome{
  0%,      2.778% {transform:translateX(-100%);}   /* encore le kinôme, hors cadre */
  2.779%, 88.333% {transform:translateX(0);}       /* la découverte, jusqu'à 31,8 s */
  89.444%,  100%  {transform:translateX(-100%);}   /* le kinôme, de 32,2 s à 36 s */
}

/* ---------- L'échange : les deux derniers messages arrivent ----------
   Entrer dans une discussion déjà complète, c'est ouvrir une archive. Un échange se
   reconnaît à ce qu'il se fait : une question, puis la réponse. Les deux derniers
   messages du fil arrivent donc l'un après l'autre, une fois la poussée terminée.

   Rien n'est ajouté : ces deux bulles sont dans le fil capturé, écrites par le prototype,
   et le pilote vérifie qu'elles viennent bien des deux bords — sans quoi « l'échange »
   serait deux répliques du même interlocuteur. Elles sont désignées par leur place dans
   la zone défilante, comme tout le reste de cette feuille ; le pilote vérifie aussi que
   les deux derniers blocs sont bien des bulles, et non une bannière posée après elles.

   L'opacité et la translation seules, jamais l'affichage : une bulle retirée du flux
   ferait sauter la hauteur du fil à chaque arrivée, et la conversation tremblerait.

   Ces règles nomment leur écran, et c'est la seule exception de la feuille à la doctrine
   « le rang, pas le nom ». Elle a une date : tant qu'un seul volet se dépliait, « la
   seconde vue d'une navigation » désignait la conversation sans ambiguïté. Le funnel en a
   mis cinq autres à cette même place — le match, le contrat, la signature, le CV et le
   suivi se déplient tous en seconde vue de leur volet, et la conversation est devenue
   l'une des six. Le rang a cessé d'identifier ce qu'il contient, et c'est le nom qui
   reprend la charge.

   Elles nomment aussi leur cadre, et c'est la correction suivante. Trois fils sont
   aujourd'hui ouverts dans la bande, à trois instants et sur deux horloges : la
   conversation de l'ordinateur en seconde vue de son rang 0, celle du téléphone en
   seconde vue de son rang 2, et l'échange du kinôme en quatrième vue de son rang 1. Un
   seul réglage pour les trois ferait arriver les bulles pendant que leur vue est hors du
   cadre — ce qui est exactement ce qui se passait : la conversation du téléphone était
   atteinte par la table de l'ordinateur et montrait un fil amputé de ses deux dernières
   bulles pendant toute la durée où on la regarde.

   Rien n'était cassé pour autant le jour où le nom a été ajouté : l'écran du kinôme tel
   qu'il est capturé aujourd'hui n'a pas de zone défilante, et le sélecteur ne l'atteignait
   donc pas. C'est précisément pourquoi la correction a été faite tout de suite — la règle
   ne tenait plus que par une propriété du produit que personne ne surveille, et le jour où
   le kinôme gagnerait une liste, ses deux derniers éléments auraient disparu cinq secondes
   puis se seraient effacés au milieu du temps, sans rien pour dire d'où cela venait. */
.cadre-ordi .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :nth-last-child(2),
.cadre-ordi .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child{
  animation:arrivee-message 36s ease-out
            calc(var(--decalage, 0s) + var(--nav-retard)) infinite;
}
/* La réponse arrive une seconde après la question. Un délai moins négatif retarde —
   « animation-delay » compte à rebours depuis le début du cycle. */
.cadre-ordi .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child{
  animation-delay:calc(var(--decalage, 0s) + var(--nav-retard) + 1s);
}
/* 13,889 % = 5 s, l'instant où la première bulle commence à monter ; la seconde la suit
   d'une seconde par son délai. Les deux s'effacent à 55,555 %, en même temps que la
   navigation revient à la liste : le volet est alors hors du cadre depuis longtemps, et
   il faut qu'il y retrouve un fil vierge de ses deux dernières bulles. */
@keyframes arrivee-message{
  0%,     13.889% {opacity:0;transform:translateY(9px);}
  16.667%,55.555% {opacity:1;transform:none;}
  55.583%,  100%  {opacity:0;transform:translateY(9px);}
}

/* ---- la conversation du téléphone ----
   Même geste, autre horloge. La vue est à l'écran de 20,3 s à 23 s en temps de cadre, et
   son volet porte « --nav-retard:0s » — les instants ci-dessous se lisent donc sur la même
   table que les deux chorégraphies de navigation du téléphone.

   La question se pose à 21 s, la réponse huit dixièmes plus tard : il reste un peu plus
   d'une seconde pour la lire avant que la vue ne glisse vers le suivi. C'est court, et
   c'est le prix de dix écrans en trente-six secondes ; ce qui compte ici est qu'on voie la
   réponse **arriver**, et non qu'on ait le temps de la lire deux fois. Les deux bulles
   s'effacent à 28 s, une seconde après que le volet a quitté le cadre. */
.cadre-tel .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :nth-last-child(2),
.cadre-tel .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child{
  animation:arrivee-message-tel 36s ease-out var(--decalage, 0s) infinite;
}
.cadre-tel .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child{
  animation-delay:calc(var(--decalage, 0s) + 0.8s);
}
@keyframes arrivee-message-tel{
  0%,     56.389% {opacity:0;transform:translateY(9px);}  /* 20,3 s : la vue arrive */
  58.333%,77.778% {opacity:1;transform:none;}             /* 21 s → 28 s */
  77.806%,  100%  {opacity:0;transform:translateY(9px);}
}

/* ---- l'échange que le kinôme ouvre ----
   Ce fil-là n'a que deux bulles, et c'est le produit qui les écrit : « ensureConversation »
   ouvre la conversation d'un kinôme sur « Bonjour ! Ravi(e) de ce match, on échange cette
   semaine ? », et le pilote y répond par les deux gestes de l'application. « Les deux
   dernières bulles » et « le fil entier » sont donc ici la même chose : on voit la
   conversation se remplir depuis rien, ce qui est exactement ce qu'elle vient de faire.

   La vue est à l'écran de 33,3 s à 36 s, et c'est la dernière du tour. Elle n'a pas besoin
   d'effacement : le cycle repart à 0 %, où les deux bulles sont déjà invisibles, et le
   volet est alors hors du cadre pour neuf secondes. */
.cadre-tel .appli .navigation > .nav-rang-3[data-ecran="echange"] .scroll > :nth-last-child(2),
.cadre-tel .appli .navigation > .nav-rang-3[data-ecran="echange"] .scroll > :last-child{
  animation:arrivee-message-echange 36s ease-out var(--decalage, 0s) infinite;
}
.cadre-tel .appli .navigation > .nav-rang-3[data-ecran="echange"] .scroll > :last-child{
  animation-delay:calc(var(--decalage, 0s) + 0.6s);
}
@keyframes arrivee-message-echange{
  0%,      92.5% {opacity:0;transform:translateY(9px);}   /* 33,3 s : la vue arrive */
  94.167%,  100% {opacity:1;transform:none;}              /* 33,9 s → 36 s */
}

/* ---------- Le fil : trois fiches réelles empilées ----------
   Le prototype n'affiche qu'un profil à la fois, c'est le principe du fil. Pour
   qu'un balayage se voie, il faut le suivant derrière : la grille superpose les
   trois fiches dans la même cellule, ce qui donne aussi à la pile la hauteur de
   la plus haute sans avoir à la mesurer. */
/* « isolation » n'est pas un ornement : c'est lui qui empêche les plans de la pile de
   sortir de la pile, et son absence était un vrai défaut d'affichage.

   L'animation « pile » ci-dessous fait monter la fiche de devant à « z-index:3 ». La
   pile étant une grille non positionnée, elle ne forme aucun contexte d'empilement : ce
   3 ne se comparait donc pas aux 1 et 2 de ses sœurs mais à tout le contenu de la zone
   de balayage, c'est-à-dire aux boutons « passer » et « ça m'intéresse », qui sont à 1,
   et au voile qui les sépare de la fiche, qui est à 0. La fiche passait devant les deux.

   Sur l'ordinateur cela ne se voyait pas : la fiche d'un candidat mesure 618 px dans une
   zone qui en offre 841, elle s'arrête bien au-dessus des boutons. Sur le téléphone la
   fiche d'un cabinet en fait 640 pour 695, moins les 98 px que l'application réserve aux
   boutons : elle déborde de 43 px. Ces 43 px, le voile aurait dû les effacer en fondu —
   c'est sa raison d'être — et ils se peignaient par-dessus. Le résultat à l'écran était
   un coin de fiche blanc posé sur le bouton rouge.

   Une ligne suffit à le corriger, et c'est la bonne ligne : elle ne retouche aucun des
   « z-index » de l'animation, qui restent justes entre eux, elle dit seulement que leur
   portée s'arrête à la pile. Le débordement, lui, reste — il est normal, c'est ce que
   fait un fil sur un écran de téléphone — et il se lit désormais comme l'application le
   prévoit : la fiche se fond dans le fond avant d'atteindre les boutons. */
.pile{display:grid;isolation:isolate;}
/* Le fil dure 9 s et la bande 36 : le fil tourne donc quatre fois par tour de bande,
   et il n'y a pas d'horloge commune à retrouver — il suffit que la découverte *arrive*
   sur un début de fiche. L'arrivée a lieu à la fin d'un glissement, c'est-à-dire à un
   multiple de 9 s de l'horloge du cadre ; ajouter le décalage du cadre au retard de la
   fiche fait que cette horloge est bien celle du fil. Sans cela, le téléphone entrerait
   en découverte sur une fiche à moitié partie. */
.pile > .fiche{
  grid-area:1 / 1;
  animation:pile 9s calc(var(--decalage, 0s) + var(--retard, 0s) + var(--phase-fil, 0s)) infinite;
}
/* La fiche de l'application est translucide — un fond à 38 % posé sur celui de
   l'écran. Seule, elle se lit parfaitement ; empilée, on lit à travers elle le
   nom et l'âge du profil suivant, et les trois fiches se brouillent.
   Le fond rendu à chacune est celui de l'application, pas une couleur choisie :
   c'est exactement ce que la fiche a derrière elle quand elle est seule. */
.pile > .fiche{background:var(--bg);border-radius:var(--radius-lg);}

/* Piège : « z-index » n'est pas une propriété discrète, le navigateur interpole
   l'entier et arrondit. Entre une étape à 2 et une étape à 3, le plan change donc
   à mi-parcours au lieu de la fin — la fiche qui remonte repassait devant celle
   qui est en train de partir, et le geste de balayage se jouait caché derrière
   elle. D'où les étapes à 65.99 % et 99.99 % : elles figent le plan jusqu'au
   dernier instant. Elles ne déclarent que « z-index », le mouvement et l'opacité
   continuent donc de s'interpoler d'une étape utile à l'autre. */
@keyframes pile{
  0%,22%    {transform:none;opacity:1;z-index:3;}
  30%       {transform:translate(calc(118% * var(--sens,1)), -4%) rotate(calc(11deg * var(--sens,1)));opacity:0;z-index:3;}
  30.01%    {transform:translateY(10px) scale(.94);opacity:0;z-index:1;}
  38%       {transform:translateY(10px) scale(.94);opacity:.5;z-index:1;}
  65.99%    {z-index:1;}
  66%       {transform:translateY(5px) scale(.97);opacity:.75;z-index:2;}
  99.99%    {z-index:2;}
  100%      {transform:none;opacity:1;z-index:3;}
}

/* L'aiguille du goniomètre rejoue son balayage à chaque fiche qui arrive.
   L'application le fait à chaque rendu, en JavaScript ; ici la même image doit
   revenir en boucle, donc c'est une animation de 9 s qui n'allume qu'un
   dixième de son cycle. « --gauge-deg » est l'angle réel du score, posé par le
   prototype sur chaque fiche — il n'est pas recalculé ici. */
.appli .fiche .gauge-needle{
  animation:balayage 9s calc(var(--decalage, 0s) + var(--retard, 0s) + var(--phase-fil, 0s)) infinite;
}
/* Le retour à zéro est la moitié du geste, et c'est celle qu'on oublie. La première
   version tenait le score de 11 % à 100 % : l'aiguille ne revenait donc à zéro qu'à
   l'instant même où la fiche prenait le devant. Or elle le prend progressivement —
   entre 66 % et 100 % elle remonte de l'arrière-plan en passant de .75 à 1 d'opacité —
   si bien qu'une demi-seconde avant son arrivée on la voyait déjà, aiguille plantée au
   score, avant qu'elle ne saute à zéro pour balayer. Mesuré : à 11,5 s la fiche de
   devant affichait 167°, à 12 s elle affichait 0°.
   L'aiguille revient donc à zéro à 32 % du cycle, juste après que la fiche est partie
   (opacité nulle à 30,01 %) et bien avant qu'elle ne remonte. Une fiche qui attend
   derrière montre ainsi un cadran vierge — ce qui est d'ailleurs juste : son score n'a
   pas encore été annoncé. */
/* La courbe est celle de l'application, et c'est tout l'objet de la déclaration :
   « gauge-sweep » y est tracée en « cubic-bezier(.33, 1.15, .45, 1) » sur une seconde.
   La vitrine avait gardé la durée et perdu la courbe — sans mention, une animation
   prend « ease », qui démarre lentement. L'aiguille partait donc mollement : 29 % de
   son arc au bout de deux dixièmes de seconde, contre 60 % avec la courbe juste.
   Deux dixièmes, c'est peu à compter et beaucoup à regarder — l'œil lit ce départ comme
   un temps mort entre l'arrivée de la fiche et le geste, c'est-à-dire exactement ce que
   le balayage est censé ne pas avoir.
   La courbe de l'application dépasse légèrement le score avant de s'y poser (1,15 en
   ordonnée de contrôle) : une aiguille qui se repose, et non un curseur qui glisse.
   Elle est posée sur l'étape de départ et non sur l'animation entière, sans quoi le
   retour à zéro dépasserait lui aussi — vers les négatifs, là où un goniomètre n'a pas
   de graduation. Il est hors cadre, donc invisible, mais une animation qu'on ne voit
   pas reste une animation qu'on ne veut pas écrire de travers. */
@keyframes balayage{
  0%          {transform:rotate(0deg);animation-timing-function:cubic-bezier(.33,1.15,.45,1);}
  11%,29.9%   {transform:rotate(var(--gauge-deg,0deg));}
  32%,100%    {transform:rotate(0deg);}
}

/* ---------- Le nombre, qui part de zéro avec l'aiguille ----------
   L'aiguille balayait, le nombre non : il affichait le score d'arrivée pendant que
   l'aiguille le cherchait encore. Un instrument dont le cadran annonce le résultat
   avant que l'aiguille y arrive n'est pas un instrument, c'est une étiquette — et il
   laissait voir, pendant une seconde, que la mesure était jouée.

   « --score » est le score posé sur le compteur par l'assembleur, à côté du
   « data-pct » dont il est tiré. « --compte » est la valeur courante, celle qui monte
   de 0 jusqu'au score. La séparation est la même que pour la jauge de la section
   « projet » dans accueil.css, et pour la même raison : une cible et un curseur ne se
   confondent pas.

   « @property » est ce qui rend la chose possible — une propriété personnalisée non
   déclarée ne s'interpole pas, elle bascule d'un coup à mi-parcours — et « counter() »
   est ce qui la rend lisible, le contenu d'un pseudo-élément étant le seul endroit où
   le CSS sait écrire un nombre.

   Ce que cette construction donne gratuitement mérite d'être noté, parce que c'est ce
   qui l'a fait préférer à un compteur en JavaScript : la valeur au repos est le score
   lui-même. Un compteur qu'aucune animation n'atteint — mouvement réduit, « @property »
   inconnu du navigateur, jauge ajoutée un jour par une capture dans un écran qui n'est
   ni une fiche du fil ni un profil — affiche donc le bon nombre sans qu'on ait eu à le
   prévoir. Les trois pièges habituels de ce fichier (reposer l'état d'arrivée quand on
   éteint une animation, prévoir le secours sans JS, ne pas laisser un écran à zéro)
   sont tous les trois désamorcés par la même ligne, et non par trois règles de plus. */
@property --compte{
  syntax:"<integer>";
  initial-value:0;
  inherits:true;
}
/* Le texte du balisage reste, et il travaille : c'est lui qui donne sa largeur à la
   boîte. Sans lui, le nombre naîtrait à deux caractères (« 0% ») pour finir à trois
   (« 86% »), et la boîte s'élargirait d'un chiffre en cours de route — dans une colonne
   centrée, cela décale tout le groupe à chaque dizaine franchie. La boîte est donc
   taillée une fois pour toutes sur la valeur d'arrivée, le texte est masqué, et le
   compteur se pose par-dessus, centré dedans. « visibility » et non « opacity » ni
   « font-size:0 » : c'est la seule des trois qu'un enfant peut rallumer pour lui seul,
   et la taille du texte est écrite en ligne par l'application — une déclaration de
   feuille ne passerait pas devant sans « !important ».
   Le texte masqué est aussi le secours : feuille non chargée, compteur affiché tel
   quel. La valeur lue par les lecteurs d'écran, elle, ne dépend d'aucun des deux — elle
   est dans l'« aria-label » du groupe, que l'application écrit en toutes lettres. */
.appli .gauge-pct{position:relative;visibility:hidden;--compte:var(--score,0);}
.appli .gauge-pct::after{
  position:absolute;inset:0;visibility:visible;text-align:center;
  counter-reset:compte var(--compte);
  content:counter(compte) "%";
}
/* Mêmes étapes que « balayage », au centième près : les deux doivent partir et arriver
   sur la même image, sans quoi on lit l'écart plutôt que la mesure.
   La courbe, elle, diffère, et c'est délibéré. L'aiguille dépasse légèrement le score
   avant de s'y poser (1,15 en ordonnée de contrôle) : sur une aiguille lestée, ce
   dépassement est le geste même d'un instrument. Sur un nombre, il afficherait 88 %
   avant de redescendre à 86 %, et un pourcentage qui se corrige ne se lit pas comme un
   ressort mais comme un bogue. Le nombre prend donc la courbe des jauges de
   l'application (« .jauge-comp » dans app.css), qui freine sans dépasser. C'est le même
   arbitrage, et le même couple de courbes, que la jauge de la section « projet ». */
.appli .fiche .gauge-pct{
  animation:comptage 9s calc(var(--decalage, 0s) + var(--retard, 0s) + var(--phase-fil, 0s)) infinite;
}
@keyframes comptage{
  0%          {--compte:0;animation-timing-function:cubic-bezier(.22,1,.36,1);}
  11%,29.9%   {--compte:var(--score,0);}
  32%,100%    {--compte:0;}
}

/* La fiche complète d'un cabinet porte le même goniomètre, mais hors de la pile :
   aucune des deux règles ci-dessus ne l'atteignait. Il ne lui restait donc que le
   balayage unique de la feuille de l'application, joué une fois au chargement de la
   page — c'est-à-dire pendant que ce volet est encore hors du cadre. Le visiteur ne
   voyait jamais l'aiguille partir : à chaque passage, elle était déjà posée.

   Le profil n'arrive pas toutes les 9 s comme une fiche du fil, il arrive une fois par
   tour de bande ; son balayage est donc calé sur les 36 s de la bande, pas sur le fil.
   Le volet de rang 0 est à l'écran de 0 à 8 s, et 2,778 % de 36 s font exactement la
   seconde que l'application met à tracer son arc.

   Le retour à zéro est l'autre moitié du problème, et la moins évidente : une aiguille
   qui reste au score jusqu'à la fin du tour reviendrait à zéro pendant le glissement
   d'entrée — le seul moment où le volet est à la fois visible et pas encore arrivé.
   On la repose donc à 91,667 % (33 s), au milieu des vingt-sept secondes où le volet
   est hors cadre, et elle attend à zéro que le glissement l'amène.

   Le sélecteur ne dit plus « le volet qui s'appelle profil » mais « ce qui s'appelle
   profil », et le détour a été payé pour l'apprendre. Le jour où le volet de rang 0 de
   l'ordinateur est devenu double — la fiche puis le kinôme —, son « data-ecran » est
   devenu « profil puis match » et le nom du profil est descendu d'un cran, sur la
   « .nav-vue ». La règle a cessé de s'appliquer en silence, et le défaut qu'elle corrige
   est revenu tel quel : l'aiguille déjà posée à chaque passage. Rien ne l'a signalé,
   parce qu'une animation qui manque ne casse pas une page — elle la rend seulement
   moins vivante, ce qu'aucun garde-fou ne mesurait. D'où le relevé des animations
   ajouté à la liste de vérification : « balayage-volet » doit y figurer une fois.

   « data-ecran » ne vit que sur les volets et sur les vues de navigation, et un seul le
   porte à « profil » : l'attribut seul est donc aussi précis que l'était le couple, et
   il survit au déplacement de l'écran dans la hiérarchie. */
.appli [data-ecran="profil"] .gauge-needle{
  animation:balayage-volet 36s var(--decalage, 0s) infinite;
}
/* Même courbe, et elle compte davantage ici. Le profil arrive par un glissement d'une
   seconde, et sa jauge est du côté gauche du volet : elle entre donc dans le cadre deux
   dixièmes avant que le volet se pose. Mesuré — à 35,8 s la jauge est visible, à 36,0 s
   le volet est arrivé. Pendant ces deux dixièmes l'aiguille est à zéro et c'est juste :
   le glissement est encore en cours, et deux mouvements à la fois ne se lisent pas.
   Mais s'il faut ensuite deux dixièmes de plus pour que l'aiguille veuille bien partir,
   le compte monte à presque une demi-seconde d'aiguille immobile sur une jauge qu'on
   regarde — et c'est cela qu'on voyait comme un décalage. La courbe de l'application
   supprime la seconde moitié : l'aiguille part à l'instant où le volet se pose. */
@keyframes balayage-volet{
  0%              {transform:rotate(0deg);animation-timing-function:cubic-bezier(.33,1.15,.45,1);}
  2.778%,91.667%  {transform:rotate(var(--gauge-deg,0deg));}
  94.444%,100%    {transform:rotate(0deg);}
}
/* Et son nombre avec elle, sur la même horloge de 36 s et aux mêmes étapes. Le laisser
   de côté aurait donné l'inverse exact du défaut qu'on vient de corriger : un fil où le
   nombre se compte, et la fiche complète — celle qu'on ouvre justement pour regarder le
   score de près — où il est déjà écrit. */
.appli [data-ecran="profil"] .gauge-pct{
  animation:comptage-volet 36s var(--decalage, 0s) infinite;
}
@keyframes comptage-volet{
  0%              {--compte:0;animation-timing-function:cubic-bezier(.22,1,.36,1);}
  2.778%,91.667%  {--compte:var(--score,0);}
  94.444%,100%    {--compte:0;}
}

/* Les deux boutons de jugement s'allument à l'instant où la fiche part, et du
   bon côté : le sens du balayage est posé fiche par fiche dans le balisage.
   Ordre d'apparition : Marion (50 %) écartée à 2,0 s, Karim (65 %) retenu à
   5,0 s, Baptiste (70 %) retenu à 8,0 s. Ces instants sont ceux du fil, et le fil
   suit maintenant l'horloge de son cadre : les boutons reçoivent le même décalage
   que la pile, sans quoi un éclat s'allumerait sur le départ d'une autre fiche. */
.appli .swipe-btn.pass{animation:eclat-pass 9s calc(var(--decalage, 0s) + var(--phase-fil, 0s)) infinite;}
.appli .swipe-btn.like{animation:eclat-like 9s calc(var(--decalage, 0s) + var(--phase-fil, 0s)) infinite;}
@keyframes eclat-pass{
  0%,20%,28%,100% {transform:none;box-shadow:0 0 0 4px var(--bg), var(--shadow-soft);}
  23%             {transform:scale(1.1);box-shadow:0 0 0 4px var(--bg), 0 0 0 12px color-mix(in srgb, var(--alert) 26%, transparent);}
}
@keyframes eclat-like{
  0%,53%,62%,86%,94%,100% {transform:none;box-shadow:0 0 0 4px var(--bg), var(--shadow-soft);}
  56%,89%                 {transform:scale(1.1);box-shadow:0 0 0 4px var(--bg), 0 0 0 12px color-mix(in srgb, var(--ok) 26%, transparent);}
}

/* ---------- Le voile du kinôme : l'animation du produit, remise sur l'horloge ----------
   Quand un kinôme tombe, l'application ne change pas d'écran : elle pose sur le cadre un
   voile qui couvre le changement — un dégradé radial couleur accent, le logo de Kinôme
   en SVG, son arc qui se trace, son aiguille qui balaie, une onde qui s'échappe du
   pivot — et le retire d'elle-même au bout d'une seconde trois quarts. C'est le plus
   beau geste du produit, et la vitrine le jetait : le pilote retirait le voile avant de
   relever l'écran, parce qu'un voile figé sur une image fixe est un cache, pas une
   animation.

   Il est maintenant relevé, et rejoué ici. **Rien n'est redessiné** : le balisage du
   voile et son SVG viennent de « jouerIntroMatch », le dégradé et les formes de
   « app.css », et les quatre mouvements ci-dessous ne font que reprendre, une à une, les
   étapes et les courbes que le produit écrit pour eux. Ce qui change est l'horloge, et
   elle seule.

   Car c'est tout le problème. Les animations du produit sont des coups uniques, avec des
   délais absolus — 0,1 s pour l'arc et l'aiguille, 0,78 s pour l'onde, 1,3 s pour la
   sortie — qui se déclenchent au moment où l'écran apparaît. Dans la vitrine il n'y a pas
   de moment : il y a une bande qui tourne en trente-six secondes et des écrans déjà
   dessinés qui défilent dedans. Les quatre animations sont donc réécrites sur ce
   cycle-là, inertes la plus grande partie du temps et actives pendant la seule fenêtre
   où l'écran du kinôme est visible.

   La conversion est une multiplication et rien d'autre : une seconde vaut 2,7778 % de
   trente-six, et les 1,76 s du geste en occupent 4,889 %. Les pourcentages qui suivent
   sont les instants du produit passés dans cette table, à la troisième décimale.

   Le repos est à opacité nulle, et c'est ce qui rend l'ensemble sûr. Le voile du produit
   compte sur « animation-fill-mode: both » pour être invisible avant de commencer ;
   appuyée sur un délai de trente-deux secondes, cette ruse laisserait le voile opaque
   par-dessus le kinôme pendant la moitié d'un tour. On déclare donc l'état de repos, et
   les étapes n'ont plus qu'à allumer leur fenêtre. C'est aussi ce qui fait que le voile
   disparaît proprement quand on demande moins d'animations : sans animation, il reste à
   son repos, et l'écran du kinôme se montre tout nu. */
.demo .appli .match-intro{
  opacity:0;
  /* Même construction que partout ailleurs dans cette feuille : l'horloge du cadre, plus
     un retard propre à l'endroit où le kinôme se déplie. Les deux cadres le déplient
     derrière leur balayage, au second passage de leur rang 1, et à deux instants
     différents de leur horloge : 32 s pour l'ordinateur, 29,7 s pour le téléphone. Les
     deux retards sont des instants d'arrivée, lus sur la même bande que tout le reste. */
  animation:kinome-voile 36s linear
            calc(var(--decalage, 0s) + var(--kinome-retard, 0s)) infinite;
}
/* 32 s, et ce nombre est le seul de la feuille qui soit contraint des deux côtés à la
   fois. Deux exigences se contredisent, et c'est ce qui rend le réglage délicat :

     — le voile doit **précéder** la pose du volet. S'il arrive après, on voit l'écran du
       kinôme à nu, puis se faire recouvrir, puis se dévoiler : un dévoilement qui
       dévoile une chose déjà vue. C'est le défaut qu'avait la version à 33 s.
     — le logo doit se jouer **après** la pose du volet. S'il se joue pendant, ses gestes
       passent sur un cadre qui glisse encore et ne se lisent pas. C'est le défaut
       qu'avait la version à 32,7 s, où l'arc achevait son tracé à 33,4 s pour un volet
       posé à 33,5 s.

   Or le voile et le logo partagent ce seul retard, parce que dans le produit ils sont un
   seul geste. On les concilie par la forme même de la courbe du produit : le voile monte
   en 0,22 s, tandis que le premier geste du logo — le tracé de l'arc — ne commence qu'à
   0,10 s et ne devient lisible qu'après. Il suffit donc que le volet se pose à l'instant
   où le voile achève de monter, soit à retard + 0,22 s.

   D'où la suite, posée à l'envers : le volet se pose à 32,2 s, donc le retard vaut 32 s,
   donc le glissement part de 31,8 s, et il peut partir de 31,8 s parce que la fiche a
   fini de s'envoler à 31,7 s — ce que le décalage du fil a rendu possible. Relevé : le
   volet arrive avec un voile à 0,99 d'opacité, l'arc se trace sur un cadre posé, et
   l'écran du kinôme à nu dure 1,28 s au lieu d'une demi-seconde.

   Le même raisonnement, mené sur l'horloge du téléphone, donne 29,7 s. Sa vue de kinôme
   se pose à 29,9 s — fin du glissement de « navigation-tel-fil » — et le voile doit
   achever de monter là, donc partir deux dixièmes avant. L'écran du kinôme à nu dure
   ensuite 1,14 s, contre 1,28 s sur l'ordinateur : le téléphone a quatre vues à faire
   tenir dans neuf secondes là où l'ordinateur en a deux.

   Les deux règles nomment leur cadre. Écrite sur « .appli », une seule valeur partait du
   principe que les deux bandes posent leur kinôme au même instant de leur horloge, ce
   qui n'est vrai que par accident : c'est la chorégraphie de chaque cadre qui le dit, et
   les deux chorégraphies sont différentes. */
.cadre-ordi .appli .volet-rang-1 .match-intro{--kinome-retard:32s;}
.cadre-tel .appli .volet-rang-1 .match-intro{--kinome-retard:29.7s;}
.demo .appli .mi-arc{
  animation:kinome-trace 36s linear
            calc(var(--decalage, 0s) + var(--kinome-retard, 0s)) infinite;
}
.demo .appli .mi-needle{
  animation:kinome-aiguille 36s linear
            calc(var(--decalage, 0s) + var(--kinome-retard, 0s)) infinite;
}
.demo .appli .mi-onde{
  animation:kinome-onde 36s linear
            calc(var(--decalage, 0s) + var(--kinome-retard, 0s)) infinite;
}

/* « linear » sur le raccourci, et les vraies courbes posées étape par étape. Ce n'est pas
   une coquetterie : une fonction de transition s'applique à **chaque intervalle** de la
   boucle, y compris aux longs plats où il ne se passe rien. Mise sur le raccourci, la
   courbe de rebond de l'aiguille s'appliquerait aussi aux trente-quatre secondes de
   repos — sans effet visible, mais en disant quelque chose de faux. Les étapes qui
   portent une courbe sont celles d'où part un mouvement du produit, et ce sont les
   mêmes courbes, aux mêmes endroits. */
@keyframes kinome-voile{
  0%             {opacity:0;transform:none;animation-timing-function:ease-out;}
  0.611%         {opacity:1;}
  3.611%         {opacity:1;transform:none;animation-timing-function:ease-in;}
  4.778%         {opacity:0;transform:scale(1.07);}
  4.779%,100%    {opacity:0;transform:none;}
}
@keyframes kinome-trace{
  0%,0.278%      {stroke-dashoffset:70;animation-timing-function:cubic-bezier(.33,1,.68,1);}
  2%,100%        {stroke-dashoffset:0;}
}
@keyframes kinome-aiguille{
  0%,0.278%      {transform:rotate(-52deg);animation-timing-function:cubic-bezier(.34,1.32,.64,1);}
  1.503%         {transform:rotate(46deg);animation-timing-function:cubic-bezier(.34,1.32,.64,1);}
  2.378%         {transform:rotate(-31deg);animation-timing-function:cubic-bezier(.34,1.32,.64,1);}
  3.194%,100%    {transform:rotate(-20deg);}
}
@keyframes kinome-onde{
  0%,2.167%      {transform:scale(.45);opacity:0;animation-timing-function:cubic-bezier(.22,1,.36,1);}
  2.686%         {opacity:.55;animation-timing-function:cubic-bezier(.22,1,.36,1);}
  4.528%,100%    {transform:scale(4.4);opacity:0;}
}

/* En dessous de la largeur où le rail de l'application a un sens (720 px dans sa
   propre feuille), le cadre d'ordinateur ne montrerait plus qu'une version
   étroite de ce que le téléphone montre déjà. On ne garde que le téléphone,
   qui reprend alors sa place dans le flux.

   Ce 900 doit rester au-dessus du 720 d'app.css, et ce n'est pas un réglage
   esthétique. L'application garde une bascule « fenêtre étroite » qui, sous
   720 px, retire le rail du format bureau et lui rend la barre du bas — une
   règle écrite pour la fenêtre du navigateur. Ici sa fenêtre est le cadre, large
   de 1440 px, mais la requête média, elle, continue de mesurer la page. Faire
   descendre ce seuil sous 720 afficherait donc la barre de téléphone à
   l'intérieur d'une fenêtre d'ordinateur : un écran que le produit ne montre
   jamais. Tant que le cadre disparaît avant, la bascule ne peut pas se voir. */
@media (max-width:900px){
  .cadre-ordi{display:none;}
  /* Il n'y a plus rien qui déborde : le téléphone rentre dans le flux et se centre
     lui-même. Le débord revient donc à zéro — ce qui annule la correction de centrage,
     qui sans cela décalerait sur la gauche un téléphone déjà centré — et la réserve de
     hauteur disparaît avec le chevauchement qui la justifiait. */
  .scene{--debord:0px;margin-bottom:0;}
  .cadre-tel{position:static;margin:0 auto;}
  /* L'échelle du téléphone est redonnée, et il faut la redonner. Les paliers du dessus
     la rabaissent — jusqu'à .33 — pour une seule raison : tenir à côté de l'ordinateur.
     Ici l'ordinateur n'est plus là, le téléphone a toute la largeur, et laisser l'échelle
     de la cohabitation lui ferait 136 px de large au milieu d'une page de 900.
     « --tel » suit, sinon la lunette resterait celle d'un téléphone trois fois plus
     petit : c'est précisément le défaut qu'on vient de corriger sur l'ordinateur. */
  .scene{--tel:.76;}
  .cadre-tel .appli{--appli-z:.76;}
}
/* Sur petit écran on réduit l'échelle, jamais la hauteur de la fenêtre. Rogner la
   fenêtre — ce que faisait la première version, avec « height:min(560px,78vh) » —
   ne rend pas l'application plus petite : ça la met en page dans une fenêtre qui
   n'existe pas, et la fiche se retrouvait tranchée de trois cents pixels, juste
   au-dessus des deux boutons qu'on vient montrer. */
/* Les deux paliers resserraient aussi la gouttière de la démonstration, à 16 puis 12 px,
   pour rendre au téléphone la place que la double enveloppe lui prenait. Cette place lui
   revient d'elle-même maintenant que l'enveloppe est unique : à 320 px le téléphone
   réduit fait 269 px pour 272 disponibles, et il garde donc la gouttière de la page —
   la même que le texte au-dessus de lui, ce qui n'était pas le cas avant. */
@media (max-width:460px){
  .scene{--tel:.70;}
  .cadre-tel .appli{--appli-z:.70;}
}
@media (max-width:380px){
  .scene{--tel:.62;}
  .cadre-tel .appli{--appli-z:.62;}
}

/* ==========================================================================
   Sections animées — parcours, vitrine, gratuité, profil, contrat, outils
   ========================================================================== */

/* ---------- Parcours en quatre temps ---------- */
/* « list-style:none » est indispensable ici : la liste reste un <ol> pour que les
   quatre temps soient annoncés dans l'ordre par un lecteur d'écran, mais les
   puces natives doubleraient les numéros 01 à 04 déjà affichés. */
.parcours{position:relative;list-style:none;display:grid;grid-template-columns:repeat(4,1fr);gap:26px;margin-top:52px;}
.parcours::before,
.parcours::after{content:"";position:absolute;left:10px;right:10px;top:10px;height:2px;}
.parcours::before{background:var(--grille);}
.parcours::after{right:auto;width:0;background:var(--accent);animation:trait 9s ease-in-out infinite;}
.temps{position:relative;}
.temps-pt{
  display:block;width:22px;height:22px;border-radius:50%;
  background:var(--page-bg);border:2px solid var(--grille);
  position:relative;z-index:1;margin-bottom:18px;
  animation:pointe1 9s infinite;
}
/* Quatre jeux d'étapes plutôt qu'un seul décalé par « animation-delay » : un délai
   positif ne retarde que le tout premier tour, ensuite il ne fait que déphaser la
   boucle. La fenêtre allumée gardant sa durée, le point 4 se retrouvait allumé dès
   le début du cycle, avant même que le trait ne l'atteigne. Ici chaque point garde
   la même extinction (93 %) et n'avance que son allumage, sur le trait ci-dessous. */
.temps:nth-child(2) .temps-pt{animation-name:pointe2;}
.temps:nth-child(3) .temps-pt{animation-name:pointe3;}
.temps:nth-child(4) .temps-pt{animation-name:pointe4;}
.temps-num{
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.1em;
  color:var(--accent-ink);display:block;margin-bottom:7px;
}
.temps h3{margin-bottom:8px;}
.temps p{color:var(--text-muted);font-size:15px;}
@media (max-width:880px){
  .parcours{grid-template-columns:1fr;gap:30px;}
  .parcours::before,.parcours::after{display:none;}
  .temps-pt{margin-bottom:14px;}
}
@keyframes trait{
  0%{width:0;opacity:1;}
  14%{width:8%;}
  38%{width:37%;}
  62%{width:66%;}
  84%,94%{width:calc(100% - 20px);opacity:1;}
  99%,100%{width:calc(100% - 20px);opacity:0;}
}
@keyframes pointe1{
  0%,9%      {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
  13%        {border-color:var(--accent);background:var(--accent);transform:scale(1.3);}
  19%,93%    {border-color:var(--accent);background:var(--accent);transform:scale(1);}
  100%       {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
}
@keyframes pointe2{
  0%,32%     {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
  36%        {border-color:var(--accent);background:var(--accent);transform:scale(1.3);}
  42%,93%    {border-color:var(--accent);background:var(--accent);transform:scale(1);}
  100%       {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
}
@keyframes pointe3{
  0%,56%     {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
  60%        {border-color:var(--accent);background:var(--accent);transform:scale(1.3);}
  66%,93%    {border-color:var(--accent);background:var(--accent);transform:scale(1);}
  100%       {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
}
@keyframes pointe4{
  0%,79%     {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
  83%        {border-color:var(--accent);background:var(--accent);transform:scale(1.3);}
  89%,93%    {border-color:var(--accent);background:var(--accent);transform:scale(1);}
  100%       {border-color:var(--grille);background:var(--page-bg);transform:scale(1);}
}

/* ---------- Vitrine du cabinet — retirée le 15 octobre 2026 ----------
   Ce bloc habillait l'encart « Côté cabinet » de la page d'accueil : une grille en
   deux colonnes, et à droite une galerie 16/11 où trois médias factices se
   relayaient en boucle de douze secondes, avec un badge de lecture au centre du
   volet vidéo et trois pastilles de progression en haut à droite.
   L'encart est supprimé — il redisait la carte « Vous êtes titulaire d'un cabinet »
   de la section 5 — et son style part avec lui : aucune autre page ne portait
   « .vitrine », « .galerie », « .vue-media », « .lecture » ni « .galerie-pts », ce
   qui a été vérifié sur l'accueil comme sur le blog avant de retirer les règles.
   Les deux animations « galerie » et « pastille » disparaissent aussi ; elles
   n'étaient nommées nulle part ailleurs. Leur repos en mouvement réduit, plus bas
   dans ce fichier, est retiré dans le même geste — une règle de repos sans
   animation à reposer est un piège pour qui relira ce bloc.
   « .carte-liste », en revanche, reste : l'encart s'en servait, mais les cartes de
   la section 5 et le pied de page aussi.
   ------------------------------------------------------------------------- */

/* ---------- Gratuité ---------- */
.gratuit{
  display:grid;grid-template-columns:auto 1fr;gap:44px;align-items:center;
  margin-top:44px;padding:38px 40px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);
}
@media (max-width:760px){ .gratuit{grid-template-columns:1fr;gap:28px;padding:30px 26px;} }
/* « .prix span » — la légende « pour les cabinets comme pour les kinés », en mono
   capitales sous le chiffre — est retirée le 14 octobre 2026 avec le span qu'elle
   habillait. Elle redisait le chapeau de la section, qui annonce déjà « nulle part,
   d'aucun côté » en meilleurs termes, et deux lignes de petites capitales sous un
   chiffre de 84 px lui faisaient un socle dont il n'a pas besoin.

   « text-align:center » reste malgré l'enfant unique : le « 0 € » est centré dans sa
   colonne de grille, pas sous une légende disparue. */
.prix{text-align:center;}
.prix b{display:block;font-family:var(--display);font-size:clamp(56px,9vw,84px);line-height:1;color:var(--accent);}
.aucun-frais{display:grid;grid-template-columns:repeat(2,1fr);gap:13px 24px;}
.aucun-frais li{display:flex;align-items:center;gap:11px;font-size:15.5px;font-weight:600;}
.aucun-frais svg{color:var(--ok);flex:none;}
/* Deux colonnes jusqu'en bas, téléphone compris. Cette requête repliait la liste sur
   une seule colonne sous 620 px ; elle fait l'inverse depuis le 18 octobre 2026, et
   la raison tient aux mesures : six lignes pleine largeur donnaient une carte de
   347 px de haut à 390 et de 365 à 320, pour six mots-clés de trois mots — un mur,
   là où il faut une grille qu'on balaie. En deux colonnes la carte tombe à 299 et à
   313. Le prix à payer est que tous les intitulés passent à la ligne, aucun ne tenant
   sur une colonne de 141 px : relevé à 390, deux lignes pour les quatre premiers et
   trois pour les deux derniers ; à 320, deux lignes pour les deux premiers et trois
   pour les quatre autres.
   Ce qui sauve la grille, c'est que les deux intitulés les plus longs — « Aucune
   commission au succès » et « Aucune carte bancaire demandée » — sont justement les
   deux derniers, donc côte à côte sur la même rangée : les rangées restent d'équerre
   au lieu de s'ébrécher. C'est une chance, pas une construction. Qui retouchera ces
   six phrases devra reprendre ce relevé.

   La gouttière horizontale est une droite passant par (620 px ; 24 px) et
   (390 px ; 14 px), selon l'usage de la page : elle rejoint exactement les 24 px de
   la règle générale au seuil, de sorte qu'il n'y a pas de marche à 620. Elle vaut
   10,9 px à 320. La gouttière verticale, elle, ne bouge pas — 13 px de part et
   d'autre du seuil.

   Les deux dernières lignes vont ensemble et ne servent qu'aux intitulés repliés :
   centrer la coche sur un bloc de deux ou trois lignes la ferait flotter au milieu
   du texte au lieu de pointer son début. Elle se cale donc en haut, décalée d'un
   pixel — la moitié de l'écart entre la ligne de 18,2 px et la coche de 17.

   Deux choses relevées et laissées telles quelles. D'abord, les colonnes ne sont pas
   rigoureusement égales en dessous de 360 px : « 1fr » a un plancher de contenu
   minimal, et « sponsorisation » est plus large que la moitié disponible, d'où 123 px
   à droite contre 109 à gauche à 320. Les forcer par « minmax(0,1fr) » couperait le
   mot en deux, ce qui est pire qu'une colonne plus large de 14 px — et les deux rails
   de coches restent alignés, qui sont ce que l'œil suit. Ensuite, l'interlignage
   resserré s'arrête au seuil alors que les intitulés se replient déjà au-dessus : de
   621 à 900 px environ, quatre des six tiennent sur deux lignes avec 25 px
   d'interligne, ce qui est lâche. C'est un défaut antérieur à ce changement et sur
   une plage qui n'était pas demandée ; il est noté, pas corrigé. */
@media (max-width:620px){
  .aucun-frais{gap:13px clamp(10.9px, calc(-2.96px + 4.35vw), 24px);}
  .aucun-frais li{align-items:flex-start;line-height:1.3;}
  .aucun-frais svg{margin-top:1px;}
}

/* Les six « Aucun… » arrivent en cascade, et il a fallu trois essais pour que la
   cascade se voie. Les deux premiers tiennent dans ce commentaire parce qu'ils se
   trompaient chacun d'endroit, et qu'un prochain réglage se tromperait du même.

   Premier essai, le décalage : il valait .09 s pour une entrée de .5 s. Six lignes
   réparties sur .45 s, donc la dernière démarrait avant que la première ait fini —
   le décalage était écrit, pas perceptible. Il passe à .13 s, et l'entrée raccourcit
   à .42 s pour que chaque ligne finisse avant que la deuxième suivante ne commence.
   .65 s du premier au dernier, 1,07 s en tout.

   Deuxième essai, le déclencheur, et c'est lui qui portait le vrai défaut. La carte
   était animée par « .visible », posée par l'observateur commun de « .apparait » :
   seuil .18, marge basse 40 px. Or la carte ne mesure que 180 px de haut sur un
   écran de 760. 18 % de 180 font 32 px : la classe tombait quand la carte pointait
   de 32 px au bord bas de l'écran. La cascade se jouait donc hors du champ de
   lecture, sous les yeux de personne, et « unobserve » interdisait qu'elle se
   rejoue. On voyait six lignes immobiles et on avait raison de dire qu'il ne se
   passait rien.

   La leçon vaut au-delà d'ici : un seuil en pourcentage mesure l'élément, pas la
   page. Sur un bloc court, 100 % de l'élément peuvent tenir dans les derniers pixels
   de l'écran. Ce qu'on voulait dire — « quand on la regarde » — se dit en marge
   négative sur la racine, et c'est ce que fait l'observateur dédié de assets/kinome.js :
   la carte doit entrer dans les 65 % hauts de la fenêtre. Elle rejoue aussi à chaque
   retour dans cette zone, une cascade manquée une fois étant sinon manquée pour
   toujours ; le réglage de ce retour — une hystérésis entre deux lignes — est expliqué
   là-bas, à l'endroit où il est écrit.

   La coche se trace, au lieu d'apparaître faite. C'est ce que la ligne dit — « aucun
   abonnement » est un engagement qu'on coche — et c'est le même geste que le sceau
   « Bravo, vous êtes recruté » du troisième temps, dans assets/accueil.css : la page
   n'a qu'une façon de dessiner une coche. Elle part .12 s après sa ligne pour qu'on
   voie la ligne arriver puis se faire cocher, et non les deux d'un coup.

   « pathLength="1" » sur les six tracés : sans lui, « stroke-dasharray:1 » vaudrait
   un pixel de pointillé au lieu de la longueur entière du trait. La leçon est déjà
   payée deux fois sur ce projet, elle est répétée ici pour la troisième.

   Le repos est sous garde « .js », comme « .apparait » plus haut et pour la même
   raison exactement : la liste ne se cache que si quelque chose est là pour la
   montrer. Sans JavaScript, six lignes cochées, servies telles quelles. */
.js .aucun-frais li{opacity:0;}
.js .aucun-frais path{stroke-dasharray:1;stroke-dashoffset:1;}
.gratuit.coche .aucun-frais li{
  animation:entre-liste .42s calc(var(--i,0) * .13s) both;
}
.gratuit.coche .aucun-frais path{
  animation:coche-frais .34s cubic-bezier(.65,0,.35,1) calc(.12s + var(--i,0) * .13s) both;
}
@keyframes entre-liste{from{opacity:0;transform:translateX(-10px);}to{opacity:1;transform:none;}}
@keyframes coche-frais{to{stroke-dashoffset:0;}}

/* ---------- Interrupteur du profil ---------- */
.veille{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:18px;
  margin-top:40px;padding:30px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);
}
.veille-mot{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);}
.veille-mot.a{animation:mot-veille 6s infinite;}
.veille-mot.b{animation:mot-actif 6s infinite;}
.piste{
  width:62px;height:32px;border-radius:999px;flex:none;
  background:var(--grille);padding:4px;animation:piste 6s infinite;
}
.piste i{display:block;width:24px;height:24px;border-radius:50%;background:var(--surface);box-shadow:var(--shadow);animation:bouton 6s infinite;}
@keyframes piste{0%,42%{background:var(--grille);}50%,92%{background:var(--ok);}100%{background:var(--grille);}}
@keyframes bouton{0%,42%{transform:none;}50%,92%{transform:translateX(30px);}100%{transform:none;}}
@keyframes mot-veille{0%,42%{color:var(--text);}50%,92%{color:var(--text-muted);}100%{color:var(--text);}}
@keyframes mot-actif{0%,42%{color:var(--text-muted);}50%,92%{color:var(--ok);}100%{color:var(--text-muted);}}

/* ---------- Contrat signé dans l'application ----------
   Vide depuis le 14 octobre 2026. Cette carte dessinait à la main, sous le texte du
   troisième temps, un document et son paraphe : quatre barres grises en guise de
   clauses, un tracé manuscrit, un tampon « signé · horodaté ». Elle date d'avant que
   la page sache montrer le produit ; depuis, l'écran de signature est relevé de la
   démonstration et posé dans un téléphone à vingt centimètres de là. Une maquette de
   contrat à côté du vrai écran de contrat, c'était dire deux fois la même chose, dont
   une fois en moins bien.
   Le tracé, lui, n'a pas été jeté : il est passé dans la zone de signature de l'écran
   relevé, sous le nom « .sig-trait », et c'est accueil.css qui le trace désormais. Son
   « pathLength="1" » et la leçon qui allait avec — la normalisation ne vaut que pour
   les longueurs le long du tracé, jamais pour l'épaisseur — sont recopiés là-bas. */

/* ---------- Outils réunis pour l'arrivée ----------
   Les tuiles ne portent plus qu'un nom depuis le 14 octobre 2026. Elles avaient
   chacune une ligne de description — « Créneaux et patientèle partagée », « Statut et
   déclarations » — et cette ligne était un engagement : elle disait ce que la brique
   ferait, alors qu'aucune n'est raccordée. La note de chantier juste à côté dit le
   contraire, et c'est elle qui est vraie. Une catégorie se nomme ; elle ne se promet
   pas.
   La tuile a maigri avec elle. Deux lignes de texte s'appuyaient sur les trente-huit
   pixels de la pastille ; une seule ligne, avec l'ancien rembourrage, donnait une boîte
   de soixante-quatorze pixels pour un mot.
   Au passage, la pastille a retrouvé sa couleur. « .outil span » la repeignait en gris
   de texte secondaire depuis toujours : la règle visait la ligne de description, mais
   elle comptait une classe et un type là où « .outil-ico » ne compte qu'une classe, et
   passait donc devant lui sans qu'on l'ait voulu. La description partie, la règle part
   avec elle et l'encre d'accent annoncée deux lignes plus haut s'applique enfin — c'est
   la paire de la maison, fond « accent-soft » et encre « accent-ink ». */
.outils{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:44px;}
@media (max-width:880px){ .outils{grid-template-columns:repeat(2,1fr);} }
@media (max-width:520px){ .outils{grid-template-columns:1fr;} }
.outil{
  display:flex;align-items:center;gap:13px;
  padding:13px 17px;border-radius:var(--radius-md);
  background:var(--surface);border:1px solid var(--border);
  animation:outil 9s calc(var(--i,0) * .6s) infinite;
}
.outil-ico{
  width:36px;height:36px;border-radius:11px;flex:none;
  display:flex;align-items:center;justify-content:center;
  background:var(--accent-soft);color:var(--accent-ink);
}
/* « min-width:0 » parce que le nom est désormais seul en vis-à-vis de la pastille :
   « Prescription d'exercice » dépassait de sa tuile au lieu de passer à la ligne,
   l'élément flexible gardant par défaut la largeur de son contenu. */
.outil b{font-family:var(--display);font-size:15px;line-height:1.25;min-width:0;}
@keyframes outil{
  0%,5%     {border-color:var(--border);transform:none;box-shadow:none;}
  11%       {border-color:var(--accent);transform:translateY(-4px);box-shadow:var(--shadow);}
  22%,100%  {border-color:var(--border);transform:none;box-shadow:none;}
}

/* Une boucle qui tourne derrière l'écran ne sert personne et coûte de la batterie :
   le script pose « anime-veille » dès que le bloc sort du champ. La règle est
   écrite dans ce sens — et non l'inverse — pour que la démonstration joue quand
   même si le script ne s'exécute pas, plutôt que de rester figée. */
.anime-veille,
.anime-veille *{animation-play-state:paused !important;}
/* « !important » depuis le 16 octobre 2026. Sans lui, la pause ne valait que pour les
   éléments dont l'animation est déclarée avec un seul sélecteur de classe, plus haut
   dans la feuille : le raccourci « animation » remet « animation-play-state » à
   « running », et toute règle plus précise que « .anime-veille * » l'emportait. Dans
   l'interrupteur du profil, seule la piste s'arrêtait ; le bouton (« .piste i ») et
   les deux mots (« .veille-mot.a ») continuaient de tourner. À chaque sortie du champ,
   ils prenaient de l'avance sur elle, et l'on finissait par voir le bouton à droite sur
   une piste encore grise. Toute la démonstration était exposée au même décalage. La
   pause doit figer un bloc entier ou rien du tout : c'est la seule règle qui ait à
   décider de l'état de lecture, d'où « !important ». */

@media (prefers-reduced-motion: reduce){
  /* Les deux mots de l'interrupteur sont listés avec leur classe « .a »/« .b » et
     non sous le simple « .veille-mot » : c'est là que leur animation est déclarée,
     donc avec une classe de plus. Une extinction écrite moins précisément perd le
     cascade et les laisse tourner — et une animation en cours l'emporte aussi sur
     les couleurs posées plus bas, qui restaient alors sans effet.

     Les deux bulles de l'échange sont dans le même cas, et le piège s'est refermé une
     seconde fois : le jour où l'animation a gagné un « [data-ecran="conversation"] »
     pour ne plus viser que les conversations, elle a gagné du même coup un cran de
     spécificité, et cette extinction-ci — restée à cinq classes contre six — a cessé de
     l'éteindre. Les bulles continuaient d'arriver chez le visiteur qui refuse le
     mouvement, et la sonde qui vérifie « aucune animation ne subsiste » l'a dit. La
     leçon est générale et mérite d'être écrite en toutes lettres : **affiner un
     sélecteur d'animation oblige à affiner l'extinction du même coup**, faute de quoi
     la précision gagnée d'un côté se paye d'un garde-fou perdu de l'autre. */
  .pile > .fiche,.appli .fiche .gauge-needle,.appli .volet,
  .appli .navigation > .nav-vue,
  /* La leçon ci-dessus, appliquée tout de suite : la sous-navigation du balayage —
     celle qui découvre le kinôme au bout de la deuxième fiche — ne porte qu'un
     « animation-name » et le porte d'une classe plus haut que l'extinction
     ci-dessus. Sans cette ligne-ci, le nom repasserait devant et il resterait une
     animation de durée nulle, que la sonde compte comme une animation. */
  .appli .volet-rang-1 .navigation > .nav-vue,
  /* Et la leçon une troisième fois, pour la même raison qu'à la deuxième : les rails
     de sous-navigation ne jouent plus la même chorégraphie dans les deux cadres —
     deux vues à l'ordinateur, quatre au téléphone — si bien que leur « animation-name »
     a dû être scopé au cadre et a gagné une classe. Les trois lignes qui suivent
     reprennent exactement les sélecteurs qui animent, sans quoi la ligne précédente,
     restée un cran en dessous, ne les éteindrait plus. */
  .cadre-ordi .appli .volet-rang-1 .navigation > .nav-vue,
  .cadre-tel .appli .volet-rang-1 .navigation > .nav-vue,
  .cadre-tel .appli .volet-rang-2 .navigation > .nav-vue,
  /* Le voile du kinôme et les quatre pièces de son logo. Leur extinction ne demande
     rien de plus : le repos est déclaré à opacité nulle sur le voile lui-même, donc
     l'écran du kinôme se montre tel quel, sans le geste qui le dévoile. Les trois
     classes sont nécessaires — les règles qui les animent en comptent trois aussi. */
  .demo .appli .match-intro,.demo .appli .mi-arc,
  .demo .appli .mi-needle,.demo .appli .mi-onde,
  /* Les bulles : trois fils, trois réglages, donc trois paires à éteindre. Les deux
     conversations portent le même « [data-ecran] » et le même rang — l'une est au
     rang 1 du volet de messagerie de l'ordinateur, l'autre au rang 1 du volet de
     discussions du téléphone — mais chacune a son propre jeu d'étapes, donc son
     propre sélecteur de cadre. L'échange du kinôme, lui, est au rang 3. */
  .cadre-ordi .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :nth-last-child(2),
  .cadre-ordi .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child,
  .cadre-tel .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :nth-last-child(2),
  .cadre-tel .appli .navigation > .nav-rang-1[data-ecran="conversation"] .scroll > :last-child,
  .cadre-tel .appli .navigation > .nav-rang-3[data-ecran="echange"] .scroll > :nth-last-child(2),
  .cadre-tel .appli .navigation > .nav-rang-3[data-ecran="echange"] .scroll > :last-child,
  .appli .swipe-btn.pass,.appli .swipe-btn.like,
  /* Les deux compteurs de pourcentage, éteints comme les deux aiguilles qu'ils
     accompagnent et avec les mêmes sélecteurs. Eux seuls n'ont rien à reposer plus
     bas : leur valeur au repos est déclarée sur l'élément — « --compte:var(--score) »
     — et non par une étape d'animation. Une animation en moins leur rend donc le
     score, là où l'aiguille, elle, retombe à plat et doit être replacée à la main. */
  .appli .fiche .gauge-pct,.appli [data-ecran="profil"] .gauge-pct,
  .parcours::after,.temps-pt,.piste,.piste i,
  .veille-mot.a,.veille-mot.b,.outil,
  .gratuit.coche .aucun-frais li,
  .gratuit.coche .aucun-frais path{animation:none;}
  /* Les six « Aucun… » ne se contentent pas de perdre leur animation : leur repos sous
     garde « .js » est une ligne transparente et une coche en pointillé, c'est-à-dire
     rien du tout à l'écran. Éteindre sans reposer laisserait la carte vide. C'est la
     leçon du projet — éteindre une animation oblige à reposer l'état qu'elle
     atteignait — et ici elle vaut deux fois, parce que l'état de départ est invisible.
     Le script, de son côté, ne pose jamais « .coche » en mouvement réduit ; ces deux
     règles sont la ceinture qui va avec ses bretelles. */
  .js .aucun-frais li{opacity:1;}
  .js .aucun-frais path{stroke-dashoffset:0;}
  /* Sans animation il faut poser soi-même l'état final, sinon la maquette
     reste vide : une seule fiche visible, aiguille au score.
     L'application pose déjà cet angle dans sa propre branche « mouvement
     réduit » ; on le repose ici parce que la règle de la vitrine, plus
     spécifique, vient d'éteindre l'animation qui le portait. */
  .appli .fiche .gauge-needle,
  .appli [data-ecran="profil"] .gauge-needle{
    animation:none;transform:rotate(var(--gauge-deg,0deg));
  }
  .pile > .fiche{opacity:0;}
  .pile > .fiche:first-child{opacity:1;z-index:3;}
  /* Le parcours s'arrête sans avoir à reposer quoi que ce soit : la bande au repos est
     déjà en place, les deux autres écrans attendent leur tour hors du cadre qui les
     rogne. La version en fondu, elle, devait rallumer l'opacité à la main — faute de
     quoi les trois volets restaient à zéro et la démonstration disparaissait
     entièrement pour ces visiteurs.
     Reste à décaler le téléphone d'un cran. Éteindre l'animation efface du même coup
     son décalage de départ, si bien que les deux cadres retombaient sur le premier
     écran : le visiteur qui refuse le mouvement voyait deux fois le même écran, alors
     que tout le propos de la paire est de montrer les deux faces du produit à la fois.
     Le décalage se pose donc à la main, avec le moyen même dont se sert l'animation —
     « left » range les volets, « translateX » les déplace. Le visiteur au repos obtient
     ainsi deux écrans distincts, et non une image appauvrie de la version animée.

     Un cran, et non deux : le repos tombe alors sur la liste des discussions à
     l'ordinateur — le rang 0 y est la messagerie depuis que le funnel a redistribué les
     volets — et sur le volet de balayage au téléphone. C'est la paire qu'on aurait
     choisie pour une image fixe : d'un côté ce que le produit a déjà noué, de l'autre ce
     qu'il propose de nouer. Le geste de balayage, lui, ne se joue pas — c'est tout
     l'objet de ce bloc — mais l'écran du fil est là, avec sa fiche et ses deux boutons de
     jugement : on voit ce que le produit propose, sans qu'on le mime.

     Et le cran vaut toujours un cran, alors que les deux cadres n'ont plus les mêmes
     écrans : c'est le rang qui est décalé, pas le contenu. Le jour où le rang 1 du
     téléphone cessera d'être le balayage, ce nombre devra être relu — le repos tomberait
     sur un écran choisi par accident. */
  .cadre-tel .appli .volet{transform:translateX(-100%);}
  /* La navigation de l'ordinateur n'a rien à reposer : sa vue de repos est celle de
     rang 0, qui est déjà à sa place, et les deux dernières bulles reprennent l'opacité
     de la feuille de l'application dès qu'on éteint l'animation qui les cachait. Le
     visiteur qui refuse le mouvement obtient donc la liste des discussions entière, et
     le fil complet derrière — ni l'un ni l'autre amputé de ce que l'animation devait
     lui apporter.

     Le téléphone, lui, demande un second cran, et pour une raison qui mérite d'être
     écrite : l'avertissement laissé juste au-dessus s'est réalisé. Son volet de balayage
     ne commence plus par la découverte mais par la fiche complète — le récit veut qu'on
     lise une fiche en entier avant de se décider, donc le balisage range « profil » en
     premier et « decouverte » en second. Sans la ligne qui suit, le repos tomberait sur
     un profil ouvert : un écran de milieu de parcours, montré sans le geste qui y mène.
     On repose donc la navigation sur sa deuxième vue, exactement comme le volet est
     reposé sur son deuxième rang, et pour le même motif.

     C'est la nuance à retenir : la garantie n'est plus « la vue de rang 0 est partout le
     point de départ » — elle ne l'est plus au téléphone — mais « le repos est posé à la
     main sur le point de départ ». Un volet qu'on réordonnerait demain obligerait donc à
     relire ces deux transformations, et non seulement l'ordre des vues dans « CADRES ».
     Ce qui reste vrai, et ce qu'il ne faut pas casser, c'est la fin : aucun des deux
     cadres ne doit reposer sur un aboutissement — ni un contrat signé, ni un kinôme
     noué — sans que le visiteur sache par quoi on y arrive. */
  .cadre-tel .appli .volet-rang-1 .navigation > .nav-vue{transform:translateX(-100%);}
  .parcours::after{width:calc(100% - 20px);}
  .temps-pt{border-color:var(--accent);background:var(--accent);}
  /* Le repos de la galerie du cabinet — premier média visible, première pastille
     allumée — est parti le 15 octobre 2026 avec l'encart « Côté cabinet » et les
     animations qu'il reposait. */
  .piste{background:var(--ok);}
  .piste i{transform:translateX(30px);}
  /* L'interrupteur est posé sur « en recherche » : les deux mots doivent le dire
     aussi, faute de quoi la maquette s'affiche allumée mais libellée en veille. */
  .veille-mot.a{color:var(--text-muted);}
  .veille-mot.b{color:var(--ok);}
}

/* ==========================================================================
   Blog — liste des billets et pages d'article
   ========================================================================== */

/* ---------- Fil d'Ariane ---------- */
/* Le fil est aussi lu par les moteurs via le JSON-LD BreadcrumbList : les deux
   doivent raconter le même chemin, sinon le balisage est jugé trompeur. */
.fil{
  display:flex;flex-wrap:wrap;align-items:center;gap:9px;
  font-family:var(--mono);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--text-muted);
}
.fil a{text-decoration:none;color:var(--text-muted);transition:color .16s ease;}
.fil a:hover{color:var(--accent-ink);}
.fil .sep{opacity:.45;}
.fil [aria-current]{color:var(--accent-ink);}

/* ---------- En-tête de page secondaire ---------- */
.page-entete{padding:clamp(38px,5.5vw,68px) 0 0;}
.page-entete h1{font-size:clamp(32px,5vw,52px);margin-top:20px;}
.page-entete .chapeau{margin-top:20px;}

/* ---------- Filtres de rubrique ---------- */
.rubriques{display:flex;flex-wrap:wrap;gap:9px;margin-top:32px;}
.rubrique{
  font-family:var(--texte);font-size:13.5px;font-weight:600;
  padding:9px 17px;border-radius:999px;
  background:var(--surface);border:1px solid var(--border);color:var(--text-muted);
  cursor:pointer;transition:border-color .16s ease, color .16s ease, background .16s ease;
}
.rubrique:hover{border-color:var(--accent);color:var(--text);}
.rubrique[aria-pressed="true"]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent-ink);}

/* ---------- Grille de billets ---------- */
.billets{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;margin-top:28px;}
@media (max-width:980px){ .billets{grid-template-columns:repeat(2,1fr);} }
@media (max-width:660px){ .billets{grid-template-columns:1fr;} }

.billet{
  display:flex;flex-direction:column;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:26px;text-decoration:none;color:inherit;
  transition:transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.billet:hover{transform:translateY(-3px);box-shadow:var(--shadow);border-color:var(--accent);}
.billet-meta{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-muted);
}
.billet-rubrique{color:var(--accent-ink);}
.billet-meta .sep{opacity:.45;}
.billet h2,.billet h3{font-size:20px;margin-top:14px;line-height:1.25;}
.billet p{color:var(--text-muted);font-size:15px;margin-top:11px;}
/* `margin-top:auto` colle le lien en bas : les résumés d'une même rangée n'ont
   pas la même longueur, les flèches doivent quand même s'aligner. */
.billet-lire{
  margin-top:auto;padding-top:22px;
  display:inline-flex;align-items:center;gap:8px;
  font-size:14px;font-weight:700;color:var(--accent-ink);
}
.fleche{transition:transform .18s ease;}
.billet:hover .fleche{transform:translateX(4px);}

/* ---------- Billet à la une ---------- */
.billet-une{grid-column:1 / -1;display:grid;grid-template-columns:1.12fr .88fr;gap:36px;align-items:center;padding:34px;}
@media (max-width:820px){ .billet-une{grid-template-columns:1fr;gap:26px;padding:28px;} }
.billet-une h2{font-size:clamp(24px,3.2vw,34px);margin-top:16px;}
.billet-une p{font-size:16.5px;}
.billet-visuel{
  border-radius:var(--radius-md);min-height:210px;
  display:flex;align-items:center;justify-content:center;
  background:
    radial-gradient(120% 90% at 22% 12%, var(--accent-soft) 0%, transparent 62%),
    linear-gradient(140deg, var(--select-soft) 0%, var(--bg) 100%);
  border:1px solid var(--border);color:var(--text);
}
@media (max-width:820px){ .billet-une .billet-visuel{order:-1;min-height:150px;} }

/* Message affiché quand un filtre ne laisse aucun billet. */
.aucun{
  grid-column:1 / -1;padding:44px;text-align:center;
  border:1px dashed var(--border);border-radius:var(--radius-lg);
  color:var(--text-muted);font-size:15.5px;
}

/* ---------- Corps d'article ---------- */
/* 730 px : environ 70 caractères par ligne à la taille de corps choisie, la
   plage où la lecture longue reste confortable. */
.article{max-width:730px;margin:0 auto;}
.article-meta{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:22px;
  font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-muted);
}
.article-meta .sep{opacity:.45;}
.article h1{font-size:clamp(30px,4.4vw,46px);margin-top:18px;}
.article-chapeau{font-size:19px;color:var(--text-muted);margin-top:22px;line-height:1.65;}

.sommaire{
  margin-top:38px;padding:24px 26px;
  border-radius:var(--radius-md);background:var(--bg);border:1px solid var(--border);
}
.sommaire b{
  font-family:var(--mono);font-size:10.5px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--accent-ink);
}
.sommaire ol{margin:15px 0 0;padding:0;list-style:none;counter-reset:som;display:flex;flex-direction:column;gap:10px;}
.sommaire li{counter-increment:som;}
.sommaire a{font-size:15px;color:var(--text-muted);text-decoration:none;display:flex;gap:11px;}
.sommaire a::before{content:counter(som,decimal-leading-zero);font-family:var(--mono);font-size:11px;color:var(--accent-ink);flex:none;}
.sommaire a:hover{color:var(--text);}

/* ---------- Typographie de contenu ---------- */
/* `scroll-margin-top` : les ancres du sommaire doivent se poser sous l'en-tête
   collante, sinon le titre visé se retrouve caché derrière elle. */
.prose h2{font-size:clamp(23px,2.8vw,30px);margin-top:54px;scroll-margin-top:96px;}
.prose h3{font-size:18.5px;margin-top:34px;}
.prose p{margin-top:17px;font-size:17px;line-height:1.75;color:var(--text-muted);}
.prose strong{color:var(--text);font-weight:600;}
.prose a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;}
.prose a:hover{background:var(--accent-soft);}
/* Puce et numéro sont positionnés en absolu, pas posés en flex : un `li` en
   `display:flex` transforme chaque lien ou chaque passage en gras du texte en
   élément flex distinct, ce qui disloque la phrase. */
.prose ul.puces{margin-top:19px;display:flex;flex-direction:column;gap:11px;}
.prose ul.puces li{position:relative;padding-left:19px;font-size:16.5px;line-height:1.62;color:var(--text-muted);}
.prose ul.puces li::before{content:"";position:absolute;left:0;top:10px;width:6px;height:6px;border-radius:50%;background:var(--accent);}
.prose ol.numeros{margin:20px 0 0;padding:0;list-style:none;counter-reset:pt;display:flex;flex-direction:column;gap:17px;}
.prose ol.numeros li{counter-increment:pt;position:relative;padding-left:48px;font-size:16.5px;line-height:1.62;color:var(--text-muted);}
.prose ol.numeros li::before{
  content:counter(pt,decimal-leading-zero);
  position:absolute;left:0;top:3px;
  font-family:var(--mono);font-size:11.5px;font-weight:600;
  color:var(--accent-ink);background:var(--accent-soft);
  border-radius:8px;padding:3px 8px;
}

.encadre{margin-top:32px;padding:24px 26px;border-radius:var(--radius-md);background:var(--accent-soft);border:1px solid var(--accent);}
.encadre b{font-family:var(--display);font-size:16.5px;display:block;margin-bottom:7px;color:var(--text);}
.encadre p{color:var(--accent-ink);font-size:15.5px;margin-top:0;}

/* L'enveloppe porte le cadre et le rayon : `border-collapse` sur le tableau
   lui-même empêcherait les coins arrondis de rogner les cellules. */
.tableau-enveloppe{margin-top:28px;overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface);}
.tableau{width:100%;border-collapse:collapse;font-size:15px;}
.tableau th{
  text-align:left;padding:14px 18px;background:var(--bg);
  font-family:var(--mono);font-size:10.5px;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);
  border-bottom:1px solid var(--border);white-space:nowrap;
}
.tableau td{padding:14px 18px;border-bottom:1px solid var(--border);color:var(--text-muted);vertical-align:top;}
.tableau tr:last-child td{border-bottom:none;}

/* ---------- Bas d'article ---------- */
.article-appel{
  margin-top:56px;padding:34px;text-align:center;
  border-radius:var(--radius-lg);background:var(--bg);border:1px solid var(--border);
}
.article-appel h2{margin-top:0;font-size:23px;}
.article-appel p{max-width:50ch;margin:13px auto 0;color:var(--text-muted);}
.article-appel .hero-actions{justify-content:center;margin-top:26px;}
/* Voir « .final » sous 360 px : même raison, même date. */
@media (max-width:359px){ .article-appel{padding-left:16px;padding-right:16px;} }

.suite{border-top:1px solid var(--border);margin-top:66px;padding-top:42px;}
.suite h2{font-size:22px;}
.suite .billets{grid-template-columns:repeat(2,1fr);margin-top:22px;}
@media (max-width:660px){ .suite .billets{grid-template-columns:1fr;} }

/* ---------- Encart de consentement (mesure d'audience) ---------- */
/* Posé par « mesure.js ». Un encart, pas un bandeau (17 octobre 2026) : dans le coin
   inférieur gauche, une phrase et deux boutons, sans bloquer la page — on peut lire
   et défiler sans répondre. Refuser et Tout accepter ont la même taille : la CNIL
   demande qu'il soit aussi simple de refuser que d'accepter. Sur téléphone, il
   reprend toute la largeur moins la gouttière. Proportions reprises d’earny.fr
   (320 px, texte de 12,5 px), plus discrètes que la première version. */
.bandeau-cookies{
  position:fixed;z-index:300;left:16px;bottom:calc(16px + env(safe-area-inset-bottom));
  width:min(320px,calc(100vw - 32px));padding:16px;
  border-radius:18px;background:var(--surface);color:var(--text);
  border:1px solid var(--border);box-shadow:var(--shadow-lg);
  animation:bandeau-monte .35s cubic-bezier(.22,1,.36,1);
}
.bandeau-cookies-texte{margin:0;font-size:12.5px;line-height:1.5;color:var(--text-muted);}
.bandeau-cookies-texte a{
  color:var(--text);font-weight:700;text-decoration:underline;white-space:nowrap;
  text-decoration-thickness:1px;text-underline-offset:3px;
}
.bandeau-cookies-texte a:hover{color:var(--accent-ink);}
.bandeau-cookies-actions{display:flex;gap:8px;margin-top:12px;}
.bandeau-cookies-actions .bouton{flex:1 1 0;min-width:0;justify-content:center;padding:9px 12px;font-size:12.5px;}
.bandeau-cookies-actions .bouton-ligne{background:var(--surface);}
@keyframes bandeau-monte{from{opacity:0;transform:translateY(12px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion: reduce){ .bandeau-cookies{animation:none;} }
