/* ── Ideogenium ─────────────────────────────────────────────────────────
   Un seul écran, rien ne défile au niveau du document. Le défilement fait
   monter une tête ; arrivée en place, son cerveau devient l'index du site :
   le curseur y promène une loupe qui révèle le réseau, et chaque région
   éclairée ouvre quelque chose. Les valeurs viennent de la maquette
   (Ideogenium Web.dc.html) ; seules les valeurs qui dépendent du défilement
   sont écrites par main.js.
   ------------------------------------------------------------------- */

/* ── Polices (auto-hébergées, découpées comme Google Fonts) ─────────── */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Les titres des aperçus du portfolio, dans la police de chaque site. Rien
   ne se télécharge tant que la liste de l'atelier n'est pas ouverte. */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/newsreader-500-latin.woff') format('woff');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/instrument-serif-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Jetons ─────────────────────────────────────────────────────────── */

html.light {
  --bg: #EDEEF5;
  /* l'échelle des encres, qui doit se voir : le secondaire (8,3:1), le
     registre (5,3:1, au-dessus du seuil AA), l'ornement (3,4:1 — jamais un
     texte qu'on doit lire, pas même l'indication du champ) */
  --ink: #0B0D12; --ink2: #3E4552; --ink3: #5A6270; --ink4: #7A8190;
  --line: rgba(11, 13, 18, .08); --line2: rgba(11, 13, 18, .14);
  --sig: color-mix(in oklab, #4FD6E8 55%, #0B0D12); /* réécrit par main.js, même valeur */
  /* La tête est tournée sur fond blanc : en clair on la multiplie, en sombre
     on l'inverse et on l'additionne, et la vidéo reste la même. */
  --headfx: contrast(1.02); --headblend: multiply;
  --revealfx: saturate(1.15) contrast(1.05);
  /* l'or champagne, assombri pour rester lisible en petits caractères sur le
     clair : 5,6:1 sur le fond — un numéro de 10 px est parfois tout ce qu'on
     lit (la navigation d'un téléphone) */
  --gold: #735A27;
  color-scheme: light;
}
html.dark {
  --bg: #05060A;
  --ink: #EEF1F6; --ink2: #A8B0BE; --ink3: #98A0AE; --ink4: #8B93A2;
  --line: rgba(238, 241, 246, .07); --line2: rgba(238, 241, 246, .14);
  --sig: oklch(0.82 0.11 197);
  /* la tête garde ses couleurs (laque noire, circuits) : en sombre, la page
     montre media/tete-noire.mp4, la même vidéo au fond passé au noir
     (main.js headSource) — ni inversion. Son noir (#000) est un rien plus
     sombre que le fond : en « lighten », le rectangle de la vidéo se fond
     dans la page, et la laque noire ne bouge pas d'un ton perceptible */
  --headfx: none; --headblend: lighten;
  --revealfx: brightness(1.1) saturate(1.2);
  /* l'or champagne : un filet, un numéro, une jauge — jamais une surface */
  --gold: #CDB27A;
  color-scheme: dark;
}
:root {
  --sans: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --mono: 'IBM Plex Mono', monospace;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --gutter: clamp(18px, 4vw, 64px);
  --accent: #4FD6E8; /* la couleur brute, celle de la loupe (et de son aperçu dans la liste de l'atelier) */
}

/* ── Base ───────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  overscroll-behavior: none;
}
button, input { font: inherit; color: inherit; }
a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--sig); }
::selection { background: color-mix(in oklab, var(--sig) 30%, transparent); }
/* l'indication du champ est un texte qu'on lit : le ton des registres, pas l'ornement */
input::placeholder { color: var(--ink3); }
[hidden] { display: none !important; }

/* Le focus du clavier, partout et une seule fois : un filet du signal,
   détaché de ce qu'il entoure — visible sans crier. Il suit le --sig de
   la surface (la vitrine et l'atelier portent le leur). */
:focus { outline: none; }
:focus-visible { outline: 1.5px solid var(--sig); outline-offset: 6px; }

.root {
  position: relative;
  height: 100dvh;
  width: 100%;
  background: var(--bg);
  overflow: hidden;
}

.sig-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--sig); box-shadow: 0 0 12px var(--sig);
}

/* ── Le système ────────────────────────────────────────────────────
   Un seul langage pour tout le site, celui de l'atelier : du texte et des
   filets, pas de boîte ni de pastille. L'or champagne (--gold) ne sert
   qu'aux filets, aux numéros et aux jauges ; le signal (--sig), qu'au focus
   du clavier et au point « vivant ». Deux polices : Space Grotesk 300 pour
   ce qui se lit (noms, phrases), IBM Plex Mono en petites capitales
   espacées pour ce qui se repère (signature, numéros, registres). Jamais de
   gras, jamais d'icône de police : une croix ou une flèche dessinées, au
   plus (les logos restent dans le cerveau, en WebGL, et dans la vitrine
   des apps, en objets rendus). Les mêmes classes servent en sombre et en
   clair (les jetons changent), sur les surfaces toujours sombres
   (l'atelier) et sur celle, toujours en couleur, de la vitrine.

     .u-mark   la signature d'une surface : .u-brand ─ .u-rule ─ .u-num .u-title
     .u-btn    un mot qui se clique : du ton secondaire au plein, un filet
               qui se trace dessous (--u : 0 rien, 1 tout le mot)
     .u-close  deux filets croisés dans un cercle de filet
     .u-meta   petites capitales mono (registres, compteurs)
     .u-num    un numéro en or
     .u-line   une jauge : un filet, et l'or qui avance dessus (--k : 0 → 1)
     .u-caption  une ligne de légende, discrète
     .u-arrow  la flèche « → », dessinée : trois filets, de la couleur du mot
     .u-bar    la ligne du haut d'une surface (signature à gauche, outils ou
               croix à droite), aux marges de l'en-tête de l'accueil
     .u-sheet, .u-head, .u-h, .u-note, .u-list
               une page d'index (la liste des sites) : la
               feuille qui défile, son titre, sa note, ses lignes de filets
   ------------------------------------------------------------------- */

.u-mark { display: flex; align-items: center; gap: 14px; min-width: 0; white-space: nowrap; }
.u-brand {
  font-family: var(--mono); font-size: 10px; font-weight: 400; letter-spacing: .28em; text-transform: uppercase;
  color: var(--ink2);
}
.u-rule { flex: 0 0 auto; width: 24px; height: 1px; background: var(--gold); }
.u-num { font-family: var(--mono); font-size: 10px; letter-spacing: .18em; color: var(--gold); font-variant-numeric: tabular-nums; }
.u-title { font-size: 15px; font-weight: 300; letter-spacing: -.01em; color: var(--ink); }
/* dans la signature, le numéro se tait (l'ornement) : le filet d'or qui le
   précède porte déjà l'or — c'est la signature de l'atelier, la référence */
.u-mark > .u-num { letter-spacing: .2em; color: var(--ink4); }
.u-meta { font-family: var(--mono); font-size: 10px; letter-spacing: .22em; text-transform: uppercase; color: var(--ink3); }
.u-caption { font-size: 14px; font-weight: 300; letter-spacing: -.005em; color: var(--ink2); }

.u-btn {
  position: relative; display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 0; border: 0; border-radius: 2px; background: none; cursor: pointer;
  font-family: var(--sans); font-size: 13.5px; font-weight: 300; line-height: 1.2; letter-spacing: -.005em;
  color: var(--ink2); white-space: nowrap; text-decoration: none; -webkit-tap-highlight-color: transparent;
  transition: color .45s var(--ease);
}
.u-btn::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 1px; height: 1px; background: currentColor;
  transform: scaleX(var(--u, 0)); transform-origin: 0 50%; transition: transform .5s var(--ease);
}
.u-btn[aria-pressed="true"] { --u: .3; }
.u-btn[aria-pressed="false"] { color: var(--ink3); }
.u-btn[aria-expanded="true"], .u-btn[aria-current="true"], .u-btn.is-on { --u: 1; color: var(--ink); }
.u-btn:focus-visible { color: var(--ink); }

.u-close {
  position: relative; flex: 0 0 auto; width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line2); border-radius: 50%; background: none; cursor: pointer;
  color: var(--ink2); -webkit-tap-highlight-color: transparent;
  transition: border-color .45s var(--ease), color .45s var(--ease);
}
.u-close::before, .u-close::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 12px; height: 1px; margin: -.5px 0 0 -6px;
  background: currentColor; transform: rotate(45deg);
}
.u-close::after { transform: rotate(-45deg); }
.u-close:focus-visible { border-color: var(--ink2); color: var(--ink); }

.u-line { position: relative; display: block; width: 140px; height: 1px; overflow: hidden; background: var(--line2); }
.u-line::after {
  content: ''; position: absolute; inset: 0; background: var(--gold);
  transform: scaleX(var(--k, 0)); transform-origin: 0 50%; transition: transform .35s linear;
}

/* « → » se dessine plutôt que de se taper : les polices du site, découpées
   comme Google Fonts, s'arrêtent à ↑ et ↓, et le repli du système la
   tracerait d'une autre main. Un filet, et deux filets courts pour la
   pointe. Dans le texte, une espace insécable la précède (elle ne passe
   jamais seule à la ligne) ; dans un .u-btn, l'écart du bouton suffit. */
.u-arrow {
  position: relative; display: inline-block; flex: 0 0 auto;
  width: .95em; height: 1px; margin-left: .25em; vertical-align: middle;
  background: currentColor;
}
.u-arrow::before, .u-arrow::after {
  content: ''; position: absolute; right: 0; top: 0; width: .42em; height: 1px;
  background: currentColor; transform-origin: 100% 50%; transform: rotate(40deg);
}
.u-arrow::after { transform: rotate(-40deg); }
.u-btn > .u-arrow { margin-left: 0; }
/* « ↗ » : la même flèche, levée — un site déjà en ligne ailleurs, qui
   s'ouvre dans un nouvel onglet (Citadisima) */
.u-arrow.is-out { rotate: -45deg; }

/* La ligne du haut, la même sur chaque surface : les marges de l'en-tête de
   l'accueil (.top, .top-in, centrée au-delà de 1560 px) — la signature ne
   bouge pas quand une surface s'ouvre. Sur la vitrine et l'atelier, --edge
   la resserre sur un téléphone. Elle ne prend le pointeur que sur ce qui se
   clique (chaque surface le rend à ses outils). */
.u-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding:
    max(clamp(18px, 2.8vh, 30px), env(safe-area-inset-top))
    max(var(--edge, var(--gutter)), env(safe-area-inset-right), calc(50% - 780px))
    0
    max(var(--edge, var(--gutter)), env(safe-area-inset-left), calc(50% - 780px));
  pointer-events: none;
}

/* Une page d'index, par-dessus une surface (la liste des sites de
   l'atelier) : la feuille défile sous la barre, qui reste au-dessus ; une
   colonne, le titre en grand, une note, des lignes séparées par des filets
   qui montent l'une après l'autre (--n). */
.u-sheet {
  position: absolute; inset: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: clamp(104px, 15vh, 140px) var(--edge, var(--gutter)) clamp(56px, 9vh, 96px);
  /* le haut s'efface sous la barre plutôt que d'y buter */
  -webkit-mask-image: linear-gradient(to bottom, transparent 64px, #000 104px);
  mask-image: linear-gradient(to bottom, transparent 64px, #000 104px);
  /* une ligne qui prend le focus (Tab, Maj+Tab) défile hors de ce fondu,
     son cadre de focus compris : focus() ne bouge pas ce qui est déjà
     « dans » le panneau, même invisible sous le masque */
  scroll-padding-top: 112px;
}
.u-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 24px; padding-bottom: clamp(28px, 4.4vh, 44px); }
.u-h { margin: 0; font-size: clamp(28px, 3.4vw, 40px); font-weight: 300; line-height: 1.1; letter-spacing: -.03em; color: var(--ink); }
.u-note { margin: calc(-1 * clamp(10px, 2vh, 20px)) 0 clamp(28px, 4.4vh, 40px); max-width: 52ch; line-height: 1.6; text-wrap: pretty; }
.u-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line2); }
.u-list > li {
  border-bottom: 1px solid var(--line2);
  animation: u-rise .8s var(--ease) backwards; animation-delay: calc(.06s + var(--n, 0) * 70ms);
}

/* au doigt, un survol resterait collé à ce qu'on vient de toucher : la
   souris seule trace le filet sous un mot ou allume la croix */
@media (hover: hover) {
  .u-btn:hover { --u: 1; color: var(--ink); }
  .u-close:hover { border-color: var(--ink2); color: var(--ink); }
}

/* Le mouvement d'une surface qui s'ouvre (l'atelier, la vitrine, le
   manifeste) : un fondu, une barre qui descend à peine, des lignes qui
   montent à peine — toujours sur --ease. Sans animation si on les réduit. */
@keyframes u-fade { from { opacity: 0; } }
@keyframes u-drop { from { opacity: 0; transform: translateY(-6px); } }
@keyframes u-rise { from { opacity: 0; transform: translateY(8px); } }

/* Le regard de l'accroche (accroche/regard.js) : le buste de l'androïde,
   en grand, posé au bas de l'écran à droite de la phrase (dessous, en
   portrait), qui suit le curseur. Sous la scène de la tête, qui monte
   par-dessus en défilant. Il arrive en fondu (son affiche, de profil). Il ne
   prend pas les événements : le module écoute la fenêtre. */
.accroche {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  opacity: 0; transition: opacity 1.1s var(--ease);
  pointer-events: none;
}
.accroche.is-on { opacity: 1; }
/* L'image entière, posée en pixels par regard.js (sa taille, sa place,
   l'approche au défilement, autour des yeux). Un groupe isolé sur le fond
   exact de la page : la vidéo, fond noir en sombre, blanc en clair, s'y
   fond par « lighten » ou « multiply », comme la tête du cerveau (.stage) ;
   le bas du buste s'efface sur le dernier quart de l'écran (--fade, écrit
   par regard.js : il dépend de la place du buste). */
.regard {
  position: absolute; left: 0; top: 0;
  background: var(--bg); isolation: isolate;
  will-change: transform, opacity;
}
.regard-v {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  object-fit: cover;
  filter: var(--headfx); mix-blend-mode: var(--headblend);
  -webkit-mask-image: var(--fade, none);
          mask-image: var(--fade, none);
  transition: opacity .45s var(--ease);
}
/* un changement de thème : cachée d'un coup (son fond ne se fondrait plus
   dans la page), rendue en fondu une fois calée sur le même instant */
.regard-v.is-hidden { opacity: 0; transition: none; }
/* le relief (accroche/relief.js) : sa toile, qui lève et baisse la tête,
   prend la place de la vidéo — qui reste dessous, cachée, et lui donne
   chaque image */
.regard.has-relief video.regard-v { visibility: hidden; }
/* la recherche ouverte : ses résultats passent devant, la figure s'efface
   à moitié (en portrait, ils descendent sur elle) */
.root:has(.results:not([hidden])) .regard-v:not(.is-hidden) { opacity: .22; }
/* au doigt, un glissé horizontal reste à la page (la tête suit le doigt) :
   sans cela le navigateur annule le geste au bout de quelques pixels ; le
   vertical, lui, continue de faire défiler (main.js, touchmove) */
.hero { touch-action: pan-y pinch-zoom; }

/* ── La scène de la tête ────────────────────────────────────────────
   Taille et position en pixels, calculées par main.js à partir de la
   hauteur de fenêtre : la loupe et les régions s'y recalent sans jamais
   relire la mise en page. */

.stage {
  position: absolute; z-index: 1; left: 0; top: 0;
  pointer-events: none;
  /* la scène est un groupe isolé (will-change) : le mélange de la tête se
     fait avec ce fond, la couleur même de la page — invisible, mais sans
     lui « lighten » et « multiply » n'auraient rien sous eux */
  background: var(--bg);
  will-change: transform;
}
.head {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0;
  filter: var(--headfx); mix-blend-mode: var(--headblend);
  transition: opacity 1s ease;
}
.head.is-on { opacity: 1; }
.reveal {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity .45s ease; pointer-events: none; overflow: hidden;
}
.reveal-mask {
  position: absolute; left: 0; top: 0; width: 460px; height: 460px;
  -webkit-mask-image: radial-gradient(circle at center, #000 0 42%, rgba(0,0,0,.82) 58%, rgba(0,0,0,.45) 74%, rgba(0,0,0,.14) 88%, transparent 100%);
          mask-image: radial-gradient(circle at center, #000 0 42%, rgba(0,0,0,.82) 58%, rgba(0,0,0,.45) 74%, rgba(0,0,0,.14) 88%, transparent 100%);
  will-change: transform;
}
.reveal-inner { position: absolute; left: 0; top: 0; width: 100%; height: 100%; will-change: transform; }
.reveal-img {
  position: absolute; left: 16.3%; top: 9%; width: 69%; height: auto;
  filter: var(--revealfx); user-select: none; -webkit-user-drag: none;
}
.stage-fade { position: absolute; bottom: 0; left: 0; right: 0; height: 10%; background: linear-gradient(to top, var(--bg), transparent); }

/* ── Régions du cerveau ─────────────────────────────────────────────
   Avec WebGL, cerveau.js dessine chaque région dans le réseau lui-même :
   la fenêtre, l'anneau de sélection et le logo tracé en lumière. Il ne reste
   ici que la zone cliquable et l'étiquette, reliée au nœud par un trait.
   Sans WebGL, .region-node montre le logo dans un nœud lumineux. */

.brain { position: absolute; left: 0; top: 0; z-index: 2; pointer-events: none; will-change: transform; }
.has-brain .reveal, .has-brain .spot, .has-brain .lens { display: none; }

.glyph {
  fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round;
  overflow: visible; flex: 0 0 auto;
}

.region {
  position: absolute; width: 150px; height: 150px;
  transform: translate(-50%, -50%);
  display: grid; place-items: center;
  border-radius: 50%; z-index: 3; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  pointer-events: none;
}
.region:hover { color: inherit; }
.region.is-live { pointer-events: auto; }
.region:focus-visible { outline: none; }

@keyframes breathe { 0%, 100% { box-shadow: 0 0 14px -4px var(--c); } 50% { box-shadow: 0 0 26px 0 var(--c); } }
.region-node {
  position: absolute; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; color: var(--c);
  background: radial-gradient(circle, #070A14 58%, color-mix(in oklab, var(--c) 30%, #070A14) 100%);
  border: 1px solid color-mix(in oklab, var(--c) 55%, transparent);
  animation: breathe 3.6s ease-in-out infinite;
  transition: transform .45s var(--ease);
}
.region-node .glyph { width: 20px; height: 20px; filter: drop-shadow(0 0 3px var(--c)); }
.region.is-on .region-node, .region:focus-visible .region-node { transform: scale(1.4); }
/* Au repos, rien ne trahit la région : son nœud n'apparaît qu'au passage du
   curseur (ou au clavier). Au doigt, sans survol, il reste visible. */
@media (hover: hover) {
  .region-node { opacity: 0; transform: scale(.55); transition: opacity .3s ease, transform .45s var(--ease); }
  .region.is-on .region-node, .region:focus-visible .region-node { opacity: 1; transform: scale(1.4); }
}
.region:focus-visible .region-node { outline: 2px solid var(--c); outline-offset: 4px; }
.has-brain .region-node { visibility: hidden; }

/* L'étiquette d'une région : le langage des étiquettes de l'atelier, sans
   boîte — le numéro en or, le nom, le registre, une ligne de description et
   sa flèche —, branchée sur la fenêtre par un filet d'or. Elle se pose sur
   la tête, qui s'inverse avec le thème (noire en clair, blanche en sombre).
   Ce qui lui garde sa lecture, c'est le halo sous les lettres : serré
   contre le trait, puis large, il suit le texte et non un rectangle.
   Dessous, une ombre du fond en ellipse apaise l'image sans dessiner de
   bord.
   --row : la hauteur où le filet entre dans la ligne du nom (main.js aligne
   cette ligne sur le nœud : ROW, même valeur). Le retrait (padding) laisse
   un souffle entre le bout du filet et la première lettre. */
.region-label {
  --row: 19px;
  position: absolute; left: 50%; top: 50%;
  display: flex; flex-direction: column; align-items: flex-start;
  width: min(272px, 64vw); padding: 6px 10px; text-wrap: pretty;
  color: var(--ink); isolation: isolate;
  text-shadow: 0 0 1px var(--bg), 0 0 3px var(--bg), 0 0 8px var(--bg), 0 0 18px var(--bg);
  opacity: 0; translate: 0 -4px;
  transition: opacity .35s var(--ease), translate .6s var(--ease);
  pointer-events: none; cursor: pointer;
}
/* l'ombre : une ellipse du fond, pleine au centre, évanouie avant l'anneau
   de la fenêtre — ni coin, ni bord, ni ombre portée */
.region-label::before {
  content: ''; position: absolute; z-index: -1; inset: -22px -34px; pointer-events: none;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--bg) 80%, transparent) 40%, color-mix(in oklab, var(--bg) 46%, transparent) 72%, transparent);
}
.region.is-on.is-live .region-label { pointer-events: auto; }
.region.is-on .region-label { opacity: 1; translate: 0 0; }
/* à gauche du nœud, le texte s'aligne sur le filet ; dessus ou dessous, il se centre */
.region-label[data-place="left"] { align-items: flex-end; text-align: right; }
.region-label[data-place="top"], .region-label[data-place="bottom"] { align-items: center; text-align: center; }
.region-row { display: flex; align-items: baseline; gap: 10px; max-width: 100%; }
.region-num { flex: 0 0 auto; }
.region-name { font-size: 19px; font-weight: 300; line-height: 1.25; letter-spacing: -.02em; color: var(--ink); white-space: nowrap; }
/* sur une image, le registre prend le ton secondaire plutôt que le ton
   « meta » : il reste au-dessus de 4,5:1 même où le voile s'amincit */
.region-tag { margin-top: 8px; color: var(--ink2); white-space: nowrap; }
/* la description vient juste après le nom ; elle part avec lui */
.region-desc { margin-top: 10px; line-height: 1.45; opacity: 0; transition: opacity .45s var(--ease); }
.region.is-on .region-desc { opacity: 1; transition-delay: .12s; }

/* le filet d'or qui branche l'étiquette sur le bord de la fenêtre ; un
   point d'or le termine côté nœud */
.region-lead { position: absolute; pointer-events: none; background: var(--gold); transition: transform .5s var(--ease) .06s; }
.region-lead::after { content: ''; position: absolute; width: 3px; height: 3px; border-radius: 50%; background: var(--gold); }
.region-label[data-place="right"] .region-lead { right: 100%; top: var(--row); width: var(--lead, 30px); height: 1px; transform: scaleX(0); transform-origin: right; }
.region-label[data-place="left"] .region-lead { left: 100%; top: var(--row); width: var(--lead, 30px); height: 1px; transform: scaleX(0); transform-origin: left; }
.region-label[data-place="bottom"] .region-lead { bottom: 100%; left: calc(50% + var(--shift, 0px)); height: var(--lead, 30px); width: 1px; transform: scaleY(0); transform-origin: bottom; }
.region-label[data-place="top"] .region-lead { top: 100%; left: calc(50% + var(--shift, 0px)); height: var(--lead, 30px); width: 1px; transform: scaleY(0); transform-origin: top; }
.region-label[data-place="right"] .region-lead::after { left: -1px; top: -1px; }
.region-label[data-place="left"] .region-lead::after { right: -1px; top: -1px; }
.region-label[data-place="bottom"] .region-lead::after { top: -1px; left: -1px; }
.region-label[data-place="top"] .region-lead::after { bottom: -1px; left: -1px; }
.region.is-on .region-lead { transform: none; }

/* Au doigt, pas de survol : le numéro et le nom restent écrits sous chaque
   nœud, en petites capitales, un halo du fond sous les lettres. */
.region-cap { display: none; }
@media (hover: none) {
  .region.is-live .region-cap {
    display: flex; align-items: baseline; gap: 8px; position: absolute; left: 50%; top: calc(50% + 34px); transform: translateX(-50%);
    padding-left: .2em; white-space: nowrap;
    font-family: var(--mono); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--ink);
    /* le halo des étiquettes : serré contre le trait, puis large */
    text-shadow: 0 0 1px var(--bg), 0 0 3px var(--bg), 0 0 10px var(--bg), 0 0 22px var(--bg);
    pointer-events: none; transition: opacity .3s var(--ease);
  }
  .region.is-on .region-cap { opacity: 0; }
}

/* ── Loupe ──────────────────────────────────────────────────────────── */

.spot, .lens {
  position: absolute; left: 0; top: 0; width: 460px; height: 460px;
  border-radius: 50%; pointer-events: none; opacity: 0;
  transition: opacity .4s ease; will-change: transform, opacity;
}
.spot {
  z-index: 2;
  background: radial-gradient(circle at center, color-mix(in oklab, var(--accent) 20%, transparent) 0%, color-mix(in oklab, var(--accent) 7%, transparent) 48%, transparent 74%);
}
.lens {
  z-index: 3;
  box-shadow: inset 0 0 70px -20px var(--accent), 0 0 40px -18px var(--accent);
  border: 1px solid color-mix(in oklab, var(--accent) 42%, transparent);
}

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

/* Une ligne de texte, sans barre : à gauche la signature, au milieu les
   régions, à droite la langue, le thème et le cerveau. Sous elle, un fondu
   du fond qui descend un peu plus bas qu'elle et s'évanouit : elle reste
   lisible sur la vidéo de l'accroche comme sur la tête, sans bord ni flou.
   Le fondu ne prend aucun clic ; la ligne, si (comme avant : un clic entre
   deux mots n'ouvre pas la région allumée dessous). */
.top {
  position: absolute; top: 0; left: 0; right: 0; z-index: 6;
  padding: max(clamp(18px, 2.8vh, 30px), env(safe-area-inset-top)) max(var(--gutter), env(safe-area-inset-right)) 0 max(var(--gutter), env(safe-area-inset-left));
}
.top::before {
  content: ''; position: absolute; z-index: -1; left: 0; right: 0; top: 0; height: calc(100% + 64px);
  /* assez dense sous la ligne pour le crâne, qui passe dessous quand la tête
     est en place (blanc en sombre, noir en clair) ; il ne s'efface qu'après */
  background: linear-gradient(to bottom, color-mix(in oklab, var(--bg) 94%, transparent) 0%, color-mix(in oklab, var(--bg) 82%, transparent) 45%, transparent 100%);
  pointer-events: none;
}
/* l'en-tête arrive du haut, à peine, comme la barre de l'atelier */
.top-in {
  max-width: 1560px; margin: 0 auto; display: flex; align-items: center; gap: clamp(12px, 3vw, 40px);
  animation: u-drop .9s var(--ease) .2s backwards;
}

.top-brand { flex: 0 0 auto; padding: 8px 0; border-radius: 2px; transition: color .45s var(--ease); }
.top-brand:hover { color: var(--ink2); }
@media (hover: hover) { .top-brand:hover { color: var(--ink); } }

/* Les trois régions : un numéro en or, un mot. Le filet se trace sous le
   mot seul (pas sous le numéro) ; la région allumée — au cerveau, à la
   recherche ou au survol — le garde plein. */
.nav { display: flex; align-items: center; gap: clamp(18px, 2.4vw, 34px); margin: 0 auto; min-width: 0; }
.nav-a { gap: 10px; }
.nav-a::after { content: none; }
.nav-t { position: relative; }
.nav-t::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px; background: currentColor;
  transform: scaleX(var(--u, 0)); transform-origin: 0 50%; transition: transform .5s var(--ease);
}
/* au doigt, :hover colle au mot touché : pas de couleur du signal (a:hover) */
.nav-a:not(.is-on):hover { color: var(--ink2); }
@media (hover: hover) { .nav-a:not(.is-on):hover { color: var(--ink); } }

.tools { display: flex; align-items: center; gap: clamp(18px, 2vw, 28px); flex: 0 0 auto; margin-left: auto; }
/* la langue : les deux codes, celui en cours au plein, l'autre effacé */
.lang { gap: 7px; letter-spacing: .04em; }
.lang [data-l] { color: var(--ink3); transition: color .45s var(--ease); }
.lang [data-l].is-cur { color: var(--ink); }
.lang-sep { color: var(--ink4); }
@media (hover: hover) { .lang:hover [data-l]:not(.is-cur) { color: var(--ink2); } }
/* « Le cerveau ↓ », « ↑ Retour » : des mots comme les autres, leur flèche
   de leur couleur (l'or ne sert qu'aux filets et aux numéros) */

/* ── Accroche ───────────────────────────────────────────────────────── */

/* La phrase et la recherche, dans le langage de l'atelier : un registre en
   petites capitales (le point vivant, un filet d'or), deux lignes en Space
   Grotesk 300 — la seconde au ton secondaire —, un champ sans cadre posé
   sur un filet, et ses résultats en index éditorial. Tout arrive d'un
   fondu, à peine un glissement, l'un après l'autre. */
.hero {
  position: absolute; inset: 0; z-index: 5;
  display: flex; flex-direction: column; justify-content: center;
  /* transform seulement : main.js le réécrit à chaque pas du défilement */
  will-change: transform;
}
.hero-in {
  width: 100%; max-width: 1560px; margin: 0 auto;
  padding: clamp(70px, 10vh, 120px) max(var(--gutter), env(safe-area-inset-right)) 0 max(var(--gutter), env(safe-area-inset-left));
  display: flex; flex-direction: column; gap: clamp(18px, 3vh, 34px);
}
/* En portrait, la phrase monte sous l'en-tête : le buste du regard
   (accroche/regard.js) se pose dessous, coupé au bas de l'écran, comme dans
   un portrait. */
@media (max-aspect-ratio: 105/100) {
  .hero { justify-content: flex-start; }
  .hero-in { padding-top: clamp(84px, 12vh, 132px); }
}
@keyframes live { 50% { opacity: .35; } }

.kicker { display: flex; align-items: center; gap: 14px; animation: u-rise 1s var(--ease) .1s backwards; }
/* le point vivant, le seul de la page : il respire, en fondu */
.kicker .sig-dot { animation: live 2.8s ease-in-out infinite; }

.h1 {
  margin: 0; font-size: clamp(32px, 5.2vw, 80px); line-height: 1.02; font-weight: 300;
  letter-spacing: -.045em;
  animation: u-rise 1.1s var(--ease) .18s backwards;
}
/* deux lignes, toujours : chacune la sienne */
.h1a, .h1b { display: block; text-wrap: balance; }
.h1a { color: var(--ink); }
.h1b { color: var(--ink2); }

/* Le champ : pas de cadre, un filet dessous ; au focus, l'or s'y trace
   depuis la gauche. À droite, la touche Entrée en toutes lettres (au
   clavier seulement) et « Chercher → » pour le pointeur. */
.search {
  position: relative; display: flex; align-items: center; gap: 22px; width: min(560px, 100%);
  margin-top: clamp(4px, 1vh, 12px);
  animation: u-rise 1.1s var(--ease) .28s backwards;
}
.search::before, .search::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; pointer-events: none;
}
/* plus marqué qu'un filet de séparation : c'est lui qui dit qu'on peut
   écrire là — 3:1 au moins sur le fond, dans les deux thèmes */
.search::before { background: color-mix(in oklab, var(--ink) 48%, transparent); }
.search::after { background: var(--gold); transform: scaleX(0); transform-origin: 0 50%; transition: transform .7s var(--ease); }
.search.is-focus::after { transform: none; }
.search input {
  flex: 1; min-width: 0; margin: 0; padding: 14px 0 13px;
  border: 0; border-radius: 0; outline: 0; background: transparent; -webkit-appearance: none; appearance: none;
  /* 18 px : au-dessus des 16 px sous lesquels iOS zoome toute la page */
  font-size: 18px; font-weight: 300; line-height: 1.3; letter-spacing: -.01em; color: var(--ink);
}
.search input:focus-visible { outline: none; }
.search-key { flex: 0 0 auto; padding-left: .22em; transition: color .45s var(--ease); }
.search.is-focus .search-key { color: var(--ink2); }
/* au doigt, le clavier de l'écran a sa propre touche « aller » */
@media (hover: none) { .search-key { display: none; } }
.search-go { flex: 0 0 auto; }

/* Les résultats : un index éditorial sous le champ — le numéro en or, le
   nom, le registre —, des lignes séparées par des filets. Celui qui est
   choisi (flèches, survol, région allumée) a son nom souligné d'or ; les
   autres passent au ton secondaire. */
.results {
  width: min(560px, 100%); display: flex; flex-direction: column;
  margin-top: calc(-1 * clamp(4px, 1.4vh, 14px));
  animation: u-fade .35s var(--ease) both;
}
.result {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; column-gap: 16px; align-items: baseline;
  padding: 12px 0 11px; border-bottom: 1px solid var(--line); cursor: pointer; color: var(--ink);
}
.result:hover { color: var(--ink); }
.result-name {
  position: relative; justify-self: start; min-width: 0;
  font-size: 16px; font-weight: 300; line-height: 1.3; letter-spacing: -.01em; color: var(--ink);
  transition: color .45s var(--ease);
}
.result-name::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: var(--gold);
  transform: scaleX(0); transform-origin: 0 50%; transition: transform .45s var(--ease);
}
.result.is-on .result-name::after { transform: none; }
.results:has(.result.is-on) .result:not(.is-on) .result-name { color: var(--ink2); }
.result-tag { max-width: 24ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.results-empty { padding: 13px 0 12px; border-bottom: 1px solid var(--line); }

/* « Défiler » : le mot, et un filet d'or qui descend dessous, comme on
   ferait descendre la page. */
/* (au-dessus du bas de page : son fondu passerait sinon devant le mot) */
.cue {
  position: absolute; z-index: 7; left: 50%; bottom: clamp(54px, 8vh, 90px);
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 6px 10px 6px calc(10px + .22em); border: 0; border-radius: 2px; background: none;
  cursor: pointer; transition: color .45s var(--ease);
}
.cue:focus-visible { color: var(--ink); }
@media (hover: hover) { .cue:hover { color: var(--ink); } }
.cue-line { position: relative; display: block; width: 1px; height: 34px; overflow: hidden; background: var(--line2); }
.cue-line::after {
  content: ''; position: absolute; inset: 0; background: var(--gold);
  transform-origin: 50% 0; animation: cue-grow 2.6s var(--ease) infinite;
}
@keyframes cue-grow {
  0% { transform: scaleY(0); opacity: 1; }
  55% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; }
}
/* En paysage, le buste du regard (accroche/regard.js) prend la droite et le
   bas de l'écran : « Défiler » passe sous la colonne du texte, à son aplomb
   (au milieu, l'épaule le couvrirait dès qu'elle se tourne). Les résultats
   de la recherche descendent jusqu'à lui : il se cache. */
@media (min-aspect-ratio: 1051/1000) {
  .cue {
    left: calc(max(0px, (100% - 1560px) / 2) + max(var(--gutter), env(safe-area-inset-left)) - 10px);
    transform: none; align-items: flex-start;
  }
}
.root:has(.results:not([hidden])) .cue { visibility: hidden; }
/* Un écran bas, couché (un téléphone, une petite fenêtre) : la phrase et le
   champ descendent jusqu'à lui. Il se retire ; « Le cerveau ↓ », dans
   l'en-tête, fait la même chose. */
@media (min-aspect-ratio: 1051/1000) and (max-height: 560px) {
  .cue { display: none; }
}

/* ── Bas de page ────────────────────────────────────────────────────── */

/* Trois textes sur une ligne, sans pastille : l'année à gauche, le coin à
   droite (petites capitales), et au centre, quand le cerveau est là, la
   consigne en légende et « ↑ Retour ». Trois colonnes : le centre reste au
   centre de l'écran, même quand les côtés se cachent. */
.foot {
  position: absolute; z-index: 6; left: max(var(--gutter), env(safe-area-inset-left)); right: max(var(--gutter), env(safe-area-inset-right));
  bottom: max(clamp(16px, 3vh, 30px), env(safe-area-inset-bottom));
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center;
  column-gap: clamp(10px, 2vw, 20px); pointer-events: none;
}
/* le pendant du fondu de l'en-tête : le fond remonte doucement sous le bas
   de page (et sous « Défiler »), d'un bord à l'autre de l'écran — la vidéo
   de l'accroche y passe ses filaments les plus vifs */
.foot::before {
  content: ''; position: absolute; z-index: -1; left: -50vw; right: -50vw; bottom: -60px; height: 170px;
  background: linear-gradient(to top, color-mix(in oklab, var(--bg) 72%, transparent) 35%, transparent);
  pointer-events: none;
}
.foot-year { grid-column: 1; justify-self: start; white-space: nowrap; }
.foot-corner { grid-column: 3; justify-self: end; padding-left: .22em; white-space: nowrap; }
.foot-cue {
  grid-column: 2; min-width: 0;
  display: flex; align-items: center; gap: 22px; opacity: 0;
}
.foot-cue-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.foot-back { pointer-events: none; }
.foot-back.is-live { pointer-events: auto; }

/* ── Notre mission : le manifeste ─────────────────────────────────────
   Un récit qui défile (mission/recit.js) : un fil d'or relie la première
   pierre taillée à la phrase qui, aujourd'hui, devient logiciel. La feuille
   porte ses jetons, ceux de la nuit du cerveau — comme l'atelier, elle est
   toujours sombre, quel que soit le thème de l'accueil. Elle défile
   elle-même, en trois couches :
     .m-fix    la scène (three.js, mission/scene.js), fixe, et une ombre
               douce sous le texte (.m-shade)
     .m-track  la piste : la longueur de chaque moment du récit (.m-sec),
               puis le pied de page (.m-end), qui monte par-dessus la scène
     .m-ui     fixe, au-dessus de tout : la barre, le texte du chapitre
               (.m-txt), les étiquettes posées sur la scène (.m-tag), le
               rail des chapitres, « Défiler », le rideau du chargement
   Le texte d'un chapitre se pose au bas à gauche, aux marges de la
   signature : le numéro en or, une jauge, le repère ; le titre en grand,
   lettre à lettre, d'un flou à net ; une ligne ; des chiffres ou une
   citation. Rien que du texte et des filets, comme partout. */
.mission {
  --bg: #030407;
  --ink: #EEF1F6;
  --ink2: rgba(238, 241, 246, .68);
  --ink3: rgba(238, 241, 246, .56);  /* les registres : un texte qu'on lit (AA) */
  --ink4: rgba(238, 241, 246, .36);  /* l'ornement seul */
  --line: rgba(238, 241, 246, .08);
  --line2: rgba(238, 241, 246, .16);
  --gold: #CDB27A;
  --sig: #4FD6E8;
  --edge: var(--gutter);
  /* les marges du texte : celles de la signature de la barre */
  --m-left: max(var(--gutter), env(safe-area-inset-left), calc(50% - 780px));
  --m-right: max(var(--gutter), env(safe-area-inset-right), calc(50% - 780px));
  position: absolute; inset: 0; z-index: 30;
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg); color: var(--ink); color-scheme: dark;
  /* le rail dit où l'on est : pas de barre de défilement */
  scrollbar-width: none;
  /* un fondu seulement : une transformation ferait des couches fixes
     des couches qui défilent */
  animation: u-fade .6s var(--ease) both;
}
.mission::-webkit-scrollbar { display: none; }
/* le rideau tiré : rien ne défile encore */
.mission.is-gated { overflow: hidden; }
.m-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ——— la scène ——— */
.m-fix { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.m-gl { position: absolute; inset: 0; }
/* une ombre sous le texte (bas à gauche), et sous la barre : le texte se lit
   quoi que la scène montre derrière */
.m-shade {
  position: absolute; inset: 0;
  background:
    radial-gradient(110% 72% at 0% 100%, rgba(3, 4, 7, .74), rgba(3, 4, 7, 0) 64%),
    linear-gradient(to top, rgba(3, 4, 7, .55), rgba(3, 4, 7, 0) 34%),
    linear-gradient(to bottom, rgba(3, 4, 7, .6), rgba(3, 4, 7, 0) 120px);
}
/* sans WebGL2 : le récit reste, sur la nuit du cerveau */
.mission.is-flat .m-fix { background: radial-gradient(80% 64% at 72% 30%, #0B1428, #030407 72%); }

/* ——— la piste ——— */
.m-track { position: relative; z-index: 1; pointer-events: none; }
.m-sec { height: 125vh; height: 125svh; }
.m-sec.is-first { height: 100vh; height: 100svh; }
.m-sec.is-last { height: 110vh; height: 110svh; }

/* ——— ce qui se lit et se clique ——— */
.m-ui { position: fixed; inset: 0; z-index: 2; pointer-events: none; }
.m-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 3; }
.m-bar > .u-mark { animation: u-drop .9s var(--ease) .1s backwards; }
.m-tools { display: flex; align-items: center; gap: 24px; pointer-events: auto; }
/* le compteur, au milieu de la barre, à la hauteur de la croix */
.m-count {
  position: absolute; left: 50%; top: max(clamp(18px, 2.8vh, 30px), env(safe-area-inset-top));
  height: 34px; display: flex; align-items: center; margin: 0; translate: -50% 0;
  color: var(--ink3); font-variant-numeric: tabular-nums; transition: opacity .6s var(--ease);
}
.m-now { color: var(--gold); }
.m-of { margin: 0 .7em; color: var(--ink4); }
.mission.is-gated .m-count, .mission.is-end .m-count { opacity: 0; }

/* le texte d'un moment du récit : un seul à la fois, au bas à gauche */
.m-texts { position: absolute; inset: 0; }
.m-txt {
  position: absolute; left: var(--m-left); bottom: max(clamp(30px, 7vh, 76px), env(safe-area-inset-bottom));
  width: min(900px, calc(100% - var(--m-left) - var(--m-right) - 48px));
  opacity: 0; transition: opacity .5s var(--ease);
  text-shadow: 0 0 26px rgba(3, 4, 7, .72), 0 0 2px rgba(3, 4, 7, .5);
}
.m-txt.is-on { opacity: 1; transition: opacity .7s var(--ease); translate: 0 calc(var(--drift, 0) * 1px); }
.m-meta, .m-kick { display: flex; align-items: center; gap: 14px; margin: 0 0 16px; opacity: 0; transition: opacity .6s var(--ease); }
.m-kick { gap: 12px; color: var(--ink2); }
.m-txt.is-on .m-meta, .m-txt.is-on .m-kick { opacity: 1; }
.m-meta .u-num { font-size: 11px; }
.m-meta .u-meta { color: var(--ink2); }
/* la jauge du chapitre suit le défilement à chaque image : sans transition */
.m-rule { width: 56px; }
.m-rule::after { transition: none; }

/* les titres, lettre à lettre : d'un flou à net, en montant à peine */
.m-h1, .m-title { margin: 0; font-weight: 300; text-wrap: balance; }
.m-h1 { max-width: 21ch; font-size: clamp(28px, 3.5vw, 56px); line-height: 1.06; letter-spacing: -.03em; }
.m-title { max-width: 12.5ch; font-size: clamp(36px, 5.2vw, 84px); line-height: .98; letter-spacing: -.04em; padding: .06em 0 .08em; }
.m-fin-title { max-width: 16ch; font-size: clamp(34px, 5.4vw, 88px); }
.m-w { display: inline-block; white-space: nowrap; }
.m-c {
  display: inline-block; opacity: 0; filter: blur(10px); transform: translateY(calc(var(--dir, 1) * .45em));
  transition: transform .9s var(--ease), opacity .7s var(--ease), filter .9s var(--ease);
  transition-delay: calc(var(--i) * 22ms);
}
.m-txt.is-on .m-c { opacity: 1; filter: blur(0); transform: none; }

/* la ligne, les chiffres, la citation : après le titre, du flou au net */
.m-line, .m-sub, .m-stats > div, .m-quote, .m-motto {
  opacity: 0; filter: blur(8px); transform: translateY(calc(var(--dir, 1) * 14px));
  transition: opacity 1s var(--ease), filter 1s var(--ease), transform 1s var(--ease);
}
.m-line { margin: 18px 0 0; max-width: 38ch; font-size: clamp(17px, 1.45vw, 21px); line-height: 1.42; letter-spacing: -.01em; text-wrap: pretty; transition-delay: .25s; }
.m-sub { margin: 20px 0 0; font-size: 15px; color: var(--ink2); transition-delay: .5s; }
.m-txt.is-on .m-line { opacity: .94; filter: blur(0); transform: none; }
.m-txt.is-on .m-sub, .m-txt.is-on .m-stats > div, .m-txt.is-on .m-quote, .m-txt.is-on .m-motto { opacity: 1; filter: blur(0); transform: none; }
.m-stats { display: flex; flex-wrap: wrap; gap: 18px 48px; margin: 28px 0 0; }
.m-stats > div { display: grid; gap: 8px; max-width: 24ch; transition-delay: calc(.45s + var(--j) * .12s); }
.m-stats dt { font-size: clamp(26px, 2.8vw, 44px); font-weight: 300; line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.m-stats dd { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink2); text-wrap: pretty; }
.m-quote { margin: 26px 0 0; max-width: 46ch; transition-delay: .45s; }
.m-quote blockquote { margin: 0; }
.m-quote p { margin: 0; font-size: clamp(15px, 1.15vw, 18px); line-height: 1.5; color: var(--ink2); text-wrap: pretty; }
.m-quote figcaption { margin-top: 12px; color: var(--gold); }
.m-motto { margin: 22px 0 0; font-size: clamp(20px, 2vw, 30px); line-height: 1.2; letter-spacing: -.02em; transition-delay: .55s; }
.m-motto-b { color: var(--ink2); }

/* les étiquettes posées sur la scène : un point d'or, un filet, un mot
   (recit.js les place à chaque image ; .is-l : le mot passe à gauche) */
.m-tags { position: absolute; inset: 0; overflow: hidden; }
.m-tag { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; color: var(--ink); text-decoration: none; will-change: transform, opacity; }
.m-tag-dot {
  position: absolute; left: -3px; top: -3px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--gold); box-shadow: 0 0 10px rgba(205, 178, 122, .7);
  transform: scale(0); transition: transform .4s var(--ease);
}
.m-tag-lead {
  position: absolute; top: 0; left: 8px; width: 44px; height: 1px; background: var(--gold); opacity: .7;
  transform: scaleX(0); transform-origin: 0 50%; transition: transform .5s var(--ease) .1s;
}
.m-tag-t {
  position: absolute; top: 0; left: 60px; translate: 0 -50%; white-space: nowrap; line-height: 1.2;
  opacity: 0; filter: blur(6px); transform: translateY(6px);
  transition: opacity .5s var(--ease) .22s, transform .6s var(--ease) .22s, filter .5s var(--ease) .22s;
  text-shadow: 0 0 18px rgba(3, 4, 7, .9);
}
.m-tag .u-meta.m-tag-t { color: var(--ink2); }
.m-tag.is-l .m-tag-lead { left: auto; right: 8px; transform-origin: 100% 50%; }
.m-tag.is-l .m-tag-t { left: auto; right: 60px; }
.m-tag.is-in .m-tag-dot { transform: scale(1); }
.m-tag.is-in .m-tag-lead { transform: scaleX(1); }
.m-tag.is-in .m-tag-t { opacity: 1; filter: blur(0); transform: none; }
.m-tag.is-idea .m-tag-t { font-size: 14px; letter-spacing: -.005em; color: var(--ink); }
/* une app : le filet monte, son nom au-dessus de son logo, centré */
.m-tag.is-app { pointer-events: auto; }
.m-tag.is-app .m-tag-lead { left: 0; top: auto; bottom: 7px; width: 1px; height: 22px; transform: scaleY(0); transform-origin: 50% 100%; }
.m-tag.is-app.is-in .m-tag-lead { transform: scaleY(1); }
.m-tag.is-app .m-tag-t { left: 0; top: -36px; translate: -50% -100%; display: flex; align-items: baseline; gap: 10px; font-size: 17px; letter-spacing: -.015em; }
.m-tag.is-app .m-tag-t::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -4px; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: 0 50%; transition: transform .5s var(--ease);
}

/* le rail des chapitres, à droite : la jauge en or, un trait par chapitre */
.m-rail {
  position: absolute; right: max(10px, calc(var(--m-right) * .45)); top: 50%; width: 26px; height: min(46vh, 380px);
  translate: 0 -50%; pointer-events: auto; transition: opacity .6s var(--ease);
}
.m-rail-line { position: absolute; right: 12px; top: 0; bottom: 0; width: 1px; overflow: hidden; background: var(--line2); }
.m-rail-fill {
  position: absolute; inset: 0; background: var(--gold); box-shadow: 0 0 8px rgba(205, 178, 122, .5);
  transform-origin: 50% 0; transform: scaleY(var(--p, 0));
}
.m-tick { position: absolute; right: 0; top: var(--y); width: 26px; height: 18px; margin-top: -9px; padding: 0; border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.m-tick::before {
  content: ''; position: absolute; top: 50%; right: 9px; width: 7px; height: 1px; background: var(--ink4);
  transition: width .4s var(--ease), right .4s var(--ease), background-color .4s var(--ease);
}
.m-tick.is-on::before { right: 6px; width: 13px; background: var(--gold); }
.m-tick-t {
  position: absolute; right: 32px; top: 50%; translate: 6px -50%;
  font-family: var(--mono); font-size: 10px; letter-spacing: .22em; text-transform: uppercase; line-height: 1;
  color: var(--ink3); white-space: nowrap; opacity: 0; transition: opacity .3s var(--ease), translate .3s var(--ease), color .3s var(--ease);
}
.m-tick.is-on .m-tick-t, .m-tick:focus-visible .m-tick-t { opacity: 1; translate: 0 -50%; }
.m-tick:focus-visible { outline-offset: 2px; }
.mission.is-gated .m-rail, .mission.is-end .m-rail { opacity: 0; pointer-events: none; }

/* « Défiler », au bas, au milieu, tant qu'on n'a pas bougé */
.m-cue {
  position: absolute; left: 50%; bottom: max(clamp(26px, 5vh, 56px), env(safe-area-inset-bottom)); translate: -50% 0;
  display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 0; padding-left: .22em;
  transition: opacity .6s var(--ease);
}
.m-cue-line { position: relative; display: block; width: 1px; height: 34px; overflow: hidden; background: var(--line2); }
.m-cue-line::after { content: ''; position: absolute; inset: 0; background: var(--gold); transform-origin: 50% 0; animation: cue-grow 2.6s var(--ease) infinite; }
.mission.is-gated .m-cue, .mission.is-moved .m-cue { opacity: 0; }

/* le rideau : un mot, un filet qui se remplit, un pourcentage — puis le
   choix du son, deux mots qui se cliquent */
.m-load {
  position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; justify-items: center; gap: 14px;
  background: rgba(3, 4, 7, .9); pointer-events: auto;
  transition: opacity 1.1s var(--ease), visibility 0s linear 1.1s;
}
.mission.is-entered .m-load { opacity: 0; visibility: hidden; pointer-events: none; }
.m-load-word { margin: 0; color: var(--ink3); }
.m-load-line { width: 180px; }
.m-load-pct { margin: 0; color: var(--ink2); font-variant-numeric: tabular-nums; }
.m-load-line, .m-load-pct { transition: opacity .4s var(--ease); }
.mission.is-ready .m-load-line, .mission.is-ready .m-load-pct { opacity: 0; }
.m-gate { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 34px; margin-top: -26px; animation: u-rise .7s var(--ease) both; }
.m-enter { font-size: 16px; }
/* le premier : le choix d'avant, au plein, souligné */
.m-enter:first-child { --u: 1; color: var(--ink); }

/* le pied de page : il monte par-dessus la scène, un filet d'or s'y trace */
.m-end {
  position: relative; display: flex; align-items: flex-end; min-height: 74vh; min-height: 74svh;
  padding: 0 var(--m-right) max(clamp(26px, 5vh, 56px), env(safe-area-inset-bottom)) var(--m-left);
  background: linear-gradient(to bottom, rgba(3, 4, 7, 0), rgba(3, 4, 7, .88) 34%, rgba(3, 4, 7, .97));
  pointer-events: auto;
}
.m-end-in { position: relative; flex: 1; padding-top: 30px; }
.m-end-in::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, var(--gold), rgba(205, 178, 122, .3) 60%, transparent);
  transform: scaleX(0); transform-origin: 0 50%; transition: transform 1.4s var(--ease);
}
.mission.is-end .m-end-in::before { transform: scaleX(1); }
.m-end-lead { display: flex; align-items: center; gap: 12px; margin: 0 0 24px; color: var(--ink3); }
.m-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 26px clamp(20px, 3vw, 48px); }
.m-steps li {
  display: grid; align-content: start; gap: 8px; padding-top: 16px; border-top: 1px solid var(--line2);
  opacity: 0; transform: translateY(10px); transition: opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay: calc(.1s + var(--n) * 90ms);
}
.mission.is-end .m-steps li { opacity: 1; transform: none; }
.m-step-t { font-size: 19px; line-height: 1.25; letter-spacing: -.02em; }
.m-step-d { font-size: 14px; line-height: 1.55; color: var(--ink2); text-wrap: pretty; }
.m-end-go { display: flex; flex-wrap: wrap; gap: 12px 40px; margin-top: clamp(36px, 7vh, 64px); }
.m-go { gap: 12px; font-size: clamp(18px, 1.6vw, 24px); color: var(--ink); }
.m-end-base {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: clamp(28px, 5vh, 48px); padding-top: 14px; border-top: 1px solid var(--line); color: var(--ink3);
}
.m-again { font-size: 13px; }

@media (hover: hover) {
  .m-tick:hover .m-tick-t { opacity: 1; translate: 0 -50%; color: var(--ink); }
  .m-tick:hover::before { width: 13px; right: 6px; }
  .m-tag.is-app:hover .m-tag-t::after { transform: scaleX(1); }
}
/* un écran étroit : le rail se tait (le compteur dit où l'on est), les
   chiffres et les étapes passent sur deux colonnes */
@media (max-width: 720px) {
  .mission { --edge: 18px; --m-left: max(18px, env(safe-area-inset-left)); --m-right: max(18px, env(safe-area-inset-right)); }
  .m-rail, .m-cue { display: none; }
  .m-txt { width: calc(100% - var(--m-left) - var(--m-right)); }
  .m-h1 { font-size: clamp(26px, 7.6vw, 38px); }
  .m-title { font-size: clamp(32px, 9.6vw, 48px); }
  .m-fin-title { font-size: clamp(30px, 8.8vw, 44px); }
  .m-line { font-size: 16px; }
  .m-stats { gap: 14px 28px; margin-top: 22px; }
  .m-stats dt { font-size: 26px; }
  .m-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .m-tag.is-idea .m-tag-t { font-size: 13px; }
}
@media (max-width: 480px) {
  .m-count { display: none; }
}

/* ── Le portfolio : les aperçus de la liste ─────────────────────────
   Dans la liste de l'atelier (plus bas, « L'atelier »), chaque site porte un
   aperçu fait de ses propres calques : au survol de sa ligne (.site), il
   joue en deux secondes ce que le site fait en entier. Au doigt, il joue seul. */

.shot {
  --t: 1.1s;
  position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden;
  container-type: inline-size; isolation: isolate;
}
.shot img { position: absolute; display: block; pointer-events: none; user-select: none; -webkit-user-drag: none; }

/* Casa Bruma : le ciel, le titre, deux rideaux de palmes qui s'écartent sur l'hôtel. */
.shot-casa { background: #1A120C; }
.sc-sky, .sc-hotel, .sc-rg, .sc-rd { inset: 0; width: 100%; height: 100%; object-fit: cover; }
.sc-sky { transform: scale(1.04); transition: transform 1.6s var(--ease); }
.sc-hotel {
  object-position: 50% 70%; opacity: 0; transform: translateY(9%) scale(1.07); transform-origin: 50% 72%;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .35) 18%, #000 40%);
  mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .35) 18%, #000 40%);
  transition: opacity .8s ease .1s, transform var(--t) var(--ease);
}
.sc-title {
  /* au-dessus des rideaux, comme dans le site (titre z 6, rideaux z 5) */
  position: absolute; left: 0; right: 0; top: 17%; z-index: 3; text-align: center; white-space: nowrap;
  font-family: 'Newsreader', Georgia, serif; font-weight: 500; font-size: 11cqw; line-height: .8; letter-spacing: .01em;
  color: #FDF1E1; text-shadow: 0 2px 3px rgba(30, 12, 2, .45), 0 10px 34px rgba(30, 12, 2, .4);
  transition: opacity .6s ease, transform var(--t) var(--ease);
}
.sc-rg, .sc-rd { z-index: 2; transition: transform var(--t) var(--ease); }
.sc-rg { transform-origin: 0% 60%; }
.sc-rd { transform-origin: 100% 60%; }
.site:is(:hover, :focus-visible) .sc-sky { transform: scale(1.08) translateY(-2%); }
.site:is(:hover, :focus-visible) .sc-hotel { opacity: 1; transform: none; }
.site:is(:hover, :focus-visible) .sc-title { opacity: 0; transform: translateY(-40%); }
.site:is(:hover, :focus-visible) .sc-rg { transform: translateX(-36%) scale(1.08); }
.site:is(:hover, :focus-visible) .sc-rd { transform: translateX(36%) scale(1.08); }

/* Le voyage : le ciel du coucher de soleil, la malle qui s'ouvre sur le monde. */
.shot-voyage {
  background:
    radial-gradient(34% 30% at 50% 0%, rgba(255, 238, 206, .7), rgba(255, 206, 150, 0) 70%),
    linear-gradient(180deg, #f5ad73 0%, #f9d3a3 28%, #f6e2c6 33%, #d6ebdd 39%, #8fdcd8 49%, #5bcbcd 59%, #2fb4c0 72%, #1892a3 86%, #0f6273 100%);
}
.sv-title {
  position: absolute; left: 6%; top: 7%; z-index: 1; white-space: pre-line;
  font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; font-size: 7.4cqw; line-height: .96; letter-spacing: -.018em;
  color: #2C1A0C;
}
.sv-closed, .sv-open {
  left: 14%; bottom: 1%; width: 72%; height: auto;
  filter: drop-shadow(0 14px 16px rgba(4, 16, 20, .45));
  transition: opacity .5s ease, transform .7s cubic-bezier(.3, .7, .2, 1);
}
.sv-open { opacity: 0; transform: scale(.965); }
.sv-dot {
  position: absolute; left: 42%; top: 64%; z-index: 1; width: 2.6cqw; height: 2.6cqw; margin: -1.3cqw 0 0 -1.3cqw;
  border-radius: 50%; background: #F0B35B; box-shadow: 0 1px 6px rgba(3, 14, 18, .45);
  transition: opacity .35s ease;
}
.sv-dot::before {
  content: ''; position: absolute; inset: -80%; border-radius: 50%; background: #F0B35B;
  animation: sv-pulse 3s ease-in-out infinite;
}
@keyframes sv-pulse { 0%, 100% { opacity: .22; transform: scale(1); } 50% { opacity: .55; transform: scale(1.4); } }
.site:is(:hover, :focus-visible) .sv-closed { opacity: 0; transform: scale(.965); }
.site:is(:hover, :focus-visible) .sv-open { opacity: 1; transform: none; }
.site:is(:hover, :focus-visible) .sv-dot { opacity: 0; }

/* Citadisima : le ciel de son accueil (ses dégradés, tels quels), l'aube
   qui passe au jour, et son soleil d'or qui se lève en tournant sur ses
   rayons. Le titre, ses deux lignes, passe du crème à l'encre avec le jour. */
.shot-cita { background: linear-gradient(180deg, #1c1a32 0%, #4c2f35 34%, #95552a 64%, #c98f31 86%, #e7be6e 100%); }
.sk-day {
  position: absolute; inset: 0; opacity: 0;
  background: linear-gradient(180deg, #eff1ea 0%, #f4efe3 40%, #f9e7ca 72%, #f3d8a8 100%);
  transition: opacity var(--t) ease;
}
.sk-glow, .sk-sun { position: absolute; left: 50%; top: 66%; translate: -50% -50%; aspect-ratio: 1; }
.sk-glow {
  width: 92cqw; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 216, 146, .62), rgba(255, 216, 146, .14) 55%, rgba(255, 216, 146, 0));
  transform: translateY(14cqw) scale(.62); transition: transform var(--t) var(--ease);
}
.sk-sun { width: 27cqw; transform: translateY(19cqw); transition: transform var(--t) var(--ease); }
.sk-sun svg {
  display: block; width: 100%; height: 100%; overflow: visible;
  fill: #C08A2C; stroke: #C08A2C; stroke-width: 2.04; stroke-linecap: round;
  filter: drop-shadow(0 .6cqw 1.4cqw rgba(120, 70, 10, .35));
}
.sk-sun g { transform-origin: 12px 12px; animation: sk-turn 7s linear infinite; }
@keyframes sk-turn { to { transform: rotate(30deg); } }
.sk-title {
  position: absolute; left: 6%; top: 8%; z-index: 1; white-space: pre-line;
  font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; font-size: 6.2cqw; line-height: 1; letter-spacing: -.015em;
  color: #F7EBD3; transition: color var(--t) ease;
}
.site:is(:hover, :focus-visible) .sk-day { opacity: 1; }
.site:is(:hover, :focus-visible) .sk-glow { transform: none; }
.site:is(:hover, :focus-visible) .sk-sun { transform: none; }
.site:is(:hover, :focus-visible) .sk-title { color: #1B1917; }

/* Ce site : la tête et, sous une loupe qui se promène (ou suit le curseur),
   le réseau calé comme sur l'accueil. */
@property --lx { syntax: '<percentage>'; inherits: true; initial-value: 62%; }
@property --ly { syntax: '<percentage>'; inherits: true; initial-value: 36%; }
/* la tête dans ses couleurs, sur le noir (media/tete-noire.webp), comme sur l'accueil */
.shot-ideo { background: #000; }
.si-head { inset: 0; width: 100%; height: 100%; object-fit: cover; }
.si-lens {
  position: absolute; display: block; aspect-ratio: 16 / 9;
  animation: si-roam 11s ease-in-out infinite;
  transition: --lx .35s var(--ease), --ly .35s var(--ease);
}
.si-net {
  inset: 0; width: 100%; height: 100%; filter: var(--revealfx);
  -webkit-mask-image: radial-gradient(19% 33.8% at var(--lx) var(--ly), #000 58%, transparent 100%);
  mask-image: radial-gradient(19% 33.8% at var(--lx) var(--ly), #000 58%, transparent 100%);
}
.si-ring {
  position: absolute; left: var(--lx); top: var(--ly); width: 30%; aspect-ratio: 1; translate: -50% -50%;
  border-radius: 50%; border: 1px solid color-mix(in oklab, var(--accent) 65%, transparent);
  box-shadow: 0 0 22px -6px var(--accent), inset 0 0 18px -8px var(--accent);
}
.shot-ideo.is-track .si-lens { animation: none; }
@keyframes si-roam {
  0%, 100% { --lx: 62%; --ly: 30%; }
  30% { --lx: 70%; --ly: 44%; }
  60% { --lx: 60%; --ly: 60%; }
  80% { --lx: 54%; --ly: 42%; }
}

/* Au doigt, pas de survol : les aperçus jouent seuls, aller-retour. */
@media (hover: none) {
  /* des propriétés détaillées, pas le raccourci : il remettrait animation-name à none */
  .shot-casa > *, .shot-voyage > img, .sv-dot {
    animation-duration: 8s; animation-timing-function: var(--ease);
    animation-iteration-count: infinite; animation-direction: alternate; transition: none;
  }
  .sc-sky { animation-name: sc-sky; }
  .sc-hotel { animation-name: sc-hotel; }
  .sc-title { animation-name: sc-title; }
  .sc-rg { animation-name: sc-rg; }
  .sc-rd { animation-name: sc-rd; }
  .sv-closed { animation-name: sv-closed; }
  .sv-open { animation-name: sv-open; }
  .sv-dot { animation-name: sv-closed; }
  .sk-day, .sk-glow, .sk-sun, .sk-title {
    animation-duration: 8s; animation-timing-function: var(--ease);
    animation-iteration-count: infinite; animation-direction: alternate; transition: none;
  }
  .sk-day { animation-name: sk-day; }
  .sk-glow { animation-name: sk-glow; }
  .sk-sun { animation-name: sk-sun; }
  .sk-title { animation-name: sk-title; }
}
@keyframes sk-day { 0%, 30% { opacity: 0; } 70%, 100% { opacity: 1; } }
@keyframes sk-glow { 0%, 30% { transform: translateY(14cqw) scale(.62); } 70%, 100% { transform: none; } }
@keyframes sk-sun { 0%, 30% { transform: translateY(19cqw); } 70%, 100% { transform: none; } }
@keyframes sk-title { 0%, 30% { color: #F7EBD3; } 70%, 100% { color: #1B1917; } }
@keyframes sc-sky { 0%, 30% { transform: scale(1.04); } 70%, 100% { transform: scale(1.08) translateY(-2%); } }
@keyframes sc-hotel { 0%, 30% { opacity: 0; transform: translateY(9%) scale(1.07); } 70%, 100% { opacity: 1; transform: none; } }
@keyframes sc-title { 0%, 30% { opacity: 1; transform: none; } 60%, 100% { opacity: 0; transform: translateY(-40%); } }
@keyframes sc-rg { 0%, 30% { transform: none; } 70%, 100% { transform: translateX(-36%) scale(1.08); } }
@keyframes sc-rd { 0%, 30% { transform: none; } 70%, 100% { transform: translateX(36%) scale(1.08); } }
@keyframes sv-closed { 0%, 40% { opacity: 1; transform: none; } 60%, 100% { opacity: 0; transform: scale(.965); } }
@keyframes sv-open { 0%, 40% { opacity: 0; transform: scale(.965); } 60%, 100% { opacity: 1; transform: none; } }

/* ── Les apps : la vitrine ──────────────────────────────────────────
   Un carrousel, comme une page produit : l'app au centre en grand, ses
   voisines plus petites et floues de part et d'autre, la dernière derrière
   elle — et tout passe ensemble quand on tourne : le fond, les places, les
   tailles, les flous, en 650 ms (main.js ne change que les rôles,
   data-role). Sobre : le fond est le ton de l'app au centre (APPS[].tone),
   une nuance de noir ou de graphite en thème sombre, de blanc cassé en
   clair, avec un halo plus clair derrière son logo — une lumière de studio
   sur la toile de fond — et un grain sur tout. La couleur vient des logos,
   chacun dans celles de son app. Derrière eux, le nom de l'app en très
   grand, ton sur ton. Le texte est celui du site : ses jetons, ses filets,
   l'or de ses numéros. */

/* déclarées : le fond et son halo passent en fondu d'une app à l'autre */
@property --app { syntax: '<color>'; inherits: true; initial-value: #161413; }
@property --app-halo { syntax: '<color>'; inherits: true; initial-value: #2B2724; }

.vitrine {
  --v-t: 650ms;
  --v-ease: cubic-bezier(.4, 0, .2, 1);
  /* les places. Le logo au centre : sa boîte carrée, posée au bas de
     l'écran (la même taille que V_SIZES, main.js, pour srcset). Ses
     voisines : décalées de --v-side, réduites à --v-small, et levées de
     --v-lift — juste au-dessus du texte du bas à gauche (≈ 340 px avec
     l'explication, à 8vh du bas), que la voisine de gauche ne doit jamais
     couvrir : noire sous une encre sombre, il ne se lirait plus. Celles de
     derrière : au milieu, plus petites, assez bas pour rester cachées
     derrière le logo du centre (plus haut, elles dépassaient de sa tête
     comme une fumée). --v-shift décale le tout vers la droite : la colonne
     du texte pèse à gauche, et le logo le plus large (le « + » lancé, 37 %
     de sa boîte de part et d'autre du milieu) n'y entre jamais — 4vw sur
     un grand écran, davantage quand l'écran se resserre (à 1024 px, 12vw). */
  --v-size: min(100vh, 58vw);
  --v-bottom: 1vh;
  --v-shift: max(4vw, calc(var(--gutter) + min(360px, 36vw) + 16px + .37 * var(--v-size) - 50vw));
  --v-side: 25vw; --v-small: .36;
  --v-lift: calc(350px + 7vh - .07 * var(--v-size));
  --v-back-lift: calc(var(--v-lift) * .5); --v-back: .27;
  --edge: var(--gutter);
  /* le nom du fond, l'ombre sous les logos, le grain : selon le thème (plus bas) */
  --v-ghost: rgba(255, 255, 255, .06);
  --v-shade: rgba(0, 0, 0, .62);
  --v-grain: .4;
  position: absolute; inset: 0; z-index: 40; overflow: hidden; isolation: isolate;
  color: var(--ink);
  background: radial-gradient(42% 48% at 50% 62%, var(--app-halo), transparent), var(--app);
  transition: --app var(--v-t) var(--v-ease), --app-halo var(--v-t) var(--v-ease);
  /* le fond s'ouvre en cercle depuis la région cliquée (main.js écrit --ox, --oy) */
  animation: v-open .75s var(--ease) backwards;
  /* un glissé horizontal du doigt est à la vitrine (main.js) */
  touch-action: pan-y pinch-zoom;
  -webkit-user-select: none; user-select: none;
}
@keyframes v-open {
  from { clip-path: circle(0% at var(--ox, 50%) var(--oy, 50%)); }
  to { clip-path: circle(150% at var(--ox, 50%) var(--oy, 50%)); }
}
/* en clair : le nom est plus blanc que le blanc cassé (un papier gaufré),
   l'ombre est une encre légère, le grain se fait discret */
html.light .vitrine { --v-ghost: rgba(255, 255, 255, .9); --v-shade: rgba(11, 13, 18, .2); --v-grain: .26; }
/* le grain, sur tout sauf le texte */
.vitrine::after {
  content: ''; position: absolute; inset: 0; z-index: 50; pointer-events: none; opacity: var(--v-grain);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)' opacity='.08'/%3E%3C/svg%3E") 0 0 / 200px 200px repeat;
}

.v-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 60; }
.v-bar > .u-mark { animation: u-drop .9s var(--ease) .25s backwards; }
.v-bar > .u-close { pointer-events: auto; animation: u-fade .6s var(--ease) .3s backwards; }

/* Les noms du fond : un par app, l'un sur l'autre, centrés dans une bande
   (12 % → 56 % de la hauteur) ; main.js donne à chacun la taille qui tient
   la largeur sans passer la bande (--ghost). Celui de l'app au centre paraît
   (.is-on), l'ancien s'efface, en même temps que le reste. La souris décale
   un peu la bande à l'opposé des logos (--mx, --my, de −1 à 1). */
.v-ghost {
  position: absolute; left: 0; right: 0; top: 12%; height: 44vh; z-index: 2; margin: 0;
  display: grid; place-items: center; pointer-events: none;
  font-weight: 500; line-height: 1; letter-spacing: -.04em;
  text-transform: uppercase; white-space: nowrap; color: var(--v-ghost);
  translate: calc(var(--mx, 0) * -14px) calc(var(--my, 0) * -8px);
  transition: translate 1.2s var(--ease);
}
.v-word {
  grid-area: 1 / 1; font-size: var(--ghost, 20vw);
  opacity: 0; transform: translateY(3%);
  transition: opacity var(--v-t) var(--v-ease), transform var(--v-t) var(--v-ease);
  animation: v-ghost 1.1s var(--ease) .15s backwards;
}
.v-word.is-on { opacity: 1; transform: none; }
@keyframes v-ghost { from { opacity: 0; transform: translateY(6%); } }

/* Les logos. Chacun dans une boîte carrée, la même pour tous ; son rôle le
   déplace, le réduit (autour de son bas, --base), le floute et l'efface.
   Le flou est compté avant la réduction : 5,5 px à .36, c'est 2 px à l'écran. */
.v-stage { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
.v-item {
  --tx: 0px; --ty: 0px; --s: 1; --blur: 0px; --o: 1; --d: 0px;
  position: absolute; left: calc(50% + var(--v-shift)); bottom: var(--v-bottom); z-index: var(--z, 1);
  width: var(--v-size); aspect-ratio: 1; margin-left: calc(var(--v-size) / -2);
  transform-origin: 50% var(--base, 93%);
  transform: translate3d(var(--tx), var(--ty), 0) scale(var(--s));
  translate: calc(var(--mx, 0) * var(--d)) calc(var(--my, 0) * var(--d) * .5);
  filter: blur(var(--blur)); opacity: var(--o);
  transition:
    transform var(--v-t) var(--v-ease), filter var(--v-t) var(--v-ease),
    opacity var(--v-t) var(--v-ease), translate 1.2s var(--ease);
  will-change: transform, filter, opacity;
}
.v-item[data-role="center"] { --z: 20; --d: 16px; }
.v-item[data-role="left"], .v-item[data-role="right"] {
  --ty: calc(-1 * var(--v-lift)); --s: var(--v-small); --blur: 5.5px; --o: .85; --z: 10; --d: 7px;
}
.v-item[data-role="left"] { --tx: calc(-1 * var(--v-side)); }
.v-item[data-role="right"] { --tx: var(--v-side); }
.v-item[data-role="back"] { --ty: calc(-1 * var(--v-back-lift)); --s: var(--v-back); --blur: 15px; --o: .8; --z: 5; --d: 3px; }

/* Chaque logo flotte, à son rythme (--i), au-dessus de son ombre — qui se
   resserre quand il monte. Il arrive en montant ; lui et son ombre
   apparaissent en fondu quand son image est là (main.js, .is-in). */
.v-shadow {
  position: absolute; left: 50%; top: var(--base, 93%);
  width: calc(var(--sw, .6) * 100%); height: 9%; translate: -50% -50%; border-radius: 50%;
  background: radial-gradient(closest-side, var(--v-shade), transparent);
  opacity: 0; transition: opacity .6s var(--ease);
  /* elle arrive avec son logo (v-rise), puis respire avec lui */
  animation: v-shadow-in 1s var(--ease) backwards, v-shadow 6.5s ease-in-out infinite;
  animation-delay: calc(.18s + var(--i, 0) * 70ms), calc(var(--i, 0) * -1.6s);
}
.v-float {
  position: absolute; inset: 0; transform-origin: 50% var(--base, 93%);
  animation: v-rise 1s var(--ease) backwards, v-float 6.5s ease-in-out infinite;
  animation-delay: calc(.18s + var(--i, 0) * 70ms), calc(var(--i, 0) * -1.6s);
  transition: scale .6s var(--ease);
}
.v-img {
  display: block; width: 100%; height: 100%; object-fit: contain;
  opacity: 0; transition: opacity .6s var(--ease);
  pointer-events: none; -webkit-user-drag: none;
}
.v-item.is-in .v-img, .v-item.is-in .v-shadow { opacity: 1; }
@keyframes v-rise { from { opacity: 0; translate: 0 10%; } }
@keyframes v-float { 50% { transform: translateY(-1.8%); } }
@keyframes v-shadow { 50% { transform: scale(.86); } }
@keyframes v-shadow-in { from { opacity: 0; } }
/* ce qui est opaque se clique (main.js : au centre, l'app s'ouvre) ; le
   logo du centre grandit un peu quand on le vise, ou « Entrer » */
.v-hit { position: absolute; pointer-events: auto; cursor: pointer; -webkit-tap-highlight-color: transparent; -webkit-user-drag: none; }
@media (hover: hover) {
  .v-item[data-role="center"]:has(.v-hit:hover) .v-float,
  .vitrine:has(.v-go:hover) .v-item[data-role="center"] .v-float { scale: 1.035; }
}

/* En bas à gauche : le numéro, le nom (il se décode quand l'app change),
   le registre, la ligne ; puis les deux flèches, dans des cercles de
   filet, et le compte. À voix haute, une seule phrase (.v-sr). Sous les
   lettres, le halo du site, dans la couleur du fond : invisible sur lui, il
   garde le texte lisible quand un logo (noir) passe dessous. */
.v-say, .v-go { text-shadow: 0 0 1px var(--app), 0 0 12px var(--app), 0 0 22px var(--app); }
.v-say {
  position: absolute; z-index: 60;
  left: max(var(--edge), env(safe-area-inset-left), calc(50% - 780px));
  bottom: max(clamp(28px, 8vh, 84px), calc(env(safe-area-inset-bottom) + 18px));
  width: min(360px, 36vw);
}
.v-slide > * { animation: u-rise .9s var(--ease) backwards; }
.v-head { display: flex; align-items: baseline; gap: 14px; margin: 0; animation-delay: .3s; }
.v-name { font-size: clamp(28px, 2.6vw, 40px); font-weight: 300; line-height: 1.08; letter-spacing: -.035em; }
.v-tag { margin: 12px 0 0; animation-delay: .36s; }
/* ce qu'est l'app : un paragraphe, au ton secondaire ; dessous, ses repères */
.v-about {
  margin: 16px 0 0; max-width: 44ch; animation-delay: .42s;
  font-size: 14.5px; font-weight: 300; line-height: 1.6; color: var(--ink2); text-wrap: pretty;
}
.v-facts { margin: 14px 0 0; line-height: 1.7; animation-delay: .46s; }
.v-slide.is-swap .v-tag, .v-slide.is-swap .v-about, .v-slide.is-swap .v-facts { animation: v-swap .55s var(--ease) both; }
.v-slide.is-swap .v-about { animation-delay: .04s; }
.v-slide.is-swap .v-facts { animation-delay: .08s; }
@keyframes v-swap { from { opacity: 0; translate: 0 6px; } }
.v-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

.v-nav {
  display: flex; align-items: center; gap: 14px; margin-top: clamp(18px, 3.2vh, 30px);
  animation: u-rise .9s var(--ease) .48s backwards;
}
.v-arrow {
  position: relative; display: grid; place-items: center; flex: 0 0 auto;
  width: 60px; height: 60px; padding: 0; border-radius: 50%;
  border: 1px solid var(--line2); background: transparent; color: var(--ink); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform .15s ease, background-color .15s ease, border-color .45s var(--ease);
}
.v-arrow .u-arrow { width: 20px; margin: 0; }
.v-arrow .u-arrow::before, .v-arrow .u-arrow::after { width: 8px; }
.v-arrow.is-prev .u-arrow { rotate: 180deg; }
.v-arrow:focus-visible { border-color: var(--ink2); }
.v-arrow:active { transform: scale(.95); }
.v-count { margin-left: 10px; }

/* En bas à droite, en grand : « Entrer → ». Un filet se trace sous le mot
   quand on le vise, la flèche avance d'un rien. */
.v-go {
  position: absolute; z-index: 60;
  right: max(var(--edge), env(safe-area-inset-right), calc(50% - 780px));
  bottom: max(clamp(28px, 8vh, 84px), calc(env(safe-area-inset-bottom) + 18px));
  display: flex; align-items: center; gap: .28em;
  font-size: clamp(26px, 4vw, 60px); font-weight: 300; line-height: 1; letter-spacing: -.045em;
  color: var(--ink); -webkit-tap-highlight-color: transparent;
  animation: u-rise .9s var(--ease) .4s backwards;
}
.v-go:hover { color: var(--ink); }
.v-go-t { position: relative; padding-bottom: .06em; }
.v-go-t::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -.04em; height: 1px; background: currentColor;
  transform: scaleX(var(--u, 0)); transform-origin: 0 50%; transition: transform .5s var(--ease);
}
.v-go .u-arrow { width: .78em; height: 1.5px; margin: 0; transition: translate .5s var(--ease); }
.v-go .u-arrow::before, .v-go .u-arrow::after { width: .3em; height: 1.5px; }
.v-go:focus-visible { --u: 1; outline-offset: 10px; }

@media (hover: hover) {
  .v-arrow:hover { transform: scale(1.08); background-color: color-mix(in oklab, var(--ink) 7%, transparent); border-color: var(--ink2); }
  .v-go:hover { --u: 1; }
  .v-go:hover .u-arrow { translate: .12em 0; }
}

/* ── L'atelier : le portfolio web ───────────────────────────────────
   Toujours sombre : un studio presque noir où un bras industriel suspendu
   au plafond (atelier/atelier.js, en WebGL) va chercher une capsule sur le
   présentoir et l'apporte au centre. Autour de la scène, une page produit
   plutôt qu'un jeu : du texte et des filets, ni boîte, ni pastille, ni
   pictogramme. C'est d'ici que vient le système du site (« Le système »,
   plus haut) : la barre, les mots qui se cliquent, la croix et les filets
   de progression sont ses classes .u-*. Ne restent ici que ce qui n'est
   qu'à l'atelier : le chargement, la légende et l'état, les étiquettes des
   capsules, la liste. Tout se déplace sur la même courbe (--ease). */

.atelier {
  /* Les jetons du système, aux valeurs du studio : un blanc posé en
     transparence sur un noir plus profond que celui de l'accueil. L'atelier
     les porte lui-même, comme la vitrine : le thème clair de l'accueil n'y
     entre pas, et les .u-* s'y lisent comme partout. */
  --bg: #020203;
  --ink: #ECEEF2;
  --ink2: rgba(236, 238, 242, .56);  /* le ton secondaire */
  /* le ton des registres, du compteur, du son coupé : effacé, mais un texte
     qu'on doit lire — 4,7:1 sur le studio, le seuil AA (à .34 : 2,7:1) */
  --ink3: rgba(236, 238, 242, .5);
  --ink4: rgba(236, 238, 242, .34);  /* l'ornement seul (le « 02 » de la signature, muet) */
  --line: rgba(236, 238, 242, .07);
  --line2: rgba(236, 238, 242, .14); /* le filet */
  --gold: #CDB27A;                    /* l'or champagne, avec parcimonie */
  --sig: #4FD6E8;                     /* le focus du clavier, le point « vous y êtes » */
  --edge: var(--gutter);              /* la marge de l'accueil ; 18 px sur un téléphone */
  position: absolute; inset: 0; z-index: 40; overflow: hidden; background: var(--bg);
  animation: a-in .6s var(--ease) both;
  /* les aperçus de la liste prennent leur version sombre, quel que soit le thème */
  --revealfx: brightness(1.1) saturate(1.2);
  color: var(--ink); color-scheme: dark;
}
/* le focus n'y attend que le retour d'une étiquette (main.js) : pas de cadre */
.atelier:focus { outline: none; }

/* l'atelier descend un peu à l'ouverture : tout y vient du plafond */
@keyframes a-in { from { opacity: 0; transform: translateY(-10px); } }

/* Retour d'un site sur /#portfolio : l'atelier est peint avant même que
   main.js l'ouvre, pour que la page qu'on quitte remonte au-dessus de lui
   (index.html pose la classe, main.js la retire). Opaque et en place dès
   cette première image, sans son entrée : un fondu y laisserait voir
   l'accueil. is-still prend le relais quand main.js retire to-folio. */
html.to-folio #portfolio[hidden] { display: block !important; }
html.to-folio .atelier:not(.is-out), .atelier.is-still:not(.is-out) { animation: none; }

/* Ce site choisi : l'atelier remonte vers le bras, hors de l'écran, et
   découvre le cerveau (main.js ferme le dialogue à la fin, et le rend
   inerte d'ici là : ses boutons rétablissent pointer-events). */
@keyframes a-out { to { transform: translateY(-100%); } }
.atelier.is-out { animation: a-out .72s cubic-bezier(.55, 0, .75, .2) both; pointer-events: none; }

/* Le studio attend le moteur : le même noir que son fond, une boîte à
   lumière à peine devinée, un soupçon d'or au sol. Le canevas s'y fond une
   fois l'atelier prêt — et d'ici là ne prend aucun clic. */
.atelier-box {
  position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(56% 42% at 50% 30%, rgba(236, 238, 242, .045), transparent 72%),
    radial-gradient(80% 28% at 50% 102%, rgba(205, 178, 122, .04), transparent 70%),
    var(--bg);
}
.atelier-box > canvas { opacity: 0; transition: opacity .9s var(--ease); }
.atelier-box.is-ready > canvas { opacity: 1; }
.atelier-box:not(.is-ready) > canvas { pointer-events: none; }

/* La barre (.u-bar) : sans fond. À gauche la signature (.u-mark), à droite
   deux mots (.u-btn : « Liste », « Son ») et la croix (.u-close). Elle
   arrive juste après la scène, et ne capte le pointeur que sur ce qui se
   clique : le canevas reçoit le reste. Le son allumé garde un filet court
   sous son mot ; la liste ouverte, un filet plein (aria-pressed,
   aria-expanded). */
.a-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 20; }
.a-bar > .u-mark, .a-tools { animation: u-drop .9s var(--ease) .3s backwards; }
.a-tools { display: flex; align-items: center; gap: 28px; pointer-events: auto; animation-delay: .4s; }

/* Le chargement : au centre du studio vide, un mot et un filet qui se
   remplit (onProgress) ; ils s'effacent quand la scène apparaît. */
.a-load {
  position: absolute; z-index: 4; left: 50%; top: 50%; translate: -50% -50%;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  pointer-events: none; opacity: 0; transition: opacity .8s var(--ease);
}
.a-load.is-on { opacity: 1; transition-duration: .4s; }
.a-load-word {
  padding-left: .3em; /* l'interlettrage tombe aussi après la dernière lettre : on le rend à gauche */
  letter-spacing: .3em; color: var(--ink2);
}
/* Les deux jauges de l'atelier sont des .u-line (main.js écrit --k) : celle
   du chargement avance en douceur, et repart de zéro sans revenir en
   arrière sous les yeux (is-snap, le temps d'une image) ; celle du trajet,
   main.js la lisse lui-même à chaque image. */
.a-load-line::after { transition: transform .6s var(--ease); }
.a-load-line.is-snap::after, .a-run-line::after { transition: none; }

/* La légende et l'état du bras partagent une place, en bas de l'écran :
   l'une au repos, l'autre pendant le trajet d'une capsule. */
.a-say {
  position: absolute; z-index: 12; left: 50%; bottom: max(30px, calc(env(safe-area-inset-bottom) + 16px)); translate: -50% 0;
  display: grid; width: max-content; max-width: calc(100% - 2 * var(--edge));
  pointer-events: none; text-align: center;
}
.a-say > * { grid-area: 1 / 1; justify-self: center; align-self: end; }
/* la légende : une ligne (.u-caption) ; elle vient un peu après la scène,
   et revient quand tout est calme (main.js) */
.a-hint {
  line-height: 1.45; text-wrap: balance;
  opacity: 0; transform: translateY(4px);
  transition: opacity .5s var(--ease), transform .7s var(--ease);
}
.a-hint.is-on { opacity: 1; transform: none; transition-delay: .45s; }
/* l'état reste dans l'arbre d'accessibilité même vide (opacité, pas
   display) : sinon sa première annonce se perdrait */
.a-status { opacity: 0; transition: opacity .4s var(--ease); }
.a-status.is-on { opacity: 1; }
/* la phrase entière, pour les lecteurs d'écran ; l'écran n'en montre que l'essentiel */
.a-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* le nom (.u-title), la jauge, le mot (.u-meta) : deux colonnes égales
   autour de la jauge, pour qu'elle reste au centre quand « Ouverture »
   apparaît */
.a-run { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 18px; }
.a-run-line { width: 120px; }
.a-run-name { justify-self: end; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.a-run-word {
  justify-self: start; white-space: nowrap; letter-spacing: .24em; color: var(--ink2);
  opacity: 0; transition: opacity .5s var(--ease);
}
.a-status.has-word .a-run-word { opacity: 1; }
/* un site déjà en ligne ailleurs, sa capsule ouverte, quand le navigateur
   n'ouvre pas son onglet de lui-même : un lien prend la place de l'état */
.a-out { pointer-events: auto; gap: 10px; font-size: 15px; color: var(--ink); animation: u-rise .5s var(--ease) both; }
.a-out[hidden] { display: none; }

/* Les étiquettes : posées sous leur capsule (main.js les recale à chaque
   image sur le point que donne le moteur), jamais dessus. Ni boîte ni
   fond : le numéro en or, le nom, le registre. Viser une capsule trace un
   filet d'or sous son nom, depuis le milieu, et ses voisines passent au ton
   secondaire. Elles apparaissent l'une après l'autre avec la scène. */
.a-caps { position: absolute; inset: 0; z-index: 5; pointer-events: none; --cap-low: 66px; }
.a-caps:not(.is-live) { visibility: hidden; }
/* un fondu seul : transform les place à chaque image, translate les étage */
.a-caps.is-live .cap { animation: u-fade 1s var(--ease) backwards; animation-delay: calc(.3s + var(--i, 0) * 80ms); }
.cap {
  position: absolute; left: 0; top: 0;
  display: flex; flex-direction: column; align-items: center;
  width: max-content; max-width: var(--cap-w, 240px);
  margin: 0; padding: 4px 8px; border: 0; border-radius: 4px; background: none;
  font: inherit; line-height: 1.25; text-align: center; color: var(--ink);
  cursor: pointer; pointer-events: auto; -webkit-tap-highlight-color: transparent;
  transition: opacity .5s var(--ease);
  will-change: transform;
}
.cap:hover { color: var(--ink); }
/* l'étiquette visée passe devant ses voisines : sa ligne de description,
   plus large qu'elle, n'est pas recouverte par l'étiquette suivante */
.cap.is-on, .cap:focus-visible { z-index: 2; }
.cap-row { display: flex; align-items: baseline; justify-content: center; gap: 9px; max-width: 100%; }
.cap-num { flex: 0 0 auto; letter-spacing: .2em; }
.cap-name {
  position: relative; min-width: 0;
  font-size: 15px; font-weight: 300; letter-spacing: -.01em; color: var(--ink); text-wrap: balance;
  transition: color .5s var(--ease);
}
.cap-name::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -4px; height: 1px; background: var(--gold);
  transform: scaleX(0); transition: transform .4s var(--ease);
}
.cap.is-on .cap-name::after, .cap:focus-visible .cap-name::after { transform: none; }
.a-caps.has-on .cap:not(.is-on) .cap-name { color: var(--ink2); }
.cap-tag {
  max-width: 100%; margin-top: 7px; padding-left: .22em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 9.5px;
}
/* La ligne de description, au survol ou au focus : sous l'étiquette, pour
   ne rien cacher de la capsule ; --dx (main.js) la garde dans l'écran
   quand sa capsule est au bord. */
.cap-desc {
  position: absolute; left: 50%; top: calc(100% + 8px); translate: calc(-50% + var(--dx, 0px)) 0;
  width: max-content; max-width: 26ch;
  font-size: 12.5px; font-weight: 300; line-height: 1.45; color: var(--ink2); text-wrap: balance;
  opacity: 0; transform: translateY(-3px); pointer-events: none;
  transition: opacity .3s var(--ease), transform .5s var(--ease);
}
.cap.is-on .cap-desc, .cap:focus-visible .cap-desc { opacity: 1; transform: none; transition-delay: .1s; }
/* Deux capsules trop proches pour deux étiquettes côte à côte : une sur
   deux descend d'un rang (--cap-low, d'après leur hauteur), reliée à sa
   capsule par un fil. */
.cap.is-low { translate: 0 var(--cap-low); }
.cap.is-low::before {
  content: ''; position: absolute; bottom: 100%; left: 50%; width: 1px; height: calc(var(--cap-low) - 4px);
  background: linear-gradient(to top, var(--line2), transparent);
  pointer-events: none;
}
/* Deux rangs, descriptions montrées (is-stagger, main.js) : celle d'une
   étiquette du haut descend sur le rang du dessous, et plus aucun fond ne
   l'en sépare. Le temps de la lire, ce rang s'efface — sauf l'étiquette
   qui a le focus du clavier, qu'on ne cache jamais. */
.a-caps.is-stagger:has(.cap:not(.is-low):is(.is-on, :focus-visible)) .cap.is-low:not(.is-on, :focus-visible) { opacity: 0; }
/* Une capsule en route : les autres s'effacent, la sienne tout à fait —
   l'état, en bas, porte son nom — sauf l'étiquette qui a le focus. */
.a-caps.is-busy .cap { opacity: .32; }
.a-caps.is-busy .cap.is-picked { opacity: 0; }
.a-caps.is-busy .cap:focus-visible { opacity: .9; }

/* La liste des sites : un calque sombre par-dessus l'atelier, la barre
   au-dessus de lui (« Liste » et la croix ne disparaissent jamais). La
   page d'index du système (.u-sheet, .u-head, .u-h, .u-note, .u-list) ;
   l'aperçu vivant de chaque site dans un cadre de
   filet, comme un tirage sous passe-partout. */
.a-list { position: absolute; inset: 0; z-index: 15; background: #050506; animation: u-fade .45s var(--ease) both; }
.a-col { max-width: 920px; margin: 0 auto; }
.a-count { letter-spacing: .24em; }
.a-arm { margin-left: auto; }
.atelier .site {
  display: grid; grid-template-columns: 48px minmax(0, 1fr) 220px; column-gap: clamp(20px, 3.2vw, 48px);
  align-items: start; padding: clamp(24px, 3.6vh, 36px) 0; border-radius: 4px; color: var(--ink);
  transition: opacity .5s var(--ease);
}
.atelier a.site { cursor: pointer; }
.atelier a.site:hover { color: var(--ink); }
.atelier .site-num { padding-top: 6px; letter-spacing: .2em; }
.atelier .site-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.atelier .site-name { font-size: 17px; font-weight: 300; line-height: 1.3; letter-spacing: -.01em; color: var(--ink); }
.atelier .site-tag { margin-top: 8px; font-size: 9.5px; }
.atelier .site-desc { margin-top: 16px; max-width: 46ch; font-size: 13.5px; font-weight: 300; line-height: 1.6; color: var(--ink2); text-wrap: pretty; }
/* « Visiter → » : son filet se trace quand on survole la ligne */
.atelier .site-go {
  position: relative; margin-top: 20px; padding-bottom: 5px;
  font-size: 13.5px; font-weight: 300; letter-spacing: -.005em; color: var(--ink); white-space: nowrap;
}
.atelier .site-go::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: 0 50%; transition: transform .5s var(--ease);
}
.atelier a.site:is(:hover, :focus-visible) .site-go::after { transform: none; }
/* ce site-ci : on y est déjà — le point du signal, pas de lien */
.atelier .is-here .site-go { display: inline-flex; align-items: center; gap: 10px; color: var(--ink2); }
.atelier .is-here .site-go::after { content: none; }
.atelier .is-here .site-go::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--sig); box-shadow: 0 0 10px color-mix(in oklab, var(--sig) 55%, transparent);
}
.atelier .site-win { display: block; padding: 5px; border: 1px solid var(--line2); transition: border-color .5s var(--ease); }
.atelier a.site:is(:hover, :focus-visible) .site-win { border-color: color-mix(in oklab, var(--ink) 30%, transparent); }
/* une ligne survolée : les autres s'effacent d'un ton */
@media (hover: hover) {
  .atelier .sites:has(a.site:hover) .site:not(:hover) { opacity: .45; }
}

/* ── Vers une application ou un site ────────────────────────────────
   Une app : le voile part de l'élément cliqué, inonde l'écran de sa
   couleur et pose son nom. La page d'arrivée (tools/arrive.html) reprend
   le même voile et le lève : les deux moitiés forment un seul mouvement.
   Un site du portfolio, lui, descend du haut (plus bas). */

.veil {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: var(--veil); color: var(--on-veil);
  /* rayon en % des deux côtés : l'interpolation px↔% n'est pas garantie */
  clip-path: circle(0% at var(--x) var(--y));
  transition: clip-path .62s var(--ease);
  will-change: clip-path;
}
.veil.is-open { clip-path: circle(150% at var(--x) var(--y)); }
/* Le nom, et dessous « Ouverture » en petites capitales. Le mot est hors du
   flux : le nom reste exactement là où tools/arrive.html le repeint à
   l'arrivée (même corps, même interlettrage, même interligne) — sans quoi
   il sauterait au changement de page. */
.veil-in { position: relative; display: block; }
.veil-name {
  display: block;
  font-family: var(--sans); font-size: clamp(40px, 7vw, 104px); font-weight: 300; line-height: 1; letter-spacing: -.05em;
  opacity: 0; transform: translateY(-8px);
  transition: opacity .42s var(--ease) .16s, transform .7s var(--ease) .12s;
}
.veil-word {
  position: absolute; left: 50%; top: 100%; translate: -50% 0; margin-top: clamp(16px, 2.2vw, 28px);
  padding-left: .24em; white-space: nowrap;
  font-family: var(--mono); font-size: 10px; letter-spacing: .24em; text-transform: uppercase;
  color: color-mix(in oklab, var(--on-veil) 66%, transparent);
  opacity: 0; transition: opacity .5s var(--ease) .3s;
}
.veil.is-open .veil-name { opacity: 1; transform: none; }
.veil.is-open .veil-word { opacity: 1; }

/* Un site, sans transition entre documents : le voile tombe du haut, d'où
   vient le bras de l'atelier, avec la couleur de la première image du site. */
.veil.is-drop { clip-path: inset(0 0 100% 0); transition: clip-path .7s var(--ease); }
.veil.is-drop.is-open { clip-path: inset(0 0 0 0); }

/* Un site, avec transition entre documents : c'est la page d'arrivée qui
   descend du haut (casa.css, voyage.css). Ici, le retour : la page du site
   remonte hors de l'écran, vers le bras, et découvre l'accueil — l'atelier,
   quand on revient sur /#portfolio. Les deux pages doivent y adhérer : les
   apps (/pncr/…) ne le font pas, leur navigation n'est pas touchée.
   mix-blend-mode: normal — par défaut les deux images se mêlent en
   plus-lighter (fait pour un fondu), et celle qui glisse éclaircirait l'autre. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root) { z-index: 1; animation: ig-rise .6s cubic-bezier(.55, 0, .75, .2) both; mix-blend-mode: normal; }
  ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
}
@keyframes ig-rise { to { transform: translateY(-100%); } }

/* ── Sans JavaScript ────────────────────────────────────────────────── */

/* Le même langage, en une colonne : la signature, la phrase sur deux lignes,
   puis deux index éditoriaux (numéro en or, nom, ligne), séparés par des
   filets. Sans script, le thème est celui écrit dans <html> : le sombre. */
.nojs { max-width: 62ch; margin: 0 auto; padding: clamp(40px, 8vh, 88px) max(24px, env(safe-area-inset-left)); }
.nojs > .u-brand { margin: 0; }
.nojs h1 { margin: 28px 0 0; font-size: clamp(32px, 6vw, 56px); font-weight: 300; line-height: 1.04; letter-spacing: -.045em; }
.nojs h1 span { display: block; color: var(--ink2); }
.nojs-lede { margin: 24px 0 0; font-size: 16px; line-height: 1.65; color: var(--ink2); text-wrap: pretty; }
.nojs h2 { display: flex; align-items: baseline; gap: 12px; margin: 48px 0 0; font-weight: 400; }
.nojs ul { list-style: none; margin: 16px 0 0; padding: 0; border-top: 1px solid var(--line2); }
.nojs li { border-bottom: 1px solid var(--line2); }
.nojs a { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 6px 14px; align-items: baseline; padding: 16px 0; color: var(--ink); }
.nojs a:hover { color: var(--ink); }
.nojs-name { font-size: 17px; letter-spacing: -.01em; }
.nojs a .u-caption { grid-column: 2; }

/* ── Largeurs ───────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .foot-year, .foot-corner { display: none; }
}
/* L'en-tête se resserre : sous 800 px, chaque région ne garde que son
   numéro — le mot reste lu, il n'est que masqué —, et le filet de la région
   allumée passe sous le numéro. */
@media (max-width: 800px) {
  .nav { gap: 2px; }
  .nav-a { padding: 10px 6px; }
  .nav-a::after { content: ''; left: 6px; right: 6px; bottom: 5px; }
  .nav-t { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}
/* Sur un téléphone, l'accueil garde tout, en plus serré : la signature, les
   trois numéros, la langue, le thème, et la flèche du cerveau (son mot
   reste lu). En bas, « ↑ Retour » seul : les noms des régions
   sont déjà écrits sous leurs nœuds. Un résultat passe son registre sous
   son nom. */
@media (max-width: 620px) {
  .top-in { gap: 12px; }
  .top-brand { letter-spacing: .22em; }
  .nav-a { padding-inline: 5px; }
  .nav-a::after { left: 5px; right: 5px; }
  .tools { gap: 14px; }
  .cta { padding-inline: 4px; }
  .cta > [data-i18n] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .foot-cue-text { display: none; }
  .result { grid-template-columns: 26px minmax(0, 1fr); column-gap: 12px; }
  .result-tag { grid-column: 2; max-width: 100%; margin-top: 5px; }
}
/* Sous 380 px, la flèche du cerveau cède sa place : le geste du doigt et
   « Défiler » font la même chose (main.js replie le focus sur la
   signature). Mesuré aux chasses des polices, en français (« Sombre ») :
   avec elle, la ligne demande 332 px — elle ne tient qu'à partir de 375. */
@media (max-width: 379px) {
  .cta { display: none; }
}
@media (max-width: 359px) {
  /* la signature de notre mission ne garde que son numéro et son titre */
  .m-bar > .u-mark > .u-brand, .m-bar > .u-mark > .u-rule { display: none; }
}
/* Au plus étroit (320 px), la ligne demande encore 302 px pour 284 : les
   écarts se resserrent et la signature s'espace un peu moins — rien ne se
   cache, et d'un numéro à l'autre il reste plus de 24 px sous le doigt. */
@media (max-width: 339px) {
  .top-in { gap: 8px; }
  .top-brand { letter-spacing: .16em; }
  .nav-a { padding-inline: 4px; }
  .nav-a::after { left: 4px; right: 4px; }
  .tools { gap: 10px; }
  .lang { gap: 5px; }
}
@media (max-width: 620px) {
  /* la liste de l'atelier : l'aperçu passe sous le texte */
  .atelier .site { grid-template-columns: 30px minmax(0, 1fr); column-gap: 14px; }
  .atelier .site-win { grid-column: 2; width: 100%; max-width: 360px; margin-top: 22px; }
}
/* Sur un téléphone — debout, ou couché (écran bas) —, l'atelier se resserre :
   la barre à 18 px des bords, « Portfolio » seul à gauche ; une étiquette
   de capsule se réduit au numéro et au nom (sur deux lignes au besoin) :
   elles restent lisibles sans se chevaucher ni sortir par le bas (main.js
   les borne à l'écart entre deux capsules, fixe --cap-low et remonte le
   rang s'il le faut, avec les mêmes bornes). */
@media (max-width: 620px), (max-height: 480px) {
  .atelier { --edge: 18px; }
  .a-bar { padding-top: max(18px, env(safe-area-inset-top)); gap: 16px; }
  .a-bar .u-brand, .a-bar .u-rule, .a-bar .u-num { display: none; }
  .a-tools { gap: 20px; }
  .a-tools .u-close { width: 32px; height: 32px; }
  .a-say { bottom: max(22px, calc(env(safe-area-inset-bottom) + 12px)); }
  .a-hint { font-size: 13px; }
  /* L'état : le nom passe au-dessus du filet, sur toute la largeur. Sur
     une ligne, les deux colonnes égales qui centrent le filet ne lui en
     laissaient que la moitié (« Le voyage, de l'intérieur » se coupait à
     390 px). Le filet reste au centre ; le mot se pose à sa droite, hors
     du flux : l'état ne gagne que la hauteur du nom, et reste sous le
     plancher des étiquettes (main.js, floor). */
  .a-run { position: relative; grid-template-areas: 'name name name' '. line .'; gap: 8px 12px; line-height: 1.2; }
  .a-run-name { grid-area: name; justify-self: center; font-size: 14px; }
  .a-run-line { grid-area: line; width: 72px; }
  .a-run-word { position: absolute; left: calc(50% + 48px); top: calc(100% - .5px); translate: 0 -50%; }
  /* la page d'index des sites remonte sous la barre, plus basse */
  .u-sheet {
    padding-top: 84px;
    -webkit-mask-image: linear-gradient(to bottom, transparent 50px, #000 80px);
    mask-image: linear-gradient(to bottom, transparent 50px, #000 80px);
    scroll-padding-top: 88px;
  }
  .cap { padding: 3px 6px; }
  .cap-tag, .cap-desc { display: none; }
  .cap-name { font-size: 14px; }
}
/* Un écran bas (un téléphone couché) : le présentoir touche presque le bas
   de l'image, la légende monte dans la ligne de la barre, entre le titre et
   les boutons (main.js laisse alors le bas aux étiquettes). */
@media (max-height: 480px) {
  .a-say { top: max(18px, env(safe-area-inset-top)); bottom: auto; min-height: 32px; max-width: 44vw; }
  .a-say > * { align-self: center; }
}

/* La vitrine sur un téléphone, debout : la barre à 18 px des bords, « Les
   apps » seul à gauche ; le logo monte au-dessus du texte, ses voisines se
   serrent contre lui ; le texte garde le nom, le registre et les flèches
   (la ligne se tait, comme dans l'exemple), « Entrer → » à leur hauteur.
   Toute fenêtre plus haute que large (une tablette debout) prend la même
   disposition : côte à côte, le logo passerait sous le texte. Couché (un
   écran bas), le logo rapetisse et le texte se resserre. */
@media (max-width: 620px), (max-height: 480px) {
  .vitrine { --edge: 18px; }
  .v-bar { padding-top: max(18px, env(safe-area-inset-top)); gap: 16px; }
  .v-bar .u-brand, .v-bar .u-rule, .v-bar .u-num { display: none; }
  .v-bar .u-close { width: 32px; height: 32px; }
  .v-about { -webkit-line-clamp: 5; line-clamp: 5; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; font-size: 13.5px; margin-top: 12px; }
  .v-facts { display: none; }
  .v-arrow { width: 48px; height: 48px; }
}
@media (max-width: 639px), (max-aspect-ratio: 9/10) {
  /* le logo se pose au-dessus du texte : 8vh sous lui, et sa hauteur (≈ 310 px
     sur une tablette, l'explication entière ; 230 sur un téléphone, plus bas) */
  .vitrine { --v-size: min(108vw, 50vh); --v-bottom: calc(8vh + 324px); --v-shift: 0px; --v-side: 35vw; --v-lift: 5vh; --v-small: .42; --v-back: .32; }
  .v-ghost { top: 12%; height: 24vh; }
  /* le texte prend toute la largeur : « Entrer → » ne partage que la ligne
     des flèches (il passe devant elle, plus loin dans la page) */
  .v-say { width: auto; right: var(--edge); }
  .v-about { max-width: none; }
  .v-go { font-size: 28px; bottom: max(clamp(28px, 8vh, 84px), calc(env(safe-area-inset-bottom) + 18px)); }
}
@media (max-width: 620px) {
  .vitrine { --v-bottom: calc(8vh + 256px); }
}
/* un écran de bureau bas (un portable, 800 px et moins) : l'explication
   tient en trois lignes, les repères se taisent — les voisines montent moins */
@media (max-height: 820px) and (min-width: 640px) and (min-aspect-ratio: 9/10) {
  .vitrine { --v-lift: calc(290px + 7vh - .07 * var(--v-size)); }
  .v-about { -webkit-line-clamp: 3; line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
  .v-facts { display: none; }
}
@media (max-height: 480px) and (min-width: 640px) {
  .vitrine { --v-size: min(104vh, 44vw); --v-bottom: 0px; --v-side: 26vw; --v-lift: 32vh; }
  .v-ghost { top: 12%; height: 46vh; }
  .v-say { width: 250px; }
  .v-about { -webkit-line-clamp: 2; line-clamp: 2; }
  .v-name { font-size: 24px; }
  .v-tag { margin-top: 8px; }
  .v-nav { margin-top: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .accroche { transition: none; }
  * { animation: none !important; }
  .veil, .veil-name, .veil-word { transition-duration: .001ms !important; }
  /* L'accueil : des fondus seulement — un filet (sous un mot, sous le
     champ, sous un résultat) apparaît d'un coup, rien ne glisse. Le point
     vivant ne respire plus, le filet de « Défiler » reste tracé. */
  .top *, .top *::after, .hero *, .hero *::before, .hero *::after,
  .foot *, .foot *::after, .cue, .cue *, .cue *::after {
    transition-property: opacity, color !important; transition-duration: .2s !important; transition-delay: 0s !important;
  }
  /* L'atelier, la vitrine, notre mission : de brefs fondus, rien ne glisse ni
     ne se trace — un filet apparaît d'un coup, un aperçu de la liste change
     sans zoomer. Les jauges suivent toujours leur valeur (main.js). Dans la
     vitrine, les logos changent de place d'un coup, et rien ne suit la souris. */
  .atelier *, .atelier *::before, .atelier *::after,
  .vitrine *, .vitrine *::before, .vitrine *::after,
  .mission *, .mission *::before, .mission *::after {
    transition-property: opacity, color, border-color !important;
    transition-duration: .2s !important; transition-delay: 0s !important;
  }
  .a-hint, .cap-desc { transform: none !important; }
  /* notre mission : les titres et les étiquettes paraissent sans flou ni glissement */
  .m-c, .m-line, .m-sub, .m-stats > div, .m-quote, .m-motto, .m-tag-t, .m-steps li { filter: none !important; transform: none !important; }
  .m-txt { translate: none !important; }
  .vitrine { transition: none; }
  .v-item, .v-ghost { translate: none !important; }
  /* l'étiquette d'une région apparaît en fondu, sans trait qui pousse ni glissement */
  .region-lead, .region-node { transition: none !important; }
  .region-label { transition: opacity .2s ease !important; translate: none !important; }
  .region-desc { transition: opacity .2s ease !important; }
}
