/* Salle noire, univers ambre. Version en service : noir cinématique, champ de
   glyphes génératif, grotesque massive, accent ambre « Pixel amber ». Deux familles,
   auto-hébergées. L'ancienne peau champagne reste servie sous /v5. */

@font-face {
  font-family: 'Clash Display';
  src: url('fonts/clash-display-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Clash Display';
  src: url('fonts/clash-display-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Clash Display';
  src: url('fonts/clash-display-700.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Switzer';
  src: url('fonts/switzer-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Switzer';
  src: url('fonts/switzer-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Switzer';
  src: url('fonts/switzer-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

:root {
  --noir: #0A0A0C;
  --surface: #131318;
  --ligne: #232329;
  --blanc: #F4F1EA;
  --gris: #98938A;
  /* Accent : univers « Pixel amber » (GASO-KOHAKU), générateur feralui.dev.
     Rampe complète #F59410 · #FFB23C · #FFCB70 · #FFE2A6 · #F8F1E0. L'accent prend le
     ton médian #FFCB70 (chiffres, liens, soulignements, halo), le survol monte à
     #FFE2A6. Le vif #FFB23C est écarté : il hurlait dans les études de cas. */
  --ambre: #FFCB70;
  --ambre-clair: #FFE2A6;

  --display: 'Clash Display', system-ui, sans-serif;
  --sans: 'Switzer', system-ui, sans-serif;

  /* Courbes de la maison : sortie expo pour les entrées, symétrique pour les volets. */
  --sortie: cubic-bezier(0.16, 1, 0.3, 1);
  --volet: cubic-bezier(0.83, 0, 0.17, 1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }

/* Lenis pilote le défilement quand il est actif (classes posées par la lib). */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

body {
  background: var(--noir);
  color: var(--blanc);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--ambre); color: var(--noir); }

a { color: inherit; }

:focus-visible { outline: 2px solid var(--ambre); outline-offset: 3px; }

/* ---------- Grain ---------- */

.grain-def { position: absolute; }
#grain {
  position: fixed; inset: -1%; z-index: 55;
  pointer-events: none;
  filter: url(#grain-f);
  mix-blend-mode: overlay;
  opacity: 0.06;
}

/* ---------- Arrivée ---------- */

/* Visible par défaut, jamais conditionné à une transition : sans JS la page est
   entière, l'arrivée ne fait que rejouer l'entrée. Les états cachés vivent sous .js. */
#entree { display: none; }
.js #entree {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1.4rem;
  background: var(--noir);
}
.entree-nom {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.6rem, 5.5vw, 3.4rem);
  letter-spacing: 0.14em;
  color: var(--blanc);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.entree-ligne {
  display: block; height: 1px; width: 0;
  background: var(--ambre);
  max-width: min(60vw, 26rem);
}
#entree.entree-part { pointer-events: none; }
body.figee { overflow: hidden; }

/* ---------- Haut de page ---------- */

.haut {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 1.4rem clamp(1.2rem, 4vw, 3rem);
  mix-blend-mode: difference;
  transition: transform 0.45s var(--sortie), opacity 0.45s var(--sortie);
}
/* En descendant, le haut s'efface (le blend difference imprimait le menu sur le
   contenu) ; remonter le rend aussitôt. */
.haut.range { transform: translateY(-110%); opacity: 0; pointer-events: none; }
/* Cibles tactiles du haut : le texte est petit par dessin, la zone doit tenir les
   44 px du pouce. Le padding grandit la zone, la marge négative la rend à la mise
   en page, la ligne de base ne bouge pas d'un pixel (30 juillet 2026). */
.marque {
  font-family: var(--display); font-weight: 600;
  font-size: 1.05rem; letter-spacing: 0.02em;
  text-decoration: none;
  padding: 0.55rem 0.4rem;
  margin: -0.55rem -0.4rem;
}
.haut-nav { display: flex; gap: 1.8rem; }
.haut-nav a, .haut-nav .commutateur {
  font-size: 0.85rem; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  text-decoration: none;
  opacity: 0.75;
  padding: 0.7rem 0.5rem;
  margin: -0.7rem -0.5rem;
  transition: opacity 0.25s var(--sortie);
}
.haut-nav a:hover, .haut-nav .commutateur:hover { opacity: 1; }
/* Le commutateur de salle (3 septembre 2026, docs/V7-LANGUES.md) : un mot, même
   typo et même poids que les liens. Il nomme la salle où l'on va, pas celle où
   l'on est : « Clair » dans la salle noire, « Sombre » dans la claire. Les deux
   mots sont dans le HTML, le CSS n'en montre qu'un : rien à peindre en JS, donc
   jamais le mauvais mot au premier rendu. Sans script, pas de choix possible :
   le bouton s'efface (html[data-theme] n'est posé que par le script). */
.haut-nav .commutateur {
  background: none; border: 0; color: inherit; font-family: inherit;
  line-height: inherit; cursor: pointer;
  /* un bouton centre son contenu dans la hauteur étirée par le flex ; les liens
     posent le leur en haut : même départ, même ligne de base */
  display: inline-flex; align-items: flex-start;
}
html[data-theme="clair"] .commutateur-clair,
html:not([data-theme="clair"]) .commutateur-sombre,
html:not([data-theme]) .commutateur { display: none; }
/* Quatre mots dans la nav depuis le lien de langue (3 septembre 2026) : sur un
   téléphone, l'air entre eux se resserre pour laisser la marque respirer. */
@media (max-width: 480px) { .haut-nav { gap: 1rem; } }
/* la porte des pages annexes (404, mentions) : le même geste que sur les
   pages projet, le battant pivote et la lumière ambre emplit l'embrasure */
.haut-nav .porte { padding: 0.5rem; margin: -0.5rem; color: var(--blanc); }
.haut-nav .porte:hover { color: var(--ambre); }
.haut-nav .porte svg { display: block; width: 22px; height: 22px; }
.haut-nav .porte .lumiere { fill: var(--ambre); stroke: none; opacity: 0; transition: opacity 0.4s ease; }
.haut-nav .porte .battant { transform-origin: 6.25px 12px; transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1); }
.haut-nav .porte:hover .battant, .haut-nav .porte:focus-visible .battant { transform: scaleX(0.24); }
.haut-nav .porte:hover .lumiere, .haut-nav .porte:focus-visible .lumiere { opacity: 0.85; }

/* ---------- Hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex; align-items: center;
  overflow: hidden;
  padding: clamp(5rem, 12vh, 8rem) clamp(1.2rem, 4vw, 3rem);
}
#champ { position: absolute; inset: 0; width: 100%; height: 100%; }

.hero-scene { position: relative; max-width: 72rem; margin-inline: auto; width: 100%; }

.hero-titre {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 8.4vw, 7rem);
  line-height: 1.02;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.ligne { display: block; overflow: hidden; }
.ligne-int { display: inline-block; }
.js .ligne-int { transform: translateY(112%); }
.ligne-accent .ligne-int { color: var(--ambre); }

.hero-sous {
  margin-top: 2rem;
  max-width: 34rem;
  color: var(--gris);
  font-size: clamp(1rem, 1.6vw, 1.2rem);
}
.js .hero-sous { opacity: 0; }

.hero-coins { position: absolute; inset: 0; pointer-events: none; }
.js .hero-coins { opacity: 0; }

/* Le lieu, dessiné plutôt qu'écrit : l'hexagone au trait, un point ambre sur
   Toulouse. Coordonnées réelles (43,60 N / 1,44 E) projetées dans le viewBox,
   le point est à sa place, pas posé à l'estime. */
.carte-bouton {
  position: absolute;
  top: 5.2rem; right: clamp(1.2rem, 4vw, 3rem);
  display: flex; align-items: center; gap: .8rem;
  width: auto; padding: .5rem; margin: -.5rem;
  background: none; border: 0; color: inherit; cursor: pointer;
  pointer-events: auto;
}
.carte-fr {
  display: block;
  width: clamp(3.6rem, 5vw, 5rem); height: auto;
  transform-origin: 70% 60%;
  transition: transform .7s cubic-bezier(.16, 1, .3, 1);
}
/* LA PORTE (3 septembre 2026) · au survol la carte grossit et sa balise bat plus
   vite ; au clic elle vole vers la fenêtre de présence (app.js, Flip). */
.carte-bouton:hover .carte-fr, .carte-bouton:focus-visible .carte-fr { transform: scale(1.22); }
.carte-bouton:hover .carte-point, .carte-bouton:focus-visible .carte-point { animation-duration: 1.1s; }

/* Un aplat très faible donne la masse (l'hexagone se reconnaît d'un coup à 60 px),
   le trait donne l'arête. L'outline seul demandait le double de taille pour se lire.
   Les valeurs sont celles de la salle noire : éclaircir un noir de 10 % ne se voit
   presque pas, assombrir un papier de 10 % se voit très bien, le clair les baisse. */
.carte-trait {
  --carte-masse: 0.2;
  fill: var(--gris); fill-opacity: var(--carte-masse);
  stroke: var(--gris); stroke-opacity: 0.85;
  stroke-width: 1.4;
  stroke-linejoin: round;
}
.carte-point { fill: var(--ambre); }

/* LA BALISE (2 septembre 2026, levier 6 de docs/V6-SALLE.md) · la carte ne
   surgit plus : le trait se trace en une seconde après le titre, la masse
   vient ensuite, le point s'allume en dernier, puis il respire, lentement,
   comme une balise, et il flashe quand on clique, en même temps que le champ.
   pathLength="1" sur le tracé : la course vaut 1 quel que soit le dessin.
   Tout est déclenché par .hero-coins.trace, posé par app.js à la révélation
   du hero ; sans JS, pas de classe, la carte est simplement là. */
.js .hero-coins:not(.trace) .carte-trait { stroke-dasharray: 1; stroke-dashoffset: 1; fill-opacity: 0; }
.js .hero-coins:not(.trace) .carte-point { transform: scale(0); }
.js .hero-coins.trace .carte-trait {
  stroke-dasharray: 1; stroke-dashoffset: 0; fill-opacity: var(--carte-masse);
  transition: stroke-dashoffset 1.3s cubic-bezier(.22, 1, .36, 1), fill-opacity 1.2s ease .7s;
}
.carte-point {
  transform-box: fill-box; transform-origin: center;
  transition: transform .9s cubic-bezier(.22, 1, .36, 1) 1.1s;
}
.js .hero-coins.trace .carte-point { animation: balise 3.4s ease-in-out 2.2s infinite; }
.js .hero-coins.trace.flash .carte-point { animation: balise-flash .55s cubic-bezier(.22, 1, .36, 1); }
@keyframes balise {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(.78); opacity: .72; }
}
@keyframes balise-flash {
  0% { transform: scale(1.9); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* LA FENÊTRE DE PRÉSENCE (3 septembre 2026, docs/V6-SALLE.md) · la carte du hero
   en grand, dans la salle noire, avec une lueur ambre derrière elle. Sous le
   grain (z 55), au-dessus du reste. Le trait garde une épaisseur d'écran
   (vector-effect) : il ne grossit pas avec le dessin, ni pendant le vol. */
.presence {
  position: fixed; inset: 0; z-index: 50;
  display: grid; align-items: center;
  padding: var(--marge);
  background: var(--noir);
}
.presence[hidden] { display: none; }
.presence::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(60% 70% at 32% 50%,
    color-mix(in srgb, var(--ambre) 11%, transparent) 0%,
    color-mix(in srgb, var(--ambre) 4%, transparent) 45%,
    transparent 72%);
}
.presence-cadre {
  position: relative; width: 100%; max-width: 72rem; margin: 0 auto;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 6rem); align-items: center;
}
/* LE CADRE DU GLOBE · carré. La carte y atterrit (absolue, sur le canvas) puis
   se dissout dans le globe (app.js). Le canvas déborde du cadre pour loger
   l'atmosphère ; le dessin réel fait ~84 % du cadre. */
.presence-figure { position: relative; width: min(52vh, 36vw, 32rem); aspect-ratio: 1; justify-self: center; }
.presence-figure .carte-fr { position: absolute; inset: 0; width: 100%; height: 100%; transform: none; transition: none; pointer-events: none; }
.globe {
  position: absolute; inset: -14%; width: 128%; height: 128%;
  display: block; opacity: 0; touch-action: none; cursor: grab;
}
.globe.glisse { cursor: grabbing; }
/* le portrait, quand c'est lui la porte : un cadre à la proportion de la photo,
   centré dans le carré, au-dessus du globe où il se dissout */
.presence-portrait {
  position: absolute; top: 0; left: 50%; height: 100%; aspect-ratio: 571 / 771;
  transform: translateX(-50%); pointer-events: none; z-index: 1;
}
.presence-portrait .moi-cadre { position: absolute; inset: 0; width: 100%; height: 100%; }
.presence-portrait .moi-cadre img, .carte-vol .moi-cadre img { transform: none; transition: none; }
.carte-vol .moi-cadre { position: absolute; top: 0; left: 0; width: min(52vh, 36vw, 32rem); }
.presence-mots { max-width: 31rem; }
.presence-et { margin: 0 0 1rem; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--gris); }
.presence-titre {
  margin: 0; font-family: var(--display); font-weight: 700;
  font-size: clamp(1.9rem, 3.6vw, 3.2rem); line-height: 1.02; letter-spacing: -.02em;
  text-wrap: balance;
}
.presence-recit p { margin: 1rem 0 0; color: var(--gris); font-size: clamp(.92rem, 1.05vw, 1.02rem); line-height: 1.58; }
.presence-recit .presence-appel { color: var(--blanc); }
.presence-ecrire {
  display: inline-block; margin-top: 1.6rem; padding-bottom: .35rem;
  font-family: var(--display); font-weight: 600; font-size: clamp(1.3rem, 2.2vw, 1.7rem);
  color: var(--blanc); text-decoration: none; border-bottom: 2px solid var(--ambre);
  transition: color .25s ease, border-color .25s ease;
}
.presence-ecrire:hover { color: var(--ambre-clair); }
.presence-fermer {
  position: absolute; top: calc(var(--marge) - .4rem); right: calc(var(--marge) - .6rem); z-index: 1;
  padding: .7rem .6rem; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: .78rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase;
  color: var(--gris); transition: color .25s ease;
}
.presence-fermer:hover { color: var(--blanc); }
.js .presence [data-presence-texte] { opacity: 0; }
/* la couche du vol : la carte y passe le temps d'aller d'un cadre à l'autre */
.carte-vol { position: fixed; inset: 0; z-index: 53; pointer-events: none; }
.carte-vol .carte-fr { position: absolute; top: 0; left: 0; width: min(52vh, 36vw, 32rem); transition: none; }
/* pendant le vol et dans le cadre : trait fin d'écran, point plus juste. Et le
   survol ne compte plus dès que la carte part : .vole efface le zoom AVANT la
   mesure du départ, sinon le retour visait une taille survolée et sautait. */
.carte-trait { vector-effect: non-scaling-stroke; }
.presence .carte-trait, .carte-vol .carte-trait { stroke-width: 1.6px; }
.presence .carte-point, .carte-vol .carte-point { r: 1.5; animation: none; }
.carte-bouton.vole .carte-fr { transform: none; transition: none; }
@media (max-width: 47.99rem) {
  .presence { align-items: start; padding-top: clamp(4.5rem, 12vh, 6rem); overflow-y: auto; }
  .presence-cadre { grid-template-columns: 1fr; gap: 1.2rem; }
  .presence-figure { width: min(64vw, 17rem); justify-self: start; }
  .carte-vol .carte-fr, .carte-vol .moi-cadre { width: min(64vw, 17rem); }
}
@media (prefers-reduced-motion: reduce) {
  .carte-fr { transition: none; }
  .globe { opacity: 1; }
  .js .presence [data-presence-texte] { opacity: 1; }
}

.hero-cue {
  /* bottom compense le padding : l'indice reste optiquement à 2,2rem du bas. */
  position: absolute; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.8rem 0.5rem;
  font-size: 0.8rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ambre);
  text-decoration: none;
}
.js .hero-cue { opacity: 0; }
.cue-fleche { display: inline-block; animation: cue 2.2s var(--sortie) infinite; }
@keyframes cue {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(0.35rem); }
}

/* La révélation du hero est pilotée par GSAP (SplitText) dans app.js. Si le socle
   manque, app.js retire .js et tout redevient visible sans animation. */

/* ---------- Études ---------- */

/* Une seule ligne en haut : la bordure de section, sur laquelle le champ du hero
   vient buter. La première carte suit aussitôt (son propre padding aère), plus de
   bandeau noir mort coincé entre deux filets. */
.etudes { border-top: 1px solid var(--ligne); }

.etude { border-bottom: 1px solid var(--ligne); }

/* Le nom d'étude EST le titre de la section : la carte vit donc dans un h2 (patron
   APG de l'accordéon, un bouton dans un titre, jamais un titre dans un bouton :
   ARIA rend présentationnel le contenu d'un bouton). Le titre ne porte aucun
   dessin, il rend sa fonte au flux pour que `font: inherit` de la carte reste
   la fonte du corps. */
.etude-titre { font: inherit; }

.etude-carte {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: clamp(1.2rem, 3vw, 2.6rem);
  width: 100%;
  padding: clamp(1.8rem, 5vh, 3.2rem) clamp(1.2rem, 4vw, 3rem);
  background: none; border: 0; color: inherit;
  font: inherit; text-align: left;
  cursor: pointer;
}
.etude-n {
  font-family: var(--display); font-weight: 500;
  font-size: 0.95rem;
  color: var(--ambre);
  font-variant-numeric: tabular-nums;
}
.etude-nom {
  display: block;
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.9rem, 5vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  transition: transform 0.45s var(--sortie), color 0.45s var(--sortie);
}
.etude-fait {
  display: block;
  margin-top: 0.55rem;
  color: var(--gris);
  font-size: clamp(0.9rem, 1.4vw, 1.05rem);
}
.etude-vignette {
  position: relative;
  width: clamp(7rem, 16vw, 13rem);
  aspect-ratio: 8 / 5;
  overflow: hidden;
  border-radius: 2px;
}
.etude-vignette img,
.etude-video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* Au repos, le produit doit rester identifiable : le gris et l'assombrissement
     d'avant (grayscale 0.35, brightness 0.82) sous un voile à 0,6 rendaient les
     quatre vignettes indistinctes tant qu'on ne survolait pas, donc jamais sur
     tactile. Corrigé le 30 juillet 2026 : l'écart au survol reste lisible. */
  filter: brightness(0.94) saturate(0.88) contrast(1.02);
  transform: scale(1.001);
  transition: transform 0.6s var(--sortie), filter 0.6s var(--sortie), opacity 0.5s var(--sortie);
}
/* La vidéo miniature vit au-dessus du poster ; révélée seulement quand elle joue. */
.etude-video { position: absolute; inset: 0; opacity: 0; }
.etude-video.joue { opacity: 1; }
/* Le voile assoit la matière claire des captures dans la salle noire ;
   il se lève au survol pour laisser la vidéo respirer. */
.etude-vignette::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(155deg, rgba(10, 10, 12, 0.12), rgba(10, 10, 12, 0.35)),
    radial-gradient(130% 130% at 72% 18%, rgba(255, 203, 112, 0.1), transparent 62%);
  transition: opacity 0.6s var(--sortie);
}
.etude-carte:hover .etude-vignette::after { opacity: 0.22; }
.etude-fleche {
  font-family: var(--display); font-weight: 500;
  font-size: 1.5rem;
  color: var(--gris);
  transform: translateX(-0.4rem);
  transition: transform 0.45s var(--sortie), color 0.45s var(--sortie);
}
.etude-carte:hover .etude-nom { transform: translateX(0.5rem); color: var(--ambre-clair); }
.etude-carte:hover .etude-vignette img,
.etude-carte:hover .etude-video { transform: scale(1.05); filter: grayscale(0) brightness(1) saturate(0.92) contrast(1); }
.etude-carte:hover .etude-fleche { transform: translateX(0); color: var(--ambre); }

/* ---------- Vue d'étude (plein écran) ---------- */

.etude-vue {
  position: fixed; inset: 0; z-index: 50;
  background: var(--surface);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none; /* jamais de barre visible, mobile compris */
}
.etude-vue::-webkit-scrollbar { display: none; }
.etude-vue[hidden] { display: none; }

.vue-cadre {
  max-width: 72rem;
  margin-inline: auto;
  padding: clamp(1.4rem, 4vh, 2.6rem) clamp(1.2rem, 4vw, 3rem) clamp(3rem, 8vh, 5rem);
  min-height: 100%;
}
.vue-haut {
  display: flex; justify-content: space-between; align-items: center;
  padding-bottom: clamp(1.6rem, 5vh, 3rem);
}
.vue-n {
  font-family: var(--display); font-weight: 500;
  color: var(--ambre);
  font-variant-numeric: tabular-nums;
}
.vue-fermer {
  background: none; border: 1px solid var(--ligne); color: var(--blanc);
  font: inherit; font-size: 0.85rem; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0.55rem 1.2rem; border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.3s var(--sortie), color 0.3s var(--sortie);
}
.vue-fermer:hover { border-color: var(--ambre); color: var(--ambre-clair); }

.vue-titre {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.4rem, 7vw, 5.6rem);
  line-height: 1.02; letter-spacing: -0.015em;
}
.vue-capacite {
  margin-top: 1rem;
  color: var(--gris);
  font-size: clamp(1rem, 1.6vw, 1.2rem);
  max-width: 40rem;
}
.vue-colonnes {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  margin-top: clamp(2.4rem, 6vh, 4rem);
  align-items: start;
}
.vue-recit h3 {
  font-family: var(--sans); font-weight: 600;
  font-size: 0.8rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ambre);
  margin-top: 2rem;
}
.vue-recit h3:first-child { margin-top: 0; }
.vue-recit p { margin-top: 0.7rem; color: var(--blanc); max-width: 38rem; }
/* Deux classes : sinon `.vue-recit p` (0,1,1) gagnait sur `.vue-resultat` (0,1,0)
   et la ligne de résultat ne recevait jamais son ambre. */
.vue-recit .vue-resultat {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.25rem, 2.4vw, 1.7rem);
  line-height: 1.3;
  color: var(--ambre-clair);
}
/* Le cadre média du volet : le poster dimensionne, la présentation le recouvre. */
.vue-media {
  position: relative;
  border-radius: 3px;
  border: 1px solid var(--ligne);
  overflow: hidden;
  background: var(--noir);
}
.vue-media img { width: 100%; height: auto; display: block; }
.vue-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 0.6s var(--sortie);
}
.vue-video.joue { opacity: 1; }
/* La miniature adoptée par le cadre média pendant le voyage d'ouverture : plein
   cadre, sans le traitement gris de la vignette, sous la présentation qui la
   recouvrira une fois chargée (app.js la rend ensuite à sa vignette). */
.etude-video.en-voyage {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 1; transform: none; filter: none;
}
/* Dans le volet, la matière est le sujet : réchauffe à peine, n'assombrit pas. */
.vue-media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(130% 130% at 70% 15%, rgba(255, 203, 112, 0.06), transparent 60%);
}
/* Contrôles de la présentation (son, agrandir), révélés quand elle joue (les
   attributs hidden sont pilotés par app.js). */
.vue-son, .vue-agrandir {
  position: absolute; bottom: 0.7rem; z-index: 3;
  background: rgba(10, 10, 12, 0.62); color: var(--blanc);
  border: 1px solid var(--ligne); border-radius: 999px;
  font: inherit; font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 0.4rem 0.85rem; cursor: pointer;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: opacity 0.3s var(--sortie), border-color 0.3s var(--sortie), color 0.3s var(--sortie);
}
.vue-son { right: 0.7rem; }
.vue-agrandir { left: 0.7rem; opacity: 0; pointer-events: none; }
.vue-son[hidden], .vue-agrandir[hidden] { display: none; }
.vue-son:hover, .vue-agrandir:hover { border-color: var(--ambre); color: var(--ambre-clair); }
.vue-son[aria-pressed="true"] { border-color: var(--ambre); color: var(--ambre); }
/* Activer le son reste visible (invite) ; couper le son ne revient qu'au survol
   de la vidéo. Agrandir n'apparaît qu'au survol. En grand, tout reste visible. */
@media (hover: hover) {
  .vue-son[aria-pressed="true"] { opacity: 0; pointer-events: none; }
  /* focus-within autant que hover : au clavier, la puce ne doit pas recevoir le
     focus en restant invisible (le piège de focus du volet la traverse). */
  .vue-media:hover .vue-son[aria-pressed="true"], .autre-media:hover .vue-son[aria-pressed="true"],
  .vue-media:focus-within .vue-son[aria-pressed="true"], .autre-media:focus-within .vue-son[aria-pressed="true"],
  .vue-media:hover .vue-agrandir, .autre-media:hover .vue-agrandir,
  .vue-media:focus-within .vue-agrandir, .autre-media:focus-within .vue-agrandir { opacity: 1; pointer-events: auto; }
}
.vue-media.agrandi .vue-son, .vue-media.agrandi .vue-agrandir,
.autre-media.agrandi .vue-son, .autre-media.agrandi .vue-agrandir,
.etude-vue.media-large .vue-son, .etude-vue.media-large .vue-agrandir { opacity: 1; pointer-events: auto; }

/* Écran large : le volet ne défile pas, seul le récit défile dans sa colonne,
   le titre et la vidéo restent en place. Des voiles haut/bas (pilotés par
   app.js via .recit-haut/.recit-bas) délimitent la matière cachée. */
@media (min-width: 861px) {
  .etude-vue { overflow: hidden; }
  .vue-cadre { height: 100%; display: flex; flex-direction: column; }
  .vue-colonnes { flex: 1; min-height: 0; grid-template-rows: 100%; align-items: stretch; }
  .vue-recit {
    grid-area: 1 / 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .vue-recit::-webkit-scrollbar { display: none; }
  .vue-figure { grid-area: 1 / 2; align-self: start; }
  .vue-colonnes::before, .vue-colonnes::after {
    content: ""; grid-area: 1 / 1; z-index: 2;
    width: 100%; height: 3.2rem;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.35s var(--sortie);
  }
  .vue-colonnes::before { align-self: start; background: linear-gradient(to bottom, var(--surface), transparent); }
  /* Le voile bas est de la couleur du fond : il masquait sans rien annoncer. Un
     filet ambre au ras du bord dit qu'il reste du récit dessous (30 juillet 2026). */
  .vue-colonnes::after {
    align-self: end;
    background:
      linear-gradient(to top, color-mix(in srgb, var(--ambre) 55%, transparent) 0 1px, transparent 1px),
      linear-gradient(to top, var(--surface), transparent);
  }
  .etude-vue.recit-haut .vue-colonnes::before { opacity: 1; }
  .etude-vue.recit-bas .vue-colonnes::after { opacity: 1; }
  /* Régime agrandi : le volet entier redevient défilant, une seule colonne. */
  .etude-vue.media-large { overflow-y: auto; }
  .etude-vue.media-large .vue-cadre { display: block; height: auto; min-height: 100%; }
  .etude-vue.media-large .vue-colonnes { grid-template-rows: none; }
  .etude-vue.media-large .vue-recit { grid-area: auto; overflow: visible; }
  .etude-vue.media-large .vue-figure { grid-area: auto; }
  .etude-vue.media-large .vue-colonnes::before, .etude-vue.media-large .vue-colonnes::after { display: none; }
}

/* Agrandi dans le volet d'étude (écran large) : la figure prend toute la largeur
   du cadre, le récit glisse dessous. La colonne reste sous la résolution native
   des vidéos (1280 px), donc jamais de flou d'upscale. Flip anime la bascule. */
.etude-vue.media-large .vue-colonnes { grid-template-columns: 1fr; }
.etude-vue.media-large .vue-figure { position: static; order: -1; }

/* Cadre agrandi flottant (panneaux, hors volet) : centré, 16:9, plafonné à la
   résolution native (80rem = 1280 px). Le Flip maison l'anime depuis sa place
   d'origine ; le fond sombre ferme au clic. */
.vue-media.agrandi, .autre-media.agrandi {
  position: fixed; z-index: 85;
  inset: 0; margin: auto;
  width: min(94vw, 80rem, calc(92vh * 16 / 9));
  height: min(92vh, calc(min(94vw, 80rem) * 9 / 16));
  max-width: 94vw; max-height: 92vh;
  border-radius: 4px;
  box-shadow: 0 40px 120px rgba(0, 0, 0, 0.6);
}
.media-fond {
  position: fixed; inset: 0; z-index: 84;
  background: rgba(10, 10, 12, 0.82);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  cursor: pointer;
}
.vue-figure figcaption { margin-top: 1.1rem; }
/* Présentation dans le panneau « Également construit » (Toul'Barz). */
.autre-media {
  position: relative;
  margin: 0 0 1.1rem;
  max-width: 34rem;
  border-radius: 3px; border: 1px solid var(--ligne);
  overflow: hidden; background: var(--noir);
}
.autre-media img { width: 100%; height: auto; display: block; }
.autre-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 0.6s var(--sortie);
}
.autre-video.joue { opacity: 1; }
.vue-lien {
  display: inline-flex; align-items: baseline; gap: 0.4rem;
  font-weight: 500;
  color: var(--ambre);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--sortie), color 0.3s var(--sortie);
}
.vue-lien:hover { color: var(--ambre-clair); border-color: var(--ambre-clair); }

/* La cascade d'ouverture du volet est pilotée par GSAP dans app.js. */

.vue-role {
  color: var(--gris);
  font-family: var(--sans); font-weight: 500;
  font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase;
}
.vue-suite { margin-top: 2.2rem; }

/* ---------- Également construit ---------- */

.aussi {
  max-width: 72rem; margin-inline: auto;
  padding: clamp(4rem, 10vh, 7rem) clamp(1.2rem, 4vw, 3rem);
}
.aussi-titre {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.7rem, 3.6vw, 2.6rem);
  letter-spacing: -0.01em;
}
.aussi-liste { list-style: none; margin-top: 2.2rem; }
.aussi-liste li { border-bottom: 1px solid var(--ligne); }
.aussi-liste li:first-child { border-top: 1px solid var(--ligne); }
.aussi-nom {
  font-family: var(--display); font-weight: 500; font-size: 1.1rem;
  transition: color 0.45s var(--sortie);
}
.aussi-fait { color: var(--gris); font-size: 0.98rem; }

/* Chaque ligne se déplie : la même sobriété, une porte de sortie en plus. */
.autre-ligne {
  display: grid;
  grid-template-columns: minmax(11rem, 3fr) 9fr auto;
  gap: 1.4rem;
  align-items: baseline;
  width: 100%;
  padding: 1.1rem 0;
  background: none; border: 0; color: inherit;
  font: inherit; text-align: left;
  cursor: pointer;
}
.autre-signe {
  font-family: var(--display); font-weight: 500;
  font-size: 1.15rem; line-height: 1;
  color: var(--gris);
  transition: transform 0.45s var(--sortie), color 0.45s var(--sortie);
}
.autre-ligne:hover .aussi-nom { color: var(--ambre-clair); }
.autre-ligne:hover .autre-signe { color: var(--ambre); }
.autre-ligne[aria-expanded="true"] .autre-signe { transform: rotate(45deg); color: var(--ambre); }
.autre-detail { overflow: hidden; }
/* 38rem comme le récit des volets : 46rem donnait ~87 caractères par ligne. */
.autre-detail p { color: var(--gris); max-width: 38rem; padding-bottom: 1.2rem; }
.autre-actions {
  display: flex; flex-wrap: wrap; gap: 1.6rem; align-items: baseline;
  margin-top: -0.2rem;
}
.autre-statut {
  color: var(--gris);
  font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
}

/* La dernière ligne : un simple plus qui devient une invitation. */
.autre-idee .autre-ligne {
  grid-template-columns: auto 1fr;
  align-items: center;
  padding: 1.35rem 0;
}
.idee-plus {
  font-family: var(--display); font-weight: 500;
  font-size: 1.7rem; line-height: 1;
  color: var(--ambre);
  transition: transform 0.6s var(--sortie);
}
.autre-idee .autre-ligne:hover .idee-plus { transform: rotate(90deg); }
.idee-invite-txt {
  font-family: var(--display); font-weight: 500; font-size: 1.1rem;
  color: var(--gris);
  transition: color 0.45s var(--sortie);
}
.autre-idee .autre-ligne:hover .idee-invite-txt { color: var(--ambre-clair); }
html:not(.js) .autre-idee { display: none; }

/* ---------- La boîte à idée ---------- */

/* Tout passe en arrière-plan : il ne reste que l'air (la réponse s'y dépose)
   et la barre de saisie, assumée. */
.idee-scene {
  position: fixed; inset: 0; z-index: 52;
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end;
  gap: 1.7rem;
  padding: clamp(1.4rem, 4vw, 3rem);
  padding-bottom: clamp(3.5rem, 14vh, 8rem);
  background: rgba(10, 10, 12, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.idee-scene[hidden] { display: none; }
.idee-fermer {
  position: absolute; top: clamp(1.2rem, 3vh, 2rem); right: clamp(1.2rem, 4vw, 3rem);
  background: none; border: 1px solid var(--ligne); color: var(--blanc);
  font: inherit; font-size: 0.85rem; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0.55rem 1.2rem; border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.3s var(--sortie), color 0.3s var(--sortie);
}
.idee-fermer:hover { border-color: var(--ambre); color: var(--ambre-clair); }
.idee-air {
  flex: 1; width: min(46rem, 100%);
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.25rem, 2.8vw, 2rem);
  line-height: 1.4;
  color: var(--ambre-clair);
  text-wrap: balance;
}
/* La relecture : le message tel qu'il partira, en retrait sous l'air. */
.idee-relecture {
  width: min(46rem, 100%);
  font-size: 0.95rem; line-height: 1.65;
  color: var(--blanc); opacity: 0.82;
  text-align: center;
  white-space: pre-line;
  max-height: 30vh; overflow-y: auto;
  scrollbar-width: none;
}
.idee-relecture::-webkit-scrollbar { display: none; }
.idee-relecture[hidden] { display: none; }
/* Le mot qui rend le message à la personne : discret, juste sous la relecture
   (marge négative : le rythme de la scène est trop aéré pour un simple mot). */
.idee-modifier {
  background: none; border: 0; cursor: pointer;
  color: var(--gris);
  font: inherit; font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  margin-top: -1.1rem;
  transition: color 0.3s var(--sortie);
}
.idee-modifier:hover { color: var(--ambre-clair); }
.idee-modifier[hidden] { display: none; }
/* En relecture, l'air cesse de remplir la hauteur : le titre vient se poser
   juste au-dessus du message, comme une légende. */
.idee-scene.relit .idee-air { flex: none; }

.idee-forme {
  width: min(46rem, 100%);
  display: flex; align-items: flex-end; gap: 1rem;
  border-bottom: 1px solid var(--ambre);
  padding-bottom: 0.65rem;
  transition: border-color 0.5s var(--sortie), opacity 0.5s var(--sortie);
}
.idee-envoi[hidden] { display: none; }
.idee-champ {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.25rem, 3.4vw, 2.1rem);
  line-height: 1.3;
  color: var(--blanc);
  caret-color: var(--ambre);
  /* Zone de texte qui grandit avec la saisie (hauteur pilotée en JS), sans
     poignée ni barre : au-delà du plafond, le défilement reste invisible. */
  resize: none;
  overflow-y: auto;
  max-height: 36vh;
  scrollbar-width: none;
}
.idee-champ::-webkit-scrollbar { display: none; }
.idee-champ::placeholder { color: var(--gris); opacity: 0.75; }
.idee-envoi {
  flex: none;
  display: grid; place-items: center; overflow: hidden;
  background: none; border: 1px solid var(--ligne); color: var(--ambre);
  width: 2.9rem; height: 2.9rem; border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.3s var(--sortie), background-color 0.3s var(--sortie), transform 0.3s var(--sortie);
}
.idee-envoi svg { display: block; width: 18px; height: 18px; }
.idee-envoi:hover {
  border-color: var(--ambre); transform: translateX(3px);
  background: color-mix(in srgb, var(--ambre) 8%, transparent);
}
/* le même geste que les flèches de la barre projet : elle sort du cadre
   et revient de l'autre bord */
.idee-envoi:hover svg { animation: envoi-passe 0.45s cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes envoi-passe {
  0% { transform: none; }
  49% { transform: translateX(160%); opacity: 0; }
  50% { transform: translateX(-160%); opacity: 0; }
  100% { transform: none; }
}
.idee-note {
  color: var(--gris);
  font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  text-align: center;
  /* Vide au départ, mais la ligne garde sa hauteur : un message qui arrive
     (erreur, consigne, confirmation) ne décale rien. */
  min-height: 1.2em;
}
.idee-scene.pense .idee-forme { border-color: var(--ligne); opacity: 0.45; pointer-events: none; }
.idee-scene.finie .idee-forme { opacity: 0.3; pointer-events: none; }

/* ---------- Contact ---------- */

.contact {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--ligne);
  padding: clamp(5rem, 14vh, 9rem) clamp(1.2rem, 4vw, 3rem);
  text-align: center;
}
.contact-fond { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.contact-fond canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.contact > :not(.contact-fond) { position: relative; z-index: 1; }

/* La vitrine : la matière vivante et l'adresse dans le même cadre, à taille où la
   vidéo est nette. Le halo curseur et le balayage doré vivent sous ces classes. */
.contact-vitrine {
  position: relative;
  width: min(46rem, 92vw);
  aspect-ratio: 16 / 9;
  margin: clamp(1.8rem, 4vh, 2.6rem) auto 0;
  border: 1px solid var(--ligne);
  border-radius: 4px;
  overflow: hidden;
  /* Toute la carte ouvre la boîte à idée (21 juillet 2026) : elle porte donc le
     curseur du geste. Seule l'adresse fait exception, elle copie, et sa règle
     `cursor: none` plus bas est plus spécifique, la pilule « Copier » y reste. */
  cursor: pointer;
  /* Au survol, c'est la carte entière qui avance. Agrandir la matière à
     l'intérieur d'un cadre qui, lui, ne bouge pas donnait un zoom rogné par
     les bords : on voyait les pixels grandir et se faire couper. */
  transform-origin: 50% 50%;
  transition: border-color 0.6s var(--sortie), transform 0.9s var(--sortie), box-shadow 0.9s var(--sortie);
}
.contact-vitrine:hover {
  border-color: color-mix(in srgb, var(--ambre) 45%, var(--ligne));
  transform: scale(1.028);
  box-shadow: 0 30px 70px -30px color-mix(in srgb, var(--ambre) 34%, transparent);
}
.contact-vitrine video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 1.4s var(--sortie), filter 1.4s var(--sortie);
}
.contact-vitrine:hover video { filter: brightness(1.22) saturate(1.08); }

/* Le médaillon de pixels : basse définition agrandie sans lissage, chaque
   pixel calculé devient un carré net. Même respiration au survol que la vidéo. */
.vitrine-pixels {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  transition: transform 1.4s var(--sortie), filter 1.4s var(--sortie);
}
.contact-vitrine:hover .vitrine-pixels { filter: brightness(1.1); }


/* Voile : assoit le texte sur la matière, et porte le halo qui suit le curseur. */
.vitrine-voile {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10, 10, 12, 0.18) 0%, rgba(10, 10, 12, 0.34) 55%, rgba(10, 10, 12, 0.58) 100%);
}
/* La rampe « Golden hour » du 20 juillet 2026 est claire (crème #FFF3D6 en cellule
   vive) : le voile allégé de l'époque du quilt sombre laissait l'adresse à 1,51:1.
   Depuis le 30 juillet 2026, le même voile vertical porte un plateau au milieu,
   la bande où vit le bloc de texte, assez dense pour tenir 4,5:1 sur la cellule la
   plus vive.
   Un socle elliptique tenait 5:1 sur les trois textes ; il a été retiré le
   30 juillet 2026, décision d'Anton : l'ovale sombre posé au milieu du cadre était
   laid, et la matière passe avant la lisibilité ici. Le voile est donc resté ce
   qu'il a toujours été, une seule rampe verticale, aucune forme.
   La lisibilité est alors reprise du côté de l'encre, pas du fond : les trois
   textes sont cernés d'une ombre serrée (voir plus bas). Cerner suit la lettre,
   voiler couvre le cadre, c'est la seule des trois voies qui ne pose aucune forme
   sur la matière. Le fond nu mesure 1,5 à 2,7:1 selon la cellule, chiffre gardé au
   clair : WCAG mesure le fond, pas le cerne, donc l'écart AA reste connu et décidé.
   Le médaillon n'est d'ailleurs pas la seule porte : l'adresse est dans le pied et
   dans les mentions légales, et « Écrire à Anton » ouvre la boîte sans rien lire.
   La règle s'annule d'elle-même si le canvas est retiré (retour à la vidéo). */
.contact-vitrine:has(.vitrine-pixels) .vitrine-voile {
  background: linear-gradient(180deg,
    rgba(10, 10, 12, 0.05) 0%,
    rgba(10, 10, 12, 0.34) 52%,
    rgba(10, 10, 12, 0.54) 100%);
}
.vitrine-voile::after {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(18rem circle at var(--mx, 50%) var(--my, 60%), rgba(255, 203, 112, 0.22), transparent 65%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 0.7s var(--sortie);
}
.contact-vitrine:hover .vitrine-voile::after { opacity: 1; }

/* Le rythme de la carte : deux écarts seulement, portés par des marges
   explicites (pas un gap uniforme). L'adresse est l'information, le mot est le
   geste, la note est leur annotation, l'air au-dessus du geste est donc plus
   large que celui qui retient la note. */
.vitrine-contenu {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 0;
  padding: clamp(1.2rem, 4vw, 2.6rem);
}

/* L'adresse : bouton de copie, balayage doré au survol. */
.vitrine-mail {
  position: relative;
  background: none; border: 0; cursor: pointer;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.15rem, 3.4vw, 2rem);
  letter-spacing: 0.01em;
  color: var(--blanc);
  padding: 0.2rem 0.1rem;
}
.vitrine-mail-txt {
  background: linear-gradient(100deg, var(--blanc) 0%, var(--blanc) 38%, var(--ambre-clair) 50%, var(--blanc) 62%, var(--blanc) 100%);
  background-size: 260% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: background-position 1.1s var(--sortie), opacity 0.35s var(--sortie);
}
.contact-vitrine:hover .vitrine-mail-txt { background-position: 0 0; }
/* Le cerne des trois textes du médaillon. Une ombre serrée d'abord (l'arête, c'est
   elle qui rend la lettre lisible sur une cellule crème), une diffuse ensuite
   (l'assise). Aucune forme n'est posée sur la matière : le cerne suit la lettre.
   L'adresse passe par `drop-shadow` et non `text-shadow` : son remplissage est
   transparent (dégradé découpé), une text-shadow se verrait à travers la lettre
   au lieu de la border. Le filtre, lui, travaille sur l'alpha rendu. */
.vitrine-mail-txt {
  filter:
    drop-shadow(0 1px 1px rgba(10, 10, 12, 0.9))
    drop-shadow(0 0 7px rgba(10, 10, 12, 0.75));
}
.ecrire-txt, .vitrine-note {
  text-shadow:
    0 1px 1px rgba(10, 10, 12, 0.9),
    0 0 8px rgba(10, 10, 12, 0.7);
}
/* Les deux gestes du médaillon sont dessinés au mot près : l'adresse et son trait,
   le geste et son soulignement. Un calque invisible leur donne les 44 px du pouce
   sans toucher au dessin, le mot garde son rythme (30 juillet 2026). */
.vitrine-mail::before, .vitrine-ecrire::before {
  content: '';
  position: absolute; left: -0.7rem; right: -0.7rem;
  top: 50%; height: 44px; transform: translateY(-50%);
}
/* En contrastes forcés, les fonds d'image disparaissent et le remplissage
   transparent reste : l'adresse s'effaçait entièrement. On rend l'encre. */
@media (forced-colors: active) {
  .vitrine-mail-txt {
    background: none;
    -webkit-text-fill-color: currentColor;
  }
}
.vitrine-mail::after {
  content: '';
  position: absolute; left: 0; right: 100%; bottom: -0.1rem;
  height: 1px;
  background: var(--ambre);
  transition: right 0.7s var(--volet);
}
.contact-vitrine:hover .vitrine-mail::after { right: 0; }

/* Confirmation de copie : l'étiquette prend la place de l'adresse un instant. */
.vitrine-copie {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-weight: 500;
  font-size: 0.85rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ambre-clair);
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity 0.35s var(--sortie), transform 0.35s var(--sortie);
  pointer-events: none;
}
.vitrine-mail.copie .vitrine-mail-txt { opacity: 0; }
.vitrine-mail.copie .vitrine-copie { opacity: 1; transform: translateY(0); }

/* Le geste principal : écrire, sans quitter la page. Toujours présent, copier
   une adresse pour aller écrire ailleurs était une friction inutile. Le contour
   arrive comme un trait qui s'ouvre, le mot se décode (chorégraphie dans app.js). */
/* Le geste principal est un mot, pas un composant : Clash Display en casse
   normale, souligné du trait ambre qui se tire à l'arrivée. L'ancienne pilule
   (bords 999px + backdrop-blur + majuscules espacées) cumulait les tics des
   interfaces générées, retirée le 21 juillet 2026, décision d'Anton. */
.vitrine-ecrire {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  /* Le bas du padding EST l'écart mot-soulignement : assez serré pour que le
     trait souligne le mot au lieu de flotter sous lui. */
  padding: 0.1rem 0.2rem 0.55rem;
  margin-top: clamp(0.9rem, 2.4vw, 1.5rem);
  border: 0;
  background: none;
  color: var(--blanc);
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.5rem, 2.8vw, 2.2rem);
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: color 0.4s var(--sortie);
}
/* Le trait vit dans son propre calque : il se tire (scaleX) sans toucher au
   mot. Le survol passe par height, jamais par transform, GSAP laisse un
   transform en ligne après l'arrivée, le CSS ne peut pas le gagner. */
.ecrire-trait {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--ambre);
  pointer-events: none;
  transition: background 0.4s var(--sortie), height 0.4s var(--sortie);
}
.ecrire-txt { position: relative; }
.vitrine-ecrire:hover, .vitrine-ecrire:focus-visible { color: var(--ambre-clair); }
.vitrine-ecrire:hover .ecrire-trait, .vitrine-ecrire:focus-visible .ecrire-trait {
  background: var(--ambre-clair);
  height: 3px;
}
/* Le bouton n'est JAMAIS caché par le CSS : c'est le geste principal de la page.
   C'est app.js qui l'escamote juste avant de le révéler, donc toute panne en
   amont le laisse simplement visible. */

/* Le lien-bouton : même dessin que .vue-lien quand l'action reste dans la page. */
button.vue-lien {
  background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer;
  text-align: left;
}

/* Le curseur « Copier » n'apparaît qu'au contact de l'adresse : là, et là
   seulement, le vrai curseur s'efface au profit de la pilule. Le bouton et le
   reste du cadre gardent le curseur du système (pointer sur le bouton, flèche
   ailleurs). Copier n'arrive donc jamais par surprise. */
/* L'adresse parasitait le geste : le seul point d'entrée est « Écrire à
   Anton ». Elle reste dans le code (elle est copiable au clavier, et les
   moteurs la lisent), mais elle n'occupe plus la scène. Hors de tout @media :
   enfermée dans (hover:hover), elle réapparaissait sur tactile. */
.vitrine-mail { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; padding: 0; }
@media (hover: hover) and (pointer: fine) {
  .vitrine-mail { cursor: none; }
}
/* Une fois copiée, la pilule s'efface et le curseur système revient (spécificité
   plus forte que la règle cursor:none ci-dessus). */
.js .contact-vitrine .vitrine-mail.copie-fait { cursor: pointer; }
/* La pilule n'existe QUE là où le JS l'anime : pointeur fin et survol réels.
   Masquée par défaut (mobile, tactile, mouvement réduit), pas de calque GPU
   ni de will-change gaspillés sur téléphone. Le sélecteur est aussi spécifique
   que `.contact > :not(.contact-fond)` plus haut, qui repose tous les enfants
   en `position: relative` : sinon la pilule retombe dans le flux, loin du
   pointeur, et comme le vrai curseur est masqué il ne reste aucun curseur. */
.contact > .vitrine-curseur { display: none; }
@media (hover: hover) and (pointer: fine) {
  .contact > .vitrine-curseur {
    /* Fixée au viewport, au-dessus de tout (voiles 50-60, grain 55) : la pilule
       ne passe jamais derrière, ni sous le cadre de la vitrine (overflow). */
    position: fixed; left: 0; top: 0; z-index: 90;
    pointer-events: none;
    display: inline-flex; align-items: center;
    padding: 0.5rem 1rem;
    /* Ambre, mais inversée : fond ambre, texte foncé, l'inverse de l'UI (partout
       ailleurs c'est texte ambre sur noir). L'inversion suffit à la distinguer. */
    border: 1px solid rgba(10, 10, 12, 0.2);
    border-radius: 999px;
    background: rgba(255, 203, 112, 0.92);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    font-size: 0.62rem; font-weight: 600;
    letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--noir);
    opacity: 0;
    will-change: transform;
  }
}

/* Les deux portes d'envoi : elles n'apparaissent qu'une fois le message écrit,
   mais leur place est réservée en permanence, l'apparition ne décale rien.
   La porte IA passe en premier et porte l'ambre (voie favorisée) ; « Envoyer à
   Anton » garde son délai dans le libellé. */
.idee-portes {
  display: flex; flex-wrap: wrap; gap: 0.8rem;
  align-items: center; justify-content: center;
  visibility: hidden;
}
.idee-portes.la { visibility: visible; }
.idee-porte {
  padding: 0.7rem 1.4rem;
  border: 1px solid var(--ligne);
  border-radius: 999px;
  background: none;
  color: var(--blanc);
  font: inherit; font-size: 0.85rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: border-color 0.3s var(--sortie), color 0.3s var(--sortie);
}
.idee-porte[data-porte="ia"] { border-color: var(--ambre); color: var(--ambre-clair); }
.idee-porte:hover, .idee-porte:focus-visible {
  border-color: var(--ambre);
  color: var(--ambre-clair);
}

.vitrine-note {
  color: var(--blanc);
  font-size: clamp(0.82rem, 1.4vw, 0.95rem);
  /* 0,92 et non 0,82 : la note reste en retrait par la taille, pas en rognant
     10 points de contraste sur un texte de 15 px qui doit tenir 4,5:1. */
  opacity: 0.92;
  max-width: 34rem;
  margin-top: clamp(0.7rem, 1.8vw, 1.1rem);
  text-wrap: balance;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
}
/* Le visage qui répond, à la taille d'une signature : il accompagne la phrase,
   il ne la concurrence pas. Le cercle suit le zoom de la vitrine entière. */
.vitrine-moi {
  flex: none;
  width: clamp(1.7rem, 3vw, 2.15rem);
  height: clamp(1.7rem, 3vw, 2.15rem);
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--blanc) 26%, transparent),
              0 6px 14px -8px rgba(0, 0, 0, 0.6);
}
.vitrine-lieu {
  color: var(--gris);
  font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
}

/* Lecteurs d'écran uniquement. */
.visu-cachee {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.contact-titre {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.4rem, 7vw, 5.4rem);
  letter-spacing: -0.015em;
}

/* ---------- Pied : la signature géante ---------- */

/* Pleine largeur (pas de max-width) : le nom court d'un bord à l'autre. overflow
   hidden clôt le débord latéral du nom et sert de masque à la révélation montante.
   Aucun padding bas : la signature est collée au bas de la page. */
/* Noir uni, sans champ de glyphes (décision du 20 juillet 2026) : la matière
   vit dans le hero et le contact, le pied est la page qui se referme. L'air
   au-dessus de la méta et sous la ligne est le même que l'écart méta-nom :
   un seul rythme vertical, desktop comme mobile. */
.pied {
  border-top: 1px solid var(--ligne);
  padding: clamp(2rem, 6vw, 4rem) clamp(1.2rem, 4vw, 3rem) 0;
  display: flex; flex-direction: column; gap: clamp(2rem, 6vw, 4rem);
  overflow: hidden;
}

/* Rang de méta : liens fins, uppercase, dans le registre des coins du hero. */
.pied-meta {
  display: flex; flex-wrap: wrap; align-items: baseline;
  /* Aucun écart de rang : le padding des trois items (44 px de cible tactile,
     30 juillet 2026) sépare les lignes quand elles passent à la ligne. */
  justify-content: space-between; gap: 0 2rem;
  font-size: 0.74rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gris);
}
.pied-meta > * { padding-block: 0.82rem; }
.pied-lien { color: var(--gris); text-decoration: none; transition: color 0.3s var(--sortie); }
.pied-lien:hover { color: var(--ambre-clair); }
.pied-lieu { color: var(--gris); }

/* Le nom géant, bord à bord assumé : le masque déborde jusqu'aux vrais bords de
   l'écran (annule le padding latéral du pied) et le nom est volontairement un peu
   plus large que le viewport, les lettres extrêmes embrassent les côtés, l'overflow
   hidden coupe le tout petit excédent. line-height serré pour coller au bas. */
.pied-nom-masque {
  line-height: 0.72;
  overflow: hidden;
  margin-inline: calc(-1 * clamp(1.2rem, 4vw, 3rem));
}
.pied-nom {
  font-family: var(--display); font-weight: 700;
  font-size: 15.4vw;
  line-height: 0.72;
  letter-spacing: -0.035em;
  color: var(--blanc);
  white-space: nowrap;
  text-align: center;
}

/* ---------- Pages simples (404, merci, mentions légales) ---------- */

.skip {
  position: absolute; left: -100vw; top: 0; z-index: 100;
  background: var(--ambre); color: var(--noir);
  padding: 0.6rem 1.2rem;
}
.skip:focus { left: 0; }

.page-simple {
  max-width: 44rem;
  margin-inline: auto;
  min-height: 70vh;
  padding: clamp(7rem, 18vh, 11rem) clamp(1.2rem, 4vw, 3rem) clamp(4rem, 10vh, 7rem);
}
.page-simple h1 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.9rem, 5vw, 3.4rem);
  line-height: 1.05; letter-spacing: -0.015em;
}
.page-simple p { margin-top: 1.4rem; color: var(--gris); }
.page-simple p strong { color: var(--blanc); }
.page-simple a {
  color: var(--ambre);
  text-decoration: none;
  border-bottom: 1px solid var(--ambre);
  transition: color 0.3s var(--sortie), border-color 0.3s var(--sortie);
}
.page-simple a:hover { color: var(--ambre-clair); border-color: var(--ambre-clair); }

/* 404 : la silhouette se tient dans la salle vide, à droite du texte, et se
   dissout par le bas. Sous 46rem elle passe sous le texte, plus petite. */
/* L'ancre est la salle entière, pas la colonne de texte : celle-ci fait 44rem
   centrées, une silhouette calée dessus passerait sur les mots. */
.perdu-salle { position: relative; }
.perdu-moi {
  position: absolute;
  right: clamp(1rem, 4vw, 5rem);
  bottom: 0;
  width: clamp(5.25rem, 8vw, 8.25rem);
  margin: 0;
  opacity: 0.82;
  pointer-events: none;
  z-index: 0;
}
.perdu-moi img { display: block; width: 100%; height: auto; }
/* Plus de marge à droite : elle repasse sous le texte, dans le flux. La salle
   perd sa hauteur minimale, sinon le vide de la colonne l'éloigne du texte. */
@media (max-width: 68rem) {
  .page-perdu { min-height: 0; padding-bottom: 0; }
  .perdu-moi { position: static; width: 6.25rem; margin: 2.5rem auto 0; opacity: 0.78; }
}

/* ---------- Responsive ---------- */

@media (max-width: 860px) {
  /* La vignette restait cachée sous 861 px : la section de preuve n'était plus que
     quatre rangées de texte sur téléphone. Elle passe sous le nom, pleine largeur
     de la colonne, au format natif des captures (30 juillet 2026). */
  .etude-carte {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "n corps fleche"
      ".  vignette vignette";
    row-gap: 1.2rem;
  }
  .etude-n { grid-area: n; align-self: start; }
  .etude-corps { grid-area: corps; }
  .etude-fleche { grid-area: fleche; }
  .etude-vignette { grid-area: vignette; width: 100%; aspect-ratio: 16 / 9; }
  /* Les deux calques de cible tactile du médaillon ne doivent pas se toucher. */
  .vitrine-ecrire { margin-top: 1.7rem; }
  .vue-colonnes { grid-template-columns: 1fr; }
  .vue-figure { position: static; order: -1; }
  .carte-bouton { top: 4.6rem; }
  .hero-cue { display: none; }
  .autre-ligne { grid-template-columns: 1fr auto; gap: 0.3rem 1rem; }
  .autre-ligne .aussi-fait { grid-column: 1 / -1; }
  .autre-ligne .autre-signe { grid-row: 1; grid-column: 2; align-self: center; }
  .autre-idee .autre-ligne { grid-template-columns: auto 1fr; }
  .idee-scene { justify-content: flex-end; padding-bottom: 3rem; }
}

/* ---------- Mouvement réduit ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js #entree { display: none; }
  .js .ligne-int { transform: none; }
  .js .hero-sous, .js .hero-coins, .js .hero-cue { opacity: 1; }
  .cue-fleche { animation: none; }
  .js .hero-coins:not(.trace) .carte-trait { stroke-dashoffset: 0; fill-opacity: var(--carte-masse); }
  .js .hero-coins:not(.trace) .carte-point { transform: none; }
  .carte-point, .bloc-etat::before { animation: none !important; }
  .etude-vue .vue-anime { opacity: 1; transform: none; transition: none; }
  /* Aucune vidéo produit en mouvement réduit : les posters restent, entiers. */
  .etude-video, .vue-video, .autre-video { display: none; }
  .vue-son, .vue-agrandir { display: none; }
  .contact > .vitrine-curseur { display: none; }
  .js .contact-vitrine, .js .contact-vitrine a, .js .contact-vitrine button { cursor: pointer; }
  * { transition-duration: 0.01ms !important; }
}

/* ---------- Salle claire, au choix ---------- */

/* Le sombre reste l'état par défaut. La salle claire se pose par html[data-theme="clair"] :
   un script en tête de page (avant la feuille, donc avant le premier rendu) lit le
   choix mémorisé (localStorage « theme »), sinon la préférence système, et suit le
   commutateur du haut ainsi qu'un basculement d'OS tant qu'aucun choix n'est mémorisé
   (3 septembre 2026, docs/V7-LANGUES.md ; le suivi système seul date du 25 juillet,
   journal docs/BETA-B1.md). Les 7 tokens rebasculent : papier chaud, encre profonde,
   ambre approfondi pour rester lisible sur clair. Les noms de tokens sont conservés :
   en clair, --noir = papier, --blanc = encre. Sans script, le @media en fin de bloc
   garde le suivi système sur les tokens seuls. */
html[data-theme="sombre"] { color-scheme: dark; }
html[data-theme="clair"] {
  color-scheme: light;
  --noir: #F4F1EA;    /* fond papier chaud */
  --surface: #FBF8F1; /* feuille surélevée */
  --ligne: #E4DDCF;   /* filets, bordures */
  --blanc: #1B1813;   /* encre : texte principal */
  --gris: #6B6459;    /* texte secondaire (AA) */
  /* L'accent du clair est le bout terracotta de la rampe « Golden hour » qu'Anton
     a choisie le 20 juillet, approfondi jusqu'à passer AA : un vermillon, pas un
     ocre. Les ocres essayés avant (#A65D00 puis #9C5400) étaient le réflexe
     arithmétique, assombrir l'ambre doré jusqu'à 4,5:1 le fait virer au kaki,
     et ça ne ressemblait à personne. Retirés le 30 juillet 2026, décision d'Anton.
     En sombre l'accent reste l'autre bout de la même rampe, l'ambre doré. */
  --ambre: #C23A1B;   /* vermillon : 4,76:1 sur le papier, 5,06:1 sur la feuille */
  /* En sombre le survol MONTE en luminosité ; en clair il DESCEND. */
  --ambre-clair: #9B2C12; /* 6,74:1 */
}

/* Inserts qui restent la salle noire dans la page claire (vitrine média dorée,
   puces de contrôle posées sur la matière) : tokens redéclarés localement, leur
   sous-arbre garde la palette vive sans qu'une règle change. */
html[data-theme="clair"] .contact-vitrine,
html[data-theme="clair"] .vue-son, html[data-theme="clair"] .vue-agrandir {
  --noir: #0A0A0C;
  --surface: #131318;
  --ligne: #232329;
  --blanc: #F4F1EA;
  --gris: #98938A;
  --ambre: #FFCB70;
  --ambre-clair: #FFE2A6;
}

/* Six points hors token, inversés pour le clair. */
html[data-theme="clair"] .carte-trait { --carte-masse: 0.09; stroke-opacity: 0.5; }
html[data-theme="clair"] .etude-vignette::after {
  background:
    linear-gradient(155deg, rgba(27, 24, 19, 0.06), rgba(27, 24, 19, 0.18)),
    radial-gradient(130% 130% at 72% 18%, rgba(194, 58, 27, 0.14), transparent 62%);
}
html[data-theme="clair"] .vue-media.agrandi, html[data-theme="clair"] .autre-media.agrandi { box-shadow: 0 40px 100px rgba(27, 24, 19, 0.25); }
html[data-theme="clair"] .idee-scene { background: rgba(244, 241, 234, 0.94); }
/* La pilule-curseur est un aplat ambre vif : texte encre (l'insert ne l'atteint pas). */
html[data-theme="clair"] .contact > .vitrine-curseur { color: var(--blanc); }
/* En sombre, `difference` gardait le menu lisible sur le hero ; en clair il
   retournait l'encre en gris pâle. Le haut ne survole que le hero clair. */
html[data-theme="clair"] .haut { mix-blend-mode: normal; }

/* Sans script (html sans data-theme) : le suivi système, sur les tokens seuls. */
@media (prefers-color-scheme: light) {
  html:not([data-theme]) {
    color-scheme: light;
    --noir: #F4F1EA;
    --surface: #FBF8F1;
    --ligne: #E4DDCF;
    --blanc: #1B1813;
    --gris: #6B6459;
    --ambre: #C23A1B;
    --ambre-clair: #9B2C12;
  }
}

/* ===================================================================
   LES PHRASES · le mur des neuf projets (section #etudes)

   Chaque projet s'ouvre sur la phrase réelle qui l'a déclenché. Au survol,
   au focus, ou au passage au centre de l'écran (tactile), la racine prend
   html[data-univers="…"] : les sept tokens de la salle rebasculent sur la
   palette du produit, définie dans projets/univers.css avec son grain, sa
   lumière, ses fontes et le composant repris de son code. Rien ici ne sort
   des tokens de DESIGN.md : les couleurs des produits vivent dans le
   fichier cité, jamais dans la charte de la maison.

   Ajouté le 29 août 2026, en remplacement des sections .etudes et .aussi.
   =================================================================== */

:root {
  /* le fondu de la salle quand elle change de lumière */
  --fondu: .85s cubic-bezier(.22, 1, .36, 1);
  /* la fonte du produit : chaque univers la redéfinit, sinon la maison */
  --u-display: var(--display);
  --u-sans: var(--sans);
  --u-lumiere: none;
  /* la marge latérale de la maison, et la largeur du mur */
  --marge: clamp(1.2rem, 4vw, 3rem);
  --large: 84rem;
}

html { transition: background-color var(--fondu); }
body { transition: background-color var(--fondu), color var(--fondu); }

/* La lumière du projet : une nappe fixe définie par l'univers, en fondu.
   Puis son grain : le même feTurbulence que le produit, mêmes réglages.
   Sous le grain de la maison (#grain, z-index 55), au-dessus du fond. */
body::before, body::after {
  content: ''; position: fixed; inset: 0; z-index: 0;
  pointer-events: none; opacity: 0; transition: opacity var(--fondu);
}
body::before { background: var(--u-lumiere); }
body::after {
  background-image: var(--u-grain, none);
  background-size: var(--u-grain-taille, 240px 240px);
  mix-blend-mode: var(--u-grain-mode, overlay);
}
html[data-univers] body::before { opacity: 1; }
html[data-univers] body::after { opacity: var(--u-grain-opacite, 0); }

/* Tout ce qui porte un token suit le fondu : la salle change d'un seul mouvement. */
.mur, .bloc, .jauges, .bloc-n, .bloc-nom, .bloc-etat, .bloc-etat::before, .phrase-lien,
.guill, .bloc-source, .jauge .et, .jauge .va, .mur-titre, .mur-titre em, .mur-sous, .mur-appel {
  transition: background-color var(--fondu), color var(--fondu), border-color var(--fondu), opacity .5s ease;
}

.mur {
  max-width: var(--large); margin: 0 auto;
  padding: clamp(3.5rem, 9vh, 6rem) var(--marge) 0;
  border-top: 1px solid var(--ligne);
}
.mur-titre {
  margin: 0 0 1.4rem;
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.8rem, 1.2rem + 2.6vw, 3.4rem);
  line-height: 1.06; letter-spacing: -.022em;
  text-wrap: balance; max-width: 22ch;
}
.mur-titre em { font-style: normal; color: var(--ambre); }
.mur-sous {
  margin: 0; max-width: 54ch; color: var(--gris);
  font-size: clamp(1rem, .95rem + .3vw, 1.1875rem); line-height: 1.62;
}
/* Une seule phrase porte la voix pleine : celle qui annonce le mur. */
.mur-appel { font-style: normal; color: var(--blanc); }
.mur-sous .indice { color: var(--blanc); }
@media (hover: none) { .mur-sous .indice-survol { display: none; } }
@media (hover: hover) { .mur-sous .indice-tactile { display: none; } }

/* ANTON DANS LA SALLE · c'est moi qui parle, donc le portrait vient en
   premier et la phrase répond. Il est détouré : plus de rectangle à
   raccorder au fond, une seule lueur derrière lui, qui prend la couleur de
   l'univers en cours. La teinte sur la peau reste un souffle, la salle
   change autour de lui, pas lui. */
.mur-entete {
  display: grid; gap: clamp(1.6rem, 4vw, 2.4rem);
  align-items: end; margin: 0 0 clamp(2rem, 5vw, 3.5rem);
}
@media (min-width: 64rem) {
  .mur-entete {
    grid-template-columns: clamp(13rem, 21vw, 18.75rem) minmax(0, 1fr);
    gap: 0 clamp(2.4rem, 5vw, 4.5rem);
    align-items: center;
  }
}
.moi { position: relative; margin: 0; width: min(15rem, 56vw); justify-self: start; }
@media (min-width: 64rem) { .moi { width: 100%; } }
/* Le texte tombait trop haut : la photo se dissout par le bas, sa masse
   visuelle est plus haute que sa boîte. On redescend la colonne de mots. */
@media (min-width: 64rem) {
  .mur-mots { position: relative; top: clamp(1rem, 3.4vw, 2.75rem); }
}

/* la lueur : elle remplace le fond de la photo, et se fond dans la salle */
.moi::before {
  content: ''; position: absolute; z-index: 0;
  left: 50%; top: 52%; width: 132%; aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--ambre) 15%, transparent) 0%,
    color-mix(in srgb, var(--ambre) 6%, transparent) 46%,
    transparent 72%);
  pointer-events: none; opacity: var(--lueur, 1);
  transition: background var(--fondu);
}
/* L'ARRIVÉE · la salle s'allume, et il est déjà là dedans. Un seul geste : la
   lumière de la lueur s'ouvre, très feutrée, et le portrait se résout dans son
   halo, pas de ligne de balayage, pas de mosaïque, aucune arête nulle part.
   Le bord du masque fait 60 % du rayon : à aucun moment on ne voit sa forme. */
.moi-cadre {
  position: relative; z-index: 1; display: block;
  --revele: 1;
  -webkit-mask-image: radial-gradient(farthest-corner at 50% 42%,
    #000 calc(var(--revele) * 162% - 62%), transparent calc(var(--revele) * 162%));
  mask-image: radial-gradient(farthest-corner at 50% 42%,
    #000 calc(var(--revele) * 162% - 62%), transparent calc(var(--revele) * 162%));
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
/* Une fois posé, plus de masque du tout : la couche disparaît avec son coût. */
.moi-cadre.pose { -webkit-mask-image: none; mask-image: none; }
/* Le buste ne se dissout pas seulement par le bas : le t-shirt clair tirait
   l'œil par les épaules aussi. Une ellipse calée sur le visage efface les
   trois côtés d'un même geste, sans jamais entamer la tête : le plein tient
   jusqu'au sommet des cheveux et jusqu'au menton, la transparence est
   atteinte au bas de la boîte, donc aucune arête nulle part. */
.moi-cadre img {
  display: block; width: 100%; height: auto;
  transition: filter .9s ease;
  -webkit-mask-image: radial-gradient(62% 62% at 50% 34%, #000 55%, transparent 100%);
  mask-image: radial-gradient(62% 62% at 50% 34%, #000 55%, transparent 100%);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
/* Le portrait porte la lumière du produit survolé, mais à peine : un voile
   à 10 %, en teinte seulement, jamais un aplat de couleur. Le voile suit la
   silhouette ET la même ellipse que l'image : sans l'intersection des deux,
   il repeindrait les épaules effacées et laisserait un fantôme rectangulaire
   là où la photo, elle, s'est déjà dissoute. */
.moi-cadre::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: var(--ambre); mix-blend-mode: color; opacity: .10;
  transition: opacity .9s ease, background-color var(--fondu);
  -webkit-mask-image: url('/media/anton.webp'),
    radial-gradient(62% 62% at 50% 34%, #000 55%, transparent 100%);
  mask-image: url('/media/anton.webp'),
    radial-gradient(62% 62% at 50% 34%, #000 55%, transparent 100%);
  -webkit-mask-size: 100% 100%, 100% 100%; mask-size: 100% 100%, 100% 100%;
  -webkit-mask-repeat: no-repeat, no-repeat; mask-repeat: no-repeat, no-repeat;
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
/* La salle noire : le t-shirt clair tirerait l'œil plus que le visage. */
.moi-cadre img { filter: saturate(.72) contrast(1.03) brightness(.9); }
html[data-theme="clair"] .moi-cadre img { filter: saturate(.8) contrast(1.02); }
.moi:hover .moi-cadre img { filter: none; }
.moi:hover .moi-cadre::after { opacity: 0; }
/* LA PORTE (4 septembre 2026, docs/V6-SALLE.md) · le portrait vit dans un bouton
   nu ; au survol il se rapproche un peu (l'image, pas le cadre : le cadre est
   animé par GSAP à l'arrivée et mesuré par Flip au départ), au clic il vole vers
   la fenêtre de présence et s'y dissout dans le globe. */
.moi-porte {
  display: block; width: 100%; padding: 0; margin: 0; border: 0;
  background: none; color: inherit; font: inherit; text-align: inherit; cursor: pointer;
}
.moi-porte .moi-cadre img { transition: filter .9s ease, transform .7s cubic-bezier(.16, 1, .3, 1); }
.moi-porte:hover .moi-cadre img, .moi-porte:focus-visible .moi-cadre img { transform: scale(1.05); }
.moi-porte:focus-visible { outline: 1px solid var(--ambre); outline-offset: 10px; border-radius: 4px; }
.moi-leg {
  position: relative; z-index: 1;
  margin: .7rem 0 0; font-size: .72rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gris);
  transition: color var(--fondu);
}

/* Rien n'est visible avant : ni le portrait, ni la lueur, ni un seul mot.
   La suite est écrite dans app.js. */
.js .moi { --lueur: 0; }
.js .moi-cadre { --revele: 0; }
.js .mur-sous, .js .mur-titre, .js .moi-leg { opacity: 0; }
.mur-ligne { display: block; overflow: hidden; }

.mur-liste { list-style: none; margin: 0; padding: 0; }

/* RESPIRATION ET SON ÉTIQUETTE (2 septembre 2026, leviers 2 et 3 de docs/V6-SALLE.md)
   Deux temps par la voix : six phrases d'autres personnes, puis les trois
   phrases d'Anton. Seul le second porte une étiquette de micro-texte, jamais un
   titre (règle typo de DESIGN.md) ; celle du premier, avant les projets, était
   du bruit (retirée le 4 septembre 2026). La respiration entre les deux est un
   écran calme, cadré par le filet bas de 06 et le filet haut de 07 : le portrait
   revient, petit, dans sa lueur, et dit une seule phrase. La salle y reprend sa
   propre lumière (app.js éteint l'univers quand elle passe au centre). */
.chapitre {
  margin: 0 0 1.1rem;
  font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--gris);
  transition: color var(--fondu);
}
.respiration {
  display: grid; justify-items: center; text-align: center;
  gap: clamp(1.4rem, 3vw, 2rem);
  padding: clamp(5rem, 16vh, 10rem) var(--marge);
}
.respiration .moi { width: clamp(6.5rem, 9vw, 8.5rem); margin: 0; justify-self: center; }
.respiration .chapitre { margin: .4rem 0 0; }
.respiration-mot {
  margin: 0; max-width: 22ch; text-wrap: balance;
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.45rem, 1rem + 1.9vw, 2.5rem); line-height: 1.12; letter-spacing: -.018em;
  color: var(--blanc); transition: color var(--fondu);
}
.js .respiration-mot, .js .respiration .chapitre { opacity: 0; }

/* LE RAIL (2 septembre 2026, levier 5 de docs/V6-SALLE.md) · neuf points fixes
   dans la marge droite, deux groupes séparés par le vide de la respiration, le
   courant grossit et prend l'accent (donc la couleur de l'univers). Le nom
   n'apparaît qu'au survol, ou pour le courant. Le compte « 03 / 09 » dessous.
   Visible seulement pendant le mur (classe .visible posée par app.js).
   Desktop au pointeur fin, à partir de 75rem : sous cette largeur la marge
   ne le loge pas, et la numérotation 01-09 suffit. Au-delà, le mur cède
   3,4rem de chaque côté au-delà de sa marge pour que le rail ne chevauche
   jamais le spécimen ; les grands écrans ne voient aucune différence. */
.rail { display: none; }
@media (min-width: 75rem) and (hover: hover) {
  .mur { --large: min(84rem, 100vw - 2 * (var(--marge) + 3.4rem)); }
  .rail {
    position: fixed; right: calc(var(--marge) - .3rem); top: 50%; z-index: 30;
    display: flex; flex-direction: column; align-items: flex-end; gap: .35rem;
    transform: translate(0, -50%);
    opacity: 0; pointer-events: none;
    transition: opacity .6s ease;
  }
  .rail.visible { opacity: 1; pointer-events: auto; }
  .rail a {
    display: flex; align-items: center; gap: .8rem;
    padding: .3rem 0; text-decoration: none; color: var(--gris);
    transition: color var(--fondu);
  }
  .rail-point {
    flex: none; width: 6px; height: 6px; border-radius: 50%;
    background: var(--gris); opacity: .5;
    transition: transform .5s cubic-bezier(.22, 1, .36, 1), opacity .4s ease, background-color var(--fondu), box-shadow var(--fondu);
  }
  .rail a:hover .rail-point { opacity: 1; }
  .rail a.courant .rail-point {
    background: var(--ambre); opacity: 1; transform: scale(1.5);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--ambre) 16%, transparent);
  }
  .rail-nom {
    font-size: .66rem; letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
    opacity: 0; transform: translateX(.4rem);
    transition: opacity .4s ease, transform .6s cubic-bezier(.22, 1, .36, 1), color var(--fondu);
  }
  .rail a:hover .rail-nom, .rail a:focus-visible .rail-nom { opacity: 1; transform: none; }
  .rail a.courant .rail-nom { opacity: 1; transform: none; color: var(--blanc); }
  .rail-sep { height: .9rem; }
  .rail-compte {
    margin-top: .7rem; padding-right: .1rem;
    font-family: var(--display); font-weight: 500; font-size: .78rem; letter-spacing: .06em;
    color: var(--gris); font-variant-numeric: tabular-nums; transition: color var(--fondu);
  }
  .rail-compte b { font-weight: 500; color: var(--ambre); transition: color var(--fondu); }
}

.bloc {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 0 clamp(2rem, 5vw, 4.5rem); align-items: center;
  border-top: 1px solid var(--ligne);
  padding: clamp(2.75rem, 6vw, 5rem) 0 clamp(2.25rem, 4.5vw, 3.5rem);
  cursor: pointer;
}
.mur-liste > .bloc:last-child { border-bottom: 1px solid var(--ligne); }
@media (min-width: 64rem) {
  .bloc {
    grid-template-columns: minmax(0, 1fr) clamp(19rem, 31vw, 27rem);
    /* chaque projet respire : l'oeil ne doit jamais lire deux blocs d'un
       coup, la frontière est d'abord du vide (décision du 2 septembre) */
    padding: clamp(4rem, 7.5vw, 6.75rem) 0 clamp(3.25rem, 5.5vw, 4.75rem);
  }
}

/* Quand la salle est dans un univers, les autres blocs s'effacent à demi. */
html[data-univers] .bloc:not(.actif) { opacity: .34; }
html[data-univers] .bloc:not(.actif) .specimen { opacity: 0; }

/* le filet d'accent qui se dessine en haut du bloc actif */
.bloc::after {
  content: ''; position: absolute; left: 0; top: -1px; height: 1px; width: 0;
  background: var(--ambre);
  transition: width .7s cubic-bezier(.22, 1, .36, 1), background-color var(--fondu);
}
.bloc.actif::after { width: 100%; }

.bloc-tete { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; margin: 0 0 clamp(1.4rem, 2.6vw, 2.1rem); }
.bloc-n { font-family: var(--display); font-weight: 500; font-variant-numeric: tabular-nums; font-size: .9rem; color: var(--ambre); letter-spacing: .04em; }
.bloc-nom { font-size: .78rem; letter-spacing: .15em; text-transform: uppercase; color: var(--gris); font-weight: 500; }
.bloc.actif .bloc-nom { color: var(--blanc); }
.bloc-etat {
  margin-left: auto;
  font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--gris);
  display: inline-flex; align-items: center; gap: .5rem;
}
.bloc-etat::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--ambre);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ambre) 16%, transparent);
}
.bloc-etat.est-chantier::before { background: var(--gris); box-shadow: 0 0 0 4px color-mix(in srgb, var(--gris) 14%, transparent); }
/* le point d'un produit en service bat, lentement : quelque chose tourne
   (levier 6). Un chantier ne bat pas. */
.bloc-etat:not(.est-chantier)::before { animation: en-service 3.6s ease-in-out infinite; }
@keyframes en-service {
  0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--ambre) 16%, transparent); }
  50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--ambre) 6%, transparent); }
}

/* LA PHRASE · le titre du bloc est un lien vers la page du projet, et c'est
   le seul objet focalisable du bloc. Le reste du bloc est une cible élargie. */
.phrase { margin: 0; font: inherit; }
.phrase-lien {
  display: block; text-decoration: none;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.65rem, 1rem + 3.4vw, 3.9rem);
  line-height: 1.09; letter-spacing: -.02em;
  text-wrap: pretty; max-width: 20ch;
  color: var(--blanc);
}
.phrase-lien:focus-visible { outline: 2px solid var(--ambre); outline-offset: 10px; border-radius: 4px; }
.guill { color: var(--ambre); font-weight: 400; }
@media (min-width: 64rem) { .guill-o { position: absolute; transform: translateX(-.62em); } }
.bloc-source { margin: 1.4rem 0 0; font-size: .875rem; color: var(--gris); font-style: italic; }

/* la barre de données : délai, chiffre clé, date */
.jauges {
  display: flex; flex-wrap: wrap; gap: 0 clamp(1.5rem, 4vw, 3.5rem);
  margin: clamp(2rem, 4vw, 3rem) 0 0; padding-top: 1.4rem;
  border-top: 1px solid var(--ligne);
}
.jauge { padding: .35rem 0; min-width: 0; margin: 0; }
.jauge .et { display: block; font-size: .68rem; letter-spacing: .14em; text-transform: uppercase; color: var(--gris); margin-bottom: .42rem; }
.jauge .va { display: block; margin: 0; font-weight: 500; font-size: clamp(.9rem, .86rem + .2vw, 1rem); line-height: 1.35; font-variant-numeric: tabular-nums; max-width: 34ch; }
.jauge-delai .va { font-family: var(--display); font-weight: 600; font-size: clamp(1rem, .9rem + .5vw, 1.35rem); }
.jauge-cle .va { color: var(--ambre); font-family: var(--display); font-weight: 600; font-size: clamp(1.15rem, 1rem + .8vw, 1.7rem); line-height: 1.15; }

/* MONTRER MOINS (2 septembre 2026, levier 4 de docs/V6-SALLE.md) · vingt-sept
   chiffres à l'écran faisaient du mur un tableau. Une seule jauge reste
   visible d'emblée, celle qui compte : ce qui en reste aujourd'hui, en ambre,
   passée en tête. Le délai et la date gardent leur place dans la rangée mais
   n'apparaissent qu'au geste : le pointeur sur la colonne de texte, le focus
   clavier dans le bloc, ou, au tactile, le bloc allumé au centre de l'écran.
   Plus on s'approche, plus on voit. Opacité seule : aucun reflow, et les
   lecteurs d'écran lisent tout. Sans JS ou en mouvement réduit, tout est là. */
.jauge-cle { order: -1; }
.js .jauge:not(.jauge-cle) {
  opacity: 0; transform: translateY(.45rem);
  transition: opacity .55s ease, transform .8s cubic-bezier(.22, 1, .36, 1);
}
.js .jauge-delai { transition-delay: .06s; }
.js .jauge:not(.jauge-cle):not(.jauge-delai) { transition-delay: .14s; }
.js .bloc-corps:hover .jauge, .js .bloc:focus-within .jauge { opacity: 1; transform: none; }
@media (hover: none) { .js .bloc.actif .jauge { opacity: 1; transform: none; } }

/* le bouton d'entrée du produit, dans son style à lui (projets/univers.css) */
.bloc-cta { margin: 1.7rem 0 0; min-height: 48px; display: flex; align-items: center; }

/* LE SPÉCIMEN · un composant réel du produit, posé dans la salle.
   Caché sous 64rem : sur petit écran, on le retrouve en tête de sa page. */
.specimen { display: none; }
@media (min-width: 64rem) {
  .specimen {
    display: block; position: relative;
    opacity: 0; transform: translate3d(0, 18px, 0) scale(.975);
    transition: opacity .55s ease, transform 1s cubic-bezier(.22, 1, .36, 1);
    pointer-events: none;
  }
  .bloc.actif .specimen { opacity: 1; transform: none; }
}
.specimen-mvt { will-change: transform; transition: transform .9s cubic-bezier(.22, 1, .36, 1); }
.specimen-mvt.suit { transition: transform .12s linear; }

/* la transition vers la page : seul le bloc actif nomme ses deux objets */
.bloc.actif .phrase-lien { view-transition-name: phrase; }
.bloc.actif .specimen { view-transition-name: specimen; }

/* arrivée au défilement */
.js .bloc { opacity: 0; transform: translateY(22px); transition: opacity .8s ease, transform .8s cubic-bezier(.22, 1, .36, 1), border-color var(--fondu); }
.js .bloc.vu { opacity: 1; transform: none; }
.js html[data-univers] .bloc.vu:not(.actif) { opacity: .34; }

/* la dernière ligne : la boîte à idée, sans spécimen ni filet d'accent. Elle
   vit hors des listes (hors de la salle traversée) : le filet du bas est le sien. */
.mur-liste-2 > .bloc:last-child { border-bottom: 0; }
.bloc-idee { cursor: default; padding: clamp(2rem, 4vw, 3rem) 0; border-bottom: 1px solid var(--ligne); }
.bloc-idee::after { display: none; }
html[data-univers] .bloc-idee { opacity: .34; }

/* FEUILLETAGE TACTILE · un projet = un écran, qui se centre dans la
   fenêtre. Les frontières entre projets sont de l'espace vide, plus aucun
   filet : c'est la lumière d'univers qui dit où l'on est. Le défilement
   s'aimante doucement sur le bloc (proximity, jamais mandatory : le geste
   reste libre et réversible, le hero et le contact ne sont pas des points
   d'aimant). La numérotation 01-09 des blocs situe déjà, aucun indicateur
   ajouté. La date de premier fichier vit sur la page projet : ici deux
   faits, le délai et ce qui en reste, pas trois. */
@media (hover: none) and (max-width: 63.99rem) {
  html { scroll-snap-type: y proximity; }
  .bloc { border-top: 0; }
  .mur-liste > .bloc:last-child, .bloc-idee { border-bottom: 0; }
  .bloc::after { display: none; }
  .bloc:not(.bloc-idee) {
    min-height: 100svh;
    align-content: center;
    scroll-snap-align: center;
    padding: clamp(3.5rem, 10svh, 6rem) 0;
  }
  .jauges { border-top: 0; padding-top: .4rem; gap: 1.1rem 2rem; }
  .jauge:not(.jauge-delai):not(.jauge-cle) { display: none; }
  /* la respiration est un écran à part entière, aimantée comme un projet */
  .respiration { min-height: 100svh; align-content: center; scroll-snap-align: center; }
}

/* LA SALLE TRAVERSÉE (2 septembre 2026, levier 1 de docs/V6-SALLE.md)
   Sur desktop au pointeur fin, hors mouvement réduit, app.js pose html.salle-active :
   le cadre .salle prend la hauteur de l'écran et s'épingle (ScrollTrigger),
   ses dix états (neuf projets et la respiration) s'empilent dedans, un seul
   visible à la fois, et se succèdent au rythme du défilement. Les listes
   disparaissent de la mise en page (display: contents) pour que chaque état
   se cale sur le cadre. Opacités et mouvements sont posés en ligne par le
   script : ici, aucune transition ne doit les lisser. Partout ailleurs, cette
   section est inerte et le mur reste une liste. */
html.salle-active .salle { position: relative; height: 100vh; height: 100svh; }
html.salle-active .salle .mur-liste { display: contents; }
html.salle-active .salle .bloc, html.salle-active .salle .respiration,
html.salle-active .salle .bloc:not(.actif) {
  position: absolute; inset: 0; margin: 0; padding: 0; border: 0;
  display: grid; align-content: center;
  opacity: 1; transform: none; transition: none;
  pointer-events: none; cursor: default;
}
html.salle-active .salle .etat-courant { pointer-events: auto; }
/* Mesuré le 3 septembre 2026 (sonde de temps d'image, Chrome headless) : les
   fondus de couleur des textes (--fondu, 0,85 s) étaient la seule cause des
   images longues pendant une traversée, et ils ne servent à rien ici, les
   textes sont effacés au moment où la lumière change. Coupés dans la salle ;
   le fondu de la lumière (html, body, nappes) reste. Chaque état et chaque
   spécimen sont promus en couche : leurs opacités et déplacements ne
   repeignent pas la page. */
html.salle-active .salle :is(.bloc-tete, .bloc-n, .bloc-nom, .bloc-etat, .phrase-lien, .guill, .bloc-source,
  .jauges, .jauge .et, .jauge .va, .chapitre, .respiration-mot, .moi-leg, .bloc-cta) { transition: none; }
html.salle-active .salle .bloc-etat::before { transition: none; }
/* Au bas de l'épingle, la lumière glisse sous un texte visible (app.js,
   lumiereDouce, du dernier univers, sombre, à la salle, sombre) : le temps du
   fondu, les encres suivent le fond au lieu de sauter. En haut, où la lumière
   s'inverse (01 est une salle claire), aucune transition ne tient : l'encre et
   le fond se croisent à mi-course, le texte est effacé puis recomposé (app.js,
   rejouer). */
html.salle-active.lumiere-douce .salle :is(.bloc-tete, .bloc-n, .bloc-nom, .bloc-etat, .phrase-lien, .guill, .bloc-source,
  .jauges, .jauge .et, .jauge .va, .chapitre, .respiration-mot, .moi-leg, .bloc-cta) { transition: color var(--fondu), border-color var(--fondu); }
html.salle-active.lumiere-douce .salle .bloc-etat::before { transition: background-color var(--fondu); }
html.salle-active .salle .bloc, html.salle-active .salle .respiration, html.salle-active .salle .specimen { will-change: opacity, transform; }
@media (min-width: 64rem) {
  html.salle-active .salle .bloc { grid-template-columns: minmax(0, 1fr) clamp(19rem, 31vw, 27rem); gap: 0 clamp(2rem, 5vw, 4.5rem); }
}
/* Sur tactile aussi (3 septembre 2026) : la salle remplace le feuilletage
   aimanté. Plus d'aimant, un seul écran épinglé et des états qui se succèdent. */
html.salle-active { scroll-snap-type: none; }
html.salle-active .salle .bloc, html.salle-active .salle .respiration { scroll-snap-align: none; min-height: 0; }
html.salle-active .salle .bloc-corps, html.salle-active .salle .specimen { cursor: pointer; }
html.salle-active .salle .bloc::after { display: none; }
/* la phrase se plie aussi à la hauteur de l'écran : la plus longue (04) doit
   tenir avec ses jauges dans un cadre de 900 px */
html.salle-active .phrase-lien { font-size: clamp(1.65rem, min(1rem + 3.4vw, 6vh), 3.9rem); }
html.salle-active .jauges { margin-top: clamp(1.4rem, 3vw, 2.2rem); }
html.salle-active .specimen { transition: none; }
html.salle-active .salle .respiration { padding: 0; justify-items: center; }
/* LES LIGNES MASQUÉES (4 septembre 2026) · la phrase et le chiffre clé sont
   découpés par app.js (decouper) en lignes, chacune dans son masque : la ligne
   monte de sous le masque (yPercent 118 vers 0). Le masque est un clip-path,
   pas un overflow : il déborde de 1em sur les côtés pour le guillemet ouvrant
   suspendu, de .22em dessous pour les jambages, de .08em dessus pour les
   accents. Aucune transition CSS ici, tout est posé en ligne par le script. */
html.salle-active .salle .ligne-masque { display: block; clip-path: inset(-.08em -1em -.22em -1em); }
html.salle-active .salle .ligne-texte { display: block; will-change: transform; }

/* La transition entre l'accueil et une page projet : la phrase et le spécimen
   voyagent, le reste se fond. Le repli sans View Transitions est dans app.js. */
@view-transition { navigation: auto; }

/* Le morphe, et rien d'autre. Le contenu nomme n'est jamais fondu : l'ancien
   disparait a l'instant du declenchement et le nouveau est peint plein pendant
   que sa boite se deplace. Croiser les deux images faisait clignoter l'objet ;
   le laisser vide faisait regarder un cadre se deformer. z-index le tient
   au-dessus du fondu general, pour qu'il ne s'eteigne a aucun moment. */
::view-transition-group(*) { animation-duration: .62s; animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
::view-transition-old(root) { animation: vt-sortir .3s ease both; }
::view-transition-new(root) { animation: vt-entrer .45s ease both; }
::view-transition-group(phrase), ::view-transition-group(specimen) { z-index: 3; }
::view-transition-old(phrase), ::view-transition-new(phrase),
::view-transition-old(specimen), ::view-transition-new(specimen) {
  animation: none; mix-blend-mode: normal; height: 100%; overflow: clip;
}
::view-transition-old(phrase), ::view-transition-old(specimen) { opacity: 0; }
@keyframes vt-sortir { to { opacity: 0; } }
@keyframes vt-entrer { from { opacity: 0; } }
html.part body { opacity: 0; transition: opacity .26s ease; }

@media (max-width: 47.99rem) {
  .phrase-lien { max-width: none; }
  .bloc-etat { margin-left: 0; width: 100%; margin-top: .4rem; }
}

@media (prefers-reduced-motion: reduce) {
  :root { --fondu: .01ms; }
  .js .bloc { opacity: 1; transform: none; }
  .js .jauge { opacity: 1; transform: none; }
  .js .respiration-mot, .js .respiration .chapitre { opacity: 1; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}
