/* CSS structurel de Wonderland. Chargé immédiatement pour éviter tout flash brut. */

:root{
  --bg:#0B0B0B;
  --bg-2:#101010;
  --gold:#D4AF37;
  --gold-dim:rgba(212,175,55,0.35);
  --gold-faint:rgba(212,175,55,0.14);
  --marble:#ECE9E2;
  --marble-dim:rgba(236,233,226,0.62);
  /* Le palier le plus discret qui reste lisible : 5.34:1 sur --bg, au-dessus
     du seuil AA de 4.5:1. En dessous, on sort de la norme — c'est un plancher,
     pas une nuance parmi d'autres. --marble-faint (1.30:1) ne porte donc que
     des filets et des fonds, jamais du texte. */
  --marble-quiet:rgba(236,233,226,0.55);
  --marble-faint:rgba(236,233,226,0.12);
  --blue:#6FA8DC;
  /* Titres. Cinzel est une capitale lapidaire : pas de bas-de-casse réel,
     pas d'italique — elle ne convient qu'aux titres, jamais au texte suivi. */
  --title: "Cinzel", Georgia, serif;
  /* Reste pour la citation, seul endroit qui demande un italique littéraire. */
  --serif: "Fraunces", Georgia, serif;
  /* Corps de texte. Nommée d'après son rôle et non d'après sa classe : la
     police peut changer de famille sans que le nom devienne un mensonge. */
  --body: "IBM Plex Serif", Georgia, serif;
  /* Manuscrite — réservée aux notes d'atelier, jamais au discours du site.
     Caveat : une cursive lisible, sans afféterie, qui tient à petite taille
     comme à grande. Elle dit « écrit de la main » sans jouer la calligraphie. */
  --main: "Caveat", "Segoe Script", cursive;
  --mono: "IBM Plex Mono", monospace;
  --maxw: 980px;
  /* Hauteur du pied de page, qui est fixe : deux règles du hero doivent la
     dégager pour ne pas passer dessous. Déclarée ici pour n'exister qu'une
     fois — la changer suffit à réaligner tout ce qui en dépend. */
  --footer-h: 37px;
}

*{box-sizing:border-box;}
.visually-hidden{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.skip-link{
  position:fixed;top:12px;left:12px;z-index:10000;
  padding:8px 12px;background:var(--bg);color:var(--blue);
  border-bottom:1px solid var(--blue);
  font-family:var(--mono);font-size:12px;letter-spacing:.08em;
  transform:translateY(-180%);transition:transform .2s ease;
}
.skip-link:focus{transform:none;outline:1px solid var(--blue);outline-offset:4px;}
/* Le défilement assisté est piloté en JS (voir « Parcours assisté ») : laisser
   `smooth` ici ferait cohabiter deux moteurs d'animation sur le même axe. */
html{scroll-behavior:auto;}
body{
  margin:0;
  background:var(--bg);
  color:var(--marble);
  font-family:var(--body);
  font-weight:300;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
}
img{max-width:100%;display:block;}
a{color:inherit;}

::selection{background:var(--gold-faint);color:var(--gold);}

/* ---------- layout helpers ---------- */
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px;}
/* Une section vaut un écran. C'est la règle, et elle ne souffre pas
   d'exception : `min-height`, jamais `height`, pour qu'une section plus
   longue que l'écran s'allonge au lieu de se faire rogner. Une section plus
   courte, elle, garde du vide sous elle — et c'est voulu. Le défilement
   assisté avance d'une section par geste : si deux d'entre elles tenaient
   dans le même écran, le geste sauterait la seconde sans rien montrer. Le
   vide est le prix de ce repère. */
section{
  position:relative;padding:150px 0 120px;
  min-height:100vh;min-height:100svh;box-sizing:border-box;
  border-bottom:1px solid var(--marble-faint);
}
section:last-of-type{border-bottom:none;}
/* la section Parcours embarque déjà sa propre frise (haute) : son padding
   est réglé plus bas, avec le reste de sa tenue en un écran. */
/* Habiter : titre, plan, récit et frise composent un écran lorsque la hauteur
   disponible le permet. Sur un écran bas, `min-height` laisse la page grandir
   plutôt que de rogner le contenu. */
#habiter{
  box-sizing:border-box;min-height:100vh;min-height:100svh;
  display:flex;align-items:stretch;
  padding:clamp(26px,4.5svh,46px) 0 calc(var(--footer-h) + clamp(14px,3svh,26px));
}
/* Le surtitre et le titre cèdent une partie de leur air au plan : chaque
   pixel repris en haut est un pixel de dessin en plus, et le dessin est le
   propos de la section. */
#habiter .eyebrow{margin-bottom:15px;}
#habiter h2{margin-bottom:12px;}
/* Habiter est la seule section, avec le hero, à sortir de la largeur
   éditoriale (--maxw : 980px). Ce n'est pas un texte qu'on lit au fil : c'est
   un plan qu'on regarde, et six volets à parcourir sous lui. À 980px, le
   dessin tombait à 592px de large dans une section de 1425 — un timbre au
   milieu du noir, avec 280px de vide sous lui. Il prend désormais toute la
   largeur utile, et toute la hauteur que la section lui laisse.
   `align-items:stretch` ci-dessus est ce qui rend cette hauteur mesurable :
   la colonne remplit la section, et le plan s'y étire. */
#habiter>.wrap{
  width:100%;max-width:1280px;
  display:flex;flex-direction:column;min-height:0;
}

.eyebrow{
  font-family:var(--mono);
  font-size:11px;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--gold);
  display:flex;
  align-items:center;
  gap:14px;
  margin-bottom:28px;
}
.eyebrow::after{
  content:"";
  flex:1;
  height:1px;
  background:linear-gradient(to right, var(--gold-dim), transparent);
}

/* Cinzel est gravée, pas écrite : ses capitales demandent de l'air entre les
   lettres, là où une romane de labeur demandait qu'on les resserre. Le
   crénage négatif d'avant les aurait fait se toucher. Sa graisse la plus
   légère est 400 : demander 300 ne ferait que déclencher une graisse
   synthétique. */
h1,h2,h3{
  font-family:var(--title);
  font-weight:400;
  color:var(--marble);
  margin:0 0 22px;
  letter-spacing:0.012em;
  line-height:1.24;
}
h2{font-size:clamp(1.75rem,3.4vw,2.5rem);font-weight:400;}
h3{font-size:1.15rem;font-weight:500;color:var(--gold);}
p{margin:0 0 18px;color:var(--marble-dim);font-weight:300;font-size:1.02rem;}
.lede{font-size:1.18rem;color:var(--marble);max-width:640px;}

.tag{
  font-family:var(--mono);
  font-size:10.5px;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--blue);
  border:1px solid rgba(111,168,220,0.35);
  border-radius:2px;
  padding:3px 8px;
  display:inline-block;
}

/* ---------- l'orbe perchée ----------
   Il n'y a qu'une orbe. Celle du hero quitte sa colonne à mesure qu'on
   descend, rétrécit, et va se percher au coin haut-droit — la place que la
   rangée d'entrées occupait, du temps où il y avait une barre du haut. Rien
   n'est irréversible : remonter la ramène.

   Le vol est publié en `--vol` (0 dans le hero, 1 perchée) par le moteur, et
   `.is-perche` est posée passé la moitié du trajet. La géométrie — position et
   taille — est écrite en `left/top/width` et non par un `transform` : une
   transformation sur l'orbe en referait une couche composite, et le carré noir
   sur la grille du hero reviendrait (voir `.hero-doors`).

   Le creux garde la colonne pendant ce temps, sinon la grille du hero se
   refermerait sur une seule colonne dès le premier pixel de défilement. */
#hero .visual-creux{
  width:120%;max-width:none;justify-self:end;
  aspect-ratio:1/1;pointer-events:none;
}
@media(max-width:860px){
  #hero .visual-creux{width:min(100%, 340px);justify-self:center;margin:40px auto 0;}
}

/* Le bouton n'existe que perchée : dans le hero, ce sont les six portes qui
   servent de menu, et une boîte cliquable posée dessus les aurait toutes
   masquées. Il ne peint rien — il ne fait qu'attraper le pointeur. */
.orbe-menu{
  position:absolute;inset:0;z-index:4;
  padding:0;margin:0;border:none;background:none;
  pointer-events:none;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.visual.is-perche .orbe-menu{pointer-events:auto;}
.orbe-menu:focus{outline:none;}
/* Le focus se marque sur le disque, pas sur la boîte carrée du bouton :
   l'objet est rond, son contour de focus doit l'être aussi. */
.orbe-menu::after{
  content:"";position:absolute;inset:6%;border-radius:50%;
  border:1px solid transparent;transition:border-color .2s;
}
.orbe-menu:focus-visible::after{border-color:var(--gold-dim);}

/* --- les sections, déployées vers le bas ---
   Une colonne, jamais une couronne : perchée, l'orbe est petite, et six noms
   disposés autour d'elle se seraient marché dessus ou seraient sortis de
   l'écran. La couronne existe déjà, d'ailleurs — ce sont les portes du hero,
   et c'est bien la même liste, dite deux fois à deux tailles.
   `top:100%` suit l'orbe quelle que soit sa taille : la colonne pend toujours
   juste sous le disque, pendant le vol comme à l'arrivée. */
.repere-liste{
  list-style:none;margin:0;padding:9px 0 0;
  position:absolute;left:50%;top:100%;transform:translateX(-50%);
  text-align:center;z-index:5;
  visibility:hidden;transition:visibility 0s linear .42s;
}
.visual.is-ouvert .repere-liste{visibility:visible;transition-delay:0s;}
/* Il n'y a plus de filet entre l'orbe et sa liste. Ce trait d'un pixel,
   incliné par la perspective du disque, se lisait comme une barre oblique
   posée au-dessus de « Parcours » — donc comme une première entrée, celle du
   hero, sur laquelle on essayait de cliquer. Or on ne revient pas au hero par
   le menu : c'est un parti pris, le hero est un seuil qu'on franchit une
   fois. Plutôt qu'un faux bouton, rien : le premier élément sous l'orbe est
   « Parcours ». Voir DEVELOPMENT.md § Décisions de conception. */
.repere-liste a{
  display:inline-block;
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  text-decoration:none;color:var(--marble-dim);white-space:nowrap;
  padding:7px 10px 5px;border-bottom:1px solid transparent;
  /* Le menu se perche au-dessus du contenu des sections : sans fond, un nom
     pouvait tomber sur un titre ou un trait du plan et devenir illisible. Un
     aplat du fond de page à 80 % suffit à détacher la colonne sans la
     transformer en boîte — on voit encore ce qui passe derrière. */
  background:rgba(11,11,11,.8);
  text-shadow:0 0 7px var(--bg), 0 0 3px var(--bg);
  opacity:0;transform:translateY(-7px);
  transition:opacity .32s ease calc(var(--i,0) * .045s),
             transform .32s ease calc(var(--i,0) * .045s),
             color .25s, border-color .25s;
}
.visual.is-ouvert .repere-liste a{opacity:1;transform:none;}
.repere-liste a:hover, .repere-liste a:focus-visible{color:var(--gold);}
.repere-liste a.active{color:var(--gold);border-color:var(--gold-dim);}
/* Comprendre, dans la colonne : même mot, même place, mais gris et inerte.
   Il tient son rang dans la liste — l'ordre des sept sections ne change pas —
   et dit au clic pourquoi il ne mène nulle part. */
.repere-chantier{
  display:inline-block;position:relative;
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(236,233,226,.3);white-space:nowrap;
  padding:7px 10px 5px;border-bottom:1px solid transparent;
  background:rgba(11,11,11,.8);
  cursor:default;
}
.repere-chantier:focus-visible{outline:1px solid var(--gold-dim);outline-offset:2px;}
.repere-aveu{
  position:absolute;left:50%;top:100%;transform:translateX(-50%);
  margin-top:2px;padding:2px 7px;
  background:rgba(11,11,11,.8);
  font-size:9px;letter-spacing:.12em;color:var(--gold);white-space:nowrap;
  opacity:0;visibility:hidden;
  transition:opacity .25s ease,visibility 0s linear .25s;
}
.repere-chantier.dit-chantier .repere-aveu{
  opacity:1;visibility:visible;transition-delay:0s;
}
/* Au doigt, la cible passe à 44px : le corps du mot ne change pas, seule la
   zone de saisie s'épaissit. */
@media (pointer:coarse){
  .repere-liste a{padding:14px 14px 12px;}
}
/* Écrans bas — un téléphone tenu en paysage. La colonne ne se déploie que
   vers le bas : sur 400px de haut, sept entrées à pleine cible en sortiraient.
   On resserre donc plutôt que de changer de sens ; la liste reste lue au même
   endroit, dans le même ordre.
   C'est un compromis assumé : la cible tactile descend sous les 44px
   recommandés. Une entrée un peu menue reste préférable à une entrée hors de
   l'écran, que rien n'atteint. */
@media (max-height:620px){
  .repere-liste a{padding:6px 10px 4px;}
}
/* Sans JS, l'orbe ne vole pas : elle reste dans le hero, à sa place de repos,
   portes cliquables. La liste des sections est alors donnée ouverte sous elle
   — c'est la seule navigation dont dispose la page, et la septième entrée
   (Contact) n'a pas de porte. */
html:not(.js) .repere-liste{visibility:visible;}
html:not(.js) .repere-liste a{opacity:1;transform:none;}

/* Bascule en colonne : elle n'est plus commandée par une largeur d'écran
   arbitraire mais par l'orbe. Voir `#hero .wrap`. */
@media(max-width:860px){
  #hero .wrap{grid-template-columns:1fr;gap:40px;}
  #hero .visual{aspect-ratio:1/1;max-width:280px;margin:40px auto 0;}
}


/* ---------- hero ---------- */
#hero{
  min-height:100svh;
  display:flex;align-items:center;
  border-bottom:1px solid var(--marble-faint);
  overflow:hidden;
  position:relative;
}
#hero .grid-bg{
  position:absolute;inset:0;z-index:0;opacity:.55;
  pointer-events:none;
}
/* Le hero est le seul bloc qui dépasse la largeur commune (--maxw : 980px) :
   deux tiers de texte, un tiers d'orbe, et le titre respire vers la droite
   sans jamais toucher le bord — 1280px laisse ~80px de marge sur un 1440. */
/* C'est l'orbe qui dimensionne le hero, et non l'inverse. Sa colonne ne
   descend jamais sous --orbe-min ; comme elle est rendue à 120 % de sa
   colonne, l'orbe conserve au moins 300px de côté. En deçà, c'est la colonne
   de texte qui cède, puis la mise en page bascule en une seule colonne — mais
   à aucun moment l'orbe n'est rognée. */
#hero .wrap{position:relative;z-index:1;display:grid;grid-template-columns:minmax(0,2fr) minmax(var(--orbe-min,250px),1fr);gap:clamp(28px,4vw,60px);align-items:center;max-width:1280px;}
#hero .hero-text{min-width:0;}
/* Les deux blocs s'écartent de 5 % chacun — le texte vers la gauche, l'orbe
   vers la droite — soit 10 % d'écart en plus. Le décalage est plafonné en
   pixels : à 1280px de large il ne reste que 28px de marge, et un 5 % non borné
   aurait poussé l'orbe hors de l'écran. */
/* Décalés par leurs marges, jamais par un `transform` : une transformation sur
   l'orbe en referait une couche composite, et le carré noir sur la grille
   reviendrait (voir `.hero-doors`).
   Le décalage vaut 5 %, mais il ne prend que ce que la fenêtre peut céder : la
   marge restante au-delà des 1280px du bloc, moins 12px de garde au bord. Sous
   ~1250px de large il n'y a pas la place, et il s'annule de lui-même. */
#hero .wrap{--push:min(5%, max(0px, (100vw - 1280px)/2 + 16px));}
#hero .hero-text{margin-left:calc(-1 * var(--push));}
/* C'est le creux qui porte le décalage, l'orbe ayant quitté le flux : sa
   position est calculée sur celle du creux, elle en hérite donc. */
#hero .visual-creux{margin-right:calc(-1 * var(--push));}
#hero .hero-cta{
  /* Mesuré depuis le haut du pied de page, et non depuis le bas de la
     fenêtre : celui-ci est fixe et couvrirait les 37 derniers pixels. */
  position:absolute;left:50%;bottom:calc(var(--footer-h) + 26px);
  transform:translateX(-50%);
  z-index:2;
  overflow:hidden;
}
/* La jauge du seuil.
   Le bouton ne s'ouvre pas au premier clic : il se charge. Survoler le remplit
   de gauche à droite, en bleu ; la charge **reste** quand on s'en éloigne et
   **s'accumule** au survol suivant. Une fois pleine — une seconde de survol
   cumulée, le temps qu'il faut aux portes pour se ranger — le bouton devient
   cliquable.

   La jauge est un **fond du bouton**, pas un pseudo-élément posé derrière lui.
   La version précédente utilisait `::before { position:absolute; inset:0;
   z-index:-1 }`, ce qui ne tient que si le bouton crée un contexte
   d'empilement. Or sous 860px il repasse en `position:static` : son `z-index`
   n'a alors plus d'effet, le pseudo-élément s'échappe vers le premier ancêtre
   positionné — `#hero` — et `inset:0` lui fait couvrir **toute la section**.
   Un fond ne peut pas s'échapper : il est borné à la boîte, quel que soit le
   positionnement.

   `background-size` est la seule propriété animée. Elle déclenche un repaint
   du bouton, pas un recalcul de mise en page ; sur une boîte de cette taille
   c'est sans conséquence, et cela vaut mieux qu'un empilement fragile. */
#hero .hero-cta{
  background-image:linear-gradient(90deg,
    rgba(111,168,220,.16) 0%, rgba(111,168,220,.42) 60%, rgba(111,168,220,.55) 100%);
  background-repeat:no-repeat;
  background-position:left center;
  background-size:calc(var(--charge, 0) * 100%) 100%;
  transition:background-size .12s linear, background-color .25s, color .25s;
}
/* Tant que la charge n'est pas pleine, le bouton se montre en attente : le
   curseur dit qu'il n'est pas encore prêt. */
#hero .hero-cta.is-charge{cursor:progress;}
@media (prefers-reduced-motion: reduce){
  /* Pas de charge à attendre quand on demande moins de mouvement : le bouton
     est immédiatement franchissable. */
  #hero .hero-cta{transition:none;}
}
@media(max-width:860px){
  /* En colonne unique, le bouton reprend sa place dans le flux, sous le bloc.
     `#hero` est un conteneur flex : sans passer en colonne, le bouton sorti du
     flux absolu viendrait se ranger à côté du contenu, pas dessous. */
  /* Le bouton repasse dans le flux : c'est au hero de réserver la hauteur du
     pied de page, sinon la dernière ligne se glisse dessous. */
  #hero{flex-direction:column;justify-content:center;padding-bottom:calc(var(--footer-h) + 24px);}
  /* La bascule en colonne unique est répétée ici, et c'est nécessaire : la
     grille à deux colonnes est déclarée plus bas dans la feuille, et à
     spécificité égale c'est la dernière qui gagne. Sans cette reprise, la
     tablette gardait deux colonnes et l'orbe sortait du cadre. */
  #hero .wrap{width:100%;grid-template-columns:1fr;gap:40px;}
  #hero .hero-text{margin-left:0;}
  /* En colonne unique l'orbe est centrée et tient dans la colonne. Ces
     déclarations doivent vivre ici, après la règle de base : celle-ci pose
     `width:120%; max-width:none`, et sa position plus tardive dans la feuille
     lui faisait écraser le plafond du premier media query — l'orbe débordait
     alors du hero, où `overflow:hidden` la tronquait. */
  #hero .visual{
    width:min(100%, 340px);max-width:none;
    margin-left:auto;margin-right:auto;
  }
  #hero .hero-cta{position:static;left:auto;bottom:auto;transform:none;align-self:center;margin:36px 0 0;}
}
/* +30 % : 4.6vw → 6vw, borne haute 3.7 → 4.8rem. La contrainte qui fixe ces
   valeurs est « deux lignes maximum » — 4.8rem est calé sur la colonne de texte
   à 1280px (« Construire des / systèmes utiles. »), au-delà le titre passe à
   trois lignes avant d'atteindre la largeur maximale.
   La borne basse reste à 2.1rem : sur 375px de large, c'est déjà la taille qui
   fait tenir « systèmes utiles. » sur une seule ligne. L'agrandir là ne
   gagnerait rien, elle ferait une ligne de plus. */
#hero h1{
  font-size:clamp(2.1rem,6vw,4.8rem);
  line-height:1.14;
  margin-bottom:26px;
}
/* Cinzel n'a pas d'italique : l'incliner ne produirait qu'une oblique
   synthétique, qui casse le dessin gravé des capitales. L'or suffit à
   détacher le mot — c'est d'ailleurs lui qu'on voyait en premier. */
#hero h1 em{
  font-style:normal;color:var(--gold);
}
#hero p.lede{margin-bottom:38px;}
.btn{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;
  display:inline-flex;align-items:center;gap:10px;
  background:transparent;border:1px solid var(--gold-dim);color:var(--gold);
  padding:14px 22px;text-decoration:none;transition:background .25s,color .25s;
}
.btn:hover{background:var(--gold);color:var(--bg);}
.btn.secondary{border-color:rgba(236,233,226,0.3);color:var(--marble);}
.btn.secondary:hover{background:var(--marble);color:var(--bg);}
#hero .visual{
  /* L'orbe est posée à l'écran, pas dans la grille : elle doit pouvoir voler
     du hero jusqu'à son perchoir, et sa colonne est tenue par le creux.
     `left/top/width` sont écrits par le moteur à chaque image — jamais un
     `transform` : celui-ci en referait une couche composite, et le carré noir
     sur la grille du hero reviendrait (voir `.hero-doors`).
     Sa largeur est aussi ce qui la dimensionne : le canvas est donc repeint à
     sa taille du moment, et le trait reste net à toutes les échelles. Un
     `scale` l'aurait rééchantillonnée. */
  position:fixed;left:0;top:0;z-index:90;
  max-width:none;aspect-ratio:1/1;cursor:crosshair;--calm:0;--vol:0;
  /* On clique dans l'orbe pour lui donner de l'élan, et un clic répété est un
     double-clic : le navigateur y sélectionnait le bloc, qui s'allumait en
     surbrillance. Rien n'est à lire ici — les libellés sont décoratifs. */
  user-select:none;-webkit-user-select:none;
  -webkit-tap-highlight-color:transparent;
  /* Perspective pour les anneaux CSS 3D : le compositeur macOS (CoreAnimation)
     rend les layers 3D à la résolution physique native — jamais de bitmap
     intermédiaire, jamais de rééchantillonnage. */
  perspective:700px;
  perspective-origin:50% 50%;
}
/* --- Posée ---
   Tant que le vol n'a pas commencé, l'orbe n'est pas placée par le moteur :
   elle redevient un élément de la grille, et c'est le navigateur qui la fait
   défiler avec la page — en une seule opération, sans retard possible.

   ⚠️ C'est la correction d'un scintillement, pas un raffinement. Une position
   `fixed` recalculée en JS à partir de `scrollY` arrive toujours une image
   après le défilement, que le compositeur a déjà présenté : l'orbe tremblait
   sur place, en retard sur le texte qui l'entoure. Un objet en vol peut se
   permettre ce retard ; un objet immobile, non.

   Le creux s'efface alors, sinon la grille compterait deux colonnes de droite.
   Les deux bascules ont lieu dans la même image, et le passage ne se voit pas :
   le creux occupe exactement la boîte que l'orbe libère.

   C'est aussi l'état sans JS — la classe est posée dans le balisage. */
#hero .visual.est-pose{
  position:relative;left:auto;top:auto;z-index:auto;
  width:120%;justify-self:end;
  margin-right:calc(-1 * var(--push));
}
#hero .visual.est-pose ~ .visual-creux{display:none;}
@media(max-width:860px){
  #hero .visual.est-pose{
    width:min(100%, 340px);justify-self:center;margin:40px auto 0;
  }
}
/* Ceinture et bretelles : si une sélection démarre malgré tout (glissé venu du
   texte voisin, raccourci « tout sélectionner »), elle ne peint rien — le fond
   du hero reste visible à travers. */
#hero .visual::selection, #hero .visual ::selection{background:transparent;}
#hero .visual::-moz-selection, #hero .visual ::-moz-selection{background:transparent;}
/* repère central : l'axe autour duquel toute la structure tourne.
   Il s'efface pendant le vol : perchée, c'est le chiffre romain de la section
   qui occupe le centre, et les deux s'y seraient superposés. */
.orb-center{
  position:absolute;left:50%;top:50%;width:5px;height:5px;
  border-radius:50%;background:rgba(212,175,55,.8);
  transform:translate(-50%,-50%);pointer-events:none;
  opacity:calc(1 - min(1, var(--vol,0) * 1.6));
}
/* Aucun voile sur le conteneur de l'orbe.
   Historique : un dégradé radial y était posé pour estomper les bords de la
   sphère. Même ramené à une opacité partielle, il restait un aplat couvrant
   un carré — donc un rectangle perceptible sur la grille du hero, puisque
   le dégradé est circulaire et le conteneur carré. Les anneaux qu'il servait
   à estomper ne sont d'ailleurs plus dessinés depuis la suppression du
   canvas : il ne masquait plus rien. Le conteneur est désormais
   intégralement transparent — seules les portes sont peintes. */

/* --- orbites ---
   Un seul canvas 2D pour les six orbites : chacune porte une traîne dont
   l'intensité décroît derrière un point actif, ce qu'un trait uniforme (SVG
   ou div) ne sait pas rendre sans un dégradé par anneau et par frame.
   Historique de rendu, deux impasses mesurées, à ne pas reprendre :
   des divs à `border-radius` inclinés par rotateX (rastérisés en texture
   puis rééchantillonnés — le trait part en halo flou), et le même trait
   rangé sous la couche des portes (même flou, en SVG comme en div, avec ou
   sans `perspective`). D'où le z-index ci-dessous. */
/* z-index: 3 — au-dessus des portes, et ce n'est pas un choix esthétique :
   sous leur couche, le trait est peint dans une texture de fond
   rééchantillonnée et devient illisible. Seul le passage au-dessus de
   `.hero-doors` le rend net. Les traînes sont fines et transparentes : elles
   ne masquent pas les portes qu'elles croisent. */
.orb-rings{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;}

/* Compteur d'images par seconde — outil de mesure.
   Posé contre l'orbe, sous son coin bas-droit : c'est elle qu'il mesure, et
   c'est là qu'on regarde quand on cherche une saccade. `position:absolute`
   dans `.visual`, qui est déjà positionné.
   Visible par défaut, masquable d'une touche (F) — le choix est mémorisé. */
.fps-meter{
  position:absolute;right:0;bottom:-8px;z-index:4;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  padding:4px 8px;border:1px solid var(--marble-faint);
  background:rgba(11,11,11,.72);
  color:var(--marble-dim);pointer-events:none;white-space:nowrap;
}
/* En colonne unique, l'orbe est centrée et plus petite : le compteur revient
   se caler en bas de l'écran plutôt que de mordre sur le texte. */
@media(max-width:860px){
  .fps-meter{position:fixed;right:auto;left:12px;bottom:12px;z-index:9999;}
}
.fps-meter[data-etat="ok"]{color:var(--marble);}
.fps-meter[data-etat="moyen"]{color:var(--gold);border-color:var(--gold-dim);}
.fps-meter[data-etat="bas"]{color:var(--gold);border-color:var(--gold);}

/* --- les 6 portes ---
   `display: contents` : le <nav> ne produit aucune boîte, seuls ses liens en
   produisent. Ce n'est pas un raffinement — c'est le correctif du carré noir
   sur la grille du hero. Le nav était une boîte positionnée couvrant l'orbe,
   et ses enfants voient leur `transform` et leur `opacity` réécrits à chaque
   frame : le moteur promeut donc le nav en couche composite, et cette couche
   était composée sur du noir opaque. La grille du hero s'arrêtait net sur ses
   bords — un rectangle sombre exactement à ses dimensions, mesuré au contour.
   Sans boîte, plus de couche, et la grille traverse l'orbe.
   ⚠️ Ne pas rendre au nav sa boîte (position, inset, z-index) : le carré
   revient. L'empilement et l'inertie des clics sont portés par les liens.
   Compromis : sur les navigateurs antérieurs à 2021, `display: contents`
   pouvait faire perdre son rôle à l'élément — les liens, eux, restent des
   liens, et le repère `aria-label` est le seul à en souffrir. */
.hero-doors{display:contents;}
.hero-door{
  position:absolute;left:var(--x);top:var(--y);
  transform:translate(-50%,-50%);
  width:46px;height:46px;
  text-decoration:none;display:block;
  pointer-events:auto;
  cursor:default;
  z-index:2;
}
/* les portes sont positionnées via style.transform par placeDoor() */
.visual.is-settled .hero-door{cursor:pointer;}
/* Perchée, les portes se sont éteintes avec le vol (leur opacité est écrite
   par le moteur) : elles ne doivent plus rien attraper, sans quoi on cliquerait
   une porte invisible en visant le menu. */
.visual.is-perche .hero-door{pointer-events:none;}
.hero-door:focus{outline:none;}

.door-gate{position:absolute;inset:0;transform:rotate(var(--rot));display:block;}
/* L'aveu du seuil, deuxième moitié : seuls les *noms* se montrent au
   chargement. Les triangles, eux, sont posés dans le HTML à leur place de
   repos (l'hexagone) alors que le moteur va aussitôt les envoyer sur leurs
   orbites : on voyait donc six flèches surgir en cercle puis sauter ailleurs.
   Dès que le JS est disponible — la classe `js` est posée pendant l'analyse
   du document, avant le premier rendu — on les masque, et le moteur les
   rallume une fois qu'il les a placés.
   Sans JS, rien de tout cela ne s'applique : les portes restent visibles et
   cliquables à leur place de repos. */
html.js .door-gate{opacity:0;}
html.js .visual.has-js .door-gate{opacity:1;transition:opacity .45s ease;}
.door-gate svg{width:100%;height:100%;display:block;overflow:visible;}

/* le cadre s'affirme à mesure que la structure s'apaise : retour visuel
   continu pendant les 2 s, l'utilisateur voit que quelque chose se passe. */
.frame{
  fill:none;stroke:var(--gold);stroke-width:1.3;
  opacity:calc(.45 + .55 * var(--calm));
  transition:stroke .35s ease;
}
.leaf{
  fill:var(--bg);stroke:var(--gold);stroke-width:1.1;
  opacity:calc(.45 + .55 * var(--calm));
  transform-box:view-box;
  transition:transform .4s cubic-bezier(.3,.9,.3,1.2), stroke .35s ease;
}
.leaf-l{transform-origin:5px 27px;}
.leaf-r{transform-origin:27px 27px;}

/* ouverture : seulement quand la porte est stabilisée (donc cliquable) */
.is-settled .hero-door:hover .leaf-l,
.hero-door:focus-visible .leaf-l{transform:rotate(-38deg);}
.is-settled .hero-door:hover .leaf-r,
.hero-door:focus-visible .leaf-r{transform:rotate(38deg);}
.is-settled .hero-door:hover .leaf,
.is-settled .hero-door:hover .frame,
.hero-door:focus-visible .leaf,
.hero-door:focus-visible .frame{stroke:var(--blue);opacity:1;}

/* titre écrit par le feu, puis scintillant */
.door-label{
  position:absolute;left:50%;top:50%;
  transform:translate(calc(var(--tx,-50%) + var(--lox)), calc(var(--ty,-50%) + var(--loy)));
  font-family:var(--mono);font-size:20px;letter-spacing:.13em;text-transform:uppercase;
  white-space:nowrap;
  background:linear-gradient(100deg,
    var(--gold) 0%, var(--marble) 38%, var(--blue) 50%, var(--marble) 62%, var(--gold) 100%);
  background-size:280% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  opacity:calc((var(--calm) - .99) * 100);
  /* Le nom fait partie de la porte : on l'ouvre en cliquant dessus. Il ne
     capte le pointeur qu'une fois lisible — invisible, il intercepterait des
     clics destinés à la structure. */
  pointer-events:none;
  /* Les six noms ne s'allument pas d'un bloc : ils se posent l'un après
     l'autre, de Parcours à Wonderland, comme on lit une liste.
     Durée et décalage sont volontairement lents : c'est le même réglage qui
     règle l'effet d'ouverture de page (voir « L'aveu du seuil » plus bas).

     L'écriture par le feu — le nom qui se découvre lettre à lettre — est une
     *transition* de `clip-path`, et non une animation. C'était une animation
     posée au survol : elle repartait de zéro à chaque entrée du pointeur, et
     les noms clignotaient dès qu'on les survolait. Une transition ne se
     rejoue pas, elle va d'un état à l'autre. Le nom s'écrit donc une fois,
     quand la structure se range, et ne recommence jamais. */
  clip-path:inset(0 100% 0 0);
  transition:opacity .7s ease calc(var(--i, 0) * .22s),
             clip-path .55s ease-out calc(var(--i, 0) * .22s);
}
.visual.is-settled .door-label{pointer-events:auto;cursor:pointer;}
/* Sans JS, la structure est figée à son état de repos : les portes sont
   cliquables, leurs noms doivent donc être lisibles d'emblée. */
.visual:not(.has-js) .door-label{opacity:1;}
/* …mais si le JS est là, on attend qu'il ait placé les portes. Sinon les six
   noms se peignent une fraction de seconde sur l'hexagone de repos — en
   couronne, loin de leurs orbites — avant de sauter avec elles. C'est le même
   défaut que celui corrigé sur les triangles : l'aveu doit se jouer sur les
   portes en orbite, pas sur leur position de départ. */
html.js .visual:not(.has-js) .door-label{opacity:0;}

/* --- L'aveu du seuil ---------------------------------------------------
   Au chargement, les six noms sont lisibles une seconde, puis s'effacent
   l'un après l'autre. C'était au départ un artefact : le HTML montrait les
   libellés, le moteur les éteignait en démarrant. On le garde, et on l'assume
   — c'est la seule fois où la page dit ce qu'elle cache. L'orbe se referme
   ensuite sur son secret, et il faudra la survoler pour le rouvrir.
   Le temps de lecture est tenu (52 % de la durée à pleine opacité) avant que
   l'effacement ne commence : une disparition immédiate ne se lit pas, elle
   se subit. Environ trois secondes pour la série entière, contre moins d'une
   à l'origine.
   `backwards` maintient l'opacité pleine pendant le retard de chaque nom ;
   aucun remplissage à l'arrivée, pour que l'opacité rende la main au calcul
   par `--calm`. La classe est retirée par le moteur une fois la série finie :
   la laisser écraserait la respiration du repos (`doorGlow`). */
.visual.is-aveu .door-label{
  animation:doorAveu var(--aveu, 1.65s) ease-out calc(var(--i, 0) * .12s) 1 backwards;
}
@keyframes doorAveu{
  0%, 52% {opacity:1;}
  100%    {opacity:0;}
}
@media (prefers-reduced-motion: reduce){
  .visual.is-aveu .door-label{animation:none;}
}
/* Au repos, un nom posé ne doit pas avoir l'air éteint : le reste du hero
   respire, lui aussi. La lueur traverse donc le texte très lentement — 9 s
   pour une passe, contre 2,4 s au survol — et chaque porte est décalée par
   son rang (--i), pour qu'aucune vague commune ne se forme. Aucune géométrie
   ne bouge : seule la position du dégradé change, donc rien à recomposer. */
/* Rangée, la structure écrit ses noms : chacun se découvre de gauche à
   droite, à son tour. */
.is-settled .door-label{
  clip-path:inset(0 0 0 0);
  animation:doorGlow 9s linear infinite;
  animation-delay:calc(var(--i, 0) * -1.5s);
}
/* Au survol, la lueur passe simplement plus vite. On ne change pas
   d'animation — en changer en relançait une, et le nom se réécrivait sous le
   pointeur. */
.is-settled .hero-door:hover .door-label,
.hero-door:focus-visible .door-label{
  opacity:1;transition-delay:0s;
  clip-path:inset(0 0 0 0);
  animation-duration:2.4s;
}
.hero-door:focus-visible .door-label{animation-name:doorGlow;}
@keyframes doorShimmer{from{background-position:120% 0;}to{background-position:-120% 0;}}
/* La respiration du repos : la lueur passe, et l'intensité varie de trois
   centièmes — assez pour que l'œil sente une présence, pas assez pour qu'il
   la remarque. */
@keyframes doorGlow{
  0%   {background-position:120% 0;  filter:brightness(.97);}
  50%  {background-position:0% 0;    filter:brightness(1.03);}
  100% {background-position:-120% 0; filter:brightness(.97);}
}
@media (prefers-reduced-motion: reduce){
  .is-settled .door-label{animation:none;}
}

/* --- La porte fermée ---------------------------------------------------
   Comprendre reste sur son anneau, à sa place dans la couronne : retirer la
   porte aurait déséquilibré la sphère, qui compte six orbites. Elle est donc
   là, éteinte. Son nom n'est plus écrit par le dégradé — un dégradé découpé
   dans le texte ne se grise pas, il se remplace par un aplat sourd — et ses
   battants ne s'ouvrent plus au survol. */
.hero-door--chantier{cursor:default;}
.hero-door--chantier .frame,
.hero-door--chantier .leaf{
  stroke:rgba(236,233,226,.3);
  opacity:calc(.3 + .3 * var(--calm));
}
.visual.is-settled .hero-door--chantier:hover .leaf-l,
.hero-door--chantier:focus-visible .leaf-l,
.visual.is-settled .hero-door--chantier:hover .leaf-r,
.hero-door--chantier:focus-visible .leaf-r{transform:none;}
.visual.is-settled .hero-door--chantier:hover .leaf,
.visual.is-settled .hero-door--chantier:hover .frame,
.hero-door--chantier:focus-visible .leaf,
.hero-door--chantier:focus-visible .frame{stroke:rgba(236,233,226,.3);opacity:.6;}
.hero-door--chantier .door-label{
  background:none;-webkit-background-clip:border-box;background-clip:border-box;
  /* Pas `--marble-faint` (12 %) : à cette valeur le mot disparaît, et une
     porte qu'on ne lit pas ne dit pas qu'elle est fermée. 30 % suffit à la
     distinguer nettement des cinq autres sans la faire passer pour active.
     C'est un repère désactivé, pas du texte courant : il sort volontairement
     du plancher de lisibilité fixé pour le discours du site. */
  color:rgba(236,233,226,.3);
  animation:none;
}
.visual.is-settled .hero-door--chantier .door-label{cursor:default;}
/* L'aveu : posé sous le nom, il ne prend aucune place et ne paraît qu'au
   clic — le temps de lire, puis il s'en va. */
.door-chantier{
  position:absolute;left:50%;top:50%;
  transform:translate(calc(var(--tx,-50%) + var(--lox)), calc(var(--ty,-50%) + var(--loy) + 22px));
  padding:2px 8px;
  background:rgba(11,11,11,.8);
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold);white-space:nowrap;pointer-events:none;
  opacity:0;visibility:hidden;
  transition:opacity .3s ease,visibility 0s linear .3s;
}
.hero-door--chantier.dit-chantier .door-chantier{
  opacity:1;visibility:visible;transition-delay:0s;
}

@media(max-width:720px){
  .hero-door{width:38px;height:38px;}
  .door-label{font-size:14px;letter-spacing:.08em;}
}

/* ---------- méthode ---------- */

/* ---------- parcours ---------- */
/* Styles déplacés dans timeline.css (constellation temporelle interactive) —
   à une exception près, ci-dessous, et elle est de fond. */

/* --- Parcours : la place est réservée avant que la frise n'arrive ---------
   Ces quatre règles ne sont pas dans `timeline.css` avec le reste, et ce n'est
   pas un oubli : **une feuille différée ne peut pas être celle qui donne sa
   place à un bloc.** Elle arrive après le premier rendu, par définition.

   Ce qu'il se passait sans elles : `#parcours` mesurait 455px au premier rendu
   — bande de domaines vide, scène à 26px — puis 884px une fois `timeline.css`
   arrivée et la frise montée. **429px apparaissaient sous le lecteur.**

   Deux conséquences, dont la seconde était le vrai défaut :
     — qui lisait pendant ce temps voyait la page se réorganiser sous lui ;
     — et surtout, tout ce qui suit `#parcours` se déplaçait d'autant. Le
       navigateur, qui avait déjà restauré le défilement ou appliqué l'ancre,
       visait alors une position devenue fausse : on rechargeait dans une
       section et on se retrouvait ailleurs.

   Le rattrapage (« Retrouver sa place », plus bas) traite le symptôme ; ces
   règles-ci traitent la cause. Les deux se gardent : la réservation ne peut
   pas être exacte au pixel près pour toujours, et le rattrapage couvre le
   reste — polices, images, données qui changent.

   ⚠️ 334px n'est pas une valeur décorative : c'est la hauteur que `timeline.js`
   calcule pour le jeu de données actuel, à partir du nombre de couloirs
   occupés, et qu'il réécrit en style en ligne au montage. Plus cette constante
   en est proche, plus le déplacement résiduel est faible. Elle valait 440px
   dans `timeline.css` — un repli jamais réajusté, faux de 106px. Si la frise
   gagne des couloirs, relever la valeur ici. */
.domain-band{ margin:var(--pc-air) 0; min-height:var(--pc-bande); }
.constellation-stage{ position:relative; margin-top:0; }
.constellation{ position:relative; width:100%; height:334px; overflow:hidden; }

/* --- Parcours : une section, un écran ---------------------------------
   La section occupe au moins toute la hauteur visible et centre son contenu
   dans une colonne flex. Seule la frise a une hauteur imposée (calculée par
   timeline.js) : tout le reste — air, icônes, libellés, titre, texte — se
   règle sur la hauteur d'écran (`svh`), entre un plancher lisible et le
   plafond d'origine. Sur un écran de bureau, l'ensemble tient donc dans un
   écran sans jamais dépasser ses tailles de référence.
   Sous le plancher (paysage très bas, téléphone avec un texte long), la
   section s'allonge au lieu de rogner : `min-height`, pas `height`.
   Le bas réserve la hauteur du pied de page fixe, qui recouvrirait le texte. */
#parcours{
  --pc-air: clamp(10px, 6svh - 26px, 34px);
  --pc-icone: clamp(22px, 3.6svh, 33px);
  --pc-libelle: clamp(10px, 1.8svh, 16.5px);
  --pc-bande: calc(var(--pc-icone) + var(--pc-libelle) * 1.3 + 20px);
  box-sizing:border-box;
  min-height:100vh;
  min-height:100svh;
  display:flex;flex-direction:column;justify-content:center;
  padding:clamp(28px, 14svh - 60px, 90px) 0 calc(var(--footer-h) + clamp(10px, 6svh - 26px, 33px));
}
#parcours > *{flex:none;width:100%;}
#parcours .eyebrow{margin-bottom:clamp(10px, 5svh - 20px, 28px);}
#parcours .domain-icon{width:var(--pc-icone);height:var(--pc-icone);}
#parcours .domain-label{font-size:min(var(--chip-fs, 16.5px), var(--pc-libelle));}
#parcours .lede--wide{
  font-size:clamp(.95rem, 2.2svh, 1.16rem);
  line-height:1.72;
}
@media (max-height:760px){
  #parcours .lede--wide{line-height:1.5;}
  #parcours .corps-accroche{margin-bottom:.5em;}
}
@media (max-width:820px){ #parcours .lede--wide{font-size:clamp(.95rem, 2.2svh, 1.05rem);} }

/* --- L'arrivée dans le Parcours ---------------------------------------
   La section ne se donne pas d'un bloc : le titre, puis la frise qui
   s'active au centre, puis les domaines un par un au-dessus d'elle, et le
   texte en dernier. L'ordre d'apparition n'est pas celui de la page. En repartant, tout se retire dans
   l'ordre inverse : la révélation est réversible, elle n'est pas un rideau
   qu'on lève une fois pour toutes.

   `rev-arme` n'est posée qu'en JS : sans lui, rien n'est masqué et la section
   s'affiche entière. Les puces sont masquées par la section et non une par
   une, parce qu'elles sont créées par timeline.js après ce code — une règle
   portée par l'ancêtre s'applique à ce qui n'existe pas encore. */
/* Masqué veut dire inerte : `visibility` retire aussi l'élément des clics et
   de l'ordre de tabulation. Elle bascule à la fin du fondu en sortie, avec
   son début en entrée. */
#parcours.rev-arme .domain-chip:not(.est-la){opacity:0;visibility:hidden;
  transition:opacity .45s ease, visibility 0s linear .45s,
             transform .5s cubic-bezier(.22,.9,.28,1), color .25s ease;}
#parcours .domain-chip{
  /* on reprend la transition de timeline.css pour lui ajouter l'opacité :
     `transform` sert déjà au rangement de la bande au survol, on n'y touche pas.
     En entrée, fondu et visibilité partent ensemble 0,1 s après le signal :
     la puce n'est jamais cliquable tant qu'on ne la voit pas encore. */
  transition:opacity .45s ease .1s,
             visibility 0s linear .1s,
             transform .5s cubic-bezier(.22,.9,.28,1),
             color .25s ease;
}
#parcours .domain-chip.est-la{opacity:1;}
/* L'accroche du corps : une seule ligne, centrée, plus haute de voix que ce
   qui suit. Le paragraphe reprend ensuite au fer à gauche — c'est la question
   qui est centrée, pas la réponse. */
/* Le titre du Parcours tient sur une seule ligne, à toute largeur. Sa taille
   est donc calculée, pas choisie : 43 caractères à 0,557 em de large chacun
   font 24 em de texte, auxquels on ajoute une marge de sécurité. La borne
   haute reste la taille commune des h2.
   ⚠️ Conséquence assumée : sous ~500px, tenir la ligne impose un corps très
   petit (≈13px à 375px). C'est le prix d'une ligne unique sur une phrase de
   cette longueur — la raccourcir serait la seule autre issue. */
#parcours h2{
  white-space:nowrap;
  font-size:min(2.4rem, calc((min(100vw, 980px) - 60px) / 24.8), 4.6svh);
  margin-bottom:0;
}
.parcours-corps{margin-top:var(--pc-air);margin-bottom:0;}
.corps-accroche{
  display:block;text-align:center;
  font-size:1.25em;font-weight:500;
  color:var(--marble);
  margin-bottom:.85em;
  text-wrap:balance;
}
#parcours .rev-item{opacity:0;visibility:hidden;
  transition:opacity .55s ease, transform .55s ease, visibility 0s linear .55s;}
#parcours .rev-item.rev-texte{transform:translateY(10px);}
#parcours .rev-item.est-la{opacity:1;transform:none;visibility:visible;
  transition-delay:0s;}
@media (prefers-reduced-motion: reduce){
  #parcours.rev-arme .domain-chip,
  #parcours .rev-item{opacity:1 !important;transform:none !important;visibility:visible !important;}
}

/* ---------- créer : filets et vide ---------- */
.flow{
  display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin:44px 0 56px;
  border-top:1px solid var(--marble-faint);border-bottom:1px solid var(--marble-faint);
}
.flow div{
  padding:22px 18px;text-align:center;font-family:var(--mono);font-size:11px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--marble-dim);
  border-right:1px solid var(--marble-faint);position:relative;
}
.flow div:last-child{border-right:none;color:var(--gold);}
.flow div::after{
  content:"→";position:absolute;right:-9px;top:50%;transform:translateY(-50%);
  color:var(--gold-dim);font-size:12px;
}
.flow div:last-child::after{content:"";}
@media(max-width:720px){
  .flow{grid-template-columns:1fr;}
  .flow div{border-right:none;border-bottom:1px solid var(--marble-faint);}
  .flow div::after{content:"↓";right:auto;left:50%;top:auto;bottom:-9px;transform:translateX(-50%);}
  .flow div:last-child{border-bottom:none;}
}

.card-grid{display:grid;grid-template-columns:1fr 1fr;gap:40px;margin-top:8px;}
.card{padding:30px 0;border-top:1px solid var(--marble-faint);}
.card h3{font-size:1.05rem;margin-bottom:10px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.card p{font-size:.95rem;margin-bottom:12px;}
.card p:last-child{margin-bottom:0;}
.card .card-links{display:flex;flex-wrap:wrap;gap:10px;margin-top:2px;}
.card .card-links a{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--blue);text-decoration:none;border-bottom:1px solid rgba(111,168,220,0.35);
  padding-bottom:1px;transition:color .2s,border-color .2s;
}
.card .card-links a:hover{color:var(--gold);border-color:var(--gold-dim);}
.card .pending{font-family:var(--mono);font-size:10px;color:var(--marble-quiet);text-transform:uppercase;letter-spacing:.06em;}
/* Vignette d'une carte : elle remonte jusqu'aux bords de la carte, sous le
   padding, pour rester une image et non une illustration encadrée. */
.card-vignette{
  display:block;width:100%;height:auto;margin:-30px 0 20px;
  aspect-ratio:16/10;object-fit:cover;
  border-bottom:1px solid var(--marble-faint);
}
@media(max-width:720px){.card-grid{grid-template-columns:1fr;}}

.sources{margin-top:16px;border-top:1px solid var(--marble-faint);padding-top:26px;}
.sources h3{font-size:.95rem;margin-bottom:16px;}
.source-list{list-style:none;margin:0;padding:0;display:grid;gap:12px;}
.source-list li{
  display:grid;grid-template-columns:70px 1fr;gap:14px;align-items:baseline;
  font-size:.92rem;color:var(--marble-dim);
}
.source-list .src-type{
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--gold);
}
.source-list a{color:var(--blue);text-decoration:none;border-bottom:1px solid rgba(111,168,220,0.35);}
.source-list a:hover{color:var(--gold);border-color:var(--gold-dim);}
@media(max-width:720px){.source-list li{grid-template-columns:1fr;gap:2px;}}

/* ---------- comprendre glossary ---------- */
.glossary{display:grid;grid-template-columns:repeat(2,1fr);gap:22px;margin-top:50px;}
.glossary .entry{border-top:1px solid var(--gold-dim);padding-top:16px;}
.glossary .entry .term{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);margin-bottom:8px;display:block;}
@media(max-width:720px){.glossary{grid-template-columns:1fr;}}

/* ---------- grimoire (dépliant) ----------
   Seul le bouton est stylé ici : la grille et la visionneuse vivent dans
   gallery.css, chargé au premier dépliage. */
/* Le grimoire ouvre la section : c'est le contenu, le glossaire n'en est
   que le vocabulaire. */
.grimoire{margin-top:44px;border-top:1px solid var(--marble-faint);padding-top:34px;}
.gr-toggle{
  all:unset;box-sizing:border-box;cursor:pointer;
  display:flex;align-items:baseline;gap:14px;width:100%;
  padding:6px 0;
}
.gr-toggle-label{
  font-family:var(--title);font-size:1.15rem;color:var(--gold);
  transition:color .2s ease;
}
.gr-toggle-hint{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--marble-dim);
}
.gr-toggle-chevron{
  margin-left:auto;color:var(--marble-dim);font-size:13px;
  transition:transform .25s ease,color .2s ease;
}
.gr-toggle:hover .gr-toggle-label,
.gr-toggle:focus-visible .gr-toggle-label{color:var(--marble);}
.gr-toggle:hover .gr-toggle-chevron,
.gr-toggle:focus-visible .gr-toggle-chevron{color:var(--gold);}
.gr-toggle:focus-visible{outline:1px solid var(--gold);outline-offset:4px;}
.gr-toggle[aria-expanded="true"] .gr-toggle-chevron{transform:rotate(180deg);}

.gr-panel{padding-top:6px;}
.gr-empty{color:var(--marble-dim);font-size:.9rem;}
.grimoire-actions{margin-top:28px;}




/* Le .lede standard est calibré pour une colonne étroite ; ce texte-là doit
   au contraire occuper la largeur disponible, sinon il ressemble à un
   bandeau coincé sous le titre. */
.lede--wide{
  max-width:none;width:100%;
  font-size:1.16rem;line-height:1.72;
  text-align:center;
  margin-left:auto;margin-right:auto;
  padding:0 clamp(0px,4vw,80px);
}
@media(max-width:820px){.lede--wide{text-align:left;padding:0;font-size:1.05rem;}}

/* ---------- la clôture qui n'en est pas une (colonne gauche du Contact) ----
   Écho du hero : beaucoup de vide, des traits fins, rien d'encadré. La
   citation dérive très lentement, comme les portes de la sphère. */
/* Le bloc réserve la place de la plus longue pensée (hauteur posée par le
   JS) ; le filet, lui, s'aligne sur le paragraphe et non sur cette réserve,
   sinon il pendrait dans le vide sous les citations courtes. */
.drift-quote{
  position:relative;margin:0;
  transition:opacity .5s ease;
}
.drift-quote.is-fading{opacity:0;}
/* un trait fin en guise de guillemet — pas d'encadré, pas de carte */
.drift-quote p{position:relative;padding-left:26px;}
.drift-quote p::before{
  content:"";position:absolute;left:0;top:.45em;bottom:.45em;width:1px;
  background:linear-gradient(to bottom,var(--gold-dim),transparent);
}
/* La citation porte la colonne : elle prend toute la largeur disponible et
   se lit un cran au-dessus du texte courant. */
.drift-quote p{
  font-family:var(--serif);font-size:1.53rem;
  line-height:1.44;max-width:none;
  color:var(--marble);margin:0;font-style:italic;
  animation:driftFloat 16s ease-in-out infinite;
}
.drift-quote cite{
  display:block;margin:14px 0 0 26px;
  font-family:var(--mono);font-size:10px;line-height:1.5;
  letter-spacing:.12em;text-transform:uppercase;font-style:normal;
  color:var(--gold);
}
@keyframes driftFloat{
  0%,100%{transform:translate(0,0);}
  50%{transform:translate(3px,-5px);}
}
/* le dé : relancer le tirage. Le libellé emprunte l'écriture des portes de
   l'orbe — même graisse, même chasse, même dégradé or → bleu qui scintille.
   C'est le même geste (franchir, retirer), il doit se lire pareil. */
.quote-reroll{
  all:unset;box-sizing:border-box;cursor:pointer;
  display:inline-flex;align-items:center;gap:14px;
  /* `margin-top:auto` le colle en bas de la colonne : il ne bouge plus d'un
     tirage à l'autre et s'aligne sur la rangée de liens d'en face. */
  align-self:flex-start;margin:44px 0 0 26px;margin-top:auto;padding:6px 2px;
  transition:color .25s ease;
}
.quote-reroll .reroll-label{
  font-family:var(--mono);font-size:19.5px;letter-spacing:.13em;
  text-transform:uppercase;white-space:nowrap;
  background:linear-gradient(100deg,
    var(--gold) 0%, var(--marble) 38%, var(--blue) 50%, var(--marble) 62%, var(--gold) 100%);
  background-size:280% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  animation:doorShimmer 3.6s linear infinite;
}
.quote-reroll:hover .reroll-label,
.quote-reroll:focus-visible .reroll-label{animation-duration:1.6s;}
@media(max-width:720px){
  .quote-reroll{margin-top:32px;}
  .quote-reroll .reroll-label{font-size:15px;letter-spacing:.09em;}
  .quote-reroll svg{width:25px;height:25px;}
  .drift-quote p{font-size:1.3rem;}
}
@media(prefers-reduced-motion:reduce){
  .quote-reroll .reroll-label{animation:none;}
}
.quote-reroll svg{
  width:31px;height:31px;display:block;
  fill:none;stroke:var(--gold-dim);stroke-width:1.2;
  transition:stroke .25s ease,transform .5s cubic-bezier(.3,.9,.3,1.1);
}
.quote-reroll svg circle{fill:var(--gold-dim);stroke:none;transition:fill .25s ease;}
.quote-reroll:hover svg,.quote-reroll:focus-visible svg{stroke:var(--gold);}
.quote-reroll:hover svg circle,.quote-reroll:focus-visible svg circle{fill:var(--gold);}
.quote-reroll:focus-visible{outline:1px solid var(--gold);outline-offset:5px;}
.quote-reroll.is-rolling svg{transform:rotate(180deg);}
@media(prefers-reduced-motion:reduce){
  .drift-quote p{animation:none;}
}

/* ---------- section Créer : ossature ----------
   Ces règles sont critiques : le titre et les trois portes sont tout ce que
   la section donne avant le premier choix. Aucun bouton brut ne doit paraître
   pendant le chargement différé des créations. */
#creer{min-height:100vh;min-height:100svh;box-sizing:border-box;display:flex;align-items:flex-start;}
/* Une porte ouverte change ce que la section doit tenir : au repos, le titre
   et les trois arches ont besoin d'air, et les 150px de padding commun les
   posent au bon endroit. Un volet ouvert, lui, ajoute son contenu sous une
   rangée déjà compacte — les mêmes 270px de padding faisaient alors sortir
   Inclassable de l'écran (mesuré : 1229px de section pour 900px de fenêtre).
   Le padding se resserre donc dès qu'un volet paraît. */
#creer.est-ouvert{
  padding:clamp(40px,7svh,72px) 0 calc(var(--footer-h) + clamp(12px,2svh,20px));
}
#creer.est-ouvert .creer-entree{gap:clamp(18px,3vh,30px);margin-top:0;}
#creer.est-ouvert .eyebrow{margin-bottom:16px;}
#creer.est-ouvert .door-panel{margin-top:24px;}
/* Créer peut employer davantage de largeur que le fil éditorial : sa rangée
   compacte doit tenir entre les deux limites visuelles de la section, sans
   empiéter sur le repère fixe à droite. */
#creer > .wrap{width:100%;max-width:1160px;}

/* Au repos, le titre précède une rangée ample et centrée. Le premier clic
   pose `is-compact` : le titre et la navigation rejoignent alors leur position
   de travail sur une seule ligne, laissant la place au volet choisi. */
.creer-entree{
  display:grid;grid-template-columns:1fr;align-items:center;
  gap:clamp(34px,6vh,64px);margin-top:8px;
}
.creer-entree:not(.is-compact){
  min-height:max(430px,calc(100svh - 345px));
  grid-template-rows:auto 1fr;
}
.creer-entree:not(.is-compact) .doors{align-self:center;}
.creer-entree h2{text-align:center;margin:0;text-wrap:balance;}
.doors{
  display:flex;justify-content:center;align-items:flex-start;
  gap:clamp(8px,2vw,28px);width:100%;margin:0 auto;
}

.creer-entree.is-compact{
  grid-template-columns:minmax(0,1fr) auto;
  align-items:start;gap:clamp(18px,3vw,48px);
}
.creer-entree.is-compact h2{
  text-align:left;white-space:nowrap;
  font-size:clamp(1.3rem,1.9vw,1.75rem);
}
.creer-entree.is-compact .doors{width:max-content;gap:8px;}
.creer-entree.is-compact .door{width:clamp(124px,10vw,140px);padding:8px 6px;gap:7px;}
.creer-entree.is-compact .door-frame{width:46px;height:64px;}
.creer-entree.is-compact .door-name{font-size:1rem;}
.creer-entree.is-compact .door-sub{font-size:8.5px;}
.door {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  cursor: pointer;
  margin: 0 32px;
  padding: 18px 26px;
  border: 1px solid transparent;
  border-radius: 3px;
  min-height:44px;
  transition:border-color .3s ease,background .3s ease;
}
.doors > .door{width:clamp(170px,20vw,230px);max-width:calc((100% - 16px) / 3);margin:0;}
.door:hover, .door:focus-visible {
  border-color: var(--gold-dim);
  background: rgba(212,175,55,0.04);
  outline: none;
}

.door-frame { display: block; width: 84px; height: 118px; }
.door-frame svg { width: 100%; height: 100%; display: block; overflow: visible; }
.door-arch {
  fill: none; stroke: var(--gold); stroke-width: 1.2;
  opacity: .55;
  transition: opacity .3s ease, stroke .3s ease;
}
/* Les deux battants partent superposés au centre et s'écartent au survol :
   la porte s'ouvre, elle ne s'illumine pas. */
.door-leaf {
  fill: none; stroke: var(--gold); stroke-width: 1.1;
  opacity: .5;
  transform-origin: 30px 82px;
  transition: transform .45s cubic-bezier(.3,.9,.3,1.15), opacity .3s ease;
}
.door-knob { fill: var(--gold); opacity: .5; transition: opacity .3s ease; }

.door:hover .door-leaf--l,
.door:focus-visible .door-leaf--l,
.door.is-active .door-leaf--l { transform: rotate(-17deg); }
.door:hover .door-leaf--r,
.door:focus-visible .door-leaf--r,
.door.is-active .door-leaf--r { transform: rotate(17deg); }
.door:hover .door-arch, .door:hover .door-leaf, .door:hover .door-knob,
.door:focus-visible .door-arch, .door:focus-visible .door-leaf, .door:focus-visible .door-knob,
.door.is-active .door-arch, .door.is-active .door-leaf, .door.is-active .door-knob { opacity: 1; }

.door-name {
  font-family: var(--title);
  font-size: 1.4rem;
  color: var(--gold);
}
.door-sub {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--marble-dim);
}

/* Description de seuil : elle ne prend aucune place dans la composition et
   ne se révèle que lorsque les portes sont au centre.
   Elle était posée à mi-hauteur des portes, décalée à gauche, au centre ou à
   droite selon la porte : le texte couvrait les arches voisines, et rien ne
   disait de laquelle des trois il parlait. Il paraît maintenant **sous** la
   porte concernée, centré sur elle — la désignation est dans la position, il
   n'y a plus à la deviner.
   C'est le seul encadré du site, et il est assumé : DA_GRAPHIQUE proscrit la
   carte parce qu'une boîte de plus n'apprend rien. Ici elle apprend quelque
   chose — qu'un texte flottant sur le noir, entre trois arches dorées, a
   besoin d'un bord pour qu'on sache où il commence. Le contour reste un filet
   d'un pixel en or estompé, sans ombre et sans arrondi appuyé. */
.door-description{
  position:absolute;top:calc(100% + 16px);left:50%;
  transform:translateX(-50%);z-index:4;
  box-sizing:border-box;width:clamp(230px,24vw,300px);
  padding:15px 18px;
  border:1px solid var(--gold-dim);border-radius:2px;
  background:rgba(11,11,11,.96);
  font-family:var(--body);font-size:.92rem;font-weight:300;line-height:1.55;
  color:var(--marble);text-align:left;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .25s ease,visibility 0s linear .25s;
}
/* Le repère qui rattache la boîte à sa porte : un trait vertical, de la même
   famille que tous les filets du site. */
.door-description::before{
  content:"";position:absolute;left:50%;top:-16px;
  width:1px;height:16px;background:var(--gold-dim);
}
.creer-entree:not(.is-compact) .door:hover .door-description,
.creer-entree:not(.is-compact) .door:focus-visible .door-description,
.creer-entree:not(.is-compact) .door.is-previewed .door-description{
  opacity:1;visibility:visible;transition-delay:0s;
}

/* ---- une porte est ouverte : elle reste à sa place compacte et s'affirme ---- */
.door.is-active { border-color: var(--gold-dim); background: rgba(212,175,55,0.05); }

.door-panel { margin-top: 34px; }

/* ---------- Numérique : un projet à la fois, texte puis aperçu ---------- */
.create-apps{margin-top:42px;border-top:1px solid var(--marble-faint);padding-top:30px;}
.create-app{
  display:grid;grid-template-columns:minmax(240px,.8fr) minmax(0,1.2fr);
  align-items:center;gap:clamp(34px,6vw,86px);min-height:360px;
}
.create-app[hidden]{display:none;}
.create-apps:focus-visible{outline:1px solid var(--blue);outline-offset:6px;}
.create-app-copy .tag{display:inline-block;margin-bottom:14px;}
.create-app-copy h3{font-size:clamp(1.45rem,2.4vw,2rem);margin:0 0 16px;}
.create-app-copy p{max-width:52ch;margin-bottom:22px;}
.create-app-copy a{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--blue);text-decoration:none;border-bottom:1px solid rgba(111,168,220,.35);
  padding-bottom:2px;transition:color .2s,border-color .2s;
}
.create-app-copy a:hover{color:var(--gold);border-color:var(--gold-dim);}
.create-app-copy a:focus-visible{outline:1px solid var(--blue);outline-offset:4px;}
.create-app-preview{
  position:relative;margin:0;aspect-ratio:16/10;overflow:hidden;
  border-top:1px solid var(--gold-dim);border-bottom:1px solid var(--marble-faint);
}
.create-app-preview img{display:block;width:100%;height:100%;object-fit:cover;}
.create-app-preview--pending{
  display:grid;place-items:center;
  background:repeating-linear-gradient(45deg,rgba(236,233,226,.022) 0 2px,transparent 2px 8px);
}
.create-app-preview--pending span{
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--marble-dim);
}
.create-apps-controls{display:flex;justify-content:flex-end;align-items:center;gap:12px;margin-top:20px;}
.create-apps-controls button{
  width:44px;height:44px;border:1px solid var(--gold-dim);border-radius:50%;
  background:transparent;color:var(--gold);font:16px var(--mono);cursor:pointer;
  transition:border-color .25s ease,background .25s ease;
}
.create-apps-controls button:hover,.create-apps-controls button:focus-visible{
  border-color:var(--blue);background:rgba(111,168,220,.06);outline:none;
}
.create-apps-controls button:focus-visible{outline:1px solid var(--blue);outline-offset:4px;}
.create-apps-controls [data-app-status]{
  min-width:48px;text-align:center;font-family:var(--mono);font-size:10px;
  letter-spacing:.12em;color:var(--marble-dim);
}

@media(max-width:900px){
  .creer-entree.is-compact{grid-template-columns:1fr;gap:22px;}
  .creer-entree.is-compact h2{text-align:center;white-space:normal;font-size:clamp(1.55rem,5vw,2rem);}
  .creer-entree.is-compact .doors{width:100%;}
}
@media(max-width:700px){
  #creer{padding-top:100px;}
  #creer > .wrap{max-width:100%;}
  .creer-entree{gap:28px;}
  .creer-entree:not(.is-compact){min-height:max(420px,calc(100svh - 280px));}
  .doors > .door{width:calc((100% - 16px) / 3);max-width:none;flex:0 1 calc((100% - 16px) / 3);}
  .door{padding:12px 6px;gap:10px;}
  .door-frame{width:58px;height:81px;}
  .door-name{font-size:clamp(.86rem,3.8vw,1.1rem);}
  .door-sub{font-size:8px;letter-spacing:.06em;}
  .creer-entree.is-compact .door{width:calc((100% - 16px) / 3);}
  .creer-entree.is-compact .door-name{font-size:.88rem;}
  .creer-entree.is-compact .door-sub{font-size:7.5px;}
  /* Trois portes étroites : une boîte centrée sur la porte de bord sortirait
     de la page. Elle se cale alors sur la rangée entière, et son trait garde
     le lien avec la porte qui l'a demandée. */
  .door-description{width:min(300px,calc(100vw - 56px));}
  .door[data-door="art"] .door-description{left:0;transform:none;}
  .door[data-door="art"] .door-description::before{left:50%;}
  .door[data-door="kino"] .door-description{left:auto;right:0;transform:none;}
  .door[data-door="kino"] .door-description::before{left:auto;right:50%;}
  .create-app{grid-template-columns:1fr;gap:28px;min-height:0;}
  .create-app-preview{grid-row:1;}
  .create-apps-controls{justify-content:center;}
}

@media(prefers-reduced-motion:reduce){
  .door,.door-description,.create-apps-controls button{transition:none;}
}

/* ---------- blocs à l'intérieur d'une colonne ---------- */
.create-block { margin-top: 16px; }
.create-block > h4 {
  font-family: var(--body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--marble);
  margin: 0 0 4px;
}
.create-block > .create-note {
  font-size: 0.9rem;
  color: var(--marble-dim);
  margin: 0;
}



/* ---------- liens du projet (Habiter) ----------
   Deux entrées de même rang, sur une seule ligne. Ce sont des liens, pas des
   destinations : les encadrer leur donnait le poids d'une section entière
   alors qu'ils ne font que pointer ailleurs. */
.plink-line{
  display:flex;flex-wrap:wrap;align-items:center;gap:30px;
  margin:38px 0 0;
}
/* Bleu : c'est la couleur des liens partout ailleurs sur le site (sources,
   cartes). Un lien nu doit se reconnaître sans être survolé. */
.plink-inline{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--body);font-size:.98rem;color:var(--blue);
  text-decoration:none;padding-bottom:2px;
  border-bottom:1px solid rgba(111,168,220,0.35);
  transition:color .22s ease,border-color .22s ease;
}
.plink-inline .plink-ico{font-size:.95em;line-height:1;}
.plink-inline:hover,.plink-inline:focus-visible{
  color:var(--gold);border-color:var(--gold-dim);outline:none;
}
@media(max-width:560px){.plink-line{gap:16px 24px;}}

/* ---------- volet « en savoir plus » ----------
   Un simple filet qui s'ouvre : l'intitulé est posé au milieu du trait, et
   le contenu se déplie centré en dessous. Aucun JS — <details> gère
   l'ouverture et le clavier. Réutilisable partout où une section a une
   suite qui n'est pas encore prête à être montrée. */
.reveal{margin-top:34px;}
.reveal>summary{
  cursor:pointer;list-style:none;
  display:flex;align-items:center;gap:16px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--marble-dim);
  padding:4px 0;transition:color .25s ease;
}
.reveal>summary::-webkit-details-marker{display:none;}
.reveal>summary::before,.reveal>summary::after{
  content:"";flex:1;height:1px;background:var(--marble-faint);
  transition:background .25s ease;
}
.reveal>summary:hover,.reveal>summary:focus-visible{color:var(--gold);}
.reveal>summary:hover::before,.reveal>summary:hover::after,
.reveal>summary:focus-visible::before,.reveal>summary:focus-visible::after{background:var(--gold-dim);}
.reveal>summary:focus-visible{outline:none;}
.reveal-chevron{font-size:9px;transition:transform .3s ease;}
.reveal[open] .reveal-chevron{transform:rotate(180deg);}
.reveal-panel{display:flex;justify-content:center;padding:30px 0 8px;}
/* Variante pour un panneau qui porte du texte plutôt que la seule mention
   « Construction en cours » : même famille que .essay-body, colonne étroite
   et alignée à gauche plutôt que centrée. */
.reveal-panel.reveal-text{display:block;max-width:680px;padding:26px 0 8px;}
.reveal-panel.reveal-text h4{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold);margin:22px 0 8px;
}
.reveal-panel.reveal-text h4:first-child{margin-top:0;}
.reveal-panel.reveal-text p{font-size:.98rem;margin:0 0 4px;color:var(--marble-dim);}

/* ---------- plan des Mar'Champs ----------
   Le trait suit `color`, jamais une couleur en dur : c'est ce qui permettra
   de piloter les zones depuis la feuille de style le jour venu. */
/* Le plan prend ce qui reste. Il n'a plus de hauteur écrite : il est l'élément
   élastique de la colonne, et c'est lui qui absorbe le vide que laissaient le
   titre, les volets et le récit. `min-height:0` est indispensable — sans lui,
   un élément flexible refuse de descendre sous la taille de son contenu, et le
   SVG aurait débordé la section sur un écran bas. */
.plan-habiter{
  /* `flex-basis:0`, et non `auto` : le SVG mesure 16:9, donc 720px de haut
     pour 1280 de large. Avec une base `auto`, l'élément réclamait cette
     hauteur intrinsèque, la colonne la lui accordait et la section passait à
     1167px — l'inverse de ce qu'on cherchait. Base nulle, il ne demande
     rien et reçoit ce qui reste. */
  flex:1 1 0;min-height:clamp(190px,28svh,300px);
  margin:clamp(8px,2svh,18px) 0 0;
  color:var(--gold);display:flex;justify-content:center;align-items:center;
}
.plan-habiter svg{width:auto;max-width:100%;height:100%;display:block;}
.plan-habiter figcaption{display:flex;justify-content:center;width:100%;}

.section-wip{margin-top:48px;}

/* ---------- les sauts de section ----------
   Deux repères par section : un vers celle d'avant, un vers celle d'après.
   Toujours au même endroit — collés au bord gauche, à hauteur fixe du haut et
   du bas de la section — pour qu'on n'ait jamais à les chercher.

   Le dessin suit la grammaire du site : un cercle d'un pixel en or estompé,
   une flèche d'un seul trait dedans. Pas de disque plein, pas d'ombre. Le
   cercle est le seul contour arrondi admis ici parce qu'il redit la sphère du
   hero : le menu du site est un anneau, ses raccourcis en sont d'autres.

   Ils s'effacent (`opacity`) au lieu de disparaître, et seule l'opacité et un
   `transform` bougent au survol — rien à recomposer.

   Sans JS, ils n'existent pas : c'est le moteur qui les pose, et la
   navigation reste entière par la colonne de l'orbe et les portes du hero. */
.saut{
  position:absolute;left:clamp(12px,3vw,40px);z-index:6;
  width:38px;height:38px;
  display:flex;align-items:center;justify-content:center;
  border:1px solid var(--gold-dim);border-radius:50%;
  background:rgba(11,11,11,.5);
  color:var(--gold);text-decoration:none;
  opacity:.34;
  transition:opacity .3s ease,border-color .3s ease,transform .3s ease;
}
.saut--haut{top:clamp(18px,4svh,42px);}
.saut--bas{bottom:calc(var(--footer-h) + clamp(14px,3svh,32px));}
.saut svg{
  width:17px;height:17px;display:block;
  fill:none;stroke:currentColor;stroke-width:1.2;
  stroke-linecap:round;stroke-linejoin:round;
}
.saut:hover,.saut:focus-visible{
  opacity:1;border-color:var(--gold);outline:none;
}
.saut--haut:hover,.saut--haut:focus-visible{transform:translateY(-3px);}
.saut--bas:hover,.saut--bas:focus-visible{transform:translateY(3px);}
.saut:focus-visible{outline:1px solid var(--gold);outline-offset:4px;}
/* Au doigt, la cible passe à 44px et les repères se rapprochent du bord :
   sur 375px de large, 40px de retrait mangeraient le texte. */
@media (pointer:coarse){
  .saut{width:44px;height:44px;}
}
@media (max-width:860px){
  .saut{left:6px;opacity:.5;}
}
@media (prefers-reduced-motion:reduce){
  .saut{transition:none;}
  .saut:hover,.saut:focus-visible{transform:none;}
}

/* ---------- bloc « chantier » ----------
   Signale une zone en construction. Pensé pour être posé tel quel partout :
   la mention est écrite dans le HTML (elle reste lisible sans JS), seul
   l'engrenage est injecté, pour ne pas recopier le même SVG à chaque emploi. */
.wip{
  display:inline-flex;flex-direction:column;align-items:center;gap:11px;
  margin:0;font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--marble-dim);
}
.wip-gear{
  width:34px;height:34px;color:var(--gold);opacity:.55;
  animation:wipTurn 11s linear infinite;
}
.wip-gear svg{
  width:100%;height:100%;display:block;
  fill:none;stroke:currentColor;stroke-width:1.3;
  stroke-linecap:round;stroke-linejoin:round;
}
@keyframes wipTurn{to{transform:rotate(360deg);}}
/* variante posée en pied de bloc, sur une ligne */
.wip--inline{flex-direction:row;gap:9px;margin-top:16px;font-size:9.5px;}
.wip--inline .wip-gear{width:19px;height:19px;}
@media(prefers-reduced-motion:reduce){.wip-gear{animation:none;}}

/* ---------- laboratoire (Habiter) ----------
   Étiquettes reprises du vocabulaire de l'orbe : point lumineux bleu, trait
   fin, halo au survol. Repliées par défaut, la section tient alors sur un
   écran ; la grille se réajuste d'elle-même selon ce qui est ouvert. */
/* ---------- diffuser ---------- */
.diffuser-box{
  border:1px solid var(--gold-dim);padding:38px;margin-top:40px;
  display:grid;grid-template-columns:1fr auto;gap:30px;align-items:center;
  background:linear-gradient(135deg, var(--gold-faint), transparent 60%);
}
@media(max-width:720px){.diffuser-box{grid-template-columns:1fr;}}

/* ---------- wonderland ---------- */
#wonderland{background:var(--bg-2);}

.essay{
  background:var(--bg-2);
  border:1px solid var(--gold-dim);
  padding:30px 34px;
  margin-top:44px;
  max-width:100%;
}
.essay summary{
  cursor:pointer;
  list-style:none;
  display:flex;
  align-items:center;
  gap:12px;
  font-family:var(--title);
  font-size:1.2rem;
  font-weight:400;
  color:var(--gold);
}
.essay summary::-webkit-details-marker{display:none;}
.essay summary::before{
  content:"▸";
  display:inline-block;
  font-family:var(--mono);
  font-size:.85rem;
  color:var(--gold);
  transition:transform .25s;
}
.essay[open] summary::before{transform:rotate(90deg);}
.essay summary .essay-tag{
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--marble-dim);margin-left:auto;
}
.essay summary .essay-date{
  font-family:var(--mono);font-size:10px;letter-spacing:.04em;
  color:var(--marble-quiet);
}
.essay-body{
  margin-top:26px;
  padding-top:26px;
  border-top:1px solid var(--marble-faint);
  max-width:680px;
}
.essay-body p{font-size:1rem;margin-bottom:18px;color:var(--marble-dim);}
.essay-signature{
  margin-top:8px;
  font-family:var(--mono);
  font-size:11px;
  letter-spacing:.06em;
  color:var(--gold);
}
.essay-signature span{display:block;color:var(--marble-dim);margin-top:4px;text-transform:none;letter-spacing:0;font-size:11px;}

/* Trois cases encore vides : l'étiquette annonce la nature de ce qui viendra,
   l'engrenage dit que ce n'est pas encore là. Rien d'autre à lire — un texte
   de remplissage aurait laissé croire à un contenu. */
.scrapbook{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:44px;}
.scrapbook div{
  border:1px dashed var(--marble-faint);padding:44px 18px 30px;
  font-size:.9rem;color:var(--marble-dim);
  min-height:150px;position:relative;
  display:flex;align-items:center;justify-content:center;
}
.scrapbook .wip{margin:0;}
/* Enfant direct seulement : en descendant, ce sélecteur attrapait aussi le
   <span> de l'engrenage et l'envoyait se coller sur l'étiquette. */
.scrapbook div > span{
  position:absolute;top:10px;right:12px;font-family:var(--mono);font-size:10px;color:var(--gold);
}
@media(max-width:720px){.scrapbook{grid-template-columns:1fr;}}

/* ---------- diffuser : lecture au bloc ----------
   Le paragraphe de présentation est long et sans images : sur une colonne
   large, un fer à gauche laisse une frange déchiquetée qui hache la lecture.
   Sur mobile, la colonne est trop étroite pour justifier sans créer de
   lézardes — on y garde le fer à gauche. */
@media(min-width:821px){
  #diffuser .lede{text-align:justify;hyphens:auto;-webkit-hyphens:auto;}
}

/* ---------- contact ---------- */
#contact{padding-bottom:90px;}
/* Deux colonnes : à gauche ce qui reste ouvert, à droite comment en parler.
   Les colonnes s'étirent à la même hauteur pour que le dé vienne se poser
   sur la même ligne que les liens, quelle que soit la longueur de la
   pensée tirée. La gauche est la plus large : c'est elle qui porte du texte. */
.contact-cols{
  display:grid;grid-template-columns:1.18fr .82fr;gap:60px;
  margin-top:44px;align-items:stretch;
}
/* Les deux colonnes poussent leur dernier élément en bas : le dé et la
   rangée de liens se retrouvent sur la même ligne, quelle que soit celle
   des deux qui est la plus haute. */
.contact-open,.contact-reach{display:flex;flex-direction:column;}
.contact-open .quote-reroll,.contact-reach .contact-grid{margin-top:auto;}
@media(max-width:820px){
  .contact-cols{grid-template-columns:1fr;gap:52px;}
  .contact-open,.contact-reach{display:block;}
}
.contact-grid{display:flex;flex-wrap:wrap;gap:14px;margin-top:36px;}
.contact-grid a{
  font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;
  border:1px solid var(--marble-faint);padding:14px 20px;text-decoration:none;color:var(--marble);
  display:inline-flex;align-items:center;gap:10px;transition:border-color .25s,color .25s;
}
.contact-grid a:hover{border-color:var(--gold);color:var(--gold);}
/* ---------- pied de page ----------
   Il ne défile plus : c'est désormais la seule mention permanente de la page,
   la barre du haut ayant été supprimée. Il en reprend le traitement — fond
   voilé, flou, filet d'un pixel — simplement retourné vers le bas.

   Sa hauteur est déclarée en variable parce que deux endroits doivent la
   dégager : le bouton du hero, posé en absolu au-dessus de lui, et le bas du
   hero en colonne unique, où ce bouton revient dans le flux.

   `z-index` sous la visionneuse d'images (100) : une image ouverte en plein
   écran doit couvrir la signature, pas cohabiter avec elle. */
footer{
  position:fixed;left:0;right:0;bottom:0;z-index:95;
  font-family:var(--mono);font-size:10.5px;color:var(--marble-dim);
  text-align:center;padding:12px 0;letter-spacing:.06em;
  background:rgba(11,11,11,0.72);
  border-top:1px solid var(--marble-faint);
}

.coord{
  position:absolute;font-family:var(--mono);font-size:9.5px;color:var(--gold-dim);letter-spacing:.1em;
}

@media(max-width:720px){
  section{padding:100px 0 80px;}
  #hero .wrap{grid-template-columns:1fr;}
  #hero .visual{max-width:300px;margin:0 auto;order:-1;}
}

@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  *{transition:none !important;}
}
