/* =========================================================================
   ONOLI — Studio Podcast, Neuchâtel
   Feuille de style unique. HTML + CSS, JavaScript uniquement pour les
   animations et le formulaire.

   Sommaire
   01. Thème (jetons de couleur)
   02. Base & réinitialisation
   03. Primitives (conteneur, titres, boutons)
   04. En-tête & navigation
   05. Fond animé en courbes de niveau
   06. Hero (accueil)
   07. Piliers & bandeau défilant
   08. Sections de contenu (cartes, tarifs, projets, statistiques)
   09. Formulaires
   10. Contact
   11. Pied de page
   12. Animations & accessibilité
   ========================================================================= */

/* ------------------------------------------------------- 01. THÈME ------ */
/*
   UN SEUL THÈME, ET C'EST LE CLAIR. Il n'y a qu'un bloc de jetons, `:root`.

   ⚠️ IL Y A EU UN THÈME SOMBRE, ET SON RETRAIT EST UNE DÉCISION, PAS UN
   OUBLI. Il existait en double — une fois sous une requête de média liée à la
   préférence du système, une fois sous un attribut posé par le bouton — et il
   fallait donc vérifier deux fois chaque section
   dessinée. Sur un site encore en train de se faire, ce doublement coûtait
   plus qu'il ne rendait : les seuls défauts d'affichage de ces dernières
   semaines lui appartenaient tous (les courbes du fond réglées trois fois, le
   voile de la montagne qu'il a fallu supprimer, l'aura au changement de
   thème). Rien de cela n'existait en clair.

   ⚠️ NE CONFONDEZ PAS LE THÈME SOMBRE ET `.section--invert` (section 04).
   Les deux redéclarent les mêmes jetons, mais `.section--invert` est un
   PANNEAU noir dans une page claire — 26 sur le site, et c'est le dessin même
   de l'alternance. Il n'a jamais fait partie du thème sombre, et il reste.

   Si le sombre devait revenir un jour : un seul bloc, piloté par un attribut
   sur la racine, jamais de jumeau lié à la préférence du système, et
   `.section--invert` redéfini à la palette claire pour que « inversé »
   continue de vouloir dire « l'inverse de la page ».
*/

:root {
  color-scheme: light;

  /* Blanc presque pur et neutre : le crème d'origine venait de la palette
     terre cuite et éteignait le rouge des courbes du fond. */
  --bg: #fdfdfd;
  --bg-2: #f4f4f5;
  --bg-3: #eaeaec;
  --bg-invert: #111113;

  --ink: #111113; /* ni noir pur — 18.6:1 sur --bg */
  --ink-2: #3d3d42; /* texte courant — 10.7:1 */
  --ink-3: #5d5d64; /* petits libellés — 6.4:1 */
  --ink-invert: #fafafa;

  --line: rgba(17, 17, 19, 0.12);
  --line-strong: rgba(17, 17, 19, 0.26);

  /*
     Rouge suisse : l'accent unique du site, et une seule teinte pour tout,
     bande latérale comprise. Auparavant la bande avait son rouge et les
     accents le leur, à peine différents, ce qui se voyait dès qu'un élément
     rouge passait près du bord gauche.

     La teinte a d'abord été #d0201c, un rouge de signalisation. #b31f22 est
     plus profond et un peu moins saturé : un rouge héraldique plutôt qu'un
     rouge de panneau routier. Il a été adopté en même temps que le noir doux,
     avec lequel le rouge vif se disputait l'attention. Sur le blanc, le
     contraste est passé de 5,3:1 à 6,6:1.

     Le nom du jeton reste --terra pour ne rien casser ailleurs.
  */
  --terra: #b31f22;
  --terra-deep: #931a1d;
  --terra-contrast: #fff7f5;
  --terra-soft: rgba(179, 31, 34, 0.09);

  /*
     LA SEULE EXCEPTION À LA TEINTE UNIQUE, et elle est mesurée.

     --terra-text ne sert qu'aux PETITS TEXTES peints en rouge : les numéros de
     bande, les prix en ligne, les surtitres, les liens soulignés. Sur fond
     clair il vaut exactement --terra, donc rien ne change ici.

     Sur fond sombre, en revanche, #b31f22 ne donne que 2,5:1, là où WCAG AA
     demande 4,5:1 pour un texte courant : les prix et les numéros deviennent
     illisibles. Les blocs de thème sombre le remontent donc à #ef5257.

     ⚠️ CETTE VALEUR EST SOLIDAIRE DE DEUX AUTRES : la profondeur du noir et
     celle du rouge. Elle a déjà dû monter quatre fois — #e8342f, #f0453c,
     #f45146, #ef5257 — à chaque fois que le fond s'est éclairci ou que le
     rouge s'est assombri, puisque l'écart entre les deux se réduit. Si vous
     touchez encore à l'un des deux, ce rouge devra suivre, et il finira par
     tirer sur le saumon (#fb6a5e), la teinte que le site a justement
     abandonnée.

     Tout le reste garde #b31f22 sans exception : la bande, les aplats de
     bouton, le balayage au survol des bandes du sommaire, les pictogrammes,
     les courbes du fond, et « On publie. » qui est assez grand pour passer
     (seuil 3:1) en rouge pur.
  */
  --terra-text: #b31f22;

  --topo: rgba(179, 31, 34, 0.2);
  --topo-faint: rgba(179, 31, 34, 0.11);

  /* Fond animé (terrain.js). L'opacité est gérée dans le script, ces
     couleurs doivent donc rester opaques.
     --contour-water marque les niveaux les plus hauts : un rouge plus
     profond, pas une autre teinte — tout reste dans la palette. */
  --contour: #b31f22;
  --contour-water: #931a1d;

  /*
     Deux familles, deux rôles, et rien d'autre.

     Inter en gras porte tous les titres : c'est une grotesque neutre, sans
     effet de style, qui tient l'autorité par la graisse plutôt que par le
     dessin. Elle a remplacé Playfair Display, un serif à fort contraste dont
     l'élégance tirait le site vers l'éditorial.

     JetBrains Mono porte le texte courant et les petits libellés. Sa chasse
     fixe donne au site un air d'objet technique, et aligne naturellement les
     chiffres, ce qui sert les tarifs, les numéros de bande et les horaires.

     Comme --rail-w, ce ne sont pas des couleurs : elles ne se redéclarent pas
     dans les blocs de thème.
  */
  --font-title: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /*
     ⚠️ LES PHRASES SE COMPOSENT DANS LA POLICE DES TITRES, PAS DANS LA MONO.

     Ce n'est pas un alias paresseux : c'est la même police à un autre poste,
     et le jeton existe pour que la règle groupée plus bas se lise pour ce
     qu'elle est — une décision, pas un accident.

     Le partage : JetBrains Mono garde les petites capitales techniques
     (surtitres, lignes rouges, étiquettes, boutons, prix, pied de page) —
     c'est là qu'elle FAIT l'identité du site. Inter prend les phrases, où la
     chasse fixe ne coûtait que de la vitesse de lecture : chaque lettre y
     occupant la même largeur, la silhouette des mots disparaît, et c'est par
     la silhouette qu'on lit vite.

     Aucun fichier en plus : Inter est déjà chargée et préchargée.
  */
  --font-lecture: var(--font-title);
  --font-body: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono',
    Consolas, monospace;

  /* Bande latérale rouge. La largeur n'est pas une couleur : elle ne se
     redéclare donc pas dans les blocs de thème. */
  --rail-w: clamp(30px, 5.6vw, 86px);
  --rail-bg: #b31f22;
  --rail-ink: #fff4f2;
  /* Ombre portée à droite de la bande, pour qu'elle passe au-dessus de la
     page au lieu d'être posée à côté. Voir .rail::after. */
  --rail-shadow: rgba(17, 17, 19, 0.28);

  --shadow: 0 30px 70px -35px rgba(17, 17, 19, 0.4);
  --shadow-sm: 0 16px 36px -24px rgba(17, 17, 19, 0.35);

  --maxw: 1400px;
  --gutter: clamp(1.25rem, 4vw, 3.25rem);
  /*
     ⚠️ CES DEUX VALEURS ÉTAIENT À 14 ET 20 PX, ET C'ÉTAIT LE DERNIER ARRONDI
     APPUYÉ DU SITE. Elles habillent les cartes de formulaire et le cadre de
     la charte. À 14 px, sur une carte de 900 px de large, l'angle se voit
     franchement — à côté des colonnes de tarifs, des repères et des boutons
     qui sont tous à angle droit, la carte du devis semblait venir d'ailleurs.

     4 px, ce n'est pas zéro : un cadre parfaitement carré a l'air d'un
     tableau, et l'œil accroche les coins. C'est juste assez pour adoucir sans
     que la forme devienne ronde.
  */
  --radius: 4px;
  --radius-lg: 6px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* --------------------------------------------------- 02. BASE ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);

  /*
     ⚠️ EN UNITÉ RELATIVE, PAS EN PIXELS. C'était `15px` — une taille absolue,
     pendant que toute la feuille se mesure en `rem`, donc sur les 16 px de la
     racine. Le site avait deux échelles qui ne se parlaient pas : un `0.8rem`
     valait 12,8 px à côté d'un corps de 15 px, alors qu'il était écrit pour
     en faire 80 %. `1rem` remet les deux sur la même base.
  */
  font-size: 1rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* Toute la page est décalée de la largeur de la bande rouge, en-tête
     collant compris — la bande n'est jamais recouverte. */
  padding-left: var(--rail-w);
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease);
}

img,
svg,
canvas {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

ul {
  list-style: none;
  padding: 0;
}

h1,
h2,
h3 {
  font-family: var(--font-title);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

/*
   LES PHRASES, ET RIEN QUE LES PHRASES.

   ⚠️ LE DÉFAUT RESTE LA MONO (`--font-body` sur `body`), ET C'EST VOULU. On
   nomme ici les blocs de PHRASES, un par un, au lieu d'inverser le défaut.
   Si une règle manque à l'appel, le texte concerné reste en monospace — un
   paragraphe un peu lent à lire, rien de plus. En inversant, l'oubli aurait
   mis une étiquette technique en Inter, et là c'est l'identité du site qui
   se défait. On choisit le sens où l'erreur se voit le moins.

   Si vous ajoutez un bloc de texte suivi, ajoutez-le à cette liste.
*/
.home-hero__lede,
.hero__sub,
.lede,
.lede--sm,
.section-head p:not(.eyebrow),
.note__desc,
.lane__desc,
.price li,
.project__desc,
.cta p,
.hint,
.price__extra,
.legal p,
.legal li,
.footer__brand p,
.field input,
.field select,
.field textarea {
  font-family: var(--font-lecture);
}

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

::selection {
  background: var(--terra);
  color: var(--terra-contrast);
}

.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 999;
  padding: 0.75rem 1.25rem;
  background: var(--bg);
  border: 1px solid var(--line-strong);
}
.skip-link:focus {
  left: calc(var(--rail-w) + 1rem);
  top: 1rem;
}

/* ------------------------------------------ 02b. BANDE LATÉRALE -------- */
/*
   Signature suisse : un aplat rouge sur toute la hauteur de la fenêtre,
   fixe, avec la croix en haut et trois libellés verticaux. Purement
   décoratif (aria-hidden) — aucune information n'existe uniquement ici.
*/

.rail {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 90; /* au-dessus de l'en-tête collant (60) */
  width: var(--rail-w);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding-block: clamp(1rem, 2.4vw, 2.4rem);
  background: var(--rail-bg);
  color: var(--rail-ink);
  pointer-events: none;
}

/*
   L'ombre est un dégradé posé JUSTE À DROITE de la bande (`left: 100%`), et
   non un `box-shadow` sur la bande elle-même : un box-shadow sur un élément
   fixe de toute la hauteur baverait aussi en haut et en bas, alors qu'on ne
   veut d'ombre que sur le flanc.

   Elle est peinte au-dessus de l'en-tête collant, parce qu'elle est dans la
   bande (z-index 90 contre 60) : c'est voulu, une bande qui passe au-dessus de
   la page doit aussi passer au-dessus de son en-tête.

   ⚠️ LES SEPT PALIERS NE SONT PAS DÉCORATIFS. Un dégradé à deux arrêts fait
   chuter l'opacité de façon linéaire : l'œil repère cette pente constante et
   la lit comme un trait à l'endroit où elle s'arrête net. Les paliers
   ci-dessous approchent une chute en cloche — brutale au départ, très longue
   à la fin — et l'ombre s'éteint alors sans qu'on puisse dire où.

   Sa largeur reste sous la marge intérieure du conteneur, donc elle assombrit
   du fond, jamais du texte.
*/
.rail::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 100%;
  width: clamp(18px, 3.4vw, 64px);
  background: linear-gradient(
    to right,
    var(--rail-shadow) 0%,
    color-mix(in srgb, var(--rail-shadow) 72%, transparent) 11%,
    color-mix(in srgb, var(--rail-shadow) 48%, transparent) 24%,
    color-mix(in srgb, var(--rail-shadow) 29%, transparent) 40%,
    color-mix(in srgb, var(--rail-shadow) 15%, transparent) 58%,
    color-mix(in srgb, var(--rail-shadow) 6%, transparent) 77%,
    transparent 100%
  );
  pointer-events: none;
}

/* Repli sans color-mix : deux arrêts, donc le trait revient, mais l'ombre
   existe toujours. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .rail::after {
    background: linear-gradient(to right, var(--rail-shadow), transparent);
  }
}

.rail__cross {
  display: block;
  width: clamp(14px, 2.1vw, 30px);
  color: var(--rail-ink);
}

.rail__cross svg {
  width: 100%;
  height: auto;
  fill: currentColor;
}

/*
   Le libellé se lit de bas en haut : writing-mode vertical puis demi-tour,
   sinon le texte descendrait la tête à l'envers.

   La bande ne porte plus qu'une seule mention, en bas. Un bloc intermédiaire y
   figurait, il a été retiré : la croix en haut et la signature en bas
   suffisent, et l'espace vide au milieu fait respirer la bande.

   `column-reverse` sert uniquement à la lisibilité du HTML : la lecture allant
   du bas vers le haut, l'ordre du fichier suivrait sinon l'ordre inverse de ce
   qu’on lit à l’écran. Avec lui, « onoli » vient bien avant
   « Suisse romande » dans la source comme dans la bande.
*/
.rail__brand {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: clamp(0.9rem, 1.8vw, 1.6rem);
}

.rail span[data-vertical] {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: clamp(0.5rem, 0.72vw, 0.66rem);
  font-weight: 600;
  letter-spacing: 0.22em;
  line-height: 1.9;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Le trait sépare les deux mentions, comme sur une carte topographique. Il est
   volontairement plus fin que la barre verticale qu'il remplace : à cette
   taille de texte, un séparateur plein pesait plus que les mots. Rien ne suit
   « Suisse romande ». */
.rail__tick {
  width: clamp(10px, 1.6vw, 22px);
  height: 1px;
  background: currentColor;
  opacity: 0.55;
}

/*
   ⚠️ SUR TÉLÉPHONE, LA BANDE N'EST PLUS QU'UN LISERÉ — ET C'EST UN GAIN DE
   LISIBILITÉ, PAS UNE ÉCONOMIE DE DÉCOR.

   Elle prenait 30 px sur un écran qui en fait 390, soit 8 % de la largeur,
   pour y poser une croix de 14 px et deux libellés verticaux de 8 px que
   personne ne lit à cette taille. La couleur reste — c'est elle la signature,
   pas les objets qu'elle porte — et les vingt pixels rendus élargissent
   chaque ligne de texte de la page.

   ⚠️ ET LE TITRE DE L'ACCUEIL EN DÉPEND : il est calculé sur la largeur
   utile, pour que « ON PHOTOGRAPHIE. » tienne sur une ligne. Si vous rendez
   à la bande sa largeur ici, réduisez son corps en conséquence.
*/
@media (max-width: 700px) {
  :root {
    --rail-w: 10px;
  }

  .rail__cross,
  .rail__brand {
    display: none;
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------- 03. PRIMITIVES ---------- */

.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  position: relative;
  padding-block: clamp(4rem, 9vw, 8rem);
}

.section--tight {
  padding-block: clamp(2.75rem, 5.5vw, 4.5rem);
}

.section--panel {
  background: var(--bg-2);
  overflow: hidden;
}

/*
   Le panneau inversé. En thème clair, la section bascule d'un coup sur la
   palette sombre : le fond devient presque noir, le texte presque blanc, le
   rouge reste exactement le même : #b31f22, comme partout ailleurs.

   Rien n'est peint en dur ici, on ne fait que redéclarer les jetons : tout ce
   que la section contient — bandes, filets, courbes décoratives, survols —
   suit sans une seule règle supplémentaire.

   En thème sombre, l'inversion n'aurait aucun sens : la page est déjà noire.
   Les deux blocs de thème du haut de ce fichier reprennent donc la main sur
   `.section--invert` (leur sélecteur est plus spécifique), et la section
   redevient le panneau discret qu'elle était.

   `--bg-2` vaut ici la même valeur que `--bg` : c'est lui qui peint le fond
   du panneau via `.section--panel`, et on veut le noir le plus profond, pas
   le gris de surface élevée. Les enfants qui ont besoin d'un relief passent
   par `--bg-3`.
*/
.section--invert {
  --bg: #1f1e1d;
  --bg-2: #1f1e1d;
  --bg-3: #2b2a28;
  --bg-invert: #f1f0ee;

  --ink: #f3f1ef;
  --ink-2: #c8c5c2;
  --ink-3: #98948f;
  --ink-invert: #0e0e0f;

  --line: rgba(243, 241, 239, 0.13);
  --line-strong: rgba(243, 241, 239, 0.28);

  --terra: #b31f22;
  --terra-deep: #d0363a;
  --terra-contrast: #fff7f5;
  --terra-soft: rgba(179, 31, 34, 0.18);
  --terra-text: #ef5257; /* voir le commentaire dans :root */

  --topo: rgba(179, 31, 34, 0.26);
  --topo-faint: rgba(179, 31, 34, 0.14);

  --shadow: 0 30px 70px -35px rgba(0, 0, 0, 1);
  --shadow-sm: 0 16px 36px -24px rgba(0, 0, 0, 0.9);

  color: var(--ink);
}

.eyebrow {
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--terra-text);
}

.section-head {
  display: grid;
  gap: 1.1rem;
  max-width: 44rem;
  margin-bottom: clamp(2.25rem, 4.5vw, 3.5rem);
}

.section-head h1,
.section-head h2 {
  font-size: clamp(2.1rem, 5vw, 3.6rem);
}

/*
   ⚠️ `:not(.eyebrow)` EST LOAD-BEARING. Sans lui, cette règle — plus
   spécifique que `.eyebrow` — imposait son corps au surtitre : 15,7 px dans
   une section, 11,2 px dans le bloc de clôture, pour le même élément. Le
   surtitre a une taille, une seule, et c'est la sienne.
*/
.section-head p:not(.eyebrow) {
  color: var(--ink-2);
  font-size: clamp(0.98rem, 1.3vw, 1.1rem);
}

.lede {
  color: var(--ink-2);
  font-size: clamp(1.02rem, 1.5vw, 1.25rem);
  line-height: 1.6;
}

.two-col {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
  align-items: start;
}

/* Boutons ---------------------------------------------------------------- */

/*
   ⚠️ LE BOUTON N'A PAS D'ANGLE ROND, ET C'EST VOULU. Il était en pilule
   (`border-radius: 999px`), la forme la plus banale qui soit. Le site arrondit
   ailleurs, mais jamais comme ça : `--radius` à 14 px sur les cartes, des
   cercles pour les icônes du menu, des pilules pour les étiquettes de métier
   des réalisations — qui sont des libellés, pas des actions. Le seul élément
   sur lequel on clique portait l'arrondi le plus fort de la page.

   Le vocabulaire du site, c'est le filet droit, le repère carré et la croix.

   À la place, il emprunte à la marque ses quatre coins. Au repos c'est un
   aplat rectangulaire, angles francs. Au survol, quatre équerres d'un pixel
   se posent autour de lui, en retrait de six pixels — le repère de coupe d'un
   photographe, et le dessin même du logo. Elles sont EN DEHORS de la boîte :
   elles n'occupent aucune place, donc rien ne bouge autour quand elles
   apparaissent.

   Sous le texte, l'aplat se recouvre d'un ton plus profond qui balaie de
   gauche à droite. C'est le geste du `.link-line` du site, à l'échelle d'un
   bloc plein plutôt que d'un filet — la même main.

   Plus de `translateY` au survol : le soulèvement est le tic de n'importe
   quel bouton, et il faisait flotter les équerres avec lui.
*/
.btn {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-sweep: transparent;
  --btn-coin: currentColor;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.8rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid transparent;
  border-radius: 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease),
    color 0.3s var(--ease);
}

/* Les quatre équerres. Huit dégradés d'un pixel : deux traits par coin, posés
   aux quatre angles d'une boîte plus grande que le bouton. */
.btn::before {
  content: '';
  position: absolute;
  inset: -6px;
  pointer-events: none;
  background:
    linear-gradient(var(--btn-coin), var(--btn-coin)) 0 0 / 1px 11px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 0 0 / 11px 1px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 100% 0 / 1px 11px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 100% 0 / 11px 1px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 0 100% / 1px 11px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 0 100% / 11px 1px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 100% 100% / 1px 11px,
    linear-gradient(var(--btn-coin), var(--btn-coin)) 100% 100% / 11px 1px;
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(0.93);
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}

/* Le balayage. `z-index: -1` le range derrière le texte mais devant le fond
   du bouton, grâce à `isolation: isolate` posé plus haut ; `inset: -1px` lui
   fait couvrir aussi la bordure, sinon un liseré de l'ancienne couleur
   subsiste tout autour. */
.btn::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset: -1px;
  background: var(--btn-sweep);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s var(--ease);
}

.btn:hover::before,
.btn:focus-visible::before {
  opacity: 1;
  transform: none;
}

.btn:hover::after,
.btn:focus-visible::after {
  transform: scaleX(1);
}

.btn svg {
  width: 1.05em;
  height: 1.05em;
  transition: transform 0.45s var(--ease);
}

.btn:hover svg,
.btn:focus-visible svg {
  transform: translateX(0.3rem);
}

.btn--primary {
  --btn-bg: var(--terra);
  --btn-fg: var(--terra-contrast);
  --btn-sweep: var(--terra-deep);
  --btn-coin: var(--terra);
}

/* Écran très contrasté, ou survol au doigt : les équerres restent, le
   balayage devient un simple changement de couleur. */
@media (prefers-reduced-motion: reduce) {
  .btn::before {
    transform: none;
  }

  .btn::after {
    transition: none;
  }
}

/*
   ⚠️ PAS DE BULLE, UNE CROIX ET LE MOT. L'étiquette était un contour
   arrondi fermé autour d'un mot — le seul objet du site à procéder ainsi, et
   la même forme que le bouton qu'on vient de redécouper.

   Le repère de la maison est la croix rouge de 9 px : les colonnes de tarifs,
   les lignes d'options à la carte, les repères de « Où nous trouver ». Ici
   elle joue le rôle que le contour tenait — dire « ceci est une étiquette »
   — sans enfermer le mot.

   Elle est dessinée en deux dégradés croisés, comme partout ailleurs, et
   `flex: none` l'empêche d'être écrasée quand le mot est long.
*/
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.tag::before {
  content: '';
  flex: none;
  width: 10px;
  height: 10px;
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}

/* ------------------------------------------------ 04. EN-TÊTE ---------- */

/*
   La barre reprend la couleur de fond de la page, à 84 % et floutée : le
   contenu se devine en dessous sans jamais gêner la lecture du menu.

   Une version noire a été essayée dans les deux thèmes : elle coupait la page
   en deux au lieu de la coiffer, et obligeait à inverser le logo au filtre.
   Abandonnée.

   ⚠️ AU REPOS, LA BARRE N'A AUCUN FOND. C'est solidaire du premier écran,
   qui n'a plus de voile non plus (voir la section 04, `main > .section`) :
   toute différence entre les deux se lirait comme une ligne en travers de
   la page, et c'est exactement ce qu'on cherche à ne plus avoir. Rien
   au-dessus, rien en dessous, le fond animé passe d'un seul tenant.

   Au défilement seulement, quand la barre passe réellement par-dessus le
   contenu, le voile apparaît à 84 % et se floute : là il faut qu'il porte.
*/
.header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 0.35s var(--ease),
    backdrop-filter 0.35s var(--ease), border-color 0.3s var(--ease);
}

.header.is-scrolled {
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}

/*
   Le menu déroulant, lui, a besoin du voile même en haut de page : le
   panneau n'a pas de fond propre, et sans voile la page se lirait à travers
   ses six entrées. C'est le seul `:has()` du fichier, et il est sans risque
   — un navigateur qui l'ignorerait ne perdrait que le voile du menu.
*/
.header:has(.nav-mobile[data-open='true']) {
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(14px);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding-block: 1.1rem;
}

/* Logo ------------------------------------------------------------------- */

/* Le logo est une image à fond transparent, encre noire. En thème sombre on
   l'inverse plutôt que d'embarquer un second fichier — 0.94 au lieu de 1
   pour éviter le blanc pur. */
.logo {
  display: inline-flex;
  align-items: center;
  flex: none; /* sinon le flex de l'en-tête écrase le logo au lieu de basculer en burger */
}

/*
   Le logotype est un SVG EN LIGNE, pas une image. C'est ce qui permet à ses
   lettres de prendre `currentColor` — donc la couleur du texte, donc le
   thème — pendant que son point garde le rouge de la marque.

   L'ancien logo était un PNG noir retourné en thème sombre par
   `filter: invert(0.94)`. Le nouveau porte un point rouge : l'inversion
   l'aurait viré au cyan. Plus de filtre, plus de problème.
*/
.logo__svg {
  display: block;
  width: auto;
  height: 42px;
  color: var(--ink);
}

.footer .logo__svg {
  height: 42px;
}

@media (max-width: 480px) {
  .logo__svg {
    height: 34px;
  }
}

/* Navigation ------------------------------------------------------------- */

.nav {
  display: none;
  align-items: center;
  gap: clamp(1.2rem, 2.6vw, 2.7rem);
}

/*
   Le menu est en Inter, pas en mono : c'est le seul texte que l'œil balaie
   sans le lire, et la chasse fixe y coûtait de la largeur pour rien. Il
   rejoint ainsi le menu mobile, déjà en Inter.
*/
.nav a {
  position: relative;
  font-family: var(--font-title);
  font-weight: 500;
  font-size: 0.92rem;

  /*
     ⚠️ `nowrap` N'EST PAS UN DÉTAIL DE COMPOSITION, C'EST CE QUI REND LA
     MESURE DU BURGER HONNÊTE. `main.js` compare la largeur dont la navigation
     a besoin à la place disponible. Un lien qui se coupe en deux lignes
     RÉTRÉCIT sa boîte — « Réseaux sociaux » sur deux lignes ne mesure plus que
     « Réseaux » — donc la navigation déclarait avoir besoin de moins de place
     qu'elle n'en demande, et restait déployée sur deux étages au lieu de
     basculer en burger.
  */
  white-space: nowrap;
  color: var(--ink-2);
  transition: color 0.25s var(--ease);
}

.nav a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 1px;
  background: var(--terra);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.35s var(--ease);
}

.nav a:hover,
.nav a[aria-current='page'] {
  color: var(--ink);
}

.nav a:hover::after,
.nav a[aria-current='page']::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* Repli sans JavaScript : au-delà de 940 px la navigation tient. Avec
   JavaScript, main.js mesure la place réellement disponible et pose
   data-nav sur l'en-tête — le burger n'apparaît donc que s'il sert. */
@media (min-width: 940px) {
  .nav {
    display: flex;
  }
  .burger,
  .nav-mobile {
    display: none;
  }
}

.header[data-nav='full'] .nav {
  display: flex;
}
.header[data-nav='full'] .burger,
.header[data-nav='full'] .nav-mobile {
  display: none;
}
.header[data-nav='compact'] .nav {
  display: none;
}
.header[data-nav='compact'] .burger {
  display: block;
}
.header[data-nav='compact'] .nav-mobile {
  display: block;
}

/*
   Burger. Les trois barres sont superposées au centre exact du bouton et
   écartées par translateY : à l'ouverture, les deux barres extérieures
   pivotent autour du même point et forment une vraie croix, pas un chevron.
*/
/*
   ⚠️ LE DÉFAUT EST `none`, ET C'EST CE QUI EMPÊCHE LE BURGER DE CLIGNOTER.

   Il était à `display: block`. Or la règle qui le cache au-delà de 940 px est
   écrite PLUS HAUT dans ce fichier, et une media query n'ajoute aucune
   spécificité : à égalité, c'est le dernier écrit qui gagne — celui-ci. Le
   burger était donc affiché à toutes les largeurs jusqu'à ce que le script
   pose `data-nav` sur l'en-tête, ce qui se voyait à chaque chargement de page.

   Caché par défaut, montré par une media query complémentaire : les deux
   règles ne s'appliquent jamais à la même largeur, il n'y a plus d'arbitrage
   à faire, et le rendu est juste avant même que le script s'exécute.
*/
.burger {
  position: relative;
  display: none;
  width: 42px;
  height: 42px;
  padding: 0;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}

.burger:hover {
  border-color: var(--terra);
  background: var(--terra-soft);
}

/* Le complément exact de `@media (min-width: 940px)` plus haut. */
@media (max-width: 939.98px) {
  .burger {
    display: block;
  }
}

.burger span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 1.6px;
  margin: -0.8px 0 0 -9px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.35s var(--ease), opacity 0.18s var(--ease);
}

.burger span:nth-child(1) {
  transform: translateY(-4.5px);
}
.burger span:nth-child(3) {
  transform: translateY(4.5px);
}

.burger[aria-expanded='true'] span:nth-child(1) {
  transform: rotate(45deg);
}
.burger[aria-expanded='true'] span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0.3);
}
.burger[aria-expanded='true'] span:nth-child(3) {
  transform: rotate(-45deg);
}

.nav-mobile {
  overflow: hidden;
  max-height: 0;
  border-top: 1px solid transparent;
  transition: max-height 0.45s var(--ease);
}

/* Le plafond ne sert qu'à animer l'ouverture, mais `overflow: hidden` le rend
   coupant : il doit rester au-dessus de la hauteur réelle du menu. Six entrées
   font 430 px — 24rem (384 px) tronquait « Contact ». */
.nav-mobile[data-open='true'] {
  max-height: 32rem;
  border-top-color: var(--line);
}

.nav-mobile ul {
  display: grid;
  padding-block: 0.5rem 1.5rem;
}

.nav-mobile a {
  display: block;
  padding: 0.85rem 0;
  font-family: var(--font-title);
  font-weight: 700;
  font-size: 1.35rem;
  border-bottom: 1px solid var(--line);
}
.nav-mobile li:last-child a {
  border-bottom: 0;
}

/* ------------------------- 05a. FOND : COURBES DE NIVEAU ANIMÉES ------- */
/*
   Canevas fixe en arrière-plan de tout le site (assets/js/terrain.js) :
   un champ de courbes de niveau qui ondule lentement sur place.

   Empilement : ce calque est en z-index 0, le contenu en z-index 1. Les
   sections sans fond laissent donc voir les courbes, celles qui ont un fond
   (--bg-2) les masquent — ce qui donne le rythme de la page.
*/

.bg-terrain {
  position: fixed;
  /* Le canvas est fixe : il ignore le décalage du body, on le décale donc
     ici de la largeur de la bande. */
  inset: 0 auto 0 var(--rail-w);
  z-index: 0;
  /* Largeur explicite : sur un <canvas>, `width: auto` retomberait sur
     l'attribut width intrinsèque au lieu de suivre le bord droit. */
  width: calc(100% - var(--rail-w));
  height: 100%;
  pointer-events: none;
  /*
     Le fond doit rester un fond : assez présent pour qu'on voie qu'il vit,
     assez discret pour que le texte passe devant sans lutter contre les
     courbes. Descendu par paliers : 0.46, 0.34, 0.24, 0.15, 0.13, et
     finalement 0.115 — à chaque cran les courbes se lisaient encore comme un
     dessin plutôt que comme un fond. On est maintenant tout près du point
     où le fond cesse de vivre ; si vous descendez encore, vérifiez sur un
     écran mal calibré qu'il reste quelque chose.

     Cette dernière baisse remplace le voile que portaient les premiers
     écrans (voir section 04) : plutôt que de poser un calque par-dessus le
     fond — dont la lisière se voyait — on baisse le fond à la source. Même
     résultat à l'œil, et plus aucune frontière à raccorder. Elle rend aussi
     à la carte de l'accueil le premier plan qu'elle avait perdu : ses
     courbes à elle ne luttent plus contre celles du fond.
  */
  opacity: 0.115;
}

/* Le contenu passe devant le fond animé. */
main,
.footer {
  position: relative;
  z-index: 1;
}

/*
   IL N'Y A PLUS DE VOILE NULLE PART, et c'est volontaire.

   Le principe était de poser, sur les sections sans fond, un aplat de la
   couleur de la page à 65 % : les courbes du fond animé passaient dessous,
   atténuées, et le texte se lisait sans lutter contre elles.

   Trois choses l'ont condamné. Sa lisière se voyait — une ligne en travers
   du haut de la page, là où le voile commençait. Deux sections voilées
   adjacentes laissaient un liseré d'un pixel à leur jointure. Et surtout il
   rendait le fond différent d'un endroit à l'autre de la même page.

   Ce qu'il faisait, l'opacité du canevas le fait maintenant à la source
   (voir `.bg-terrain`, descendue à 0.15) : même discrétion, partout pareil,
   et plus une seule frontière à raccorder. Les panneaux pleins
   (`section--panel`) restent le seul moyen d'arrêter les courbes — c'est
   eux qui donnent son rythme à la page.
*/

/* --------------------------------------------------- 05. TOPO ---------- */

.topo {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.topo svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.topo path {
  fill: none;
  stroke: var(--topo);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.topo g {
  transform-origin: center;
  will-change: transform;
  animation: topo-drift var(--dur, 30s) ease-in-out infinite alternate;
}

@keyframes topo-drift {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    transform: translate3d(var(--tx, 20px), var(--ty, -16px), 0)
      scale(var(--sc, 1.05));
  }
}

/* --------------------------------------------------- 06. HERO ---------- */

/*
   HERO de la page podcast, désormais purement typographique.

   Il a longtemps porté la photo du studio en deuxième couche, avec un
   parallaxe GSAP en `scrub` et une surface d'eau WebGL sur le bas du visuel.
   Photo retirée : le parallaxe, la surface d'eau, le badge et tout le calage
   du titre sur le centre de l'image (--title-center, mesuré en JavaScript)
   sont partis avec elle. Le hero est maintenant une colonne de texte et le
   pictogramme du métier à sa droite.

   Le titre reprend EXACTEMENT le traitement de celui de l'accueil : la règle
   de corps est mutualisée plus bas, avec `.home-hero__title`. Ne lui redonnez
   pas de taille propre ici, les deux titres doivent rester identiques.
*/
/*
   Comme sur l'accueil, le hero remplit le premier écran : on ne doit voir que
   du blanc en arrivant, le panneau noir commence sous le pli. Même valeur
   retranchée, même raison — voir `.home-hero`, tout y est expliqué.
*/
.hero {
  position: relative;
  display: grid;
  align-content: center;
  min-height: calc(100vh - 76px);
  min-height: calc(100svh - 76px);
  padding-block: clamp(3.5rem, 8vw, 7rem) clamp(3rem, 6vw, 5rem);
}

.hero__grid {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  align-items: center;
}

/*
   Une seule colonne, plafonnée : la droite du hero appartient au décor animé
   (voir `.hero-scene`), qui passe derrière. Le pictogramme de micro qui
   occupait cette place a été retiré avec sa colonne.
*/
@media (min-width: 1000px) {
  .hero__text {
    max-width: 46rem;
  }
}

/* --------------------- 04c. LE RELIEF (page contact) ------------------- */
/*
   La montagne du bandeau de contact. Elle est en SVG dans la page — et non
   en canvas comme les décors des pages métier — parce qu'elle ne bouge pas :
   un dessin fixe se code en vectoriel, s'anime en CSS et reste net sur tout
   écran, sans une ligne de JavaScript.

   La couleur ne se lit qu'ici : les tracés prennent le dégradé `#mnt-fade`,
   dont les arrêts sont peints par `.mnt-stop` avec le rouge du thème. Le
   dégradé éteint les extrémités gauche et droite ; le masque ci-dessous
   éteint la base. La montagne se perd donc dans le blanc de trois côtés.
*/
.hero-mountain {
  position: absolute;
  right: 4%;
  top: 50%;
  /*
     Un cran au-dessus du centre optique : centrée pile, la montagne posait sa
     base au niveau du chapeau et son sommet restait sous la ligne du titre.

     ⚠️ LE DÉCALAGE SUIT LA HAUTEUR DE LA FENÊTRE, IL N'EST PAS FIXE. Quatre
     rem valent quatre lignes de texte sur un grand écran, mais presque un
     dixième de la hauteur sur un portable — la montagne y montait trop et son
     sommet sortait par le haut. `min()` plafonne le décalage à quatre rem et
     le laisse fondre avec la fenêtre en dessous.
  */
  transform: translateY(calc(-50% - min(4rem, 4vh)));
  width: min(50%, 48rem);
  height: 80%;
  z-index: 0;
  pointer-events: none;
}

/*
   ⚠️ IL N'Y A PLUS DE VOILE DERRIÈRE LA MONTAGNE, ET IL NE FAUT PAS EN
   REMETTRE UN.

   Un `::before` en dégradé radial posait ici un disque de la couleur de la
   page. Un tel disque ne peut pas être invisible : c'est une zone plus claire
   avec une frontière, quelle que soit sa taille et son opacité. Petit, sa
   lisière tombait sur le dessin ; grand, le disque entier se voyait. Et
   comme sa couleur venait de `--bg`, il changeait de teinte au basculement
   de thème — l'aura qu'on voyait passer.

   Il existait pour une raison réelle : les courbes du fond paraissaient
   traverser le dessin, qui n'a aucun remplissage. Cette raison a fondu
   depuis — le calque du fond est passé de 0,46 à 0,115 d'opacité en clair,
   par paliers, et vaut 0,13 en sombre. Les courbes ne sont plus qu'une trame
   très faible là où elles étaient un dessin à part entière.

   Si elles redevenaient gênantes, ne remettez pas un disque : baissez
   `.bg-terrain`, ou donnez au dessin une silhouette fermée qu'on puisse
   remplir. Le SVG n'est fait que de hachures ouvertes — c'est justement ce
   qui interdisait la solution propre.
*/


/*
   ⚠️ LE FONDU DU BAS EST SUR LE DESSIN, PAS SUR LE BLOC. Il était posé sur
   `.hero-mountain`, donc il masquait aussi le voile — et `mask-image` sans
   `mask-repeat` PAVE : le dégradé était recopié au-dessus et en dessous de la
   boîte, là où le voile déborde de 22 %. Chaque copie posait sa propre
   lisière, et ce sont ces traits très légers qu'on voyait sous le dessin.

   Ici le masque ne s'applique qu'au SVG, qui remplit exactement sa boîte : il
   n'y a plus rien à paver. `no-repeat` est posé quand même, parce qu'un
   masque qui pave est un piège qu'on ne voit pas venir.
*/
.hero-mountain svg {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  overflow: visible;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 74%, transparent 99%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 74%, transparent 99%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/*
   `vector-effect` est ce qui garde le trait à la même finesse quelle que soit
   la taille d'affichage : sans lui, le dessin s'épaissit sur grand écran et
   perd sa précision. C'est aussi ce qui rend la valeur ci-dessous absolue :
   0,7 px à l'écran, quelle que soit l'échelle du SVG.

   ⚠️ UNE PROPRIÉTÉ CSS ÉCRASE UN ATTRIBUT DE PRÉSENTATION. Poser une
   `stroke-width` ici rend donc inopérante toute valeur posée sur un tracé
   dans le balisage : si un dessin arrive un jour avec ses propres épaisseurs,
   c'est cette règle qu'il faudra retirer. Le dessin actuel n'en porte pas —
   il tient son modelé de la densité des traits, pas de leur épaisseur.
*/
.hero-mountain svg {
  stroke-width: 0.7;
}

.hero-mountain path {
  fill: none;
  stroke: url(#mnt-fade);
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/*
   L'arête centrale est la seule ligne un peu plus appuyée du dessin : c'est
   elle qui dit où le versant éclairé s'arrête. À 0,7 elle se noyait dans les
   hachures ; à 1,15 elle faisait tache — un trait plein, continu, au milieu
   de six cents hachures brisées. 0,95 la garde lisible sans la détacher, et
   c'est surtout son dégradé vertical propre (`#mnt-arete`, défini dans la
   page) qui l'intègre : elle naît en fondu sous le sommet et se perd avant
   le pied, comme tous les autres tracés.
*/
.hero-mountain__arete path {
  stroke: url(#mnt-arete);
  stroke-width: 0.95;
}

.mnt-stop {
  stop-color: var(--terra);
}

/*
   L'APPARITION, EN DEUX RÉGIMES.

   Les crêtes se dessinent au trait : `--len` porte la longueur exacte du
   chemin, mesurée à l'installation, pour que le pointillé colle au tracé.

   Les deux autres groupes montent en fondu. C'est délibéré : animer le
   `stroke-dashoffset` de quinze cents tracés à la fois oblige le navigateur à
   redessiner tout le SVG à chaque image, et ça se sent sur une machine
   modeste. Une opacité, elle, se compose. À l'œil, le relief se remplit
   pendant que la silhouette se trace — c'est exactement l'effet voulu, pour
   un coût sans commune mesure.

   Sous `.js` seulement : sans JavaScript la classe n'est pas posée et le
   relief est là d'emblée, entier.
*/
.js .hero-mountain__crete path {
  stroke-dasharray: var(--len, 900);
  stroke-dashoffset: var(--len, 900);
  animation: mnt-trace 1.6s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 12ms);
}

/* L'arête mène l'apparition : elle part sans délai, du sommet vers le pied
   (c'est le sens de son chemin), et met un peu plus longtemps que tout le
   reste. L'œil la suit pendant que le relief se remplit derrière elle. */
.js .hero-mountain__arete path {
  stroke-dasharray: var(--len, 950);
  stroke-dashoffset: var(--len, 950);
  animation: mnt-trace 2.2s var(--ease) forwards;
}

.js .hero-mountain__topo path,
.js .hero-mountain__detail path {
  opacity: 0;
  animation: mnt-lever 0.9s var(--ease) forwards;
  animation-delay: calc(260ms + var(--i, 0) * 10ms);
}

@keyframes mnt-trace {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes mnt-lever {
  to {
    opacity: 1;
  }
}

/* Les quatre groupes se décalent à la souris (voir assets/js/mountain.js).
   `will-change` évite que le navigateur repeigne le SVG entier à chaque
   image. */
.hero-mountain g {
  will-change: transform;
}

/* Sous 900 px, le titre prend toute la largeur : le relief se retire plutôt
   que de passer derrière le texte. */
@media (max-width: 899px) {
  .hero-mountain {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .js .hero-mountain path {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    opacity: 1;
    animation: none;
  }
}

/* --------------------- 04b. LES DÉCORS DES PAGES MÉTIER ---------------- */
/*
   Le même relevé topographique que partout sur le site, traversé par le
   motif du métier : l'onde du podcast, le graphe des réseaux, les blocs
   d'interface des sites web, le diaphragme de la photo. Tout est dessiné en
   canvas par assets/js/heroscene.js, qui lit les trois jetons ci-dessous —
   un canvas ne connaît pas les variables CSS — et suit donc le thème comme
   le reste.

   Le dégradé de gauche n'est pas décoratif : le décor traverse toute la
   largeur, et sans lui ses courbes passeraient derrière le titre.
*/
.hero-scene {
  --hs-line: var(--line-strong);
  --hs-red: var(--terra);
  --hs-bg: var(--bg);

  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.hero-scene canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    #000 46%,
    #000 93%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    #000 46%,
    #000 93%,
    transparent 100%
  );
}

/* Sous 900 px, le titre prend toute la largeur : le décor n'aurait plus que
   le fond du texte pour lui, il se retire plutôt que de le gêner. */
@media (max-width: 899px) {
  .hero-scene {
    display: none;
  }
}

/*
   ⚠️ LA CARTE DE L'ACCUEIL SE RETIRE AU MÊME SEUIL, ET POUR LA MÊME RAISON.
   Elle faisait exception : au lieu de passer derrière le texte comme les
   décors ci-dessus, elle s'empilait SOUS lui et prenait 380 px du premier
   écran d'un téléphone.

   Ses quatre repères sont de vrais liens — mais ils mènent aux quatre pages
   métier, qui sont dans le menu ET dans la section « Nos services » juste en
   dessous, avec leur prix d'entrée. Rien ne se perd.

   La hauteur d'écran plein se relâche avec elle : elle existait pour porter
   la carte à côté du texte ; sans carte, elle ne ferait que du vide sous
   l'accroche.
*/
@media (max-width: 899px) {
  .hero-map {
    display: none;
  }

  .home-hero {
    min-height: 0;
  }
}

.hero__title span {
  display: block;
}

/*
   Le titre du studio reprend EXACTEMENT le corps des titres de pages
   (`.page-head h1`) : c'est une demande explicite, la page podcast doit
   annoncer ni plus fort ni moins fort que réseaux, sites web et photo. Il
   avait sa propre échelle, deux fois plus grande, du temps où il ne disait
   que « STUDIO PODCAST » en deux mots.

   ⚠️ Si vous changez ce corps, changez aussi `.page-head h1` — les deux
   doivent rester solidaires.

   La ligne contraignante est désormais « NOUS ENREGISTRONS, » : dix-huit
   signes, soit le même gabarit que « RESSEMBLE VRAIMENT. » sur la page
   sites web.
*/
.hero__title {
  font-size: clamp(2rem, 7vw, 5.4rem);
  line-height: 1.02;
}

.hero__sub {
  margin-top: clamp(1.5rem, 3vw, 2.4rem);
  max-width: 24rem;
  font-size: clamp(0.92rem, 1.2vw, 1.05rem);
  color: var(--ink-2);
}

.hero__links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 2.4vw, 1.9rem);
  margin-top: clamp(1.8rem, 3.5vw, 2.8rem);
}

/*
   Les pastilles rondes et les cartes ont un temps servi sur le panneau noir de
   l'accueil, avec des réglages exprès (tracé clair, anneau épaissi, carte sur
   --bg-3) parce qu'un anneau d'un pixel en rouge sur du presque noir ne donne
   que 3,7:1 et grésille. La section a depuis été refaite sans encadrés (voir
   `.notes`), et ces réglages ont été retirés avec elle. Si vous remettez des
   cartes sur un fond sombre, souvenez-vous du problème.
*/
/*
   Le panneau noir de la page podcast porte des cartes et des pastilles rondes.
   Sans ces trois réglages, l'anneau d'un pixel et le tracé fin en rouge sur du
   sombre ne donnent que 3,3:1 et grésillent, et la carte prend exactement la
   couleur du panneau. Voir la page podcast, section « Le studio ».
*/
.section--invert .card {
  background: var(--bg-3);
}


.studio-infos {
  margin-top: clamp(2.75rem, 6vw, 4.5rem);
}

/* --------------------------- LIEUX (accueil, ancrage) ------------------ */
/*
   Trois repères posés sur un filet, à la place des trois encadrés d'avant.
   Les cartes enfermaient l'information dans des boîtes qui ne ressemblaient à
   rien d'autre sur la page ; ici on reprend le vocabulaire du hero, filets
   fins et petite croix, sur un fond qui est déjà une carte topographique.

   ⚠️ La croix est placée en `left: 0` du bloc, donc sur le BORD de la colonne.
   Comme les colonnes voisines portent leur trait de séparation à gauche, la
   croix tombe exactement à l'intersection des deux filets. C'est tout l'effet :
   ne lui donnez pas de décalage horizontal, il la ferait glisser à côté du
   croisement. Seule la première colonne, qui n'a pas de trait à sa gauche,
   garde sa croix alignée sur le texte plutôt que centrée.
*/
/*
   ⚠️ LE FILET DU BAS EST AUSSI IMPORTANT QUE CEUX DU HAUT. Chaque repère
   porte un `border-top` : sur une grille pleine, les rangées se lisent donc
   comme les lignes d'un relevé — sauf la dernière, qui restait ouverte. Et
   quand le nombre de repères ne remplit pas la dernière rangée — cinq
   services sur trois colonnes — la ligne du haut de cette rangée s'arrêtait
   au milieu de la page.

   Le filet du bas ferme la grille ; le repère vide (`.note--vide`, posé dans
   le HTML là où il manque une cellule) rétablit celui du haut.
*/
.notes {
  border-bottom: 1px solid var(--line);

  /*
     Trois colonnes par défaut, deux ou quatre avec `.notes--2` / `.notes--4`.

     --rows : nombre de lignes de CHAQUE repère, donc le nombre de ses enfants
              (nom, méta, description) — 2 si le repère n'a pas de ligne méta.
              Se pose en style inline sur le conteneur, comme --delay.
  */
  --rows: 3;

  /* La gouttière d'une colonne de repères. Elle sert de part et d'autre de
     chaque filet vertical, et la colonne d'ouverture s'en sert pour rendre à
     droite ce qu'elle ne prend pas à gauche — voir plus bas. */
  --note-gouttiere: clamp(1.6rem, 3vw, 2.8rem);
  display: grid;
  margin-top: clamp(2.4rem, 5vw, 4rem);
}

/*
   ⚠️ `display: block` EST NÉCESSAIRE PARCE QU'UN REPÈRE PEUT ÊTRE UN LIEN.
   Les quatre canaux de la page contact sont des `<a class="note">` : sans
   cela, en dessous de 860 px — là où le repère n'est pas encore une grille —
   ils resteraient des éléments en ligne et leurs marges seraient ignorées.
*/
.note {
  display: block;
  position: relative;
  padding-block: clamp(1.7rem, 3vw, 2.5rem) clamp(1.6rem, 3vw, 2.2rem);
  border-top: 1px solid var(--line);
}

.note:last-child {
  border-bottom: 1px solid var(--line);
}

/* Le repère-lien. Seul son nom réagit : le reste est de l'information, et de
   l'information qui change de couleur au survol se lit comme un défaut. */
.note--link {
  color: inherit;
}

/* ⚠️ LE NOM D'UN REPÈRE-LIEN EST PLUS PETIT QUE CELUI D'UN REPÈRE ORDINAIRE.
   Les canaux tiennent en QUATRE colonnes, et le nom y porte en plus la marque
   du service : à la taille normale, « Instagram » et son pictogramme ne
   tenaient plus sur une ligne, le mot passait dessous et sa colonne perdait
   l'alignement des trois autres. */

.note--link .note__name {
  transition: color 0.3s var(--ease);
}

.note--link:hover .note__name,
.note--link:focus-visible .note__name {
  color: var(--terra-text);
}

/* La marque du canal, dans le nom plutôt qu'au-dessus : un quatrième élément
   aurait décalé d'un cran les rangées partagées par les quatre colonnes. */
.note__name svg {
  /* ⚠️ `display: inline-block` est indispensable : la règle globale met tous
     les `svg` en `display: block` (voir section 02), et la marque partait
     alors sur sa propre ligne au-dessus du nom. */
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  margin-right: 0.55rem;
  vertical-align: -0.14em;
  color: var(--terra);
}

/* Un lien posé au fil du texte, sans le gabarit de `.link-line`. */
.link-inline {
  color: var(--terra-text);
  border-bottom: 1px solid color-mix(in srgb, var(--terra) 45%, transparent);
  transition: border-color 0.3s var(--ease);
}

.link-inline:hover,
.link-inline:focus-visible {
  border-bottom-color: var(--terra);
}

@media (min-width: 860px) {
  .notes {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(var(--rows), auto);
  }

  .notes--4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* ⚠️ LA TAILLE SUIT LE NOMBRE DE COLONNES, PAS LA NATURE DU REPÈRE. Cette
     réduction était posée sur `.note--link` ; ce n'était pas le fait d'être un
     lien qui l'imposait, mais celui de tenir dans une colonne quatre fois plus
     étroite que la page.

     ⚠️ ET ELLE EST DANS CETTE REQUÊTE, PAS AU-DEHORS. Posée à l'extérieur,
     elle s'appliquait aussi sur téléphone — où la grille n'a plus qu'une
     colonne de 350 px et où rien ne justifie de réduire quoi que ce soit. */
  .notes--4 .note__name {
    font-size: clamp(1.2rem, 1.7vw, 1.5rem);
  }

  /*
     « Partout en Suisse romande » tient sur deux lignes là où les deux autres
     noms en font une. Sans rien, la coordonnée et la description de cette
     colonne descendaient d'une ligne et les trois colonnes ne s'alignaient
     plus. C'est le même remède que pour `.lanes` : les rangées sont portées
     par `.notes`, chaque repère les hérite en `subgrid`.

     Repli sans `subgrid` : les colonnes se décalent comme avant, rien ne
     casse.
  */
  @supports (grid-template-rows: subgrid) {
    .note {
      display: grid;
      grid-row: span var(--rows);
      grid-template-rows: subgrid;
      align-content: start;
    }
  }

  /*
     ⚠️ L'ESPACE DU BAS EST PORTÉ PAR LE DERNIER ÉLÉMENT, PAS PAR LE REPÈRE.

     `.note` ne peut pas avoir de `padding-bottom` ici, et c'est pour ça qu'il
     est mis à zéro : chaque repère occupe des rangées PARTAGÉES (`subgrid`),
     et une marge sous sa dernière rangée ferait dépasser sa boîte dans la
     rangée du repère d'en dessous — sur une grille à deux étages comme
     « Quatre terrains », la boîte du haut passait par-dessus le filet du bas.

     D'où le remède : la marge est posée sur le DERNIER ENFANT du repère.
     C'est alors la rangée qui grandit, donc toutes les colonnes d'un même
     étage ensemble, et la dernière ligne de texte ne touche plus le filet.
  */
  .note {
    padding-bottom: 0;
  }

  .note > :last-child {
    padding-bottom: clamp(1.7rem, 3vw, 2.4rem);
  }

  .note:last-child {
    border-bottom: 0;
  }

  /*
     ⚠️ LE RETRAIT DE DROITE EST OBLIGATOIRE, PAS DÉCORATIF. Il n'y avait que
     celui de gauche : la description d'un repère courait alors jusqu'au bord
     de sa colonne et venait toucher le filet vertical du repère suivant.
     « on se déplace pour vous voir s'il le faut » collait au trait, et la
     colonne paraissait mal calée.
  */
  .note {
    padding-inline-end: var(--note-gouttiere);
  }

  /* Le trait vertical et le retrait qui va avec, sur tout repère qui n'ouvre
     pas une rangée. La croix s'y centre alors sur le croisement des deux
     filets ; celle des ouvreurs de rangée reste alignée sur le texte. */
  .note + .note {
    border-left: 1px solid var(--line);
    padding-inline-start: var(--note-gouttiere);
  }

  .note + .note .note__cross {
    transform: translate(-50%, -50%);
  }

  /*
     ⚠️ `nth-child` n'accepte pas de variable CSS : le nombre de colonnes ne
     peut pas être une valeur `--cols` ici. D'où ces trois variantes écrites à
     la main, une par nombre de colonnes possible. Si vous ajoutez un
     `.notes--5`, ajoutez aussi sa règle ci-dessous.
  */
  /*
     ⚠️ ET ELLE REND À DROITE CE QU'ELLE NE PREND PAS À GAUCHE. Sans ce
     retrait, la colonne d'ouverture disposait de 370,7 px de texte à 1440 px
     quand les deux autres en avaient 326,9 : les pistes étaient égales au
     centième, les colonnes de TEXTE non, et une ligne se coupait ailleurs
     dans la première. `2 × gouttière + 1px` rend exactement ce que le filet
     et le retrait de gauche prennent aux autres.
  */
.notes .note:nth-child(3n + 1),
.notes--4 .note:nth-child(4n + 1) {
    border-left: 0;
    padding-inline-start: 0;
    padding-inline-end: calc(2 * var(--note-gouttiere) + 1px);
  }
.notes .note:nth-child(3n + 1) .note__cross,
.notes--4 .note:nth-child(4n + 1) .note__cross {
    transform: translateY(-50%);
  }

  /* Les variantes rétablissent le compte de colonnes que `.notes--*` change,
     et neutralisent la règle à 3 colonnes ci-dessus quand elle ne s'applique
     pas. */
.notes--4 .note:not(:nth-child(4n + 1)) {
    border-left: 1px solid var(--line);
    padding-inline-start: var(--note-gouttiere);
    padding-inline-end: var(--note-gouttiere);
  }
.notes--4 .note:not(:nth-child(4n + 1)) .note__cross {
    transform: translate(-50%, -50%);
  }
}


.note__cross {
  position: absolute;
  top: 0;
  left: 0;
  width: 13px;
  height: 13px;
  color: var(--terra);
  transform: translateY(-50%);
}

.note__cross::before,
.note__cross::after {
  content: '';
  position: absolute;
  background: currentColor;
}

.note__cross::before {
  top: 0;
  left: 50%;
  width: 1px;
  height: 100%;
  transform: translateX(-50%);
}

.note__cross::after {
  top: 50%;
  left: 0;
  width: 100%;
  height: 1px;
  transform: translateY(-50%);
}

/* Les trois noms partent de la même ligne du haut : c'est là que l'œil les
   compare, et « Partout en Suisse romande » tient sur deux lignes quand les
   deux autres n'en font qu'une. La marge de la coordonnée ci-dessous est
   calculée pour que le nom le plus long garde malgré tout de l'air. */
.note__name {
  align-self: start;
  font-size: clamp(1.45rem, 2.5vw, 2rem);
  line-height: 1.06;
  text-transform: uppercase;
}

/* La coordonnée en mono : la chasse fixe aligne les chiffres, et c'est le
   seul endroit du site où la carte topographique du fond devient un propos. */
.note__meta {
  margin-top: 1.05rem;
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--terra-text);
}

.note__desc {
  max-width: 26rem;
  margin-top: 1.05rem;
  font-size: 0.95rem;
  line-height: 1.62;
  color: var(--ink-2);
}

/* ------------------------- 07b. ACCUEIL : ANNONCE + SOMMAIRE ----------- */
/*
   L'accueil n'a pas de photo — celle du studio appartient à la page podcast.
   Il repose sur deux blocs : une annonce typographique, puis les quatre
   services en bandes numérotées, comme le sommaire d'une revue.
*/

/* Annonce ---------------------------------------------------------------- */

/* Pas `.section` : le fond animé doit rester à pleine force ici, comme dans
   le hero de la page podcast (le voile ne s'applique qu'aux `.section`). */
/*
   ⚠️ LE HERO REMPLIT LE PREMIER ÉCRAN, ET C'EST VOULU.

   À l'arrivée sur le site, on ne doit voir que du blanc : si le panneau noir du
   sommaire dépasse ne serait-ce que d'une bande en bas de la fenêtre, la page
   se lit comme coupée en deux au lieu de s'ouvrir.

   D'où le `min-height`. Les 76 px retranchés sont l'en-tête collant, qui
   occupe bien sa place dans le flux : mesuré, il fait 79 px avec le petit logo
   et 82 px avec le grand. On retranche donc VOLONTAIREMENT MOINS que sa
   hauteur réelle — l'erreur se paie alors en quelques pixels de blanc en trop,
   jamais en noir qui dépasse. Ne « corrigez » pas cette valeur vers 82.

   `svh` et non `vh` : sur mobile, `vh` vaut la hauteur SANS les barres du
   navigateur, donc la page déborderait tant qu'elles sont visibles. `svh` est
   la petite hauteur, celle qui garantit le pli. Le `vh` juste avant sert de
   repli aux navigateurs qui ignorent `svh`.

   Le contenu est centré dans cette hauteur : sinon, sur un grand écran, tout
   se tasserait en haut sous un vide.
*/
.home-hero {
  position: relative;
  z-index: 1;
  display: grid;
  align-content: center;
  min-height: calc(100vh - 76px);
  min-height: calc(100svh - 76px);
  padding-block: clamp(4.5rem, 11vw, 10rem) clamp(4rem, 8vw, 7.5rem);
}

/*
   Une colonne par défaut : le texte, puis les pictogrammes en dessous. À
   partir de 1000 px, deux colonnes côte à côte — c'est la composition de la
   maquette, texte à gauche et grille de métiers à droite.
*/
.home-hero__inner {
  display: grid;
  gap: clamp(2.2rem, 5vw, 4.5rem);
  max-width: 62rem;
}

.home-hero__text {
  display: grid;
  gap: clamp(1.4rem, 2.6vw, 2.1rem);
  align-content: start;
}

@media (min-width: 1000px) {
  .home-hero__inner {
    max-width: none;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    align-items: center;
  }
}

/* --------------------- 06b. CARTE DES MÉTIERS (accueil) ---------------- */
/*
   À droite de l'annonce : un relevé topographique sur lequel sont posés les
   quatre métiers. Il remplace la grille de pictogrammes — un relief commun
   dit mieux que quatre cases que les métiers appartiennent au même
   écosystème, et qu'on peut pourtant n'en prendre qu'un.

   Le relief est dessiné en canvas par assets/js/heromap.js : courbes de
   niveau extraites d'un champ de bruit, points qui cheminent dessus,
   réactions au survol. Ici, il n'y a que le cadre, les couleurs et les
   quatre repères — qui sont de VRAIS liens, écrits dans index.html, donc
   présents même sans JavaScript.

   Les deux jetons `--hm-*` sont lus PAR LE SCRIPT (le canvas ne connaît pas
   les variables CSS) : c'est le seul endroit où les couleurs de la carte
   sont écrites, et elles suivent le thème comme le reste du site.
*/

.hero-map {
  --hm-line: var(--line-strong);
  --hm-red: var(--terra);

  position: relative;
  width: 100%;
  max-width: 34rem;
  margin-inline: auto;
  /* Carré, comme le champ : la position des repères, en pourcentage, se lit
     alors directement dans le repère du script. */
  aspect-ratio: 1 / 1;
  color: var(--ink);
}

@media (min-width: 1000px) {
  .hero-map {
    max-width: none;
  }
}

/*
   Le canvas déborde du cadre de 8 % de chaque côté, et son dessin s'efface
   en dégradé sur la bordure. Sans ces deux choses, les courbes butaient sur
   le bord du cadre et s'y coupaient net, juste au-dessus de « Podcast » :
   la carte avait l'air posée sur un carré. Là, le relevé continue et se
   perd, comme un extrait de carte.

   `mask` n'est pas encore universel sans préfixe : les deux lignes sont
   nécessaires, et l'absence de support ne coûte qu'un bord franc.
*/
.hero-map__canvas {
  position: absolute;
  inset: -8%;
  width: 116%;
  height: 116%;
  /* La règle générale plafonne les canvas à la largeur de leur parent — ici
     il doit justement la dépasser. */
  max-width: none;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(closest-side, #000 84%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 84%, transparent 100%);
}

/* Les quatre repères -------------------------------------------------------- */
/*
   `--x` / `--y` placent chaque repère en pourcentage de la carte, et c'est
   la seule source de vérité : le script relève la position réelle de la
   pastille pour savoir quelles courbes allumer au survol. Ils sont ici et
   non dans le HTML parce qu'ils changent avec le format — sur téléphone,
   les libellés sont grands par rapport à la carte et doivent se resserrer.

   Le libellé est posé À CÔTÉ de la pastille, à gauche ou à droite selon la
   place disponible : c'est ce qui permet de garder les quatre repères dans
   le cadre sans jamais recouvrir le sommet du relief.

   Les quatre positions ne sont pas réparties en croix : la carte doit
   rester un relevé, pas un diagramme. Aucune ne dit un ordre.
*/
.hero-map__nodes {
  position: absolute;
  inset: 0;
}

.hm-node {
  position: absolute;
  left: var(--x);
  top: var(--y);
  display: flex;
  align-items: center;
  gap: 0.85rem;
  /* La pastille, et non le bloc, tombe sur le point : le texte est centré
     verticalement sur elle. */
  transform: translate(-100%, -50%);
  color: var(--ink);
  white-space: nowrap;
  text-align: right;
}

/* Les deux repères dont le libellé part vers la droite. */
.hm-node--droite {
  flex-direction: row-reverse;
  transform: translate(0, -50%);
  text-align: left;
}

/*
   Le halo. Les libellés sont posés à même le relevé, sans cartouche : sans
   ce voile de la couleur du fond, une courbe qui passe derrière une lettre
   la rendrait illisible. C'est le procédé des cartes imprimées, et il coûte
   moins qu'un aplat — le dessin continue de se voir tout autour du mot.
*/
.hm-node__text {
  display: grid;
  gap: 0.42rem;
  text-shadow: 0 0 4px var(--bg), 0 0 4px var(--bg), 0 0 8px var(--bg);
}

.hm-node__name {
  font-size: clamp(0.62rem, 0.78vw, 0.74rem);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1;
  transition: color 0.45s var(--ease);
}

/*
   ⚠️ LE PETIT FILET ROUGE EST SOUS LE NOM, ET IL VIENT D'AILLEURS. Chaque
   repère portait ses coordonnées — « 46.9930° N / 6.9310° E » — et ce trait les
   soulignait comme l'échelle d'une carte. Les coordonnées ont été retirées : de
   faux relevés géodésiques sous quatre noms de métier, ça faisait décor de
   carte au lieu de carte.

   Le trait, lui, reste : c'est l'un des rares rappels de rouge de cette moitié
   de page, et il tient le repère à sa ligne. Il se range du côté opposé au
   texte selon que le repère pointe à gauche ou à droite.
*/
.hm-node__name {
  position: relative;
  padding-bottom: 0.55rem;
}

.hm-node__name::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0.1rem;
  width: 22px;
  height: 1px;
  background: var(--terra);
  opacity: 0.6;
}

.hm-node--droite .hm-node__name::after {
  right: auto;
  left: 0;
}

/*
   La pastille : un disque rouge plein, détouré par un anneau de la couleur
   du fond. Sans cet anneau, le point se perdrait dans les courbes qui
   passent dessous.
*/
.hm-node__pin {
  flex: none;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--terra);
  box-shadow: 0 0 0 4px var(--bg), 0 0 0 5px rgba(179, 31, 34, 0.45);
  transition: transform 0.5s var(--ease), box-shadow 0.5s var(--ease);
}

/* ------------------------- APPARITION, UNE SEULE FOIS -------------------- */
/*
   Le relevé se trace (c'est le script qui le pilote), puis les quatre
   repères se posent — ENSEMBLE. Les décalages sont de quelques dizaines de
   millisecondes, juste assez pour éviter l'effet « bloc », jamais assez
   pour qu'on lise un parcours 1 → 2 → 3 → 4 : les quatre métiers sont sur
   le même plan, l'animation ne doit pas suggérer un ordre.

   Tout est sous `.js` : sans JavaScript, rien n'est masqué.
*/
.js .hero-map .hm-node {
  opacity: 0;
}

.js .hero-map.is-in .hm-node {
  opacity: 1;
  transition: opacity 0.75s var(--ease) calc(1.35s + var(--nd, 0ms));
}

/* ------------------------------- AU SURVOL ------------------------------- */
/*
   Le métier survolé prend la main : sa pastille grossit, son libellé passe
   au rouge, les courbes qui le longent s'allument (le script les repère par
   leur distance au point) et tout le reste de la carte recule d'un cran. Le
   relief, lui, a sa propre micro-réaction, une par métier — voir heromap.js.
*/
.hm-node:hover .hm-node__pin,
.hm-node:focus-visible .hm-node__pin {
  transform: scale(1.35);
  box-shadow: 0 0 0 4px var(--bg), 0 0 0 6px rgba(179, 31, 34, 0.75);
}

.hm-node:hover .hm-node__name,
.hm-node:focus-visible .hm-node__name {
  color: var(--terra-text);
}

.hero-map.is-focused .hm-node {
  opacity: 0.4;
  transition: opacity 0.4s var(--ease);
}

.hero-map.is-focused .hm-node.is-active {
  opacity: 1;
}

/* Les positions ------------------------------------------------------------ */

.hm-node[data-hm-node='podcast'] {
  --x: 44%;
  --y: 14%;
  --nd: 0ms;
}

.hm-node[data-hm-node='photo'] {
  --x: 76%;
  --y: 40%;
  --nd: 40ms;
}

.hm-node[data-hm-node='web'] {
  --x: 26%;
  --y: 62%;
  --nd: 20ms;
}

.hm-node[data-hm-node='reseaux'] {
  --x: 68%;
  --y: 84%;
  --nd: 60ms;
}

/*
   ENTRE 1000 ET 1440 PX, LES QUATRE REPÈRES SE REGROUPENT À DROITE.

   C'est la contrepartie du grand titre : tant que la fenêtre n'est pas
   large, l'annonce court jusqu'à la moitié de la carte (voir
   `.home-hero__title`). Elle peut passer sur le relief — c'est un décor —
   mais pas sur les libellés : deux textes qui se chevauchent, on n'en lit
   aucun des deux. Ils se rangent donc au-delà de sa plus longue ligne, et
   passent tous du côté droit de leur pastille pour ne pas revenir vers
   elle.

   Au-delà de 1440 px le titre plafonne à 5,4 rem pendant que la carte, elle,
   continue de s'élargir : il ne mord plus que sur son extrême gauche, et les
   repères retrouvent leur position étalée.
*/
@media (min-width: 1000px) and (max-width: 1439px) {
  .hm-node[data-hm-node='podcast'] {
    --x: 62%;
    --y: 15%;
  }

  .hm-node[data-hm-node='photo'] {
    --x: 68%;
    --y: 39%;
  }

  .hm-node[data-hm-node='web'] {
    --x: 61%;
    --y: 63%;
  }

  .hm-node[data-hm-node='reseaux'] {
    --x: 62%;
    --y: 86%;
  }

  /* Les deux repères qui portaient leur libellé à gauche passent à droite :
     sinon ils le renverraient vers le titre. Même bascule que
     `.hm-node--droite`, appliquée le temps de cette plage. */
  .hm-node[data-hm-node='podcast'],
  .hm-node[data-hm-node='web'] {
    flex-direction: row-reverse;
    transform: translate(0, -50%);
    text-align: left;
  }

  .hm-node[data-hm-node='podcast'] .hm-node__name::after,
  .hm-node[data-hm-node='web'] .hm-node__name::after {
    right: auto;
    left: 0;
  }
}

/*
   Sur petit écran, les libellés ne rétrécissent pas autant que la carte :
   les repères se resserrent donc vers le centre, sinon le plus large
   (« Réseaux sociaux ») sortirait du cadre.
*/
@media (max-width: 700px) {
  .hm-node[data-hm-node='podcast'] {
    --x: 46%;
    --y: 13%;
  }

  .hm-node[data-hm-node='photo'] {
    --x: 62%;
    --y: 41%;
  }

  .hm-node[data-hm-node='web'] {
    --x: 32%;
    --y: 62%;
  }

  .hm-node[data-hm-node='reseaux'] {
    --x: 54%;
    --y: 85%;
  }
}

/* Les coordonnées, elles, encombrent plus qu'elles n'informent à cette
   taille : ce sont quatre libellés qu'on veut lire. */
@media (max-width: 560px) {
  .hero-map {
    max-width: 22rem;
  }
}

/*
   Mouvement réduit : la carte est dessinée d'un coup et ne bouge plus. Le
   script coupe déjà sa boucle ; il reste à annuler l'état de départ des
   repères, qui sinon resteraient invisibles.
*/
@media (prefers-reduced-motion: reduce) {
  .js .hero-map .hm-node {
    opacity: 1;
  }
}

/*
   Quatre lignes, une par métier — chacune doit tenir sur UNE ligne, sinon
   l'énumération perd son rythme. La contrainte vient de « On photographie. »,
   la plus longue : elle mesure 10,12 em. Le corps est donc plafonné à la
   largeur utile divisée par 10,12. À remesurer si le texte change.

   En une colonne (sous 1000 px), la largeur utile est celle du conteneur :
   7.4vw tient de 302 px — où le minimum de 1.4rem prend le relais — jusqu'au
   maximum de 5.6rem.
*/
/*
   Trois paliers, et non un seul, parce que la part de la fenêtre réellement
   disponible pour le titre change avec la largeur : la bande rouge et les
   marges latérales en retirent proportionnellement beaucoup plus sur un
   téléphone que sur un écran large.

   La ligne contraignante est « On photographie. », qui mesure 9,741 em en
   Inter 900 avec l'interlettrage de -0.03em. Le corps maximal est donc la
   largeur disponible divisée par 9,741. Mesuré, ce maximum vaut 7,54vw à
   320 px, 8,6vw à partir de 600 px, et 4,2vw dès que le hero passe en deux
   colonnes. Les valeurs ci-dessous s'en approchent en gardant 3 à 6 % de
   marge.

   ⚠️ Si vous changez le texte du titre, remesurez : ces valeurs sont justes,
   pas larges.
*/
/*
   ⚠️ CETTE RÈGLE SERT AUSSI AU TITRE DE LA PAGE PODCAST (`.hero__title`).
   Les deux doivent rester identiques : c'est une demande explicite, le titre
   du studio reprend exactement la typographie et le corps de celui de
   l'accueil. Si vous modifiez l'un, vous modifiez l'autre — c'est voulu.

   La ligne contraignante reste « On photographie. » de l'accueil : « STUDIO »
   et « PODCAST » sont bien plus courts et tiennent avec de la marge.
*/
/*
   ⚠️ La famille, la graisse, l'interlettrage et les capitales sont PARTAGÉS
   avec le titre de la page podcast (`.hero__title`) : les deux doivent se
   ressembler. Seul le CORPS diffère, et il est redéclaré plus haut pour le
   podcast, qui dispose de toute la largeur là où celui-ci n'a que sa moitié
   gauche.
*/
.home-hero__title,
.hero__title {
  font-weight: 900;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

/* L'interligne n'est plus partagé : celui du podcast est solidaire de
   `.page-head h1` (1.02), celui de l'accueil garde le sien. */
/*
   ⚠️ LE CORPS EST CALCULÉ SUR LA LARGEUR UTILE, PAS CHOISI. « ON
   PHOTOGRAPHIE. » est la ligne la plus longue et mesure 10,12 em ; le corps
   maximal est donc la largeur disponible divisée par 10,12, sans quoi elle se
   coupe en deux et l'énumération perd son rythme.

   ⚠️ 8,6vw ET NON 7,3 : la bande latérale ne prend plus que 10 px sous 700 px
   (voir section 02b), la largeur utile passe donc de 320 à 342 px sur un
   écran de 390. Sans ce relèvement, le titre de page restait à 28,5 px quand
   les titres de section en font 33,6 — la hiérarchie s'inversait sur
   téléphone.

   À remesurer si le texte du titre change, ou si la bande reprend sa largeur.
*/
.home-hero__title {
  line-height: 0.98;
  font-size: clamp(1.5rem, 8.6vw, 5.4rem);
}

@media (min-width: 600px) {
  .home-hero__title {
    font-size: clamp(1.3rem, 8.3vw, 5.4rem);
  }
}

/*
   En deux colonnes, le titre NE SE LIMITE PLUS à sa colonne : il reprend le
   corps des titres de pages (`.page-head h1`, clamp(2rem, 7vw, 5.4rem)) et
   déborde sur la carte, qui est un décor et se laisse traverser. C'est un
   choix assumé — l'annonce doit être ce qu'on voit en premier, la carte ce
   qu'on remarque ensuite.

   Deux conséquences à tenir :
   — `white-space: nowrap` sur les lignes, sinon « On photographie. » se
     couperait en deux dans sa colonne au lieu de déborder ;
   — `z-index` sur le titre, pour qu'il passe DEVANT le canvas de la carte.

   ⚠️ Ce corps est solidaire de celui de `.page-head h1` : c'est une demande
   explicite, l'accueil doit annoncer aussi fort que les pages métier.
*/
@media (min-width: 1000px) {
  .home-hero__title {
    position: relative;
    z-index: 2;
    font-size: clamp(2rem, 7vw, 5.4rem);
  }

  .home-hero__title span {
    white-space: nowrap;
  }
}

.home-hero__title span {
  display: block;
}

/* La dernière ligne en rouge : elle ferme l'énumération et annonce la couleur
   du reste de la page.

   Elle prend --terra-text et non --terra : en thème clair les deux jetons ont
   exactement la même valeur, mais sur fond sombre le rouge profond tombe à
   2,5:1, sous le seuil de 3:1 des grands textes. C'est le seul grand titre du
   site peint en rouge, donc le seul concerné. */
.home-hero__title span:last-child {
  color: var(--terra-text);
}

/*
   Le corps a été baissé d'environ un sixième : en mono, l'ancienne valeur
   (jusqu'à 20 px) faisait un pavé qui concurrençait le titre. Il reste
   au-dessus du texte courant, ce qui suffit à le tenir pour une accroche.
*/
.home-hero__lede {
  max-width: 38rem;
  color: var(--ink-2);
  font-size: clamp(0.92rem, 1.2vw, 1.05rem);
  line-height: 1.65;
}

/* Sommaire des services --------------------------------------------------- */

/*
   C'est `.lanes` qui porte les colonnes, pas chaque bande : sans cela chaque
   bande dimensionnerait sa propre colonne de nom sur son propre texte, et les
   descriptions ne seraient plus alignées d'une bande à l'autre. La colonne des
   noms se cale donc sur le plus long (« RÉSEAUX SOCIAUX »), sans valeur magique.
*/
.lanes {
  /* Cinq pistes : repère, nom, description, prix, flèche. La quatrième est
     nouvelle — voir `.lane__prix`. */
  display: grid;
  grid-template-columns:
    auto minmax(0, max-content) minmax(0, 1.05fr) minmax(0, max-content) auto;
  border-top: 1px solid var(--line);
}

/*
   `isolation: isolate` est indispensable : le voile rouge est un ::before en
   z-index -1, qui doit passer derrière le texte de la bande mais devant le
   fond de la page. Sans contexte d'empilement propre, il disparaîtrait
   derrière la section.
*/
.lane {
  position: relative;
  isolation: isolate;
  display: grid;
  /* Repli si `subgrid` manque : les mêmes colonnes, chaque bande pour elle.
     Les noms ne s'alignent alors plus entre eux, mais rien ne casse. */
  grid-template-columns:
    auto minmax(0, max-content) minmax(0, 1.05fr) minmax(0, max-content) auto;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.75rem);
  padding: clamp(1.3rem, 2.8vw, 2.2rem) clamp(0.6rem, 1.6vw, 1.4rem);
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  transition: color 0.35s var(--ease);
}

/* Les quatre bandes empruntent les colonnes de `.lanes`. Au-dessus de 900 px
   les enfants se placent tout seuls dans l'ordre du HTML (repère, nom,
   description, flèche) : aucune zone nommée n'est nécessaire. */
@supports (grid-template-columns: subgrid) {
  .lane {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }
}

.lane::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--terra);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.5s var(--ease);
}

.lane:hover::before,
.lane:focus-visible::before {
  transform: scaleX(1);
}

.lane:hover,
.lane:focus-visible {
  color: var(--terra-contrast);
}

/*
   Le repère de la bande : la petite croix rouge du site, celle des repères de
   « Où nous trouver » et de la pastille du hero. Deux pseudo-éléments d'un
   pixel plutôt qu'un SVG — c'est un trait, pas un dessin, et il doit rester
   net à toutes les tailles.
*/
.lane__mark {
  position: relative;
  width: 12px;
  height: 12px;
  color: var(--terra);
  transition: color 0.35s var(--ease);
}

.lane__mark::before,
.lane__mark::after {
  content: '';
  position: absolute;
  background: currentColor;
}

.lane__mark::before {
  top: 0;
  left: 50%;
  width: 1px;
  height: 100%;
  transform: translateX(-50%);
}

.lane__mark::after {
  top: 50%;
  left: 0;
  width: 100%;
  height: 1px;
  transform: translateY(-50%);
}

.lane__name {
  font-size: clamp(1.7rem, 4.2vw, 3.4rem);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
}

/* 1rem et non 0.93 : sur le panneau noir, la mono à 14 px devenait pénible à
   lire. C'est la seule description longue du site posée sur du sombre. */
.lane__desc {
  font-size: 1rem;
  line-height: 1.62;
  color: var(--ink-2);
  transition: color 0.35s var(--ease);
}

/* Au survol, tout le contenu passe sur le rouge : les couleurs propres au
   repère, à la description et à la fin de bande doivent céder la place. */
.lane:hover .lane__mark,
.lane:focus-visible .lane__mark,
.lane:hover .lane__prix,
.lane:focus-visible .lane__prix,
.lane:hover .lane__prix span,
.lane:focus-visible .lane__prix span,
.lane:hover .lane__desc,
.lane:focus-visible .lane__desc,
.lane:hover .lane__go,
.lane:focus-visible .lane__go {
  color: inherit;
}

/*
   LE PRIX D'ENTRÉE DU MÉTIER.

   ⚠️ IL A SA PROPRE COLONNE, ET C'EST TOUT SON INTÉRÊT. Les quatre montants
   tombent sur le même axe d'une bande à l'autre et se lisent comme une
   colonne de tarifs — ce qu'ils sont. Glissés dans la description, ils
   auraient été quatre chiffres à chercher dans quatre paragraphes.

   Il est composé dans la police des titres, comme tous les montants du site
   (`.price__amount`) : un prix est un nombre qu'on lit d'un coup d'œil, pas
   une étiquette technique.

   Le « dès » reste en retrait — c'est une nuance, pas l'information.
*/
.lane__prix {
  justify-self: end;
  font-family: var(--font-title);
  font-size: clamp(0.95rem, 1.5vw, 1.2rem);
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--ink);
  transition: color 0.35s var(--ease);
}

.lane__prix span {
  font-weight: 500;
  color: var(--ink-3);
  transition: color 0.35s var(--ease);
}

.lane__prix b {
  font-weight: inherit;
}

/*
   La fin de bande : une flèche, rien d'autre. Elle a d'abord été enfermée
   dans un rond — or un rond est un bouton, et la bande entière est déjà le
   lien : il promettait un second point de clic qui n'existe pas. Elle a
   ensuite été posée sur un filet qui s'allongeait au survol, et c'était pire :
   le filet poussait la flèche, qui se remettait en place au moindre
   déplacement du pointeur sur la bande. Ça se lisait comme un défaut
   d'affichage, pas comme une animation.

   ⚠️ ELLE NE BOUGE DONC PLUS. Le survol ne change que sa couleur, avec le
   reste de la bande. Toute géométrie animée ici — largeur, marge, translation
   — ramènera le même tremblement.
*/
.lane__go {
  display: flex;
  align-items: center;
  color: var(--terra-text);
  transition: color 0.35s var(--ease);
}

.lane__go svg {
  width: 22px;
  height: 22px;
  flex: none;
}

/*
   Sous 900 px la description passe sous le nom, et le repère tient la colonne
   de gauche sur les deux lignes. La bande redevient une grille autonome :
   `.lanes` cesse d'être une grille, donc plus de subgrid à hériter, et les
   zones nommées reprennent la main sur le placement automatique.
*/
@media (max-width: 900px) {
  .lanes {
    display: block;
  }

  .lane {
    grid-column: auto;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      'mark name go'
      'mark prix prix'
      'mark desc desc';
    align-items: center;
    row-gap: 0.55rem;
  }

  /* Calé sur la première ligne du nom, pas au milieu des deux rangées :
     c'est le titre qu'il repère, pas le bloc entier. */
  .lane__mark {
    grid-area: mark;
    align-self: start;
    margin-top: 0.8em;
  }

  .lane__name {
    grid-area: name;
  }
  .lane__desc {
    grid-area: desc;
  }

  /* ⚠️ SOUS LE NOM, PAS À CÔTÉ DE LA FLÈCHE. En colonne étroite le prix
     serrait le nom contre le bord ; sur sa propre ligne, il se lit et le nom
     garde sa place. Il repasse au fer à gauche : il n'y a plus de colonne de
     prix à aligner, donc plus de raison de le cadrer à droite. */
  .lane__prix {
    grid-area: prix;
    justify-self: start;
  }
  .lane__go {
    grid-area: go;
  }
}

@media (max-width: 480px) {
  .lane__go {
    width: 38px;
    height: 38px;
  }
}

/* --------------------------------------------- 08. CONTENU ------------- */

/*
   La tête de page remplit le premier écran, comme les heros de l'accueil et
   de la page podcast, et pour la même raison : en arrivant sur une page de
   service on ne doit voir que du blanc, la première section noire commence
   sous le pli.

   Même valeur retranchée que pour `.home-hero`, mêmes précautions — les 76 px
   sont l'en-tête collant, volontairement sous-estimés (il fait 79 à 82 px), et
   `svh` plutôt que `vh` pour que le pli soit garanti sur mobile barres
   visibles. Tout est expliqué en détail sur `.home-hero`.
*/
.page-head {
  position: relative;
  overflow: hidden;
  display: grid;
  align-content: center;
  min-height: calc(100vh - 76px);
  min-height: calc(100svh - 76px);
  padding-block: clamp(3.5rem, 8vw, 6.5rem) clamp(2.25rem, 4.5vw, 3.5rem);
}

/* ⚠️ PAS DE FILET SOUS LE CHAPEAU. Il y en avait un, et il ne séparait rien :
   la section suivante commence déjà par son propre blanc, et le trait posé en
   travers de la page au moment où l'on quitte le relief se lisait comme une
   césure de trop. Il était invisible sur les pages dont la section suivante
   est un panneau noir, et bien visible sur les autres — ce qui est la
   définition d'un trait qui n'a pas de rôle. */

.page-head__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 1.35rem;
  max-width: 52rem;
}

/*
   `min-width: 0` n'est pas décoratif : un élément de grille a par défaut
   `min-width: auto`, donc il refuse de descendre sous la largeur de son mot le
   plus long. Sur photo.html, « RESSEMBLENT. » en Inter 900 dépassait la colonne
   à 320 px et élargissait toute la page de 37 px. Avec 0, la grille reprend la
   main et le titre se contente de la place disponible.
*/
.page-head__inner > *,
.page-head__text {
  min-width: 0;
}

.page-head__text {
  display: grid;
  gap: 1.35rem;
}

/*
   Le pictogramme du métier, à droite du titre. Il reprend exactement le dessin
   de la case correspondante sur l'accueil : c'est ce qui fait qu'on reconnaît
   la page d'un coup d'œil.

   Sous 900 px il disparaît : la colonne de texte prend toute la largeur et il
   n'y a plus de place à sa droite.
*/
/* Le bandeau garde sa colonne de texte plafonnée : la droite appartient au
   décor animé, qui passe derrière (voir `.hero-scene`). Le pictogramme du
   métier qui l'occupait a été retiré avec sa colonne. */
@media (min-width: 900px) {
  .page-head__inner {
    max-width: 52rem;
  }
}

/*
   Le minimum de la fourchette est descendu de 2.6rem à 2rem : en Inter, plus
   large que l'ancien serif, le mot le plus long des titres de page ne tenait
   plus dans la colonne des petits écrans. Vérifié à 320 px sur les cinq pages
   à bandeau.
*/
.page-head h1 {
  font-size: clamp(2rem, 7vw, 5.4rem);
  font-weight: 900;
  /*
     1.02 et non 0.92 : les capitales accentuées d'Inter (É, À) montent plus
     haut que celles de l'ancien serif. À 0.92, l'accent du « GÉRÉS » de la
     deuxième ligne venait toucher la première. Les titres sans accent n'y
     perdent rien de visible.
  */
  line-height: 1.02;
  text-transform: uppercase;
}

.page-head .lede {
  max-width: 36rem;
}

/*
   Le chapeau court des pages métier. Au corps du `.lede`, un chapeau de trois
   lignes pèse presque autant que le titre et les deux se disputent l'entrée de
   page. Il prend donc le corps du sous-titre de la page podcast : les quatre
   pages métier s'annoncent ainsi de la même voix. C'est une demande explicite.
*/
.lede--sm {
  font-size: clamp(0.92rem, 1.2vw, 1.05rem);
  max-width: 32rem;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  height: 100%;
  padding: clamp(1.5rem, 2.8vw, 2.2rem);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: transform 0.4s var(--ease), border-color 0.4s var(--ease);
}

.section--panel .card {
  background: var(--bg);
}

.card:hover {
  transform: translateY(-4px);
  border-color: var(--line-strong);
}

.card h3 {
  font-size: 1.45rem;
}

.card p {
  color: var(--ink-2);
  font-size: 0.93rem;
}

/* Tarifs ------------------------------------------------------------------ */

/*
   LES TARIFS SONT DES REPÈRES POSÉS SUR UN FILET, EXACTEMENT COMME
   « OÙ NOUS TROUVER ».

   ⚠️ UNE VERSION CENTRÉE A ÉTÉ ESSAYÉE ET ABANDONNÉE. Elle était propre, mais
   elle ne ressemblait plus à ce site : tout est au fer à gauche ici, les
   titres, les repères, les bandes du sommaire, les réalisations. Trois
   colonnes centrées, c'est le tableau de prix de n'importe quel service en
   ligne — la mise en page devenait interchangeable, et l'identité partait
   avec.

   Ce qui la fait tenir maintenant, ce sont les mêmes trois choses que partout
   ailleurs sur le site : la croix rouge à l'intersection des filets, le fer à
   gauche, et la petite capitale espacée en guise d'étiquette.

   ⚠️ AUCUNE COLONNE N'EST MISE EN AVANT, ET C'EST VOULU. Il y a eu une
   étiquette « le plus choisi » et un nom en rouge sur la colonne du milieu :
   ça désignait un gagnant au lieu de laisser comparer, et le rouge servait
   d'argument de vente alors qu'il est, partout ailleurs sur ce site, un
   repère. Les trois colonnes sont au même niveau, en noir.
*/

/* Les colonnes n'ont pas de carte, donc pas de fond : c'est la bande qui en
   porte un, avec son dégradé de bord. Voir « Bandes opaques » plus haut. */
.prices {
  /* La gouttière d'une colonne de tarifs. Elle sert de part et d'autre de
     chaque filet vertical, et la colonne d'ouverture s'en sert pour rendre à
     droite ce qu'elle ne prend pas à gauche — voir plus bas. */
  --price-gouttiere: clamp(1.2rem, 2.4vw, 2rem);
  display: grid;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/*
   LA CROIX QUI FERME LA RANGÉE. Chaque colonne en porte une à son coin
   haut-gauche ; la dernière colonne n'avait donc rien à son extrémité droite,
   et le filet du haut s'arrêtait dans le vide. Celle-ci se pose sur le coin
   haut-droit de la bande entière, et la rangée est bornée aux deux bouts.

   `::after` et non `::before` : le `::before` porte déjà le fond opaque de la
   bande, voir « Bandes opaques ».
*/
.prices::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 13px;
  height: 13px;
  transform: translate(50%, -50%);
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}

/*
   ⚠️ LA COLONNE QUI OUVRE UNE RANGÉE N'A PAS DE RETRAIT À GAUCHE. C'est la
   même règle que pour les repères de « Où nous trouver » : le retrait
   accompagne le filet vertical, il ne le précède pas. Sans cela, le premier
   nom de formule était décalé de deux centimètres par rapport au titre de la
   section, et toute la bande paraissait mal calée.
*/
.price {
  position: relative;
  display: grid;
  align-content: start;
  padding-block: clamp(2rem, 3.4vw, 2.9rem);
}

/*
   LA CROIX DE REPÉRAGE, posée sur le coin haut-gauche de la colonne — donc à
   l'intersection du filet horizontal de la bande et du filet vertical qui
   sépare deux colonnes. C'est la croix des repères de « Où nous trouver », et
   avant cela la pastille centrale de la grille du hero. C'est elle qui relie
   cette section au reste du site.

   Elle est en `::after` et non en `::before` : le `::before` porte déjà
   l'étiquette. Et comme elle est en position absolue, elle ne compte pas dans
   les rangées de la grille.
*/
.price::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 13px;
  height: 13px;
  transform: translate(-50%, -50%);
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}

/* En colonne unique, les filets sont horizontaux. */
.price + .price {
  border-top: 1px solid var(--line);
}

@media (min-width: 880px) {
  .price + .price {
    border-top: 0;
  }

  .prices--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* Le retrait, sur toute colonne : à droite pour ne pas coller au filet
     suivant, à gauche seulement si un filet la précède. */
  .price {
    padding-right: var(--price-gouttiere);
  }

  .prices--3 > .price + .price {
    border-left: 1px solid var(--line);
    padding-left: var(--price-gouttiere);
  }

  /*
     ⚠️ LA COLONNE D'OUVERTURE REND À DROITE CE QU'ELLE NE PREND PAS À GAUCHE.
     Sans ce retrait, elle disposait de 381,4 px de texte à 1440 px quand les
     deux autres en avaient 348,4 : les pistes étaient égales au centième, les
     colonnes de TEXTE non, et une ligne de liste se coupait ailleurs dans la
     première. `2 × gouttière + 1px` rend exactement ce que le filet et le
     retrait de gauche prennent aux autres.
  */
  .prices--3 > .price:first-child {
    padding-right: calc(2 * var(--price-gouttiere) + 1px);
  }

  /* Quatre forfaits (la photo) tiennent d'abord en deux colonnes de deux : le
     filet vertical ne court qu'entre les colonnes, l'horizontal qu'entre les
     deux rangées. */
  .prices--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .prices--4 > .price:nth-child(even) {
    border-left: 1px solid var(--line);
    padding-left: var(--price-gouttiere);
  }

  /* Sur deux colonnes, ce sont les colonnes 1 et 3 qui ouvrent une rangée. */
  .prices--4 > .price:nth-child(odd) {
    padding-right: calc(2 * var(--price-gouttiere) + 1px);
  }

  .prices--4 > .price:nth-child(n + 3) {
    border-top: 1px solid var(--line);
  }

  /*
     ⚠️ LE NOMBRE DE RANGÉES EST UNE VARIABLE, `--price-rows`, POSÉE SUR
     `.prices`. Il valait 4 en dur, ce qui allait tant que toutes les pages
     avaient le même gabarit de colonne. La page sites web en compte cinq —
     ses colonnes portent une ligne de suivi mensuel sous la liste — et un
     chiffre écrit en dur ne pouvait plus servir les deux. Sans valeur posée,
     c'est 4.

     QUATRE RANGÉES PORTÉES PAR `.prices`, EMPRUNTÉES PAR CHAQUE COLONNE.
     Titre, montant, liste, bouton : chacun se pose sur la même ligne d'une
     colonne à l'autre, quelle que soit la longueur des textes. Des hauteurs
     plancher avaient été essayées d'abord et ne suffisaient pas — il reste
     toujours une liste qui prend une ligne de plus que ses voisines.

     ⚠️ QUATRE, C'EST LE NOMBRE D'ENFANTS D'UNE COLONNE. Il y en a eu sept
     tant qu'un `::before` portait « le plus choisi » — un pseudo-élément d'un
     conteneur de grille EST un élément de grille — puis six, du temps où
     chaque colonne portait un résumé et une unité sous son nom. Si vous
     ajoutez ou retirez une ligne dans le HTML d'une colonne, ce chiffre
     change ici.
  */
  @supports (grid-template-rows: subgrid) {
    .price {
      grid-row: span var(--price-rows, 4);
      grid-template-rows: subgrid;
    }

    .price .btn {
      align-self: end;
      justify-self: start;
    }
  }

  /* Repli sans `subgrid` : le bouton est poussé en bas de sa colonne, ce qui
     rattrape l'essentiel sans rien casser. */
  @supports not (grid-template-rows: subgrid) {
    .price .btn {
      margin-top: auto;
      justify-self: start;
    }
  }
}

/* Quatre de front seulement à partir de 1300 px : en dessous, la colonne
   descend sous 250 px et « dès CHF 250.– » se coupe en deux. */
@media (min-width: 1300px) {
  .prices--4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .prices--4 > .price:nth-child(n + 3) {
    border-top: 0;
  }

  .prices--4 > .price + .price {
    border-left: 1px solid var(--line);
    padding-left: var(--price-gouttiere);
  }

  /*
     ⚠️ L'OUVREUR CHANGE AVEC LE NOMBRE DE COLONNES. Sur deux colonnes, la
     troisième ouvrait la seconde rangée et portait donc la compensation de
     droite ; de front, elle a un filet à gauche comme les autres. On la lui
     retire ici — à égalité de spécificité avec la règle du dessus, c'est la
     position dans le fichier qui décide — et seule la première la garde.
  */
  .prices--4 > .price:nth-child(odd) {
    padding-right: var(--price-gouttiere);
  }

  .prices--4 > .price:first-child {
    padding-right: calc(2 * var(--price-gouttiere) + 1px);
  }

  .prices--4 .price__amount {
    font-size: clamp(1.5rem, 1.9vw, 2.05rem);
  }
}

.price h3 {
  font-size: clamp(1.2rem, 2vw, 1.5rem);
}

/*
   ⚠️ LE BOUTON D'UNE COLONNE EST PLUS SERRÉ QUE LES AUTRES, et il le faut.
   « RÉSERVER CETTE FORMULE » au gabarit courant fait 262 px ; une colonne de
   la page photo, à quatre de front, en fait 300. Le libellé passait à la
   ligne. On resserre le rembourrage et l'interlettrage — le dessin, les
   équerres et le balayage ne changent pas.
*/
.price .btn {
  margin-top: 1.6rem;
  padding: 0.85rem 1.15rem;
  letter-spacing: 0.11em;
}

/*
   LA LIGNE DE SUIVI, AU PIED D'UNE COLONNE. Ce n'est ni un élément de la
   formule — ceux-là sont au-dessus, dans la liste — ni le prix. C'est ce
   qu'on peut prendre AVEC, et ça se lit comme tel : séparé par un filet, en
   rouge, juste avant le bouton.
*/
.price__extra {
  align-self: end;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--terra-text);
}

.price__amount {
  margin-top: 1.1rem;
  font-family: var(--font-title);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: clamp(1.9rem, 3vw, 2.5rem);
  line-height: 1;
}

/*
   La liste de ce qui est compris. Une petite croix rouge par ligne plutôt
   qu'une coche : c'est le repère du site — celui des bandes du sommaire et des
   repères de « Où nous trouver » — à l'échelle d'une ligne de liste. Le filet
   au-dessus sépare le prix de son détail.

   ⚠️ Un tiret avait d'abord tenu ce rôle, ici et dans les options à la carte.
   Si vous en remettez un à un endroit, remettez-le aux deux : ces deux listes
   se lisent l'une sous l'autre dans la même section.
*/
.price ul {
  display: grid;
  /* `start` et non l'étirement par défaut : la rangée fait la hauteur de la
     plus longue liste, et sans cela une liste de cinq lignes voyait ses lignes
     s'écarter pour occuper la place d'une liste de sept. */
  align-content: start;
  gap: 0.6rem;
  width: 100%;
  margin: 1.6rem 0 1.8rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
  /* ⚠️ 0,88 rem ET NON 0,8. Ce qu'une formule comprend est ce qu'on lit le
     plus longtemps dans une colonne de tarifs, et c'était le plus petit texte
     de la section — plus petit que les métadonnées d'une réalisation. */
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--ink-2);
}

.price li {
  display: grid;
  grid-template-columns: 0.7rem 1fr;
  gap: 0.7rem;
  align-items: baseline;
}

.price li::before {
  content: '';
  width: 9px;
  height: 9px;
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}


/* L'écart entre deux blocs d'une même section, toujours le même. */
.sous-bloc {
  margin-top: clamp(2.75rem, 5.5vw, 4.25rem);
}

/* Bandes opaques ---------------------------------------------------------- */

/*
   ⚠️ TROIS SECTIONS POSENT UN FOND OPAQUE SOUS LEUR TEXTE, ET C'EST OBLIGÉ :
   le relevé des réalisations, les colonnes de tarifs et la liste des options.
   Aucune n'a de panneau, donc le champ de courbes animé de la page passe
   derrière, et on ne lit pas un prix ni une description par-dessus des lignes
   qui bougent.

   ⚠️ MAIS LE FOND NE DOIT PAS ÊTRE COUPÉ NET SUR LES CÔTÉS. Posé à plat sur
   l'élément, il s'arrêtait pile au bord du texte : les courbes disparaissaient
   d'un coup sur une arête verticale invisible, et cette arête se voyait
   justement parce qu'elle ne correspondait à rien.

   Un dégradé d'extinction a été essayé, puis abandonné — on voyait le dégradé.
   La bande va donc simplement D'UN BORD À L'AUTRE DE LA PAGE : un blanc franc,
   sans arête verticale nulle part, puisqu'il n'y a plus de bord vertical. Les
   seules limites sont les filets horizontaux du haut et du bas, et ceux-là on
   les a dessinés exprès.

   ⚠️ LE CALQUE EST BORNÉ AU PIXEL, PAS ÉTENDU AU HASARD. Une première version
   débordait de `100vw` de chaque côté : `body` porte `overflow-x: hidden`,
   donc la page ne défilait pas — mais `document.scrollWidth` annonçait 2796 px
   pour une fenêtre de 1424, et le jour où quelqu'un retire ce `hidden`, la
   page part en biais.

   Le calcul pose les deux bords exactement sur ceux de la page : `50%` est la
   demi-largeur de la bande, `(100vw - --rail-w) / 2` la demi-largeur de la
   zone de contenu, bande rouge déduite. La différence est le débord voulu, ni
   plus ni moins.

   `isolation: isolate` est indispensable : le calque est un `::before` en
   `z-index: -1`, qui doit passer derrière le texte de la bande mais devant le
   fond de la page. Sans contexte d'empilement propre, il disparaîtrait
   derrière la section. C'est le même montage que le voile rouge des bandes du
   sommaire, voir `.lane::before`.
*/
/* ⚠️ `.notes` A REJOINT LA LISTE, MAIS PAS DANS LES PANNEAUX. Un panneau
   porte déjà son aplat, et son relevé topographique est fait pour se voir
   derrière le texte : y poser une bande en plus masquerait le décor pour
   rien. Hors panneau — les canaux de la page contact, les informations de
   réservation — le champ de courbes animé passe derrière le texte, et une
   adresse ou un numéro se lit mal par-dessus des lignes qui bougent. */
.projects,
.prices,
.notes {
  position: relative;
  isolation: isolate;
}
.projects::before,
.prices::before,
.section:not(.section--panel) .notes::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0 calc(50% - (100vw - var(--rail-w)) / 2);
  background: var(--bg);
}

/* Projets ----------------------------------------------------------------- */

/*
   CINQ COLONNES : LE REPÈRE, LA VIGNETTE, LE NOM, LE RÉCIT, LES MÉTIERS.

   Les métiers sont revenus en pastilles au bout de la ligne. Ils ont été
   essayés en surtitre rouge au-dessus du nom : c'était plus discret, mais on
   ne pouvait plus balayer la colonne du regard pour trouver « tout ce qui est
   photo ». Au bout de la ligne, ils s'alignent d'un projet à l'autre et se
   lisent comme un index.

   ⚠️ LA VIGNETTE EST AU FORMAT TÉLÉPHONE (9/16) ET ELLE EST PETITE. CE SONT
   LES DEUX CONDITIONS DE SA PRÉSENCE.

   Une version en 16/10 a été montée puis retirée. Le problème n'était pas sa
   qualité : c'est qu'une vignette paysage montre UNE PHOTOGRAPHIE, et il n'y
   en a nulle part ailleurs sur ce site. La commande le vérifie en une
   seconde : à part ces vignettes, `grep "<img" *.html` ne renvoie rien. Le
   logo, la montagne de la page contact, le fond de toutes les pages sont
   dessinés — SVG et canevas. Poser des photos dans une seule section, sur
   quatre pages, cassait la seule chose qui tenait l'ensemble.

   Le format téléphone change la nature de l'objet : ce n'est plus une photo
   posée là pour décorer, c'est LA PUBLICATION elle-même, dans son cadre
   d'origine — un Reel, une vidéo TikTok, un post. Elle se justifie toute
   seule, et sa petite taille l'empêche de prendre la place du texte.

   Si vous l'agrandissez ou si vous la repassez en paysage, le problème
   revient tel quel.

   ⚠️ IL Y A EU AUSSI UNE QUATRIÈME COLONNE, à droite, qui portait un grand
   chiffre rouge. Retirée : collée au bord du site, et vide une ligne sur deux
   — tous les projets n'ont pas de résultat chiffrable. Le chiffre est
   retourné dans la ligne de métadonnées. Ne la remettez pas sans avoir un
   chiffre vrai pour CHAQUE projet.

   Les colonnes sont portées par `.projects` et empruntées en `subgrid` par
   chaque ligne : les noms commencent tous au même fer, les récits aussi.
*/

/* Le fond opaque du relevé et son dégradé de bord sont décrits plus haut,
   voir « Bandes opaques ». */
.projects {
  display: grid;
  border-top: 1px solid var(--line);
}

@media (min-width: 900px) {
  .projects {
    grid-template-columns:
      auto auto minmax(0, max-content) minmax(0, 1.35fr);
  }
}

.project {
  position: relative;
  display: grid;
  gap: 0.8rem;
  align-items: start;
  padding-block: clamp(1.6rem, 3.2vw, 2.5rem);
  border-bottom: 1px solid var(--line);
}

/*
   ⚠️ LA LIGNE NE S'OUVRE PLUS AU SURVOL, ET C'EST UNE CORRECTION, PAS UN
   RENONCEMENT. Elle gagnait un demi-rem de rembourrage haut et bas —
   `padding-block`, une propriété de mise en page. La ligne grandissait donc
   pour de vrai : tout ce qui la suit descendait, et la page se remettait en
   place à chaque passage de souris. Sur la dernière ligne d'une page déjà
   déroulée jusqu'en bas, cela se lit comme un soubresaut.

   Ce rembourrage faisait de la place au cadre, qui grandissait par-dessus sa
   ligne. Le cadre ne grandit plus (voir `.project__frame`) : la place n'est
   plus nécessaire.

   ⚠️ NE REMETTEZ RIEN ICI QUI TOUCHE À LA MISE EN PAGE — ni `padding`, ni
   `margin`, ni `height`, ni `gap`. Le survol d'une ligne de cette section ne
   doit coûter que `transform`, `opacity`, `filter` et des couleurs.

   Le filet rouge qui balaie le bas de la ligne remplace l'ouverture : il dit
   la même chose — il y a quelque chose à prendre ici — sans rien déplacer.
   C'est le geste du `.link-line` et du bouton, à l'échelle d'une ligne.
*/
.project::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0;
  height: 1px;
  background: var(--terra);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.55s var(--ease);
}

.project:hover::after,
.project:focus-within::after {
  transform: scaleX(1);
}

@media (min-width: 900px) {
  /* Repli sans `subgrid` : les mêmes colonnes, chaque ligne pour elle. Les
     noms ne s'alignent alors plus entre eux, mais rien ne casse. */
  .project {
    grid-template-columns:
      auto auto minmax(0, max-content) minmax(0, 1.35fr);
    column-gap: clamp(1.4rem, 3.2vw, 2.8rem);
  }

  @supports (grid-template-columns: subgrid) {
    .project {
      grid-column: 1 / -1;
      grid-template-columns: subgrid;
    }
  }
}

/*
   LA CROIX DE REPÉRAGE, devant le numéro.

   ⚠️ IL Y A EU ICI UN FILET VERTICAL AVEC UNE PASTILLE PAR PROJET. Retiré :
   c'était un objet inventé pour cette section, et il ne se retrouvait nulle
   part ailleurs sur le site. La croix, elle, est le repère de la maison : les
   bandes de « Nos services » (`.lane__mark`), les repères de « Où nous
   trouver » (`.note__cross`), les colonnes de tarifs (`.price::after`). Même
   dessin, même rouge, même taille de 12 px.

   Elle est dessinée en deux dégradés plutôt qu'en deux pseudo-éléments : le
   numéro a besoin de son `::before`, et cela évite d'ajouter une balise vide
   dans le HTML pour un trait.

   Elle ne bouge pas et ne change pas au survol. C'est l'ouverture de la ligne
   qui répond au pointeur, et une géométrie animée à cet endroit se lirait
   comme un défaut d'affichage ; voir la note de `.lane__go`.
*/
.project__index {
  position: relative;
  padding-top: 0.2rem;
  padding-left: 1.6rem;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--terra-text);
}

.project__index::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.5rem;
  width: 12px;
  height: 12px;
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}

/*
   LA VIGNETTE. Largeur FIXE et volontairement petite : c'est un repère visuel,
   pas une illustration. La hauteur suit toute seule par le rapport 9/16.

   ⚠️ `height: auto` est obligatoire : les attributs width/height du HTML sont
   des indications de présentation, donc `height="960"` s'appliquerait comme
   du CSS et la vignette ferait 960 px de haut.

   Noir et blanc au repos, couleur au survol : c'est la ligne survolée qui
   s'anime, pas la page entière.

   ⚠️ Les fichiers `fictif-0N.jpg` NE SONT PAS des captures de projet. Voir
   assets/img/projets/LISEZMOI.txt pour les remplacer.
*/
/*
   ⚠️ LE CADRE NE GRANDIT PLUS, L'IMAGE GRANDIT DEDANS. Le cadre montait à
   1,22 et l'image à 1,06 par-dessus : le geste était franc, mais le cadre
   débordait alors de sa ligne en hauteur, et il fallait ouvrir la ligne pour
   lui faire de la place — c'est-à-dire déplacer toute la page. Voir la note
   de `.project::after`.

   Le cadre est donc plus grand au repos (6,4 rem contre 5) et il ne bouge
   plus. L'image, elle, grandit toujours à l'intérieur, et `overflow: hidden`
   la retient. Le geste se lit autant, et il ne coûte rien.

   ⚠️ ET SURTOUT PAS UNE LARGEUR ANIMÉE. La vignette occupe une colonne
   partagée par toutes les lignes (`subgrid`) : élargir vraiment le cadre
   élargirait la colonne de TOUTES les lignes d'un coup.

   L'arrondi est à 2 px, comme les champs de formulaire : le site n'a plus
   d'angle appuyé nulle part.
*/
/*
   ⚠️ EN COLONNE, LA VIGNETTE PREND TOUTE LA LARGEUR. Elle mesurait 102 px sur
   un téléphone : sur un portfolio, la photo est le propos, pas une pastille
   posée à côté du texte. Au-delà de 900 px elle redevient une vignette, parce
   qu'elle y tient sa colonne dans un relevé qu'on parcourt.

   Le rapport 3/4 tient la hauteur : sans lui, une image encore en cours de
   chargement ferait sauter toute la page à son arrivée.
*/
.project__frame {
  position: relative;
  display: block;
  overflow: hidden;
  width: 100%;
  max-width: 26rem;
  aspect-ratio: 1 / 1;
  border-radius: 2px;
  background: var(--bg-3);
}

@media (min-width: 900px) {
  .project__frame {
    width: clamp(5.4rem, 7vw, 6.4rem);
    max-width: none;
    aspect-ratio: auto;
  }
}

/*
   LES QUATRE ÉQUERRES, POSÉES SUR L'IMAGE. C'est le repère de coupe du
   photographe et la signature de survol du site — les boutons et les liens
   portent les mêmes. Ici elles sont DEDANS et non autour : le cadre découpe
   ce qui le dépasse, des équerres posées à l'extérieur seraient rognées.

   Dedans, elles disent d'ailleurs mieux ce qu'elles sont : un viseur cadre
   l'image, il ne l'entoure pas.

   Même dessin que `.btn::before` : huit dégradés d'un pixel, deux par coin.
*/
.project__frame::after {
  content: '';
  position: absolute;
  inset: 6px;
  pointer-events: none;
  background:
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 0 0 / 2px 14px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 0 0 / 14px 2px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 100% 0 / 2px 14px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 100% 0 / 14px 2px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 0 100% / 2px 14px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 0 100% / 14px 2px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 100% 100% / 2px 14px,
    linear-gradient(var(--terra-contrast), var(--terra-contrast)) 100% 100% / 14px 2px;
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}

.project:hover .project__frame::after,
.project:focus-within .project__frame::after {
  opacity: 1;
  transform: none;
}

.project__shot {
  width: 100%;
  height: auto;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  /* ⚠️ DÉSATURÉE, PAS NOIR ET BLANC. Le repos était à `grayscale(1)` : la
     couleur ne se voyait qu'au survol, donc jamais sur une capture, jamais
     au premier coup d'œil, et jamais du tout au doigt. À 0,55 la dominante
     de chaque vignette se lit tout de suite, et le retour en couleur au
     survol reste un vrai geste. Mettez `1` pour le noir et blanc intégral,
     `0` pour la couleur pleine. */
  filter: grayscale(0.55) contrast(1.04);
  transition: transform 0.7s var(--ease), filter 0.5s var(--ease);
}

/* L'image grandit dans son cadre, qui la retient. C'est le seul
   agrandissement du geste depuis que le cadre ne bouge plus, donc il est plus
   ample qu'avant : 1,12 contre 1,06. */
.project:hover .project__shot,
.project:focus-within .project__shot {
  transform: scale(1.12);
  filter: grayscale(0) contrast(1);
}

/*
   Le texte avance d'un demi-rem. Il s'écartait autrefois d'une vignette qui
   venait vers lui ; la vignette ne bouge plus, et le geste reste — c'est ce
   qui fait que la ligne entière répond, et pas seulement l'image.

   ⚠️ C'est un `transform` : il ne coûte pas une ligne de mise en page. Ne le
   remplacez pas par une marge.
*/
.project__name,
.project__body {
  transition: transform 0.55s var(--ease);
}

.project:hover .project__name,
.project:focus-within .project__name,
.project:hover .project__body,
.project:focus-within .project__body {
  transform: translateX(0.5rem);
}

/* Aucun mouvement pour qui n'en veut pas — l'image passe quand même en
   couleur, et la ligne s'ouvre quand même. */
@media (prefers-reduced-motion: reduce) {
  .project:hover .project__shot,
  .project:focus-within .project__shot,
  .project:hover .project__name,
  .project:focus-within .project__name,
  .project:hover .project__body,
  .project:focus-within .project__body {
    transform: none;
  }

  /* Les équerres et le filet rouge restent : ce sont des couleurs qui
     apparaissent, pas des choses qui bougent. On leur retire seulement leur
     mise à l'échelle. */
  .project__frame::after {
    transform: none;
  }
}

.project__name {
  display: grid;
  gap: 0.5rem;
  align-content: start;
}

/*
   LE SECTEUR, SOUS LE NOM. Il vient de la ligne de métadonnées, où il était le
   premier des trois repères. Les deux autres décrivent le TRAVAIL — « Instagram
   & TikTok », « Record à 650 000 vues » ; celui-là dit QUI est le client. Il
   appartient au nom.

   Le déplacement répare aussi un déséquilibre : la colonne du nom ne portait
   qu'un titre et du vide en dessous, pendant que la colonne d'à côté empilait
   trois blocs. C'est ce vide qui faisait paraître la ligne inachevée.
*/
.project__secteur {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--terra-text);
}

.project h3 {
  font-size: clamp(1.35rem, 2.4vw, 1.8rem);
  line-height: 1.05;
}

/* Le premier projet de chaque page est le plus fort : il passe en grand, sans
   changer de gabarit ni sortir de la grille. */
.project:first-child h3 {
  font-size: clamp(1.6rem, 3vw, 2.2rem);
}

/* L'écart entre les trois blocs du récit s'ouvre avec eux : à corps égal,
   l'ancien 0,6 rem collait la phrase à la ligne de repères au-dessus. */
.project__body {
  display: grid;
  gap: 0.75rem;
  align-content: start;
}

/* ⚠️ `--ink-2`, PAS `--ink-3`. Ces deux repères disent ce qui a été fait et
   ce que ça a donné — « Record à 650 000 vues » est l'argument le plus fort
   de la ligne. En gris pâle et en petites capitales, il passait pour une
   mention de bas de page. */
.project__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* Un point de séparation dessiné plutôt qu'écrit : il ne se copie pas avec le
   texte et disparaît proprement quand la ligne se replie. */
.project__meta span + span::before {
  content: '';
  display: inline-block;
  width: 3px;
  height: 3px;
  margin-right: 1rem;
  margin-bottom: 0.2em;
  border-radius: 50%;
  background: var(--line-strong);
  vertical-align: middle;
}

/*
   ⚠️ LE RÉCIT EST LE DEUXIÈME NIVEAU DE LECTURE, ET IL DOIT SE VOIR COMME TEL.
   C'est la seule phrase de la ligne : après le nom, c'est ce qu'on lit. Il est
   passé par 0,9 puis 0,95 rem en `--ink-2` — à chaque fois trop proche des
   petites capitales qui l'entourent, donc noyé dedans.

   À 1,02 rem et à l'encre pleine, l'écart avec les repères de niveau 3 est
   franc : un tiers de corps de plus, et le seul texte de la ligne en bas de
   casse. C'est ce contraste-là qui fait la hiérarchie, pas la couleur.

   `max-width` en `ch` plutôt qu'en `rem` : la mesure suit la chasse réelle de
   la police, donc la ligne garde sa longueur de lecture si le corps bouge.
*/
.project__desc {
  max-width: 62ch;
  color: var(--ink);
  font-size: 1.02rem;
  line-height: 1.75;
}

/*
   LES LIENS. C'est le seul endroit du site où un visiteur peut aller voir le
   travail lui-même plutôt que d'en lire le résumé. C'est aussi ce qui remplace
   la colonne de vignettes : un compte vivant vaut mieux qu'une image figée.

   ⚠️ Un lien SANS `href` n'est ni cliquable ni atteignable au clavier : c'est
   volontairement un emplacement en attente, pas un lien mort. Le jour où
   l'adresse est connue, on ajoute `href` et on retire `data-todo` — le style
   actif prend le relais tout seul.
*/
/*
   LES MÉTIERS, AU BOUT DE LA LIGNE. Un projet peut en relever de plusieurs :
   Novalla et MVLF portent « réseaux sociaux » et « photo », MyPulse « photo »
   et « sites web ». Ils sont groupés dans un conteneur, sinon ils partiraient
   chacun de leur côté.

   Alignés à droite et repliés ensemble quand la place manque : la largeur
   maximale force le repli à deux lignes plutôt que de laisser la colonne
   s'étirer et manger le récit.
*/
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/*
   ⚠️ LE MÉTIER EST LA DEUXIÈME CHOSE QU'ON LIT SUR LA LIGNE, PAS LA DERNIÈRE.
   Il avait le corps, la graisse et la couleur de la ligne de métadonnées à
   l'autre bout de la ligne : rien ne disait que c'était la réponse à
   « qu'est-ce que vous avez fait pour eux ». Il passe à la police de titre,
   en gras et à l'encre pleine — le nom du client d'abord, le travail ensuite,
   le récit en dessous.

   ⚠️ ET CETTE RÈGLE RESTE HORS DE TOUTE REQUÊTE DE MÉDIA. Elle a d'abord été
   posée dans le bloc de la colonne de droite : en dessous du seuil, donc sur
   toute tablette et tout téléphone, le métier redevenait une petite capitale
   mono. Ce qu'il dit ne dépend pas de la largeur de l'écran ; seul l'endroit
   où il se pose en dépend.

   La croix rouge, elle, ne change pas : c'est elle qui relie cette colonne à
   celle des numéros, à l'autre bout de la même ligne.
*/
.project .tag {
  gap: 0.7rem;
  font-family: var(--font-title);
  font-size: 0.87rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--ink);
}

/*
   ⚠️ LES MÉTIERS NE PASSENT À DROITE QU'À PARTIR DE 1300 PX. Sous ce seuil,
   la cinquième colonne étrangle le récit — il ne lui restait que 230 px à
   1000 px, et 290 px à 1200 px : la description tombait sur six lignes et sa
   ligne de métadonnées s'empilait en escalier. En dessous du seuil, les
   métiers passent simplement sous le texte, au fer à gauche.

   ⚠️ LE SEUIL SUIT LA LARGEUR DE LA COLONNE. Il était à 1150 px tant que le
   métier tenait en petites capitales mono ; « GESTION DES RÉSEAUX » en gras
   est plus large, et l'étranglement revenait à 1200 px. Si vous rallongez ces
   libellés, remontez le seuil avec eux.
*/
@media (min-width: 900px) {
  .tags {
    grid-column: 4 / -1;
    padding-top: 0.9rem;
  }
}

/*
   ⚠️ `.project` NE DOIT JAMAIS RECEVOIR DE GABARIT EXPLICITE QUAND LE
   NAVIGATEUR SAIT FAIRE `subgrid`. Cette règle visait `.projects, .project`
   d'un seul tenant : elle reposait sur chaque ligne un gabarit à elle, qui
   écrasait le `subgrid` déclaré au-dessus. Chaque ligne dimensionnait alors sa
   colonne de nom sur SON nom, et les récits partaient de trois fers différents
   — « La Kebaberie » à 605 px, « Novalla SA » à 535 px.

   Les colonnes se déclarent sur le conteneur. Les lignes les empruntent.
*/
@media (min-width: 1300px) {
  .projects {
    grid-template-columns:
      auto auto minmax(0, max-content) minmax(0, 1.35fr) minmax(0, max-content);
  }

  /* Repli sans `subgrid` seulement : les mêmes colonnes, chaque ligne pour
     elle. Les noms ne s'alignent alors plus entre eux, mais rien ne casse. */
  .project {
    grid-template-columns:
      auto auto minmax(0, max-content) minmax(0, 1.35fr) minmax(0, max-content);
  }

  @supports (grid-template-columns: subgrid) {
    .project {
      grid-template-columns: subgrid;
    }
  }

  /* ⚠️ UN MÉTIER PAR LIGNE, TOUJOURS. En rangée, deux métiers courts
     tenaient côte à côte quand deux métiers longs passaient à la ligne : la
     colonne de droite n'avait plus la même forme d'un projet à l'autre. En
     colonne, tous les projets se lisent pareil.

     ⚠️ ET AU FER À GAUCHE, PAS À DROITE. Alignées à droite, les croix se
     décalaient d'une étiquette à l'autre selon la longueur du mot. Au fer à
     gauche elles tombent toutes sur le même axe, comme les croix et les
     numéros à l'autre bout de la ligne. La colonne, elle, reste cadrée à
     droite de la grille : c'est sa largeur qui la tient là. */
  .tags {
    grid-column: auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.65rem;

    /*
       ⚠️ LE FILET FAIT DE LA COLONNE UN CHAMP, ET NON UNE REMARQUE EN MARGE.
       C'est le même trait vertical que les quatre colonnes du pied de page et
       que les repères : du vocabulaire déjà posé ailleurs, pas un objet
       inventé pour cette section. `align-self: stretch` le fait courir sur
       toute la hauteur de la ligne — sans quoi il ne mesurerait que la
       hauteur des deux mots et se lirait comme un accident.
    */
    align-self: stretch;
    padding-top: 0.3rem;
    padding-left: clamp(1.4rem, 2.6vw, 2.2rem);
    border-left: 1px solid var(--line);
  }

}

/* Le rouge de la fin de ligne est maintenant porté par la croix de
   l'étiquette, en face de la croix et du numéro qui ouvrent la ligne. Le mot,
   lui, reste à l'encre du site : deux rouges superposés au même endroit,
   c'était un de trop. */

/*
   ⚠️ SOUS 620 PX, LA LIGNE DE MÉTADONNÉES DEVIENT UNE COLONNE — et c'est la
   pastille qui l'impose. Elle sépare deux mentions posées côte à côte ; dès
   que la ligne se replie, celle de la seconde mention ouvre la ligne suivante
   et se lit comme une puce orpheline : « · RECORD À 650 000 VUES ». En
   colonne il n'y a plus rien à séparer horizontalement, donc plus de pastille.
*/
@media (max-width: 620px) {
  .project__meta {
    flex-direction: column;
    gap: 0.15rem;
  }

  .project__meta span + span::before {
    display: none;
  }
}

.project__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.6rem;
  margin-top: 0.35rem;
}

.project__link {
  position: relative;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--terra-text);
  transition: color 0.3s var(--ease);
}

/* La petite flèche penchée : elle dit qu'on quitte le site, ce qu'un
   soulignement ne dit pas. Elle est portée aussi par les emplacements en
   attente — sans elle, la ligne se lisait comme une deuxième ligne de
   métadonnées et non comme des liens. */
.project__link::before {
  content: '↗';
  margin-right: 0.35rem;
  font-size: 0.9em;
}

.project__link::after {
  content: '';
  position: absolute;
  inset: auto 0 -3px;
  height: 1px;
  background: var(--terra-deep);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}

.project__link:hover::after,
.project__link:focus-visible::after {
  transform: scaleX(1);
}

.project__link[data-todo] {
  color: var(--ink-3);
  cursor: default;
}

.project__link[data-todo]::after {
  display: none;
}

/* Appel à l'action -------------------------------------------------------- */

/*
   ⚠️ CE BLOC EST CLAIR, ET C'EST TOUT SON INTÉRÊT. Il a d'abord existé en
   carte noire, et il a été retiré pour ça : il fermait les pages sur du noir,
   à l'époque où le pied de page était blanc. Le pied de page est noir depuis,
   et c'est l'inverse qu'il faut — un dernier bloc clair avant lui, sinon deux
   noirs se touchent et la page paraît s'arrêter deux fois.

   ⚠️ IL A ENSUITE ÉTÉ UNE CARTE GRISE À COINS ARRONDIS, CENTRÉE. Abandonnée :
   il n'y a rien de gris, rien de centré et rien d'arrondi nulle part ailleurs
   sur ce site. C'était l'objet le moins ONOLI de toutes les pages, et il
   fermait chacune d'elles.

   Ce qui le tient maintenant : une bande claire d'un bord à l'autre, deux
   filets, la croix rouge posée sur le filet du haut, et le relevé
   topographique qui dérive lentement derrière — la seule chose qui bouge, et
   elle vient du fond même du site.

   ⚠️ C'EST LE SEUL BLOC CENTRÉ DU SITE, ET C'EST VOULU. Tout le reste est au
   fer à gauche : les titres, les repères, les colonnes de tarifs, le relevé
   des réalisations. Ici il ne s'agit pas d'un contenu à parcourir mais d'une
   adresse au visiteur, la dernière avant le pied de page — elle se tient au
   milieu, et le regard n'a plus nulle part à aller ensuite. La croix, elle,
   reste au coin : elle marque le filet, pas le texte.
*/
.cta {
  position: relative;
  /*
     `isolation: isolate` est indispensable : la bande claire est un `::before`
     en `z-index: -1`, qui doit passer derrière le texte mais devant le fond de
     la page. Même montage que le voile rouge des bandes du sommaire, voir
     `.lane::before`.
  */
  isolation: isolate;
  padding-block: clamp(3.5rem, 8vw, 6rem);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/*
   La bande va d'un bord à l'autre de la page, bornée au pixel : `50%` est la
   demi-largeur du bloc, `(100vw - --rail-w) / 2` la demi-largeur de la zone de
   contenu, bande rouge déduite. Voir la note complète de « Bandes opaques ».
*/
.cta::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0 calc(50% - (100vw - var(--rail-w)) / 2);
  background: var(--bg);
}

/* La croix de repérage, posée sur le filet du haut. La même qu'aux colonnes de
   tarifs, aux repères de « Où nous trouver » et aux bandes du sommaire. */
.cta::after {
  content: '';
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  width: 13px;
  height: 13px;
  transform: translate(-50%, -50%);
  background:
    linear-gradient(var(--terra), var(--terra)) center / 1px 100% no-repeat,
    linear-gradient(var(--terra), var(--terra)) center / 100% 1px no-repeat;
}

/*
   LE RELEVÉ QUI DÉRIVE, C'EST L'ANIMATION DU BLOC. Le calque `.topo` existait
   déjà dans le HTML mais ses courbes étaient noyées sous la carte grise. Il
   court maintenant sur toute la largeur de la bande, un peu plus marqué qu'au
   fond de la page, et ses trois groupes dérivent à des vitesses différentes —
   voir `@keyframes topo-drift`.

   Il s'arrête tout seul pour qui a demandé à son système de réduire les
   animations : la règle est portée par `.topo g`.
*/
.cta .topo {
  inset: 0 calc(50% - (100vw - var(--rail-w)) / 2);
}

.cta .topo path {
  stroke: color-mix(in srgb, var(--terra) 30%, transparent);
}

.cta__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 1.2rem;
  justify-items: center;
  text-align: center;
  max-width: 46rem;
  margin-inline: auto;
}

.cta h2 {
  font-size: clamp(2.1rem, 5vw, 3.6rem);
}

.cta p {
  max-width: 38rem;
  color: var(--ink-2);
}

/* Le titre ne dépasse pas trois lignes courtes : au-delà, une phrase centrée
   se lit mal parce que l'œil doit retrouver le début de chaque ligne. */
.cta h2 {
  max-width: 20ch;
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(1.25rem, 2.4vw, 1.9rem);
  margin-top: 1.1rem;
}

/* Pages légales ------------------------------------------------------------

   Mentions légales et protection des données : deux pages de texte pur, sans
   décor ni carte. La largeur est bornée en `ch` et non en `rem` — sur un long
   texte, c'est le nombre de caractères par ligne qui décide de la lisibilité,
   pas la largeur en pixels. Soixante-huit est le haut de la fourchette
   confortable ; au-delà, l'œil rate le début de la ligne suivante.
*/
.legal {
  display: grid;
  gap: 1.1rem;
  max-width: 68ch;
}

.legal h2 {
  margin-top: clamp(1.5rem, 3vw, 2.3rem);
  font-size: clamp(1.2rem, 2.1vw, 1.55rem);
}

.legal h2:first-child {
  margin-top: 0;
}

.legal p,
.legal li {
  color: var(--ink-2);
  font-size: 0.98rem;
  line-height: 1.75;
}

.legal ul {
  display: grid;
  gap: 0.7rem;
  padding-left: 1.15rem;
  list-style: disc;
}

.legal li::marker {
  color: var(--terra-text);
}

.legal b {
  color: var(--ink);
  font-weight: 600;
}

.legal a {
  color: var(--terra-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Ce qui reste à remplir avant publication : raison sociale, IDE, hébergeur.
   En rouge et entre crochets pour qu'on ne puisse pas mettre le site en ligne
   sans les voir — c'est un garde-fou, pas une décoration. */
.legal__todo {
  color: var(--terra-text);
  font-weight: 600;
}

.legal__updated {
  margin-top: clamp(1.8rem, 3.5vw, 2.6rem);
  padding-top: 1.15rem;
  border-top: 1px solid var(--line);
  font-size: 0.82rem;
  color: var(--ink-3);
}

/* ----------------------------------------------- 09. FORMULAIRES ------- */

.form {
  display: grid;
  gap: 1.15rem;
}

/*
   ⚠️ LE DEVIS TIENT SUR UNE SEULE GRILLE, PLEINE LARGEUR. Il a existé en deux
   colonnes côte à côte : c'était plus court, mais les deux colonnes ne
   partageaient AUCUNE ligne — cinq blocs d'un côté, trois de l'autre, et rien
   qui tombe en face de rien. Le regard n'avait plus de repère horizontal.

   Sur une grille unique, chaque paire de champs se pose sur les mêmes deux
   colonnes que la paire du dessus, et les groupes de cases s'étalent sur six
   colonnes au lieu de deux. Si vous remettez deux colonnes, il faudra leur
   faire partager leurs rangées, sans quoi le défaut revient.
*/
.form__row {
  display: grid;
  align-items: start;
  gap: 1.15rem;
}

@media (min-width: 640px) {
  .form__row {
    grid-template-columns: 1fr 1fr;
  }
}

.field {
  display: grid;
  gap: 0.45rem;
}

.field > label,
.fieldset > legend {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.85rem 1rem;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--terra);
  box-shadow: 0 0 0 3px var(--terra-soft);
}

.field textarea {
  min-height: 7rem;
  resize: vertical;
}

.hint {
  font-size: 0.84rem;
  color: var(--ink-3);
}

.fieldset {
  border: 0;
  padding: 0;
  margin: 0;
}

.fieldset > legend {
  margin-bottom: 0.55rem;
}

.radios {
  display: grid;
  gap: 0.7rem;
}

@media (min-width: 520px) {
  .radios {
    grid-template-columns: 1fr 1fr;
  }
}

/* Les cases à cocher d'un devis : mêmes pastilles que les radios, mais en
   colonnes plus étroites — on en coche plusieurs, la liste est plus longue,
   et deux colonnes larges la feraient déborder sous le pli. */
.radios--tight {
  gap: 0.55rem;
}

@media (min-width: 520px) {
  .radios--tight {
    grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  }
}

.radio {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.85rem 1.05rem;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  cursor: pointer;
  font-size: 0.92rem;
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}

.radio:has(input:checked) {
  border-color: var(--terra);
  background: var(--terra-soft);
}

.radio input {
  width: 1rem;
  height: 1rem;
  accent-color: var(--terra);
}

.radio svg {
  width: 1.15rem;
  height: 1.15rem;
  color: var(--terra);
}

.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__submit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
}

.form__status {
  display: none;
  padding: 0.85rem 1.05rem;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  font-size: 0.9rem;
}

.form__status[data-tone] {
  display: block;
}

.form__status[data-tone='ok'] {
  border-color: var(--terra);
  background: var(--terra-soft);
  color: var(--terra-deep);
}

.form__status[data-tone='error'] {
  border-color: #c0392b;
  background: rgba(192, 57, 43, 0.08);
}

.form__status a {
  text-decoration: underline;
}

/* -------------------------------------------------- 11. FOOTER --------- */

/*
   LE PIED DE PAGE EST NOIR, et c'est ce qui ferme le site : la page alterne
   blanc et noir de section en section, elle devait finir sur le noir. Il
   portait un fond blanc, qui laissait la page se terminer en creux.

   Il n'écrit aucune couleur en dur : `section--invert`, posé sur la balise,
   redéfinit les jetons du thème pour ce bloc — exactement comme pour les
   panneaux du sommaire. Le pied de page est donc identique dans les deux
   thèmes, ce qui est juste : un aplat noir est un aplat noir.

   ⚠️ Pas de `margin-top` ici, et c'est délibéré. Une marge est transparente :
   elle laisserait voir le fond animé entre la dernière section et le pied de
   page, comme une troisième bande. Sans marge, le noir attaque net. L'air
   est repris par le `padding-block` de `.footer__inner`.
*/
.footer {
  position: relative;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  /* Un filet, pour les pages qui finissent déjà sur un panneau noir : sans
     lui, le pied de page et la dernière section n'en font plus qu'un. */
  border-top: 1px solid var(--line);
}

.footer__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 2.4rem;
  padding-block: clamp(2.6rem, 4.5vw, 4rem) clamp(1.8rem, 3vw, 2.5rem);
}

/*
   Quatre colonnes : la marque, puis trois listes. Les filets verticaux les
   séparent au lieu d'un simple écart — c'est ce qui donne au pied de page
   son air de fiche technique plutôt que de bas de page.
*/
@media (min-width: 900px) {
  .footer__inner {
    /* ⚠️ QUATRE PISTES ÉGALES, PAS QUATRE LARGEURS AU JUGÉ. Le réglage
       précédent — 1,5 / 1 / 1,2 / 1,3 — donnait quatre filets verticaux à
       des intervalles irréguliers sous une page réglée au tiers.

       ⚠️ ET AUCUNE GOUTTIÈRE, POUR LA MÊME RAISON QUE `.notes` N'EN A PAS.
       Le pied de page en portait une de 56 px. Ses quatre pistes restaient
       égales entre elles, mais elles ne faisaient plus la même largeur que
       celles d'une grille sans gouttière : ses filets se décalaient donc de
       14, 28 puis 42 px par rapport à ceux des canaux, juste au-dessus.

           canaux   132   442   752   1062     pas de 310
           pied     132   456   780   1104     pas de 324

       Sans gouttière, l'écart entre colonnes est obtenu par le retrait
       intérieur ci-dessous — et c'est ce retrait, identique à celui des
       repères, qui met aussi les TEXTES des deux grilles sur le même axe. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 0;
  }

  /* Même retrait que `.note` : les deux grilles se lisent sur les mêmes
     axes, filets compris. */
  .footer__inner > * + * {
    padding-left: clamp(1.6rem, 3vw, 2.8rem);
    border-left: 1px solid var(--line);
  }
}

.footer .logo__svg {
  height: 38px;
}

.footer__brand p {
  max-width: 22rem;
  margin-top: 1.3rem;
  /* La même taille que les trois listes à côté : quatre colonnes, une seule
     échelle de texte. */
  font-size: 0.92rem;
  line-height: 1.75;
  color: var(--ink-2);
}

/* Le filet rouge sous la signature : le même que sous les coordonnées des
   repères de l'accueil. */
.footer__tick {
  display: block;
  width: 32px;
  height: 2px;
  margin-top: 1.4rem;
  background: var(--terra);
}

.footer h4 {
  margin-bottom: 1.1rem;
  /* ⚠️ EN CAPITALES, ILS ÉTAIENT À 11,8 px. Les capitales n'ont pas de
     jambages : à taille égale elles se repèrent moins bien qu'un bas de
     casse. Un intitulé de colonne doit se trouver d'un coup d'œil. */
  font-size: 0.79rem;
  font-weight: 700;
  /* Inter demande moins d'interlettrage que la chasse fixe en capitales. */
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/*
   ⚠️ TOUT LE PIED DE PAGE EST EN INTER, COORDONNÉES COMPRISES. Une première
   version n'avait changé que la navigation et gardait la chasse fixe sur les
   deux colonnes de droite, au motif qu'une adresse et un numéro sont des
   DONNÉES. L'argument tient en théorie et passe à côté du problème : la mono
   a quitté le site parce qu'à petite taille elle se lit mal, et le pied de
   page porte le plus petit texte de toute la page. C'était l'endroit le plus
   concerné, pas l'exception.

   ⚠️ LA MONO NE QUITTE PAS LE SITE POUR AUTANT. Surtitres, montants, méta de
   projet, étiquettes : l'identité tient à ces marques-là. Un bas de page n'a
   jamais porté l'identité de personne.

   ⚠️ ET INTER LIBÈRE DE LA LARGEUR, ce qui compte ici : à taille égale elle
   est sensiblement plus étroite que la chasse fixe. Les quatre colonnes
   respirent davantage qu'avant malgré le texte agrandi.
*/
.footer {
  font-family: var(--font-title);
}

.footer__nav ul {
  font-weight: 500;
}

.footer ul {
  display: grid;
  gap: 0.7rem;
  /* Le plus petit texte suivi du site : il remonte avec le reste. */
  font-size: 0.92rem;
}

/* Les listes à pictogramme : l'icône tient sa colonne, le texte s'aligne
   dessus même quand il court sur trois lignes. */
.footer__list li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.75rem;
  align-items: start;
  line-height: 1.6;
}

.footer__ico {
  width: 17px;
  height: 17px;
  margin-top: 0.15rem;
  color: var(--terra-text);
}

.footer a {
  transition: color 0.25s var(--ease);
}

.footer a:hover {
  color: var(--terra-text);
}

.footer__bottom {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1.15rem;
  border-top: 1px solid var(--line);
  /* Le copyright et les deux liens légaux : le dernier texte du site, et
     celui qu'on lisait le plus mal. */
  font-size: 0.84rem;
  color: var(--ink-3);
}

/* Les liens légaux se rangent au milieu du bas de page. Ils héritent de la
   taille et de la couleur de `.footer__bottom` : rien ne doit les faire
   ressortir, mais rien ne doit non plus les rendre illisibles. */
.footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
}

.footer__legal a {
  color: inherit;
}

/* ------------------------------------------- 12. ANIMATIONS ------------ */

/* L'état masqué n'est appliqué que si JavaScript tourne (la classe .js est
   posée par le script du <head>). Sans lui, tout le contenu reste visible :
   une page ne peut jamais rester blanche à cause d'une animation. */
.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
  transition-delay: var(--delay, 0ms);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Variante qui n'anime que l'opacité : réservée aux éléments dont la
   transformation sert déjà à autre chose — ici le parallaxe du hero. */
.js .reveal--fade {
  opacity: 0;
  transition: opacity 0.9s var(--ease);
  transition-delay: var(--delay, 0ms);
}

.js .reveal--fade.is-visible {
  opacity: 1;
}

/* Aucune animation si le visiteur a demandé à les réduire. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js .reveal,
  .js .reveal--fade {
    opacity: 1;
    transform: none;
  }
  /* Pas de parallaxe non plus : main.js laisse --p à 0. */
}
